active_canvas 0.0.1

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +318 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
  6. data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
  7. data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
  8. data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
  9. data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
  10. data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
  11. data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
  12. data/app/assets/javascripts/active_canvas/editor.js +295 -0
  13. data/app/assets/stylesheets/active_canvas/application.css +15 -0
  14. data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
  15. data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
  16. data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
  17. data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
  18. data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
  19. data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
  20. data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
  21. data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
  22. data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
  23. data/app/controllers/active_canvas/application_controller.rb +20 -0
  24. data/app/controllers/active_canvas/pages_controller.rb +18 -0
  25. data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
  26. data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
  27. data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
  28. data/app/helpers/active_canvas/application_helper.rb +4 -0
  29. data/app/jobs/active_canvas/application_job.rb +4 -0
  30. data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
  31. data/app/mailers/active_canvas/application_mailer.rb +6 -0
  32. data/app/models/active_canvas/ai_model.rb +136 -0
  33. data/app/models/active_canvas/application_record.rb +5 -0
  34. data/app/models/active_canvas/media.rb +141 -0
  35. data/app/models/active_canvas/page.rb +85 -0
  36. data/app/models/active_canvas/page_type.rb +22 -0
  37. data/app/models/active_canvas/page_version.rb +80 -0
  38. data/app/models/active_canvas/partial.rb +73 -0
  39. data/app/models/active_canvas/setting.rb +292 -0
  40. data/app/services/active_canvas/ai_configuration.rb +40 -0
  41. data/app/services/active_canvas/ai_models.rb +128 -0
  42. data/app/services/active_canvas/ai_service.rb +289 -0
  43. data/app/services/active_canvas/content_sanitizer.rb +112 -0
  44. data/app/services/active_canvas/tailwind_compiler.rb +156 -0
  45. data/app/views/active_canvas/admin/media/index.html.erb +401 -0
  46. data/app/views/active_canvas/admin/media/show.html.erb +297 -0
  47. data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
  48. data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
  49. data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
  50. data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
  51. data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
  52. data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
  53. data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
  54. data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
  55. data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
  56. data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
  57. data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
  58. data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
  59. data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
  60. data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
  61. data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
  62. data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
  63. data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
  64. data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
  65. data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
  66. data/app/views/active_canvas/pages/show.html.erb +113 -0
  67. data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
  68. data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
  69. data/app/views/layouts/active_canvas/application.html.erb +55 -0
  70. data/config/routes.rb +48 -0
  71. data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
  72. data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
  73. data/lib/active_canvas/configuration.rb +232 -0
  74. data/lib/active_canvas/engine.rb +44 -0
  75. data/lib/active_canvas/version.rb +3 -0
  76. data/lib/active_canvas.rb +26 -0
  77. data/lib/generators/active_canvas/install/install_generator.rb +263 -0
  78. data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
  79. data/lib/tasks/active_canvas_tasks.rake +69 -0
  80. metadata +150 -0
