athena_admin 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 51c701bedf6216f3398002f879be8edefe563a8bd21cf029b3f776614cd2e26a
4
+ data.tar.gz: 89be415ca46b8ca6ad093ca1e128a7d7b944879ef69b851df33b67dc336a84be
5
+ SHA512:
6
+ metadata.gz: c7eb73e4dab995c8665a55c55951095c7ac17e8ea25cab7eb37c80664ac2e38d800f7fe5c5c6b35207a35349cfcba71027313ae0ae4498448dbdf042e7f29e03
7
+ data.tar.gz: 1fe4571c0d3f47291d881c462e9bd0e24f66222eece14393cf20e2af4e58a09429b6dcceb769ef6ca4bfd3ef58f66322a7974be72f0ce2229d28983dcd88c528
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # AthenaAdmin
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "athena_admin"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install athena_admin
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,521 @@
1
+ /* Athena — Ember. Tailwind v4 design tokens + components. */
2
+ /* Self-hosted fonts served statically from public/athena-fonts (copied by the installer). */
3
+ @font-face { font-family:"Inter"; font-weight:400; font-display:swap; src:url("/athena-fonts/Inter-Regular.woff2") format("woff2"); }
4
+ @font-face { font-family:"Inter"; font-weight:600; font-display:swap; src:url("/athena-fonts/Inter-SemiBold.woff2") format("woff2"); }
5
+ @font-face { font-family:"Inter"; font-weight:700; font-display:swap; src:url("/athena-fonts/Inter-Bold.woff2") format("woff2"); }
6
+ @font-face { font-family:"JetBrains Mono"; font-weight:400; font-display:swap; src:url("/athena-fonts/JetBrainsMono-Regular.woff2") format("woff2"); }
7
+ @font-face { font-family:"JetBrains Mono"; font-weight:500; font-display:swap; src:url("/athena-fonts/JetBrainsMono-Medium.woff2") format("woff2"); }
8
+
9
+ @theme {
10
+ --font-sans: "Inter", system-ui, -apple-system, sans-serif;
11
+ --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
12
+
13
+ /* Ember — dark reference values (light/dark resolved via --athena-* below) */
14
+ --color-ember-bg: #0A0A0B;
15
+ --color-ember-panel: #100C09;
16
+ --color-ember-surface: #16110D;
17
+ --color-ember-surface2: #1C1611;
18
+ --color-ember-line: #241A12;
19
+ --color-ember-line2: #2E2118;
20
+ --color-ember-text: #F5EDE6;
21
+ --color-ember-muted: #8C8076;
22
+ --color-ember-dim: #5E554D;
23
+ --color-fire-1: #FF6A00;
24
+ --color-fire-2: #FF2D00;
25
+ --color-pos: #34D399;
26
+ --color-neg: #F87171;
27
+ --color-amber: #F59E0B;
28
+
29
+ --radius-athena: 13px;
30
+ }
31
+
32
+ /* Recolor: remap Tailwind's neutral scale to WARM Ember tones (dark ends pushed to
33
+ Ember near-blacks). AA4's chrome uses bg-white / gray-50…950 + dark: variants, so this
34
+ single remap re-skins the whole admin in both modes without touching partials. */
35
+ @theme {
36
+ --color-white: #ffffff;
37
+ --color-gray-50: #F7F3EE;
38
+ --color-gray-100: #EFE9E0;
39
+ --color-gray-200: #E7DCCD; /* light borders */
40
+ --color-gray-300: #D6C8B6;
41
+ --color-gray-400: #A99C8E; /* muted */
42
+ --color-gray-500: #8A7E72;
43
+ --color-gray-600: #6B6055; /* light secondary text */
44
+ --color-gray-700: #2E2218; /* dark borders (warm) */
45
+ --color-gray-800: #1C1611; /* dark elevated */
46
+ --color-gray-900: #16110D; /* dark surface */
47
+ --color-gray-950: #0A0A0B; /* dark bg */
48
+ }
49
+
50
+ /* Accent remap: AA4's chrome (focus rings, checkboxes, buttons, links) is hardwired to
51
+ Tailwind's `blue` scale (see activeadmin plugin.js: colors.blue.600), and scaffolds use
52
+ `indigo`. Remap BOTH to the Ember fire scale so every accent burns orange in one stroke.
53
+ 600 is AA's primary accent → fire red-orange; 500 → fire orange. */
54
+ @theme {
55
+ --color-blue-50: #FFF3EC; --color-indigo-50: #FFF3EC;
56
+ --color-blue-100: #FFE3D2; --color-indigo-100: #FFE3D2;
57
+ --color-blue-200: #FFC9A8; --color-indigo-200: #FFC9A8;
58
+ --color-blue-300: #FFA770; --color-indigo-300: #FFA770;
59
+ --color-blue-400: #FF8438; --color-indigo-400: #FF8438;
60
+ --color-blue-500: #FF6A00; --color-indigo-500: #FF6A00; /* fire1 */
61
+ --color-blue-600: #F23005; --color-indigo-600: #F23005; /* fire2 — primary accent */
62
+ --color-blue-700: #C41F00; --color-indigo-700: #C41F00;
63
+ --color-blue-800: #9A1C04; --color-indigo-800: #9A1C04;
64
+ --color-blue-900: #7C1D0A; --color-indigo-900: #7C1D0A;
65
+ --color-blue-950: #430D03; --color-indigo-950: #430D03;
66
+ }
67
+
68
+ @layer base {
69
+ body { font-family: var(--font-sans); }
70
+ /* warm paper page background in light mode (AA body is bg-white dark:bg-gray-950) */
71
+ body { background-color: var(--athena-bg); }
72
+ }
73
+
74
+ /* Light is the base (html without .dark); dark tokens apply under .dark. */
75
+ :root {
76
+ --athena-bg: #FAF6F1; --athena-panel: #FFFFFF; --athena-surface: #FFFFFF;
77
+ --athena-surface2: #F6F0E8; --athena-line: #ECE2D5; --athena-line2: #E2D6C5;
78
+ --athena-text: #1A1411; --athena-muted: #8A7E72; --athena-dim: #B3A899;
79
+ --athena-fire1: #FF6A00; --athena-fire2: #F23005;
80
+ --athena-pos: #15803D; --athena-neg: #DC2626; --athena-amber: #B45309;
81
+ }
82
+ .dark {
83
+ --athena-bg: #0A0A0B; --athena-panel: #100C09; --athena-surface: #16110D;
84
+ --athena-surface2: #1C1611; --athena-line: #241A12; --athena-line2: #2E2118;
85
+ --athena-text: #F5EDE6; --athena-muted: #8C8076; --athena-dim: #5E554D;
86
+ --athena-fire1: #FF6A00; --athena-fire2: #FF2D00;
87
+ --athena-pos: #34D399; --athena-neg: #F87171; --athena-amber: #F59E0B;
88
+ }
89
+
90
+ @layer components {
91
+ .athena-fire-text {
92
+ background: linear-gradient(90deg, var(--athena-fire1), var(--athena-fire2));
93
+ -webkit-background-clip: text; background-clip: text; color: transparent;
94
+ }
95
+ .athena-btn-fire {
96
+ background: linear-gradient(90deg, var(--athena-fire1), var(--athena-fire2));
97
+ color: #fff; border: none; border-radius: 9px; padding: 8px 13px;
98
+ font-weight: 600; box-shadow: 0 6px 18px rgba(242,48,5,.30); cursor: pointer;
99
+ }
100
+ .athena-tag { font-size: 10.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px; }
101
+ .athena-tag-posted { background: color-mix(in srgb, var(--athena-pos) 16%, transparent); color: var(--athena-pos); }
102
+ .athena-tag-pending { background: color-mix(in srgb, var(--athena-amber) 16%, transparent); color: var(--athena-amber); }
103
+ .athena-tag-vanished { background: color-mix(in srgb, var(--athena-muted) 16%, transparent); color: var(--athena-muted); }
104
+ .athena-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
105
+ .athena-card { background: var(--athena-surface); border: 1px solid var(--athena-line); border-radius: var(--radius-athena); }
106
+ .athena-fab {
107
+ position: fixed; right: 16px; bottom: 18px; width: 52px; height: 52px;
108
+ border-radius: 16px; display: flex; align-items: center; justify-content: center;
109
+ background: linear-gradient(150deg, var(--athena-fire1), var(--athena-fire2));
110
+ color: #fff; box-shadow: 0 10px 24px rgba(242,48,5,.45); z-index: 40;
111
+ }
112
+
113
+ /* ── Ember Command Rail (main navigation) ─────────────────────────────────────── */
114
+ #main-menu.athena-rail {
115
+ /* ember glow bleeding down from the top of the rail */
116
+ background:
117
+ radial-gradient(135% 50% at 50% -8%, color-mix(in srgb, var(--athena-fire2) 9%, transparent), transparent 72%),
118
+ var(--athena-bg);
119
+ border-inline-end: 1px solid var(--athena-line);
120
+ }
121
+ .athena-rail-cap {
122
+ margin: 4px 8px 12px; font-family: var(--font-mono);
123
+ font-size: 10px; font-weight: 600; letter-spacing: .22em; text-transform: uppercase;
124
+ color: var(--athena-dim);
125
+ }
126
+
127
+ .athena-nav {
128
+ position: relative; display: flex; align-items: center; gap: 11px;
129
+ padding: 8px 10px; border-radius: 12px; text-decoration: none;
130
+ font-size: 13.5px; font-weight: 500; color: var(--athena-muted);
131
+ border: 1px solid transparent;
132
+ transition: background .18s, color .18s, border-color .18s, transform .12s, box-shadow .18s;
133
+ }
134
+ .athena-nav:hover { color: var(--athena-text); background: var(--athena-surface2); transform: translateX(2px); }
135
+
136
+ .athena-nav-ic {
137
+ flex: 0 0 auto; width: 31px; height: 31px; display: grid; place-items: center;
138
+ border-radius: 9px; color: var(--athena-muted);
139
+ background: var(--athena-surface2); border: 1px solid var(--athena-line2);
140
+ transition: all .18s;
141
+ }
142
+ .athena-nav-ic svg { width: 17px; height: 17px; }
143
+ .athena-nav:hover .athena-nav-ic {
144
+ color: var(--athena-fire1);
145
+ border-color: color-mix(in srgb, var(--athena-fire1) 45%, var(--athena-line2));
146
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 9%, transparent);
147
+ }
148
+ .athena-nav-label { flex: 1 1 auto; min-width: 0; }
149
+ .athena-nav-spark { width: 6px; height: 6px; border-radius: 99px; background: transparent; flex: 0 0 auto; }
150
+
151
+ /* Active: fire-tinted pill, glowing icon tile, glowing left bar */
152
+ .athena-nav.is-active {
153
+ color: #fff; font-weight: 600;
154
+ background: linear-gradient(95deg,
155
+ color-mix(in srgb, var(--athena-fire2) 24%, transparent),
156
+ color-mix(in srgb, var(--athena-fire1) 9%, transparent));
157
+ border-color: color-mix(in srgb, var(--athena-fire1) 26%, transparent);
158
+ box-shadow: 0 10px 26px -12px rgba(242, 48, 5, .75);
159
+ }
160
+ .athena-nav.is-active .athena-nav-ic {
161
+ color: #fff; border-color: transparent;
162
+ background: linear-gradient(150deg, var(--athena-fire1), var(--athena-fire2));
163
+ box-shadow: 0 5px 14px -3px rgba(242, 48, 5, .65);
164
+ }
165
+ .athena-nav.is-active .athena-nav-spark {
166
+ background: var(--athena-fire1); box-shadow: 0 0 9px 1px var(--athena-fire1);
167
+ }
168
+ .athena-nav.is-active::before {
169
+ content: ""; position: absolute; inset-inline-start: -3px; top: 50%; transform: translateY(-50%);
170
+ width: 3px; height: 22px; border-radius: 99px;
171
+ background: linear-gradient(180deg, var(--athena-fire1), var(--athena-fire2));
172
+ box-shadow: 0 0 11px 1px var(--athena-fire1);
173
+ }
174
+
175
+ /* Parent (section) rows + caret */
176
+ .athena-nav-parent { cursor: pointer; }
177
+ .athena-nav-caret { width: 16px; height: 16px; flex: 0 0 auto; color: var(--athena-dim); transition: transform .18s; }
178
+
179
+ /* Sub-navigation */
180
+ .athena-subnav { margin: 4px 0 2px; padding-inline-start: 18px;
181
+ border-inline-start: 1px solid var(--athena-line2); margin-inline-start: 23px; }
182
+ .athena-subnav-link {
183
+ display: flex; align-items: center; gap: 9px; padding: 6px 9px; border-radius: 9px;
184
+ font-size: 13px; color: var(--athena-muted); text-decoration: none; transition: all .15s;
185
+ }
186
+ .athena-subnav-link:hover { color: var(--athena-text); background: var(--athena-surface2); }
187
+ .athena-subnav-dot { width: 5px; height: 5px; border-radius: 99px; background: var(--athena-dim); flex: 0 0 auto; transition: all .15s; }
188
+ .athena-subnav-link.is-active { color: var(--athena-fire1); font-weight: 600; }
189
+ .athena-subnav-link.is-active .athena-subnav-dot { background: var(--athena-fire1); box-shadow: 0 0 8px 1px var(--athena-fire1); }
190
+ }
191
+
192
+ /* The whole paginated collection — table + "Showing…" + download footer — is ONE ember
193
+ panel, so they share a single border and radius (instead of AA's outer .paginated-collection
194
+ box nesting around our inner table box, which produced a double, mismatched-radius border).
195
+ The inner .index-as-table is just the horizontal scroll container for wide tables. */
196
+ .paginated-collection {
197
+ border: 1px solid var(--athena-line2);
198
+ border-radius: 14px;
199
+ overflow: hidden;
200
+ background: var(--athena-surface);
201
+ box-shadow:
202
+ inset 0 1px 0 color-mix(in srgb, var(--athena-fire1) 8%, transparent),
203
+ 0 18px 42px -28px rgba(0, 0, 0, .6);
204
+ }
205
+ .index-as-table { overflow-x: auto; }
206
+ /* footer rows (pagination info + downloads) sit inside the panel, divided from the table */
207
+ .paginated-collection-pagination,
208
+ .paginated-collection-footer {
209
+ padding: 11px 14px; border-top: 1px solid var(--athena-line);
210
+ font-size: 13px; color: var(--athena-muted);
211
+ }
212
+
213
+ /* Mobile/tablet-portrait: restack the index table into Ember cards. The _html_head enhancer
214
+ stamps each cell with data-label from its column header; here we surface those as labels. */
215
+ @media (max-width: 767px) {
216
+ /* cards provide their own borders — drop the panel chrome so they aren't double-boxed */
217
+ .index-as-table { overflow-x: visible; }
218
+ .paginated-collection { border: 0; border-radius: 0; box-shadow: none; background: transparent; overflow: visible; }
219
+ .paginated-collection-pagination, .paginated-collection-footer { padding-inline: 0; }
220
+ .index-as-table table.data-table,
221
+ .index-as-table table.data-table tbody { display: block; width: 100%; }
222
+ .index-as-table table.data-table thead { display: none; }
223
+
224
+ .index-as-table table.data-table tr {
225
+ display: block; margin-bottom: 12px; padding: 4px 13px;
226
+ background: var(--athena-surface);
227
+ border: 1px solid var(--athena-line); border-radius: var(--radius-athena);
228
+ }
229
+ .index-as-table table.data-table td {
230
+ display: flex; align-items: center; justify-content: space-between; gap: 16px;
231
+ padding: 9px 0; border: 0; border-bottom: 1px solid var(--athena-line);
232
+ text-align: end; white-space: normal;
233
+ }
234
+ .index-as-table table.data-table tr td:last-child { border-bottom: 0; }
235
+ .index-as-table table.data-table td::before {
236
+ content: attr(data-label); text-align: start;
237
+ font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
238
+ color: var(--athena-muted); flex: 0 0 auto;
239
+ }
240
+ /* selection checkbox has no column header → left-align, drop the empty label gap */
241
+ .index-as-table table.data-table td:not([data-label]) { justify-content: flex-start; }
242
+ .index-as-table table.data-table td:not([data-label])::before { content: none; }
243
+
244
+ /* Row actions become a full-width, evenly-spaced, touch-friendly button row. */
245
+ .index-as-table table.data-table td:has(.data-table-resource-actions) { padding-top: 4px; }
246
+ .index-as-table table.data-table .data-table-resource-actions {
247
+ display: flex; width: 100%; gap: 8px;
248
+ }
249
+ .index-as-table table.data-table .data-table-resource-actions a {
250
+ flex: 1; text-align: center; padding: 9px 10px; font-weight: 600;
251
+ border: 1px solid var(--athena-line2); border-radius: 9px;
252
+ background: var(--athena-surface2); text-decoration: none;
253
+ }
254
+ }
255
+
256
+ /* ════ Ember component pass — "same intensity" art across the whole admin ════ */
257
+ /* Declared in a trailing `athena` layer so these overrides outrank ActiveAdmin's plugin
258
+ defaults (which live in the `components` layer and would otherwise win on layer order,
259
+ e.g. doubling the row border by styling both tbody>tr AND our td). */
260
+ @layer athena {
261
+
262
+ /* ── Top bar ─────────────────────────────────────────────────────────────────── */
263
+ .athena-topbar {
264
+ background: color-mix(in srgb, var(--athena-panel) 82%, transparent);
265
+ border-bottom: 1px solid var(--athena-line);
266
+ box-shadow:
267
+ inset 0 -1px 0 color-mix(in srgb, var(--athena-fire1) 16%, transparent),
268
+ 0 8px 24px -14px rgba(0, 0, 0, .7);
269
+ }
270
+ .athena-brand-mark {
271
+ width: 30px; height: 30px; flex: 0 0 auto; border-radius: 9px; display: grid; place-items: center;
272
+ background: linear-gradient(150deg, var(--athena-fire1), var(--athena-fire2));
273
+ box-shadow: 0 5px 14px -3px rgba(242, 48, 5, .6), inset 0 1px 0 rgba(255, 255, 255, .28);
274
+ }
275
+ .athena-brand-mark svg { width: 17px; height: 17px; }
276
+ .athena-brand-word {
277
+ letter-spacing: .14em;
278
+ background: linear-gradient(90deg, var(--athena-fire1), var(--athena-fire2));
279
+ -webkit-background-clip: text; background-clip: text; color: transparent;
280
+ }
281
+ .athena-icon-btn {
282
+ display: inline-flex; align-items: center; justify-content: center;
283
+ width: 38px; height: 38px; border-radius: 10px; cursor: pointer;
284
+ color: var(--athena-muted); background: transparent; border: 1px solid transparent;
285
+ transition: color .16s, background .16s, border-color .16s, box-shadow .16s;
286
+ }
287
+ .athena-icon-btn:hover {
288
+ color: var(--athena-fire1); background: var(--athena-surface2);
289
+ border-color: color-mix(in srgb, var(--athena-fire1) 32%, var(--athena-line2));
290
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 8%, transparent);
291
+ }
292
+ .athena-menu {
293
+ background: var(--athena-surface); border: 1px solid var(--athena-line2); border-radius: 12px;
294
+ box-shadow: 0 20px 46px -14px rgba(0, 0, 0, .65); overflow: hidden;
295
+ }
296
+ .athena-menu-item { padding: 8px 14px; color: var(--athena-text); transition: background .14s, color .14s; }
297
+ .athena-menu-item:hover { background: var(--athena-surface2); color: var(--athena-fire1); }
298
+ .athena-menu-danger:hover { color: var(--athena-neg); }
299
+
300
+ /* ── Page header ─────────────────────────────────────────────────────────────── */
301
+ [data-test-page-header] {
302
+ background:
303
+ radial-gradient(130% 130% at 0% 0%, color-mix(in srgb, var(--athena-fire2) 8%, transparent), transparent 46%),
304
+ var(--athena-surface2);
305
+ border-bottom: 1px solid var(--athena-line);
306
+ }
307
+ [data-test-page-header] h2 { position: relative; padding-inline-start: 15px; }
308
+ [data-test-page-header] h2::before {
309
+ content: ""; position: absolute; inset-inline-start: 0; top: 50%; transform: translateY(-50%);
310
+ width: 4px; height: 1.05em; border-radius: 99px;
311
+ background: linear-gradient(180deg, var(--athena-fire1), var(--athena-fire2));
312
+ box-shadow: 0 0 11px 0 var(--athena-fire1);
313
+ }
314
+
315
+ /* ── Buttons ─────────────────────────────────────────────────────────────────── */
316
+ /* Primary — fire gradient + glow (submits, filter button) */
317
+ .filters-form-submit,
318
+ input[type="submit"],
319
+ button[type="submit"] {
320
+ background: linear-gradient(95deg, var(--athena-fire1), var(--athena-fire2)) !important;
321
+ color: #fff !important; border: none !important; border-radius: 10px !important;
322
+ font-weight: 600 !important; padding: 9px 17px !important; cursor: pointer;
323
+ box-shadow: 0 10px 24px -8px rgba(242, 48, 5, .55) !important;
324
+ transition: box-shadow .16s, transform .1s !important;
325
+ }
326
+ .filters-form-submit:hover,
327
+ input[type="submit"]:hover,
328
+ button[type="submit"]:hover { box-shadow: 0 13px 32px -6px rgba(242, 48, 5, .72) !important; }
329
+ .filters-form-submit:active,
330
+ input[type="submit"]:active,
331
+ button[type="submit"]:active { transform: translateY(1px); }
332
+
333
+ /* Secondary — outline action item with fire hover (e.g. New User) */
334
+ .action-item-button {
335
+ display: inline-flex; align-items: center; gap: 7px;
336
+ padding: 8px 14px; border-radius: 10px; font-weight: 600; font-size: 13.5px;
337
+ color: var(--athena-text); text-decoration: none;
338
+ background: var(--athena-surface); border: 1px solid var(--athena-line2);
339
+ transition: color .16s, border-color .16s, box-shadow .16s, transform .1s;
340
+ }
341
+ .action-item-button:hover {
342
+ color: var(--athena-fire1);
343
+ border-color: color-mix(in srgb, var(--athena-fire1) 48%, var(--athena-line2));
344
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 8%, transparent);
345
+ transform: translateY(-1px);
346
+ }
347
+
348
+ /* ── Index table ─────────────────────────────────────────────────────────────── */
349
+ .data-table { border-collapse: separate; border-spacing: 0; width: 100%; }
350
+
351
+ /* header band */
352
+ .data-table thead tr { background: var(--athena-surface2); }
353
+ .data-table thead th {
354
+ padding: 12px 12px; text-align: start;
355
+ font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
356
+ letter-spacing: .1em; text-transform: uppercase; color: var(--athena-muted);
357
+ border-bottom: 1px solid color-mix(in srgb, var(--athena-fire1) 22%, var(--athena-line2));
358
+ white-space: nowrap;
359
+ }
360
+ .data-table thead th a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
361
+ .data-table thead th a:hover { color: var(--athena-fire1); }
362
+
363
+ /* row rhythm + hover glow with inset fire bar */
364
+ .data-table tbody td {
365
+ padding: 11px 10px; border-bottom: 1px solid var(--athena-line);
366
+ vertical-align: middle;
367
+ }
368
+ .data-table tbody tr:last-child td { border-bottom: 0; }
369
+ /* AA styles the divider on tbody>tr; we draw ours on td — kill theirs to avoid a double line */
370
+ .data-table tbody tr { border-bottom: 0; transition: background .14s, box-shadow .14s; }
371
+ .data-table tbody tr:hover { background: color-mix(in srgb, var(--athena-fire1) 7%, transparent); }
372
+ .data-table tbody tr:hover td:first-child { box-shadow: inset 3px 0 0 0 var(--athena-fire1); }
373
+
374
+ /* numeric admin links read mono; the ID column becomes a fire rank chip (targeted by the
375
+ data-label the _html_head enhancer stamps, so it's column-position independent). */
376
+ .data-table tbody td > a[href*="/admin/"]:not(.data-table-resource-actions a) { font-family: var(--font-mono); }
377
+ .data-table tbody td[data-label="Id" i] > a {
378
+ display: inline-flex; align-items: center; justify-content: center; min-width: 28px;
379
+ padding: 2px 9px; border-radius: 7px; font-size: 12px; font-weight: 600;
380
+ color: var(--athena-fire1); text-decoration: none;
381
+ background: color-mix(in srgb, var(--athena-fire1) 12%, transparent);
382
+ border: 1px solid color-mix(in srgb, var(--athena-fire1) 26%, transparent);
383
+ transition: background .14s, box-shadow .14s;
384
+ }
385
+ .data-table tbody td[data-label="Id" i] > a:hover {
386
+ background: color-mix(in srgb, var(--athena-fire1) 20%, transparent);
387
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 10%, transparent);
388
+ }
389
+
390
+ /* temporal & numeric columns → mono tabular numerics (the Ember data-console look),
391
+ keyed off the stamped data-label so text columns (name/email) stay in Inter. */
392
+ .data-table tbody td[data-label*="at" i],
393
+ .data-table tbody td[data-label*="date" i],
394
+ .data-table tbody td[data-label*="amount" i],
395
+ .data-table tbody td[data-label*="balance" i],
396
+ .data-table tbody td[data-label*="total" i],
397
+ .data-table tbody td[data-label*="price" i],
398
+ .data-table tbody td[data-label*="qty" i],
399
+ .data-table tbody td[data-label*="count" i],
400
+ .data-table tbody td[data-label*="number" i] {
401
+ font-family: var(--font-mono); font-variant-numeric: tabular-nums;
402
+ font-size: 12px; letter-spacing: -.02em; color: var(--athena-muted);
403
+ }
404
+
405
+ /* row action links: muted ghost chips that light fire (delete → danger) on hover */
406
+ .data-table-resource-actions { display: inline-flex; align-items: center; gap: 3px; }
407
+ .data-table-resource-actions a {
408
+ display: inline-flex; align-items: center; padding: 4px 8px; border-radius: 7px;
409
+ font-size: 12.5px; font-weight: 600; text-decoration: none;
410
+ color: var(--athena-muted); border: 1px solid transparent;
411
+ transition: color .14s, background .14s, border-color .14s;
412
+ }
413
+ .data-table-resource-actions a:hover {
414
+ color: var(--athena-fire1);
415
+ background: color-mix(in srgb, var(--athena-fire1) 12%, transparent);
416
+ border-color: color-mix(in srgb, var(--athena-fire1) 28%, transparent);
417
+ }
418
+ .data-table-resource-actions a[data-method="delete"]:hover {
419
+ color: var(--athena-neg);
420
+ background: color-mix(in srgb, var(--athena-neg) 12%, transparent);
421
+ border-color: color-mix(in srgb, var(--athena-neg) 30%, transparent);
422
+ }
423
+
424
+ /* export links → mono chip buttons (CSV / XML / JSON) */
425
+ div:has(> a[href$=".csv"]) { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
426
+ div:has(> a[href$=".csv"]) > span {
427
+ font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
428
+ letter-spacing: .12em; text-transform: uppercase; color: var(--athena-dim);
429
+ }
430
+ a[href$=".csv"], a[href$=".xml"], a[href$=".json"] {
431
+ display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 8px;
432
+ font-family: var(--font-mono); font-size: 12px; font-weight: 600; text-decoration: none;
433
+ color: var(--athena-muted); background: var(--athena-surface); border: 1px solid var(--athena-line2);
434
+ transition: color .14s, border-color .14s, box-shadow .14s, transform .1s;
435
+ }
436
+ a[href$=".csv"]:hover, a[href$=".xml"]:hover, a[href$=".json"]:hover {
437
+ color: var(--athena-fire1);
438
+ border-color: color-mix(in srgb, var(--athena-fire1) 42%, var(--athena-line2));
439
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 8%, transparent);
440
+ transform: translateY(-1px);
441
+ }
442
+
443
+ /* ── Attributes table (show) → ember panel ───────────────────────────────────── */
444
+ .attributes-table {
445
+ background: var(--athena-surface); border: 1px solid var(--athena-line2);
446
+ border-radius: 14px; overflow: hidden;
447
+ box-shadow:
448
+ inset 0 1px 0 color-mix(in srgb, var(--athena-fire1) 9%, transparent),
449
+ 0 18px 42px -26px rgba(0, 0, 0, .65);
450
+ }
451
+ .attributes-table th {
452
+ font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
453
+ letter-spacing: .08em; text-transform: uppercase; color: var(--athena-muted);
454
+ background: var(--athena-surface2);
455
+ }
456
+ .attributes-table tr + tr th, .attributes-table tr + tr td { border-top: 1px solid var(--athena-line); }
457
+
458
+ /* ── Filters panel → ember card ──────────────────────────────────────────────── */
459
+ .filters-form {
460
+ background: var(--athena-surface); border: 1px solid var(--athena-line2);
461
+ border-radius: 14px; padding: 16px 16px 6px;
462
+ box-shadow:
463
+ inset 0 1px 0 color-mix(in srgb, var(--athena-fire1) 8%, transparent),
464
+ 0 18px 42px -28px rgba(0, 0, 0, .6);
465
+ }
466
+ .filters-form-title {
467
+ font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .14em;
468
+ font-size: 12px; font-weight: 600; color: var(--athena-text);
469
+ display: flex; align-items: center; gap: 8px; margin-bottom: 14px;
470
+ }
471
+ .filters-form-title::before {
472
+ content: ""; width: 4px; height: 14px; border-radius: 99px;
473
+ background: linear-gradient(180deg, var(--athena-fire1), var(--athena-fire2));
474
+ box-shadow: 0 0 9px 0 var(--athena-fire1);
475
+ }
476
+ .filters-form-field { margin-bottom: 14px; }
477
+ .filters-form-field > label.label {
478
+ display: block; margin-bottom: 6px;
479
+ font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
480
+ color: var(--athena-muted);
481
+ }
482
+ /* string filter: operator select stacks above the value input (keeps each readable in the
483
+ narrow sidebar instead of crushing the "Contains/Equals/…" select to a sliver). */
484
+ .filters-form-input-group { display: flex; flex-direction: column; gap: 7px; }
485
+ .filters-form-input-group > select { width: 100%; font-size: 12.5px; color: var(--athena-muted); }
486
+ .filters-form-input-group > input { width: 100%; min-width: 0; }
487
+ /* date range: two inputs side by side */
488
+ .date_range .filters-form-input-group { flex-direction: row; gap: 8px; }
489
+ .date_range .filters-form-input-group input { flex: 1 1 0; width: auto; min-width: 0; }
490
+ /* consistent fire focus on every filter control */
491
+ .filters-form input:focus, .filters-form select:focus {
492
+ border-color: var(--athena-fire1) !important;
493
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--athena-fire1) 18%, transparent) !important;
494
+ }
495
+ /* button row */
496
+ .filters-form-buttons {
497
+ display: flex; align-items: center; gap: 12px;
498
+ margin-top: 4px; padding-top: 14px; border-top: 1px solid var(--athena-line);
499
+ }
500
+ .filters-form-clear {
501
+ font-size: 12.5px; font-weight: 600; color: var(--athena-muted); text-decoration: none;
502
+ transition: color .14s;
503
+ }
504
+ .filters-form-clear:hover { color: var(--athena-fire1); }
505
+
506
+ /* ── Status tags ─────────────────────────────────────────────────────────────── */
507
+ .status_tag, .status-tag {
508
+ font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
509
+ padding: 3px 9px; border-radius: 999px; text-transform: uppercase;
510
+ background: color-mix(in srgb, var(--athena-muted) 16%, transparent); color: var(--athena-muted);
511
+ }
512
+ .status_tag.yes, .status_tag.completed, .status_tag.active, .status_tag.published, .status_tag.posted {
513
+ background: color-mix(in srgb, var(--athena-pos) 16%, transparent); color: var(--athena-pos);
514
+ }
515
+ .status_tag.no, .status_tag.error, .status_tag.failed, .status_tag.declined {
516
+ background: color-mix(in srgb, var(--athena-neg) 16%, transparent); color: var(--athena-neg);
517
+ }
518
+ .status_tag.warning, .status_tag.pending {
519
+ background: color-mix(in srgb, var(--athena-amber) 16%, transparent); color: var(--athena-amber);
520
+ }
521
+ }
@@ -0,0 +1,35 @@
1
+ <%# Athena override of ActiveAdmin's _html_head: keeps AA's head verbatim, then appends a %>
2
+ <%# tiny enhancer that copies column headers onto each table cell as data-label, so the %>
3
+ <%# CSS card layout (max-width:640px) can label fields when the table restacks on mobile. %>
4
+ <%= stylesheet_link_tag "active_admin" %>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <% # On page load or when changing themes, best to add inline in `head` to avoid FOUC %>
9
+ <%= javascript_tag nonce: true do %>
10
+ if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
11
+ document.documentElement.classList.add('dark')
12
+ } else {
13
+ document.documentElement.classList.remove('dark')
14
+ }
15
+ <% end %>
16
+ <%= javascript_importmap_tags "active_admin", importmap: ActiveAdmin.importmap %>
17
+ <%= javascript_tag nonce: true do %>
18
+ (function () {
19
+ function athenaLabelize() {
20
+ document.querySelectorAll('table.data-table').forEach(function (t) {
21
+ var heads = Array.prototype.map.call(t.querySelectorAll('thead th'), function (th) {
22
+ return th.textContent.trim();
23
+ });
24
+ t.querySelectorAll('tbody tr').forEach(function (tr) {
25
+ Array.prototype.forEach.call(tr.children, function (td, i) {
26
+ if (heads[i]) td.setAttribute('data-label', heads[i]);
27
+ });
28
+ });
29
+ });
30
+ }
31
+ document.addEventListener('DOMContentLoaded', athenaLabelize);
32
+ document.addEventListener('turbo:load', athenaLabelize);
33
+ document.addEventListener('turbo:render', athenaLabelize);
34
+ })();
35
+ <% end %>
@@ -0,0 +1,65 @@
1
+ <%#
2
+ Athena "Ember Command Rail" — artful main navigation.
3
+ Keeps AA's #main-menu container + translate classes intact (Flowbite drawer toggle),
4
+ and restyles items as icon-tile rows with a glowing fire active state.
5
+ %>
6
+ <%
7
+ athena_nav_icon = lambda do |label|
8
+ case label.to_s.strip.downcase
9
+ when "dashboard"
10
+ '<rect x="3" y="3" width="7.5" height="7.5" rx="2"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="2"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="2"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="2"/>'
11
+ when "users", "admin users", "accounts"
12
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.5a6 6 0 0 0-12 0M9 11.25a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5ZM21 19.5a5.99 5.99 0 0 0-4.5-5.805M15.75 3.97a3.75 3.75 0 0 1 0 7.06"/>'
13
+ when "transactions", "ledger"
14
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-9L21 3m0 0-4.5 4.5M21 3H7.5"/>'
15
+ when "loans", "charity", "budget"
16
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33"/>'
17
+ when "properties", "real estate"
18
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21"/>'
19
+ when "businessmen", "people", "motivation"
20
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"/>'
21
+ else
22
+ '<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c1.6 3.2 4.5 4.6 4.5 8.2a4.5 4.5 0 1 1-9 0c0-1.3.5-2.4 1.3-3.2.3 1.1 1 1.8 1.9 2C10.5 7.6 10.6 5.2 12 3Z"/>'
23
+ end
24
+ end
25
+ %>
26
+ <div id="main-menu" class="athena-rail fixed top-0 xl:top-16 bottom-0 inset-s-0 z-40 w-72 xl:w-60 p-3 overflow-y-auto transition-transform duration-200 -translate-x-full xl:translate-x-0" tabindex="-1">
27
+ <p class="athena-rail-cap">Navigation</p>
28
+ <ul role="list" class="flex flex-1 flex-col gap-1">
29
+ <% current_menu.items(self).each do |item| %>
30
+ <% children = item.items(self).presence %>
31
+ <% url = item.url(self) %>
32
+ <% active = current_menu_item?(item, children: false) %>
33
+ <li class="athena-nav-li group" <%= "data-open" if current_menu_item?(item) %> data-item-id="<%= item.id %>">
34
+ <% if children %>
35
+ <button data-menu-button class="athena-nav athena-nav-parent w-full" aria-label="<%= t('active_admin.toggle_section') %>">
36
+ <span class="athena-nav-ic">
37
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><%== athena_nav_icon.call(item.label(self)) %></svg>
38
+ </span>
39
+ <span class="athena-nav-label"><%= item.label(self) %></span>
40
+ <svg class="athena-nav-caret group-data-open:rotate-90 rtl:-scale-x-100" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
41
+ <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02z" clip-rule="evenodd" />
42
+ </svg>
43
+ </button>
44
+ <ul role="list" class="athena-subnav hidden group-data-open:block">
45
+ <% children.each do |j| %>
46
+ <li data-item-id="<%= j.id %>">
47
+ <%= link_to j.url(self), j.html_options.merge(class: "athena-subnav-link #{'is-active' if current_menu_item?(j)}") do %>
48
+ <span class="athena-subnav-dot"></span><%= j.label(self) %>
49
+ <% end %>
50
+ </li>
51
+ <% end %>
52
+ </ul>
53
+ <% else %>
54
+ <%= link_to url, item.html_options.merge(class: "athena-nav #{'is-active selected' if active}") do %>
55
+ <span class="athena-nav-ic">
56
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><%== athena_nav_icon.call(item.label(self)) %></svg>
57
+ </span>
58
+ <span class="athena-nav-label"><%= item.label(self) %></span>
59
+ <span class="athena-nav-spark"></span>
60
+ <% end %>
61
+ <% end %>
62
+ </li>
63
+ <% end %>
64
+ </ul>
65
+ </div>
@@ -0,0 +1,34 @@
1
+ <%# Athena top bar — ember gradient, flame brand mark, glowing icon buttons. Keeps all of
2
+ AA's functional hooks: drawer toggle, .dark-mode-toggle, #user-menu-button + #user-menu. %>
3
+ <div class="athena-topbar px-4 py-2 flex items-center fixed top-0 z-20 h-16 w-full backdrop-blur-md">
4
+ <button class="athena-icon-btn xl:hidden me-1" data-drawer-target="main-menu" data-drawer-show="main-menu" aria-controls="main-menu" aria-label="<%= t('active_admin.toggle_main_navigation_menu') %>">
5
+ <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/></svg>
6
+ </button>
7
+
8
+ <div class="grow flex items-center gap-2.5">
9
+ <span class="athena-brand-mark" aria-hidden="true">
10
+ <svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.7"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c1.7 3.3 4.7 4.7 4.7 8.4a4.7 4.7 0 1 1-9.4 0c0-1.35.55-2.5 1.35-3.3.3 1.15 1.05 1.85 2 2.05C11 7.7 11.1 5.2 12 3Z"/></svg>
11
+ </span>
12
+ <h1 data-test-site-title class="athena-brand-word text-lg font-semibold">
13
+ <%= title %>
14
+ </h1>
15
+ </div>
16
+
17
+ <button type="button" class="dark-mode-toggle athena-icon-btn me-1" aria-label="<%= t('active_admin.toggle_dark_mode') %>">
18
+ <svg class="hidden dark:block w-5 h-5 rtl:-scale-x-100" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.509 5.75c0-1.493.394-2.96 1.144-4.25h-.081a8.5 8.5 0 1 0 7.356 12.746A8.5 8.5 0 0 1 8.509 5.75Z"/></svg>
19
+ <svg class="dark:hidden w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 3V1m0 18v-2M5.05 5.05 3.636 3.636m12.728 12.728L14.95 14.95M3 10H1m18 0h-2M5.05 14.95l-1.414 1.414M16.364 3.636 14.95 5.05M14 10a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/></svg>
20
+ </button>
21
+
22
+ <button id="user-menu-button" class="athena-icon-btn athena-avatar-btn" data-dropdown-toggle="user-menu" data-dropdown-offset-distance="6" data-dropdown-placement="bottom-end" aria-label="<%= t('active_admin.toggle_user_menu') %>">
23
+ <svg class="w-7 h-7" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"><path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/></svg>
24
+ </button>
25
+
26
+ <div id="user-menu" class="athena-menu z-50 hidden min-w-max focus:outline-hidden py-1.5 text-sm" aria-labelledby="user-menu-button">
27
+ <ul>
28
+ <% if current_active_admin_user? %>
29
+ <li><%= auto_link current_active_admin_user, class: "athena-menu-item block no-underline" %></li>
30
+ <li><%= link_to I18n.t("active_admin.logout"), auto_logout_link_path, class: "athena-menu-item athena-menu-danger block no-underline", data: { method: :delete } %></li>
31
+ <% end %>
32
+ </ul>
33
+ </div>
34
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,11 @@
1
+ module AthenaAdmin
2
+ class Engine < ::Rails::Engine
3
+ # Prepend the gem's view path so our partials (e.g. active_admin/_html_head)
4
+ # take precedence over ActiveAdmin's same-path partials.
5
+ initializer "athena_admin.prepend_views" do
6
+ ActiveSupport.on_load(:action_controller_base) do
7
+ prepend_view_path AthenaAdmin::Engine.root.join("app/views")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module AthenaAdmin
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "athena_admin/version"
2
+ require "athena_admin/engine"
3
+
4
+ module AthenaAdmin
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,51 @@
1
+ require "rails/generators/base"
2
+
3
+ module AthenaAdmin
4
+ module Generators
5
+ # Wires the Athena (Ember) theme into a host ActiveAdmin 4 app:
6
+ # - vendors the token/component CSS into app/assets/tailwind (out of the served path)
7
+ # - imports it into the AA tailwind build
8
+ # - registers the gem's views as a tailwind content source (so partial classes scan)
9
+ # - copies self-hosted fonts to public/athena-fonts
10
+ class InstallGenerator < Rails::Generators::Base
11
+ desc "Install the Athena (Ember) theme into this ActiveAdmin 4 app."
12
+
13
+ AA_TW_SRC = "app/assets/tailwind/active_admin.css".freeze
14
+ TW_CONF = "tailwind-active_admin.config.js".freeze
15
+
16
+ def vendor_theme_css
17
+ css = AthenaAdmin::Engine.root.join("app/assets/stylesheets/athena_admin.css").read
18
+ create_file "app/assets/tailwind/athena_admin.css", css
19
+ end
20
+
21
+ def import_into_build
22
+ unless File.exist?(AA_TW_SRC)
23
+ say "Skipped @import: #{AA_TW_SRC} not found — run ActiveAdmin's tailwind setup first.", :yellow
24
+ return
25
+ end
26
+ import_line = %(@import "./athena_admin.css";\n)
27
+ return if File.read(AA_TW_SRC).include?(import_line)
28
+ inject_into_file AA_TW_SRC, import_line, after: %(@import "tailwindcss";\n)
29
+ end
30
+
31
+ def add_view_content_source
32
+ return unless File.exist?(TW_CONF)
33
+ return if File.read(TW_CONF).include?("athena_admin")
34
+ snippet = " `${execSync('bundle show athena_admin', { encoding: 'utf-8' })" \
35
+ ".trim().split(/\\r?\\n/).pop()}/app/views/**/*.{erb,html,arb,rb}`,\n"
36
+ inject_into_file TW_CONF, snippet, after: "content: [\n"
37
+ end
38
+
39
+ def copy_fonts
40
+ src = AthenaAdmin::Engine.root.join("app/assets/fonts/athena_admin")
41
+ src.children.select { |c| c.to_s.end_with?(".woff2") }.sort.each do |path|
42
+ create_file "public/athena-fonts/#{path.basename}", File.binread(path)
43
+ end
44
+ end
45
+
46
+ def done
47
+ say "✓ Athena installed. Rebuild CSS: `yarn build:css` (or `bin/rails css:build`).", :green
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :athena_admin do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: athena_admin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergio Reyes
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activeadmin
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 4.0.0.beta1
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '5'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 4.0.0.beta1
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '5'
46
+ description: 'Athena restyles ActiveAdmin 4 with the Ember design system: orange/fire
47
+ accent, monospace numerics, light + dark modes.'
48
+ email:
49
+ - sdreyesg@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - MIT-LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - app/assets/fonts/athena_admin/Inter-Bold.woff2
58
+ - app/assets/fonts/athena_admin/Inter-Regular.woff2
59
+ - app/assets/fonts/athena_admin/Inter-SemiBold.woff2
60
+ - app/assets/fonts/athena_admin/JetBrainsMono-Medium.woff2
61
+ - app/assets/fonts/athena_admin/JetBrainsMono-Regular.woff2
62
+ - app/assets/stylesheets/athena_admin.css
63
+ - app/views/active_admin/_html_head.html.erb
64
+ - app/views/active_admin/_main_navigation.html.erb
65
+ - app/views/active_admin/_site_header.html.erb
66
+ - config/routes.rb
67
+ - lib/athena_admin.rb
68
+ - lib/athena_admin/engine.rb
69
+ - lib/athena_admin/version.rb
70
+ - lib/generators/athena_admin/install/install_generator.rb
71
+ - lib/tasks/athena_admin_tasks.rake
72
+ homepage: https://github.com/BarbaricCorgi/athena_admin
73
+ licenses:
74
+ - MIT
75
+ metadata:
76
+ homepage_uri: https://github.com/BarbaricCorgi/athena_admin
77
+ source_code_uri: https://github.com/BarbaricCorgi/athena_admin
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '3.2'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 4.0.10
93
+ specification_version: 4
94
+ summary: Ember — a dark-first, mobile-first ActiveAdmin 4 theme.
95
+ test_files: []