rails_map 1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.idea/.gitignore +8 -0
  3. data/.idea/material_theme_project_new.xml +12 -0
  4. data/AUTHENTICATION.md +221 -0
  5. data/CHANGELOG.md +75 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE.txt +21 -0
  8. data/QUICKSTART.md +156 -0
  9. data/README.md +178 -0
  10. data/Rakefile +8 -0
  11. data/app/controllers/rails_map/docs_controller.rb +73 -0
  12. data/app/models/rails_map/user.rb +12 -0
  13. data/app/views/rails_map/docs/_styles.html.erb +489 -0
  14. data/app/views/rails_map/docs/controller.html.erb +137 -0
  15. data/app/views/rails_map/docs/index.html.erb +212 -0
  16. data/app/views/rails_map/docs/model.html.erb +214 -0
  17. data/app/views/rails_map/docs/routes.html.erb +139 -0
  18. data/config/initializers/rails_map.example.rb +44 -0
  19. data/config/routes.rb +9 -0
  20. data/docs/index.html +1354 -0
  21. data/lib/generators/rails_map/install_generator.rb +49 -0
  22. data/lib/generators/rails_map/templates/README +47 -0
  23. data/lib/generators/rails_map/templates/initializer.rb +38 -0
  24. data/lib/generators/rails_map/templates/migration.rb +14 -0
  25. data/lib/rails_map/auth.rb +15 -0
  26. data/lib/rails_map/configuration.rb +35 -0
  27. data/lib/rails_map/engine.rb +11 -0
  28. data/lib/rails_map/generators/html_generator.rb +120 -0
  29. data/lib/rails_map/parsers/model_parser.rb +257 -0
  30. data/lib/rails_map/parsers/route_parser.rb +356 -0
  31. data/lib/rails_map/railtie.rb +46 -0
  32. data/lib/rails_map/version.rb +5 -0
  33. data/lib/rails_map.rb +66 -0
  34. data/templates/controller.html.erb +74 -0
  35. data/templates/index.html.erb +64 -0
  36. data/templates/layout.html.erb +289 -0
  37. data/templates/model.html.erb +219 -0
  38. data/templates/routes.html.erb +86 -0
  39. metadata +144 -0