@@ -0,0 +1,333 @@
1
+ <% content_for :page_title, "#{@page.title} - Version History" %>
2
+
3
+ <style>
4
+ .version-timeline {
5
+ position: relative;
6
+ padding-left: 2rem;
7
+ }
8
+ .version-timeline::before {
9
+ content: '';
10
+ position: absolute;
11
+ left: 0.5rem;
12
+ top: 0;
13
+ bottom: 0;
14
+ width: 2px;
15
+ background: var(--border);
16
+ }
17
+ .version-item {
18
+ position: relative;
19
+ padding-bottom: 1.5rem;
20
+ }
21
+ .version-item:last-child {
22
+ padding-bottom: 0;
23
+ }
24
+ .version-dot {
25
+ position: absolute;
26
+ left: -1.75rem;
27
+ top: 0.25rem;
28
+ width: 1rem;
29
+ height: 1rem;
30
+ background: var(--primary);
31
+ border-radius: 50%;
32
+ border: 3px solid var(--bg-card);
33
+ box-shadow: 0 0 0 2px var(--primary);
34
+ }
35
+ .version-dot.first {
36
+ background: var(--success);
37
+ box-shadow: 0 0 0 2px var(--success);
38
+ }
39
+ .version-card {
40
+ background: var(--bg-card);
41
+ border: 1px solid var(--border);
42
+ border-radius: var(--radius);
43
+ padding: 1rem;
44
+ transition: box-shadow 0.15s ease;
45
+ }
46
+ .version-card:hover {
47
+ box-shadow: var(--shadow-md);
48
+ }
49
+ .version-header {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ margin-bottom: 0.75rem;
54
+ }
55
+ .version-number {
56
+ font-weight: 600;
57
+ font-size: 0.9375rem;
58
+ color: var(--text);
59
+ display: inline-flex;
60
+ align-items: center;
61
+ transition: color 0.15s ease;
62
+ }
63
+ .version-number:hover {
64
+ color: var(--primary);
65
+ }
66
+ .version-number:hover svg {
67
+ opacity: 1;
68
+ }
69
+ .version-time {
70
+ font-size: 0.75rem;
71
+ color: var(--text-muted);
72
+ }
73
+ .version-meta {
74
+ display: flex;
75
+ gap: 1rem;
76
+ flex-wrap: wrap;
77
+ font-size: 0.8125rem;
78
+ color: var(--text-muted);
79
+ }
80
+ .version-meta-item {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 0.375rem;
84
+ }
85
+ .version-meta-item svg {
86
+ width: 14px;
87
+ height: 14px;
88
+ opacity: 0.7;
89
+ }
90
+ .version-changes {
91
+ margin-top: 0.75rem;
92
+ padding-top: 0.75rem;
93
+ border-top: 1px solid var(--border);
94
+ }
95
+ .version-diff {
96
+ margin-top: 0.75rem;
97
+ }
98
+ .version-diff-toggle {
99
+ font-size: 0.75rem;
100
+ color: var(--primary);
101
+ background: none;
102
+ border: none;
103
+ cursor: pointer;
104
+ padding: 0;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 0.25rem;
108
+ }
109
+ .version-diff-toggle:hover {
110
+ text-decoration: underline;
111
+ }
112
+ .version-diff-content {
113
+ display: none;
114
+ margin-top: 0.5rem;
115
+ background: #1e293b;
116
+ border-radius: var(--radius);
117
+ padding: 0.75rem;
118
+ font-family: 'SF Mono', 'Fira Code', monospace;
119
+ font-size: 0.6875rem;
120
+ line-height: 1.5;
121
+ max-height: 300px;
122
+ overflow: auto;
123
+ }
124
+ .version-diff-content.show {
125
+ display: block;
126
+ }
127
+ .diff-line {
128
+ white-space: pre-wrap;
129
+ word-break: break-all;
130
+ }
131
+ .diff-line.added {
132
+ color: #4ade80;
133
+ background: rgba(74, 222, 128, 0.1);
134
+ }
135
+ .diff-line.removed {
136
+ color: #f87171;
137
+ background: rgba(248, 113, 113, 0.1);
138
+ }
139
+ .diff-line.unchanged {
140
+ color: #94a3b8;
141
+ }
142
+ .size-change {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 0.25rem;
146
+ padding: 0.125rem 0.5rem;
147
+ border-radius: 9999px;
148
+ font-size: 0.6875rem;
149
+ font-weight: 500;
150
+ }
151
+ .size-change.positive {
152
+ background: var(--success-light);
153
+ color: var(--success);
154
+ }
155
+ .size-change.negative {
156
+ background: var(--danger-light);
157
+ color: var(--danger);
158
+ }
159
+ .size-change.neutral {
160
+ background: var(--bg-main);
161
+ color: var(--text-muted);
162
+ }
163
+ .empty-versions {
164
+ text-align: center;
165
+ padding: 3rem;
166
+ color: var(--text-muted);
167
+ }
168
+ .empty-versions svg {
169
+ width: 48px;
170
+ height: 48px;
171
+ margin-bottom: 1rem;
172
+ opacity: 0.5;
173
+ }
174
+ </style>
175
+
176
+ <!-- Page Header -->
177
+ <div class="page-header">
178
+ <div class="page-header-left">
179
+ <h2>Version History</h2>
180
+ <p class="page-header-subtitle">
181
+ <%= @page.title %> &middot; <%= @page.versions.count %> versions
182
+ </p>
183
+ </div>
184
+ <div class="page-header-actions">
185
+ <%= link_to admin_page_path(@page), class: "btn btn-secondary" do %>
186
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
187
+ <line x1="19" y1="12" x2="5" y2="12"/>
188
+ <polyline points="12 19 5 12 12 5"/>
189
+ </svg>
190
+ Back to Page
191
+ <% end %>
192
+ <%= link_to editor_admin_page_path(@page), class: "btn btn-primary" do %>
193
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
194
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
195
+ <polyline points="2 17 12 22 22 17"/>
196
+ <polyline points="2 12 12 17 22 12"/>
197
+ </svg>
198
+ Open Editor
199
+ <% end %>
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Timeline -->
204
+ <div class="card">
205
+ <div class="card-body">
206
+ <% if @versions.any? %>
207
+ <div class="version-timeline">
208
+ <% @versions.each_with_index do |version, index| %>
209
+ <div class="version-item">
210
+ <div class="version-dot <%= 'first' if index == 0 %>"></div>
211
+ <div class="version-card">
212
+ <div class="version-header">
213
+ <%= link_to admin_page_version_path(@page, version), class: "version-number", style: "text-decoration: none; color: inherit;" do %>
214
+ Version <%= version.version_number %>
215
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-left: 0.25rem; opacity: 0.5;">
216
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
217
+ <polyline points="15 3 21 3 21 9"/>
218
+ <line x1="10" y1="14" x2="21" y2="3"/>
219
+ </svg>
220
+ <% end %>
221
+ <span class="version-time">
222
+ <%= time_ago_in_words(version.created_at) %> ago
223
+ <span style="color: var(--text-light);">&middot; <%= version.created_at.strftime("%b %d, %Y at %l:%M %p") %></span>
224
+ </span>
225
+ </div>
226
+
227
+ <div class="version-meta">
228
+ <% if version.changed_by.present? %>
229
+ <div class="version-meta-item">
230
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
231
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
232
+ <circle cx="12" cy="7" r="4"/>
233
+ </svg>
234
+ <%= version.changed_by %>
235
+ </div>
236
+ <% end %>
237
+
238
+ <div class="version-meta-item">
239
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
240
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
241
+ <polyline points="14 2 14 8 20 8"/>
242
+ </svg>
243
+ <%= number_to_human_size(version.content_size_after || 0) %>
244
+ </div>
245
+
246
+ <div class="version-meta-item">
247
+ <% diff = version.size_difference %>
248
+ <% if diff > 0 %>
249
+ <span class="size-change positive">+<%= number_to_human_size(diff) %></span>
250
+ <% elsif diff < 0 %>
251
+ <span class="size-change negative"><%= number_to_human_size(diff) %></span>
252
+ <% else %>
253
+ <span class="size-change neutral">No size change</span>
254
+ <% end %>
255
+ </div>
256
+ </div>
257
+
258
+ <% if version.change_summary.present? %>
259
+ <div class="version-changes">
260
+ <span style="font-size: 0.8125rem; color: var(--text-muted);">
261
+ <%= version.change_summary.capitalize %>
262
+ </span>
263
+ </div>
264
+ <% end %>
265
+
266
+ <div class="version-actions" style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--border); display: flex; gap: 0.5rem; align-items: center;">
267
+ <% if version.content_diff.present? %>
268
+ <button type="button" class="version-diff-toggle" onclick="toggleDiff(this)">
269
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
270
+ <polyline points="9 18 15 12 9 6"/>
271
+ </svg>
272
+ Quick diff
273
+ </button>
274
+ <% end %>
275
+ <%= link_to admin_page_version_path(@page, version), class: "btn btn-secondary btn-sm", style: "margin-left: auto;" do %>
276
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
277
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
278
+ <circle cx="12" cy="12" r="3"/>
279
+ </svg>
280
+ View Details
281
+ <% end %>
282
+ </div>
283
+
284
+ <% if version.content_diff.present? %>
285
+ <div class="version-diff-content">
286
+ <% version.content_diff.to_s.lines.first(100).each do |line| %>
287
+ <% css_class = if line.start_with?('+')
288
+ 'added'
289
+ elsif line.start_with?('-')
290
+ 'removed'
291
+ else
292
+ 'unchanged'
293
+ end %>
294
+ <div class="diff-line <%= css_class %>"><%= line %></div>
295
+ <% end %>
296
+ <% if version.content_diff.to_s.lines.size > 100 %>
297
+ <div class="diff-line unchanged" style="color: var(--text-muted); font-style: italic;">
298
+ ... <%= version.content_diff.to_s.lines.size - 100 %> more lines
299
+ </div>
300
+ <% end %>
301
+ </div>
302
+ <% end %>
303
+ </div>
304
+ </div>
305
+ <% end %>
306
+ </div>
307
+ <% else %>
308
+ <div class="empty-versions">
309
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
310
+ <circle cx="12" cy="12" r="10"/>
311
+ <polyline points="12 6 12 12 16 14"/>
312
+ </svg>
313
+ <h3 style="font-size: 1rem; margin-bottom: 0.5rem;">No version history yet</h3>
314
+ <p style="font-size: 0.875rem;">Changes will be tracked when you edit and save the page.</p>
315
+ </div>
316
+ <% end %>
317
+ </div>
318
+ </div>
319
+
320
+ <script>
321
+ function toggleDiff(button) {
322
+ const card = button.closest('.version-card');
323
+ const content = card.querySelector('.version-diff-content');
324
+
325
+ if (content.classList.contains('show')) {
326
+ content.classList.remove('show');
327
+ button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> Quick diff';
328
+ } else {
329
+ content.classList.add('show');
330
+ button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg> Hide diff';
331
+ }
332
+ }
333
+ </script>
@@ -0,0 +1,182 @@
1
+ <% content_for :page_title, "Edit #{@partial.name}" %>
2
+
3
+ <!-- Page Header -->
4
+ <div class="page-header">
5
+ <div class="page-header-left">
6
+ <div style="display: flex; align-items: center; gap: 0.75rem;">
7
+ <h2>Edit <%= @partial.name %></h2>
8
+ <% if @partial.active? %>
9
+ <span class="badge badge-success">
10
+ <span class="badge-dot"></span>
11
+ Active
12
+ </span>
13
+ <% else %>
14
+ <span class="badge badge-gray">
15
+ <span class="badge-dot"></span>
16
+ Inactive
17
+ </span>
18
+ <% end %>
19
+ </div>
20
+ <p class="page-header-subtitle">
21
+ <span style="display: inline-flex; align-items: center; gap: 0.5rem;">
22
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="<%= @partial.header? ? '#6366f1' : '#10b981' %>" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
23
+ <% if @partial.header? %>
24
+ <rect x="3" y="3" width="18" height="6" rx="2"/>
25
+ <rect x="3" y="13" width="18" height="8" rx="2" opacity="0.3"/>
26
+ <% else %>
27
+ <rect x="3" y="3" width="18" height="8" rx="2" opacity="0.3"/>
28
+ <rect x="3" y="15" width="18" height="6" rx="2"/>
29
+ <% end %>
30
+ </svg>
31
+ <%= @partial.partial_type.capitalize %> partial
32
+ </span>
33
+ </p>
34
+ </div>
35
+ <div class="page-header-actions">
36
+ <%= link_to editor_admin_partial_path(@partial), class: "btn btn-primary" do %>
37
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
38
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
39
+ <polyline points="2 17 12 22 22 17"/>
40
+ <polyline points="2 12 12 17 22 12"/>
41
+ </svg>
42
+ Open Designer
43
+ <% end %>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Two Column Layout -->
48
+ <div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
49
+ <!-- Main Column -->
50
+ <div>
51
+ <%= form_with model: @partial, url: admin_partial_path(@partial), method: :patch do |f| %>
52
+ <% if @partial.errors.any? %>
53
+ <div class="error-messages" style="margin-bottom: 1.5rem;">
54
+ <strong><%= pluralize(@partial.errors.count, "error") %> prohibited this partial from being saved:</strong>
55
+ <ul>
56
+ <% @partial.errors.full_messages.each do |message| %>
57
+ <li><%= message %></li>
58
+ <% end %>
59
+ </ul>
60
+ </div>
61
+ <% end %>
62
+
63
+ <!-- Settings Card -->
64
+ <div class="card" style="margin-bottom: 1.5rem;">
65
+ <div class="card-header">
66
+ <span class="card-title">
67
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: -2px; margin-right: 0.5rem;">
68
+ <circle cx="12" cy="12" r="3"/>
69
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
70
+ </svg>
71
+ Settings
72
+ </span>
73
+ </div>
74
+ <div class="card-body">
75
+ <div class="form-group">
76
+ <%= f.label :name %>
77
+ <%= f.text_field :name, class: "form-control", placeholder: "e.g., Main Header" %>
78
+ <p class="help-text">A friendly name to identify this partial in the admin.</p>
79
+ </div>
80
+
81
+ <div class="form-group" style="margin-bottom: 0;">
82
+ <label style="font-weight: 500; margin-bottom: 0.5rem; display: block;">Visibility</label>
83
+ <div style="background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
84
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
85
+ <%= f.check_box :active, id: "partial_active" %>
86
+ <label for="partial_active" style="font-weight: 500; margin: 0;">
87
+ Active
88
+ </label>
89
+ </div>
90
+ <p class="help-text" style="margin-top: 0.5rem; margin-bottom: 0;">
91
+ When active, this <%= @partial.partial_type %> will appear on pages that have it enabled.
92
+ </p>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Form Actions -->
99
+ <div class="form-actions" style="background: var(--bg-card, white); padding: 1.25rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
100
+ <%= f.submit "Save Settings", class: "btn btn-primary" %>
101
+ <%= link_to "Cancel", admin_partials_path, class: "btn btn-secondary" %>
102
+ </div>
103
+ <% end %>
104
+ </div>
105
+
106
+ <!-- Sidebar Column -->
107
+ <div>
108
+ <!-- Quick Actions -->
109
+ <div class="card" style="margin-bottom: 1.5rem;">
110
+ <div class="card-header">
111
+ <span class="card-title">Actions</span>
112
+ </div>
113
+ <div style="padding: 1rem;">
114
+ <%= link_to editor_admin_partial_path(@partial), class: "btn btn-primary", style: "width: 100%; justify-content: center; margin-bottom: 0.75rem;" do %>
115
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
116
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
117
+ <polyline points="2 17 12 22 22 17"/>
118
+ <polyline points="2 12 12 17 22 12"/>
119
+ </svg>
120
+ Open Designer
121
+ <% end %>
122
+ <%= link_to admin_partials_path, class: "btn btn-secondary", style: "width: 100%; justify-content: center;" do %>
123
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
124
+ <line x1="19" y1="12" x2="5" y2="12"/>
125
+ <polyline points="12 19 5 12 12 5"/>
126
+ </svg>
127
+ Back to Partials
128
+ <% end %>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Status Info -->
133
+ <div class="card" style="margin-bottom: 1.5rem;">
134
+ <div class="card-header">
135
+ <span class="card-title">Status</span>
136
+ </div>
137
+ <div class="card-body" style="padding: 0;">
138
+ <div style="padding: 0.875rem 1rem; border-bottom: 1px solid var(--border);">
139
+ <div style="font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.25rem;">Content</div>
140
+ <div style="font-size: 0.9375rem;">
141
+ <% if @partial.content.present? %>
142
+ <span style="color: var(--success);">Configured</span>
143
+ <span style="color: var(--text-muted); font-size: 0.8125rem; margin-left: 0.5rem;">(<%= number_to_human_size(@partial.content.bytesize) %>)</span>
144
+ <% else %>
145
+ <span style="color: var(--text-muted);">Empty</span>
146
+ <% end %>
147
+ </div>
148
+ </div>
149
+ <div style="padding: 0.875rem 1rem; border-bottom: 1px solid var(--border);">
150
+ <div style="font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.25rem;">Tailwind CSS</div>
151
+ <div style="font-size: 0.9375rem;">
152
+ <% if @partial.compiled_css.present? %>
153
+ <span style="color: var(--success);"><%= number_to_human_size(@partial.compiled_css.bytesize) %></span>
154
+ <% else %>
155
+ <span style="color: var(--text-muted);">Not compiled</span>
156
+ <% end %>
157
+ </div>
158
+ </div>
159
+ <div style="padding: 0.875rem 1rem;">
160
+ <div style="font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.25rem;">Last Updated</div>
161
+ <div style="font-size: 0.9375rem;"><%= @partial.updated_at.strftime("%b %d, %Y at %l:%M %p") %></div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <!-- Info Box -->
167
+ <div class="card">
168
+ <div style="padding: 1rem;">
169
+ <div style="display: flex; align-items: flex-start; gap: 0.75rem;">
170
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 2px;">
171
+ <circle cx="12" cy="12" r="10"/>
172
+ <path d="M12 16v-4"/>
173
+ <path d="M12 8h.01"/>
174
+ </svg>
175
+ <div style="font-size: 0.8125rem; color: var(--text-muted); line-height: 1.5;">
176
+ This <%= @partial.partial_type %> appears at the <%= @partial.header? ? 'top' : 'bottom' %> of pages that have it enabled.
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>