one-for-all-framework 1.0.0 → 3.0.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 +4 -4
- data/README.md +77 -19
- data/app/controllers/api_controller.rb +1 -1
- data/app/controllers/application_controller.rb +2 -2
- data/app/controllers/cart_controller.rb +52 -0
- data/app/controllers/dashboard_controller.rb +4 -3
- data/app/controllers/products_controller.rb +58 -0
- data/app/models/product.rb +8 -0
- data/app/views/cart_index.erb +112 -0
- data/app/views/cms/pages_form.erb +1 -1
- data/app/views/cms/pages_index.erb +61 -45
- data/app/views/cms/posts_form.erb +1 -1
- data/app/views/cms/posts_index.erb +54 -41
- data/app/views/cms/products_form.erb +89 -0
- data/app/views/cms/products_index.erb +129 -0
- data/app/views/cms/projects_form.erb +8 -8
- data/app/views/cms/projects_index.erb +47 -37
- data/app/views/dashboard.erb +18 -0
- data/app/views/docs.erb +180 -28
- data/app/views/index.erb +1 -1
- data/app/views/layout.erb +299 -20
- data/app/views/login.erb +1 -1
- data/app/views/product_show.erb +42 -0
- data/app/views/products_index.erb +40 -0
- data/bin/ofa +38 -9
- data/config/boot.rb +1 -0
- data/config/features.json +1 -1
- data/config/routes.rb +32 -0
- data/db/development.sqlite3 +0 -0
- data/db/migrations/20260502000000_create_products.rb +17 -0
- metadata +10 -1
data/app/views/layout.erb
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<!-- Fonts & Icons -->
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Fira+Code&display=swap" rel="stylesheet">
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Fira+Code&family=VT323&display=swap" rel="stylesheet">
|
|
13
13
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
14
14
|
|
|
15
15
|
<!-- Tailwind CSS CDN -->
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
theme: {
|
|
28
28
|
extend: {
|
|
29
29
|
colors: {
|
|
30
|
-
primary: '<%= FEATURES_CONFIG["theme"] == "
|
|
31
|
-
secondary: '<%= FEATURES_CONFIG["theme"] == "
|
|
32
|
-
surface: '<%= FEATURES_CONFIG["theme"] == "
|
|
33
|
-
bg: '<%= FEATURES_CONFIG["theme"] == "
|
|
30
|
+
primary: '<%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#4f46e5" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#f0f" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#0f0" : "#818cf8")) %>',
|
|
31
|
+
secondary: '<%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#8b5cf6" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#0ff" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#040" : "#c084fc")) %>',
|
|
32
|
+
surface: '<%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "rgba(255, 255, 255, 0.7)" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "rgba(20, 20, 20, 0.8)" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#050505" : "rgba(17, 24, 39, 0.6)")) %>',
|
|
33
|
+
bg: '<%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#f8fafc" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#000" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#0a0a0a" : "#030712")) %>',
|
|
34
34
|
},
|
|
35
35
|
fontFamily: {
|
|
36
|
-
sans: ['Outfit', 'sans-serif'],
|
|
36
|
+
sans: ['<%= FEATURES_CONFIG["theme"] == "retro_terminal" ? "VT323" : "Outfit" %>', 'sans-serif'],
|
|
37
37
|
mono: ['Fira Code', 'monospace'],
|
|
38
38
|
},
|
|
39
39
|
backdropBlur: {
|
|
@@ -46,15 +46,29 @@
|
|
|
46
46
|
|
|
47
47
|
<style>
|
|
48
48
|
:root {
|
|
49
|
-
--primary: <%= FEATURES_CONFIG["theme"] == "
|
|
50
|
-
--secondary: <%= FEATURES_CONFIG["theme"] == "
|
|
51
|
-
--surface: <%= FEATURES_CONFIG["theme"] == "
|
|
52
|
-
--bg-color: <%= FEATURES_CONFIG["theme"] == "
|
|
53
|
-
--glass-border: <%= FEATURES_CONFIG["theme"]
|
|
49
|
+
--primary: <%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#4f46e5" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#f0f" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#0f0" : "#818cf8")) %>;
|
|
50
|
+
--secondary: <%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#8b5cf6" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#0ff" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#040" : "#c084fc")) %>;
|
|
51
|
+
--surface: <%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "rgba(255, 255, 255, 0.7)" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "rgba(20, 20, 20, 0.8)" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#050505" : "rgba(17, 24, 39, 0.6)")) %>;
|
|
52
|
+
--bg-color: <%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "#f8fafc" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#000" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#0a0a0a" : "#030712")) %>;
|
|
53
|
+
--glass-border: <%= ["light_glass", "light_sidebar"].include?(FEATURES_CONFIG["theme"]) ? "rgba(0, 0, 0, 0.05)" : (FEATURES_CONFIG["theme"] == "cyber_sidebar" ? "#f0f" : (FEATURES_CONFIG["theme"] == "retro_terminal" ? "#0f0" : "rgba(255, 255, 255, 0.1)")) %>;
|
|
54
|
+
--neo-shadow: 8px 8px 0px 0px var(--primary);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
body {
|
|
57
58
|
background-color: var(--bg-color);
|
|
59
|
+
<% if FEATURES_CONFIG['theme'] == 'cyber_sidebar' %>
|
|
60
|
+
background-image:
|
|
61
|
+
linear-gradient(rgba(255, 0, 255, 0.05) 1px, transparent 1px),
|
|
62
|
+
linear-gradient(90deg, rgba(255, 0, 255, 0.05) 1px, transparent 1px);
|
|
63
|
+
background-size: 50px 50px;
|
|
64
|
+
<% elsif FEATURES_CONFIG['theme'] == 'light_sidebar' %>
|
|
65
|
+
background-image:
|
|
66
|
+
radial-gradient(at 0% 0%, hsla(220,100%,95%,1) 0, transparent 50%),
|
|
67
|
+
radial-gradient(at 100% 0%, hsla(320,100%,95%,0.8) 0, transparent 50%);
|
|
68
|
+
<% elsif FEATURES_CONFIG['theme'] == 'retro_terminal' %>
|
|
69
|
+
background-image: radial-gradient(circle, rgba(0, 255, 0, 0.05) 1px, transparent 1px);
|
|
70
|
+
background-size: 20px 20px;
|
|
71
|
+
<% else %>
|
|
58
72
|
background-image:
|
|
59
73
|
<% if FEATURES_CONFIG['theme'] == 'light_glass' %>
|
|
60
74
|
radial-gradient(at 0% 0%, hsla(220,100%,95%,1) 0, transparent 50%),
|
|
@@ -65,7 +79,25 @@
|
|
|
65
79
|
radial-gradient(at 50% 0%, hsla(225,39%,30%,0.15) 0, transparent 50%),
|
|
66
80
|
radial-gradient(at 100% 0%, hsla(339,49%,30%,0.15) 0, transparent 50%);
|
|
67
81
|
<% end %>
|
|
82
|
+
<% end %>
|
|
68
83
|
background-attachment: fixed;
|
|
84
|
+
<% if FEATURES_CONFIG['theme'] == 'retro_terminal' %>
|
|
85
|
+
overflow-x: hidden;
|
|
86
|
+
<% end %>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.neo-card {
|
|
90
|
+
background: var(--surface);
|
|
91
|
+
border: 3px solid var(--primary);
|
|
92
|
+
box-shadow: var(--neo-shadow);
|
|
93
|
+
border-radius: 0px;
|
|
94
|
+
padding: 2rem;
|
|
95
|
+
transition: all 0.2s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.neo-card:hover {
|
|
99
|
+
transform: translate(-4px, -4px);
|
|
100
|
+
box-shadow: 12px 12px 0px 0px var(--secondary);
|
|
69
101
|
}
|
|
70
102
|
|
|
71
103
|
.glass-panel {
|
|
@@ -184,9 +216,95 @@
|
|
|
184
216
|
.markdown-content h1 { font-size: 2.25rem; font-weight: 800; margin: 2rem 0 1rem; color: var(--primary); }
|
|
185
217
|
.markdown-content p { font-size: 1.125rem; color: #475569; margin-bottom: 1.5rem; }
|
|
186
218
|
.dark .markdown-content p { color: #cbd5e1; }
|
|
219
|
+
|
|
220
|
+
/* Cyber Theme Specifics */
|
|
221
|
+
.cyber-border {
|
|
222
|
+
border: 2px solid var(--primary);
|
|
223
|
+
box-shadow: 4px 4px 0px var(--secondary);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.cyber-btn {
|
|
227
|
+
background: var(--primary);
|
|
228
|
+
color: black;
|
|
229
|
+
font-weight: 900;
|
|
230
|
+
text-transform: uppercase;
|
|
231
|
+
padding: 1rem 2rem;
|
|
232
|
+
border: none;
|
|
233
|
+
box-shadow: 6px 6px 0px var(--secondary);
|
|
234
|
+
transition: all 0.1s;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.cyber-btn:active {
|
|
238
|
+
transform: translate(2px, 2px);
|
|
239
|
+
box-shadow: 4px 4px 0px var(--secondary);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Global Text Color for Dark Themes */
|
|
243
|
+
.dark {
|
|
244
|
+
color: #e2e8f0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.dark h1, .dark h2, .dark h3, .dark h4, .dark h5, .dark h6 {
|
|
248
|
+
color: <%= FEATURES_CONFIG['theme'] == 'retro_terminal' ? '#0f0' : '#ffffff' %> !important;
|
|
249
|
+
<% if FEATURES_CONFIG['theme'] == 'retro_terminal' %>
|
|
250
|
+
text-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
|
|
251
|
+
text-transform: uppercase;
|
|
252
|
+
letter-spacing: 2px;
|
|
253
|
+
<% end %>
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.dark p, .dark span, .dark li, .dark a {
|
|
257
|
+
color: <%= FEATURES_CONFIG['theme'] == 'retro_terminal' ? '#0f0' : 'rgba(255, 255, 255, 0.9)' %>;
|
|
258
|
+
<% if FEATURES_CONFIG['theme'] == 'retro_terminal' %>
|
|
259
|
+
font-family: 'VT323', monospace;
|
|
260
|
+
font-size: 1.25rem;
|
|
261
|
+
<% end %>
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.dark .text-slate-500, .dark .text-slate-600, .dark .text-slate-700 {
|
|
265
|
+
color: <%= FEATURES_CONFIG['theme'] == 'retro_terminal' ? '#0a0' : 'rgba(255, 255, 255, 0.7)' %> !important;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Scanlines for Retro Theme */
|
|
269
|
+
<% if FEATURES_CONFIG['theme'] == 'retro_terminal' %>
|
|
270
|
+
body::before {
|
|
271
|
+
content: " ";
|
|
272
|
+
display: block;
|
|
273
|
+
position: fixed;
|
|
274
|
+
top: 0; left: 0; bottom: 0; right: 0;
|
|
275
|
+
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
|
|
276
|
+
linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
|
|
277
|
+
z-index: 999;
|
|
278
|
+
background-size: 100% 2px, 3px 100%;
|
|
279
|
+
pointer-events: none;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@keyframes flicker {
|
|
283
|
+
0% { opacity: 0.97; }
|
|
284
|
+
5% { opacity: 0.95; }
|
|
285
|
+
10% { opacity: 0.9; }
|
|
286
|
+
15% { opacity: 0.95; }
|
|
287
|
+
20% { opacity: 0.98; }
|
|
288
|
+
100% { opacity: 1; }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.dark main {
|
|
292
|
+
animation: flicker 0.15s infinite;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Fix visibility for buttons with background in Retro theme */
|
|
296
|
+
.dark .btn-premium,
|
|
297
|
+
.dark .bg-primary,
|
|
298
|
+
.dark .bg-white,
|
|
299
|
+
.dark .bg-primary > *,
|
|
300
|
+
.dark .bg-white > * {
|
|
301
|
+
color: #000000 !important;
|
|
302
|
+
text-shadow: none !important;
|
|
303
|
+
}
|
|
304
|
+
<% end %>
|
|
187
305
|
</style>
|
|
188
306
|
</head>
|
|
189
|
-
<body class="<%=
|
|
307
|
+
<body class="<%= ['dark_glass', 'cyber_sidebar', 'retro_terminal'].include?(FEATURES_CONFIG['theme']) ? 'dark' : '' %>">
|
|
190
308
|
|
|
191
309
|
<!-- Animated Background Shapes -->
|
|
192
310
|
<div class="fixed inset-0 -z-10 overflow-hidden opacity-30 pointer-events-none">
|
|
@@ -194,7 +312,76 @@
|
|
|
194
312
|
<div class="absolute top-1/2 -right-24 w-80 h-80 bg-secondary rounded-full blur-[100px] delay-700 animate-bounce"></div>
|
|
195
313
|
</div>
|
|
196
314
|
|
|
197
|
-
|
|
315
|
+
<% if ['cyber_sidebar', 'light_sidebar'].include?(FEATURES_CONFIG['theme']) %>
|
|
316
|
+
<!-- Sidebar Layout -->
|
|
317
|
+
<div class="flex min-h-screen">
|
|
318
|
+
<!-- Sidebar -->
|
|
319
|
+
<aside class="fixed left-0 top-0 bottom-0 w-64 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'glass-panel !rounded-none border-r border-black/5' : 'bg-black border-r-4 border-primary shadow-[10px_0_30px_rgba(0,0,0,0.5)]' %> p-6 pb-12 z-50 hidden md:flex flex-col gap-10 overflow-y-auto custom-scrollbar">
|
|
320
|
+
<div class="flex items-center gap-3">
|
|
321
|
+
<% if FEATURES_CONFIG['theme'] == 'light_sidebar' %>
|
|
322
|
+
<div class="w-10 h-10 bg-primary text-white flex items-center justify-center font-black text-xl rounded-xl shadow-lg shadow-primary/20">OF</div>
|
|
323
|
+
<span class="text-2xl font-black tracking-tighter text-slate-800">SYSTEM</span>
|
|
324
|
+
<% else %>
|
|
325
|
+
<div class="w-10 h-10 bg-primary flex items-center justify-center text-white font-black text-xl">OF</div>
|
|
326
|
+
<span class="text-2xl font-black tracking-tighter text-white">SYSTEM</span>
|
|
327
|
+
<% end %>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<nav class="flex flex-col gap-2">
|
|
331
|
+
<a href="/" class="p-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? (req.path == '/' ? 'bg-primary/10 text-primary rounded-xl' : 'text-slate-600 hover:bg-primary/5 rounded-xl') : (req.path == '/' ? 'bg-primary text-white border-primary border-2 rounded-2xl' : 'border-white/10 text-white hover:border-primary border-2 rounded-2xl') %> font-bold transition-all">
|
|
332
|
+
<i class="fas fa-grid-2 mr-3"></i> OVERVIEW
|
|
333
|
+
</a>
|
|
334
|
+
<% if FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
335
|
+
<a href="/cart" class="p-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? (req.path == '/cart' ? 'bg-primary/10 text-primary rounded-xl' : 'text-slate-600 hover:bg-primary/5 rounded-xl') : (req.path == '/cart' ? 'bg-primary text-white border-primary border-2 rounded-2xl' : 'border-white/10 text-white hover:border-primary border-2 rounded-2xl') %> font-bold transition-all">
|
|
336
|
+
<i class="fas fa-shopping-cart mr-3"></i> MY CART
|
|
337
|
+
</a>
|
|
338
|
+
<% end %>
|
|
339
|
+
<% Page.where(is_nav: true, is_active: true).all.each do |p| %>
|
|
340
|
+
<a href="/<%= p.slug %>" class="p-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? (req.path == "/#{p.slug}" ? 'bg-primary/10 text-primary rounded-xl' : 'text-slate-600 hover:bg-primary/5 rounded-xl') : (req.path == "/#{p.slug}" ? 'bg-primary text-white border-primary border-2 rounded-2xl' : 'border-white/10 text-white hover:border-primary border-2 rounded-2xl') %> font-bold transition-all">
|
|
341
|
+
<i class="fas fa-file-code mr-3"></i> <%= p.title.upcase %>
|
|
342
|
+
</a>
|
|
343
|
+
<% end %>
|
|
344
|
+
<a href="/dashboard" class="p-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? (req.path.start_with?('/dashboard') ? 'bg-primary/10 text-primary rounded-xl' : 'text-slate-600 hover:bg-primary/5 rounded-xl') : (req.path.start_with?('/dashboard') ? 'bg-primary text-white border-primary border-2 rounded-2xl' : 'border-white/10 text-white hover:border-primary border-2 rounded-2xl') %> font-bold transition-all">
|
|
345
|
+
<i class="fas fa-terminal mr-3"></i> CONSOLE
|
|
346
|
+
</a>
|
|
347
|
+
</nav>
|
|
348
|
+
|
|
349
|
+
<div class="mt-auto space-y-4">
|
|
350
|
+
<div class="p-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'bg-slate-100/50 border border-slate-200 text-slate-500' : 'bg-white/5 border border-white/10 text-slate-400' %> text-[10px] font-mono rounded-xl">
|
|
351
|
+
<% if FEATURES_CONFIG['theme'] == 'light_sidebar' %>
|
|
352
|
+
SYSTEM: <span class="text-indigo-600 font-bold">OPERATIONAL</span><br>
|
|
353
|
+
LICENSE: <span class="text-slate-800">ENTERPRISE</span>
|
|
354
|
+
<% else %>
|
|
355
|
+
PROTOCOL: <span class="text-green-500 animate-pulse">SECURED</span><br>
|
|
356
|
+
UPLINK: <span class="text-primary">ENCRYPTED</span>
|
|
357
|
+
<% end %>
|
|
358
|
+
</div>
|
|
359
|
+
<a href="https://github.com/IshikawaUta/one-for-all-framework" class="flex items-center justify-center w-full p-4 mb-4 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'bg-slate-900 text-white rounded-xl shadow-lg shadow-slate-200' : 'bg-primary text-black border-2 border-primary rounded-2xl shadow-[4px_4px_0px_var(--secondary)]' %> font-black hover:opacity-90 hover:-translate-y-0.5 transition-all">
|
|
360
|
+
<i class="fab fa-github mr-2 text-lg"></i> <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'VIEW SOURCE' : 'ACCESS REPO' %>
|
|
361
|
+
</a>
|
|
362
|
+
</div>
|
|
363
|
+
</aside>
|
|
364
|
+
|
|
365
|
+
<!-- Mobile Header for Sidebar Themes -->
|
|
366
|
+
<header class="md:hidden fixed top-0 left-0 right-0 h-16 <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'glass-panel !rounded-none border-b border-black/5' : 'bg-black border-b-4 border-primary' %> z-[60] flex items-center justify-between px-6">
|
|
367
|
+
<span class="font-black <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'text-slate-800' : 'text-white' %>">OFA<span class="text-primary">.SYS</span></span>
|
|
368
|
+
<button id="mobile-menu-btn" class="text-primary"><i class="fas fa-bars text-2xl"></i></button>
|
|
369
|
+
</header>
|
|
370
|
+
|
|
371
|
+
<!-- Main Content Area -->
|
|
372
|
+
<main class="flex-1 md:ml-64 p-6 md:p-12 pt-24 md:pt-12">
|
|
373
|
+
<div class="max-w-5xl mx-auto">
|
|
374
|
+
<%= @content %>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<!-- Footer -->
|
|
378
|
+
<footer class="w-full py-12 mt-20 border-t <%= FEATURES_CONFIG['theme'] == 'light_sidebar' ? 'border-black/5' : 'border-white/5' %> text-center text-slate-500 text-sm">
|
|
379
|
+
<p>© <%= Time.now.year %> One-For-All Framework.</p>
|
|
380
|
+
</footer>
|
|
381
|
+
</main>
|
|
382
|
+
</div>
|
|
383
|
+
<% else %>
|
|
384
|
+
<!-- Navigation (Original Top Nav) -->
|
|
198
385
|
<header class="w-full flex justify-center p-4 sticky top-0 z-50">
|
|
199
386
|
<nav class="glass-panel w-full max-w-4xl px-8 py-4 flex items-center justify-between">
|
|
200
387
|
<div class="flex items-center gap-3">
|
|
@@ -203,6 +390,9 @@
|
|
|
203
390
|
|
|
204
391
|
<ul class="hidden md:flex items-center gap-8">
|
|
205
392
|
<li><a href="/" class="nav-item <%= req.path == '/' ? 'active' : '' %>">Home</a></li>
|
|
393
|
+
<% if FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
394
|
+
<li><a href="/cart" class="nav-item <%= req.path == '/cart' ? 'active' : '' %>"><i class="fas fa-shopping-cart mr-1"></i> Cart</a></li>
|
|
395
|
+
<% end %>
|
|
206
396
|
<% Page.where(is_nav: true, is_active: true).all.each do |p| %>
|
|
207
397
|
<li><a href="/<%= p.slug %>" class="nav-item <%= req.path == "/#{p.slug}" ? 'active' : '' %>"><%= p.title %></a></li>
|
|
208
398
|
<% end %>
|
|
@@ -210,20 +400,64 @@
|
|
|
210
400
|
</ul>
|
|
211
401
|
|
|
212
402
|
<div class="flex items-center gap-4">
|
|
213
|
-
<a href="https://github.com" class="text-slate-400 hover:text-primary transition-colors"><i class="fab fa-github text-xl"></i></a>
|
|
214
|
-
<button class="md:hidden text-slate-400
|
|
403
|
+
<a href="https://github.com/IshikawaUta/one-for-all-framework" class="text-slate-400 hover:text-primary transition-colors"><i class="fab fa-github text-xl"></i></a>
|
|
404
|
+
<button id="mobile-menu-btn" class="md:hidden text-slate-400 hover:text-primary transition-colors">
|
|
405
|
+
<i class="fas fa-bars text-xl"></i>
|
|
406
|
+
</button>
|
|
215
407
|
</div>
|
|
216
408
|
</nav>
|
|
217
409
|
</header>
|
|
218
410
|
|
|
219
|
-
|
|
411
|
+
|
|
412
|
+
<main class="container mx-auto max-w-6xl px-6 py-12">
|
|
220
413
|
<%= @content %>
|
|
414
|
+
|
|
415
|
+
<!-- Footer -->
|
|
416
|
+
<footer class="w-full py-12 mt-20 border-t border-white/5 text-center text-slate-500 text-sm">
|
|
417
|
+
<p>© <%= Time.now.year %> One-For-All Framework.</p>
|
|
418
|
+
</footer>
|
|
221
419
|
</main>
|
|
420
|
+
<% end %>
|
|
421
|
+
|
|
422
|
+
<!-- Mobile Menu Overlay (Shared) -->
|
|
423
|
+
<div id="mobile-menu" class="fixed inset-0 z-[100] hidden">
|
|
424
|
+
<div id="mobile-menu-backdrop" class="absolute inset-0 bg-black/40 backdrop-blur-sm opacity-0"></div>
|
|
425
|
+
<div id="mobile-menu-content" class="absolute right-0 top-0 bottom-0 w-72 glass-panel !rounded-none border-l <%= ['light_sidebar', 'light_glass'].include?(FEATURES_CONFIG['theme']) ? 'border-black/5' : 'border-white/10' %> p-8 flex flex-col translate-x-full">
|
|
426
|
+
<div class="flex justify-between items-center mb-10">
|
|
427
|
+
<span class="text-xl font-black tracking-tighter">OFA<span class="text-primary">.menu</span></span>
|
|
428
|
+
<button id="mobile-menu-close" class="text-slate-400 hover:text-red-400 transition-colors">
|
|
429
|
+
<i class="fas fa-times text-2xl"></i>
|
|
430
|
+
</button>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<nav class="flex flex-col gap-6">
|
|
434
|
+
<a href="/" class="text-lg font-bold <%= req.path == '/' ? 'text-primary' : 'text-slate-500' %>">
|
|
435
|
+
<i class="fas fa-home w-8"></i> Home
|
|
436
|
+
</a>
|
|
437
|
+
<% if FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
438
|
+
<a href="/cart" class="text-lg font-bold <%= req.path == '/cart' ? 'text-primary' : 'text-slate-500' %>">
|
|
439
|
+
<i class="fas fa-shopping-cart w-8"></i> Cart
|
|
440
|
+
</a>
|
|
441
|
+
<% end %>
|
|
442
|
+
<% Page.where(is_nav: true, is_active: true).all.each do |p| %>
|
|
443
|
+
<a href="/<%= p.slug %>" class="text-lg font-bold <%= req.path == "/#{p.slug}" ? 'text-primary' : 'text-slate-500' %>">
|
|
444
|
+
<i class="fas fa-file-alt w-8"></i> <%= p.title %>
|
|
445
|
+
</a>
|
|
446
|
+
<% end %>
|
|
447
|
+
<div class="h-px <%= ['light_sidebar', 'light_glass'].include?(FEATURES_CONFIG['theme']) ? 'bg-black/10' : 'bg-white/5' %> my-2"></div>
|
|
448
|
+
<a href="/dashboard" class="text-lg font-bold <%= req.path.start_with?('/dashboard') ? 'text-primary' : (['light_sidebar', 'light_glass'].include?(FEATURES_CONFIG['theme']) ? 'text-slate-600' : 'text-slate-500') %>">
|
|
449
|
+
<i class="fas fa-gauge-high w-8"></i> Dashboard
|
|
450
|
+
</a>
|
|
451
|
+
</nav>
|
|
452
|
+
|
|
453
|
+
<div class="mt-auto pt-10 border-t <%= ['light_sidebar', 'light_glass'].include?(FEATURES_CONFIG['theme']) ? 'border-black/10' : 'border-white/5' %> flex gap-6 justify-center">
|
|
454
|
+
<a href="#" class="text-slate-400 hover:text-primary text-xl"><i class="fab fa-twitter"></i></a>
|
|
455
|
+
<a href="#" class="text-slate-400 hover:text-primary text-xl"><i class="fab fa-discord"></i></a>
|
|
456
|
+
<a href="https://github.com/IshikawaUta/one-for-all-framework" class="text-slate-400 hover:text-primary text-xl"><i class="fab fa-github"></i></a>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
222
460
|
|
|
223
|
-
<!-- Footer -->
|
|
224
|
-
<footer class="w-full py-12 mt-20 border-t border-white/5 text-center text-slate-500 text-sm">
|
|
225
|
-
<p>© <%= Time.now.year %> One-For-All Framework.</p>
|
|
226
|
-
</footer>
|
|
227
461
|
|
|
228
462
|
<script>
|
|
229
463
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -244,6 +478,51 @@
|
|
|
244
478
|
duration: 1000,
|
|
245
479
|
easing: 'easeOutCubic'
|
|
246
480
|
});
|
|
481
|
+
|
|
482
|
+
// Mobile Menu Logic
|
|
483
|
+
const menuBtn = document.getElementById('mobile-menu-btn');
|
|
484
|
+
const menuClose = document.getElementById('mobile-menu-close');
|
|
485
|
+
const menu = document.getElementById('mobile-menu');
|
|
486
|
+
const backdrop = document.getElementById('mobile-menu-backdrop');
|
|
487
|
+
const content = document.getElementById('mobile-menu-content');
|
|
488
|
+
let isMenuOpen = false;
|
|
489
|
+
|
|
490
|
+
const toggleMenu = () => {
|
|
491
|
+
isMenuOpen = !isMenuOpen;
|
|
492
|
+
if (isMenuOpen) {
|
|
493
|
+
menu.classList.remove('hidden');
|
|
494
|
+
anime({
|
|
495
|
+
targets: backdrop,
|
|
496
|
+
opacity: [0, 1],
|
|
497
|
+
duration: 400,
|
|
498
|
+
easing: 'easeOutQuad'
|
|
499
|
+
});
|
|
500
|
+
anime({
|
|
501
|
+
targets: content,
|
|
502
|
+
translateX: ['100%', '0%'],
|
|
503
|
+
duration: 600,
|
|
504
|
+
easing: 'easeOutExpo'
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
anime({
|
|
508
|
+
targets: backdrop,
|
|
509
|
+
opacity: 0,
|
|
510
|
+
duration: 300,
|
|
511
|
+
easing: 'easeInQuad'
|
|
512
|
+
});
|
|
513
|
+
anime({
|
|
514
|
+
targets: content,
|
|
515
|
+
translateX: '100%',
|
|
516
|
+
duration: 400,
|
|
517
|
+
easing: 'easeInExpo',
|
|
518
|
+
complete: () => menu.classList.add('hidden')
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
menuBtn.addEventListener('click', toggleMenu);
|
|
524
|
+
menuClose.addEventListener('click', toggleMenu);
|
|
525
|
+
backdrop.addEventListener('click', toggleMenu);
|
|
247
526
|
});
|
|
248
527
|
</script>
|
|
249
528
|
</body>
|
data/app/views/login.erb
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<div class="text-center pt-4 border-t border-white/5">
|
|
40
40
|
<p class="text-sm text-slate-500 dark:text-slate-400">
|
|
41
|
-
Forgot password? <a href="
|
|
41
|
+
Forgot password? <a href="mailto:komikers09@gmail.com" class="text-primary font-bold hover:underline">Contact System Admin</a>
|
|
42
42
|
</p>
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<a href="/products" class="text-slate-400 hover:text-primary transition-colors">
|
|
3
|
+
<i class="fas fa-arrow-left mr-2"></i> Back to Shop
|
|
4
|
+
</a>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="glass-panel p-8 md:p-12">
|
|
8
|
+
<div class="flex flex-col md:flex-row gap-12">
|
|
9
|
+
<div class="w-full md:w-1/2 aspect-square rounded-3xl overflow-hidden bg-slate-900/50 shadow-2xl">
|
|
10
|
+
<img src="<%= @product.image_url || '/images/logo.png' %>" class="w-full h-full object-cover">
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="w-full md:w-1/2 flex flex-col justify-center">
|
|
14
|
+
<div class="badge-premium"><%= @product.category || 'General' %></div>
|
|
15
|
+
<h1 class="text-4xl md:text-5xl font-black mb-4 tracking-tighter"><%= @product.name %></h1>
|
|
16
|
+
<div class="text-3xl font-bold text-primary mb-6">$<%= @product.price %></div>
|
|
17
|
+
|
|
18
|
+
<div class="markdown-content mb-10">
|
|
19
|
+
<%= markdown(@product.description) %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="flex items-center gap-6">
|
|
23
|
+
<form action="/cart/add" method="POST" class="flex items-center gap-4">
|
|
24
|
+
<%= csrf_tag %>
|
|
25
|
+
<input type="hidden" name="product_id" value="<%= @product.id %>">
|
|
26
|
+
<div class="flex items-center glass-panel !rounded-xl overflow-hidden">
|
|
27
|
+
<button type="button" onclick="this.nextElementSibling.stepDown()" class="px-4 py-2 hover:bg-white/5">-</button>
|
|
28
|
+
<input type="number" name="qty" value="1" min="1" class="w-12 bg-transparent text-center font-bold focus:outline-none">
|
|
29
|
+
<button type="button" onclick="this.previousElementSibling.stepUp()" class="px-4 py-2 hover:bg-white/5">+</button>
|
|
30
|
+
</div>
|
|
31
|
+
<button type="submit" class="btn-premium flex-1">
|
|
32
|
+
<i class="fas fa-shopping-cart mr-2"></i> Add to Cart
|
|
33
|
+
</button>
|
|
34
|
+
</form>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="mt-8 pt-8 border-t border-white/5 text-sm text-slate-500">
|
|
38
|
+
<i class="fas fa-cubes mr-2"></i> Stock available: <%= @product.stock %> items
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<div class="text-center mb-16">
|
|
2
|
+
<div class="badge-premium">E-Commerce Mode</div>
|
|
3
|
+
<h1 class="text-5xl font-black mb-4 tracking-tighter">Premium Shop</h1>
|
|
4
|
+
<p class="text-slate-500 text-lg">Curated collection of state-of-the-art digital assets.</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
8
|
+
<% @products.each do |product| %>
|
|
9
|
+
<div class="glass-card group">
|
|
10
|
+
<div class="card-glow"></div>
|
|
11
|
+
<div class="aspect-video mb-6 rounded-2xl overflow-hidden bg-slate-900/50 relative">
|
|
12
|
+
<img src="<%= product.image_url || '/images/logo.png' %>" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700">
|
|
13
|
+
<div class="absolute top-4 right-4 bg-primary text-white font-black px-4 py-2 rounded-xl shadow-xl">
|
|
14
|
+
$<%= product.price %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<h3 class="text-2xl font-black mb-2"><%= product.name %></h3>
|
|
18
|
+
<p class="text-slate-500 mb-6 line-clamp-2"><%= product.description %></p>
|
|
19
|
+
|
|
20
|
+
<div class="flex items-center justify-between mt-auto">
|
|
21
|
+
<a href="/products/<%= product.slug %>" class="btn-secondary">Details</a>
|
|
22
|
+
<form action="/cart/add" method="POST">
|
|
23
|
+
<%= csrf_tag %>
|
|
24
|
+
<input type="hidden" name="product_id" value="<%= product.id %>">
|
|
25
|
+
<button type="submit" class="btn-premium">
|
|
26
|
+
<i class="fas fa-shopping-cart mr-2"></i> Add to Cart
|
|
27
|
+
</button>
|
|
28
|
+
</form>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<% if @products.empty? %>
|
|
34
|
+
<div class="col-span-full glass-card text-center py-20">
|
|
35
|
+
<i class="fas fa-box-open text-6xl text-slate-300 mb-6"></i>
|
|
36
|
+
<h2 class="text-2xl font-bold text-slate-400">No products available yet.</h2>
|
|
37
|
+
<p class="text-slate-500">Visit dashboard to add your first product.</p>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
data/bin/ofa
CHANGED
|
@@ -17,6 +17,13 @@ rescue LoadError
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def help
|
|
20
|
+
puts " "
|
|
21
|
+
puts " ____ _______ ___ "
|
|
22
|
+
puts " / __ \\/ ____/ / | "
|
|
23
|
+
puts " / / / / /_ / /| | Framework "
|
|
24
|
+
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
25
|
+
puts "\\____/_/ /_/ |_| v2.0.0 "
|
|
26
|
+
puts " "
|
|
20
27
|
puts "✨ One-For-All Framework CLI ✨"
|
|
21
28
|
puts "-----------------------------"
|
|
22
29
|
puts "Usage:"
|
|
@@ -27,8 +34,8 @@ def help
|
|
|
27
34
|
puts " ofa g migration NAME - Generate a new migration"
|
|
28
35
|
puts " ofa g post TITLE - Create a new post [args: --category, --author, --image]"
|
|
29
36
|
puts " ofa feature ACTION F - Toggle features: action=enable/disable, F=cms/auth"
|
|
30
|
-
puts " ofa type NAME - Set application type: portfolio, blog, landing_page"
|
|
31
|
-
puts " ofa theme NAME - Set UI theme: light_glass, dark_glass"
|
|
37
|
+
puts " ofa type NAME - Set application type: portfolio, blog, landing_page, e_commerce"
|
|
38
|
+
puts " ofa theme NAME - Set UI theme: light_glass, dark_glass, cyber_sidebar, retro_terminal, light_sidebar"
|
|
32
39
|
puts " ofa storage NAME - Set image storage: local, cloudinary"
|
|
33
40
|
puts " ofa reset-password USR PWD - Reset admin account password"
|
|
34
41
|
puts " ofa db switch TYPE [NAME] - Switch DB: sqlite, mysql, mariadb, mongodb, postgres"
|
|
@@ -42,6 +49,7 @@ end
|
|
|
42
49
|
|
|
43
50
|
def run_migrations
|
|
44
51
|
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
52
|
+
ENV['SKIP_MODELS'] = '1'
|
|
45
53
|
require File.join(FRAMEWORK_ROOT, 'config', 'boot')
|
|
46
54
|
puts "Running migrations..."
|
|
47
55
|
migration_path = File.join(PROJECT_ROOT, "db/migrations")
|
|
@@ -101,6 +109,13 @@ when 'new'
|
|
|
101
109
|
|
|
102
110
|
when 'init'
|
|
103
111
|
app_type = ARGV.shift || 'landing_page'
|
|
112
|
+
puts " "
|
|
113
|
+
puts " ____ _______ ___ "
|
|
114
|
+
puts " / __ \\/ ____/ / | "
|
|
115
|
+
puts " / / / / /_ / /| | Framework "
|
|
116
|
+
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
117
|
+
puts "\\____/_/ /_/ |_| v2.0.0 "
|
|
118
|
+
puts " "
|
|
104
119
|
puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
|
|
105
120
|
|
|
106
121
|
# --- Interactive Wizard ---
|
|
@@ -130,7 +145,7 @@ when 'init'
|
|
|
130
145
|
puts "-----------------------------\n"
|
|
131
146
|
|
|
132
147
|
# Create directories
|
|
133
|
-
dirs = ['app/controllers', 'app/models', 'app/views', 'config', 'db/migrations', 'public/css', 'public/js', 'public/
|
|
148
|
+
dirs = ['app/controllers', 'app/models', 'app/views', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
|
|
134
149
|
dirs.each { |dir| FileUtils.mkdir_p(File.join(PROJECT_ROOT, dir)) }
|
|
135
150
|
|
|
136
151
|
# Copy framework core migrations
|
|
@@ -263,8 +278,6 @@ when 'init'
|
|
|
263
278
|
File.write(db_config_path, JSON.pretty_generate(db_default))
|
|
264
279
|
end
|
|
265
280
|
|
|
266
|
-
# Premium Scaffolding
|
|
267
|
-
puts " Generating premium UI components..."
|
|
268
281
|
# Copy views from framework to project
|
|
269
282
|
framework_views = File.join(FRAMEWORK_ROOT, 'app', 'views')
|
|
270
283
|
project_views = File.join(PROJECT_ROOT, 'app', 'views')
|
|
@@ -272,6 +285,14 @@ when 'init'
|
|
|
272
285
|
# Recursive copy for views (including cms folder)
|
|
273
286
|
FileUtils.cp_r(File.join(framework_views, '.'), project_views)
|
|
274
287
|
|
|
288
|
+
# Copy public assets from framework to project
|
|
289
|
+
framework_public = File.join(FRAMEWORK_ROOT, 'public')
|
|
290
|
+
project_public = File.join(PROJECT_ROOT, 'public')
|
|
291
|
+
if File.directory?(framework_public)
|
|
292
|
+
# Use cp_r with '.' to copy contents of public, not the public folder itself
|
|
293
|
+
FileUtils.cp_r(File.join(framework_public, '.'), project_public)
|
|
294
|
+
end
|
|
295
|
+
|
|
275
296
|
# Copy models from framework to project
|
|
276
297
|
framework_models = File.join(FRAMEWORK_ROOT, 'app', 'models')
|
|
277
298
|
project_models = File.join(PROJECT_ROOT, 'app', 'models')
|
|
@@ -302,6 +323,14 @@ when 'init'
|
|
|
302
323
|
def index; end
|
|
303
324
|
end
|
|
304
325
|
RUBY
|
|
326
|
+
when 'e_commerce'
|
|
327
|
+
puts " Scaffolding E-Commerce starter..."
|
|
328
|
+
File.write(File.join(PROJECT_ROOT, 'app/controllers/products_controller.rb'), <<~RUBY)
|
|
329
|
+
require_relative 'application_controller'
|
|
330
|
+
class ProductsController < ApplicationController
|
|
331
|
+
def index; end
|
|
332
|
+
end
|
|
333
|
+
RUBY
|
|
305
334
|
end
|
|
306
335
|
|
|
307
336
|
puts "Done! Run 'ofa run' to start your app."
|
|
@@ -376,8 +405,8 @@ when 'feature'
|
|
|
376
405
|
when 'type'
|
|
377
406
|
ensure_initialized!
|
|
378
407
|
name = ARGV.shift
|
|
379
|
-
unless %w[landing_page portfolio blog].include?(name)
|
|
380
|
-
puts "❌ Error: Invalid app type '#{name}'. Choose: landing_page, portfolio, blog."
|
|
408
|
+
unless %w[landing_page portfolio blog e_commerce].include?(name)
|
|
409
|
+
puts "❌ Error: Invalid app type '#{name}'. Choose: landing_page, portfolio, blog, e_commerce."
|
|
381
410
|
exit 1
|
|
382
411
|
end
|
|
383
412
|
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
@@ -389,8 +418,8 @@ when 'type'
|
|
|
389
418
|
when 'theme'
|
|
390
419
|
ensure_initialized!
|
|
391
420
|
name = ARGV.shift
|
|
392
|
-
unless %w[light_glass dark_glass].include?(name)
|
|
393
|
-
puts "❌ Error: Invalid theme '#{name}'. Choose: light_glass, dark_glass."
|
|
421
|
+
unless %w[light_glass dark_glass cyber_sidebar retro_terminal light_sidebar].include?(name)
|
|
422
|
+
puts "❌ Error: Invalid theme '#{name}'. Choose: light_glass, dark_glass, cyber_sidebar, retro_terminal, light_sidebar."
|
|
394
423
|
exit 1
|
|
395
424
|
end
|
|
396
425
|
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
data/config/boot.rb
CHANGED
|
@@ -76,6 +76,7 @@ require_relative 'database'
|
|
|
76
76
|
# Autoloading (Framework Core first, then APP_ROOT)
|
|
77
77
|
framework_app = File.expand_path('../app', __dir__)
|
|
78
78
|
['controllers', 'models', 'middleware', 'helpers'].each do |folder|
|
|
79
|
+
next if folder == 'models' && ENV['SKIP_MODELS']
|
|
79
80
|
loaded = []
|
|
80
81
|
# Load framework core
|
|
81
82
|
Dir.glob(File.join(framework_app, folder, '*.rb')).each do |f|
|