data/docs/index.html ADDED
@@ -0,0 +1,1354 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RailsMap - The Missing Bridge Between Your Rails Backend & Frontend Team</title>
7
+ <meta name="description" content="RailsMap automatically maps your Rails routes, controllers, and models into beautiful, interactive documentation. Bridge the gap between backend and frontend teams instantly.">
8
+ <meta name="keywords" content="rails, api documentation, ruby on rails, api docs, rails routes, rails models, developer tools">
9
+ <meta property="og:title" content="RailsMap - The Missing Bridge Between Backend & Frontend">
10
+ <meta property="og:description" content="Auto-generate interactive API documentation for Rails. Zero configuration.">
11
+ <meta property="og:type" content="website">
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
15
+ <style>
16
+ :root {
17
+ --primary: #ef4444;
18
+ --primary-dark: #dc2626;
19
+ --primary-light: #f87171;
20
+ --ruby: #cc342d;
21
+ --ruby-light: #ff6b6b;
22
+ --accent: #3b82f6;
23
+ --accent-light: #60a5fa;
24
+ --bg-dark: #09090b;
25
+ --bg-card: #18181b;
26
+ --bg-card-hover: #27272a;
27
+ --text: #fafafa;
28
+ --text-muted: #a1a1aa;
29
+ --text-dim: #71717a;
30
+ --border: #27272a;
31
+ --border-light: #3f3f46;
32
+ --success: #22c55e;
33
+ --warning: #f59e0b;
34
+ }
35
+
36
+ * { margin: 0; padding: 0; box-sizing: border-box; }
37
+ html { scroll-behavior: smooth; }
38
+
39
+ body {
40
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
41
+ background: var(--bg-dark);
42
+ color: var(--text);
43
+ line-height: 1.6;
44
+ overflow-x: hidden;
45
+ }
46
+
47
+ .container { max-width: 1200px; margin: 0 auto; padding: 0 2rem; }
48
+
49
+ /* Navbar */
50
+ .navbar {
51
+ position: fixed;
52
+ top: 0;
53
+ left: 0;
54
+ right: 0;
55
+ z-index: 100;
56
+ padding: 1rem 2rem;
57
+ background: rgba(9, 9, 11, 0.9);
58
+ backdrop-filter: blur(20px);
59
+ border-bottom: 1px solid var(--border);
60
+ }
61
+
62
+ .navbar-content {
63
+ max-width: 1200px;
64
+ margin: 0 auto;
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: center;
68
+ }
69
+
70
+ .logo {
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 0.75rem;
74
+ text-decoration: none;
75
+ color: var(--text);
76
+ }
77
+
78
+ .logo-img {
79
+ height: 40px;
80
+ width: auto;
81
+ }
82
+
83
+ .logo-text {
84
+ font-size: 1.5rem;
85
+ font-weight: 800;
86
+ letter-spacing: -0.025em;
87
+ }
88
+
89
+ .logo-text span {
90
+ color: var(--ruby);
91
+ }
92
+
93
+ .nav-links {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 2rem;
97
+ }
98
+
99
+ .nav-links a {
100
+ color: var(--text-muted);
101
+ text-decoration: none;
102
+ font-size: 0.9375rem;
103
+ font-weight: 500;
104
+ transition: color 0.2s;
105
+ }
106
+
107
+ .nav-links a:hover { color: var(--text); }
108
+
109
+ .nav-btn {
110
+ padding: 0.625rem 1.25rem;
111
+ background: var(--ruby);
112
+ color: white !important;
113
+ border-radius: 0.5rem;
114
+ font-weight: 600;
115
+ transition: all 0.2s;
116
+ }
117
+
118
+ .nav-btn:hover {
119
+ background: var(--ruby-light);
120
+ transform: translateY(-1px);
121
+ }
122
+
123
+ /* Hero */
124
+ .hero {
125
+ min-height: 100vh;
126
+ display: flex;
127
+ align-items: center;
128
+ padding: 8rem 2rem 4rem;
129
+ position: relative;
130
+ overflow: hidden;
131
+ }
132
+
133
+ .hero::before {
134
+ content: '';
135
+ position: absolute;
136
+ top: -50%;
137
+ left: -50%;
138
+ width: 200%;
139
+ height: 200%;
140
+ background:
141
+ radial-gradient(ellipse at 30% 20%, rgba(204, 52, 45, 0.08) 0%, transparent 50%),
142
+ radial-gradient(ellipse at 70% 80%, rgba(59, 130, 246, 0.08) 0%, transparent 50%);
143
+ pointer-events: none;
144
+ }
145
+
146
+ .hero-content {
147
+ max-width: 1200px;
148
+ margin: 0 auto;
149
+ display: grid;
150
+ grid-template-columns: 1fr 1fr;
151
+ gap: 4rem;
152
+ align-items: center;
153
+ position: relative;
154
+ z-index: 1;
155
+ }
156
+
157
+ .hero-text { max-width: 600px; }
158
+
159
+ .hero-badge {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ gap: 0.5rem;
163
+ padding: 0.5rem 1rem;
164
+ background: rgba(204, 52, 45, 0.1);
165
+ border: 1px solid rgba(204, 52, 45, 0.3);
166
+ border-radius: 2rem;
167
+ font-size: 0.875rem;
168
+ color: var(--ruby-light);
169
+ margin-bottom: 1.5rem;
170
+ }
171
+
172
+ .hero-badge-dot {
173
+ width: 8px;
174
+ height: 8px;
175
+ background: var(--ruby);
176
+ border-radius: 50%;
177
+ animation: pulse 2s infinite;
178
+ }
179
+
180
+ @keyframes pulse {
181
+ 0%, 100% { opacity: 1; transform: scale(1); }
182
+ 50% { opacity: 0.5; transform: scale(0.9); }
183
+ }
184
+
185
+ h1 {
186
+ font-size: clamp(2.5rem, 5vw, 3.5rem);
187
+ font-weight: 900;
188
+ line-height: 1.1;
189
+ margin-bottom: 1.5rem;
190
+ letter-spacing: -0.03em;
191
+ }
192
+
193
+ .highlight { color: var(--ruby); }
194
+ .highlight-blue { color: var(--accent); }
195
+
196
+ .hero-description {
197
+ font-size: 1.25rem;
198
+ color: var(--text-muted);
199
+ margin-bottom: 2rem;
200
+ line-height: 1.7;
201
+ }
202
+
203
+ .hero-buttons {
204
+ display: flex;
205
+ gap: 1rem;
206
+ flex-wrap: wrap;
207
+ margin-bottom: 3rem;
208
+ }
209
+
210
+ .btn {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ gap: 0.5rem;
214
+ padding: 1rem 2rem;
215
+ border-radius: 0.75rem;
216
+ font-size: 1rem;
217
+ font-weight: 600;
218
+ text-decoration: none;
219
+ transition: all 0.2s;
220
+ cursor: pointer;
221
+ border: none;
222
+ }
223
+
224
+ .btn-primary {
225
+ background: linear-gradient(135deg, var(--ruby), var(--primary));
226
+ color: white;
227
+ box-shadow: 0 4px 20px rgba(204, 52, 45, 0.4);
228
+ }
229
+
230
+ .btn-primary:hover {
231
+ transform: translateY(-2px);
232
+ box-shadow: 0 8px 30px rgba(204, 52, 45, 0.5);
233
+ }
234
+
235
+ .btn-secondary {
236
+ background: var(--bg-card);
237
+ color: var(--text);
238
+ border: 1px solid var(--border);
239
+ }
240
+
241
+ .btn-secondary:hover {
242
+ background: var(--bg-card-hover);
243
+ border-color: var(--border-light);
244
+ }
245
+
246
+ .hero-stats {
247
+ display: flex;
248
+ gap: 2rem;
249
+ }
250
+
251
+ .hero-stat {
252
+ text-align: left;
253
+ }
254
+
255
+ .hero-stat-value {
256
+ font-size: 1.5rem;
257
+ font-weight: 800;
258
+ color: var(--text);
259
+ }
260
+
261
+ .hero-stat-label {
262
+ font-size: 0.8125rem;
263
+ color: var(--text-dim);
264
+ }
265
+
266
+ /* Hero Visual */
267
+ .hero-visual {
268
+ position: relative;
269
+ }
270
+
271
+ .bridge-diagram {
272
+ background: var(--bg-card);
273
+ border: 1px solid var(--border);
274
+ border-radius: 1.5rem;
275
+ padding: 2rem;
276
+ position: relative;
277
+ }
278
+
279
+ .bridge-row {
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: space-between;
283
+ gap: 1rem;
284
+ margin-bottom: 1.5rem;
285
+ }
286
+
287
+ .bridge-row:last-child { margin-bottom: 0; }
288
+
289
+ .bridge-box {
290
+ flex: 1;
291
+ padding: 1.25rem;
292
+ border-radius: 0.75rem;
293
+ text-align: center;
294
+ }
295
+
296
+ .bridge-box.backend {
297
+ background: linear-gradient(135deg, rgba(204, 52, 45, 0.15), rgba(204, 52, 45, 0.05));
298
+ border: 1px solid rgba(204, 52, 45, 0.3);
299
+ }
300
+
301
+ .bridge-box.frontend {
302
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(59, 130, 246, 0.05));
303
+ border: 1px solid rgba(59, 130, 246, 0.3);
304
+ }
305
+
306
+ .bridge-box.center {
307
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(34, 197, 94, 0.05));
308
+ border: 1px solid rgba(34, 197, 94, 0.3);
309
+ flex: 0.8;
310
+ }
311
+
312
+ .bridge-icon {
313
+ font-size: 2rem;
314
+ margin-bottom: 0.5rem;
315
+ }
316
+
317
+ .bridge-label {
318
+ font-size: 0.75rem;
319
+ font-weight: 600;
320
+ text-transform: uppercase;
321
+ letter-spacing: 0.05em;
322
+ color: var(--text-muted);
323
+ }
324
+
325
+ .bridge-title {
326
+ font-size: 1rem;
327
+ font-weight: 700;
328
+ margin-top: 0.25rem;
329
+ }
330
+
331
+ .bridge-connector {
332
+ display: flex;
333
+ align-items: center;
334
+ justify-content: center;
335
+ color: var(--text-dim);
336
+ }
337
+
338
+ .bridge-connector svg {
339
+ animation: flowRight 1.5s infinite;
340
+ }
341
+
342
+ @keyframes flowRight {
343
+ 0%, 100% { opacity: 0.3; transform: translateX(-5px); }
344
+ 50% { opacity: 1; transform: translateX(5px); }
345
+ }
346
+
347
+ .bridge-items {
348
+ display: grid;
349
+ grid-template-columns: repeat(3, 1fr);
350
+ gap: 0.75rem;
351
+ margin-top: 1.5rem;
352
+ padding-top: 1.5rem;
353
+ border-top: 1px solid var(--border);
354
+ }
355
+
356
+ .bridge-item {
357
+ background: var(--bg-dark);
358
+ border: 1px solid var(--border);
359
+ border-radius: 0.5rem;
360
+ padding: 0.75rem;
361
+ text-align: center;
362
+ }
363
+
364
+ .bridge-item-icon {
365
+ font-size: 1.25rem;
366
+ margin-bottom: 0.25rem;
367
+ }
368
+
369
+ .bridge-item-text {
370
+ font-size: 0.75rem;
371
+ color: var(--text-muted);
372
+ }
373
+
374
+ /* Problem Section */
375
+ .problem {
376
+ padding: 6rem 2rem;
377
+ background: var(--bg-card);
378
+ border-top: 1px solid var(--border);
379
+ }
380
+
381
+ .problem-content {
382
+ max-width: 900px;
383
+ margin: 0 auto;
384
+ text-align: center;
385
+ }
386
+
387
+ .section-label {
388
+ display: inline-block;
389
+ font-size: 0.75rem;
390
+ font-weight: 700;
391
+ text-transform: uppercase;
392
+ letter-spacing: 0.1em;
393
+ color: var(--ruby);
394
+ margin-bottom: 1rem;
395
+ }
396
+
397
+ .section-title {
398
+ font-size: clamp(2rem, 4vw, 2.75rem);
399
+ font-weight: 800;
400
+ margin-bottom: 1.5rem;
401
+ letter-spacing: -0.025em;
402
+ }
403
+
404
+ .section-description {
405
+ font-size: 1.125rem;
406
+ color: var(--text-muted);
407
+ max-width: 700px;
408
+ margin: 0 auto 3rem;
409
+ }
410
+
411
+ .pain-points {
412
+ display: grid;
413
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
414
+ gap: 1.5rem;
415
+ text-align: left;
416
+ }
417
+
418
+ .pain-point {
419
+ background: var(--bg-dark);
420
+ border: 1px solid var(--border);
421
+ border-radius: 1rem;
422
+ padding: 1.5rem;
423
+ position: relative;
424
+ overflow: hidden;
425
+ }
426
+
427
+ .pain-point::before {
428
+ content: '';
429
+ position: absolute;
430
+ top: 0;
431
+ left: 0;
432
+ right: 0;
433
+ height: 3px;
434
+ background: linear-gradient(90deg, var(--ruby), var(--primary-light));
435
+ }
436
+
437
+ .pain-point-icon {
438
+ font-size: 1.5rem;
439
+ margin-bottom: 1rem;
440
+ }
441
+
442
+ .pain-point h3 {
443
+ font-size: 1.125rem;
444
+ font-weight: 700;
445
+ margin-bottom: 0.5rem;
446
+ }
447
+
448
+ .pain-point p {
449
+ font-size: 0.9375rem;
450
+ color: var(--text-muted);
451
+ line-height: 1.6;
452
+ }
453
+
454
+ /* Solution Section */
455
+ .solution {
456
+ padding: 6rem 2rem;
457
+ }
458
+
459
+ .solution-header {
460
+ text-align: center;
461
+ margin-bottom: 4rem;
462
+ }
463
+
464
+ .solution-grid {
465
+ display: grid;
466
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
467
+ gap: 2rem;
468
+ max-width: 1200px;
469
+ margin: 0 auto;
470
+ }
471
+
472
+ .solution-card {
473
+ background: var(--bg-card);
474
+ border: 1px solid var(--border);
475
+ border-radius: 1.25rem;
476
+ padding: 2rem;
477
+ transition: all 0.3s;
478
+ }
479
+
480
+ .solution-card:hover {
481
+ border-color: var(--ruby);
482
+ transform: translateY(-4px);
483
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
484
+ }
485
+
486
+ .solution-card-icon {
487
+ width: 56px;
488
+ height: 56px;
489
+ background: linear-gradient(135deg, rgba(204, 52, 45, 0.2), rgba(59, 130, 246, 0.2));
490
+ border-radius: 1rem;
491
+ display: flex;
492
+ align-items: center;
493
+ justify-content: center;
494
+ font-size: 1.75rem;
495
+ margin-bottom: 1.5rem;
496
+ }
497
+
498
+ .solution-card h3 {
499
+ font-size: 1.25rem;
500
+ font-weight: 700;
501
+ margin-bottom: 0.75rem;
502
+ }
503
+
504
+ .solution-card p {
505
+ font-size: 0.9375rem;
506
+ color: var(--text-muted);
507
+ line-height: 1.7;
508
+ margin-bottom: 1rem;
509
+ }
510
+
511
+ .solution-card-features {
512
+ display: flex;
513
+ flex-wrap: wrap;
514
+ gap: 0.5rem;
515
+ }
516
+
517
+ .solution-card-feature {
518
+ font-size: 0.75rem;
519
+ padding: 0.25rem 0.625rem;
520
+ background: var(--bg-dark);
521
+ border: 1px solid var(--border);
522
+ border-radius: 1rem;
523
+ color: var(--text-muted);
524
+ }
525
+
526
+ /* Demo Section */
527
+ .demo {
528
+ padding: 6rem 2rem;
529
+ background: var(--bg-card);
530
+ border-top: 1px solid var(--border);
531
+ }
532
+
533
+ .demo-header {
534
+ text-align: center;
535
+ margin-bottom: 3rem;
536
+ }
537
+
538
+ .demo-container {
539
+ max-width: 1000px;
540
+ margin: 0 auto;
541
+ }
542
+
543
+ .demo-window {
544
+ background: linear-gradient(135deg, var(--ruby), var(--accent));
545
+ padding: 3px;
546
+ border-radius: 1rem;
547
+ box-shadow: 0 25px 80px rgba(204, 52, 45, 0.2), 0 10px 30px rgba(0, 0, 0, 0.3);
548
+ }
549
+
550
+ .demo-window-inner {
551
+ background: var(--bg-dark);
552
+ border-radius: calc(1rem - 3px);
553
+ overflow: hidden;
554
+ }
555
+
556
+ .demo-header-bar {
557
+ display: flex;
558
+ align-items: center;
559
+ gap: 0.5rem;
560
+ padding: 1rem 1.25rem;
561
+ background: var(--bg-card);
562
+ border-bottom: 1px solid var(--border);
563
+ }
564
+
565
+ .demo-dot {
566
+ width: 12px;
567
+ height: 12px;
568
+ border-radius: 50%;
569
+ }
570
+
571
+ .demo-dot.red { background: #ff5f57; }
572
+ .demo-dot.yellow { background: #febc2e; }
573
+ .demo-dot.green { background: #28c840; }
574
+
575
+ .demo-url {
576
+ flex: 1;
577
+ text-align: center;
578
+ font-size: 0.8125rem;
579
+ color: var(--text-dim);
580
+ font-family: 'JetBrains Mono', monospace;
581
+ }
582
+
583
+ .demo-content {
584
+ padding: 2rem;
585
+ }
586
+
587
+ .demo-nav {
588
+ display: flex;
589
+ gap: 1rem;
590
+ margin-bottom: 2rem;
591
+ padding-bottom: 1rem;
592
+ border-bottom: 1px solid var(--border);
593
+ }
594
+
595
+ .demo-nav-item {
596
+ padding: 0.5rem 1rem;
597
+ background: var(--bg-card);
598
+ border: 1px solid var(--border);
599
+ border-radius: 0.5rem;
600
+ font-size: 0.875rem;
601
+ color: var(--text-muted);
602
+ }
603
+
604
+ .demo-nav-item.active {
605
+ background: var(--ruby);
606
+ border-color: var(--ruby);
607
+ color: white;
608
+ }
609
+
610
+ .demo-stats {
611
+ display: grid;
612
+ grid-template-columns: repeat(3, 1fr);
613
+ gap: 1rem;
614
+ margin-bottom: 2rem;
615
+ }
616
+
617
+ .demo-stat {
618
+ background: var(--bg-card);
619
+ border: 1px solid var(--border);
620
+ border-radius: 0.75rem;
621
+ padding: 1.5rem;
622
+ text-align: center;
623
+ }
624
+
625
+ .demo-stat-value {
626
+ font-size: 2.5rem;
627
+ font-weight: 800;
628
+ background: linear-gradient(135deg, var(--ruby), var(--accent));
629
+ -webkit-background-clip: text;
630
+ -webkit-text-fill-color: transparent;
631
+ }
632
+
633
+ .demo-stat-label {
634
+ font-size: 0.75rem;
635
+ color: var(--text-dim);
636
+ text-transform: uppercase;
637
+ letter-spacing: 0.05em;
638
+ }
639
+
640
+ .demo-cards {
641
+ display: grid;
642
+ grid-template-columns: repeat(2, 1fr);
643
+ gap: 1rem;
644
+ }
645
+
646
+ .demo-card {
647
+ background: var(--bg-card);
648
+ border: 1px solid var(--border);
649
+ border-radius: 0.75rem;
650
+ padding: 1.25rem;
651
+ transition: all 0.2s;
652
+ }
653
+
654
+ .demo-card:hover {
655
+ border-color: var(--border-light);
656
+ }
657
+
658
+ .demo-card-header {
659
+ display: flex;
660
+ justify-content: space-between;
661
+ align-items: center;
662
+ margin-bottom: 0.75rem;
663
+ }
664
+
665
+ .demo-card-title {
666
+ font-weight: 600;
667
+ font-size: 0.9375rem;
668
+ }
669
+
670
+ .demo-card-badge {
671
+ font-size: 0.6875rem;
672
+ padding: 0.25rem 0.5rem;
673
+ background: rgba(34, 197, 94, 0.15);
674
+ color: var(--success);
675
+ border-radius: 0.25rem;
676
+ font-weight: 600;
677
+ }
678
+
679
+ .demo-card-meta {
680
+ font-size: 0.8125rem;
681
+ color: var(--text-dim);
682
+ }
683
+
684
+ /* How it Works */
685
+ .how-it-works {
686
+ padding: 6rem 2rem;
687
+ }
688
+
689
+ .how-header {
690
+ text-align: center;
691
+ margin-bottom: 4rem;
692
+ }
693
+
694
+ .steps {
695
+ max-width: 800px;
696
+ margin: 0 auto;
697
+ }
698
+
699
+ .step {
700
+ display: flex;
701
+ gap: 2rem;
702
+ margin-bottom: 3rem;
703
+ position: relative;
704
+ }
705
+
706
+ .step:not(:last-child)::after {
707
+ content: '';
708
+ position: absolute;
709
+ left: 28px;
710
+ top: 60px;
711
+ bottom: -30px;
712
+ width: 2px;
713
+ background: linear-gradient(to bottom, var(--ruby), transparent);
714
+ }
715
+
716
+ .step-number {
717
+ width: 56px;
718
+ height: 56px;
719
+ min-width: 56px;
720
+ background: linear-gradient(135deg, var(--ruby), var(--primary));
721
+ border-radius: 50%;
722
+ display: flex;
723
+ align-items: center;
724
+ justify-content: center;
725
+ font-weight: 800;
726
+ font-size: 1.25rem;
727
+ color: white;
728
+ }
729
+
730
+ .step-content {
731
+ flex: 1;
732
+ padding-top: 0.5rem;
733
+ }
734
+
735
+ .step-content h3 {
736
+ font-size: 1.375rem;
737
+ font-weight: 700;
738
+ margin-bottom: 0.5rem;
739
+ }
740
+
741
+ .step-content p {
742
+ color: var(--text-muted);
743
+ margin-bottom: 1rem;
744
+ }
745
+
746
+ .step-code {
747
+ background: var(--bg-card);
748
+ border: 1px solid var(--border);
749
+ border-radius: 0.75rem;
750
+ padding: 1.25rem;
751
+ font-family: 'JetBrains Mono', monospace;
752
+ font-size: 0.875rem;
753
+ overflow-x: auto;
754
+ }
755
+
756
+ .step-code .comment { color: var(--text-dim); }
757
+ .step-code .keyword { color: #f472b6; }
758
+ .step-code .string { color: #4ade80; }
759
+
760
+ /* CTA */
761
+ .cta {
762
+ padding: 8rem 2rem;
763
+ text-align: center;
764
+ background: var(--bg-card);
765
+ border-top: 1px solid var(--border);
766
+ position: relative;
767
+ overflow: hidden;
768
+ }
769
+
770
+ .cta::before {
771
+ content: '';
772
+ position: absolute;
773
+ top: 50%;
774
+ left: 50%;
775
+ transform: translate(-50%, -50%);
776
+ width: 800px;
777
+ height: 800px;
778
+ background: radial-gradient(ellipse at center, rgba(204, 52, 45, 0.1) 0%, transparent 60%);
779
+ pointer-events: none;
780
+ }
781
+
782
+ .cta-content {
783
+ max-width: 700px;
784
+ margin: 0 auto;
785
+ position: relative;
786
+ z-index: 1;
787
+ }
788
+
789
+ .cta h2 {
790
+ font-size: clamp(2rem, 4vw, 3rem);
791
+ font-weight: 800;
792
+ margin-bottom: 1rem;
793
+ }
794
+
795
+ .cta p {
796
+ font-size: 1.25rem;
797
+ color: var(--text-muted);
798
+ margin-bottom: 2rem;
799
+ }
800
+
801
+ .cta-buttons {
802
+ display: flex;
803
+ gap: 1rem;
804
+ justify-content: center;
805
+ flex-wrap: wrap;
806
+ }
807
+
808
+ .install-box {
809
+ display: inline-flex;
810
+ align-items: center;
811
+ gap: 1rem;
812
+ background: var(--bg-dark);
813
+ border: 1px solid var(--border);
814
+ border-radius: 0.75rem;
815
+ padding: 1rem 1.5rem;
816
+ font-family: 'JetBrains Mono', monospace;
817
+ font-size: 0.9375rem;
818
+ margin-top: 2rem;
819
+ }
820
+
821
+ .install-box code { color: var(--ruby-light); }
822
+
823
+ .copy-btn {
824
+ background: none;
825
+ border: none;
826
+ color: var(--text-muted);
827
+ cursor: pointer;
828
+ padding: 0.5rem;
829
+ border-radius: 0.375rem;
830
+ transition: all 0.2s;
831
+ }
832
+
833
+ .copy-btn:hover {
834
+ background: var(--border);
835
+ color: var(--text);
836
+ }
837
+
838
+ /* Footer */
839
+ .footer {
840
+ padding: 3rem 2rem;
841
+ border-top: 1px solid var(--border);
842
+ }
843
+
844
+ .footer-content {
845
+ max-width: 1200px;
846
+ margin: 0 auto;
847
+ display: flex;
848
+ justify-content: space-between;
849
+ align-items: center;
850
+ flex-wrap: wrap;
851
+ gap: 1.5rem;
852
+ }
853
+
854
+ .footer-links {
855
+ display: flex;
856
+ gap: 2rem;
857
+ }
858
+
859
+ .footer-links a {
860
+ color: var(--text-muted);
861
+ text-decoration: none;
862
+ font-size: 0.875rem;
863
+ transition: color 0.2s;
864
+ }
865
+
866
+ .footer-links a:hover { color: var(--text); }
867
+
868
+ .footer-copy {
869
+ color: var(--text-dim);
870
+ font-size: 0.875rem;
871
+ }
872
+
873
+ .footer-copy a {
874
+ color: var(--ruby-light);
875
+ text-decoration: none;
876
+ }
877
+
878
+ .footer-copy a:hover { text-decoration: underline; }
879
+
880
+ /* Responsive */
881
+ @media (max-width: 968px) {
882
+ .hero-content {
883
+ grid-template-columns: 1fr;
884
+ text-align: center;
885
+ }
886
+
887
+ .hero-text { max-width: 100%; }
888
+
889
+ .hero-buttons, .hero-stats {
890
+ justify-content: center;
891
+ }
892
+
893
+ .hero-stat { text-align: center; }
894
+
895
+ .bridge-row { flex-direction: column; }
896
+
897
+ .demo-stats { grid-template-columns: 1fr; }
898
+
899
+ .demo-cards { grid-template-columns: 1fr; }
900
+ }
901
+
902
+ @media (max-width: 768px) {
903
+ .navbar-content {
904
+ flex-direction: column;
905
+ gap: 1rem;
906
+ }
907
+
908
+ .nav-links { gap: 1rem; flex-wrap: wrap; justify-content: center; }
909
+
910
+ .hero { padding: 6rem 1.5rem 3rem; }
911
+
912
+ .step { flex-direction: column; text-align: center; }
913
+
914
+ .step:not(:last-child)::after { display: none; }
915
+
916
+ .step-number { margin: 0 auto; }
917
+
918
+ .pain-points { grid-template-columns: 1fr; }
919
+
920
+ .solution-grid { grid-template-columns: 1fr; }
921
+
922
+ .footer-content { flex-direction: column; text-align: center; }
923
+
924
+ .footer-links { flex-wrap: wrap; justify-content: center; }
925
+ }
926
+ </style>
927
+ </head>
928
+ <body>
929
+ <!-- Navbar -->
930
+ <nav class="navbar">
931
+ <div class="navbar-content">
932
+ <a href="#" class="logo">
933
+ <img src="https://raw.githubusercontent.com/ArshdeepGrover/rails-map/main/docs/logo.png" alt="RailsMap" class="logo-img" onerror="this.style.display='none'">
934
+ <span class="logo-text">Rails<span>Map</span></span>
935
+ </a>
936
+ <div class="nav-links">
937
+ <a href="#problem">Why RailsMap</a>
938
+ <a href="#features">Features</a>
939
+ <a href="#how-it-works">Get Started</a>
940
+ <a href="https://github.com/ArshdeepGrover/rails-map" target="_blank">GitHub</a>
941
+ <a href="https://rubygems.org/gems/rails_map" target="_blank" class="nav-btn">Install Free</a>
942
+ </div>
943
+ </div>
944
+ </nav>
945
+
946
+ <!-- Hero -->
947
+ <section class="hero">
948
+ <div class="hero-content">
949
+ <div class="hero-text">
950
+ <div class="hero-badge">
951
+ <span class="hero-badge-dot"></span>
952
+ Open Source · MIT License
953
+ </div>
954
+ <h1>
955
+ The <span class="highlight">missing bridge</span> between your backend & frontend team
956
+ </h1>
957
+ <p class="hero-description">
958
+ Stop wasting hours explaining your Rails API to frontend developers. RailsMap automatically maps your routes, controllers, and models into beautiful, interactive documentation that everyone can understand.
959
+ </p>
960
+ <div class="hero-buttons">
961
+ <a href="#how-it-works" class="btn btn-primary">
962
+ Get Started Free
963
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
964
+ <path d="M5 12h14M12 5l7 7-7 7"/>
965
+ </svg>
966
+ </a>
967
+ <a href="https://github.com/ArshdeepGrover/rails-map" target="_blank" class="btn btn-secondary">
968
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
969
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
970
+ </svg>
971
+ Star on GitHub
972
+ </a>
973
+ </div>
974
+ <div class="hero-stats">
975
+ <div class="hero-stat">
976
+ <div class="hero-stat-value">Zero</div>
977
+ <div class="hero-stat-label">Configuration</div>
978
+ </div>
979
+ <div class="hero-stat">
980
+ <div class="hero-stat-value">30 sec</div>
981
+ <div class="hero-stat-label">Setup Time</div>
982
+ </div>
983
+ <div class="hero-stat">
984
+ <div class="hero-stat-value">100%</div>
985
+ <div class="hero-stat-label">Automatic</div>
986
+ </div>
987
+ </div>
988
+ </div>
989
+
990
+ <div class="hero-visual">
991
+ <div class="bridge-diagram">
992
+ <div class="bridge-row">
993
+ <div class="bridge-box backend">
994
+ <div class="bridge-icon">💎</div>
995
+ <div class="bridge-label">Backend</div>
996
+ <div class="bridge-title">Rails API</div>
997
+ </div>
998
+ <div class="bridge-connector">
999
+ <svg width="40" height="24" viewBox="0 0 40 24" fill="none">
1000
+ <path d="M0 12H40M32 4L40 12L32 20" stroke="currentColor" stroke-width="2"/>
1001
+ </svg>
1002
+ </div>
1003
+ <div class="bridge-box center">
1004
+ <div class="bridge-icon">🗺️</div>
1005
+ <div class="bridge-label">Bridge</div>
1006
+ <div class="bridge-title">RailsMap</div>
1007
+ </div>
1008
+ <div class="bridge-connector">
1009
+ <svg width="40" height="24" viewBox="0 0 40 24" fill="none">
1010
+ <path d="M0 12H40M32 4L40 12L32 20" stroke="currentColor" stroke-width="2"/>
1011
+ </svg>
1012
+ </div>
1013
+ <div class="bridge-box frontend">
1014
+ <div class="bridge-icon">⚛️</div>
1015
+ <div class="bridge-label">Frontend</div>
1016
+ <div class="bridge-title">React / Angular</div>
1017
+ </div>
1018
+ </div>
1019
+ <div class="bridge-items">
1020
+ <div class="bridge-item">
1021
+ <div class="bridge-item-icon">🛣️</div>
1022
+ <div class="bridge-item-text">Routes</div>
1023
+ </div>
1024
+ <div class="bridge-item">
1025
+ <div class="bridge-item-icon">🎮</div>
1026
+ <div class="bridge-item-text">Controllers</div>
1027
+ </div>
1028
+ <div class="bridge-item">
1029
+ <div class="bridge-item-icon">📊</div>
1030
+ <div class="bridge-item-text">Models</div>
1031
+ </div>
1032
+ </div>
1033
+ </div>
1034
+ </div>
1035
+ </div>
1036
+ </section>
1037
+
1038
+ <!-- Problem Section -->
1039
+ <section class="problem" id="problem">
1040
+ <div class="problem-content">
1041
+ <span class="section-label">The Problem</span>
1042
+ <h2 class="section-title">Sound familiar?</h2>
1043
+ <p class="section-description">
1044
+ Every Rails developer knows the pain. Your backend is powerful, but getting the frontend team on the same page feels impossible.
1045
+ </p>
1046
+ <div class="pain-points">
1047
+ <div class="pain-point">
1048
+ <div class="pain-point-icon">😩</div>
1049
+ <h3>"What endpoints do we have?"</h3>
1050
+ <p>Frontend devs constantly asking about available routes, required params, and response formats. You spend more time explaining than coding.</p>
1051
+ </div>
1052
+ <div class="pain-point">
1053
+ <div class="pain-point-icon">📝</div>
1054
+ <h3>Documentation is always outdated</h3>
1055
+ <p>You wrote docs once, then the API changed. Now nobody trusts them, and you're back to Slack messages and screen shares.</p>
1056
+ </div>
1057
+ <div class="pain-point">
1058
+ <div class="pain-point-icon">🔄</div>
1059
+ <h3>Constant context switching</h3>
1060
+ <p>Every time someone needs API info, you have to stop what you're doing, dig through controllers, and piece together the answer.</p>
1061
+ </div>
1062
+ <div class="pain-point">
1063
+ <div class="pain-point-icon">🤷</div>
1064
+ <h3>"What columns does this model have?"</h3>
1065
+ <p>New team members struggle to understand your data structure. They open migration files, check schemas, ask questions...</p>
1066
+ </div>
1067
+ </div>
1068
+ </div>
1069
+ </section>
1070
+
1071
+ <!-- Solution Section -->
1072
+ <section class="solution" id="features">
1073
+ <div class="solution-header">
1074
+ <span class="section-label">The Solution</span>
1075
+ <h2 class="section-title">RailsMap does the work for you</h2>
1076
+ <p class="section-description">
1077
+ Install once, forget forever. Your API documentation stays in sync automatically.
1078
+ </p>
1079
+ </div>
1080
+ <div class="solution-grid">
1081
+ <div class="solution-card">
1082
+ <div class="solution-card-icon">🛣️</div>
1083
+ <h3>Automatic Route Discovery</h3>
1084
+ <p>Every route in your Rails app is automatically discovered and documented. HTTP methods, paths, constraints, and route names—all mapped instantly.</p>
1085
+ <div class="solution-card-features">
1086
+ <span class="solution-card-feature">GET / POST / PUT / DELETE</span>
1087
+ <span class="solution-card-feature">Route Params</span>
1088
+ <span class="solution-card-feature">Constraints</span>
1089
+ </div>
1090
+ </div>
1091
+ <div class="solution-card">
1092
+ <div class="solution-card-icon">📊</div>
1093
+ <h3>Model Introspection</h3>
1094
+ <p>Database columns, associations, validations, and scopes are extracted automatically. Your data structure, documented without writing a single line.</p>
1095
+ <div class="solution-card-features">
1096
+ <span class="solution-card-feature">Columns & Types</span>
1097
+ <span class="solution-card-feature">Associations</span>
1098
+ <span class="solution-card-feature">Validations</span>
1099
+ </div>
1100
+ </div>
1101
+ <div class="solution-card">
1102
+ <div class="solution-card-icon">🎨</div>
1103
+ <h3>Beautiful Dark UI</h3>
1104
+ <p>Modern, responsive interface that developers actually want to use. Searchable, filterable, and easy to navigate. Looks great on any device.</p>
1105
+ <div class="solution-card-features">
1106
+ <span class="solution-card-feature">Dark Theme</span>
1107
+ <span class="solution-card-feature">Instant Search</span>
1108
+ <span class="solution-card-feature">Responsive</span>
1109
+ </div>
1110
+ </div>
1111
+ <div class="solution-card">
1112
+ <div class="solution-card-icon">🔒</div>
1113
+ <h3>Environment-Based Auth</h3>
1114
+ <p>Secure by default with environment variables. No database, no migrations—just set RAILS_MAP_USERNAME and RAILS_MAP_PASSWORD. Defaults to admin/password.</p>
1115
+ <div class="solution-card-features">
1116
+ <span class="solution-card-feature">No Database</span>
1117
+ <span class="solution-card-feature">ENV Variables</span>
1118
+ <span class="solution-card-feature">Default Credentials</span>
1119
+ </div>
1120
+ </div>
1121
+ <div class="solution-card">
1122
+ <div class="solution-card-icon">⚡</div>
1123
+ <h3>Zero Configuration</h3>
1124
+ <p>Run one command. That's it. No config files, no annotations, no comments. RailsMap reads your code and generates docs automatically.</p>
1125
+ <div class="solution-card-features">
1126
+ <span class="solution-card-feature">One Command</span>
1127
+ <span class="solution-card-feature">Auto-mount</span>
1128
+ <span class="solution-card-feature">Works Instantly</span>
1129
+ </div>
1130
+ </div>
1131
+ <div class="solution-card">
1132
+ <div class="solution-card-icon">🔄</div>
1133
+ <h3>Always In Sync</h3>
1134
+ <p>Add a route? It's documented. Change a model? Updated automatically. Your docs are always current because they come from your actual code.</p>
1135
+ <div class="solution-card-features">
1136
+ <span class="solution-card-feature">Live Updates</span>
1137
+ <span class="solution-card-feature">No Manual Work</span>
1138
+ <span class="solution-card-feature">Always Accurate</span>
1139
+ </div>
1140
+ </div>
1141
+ </div>
1142
+ </section>
1143
+
1144
+ <!-- Demo Section -->
1145
+ <section class="demo">
1146
+ <div class="demo-header">
1147
+ <span class="section-label">Live Preview</span>
1148
+ <h2 class="section-title">See what your team will see</h2>
1149
+ <p class="section-description">
1150
+ A clean, organized view of your entire Rails API. No more digging through code.
1151
+ </p>
1152
+ </div>
1153
+ <div class="demo-container">
1154
+ <div class="demo-window">
1155
+ <div class="demo-window-inner">
1156
+ <div class="demo-header-bar">
1157
+ <span class="demo-dot red"></span>
1158
+ <span class="demo-dot yellow"></span>
1159
+ <span class="demo-dot green"></span>
1160
+ <span class="demo-url">localhost:3000/api-doc</span>
1161
+ </div>
1162
+ <div class="demo-content">
1163
+ <div class="demo-nav">
1164
+ <span class="demo-nav-item active">Dashboard</span>
1165
+ <span class="demo-nav-item">Routes</span>
1166
+ <span class="demo-nav-item">Models</span>
1167
+ </div>
1168
+ <div class="demo-stats">
1169
+ <div class="demo-stat">
1170
+ <div class="demo-stat-value">15</div>
1171
+ <div class="demo-stat-label">Controllers</div>
1172
+ </div>
1173
+ <div class="demo-stat">
1174
+ <div class="demo-stat-value">67</div>
1175
+ <div class="demo-stat-label">Routes</div>
1176
+ </div>
1177
+ <div class="demo-stat">
1178
+ <div class="demo-stat-value">12</div>
1179
+ <div class="demo-stat-label">Models</div>
1180
+ </div>
1181
+ </div>
1182
+ <div class="demo-cards">
1183
+ <div class="demo-card">
1184
+ <div class="demo-card-header">
1185
+ <span class="demo-card-title">Api::V1::UsersController</span>
1186
+ <span class="demo-card-badge">7 routes</span>
1187
+ </div>
1188
+ <div class="demo-card-meta">/api/v1/users · index, show, create, update, destroy</div>
1189
+ </div>
1190
+ <div class="demo-card">
1191
+ <div class="demo-card-header">
1192
+ <span class="demo-card-title">Api::V1::PostsController</span>
1193
+ <span class="demo-card-badge">5 routes</span>
1194
+ </div>
1195
+ <div class="demo-card-meta">/api/v1/posts · index, show, create, update, destroy</div>
1196
+ </div>
1197
+ <div class="demo-card">
1198
+ <div class="demo-card-header">
1199
+ <span class="demo-card-title">User</span>
1200
+ <span class="demo-card-badge">8 columns</span>
1201
+ </div>
1202
+ <div class="demo-card-meta">has_many :posts, has_many :comments</div>
1203
+ </div>
1204
+ <div class="demo-card">
1205
+ <div class="demo-card-header">
1206
+ <span class="demo-card-title">Post</span>
1207
+ <span class="demo-card-badge">6 columns</span>
1208
+ </div>
1209
+ <div class="demo-card-meta">belongs_to :user, has_many :comments</div>
1210
+ </div>
1211
+ </div>
1212
+ </div>
1213
+ </div>
1214
+ </div>
1215
+ </div>
1216
+ </section>
1217
+
1218
+ <!-- How it Works -->
1219
+ <section class="how-it-works" id="how-it-works">
1220
+ <div class="how-header">
1221
+ <span class="section-label">Get Started</span>
1222
+ <h2 class="section-title">Up and running in 30 seconds</h2>
1223
+ <p class="section-description">
1224
+ Seriously. Three commands and you're done.
1225
+ </p>
1226
+ </div>
1227
+ <div class="steps">
1228
+ <div class="step">
1229
+ <div class="step-number">1</div>
1230
+ <div class="step-content">
1231
+ <h3>Add RailsMap to your Gemfile</h3>
1232
+ <p>Just one line. That's all you need to add.</p>
1233
+ <div class="step-code">
1234
+ <span class="comment"># Gemfile</span><br>
1235
+ <span class="keyword">gem</span> <span class="string">'rails_map'</span>
1236
+ </div>
1237
+ </div>
1238
+ </div>
1239
+ <div class="step">
1240
+ <div class="step-number">2</div>
1241
+ <div class="step-content">
1242
+ <h3>Run the install generator</h3>
1243
+ <p>This mounts the engine and creates the config. Set environment variables to customize credentials (defaults: admin/password).</p>
1244
+ <div class="step-code">
1245
+ $ bundle install<br>
1246
+ $ rails generate rails_map:install<br>
1247
+ <span class="comment"># Optional: export RAILS_MAP_USERNAME=your_user</span><br>
1248
+ <span class="comment"># Optional: export RAILS_MAP_PASSWORD=your_pass</span>
1249
+ </div>
1250
+ </div>
1251
+ </div>
1252
+ <div class="step">
1253
+ <div class="step-number">3</div>
1254
+ <div class="step-content">
1255
+ <h3>Visit your docs</h3>
1256
+ <p>Start your server and open the documentation URL. Login with default credentials: <strong>admin/password</strong></p>
1257
+ <div class="step-code">
1258
+ $ rails server<br>
1259
+ <span class="comment"># Open http://localhost:3000/api-doc</span><br>
1260
+ <span class="comment"># Default: username=admin, password=password</span>
1261
+ </div>
1262
+ </div>
1263
+ </div>
1264
+ </div>
1265
+ </section>
1266
+
1267
+ <!-- CTA -->
1268
+ <section class="cta">
1269
+ <div class="cta-content">
1270
+ <h2>Stop explaining. Start shipping.</h2>
1271
+ <p>
1272
+ Give your team the documentation they need. Install RailsMap today and bridge the gap between backend and frontend—permanently.
1273
+ </p>
1274
+ <div class="cta-buttons">
1275
+ <a href="https://rubygems.org/gems/rails_map" target="_blank" class="btn btn-primary">
1276
+ Install RailsMap Free
1277
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1278
+ <path d="M5 12h14M12 5l7 7-7 7"/>
1279
+ </svg>
1280
+ </a>
1281
+ <a href="https://github.com/ArshdeepGrover/rails-map" target="_blank" class="btn btn-secondary">
1282
+ View on GitHub
1283
+ </a>
1284
+ </div>
1285
+ <div class="install-box">
1286
+ <code>gem install rails_map</code>
1287
+ <button class="copy-btn" onclick="copyToClipboard('gem install rails_map')">
1288
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1289
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
1290
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
1291
+ </svg>
1292
+ </button>
1293
+ </div>
1294
+ </div>
1295
+ </section>
1296
+
1297
+ <!-- Footer -->
1298
+ <footer class="footer">
1299
+ <div class="footer-content">
1300
+ <div class="footer-links">
1301
+ <a href="https://github.com/ArshdeepGrover/rails-map" target="_blank">GitHub</a>
1302
+ <a href="https://rubygems.org/gems/rails_map" target="_blank">RubyGems</a>
1303
+ <a href="https://github.com/ArshdeepGrover/rails-map/issues" target="_blank">Issues</a>
1304
+ <a href="https://github.com/ArshdeepGrover/rails-map/blob/main/CHANGELOG.md" target="_blank">Changelog</a>
1305
+ </div>
1306
+ <div class="footer-copy">
1307
+ Built with ❤️ by <a href="https://www.arshdeepsingh.info?utm_source=rails_map&utm_medium=footer&utm_campaign=developer_credit&utm_content=landing_page" target="_blank">Arshdeep Singh</a>
1308
+ </div>
1309
+ </div>
1310
+ </footer>
1311
+
1312
+ <script>
1313
+ function copyToClipboard(text) {
1314
+ navigator.clipboard.writeText(text).then(() => {
1315
+ const btn = document.querySelector('.copy-btn');
1316
+ btn.innerHTML = `
1317
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2">
1318
+ <polyline points="20 6 9 17 4 12"/>
1319
+ </svg>
1320
+ `;
1321
+ setTimeout(() => {
1322
+ btn.innerHTML = `
1323
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1324
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
1325
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
1326
+ </svg>
1327
+ `;
1328
+ }, 2000);
1329
+ });
1330
+ }
1331
+
1332
+ // Smooth scroll
1333
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
1334
+ anchor.addEventListener('click', function(e) {
1335
+ e.preventDefault();
1336
+ const target = document.querySelector(this.getAttribute('href'));
1337
+ if (target) {
1338
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
1339
+ }
1340
+ });
1341
+ });
1342
+
1343
+ // Navbar scroll effect
1344
+ window.addEventListener('scroll', () => {
1345
+ const navbar = document.querySelector('.navbar');
1346
+ if (window.scrollY > 50) {
1347
+ navbar.style.background = 'rgba(9, 9, 11, 0.95)';
1348
+ } else {
1349
+ navbar.style.background = 'rgba(9, 9, 11, 0.9)';
1350
+ }
1351
+ });
1352
+ </script>
1353
+ </body>
1354
+ </html>