fragment-ts 1.2.6 → 1.2.8

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.
@@ -227,7 +227,7 @@ class DocGenerator {
227
227
  return schema;
228
228
  }
229
229
  renderHtml(spec, templatePath) {
230
- let template = DEFAULT_TEMPLATE;
230
+ let template = MODERN_DARK_TEMPLATE;
231
231
  if (templatePath) {
232
232
  const resolved = path.isAbsolute(templatePath)
233
233
  ? templatePath
@@ -322,112 +322,168 @@ function resolveModuleName(controller) {
322
322
  function sanitizeModuleName(name) {
323
323
  return name.replace(/[^a-zA-Z0-9-_]+/g, "-");
324
324
  }
325
- const DEFAULT_TEMPLATE = `<!doctype html>
325
+ const MODERN_DARK_TEMPLATE = `<!doctype html>
326
326
  <html lang="en">
327
327
  <head>
328
328
  <meta charset="utf-8" />
329
329
  <meta name="viewport" content="width=device-width, initial-scale=1" />
330
330
  <title><%= spec.info.title %></title>
331
+ <link rel="preconnect" href="https://fonts.googleapis.com">
332
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
333
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
331
334
  <style>
332
- * { box-sizing: border-box; }
333
- :root {
334
- --color-bg: #0f172a;
335
- --color-bg-secondary: #1e293b;
336
- --color-surface: #334155;
337
- --color-text: #f1f5f9;
338
- --color-text-muted: #94a3b8;
339
- --color-primary: #3b82f6;
340
- --color-primary-dark: #2563eb;
341
- --color-success: #10b981;
342
- --color-warning: #f59e0b;
343
- --color-error: #ef4444;
344
- --color-border: #475569;
345
- --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
346
- --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
347
- --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3);
348
- }
349
-
350
- body {
351
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
352
- margin: 0;
353
- background: var(--color-bg);
354
- color: var(--color-text);
335
+ * {
336
+ box-sizing: border-box;
337
+ margin: 0;
338
+ padding: 0;
339
+ }
340
+
341
+ :root {
342
+ /* Dark Theme Colors */
343
+ --bg-primary: #0a0a0a;
344
+ --bg-secondary: #111111;
345
+ --bg-tertiary: #1a1a1a;
346
+ --bg-elevated: #1f1f1f;
347
+ --bg-hover: #252525;
348
+ --bg-active: #2a2a2a;
349
+
350
+ --border-subtle: #2a2a2a;
351
+ --border-default: #333333;
352
+ --border-strong: #3f3f3f;
353
+
354
+ --text-primary: #ffffff;
355
+ --text-secondary: #a1a1a1;
356
+ --text-tertiary: #737373;
357
+ --text-inverse: #0a0a0a;
358
+
359
+ --accent-blue: #3b82f6;
360
+ --accent-blue-hover: #2563eb;
361
+ --accent-green: #10b981;
362
+ --accent-yellow: #f59e0b;
363
+ --accent-orange: #f97316;
364
+ --accent-red: #ef4444;
365
+ --accent-purple: #8b5cf6;
366
+
367
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
368
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
369
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6);
370
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.7);
371
+
372
+ --radius-sm: 6px;
373
+ --radius-md: 8px;
374
+ --radius-lg: 12px;
375
+ --radius-xl: 16px;
376
+ }
377
+
378
+ body {
379
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif;
380
+ background: var(--bg-primary);
381
+ color: var(--text-primary);
355
382
  line-height: 1.6;
383
+ -webkit-font-smoothing: antialiased;
384
+ -moz-osx-font-smoothing: grayscale;
356
385
  }
357
386
 
358
- .header {
359
- background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
360
- padding: 2rem 2rem 3rem;
361
- border-bottom: 1px solid var(--color-border);
362
- box-shadow: var(--shadow-lg);
387
+ /* Header */
388
+ .header {
389
+ position: sticky;
390
+ top: 0;
391
+ z-index: 1000;
392
+ background: rgba(10, 10, 10, 0.8);
393
+ backdrop-filter: blur(12px);
394
+ border-bottom: 1px solid var(--border-default);
363
395
  }
364
396
 
365
397
  .header-content {
366
- max-width: 1400px;
398
+ max-width: 1600px;
367
399
  margin: 0 auto;
400
+ padding: 0 24px;
401
+ height: 64px;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: space-between;
368
405
  }
369
406
 
370
- .header h1 {
371
- margin: 0 0 0.5rem;
372
- font-size: 2.25rem;
373
- font-weight: 700;
374
- background: linear-gradient(135deg, var(--color-primary) 0%, #60a5fa 100%);
375
- -webkit-background-clip: text;
376
- -webkit-text-fill-color: transparent;
377
- background-clip: text;
407
+ .header-brand {
408
+ display: flex;
409
+ align-items: center;
410
+ gap: 12px;
378
411
  }
379
412
 
380
- .header-info {
413
+ .header-logo {
414
+ font-size: 24px;
415
+ }
416
+
417
+ .header-title {
418
+ font-size: 18px;
419
+ font-weight: 600;
420
+ color: var(--text-primary);
421
+ }
422
+
423
+ .header-nav {
381
424
  display: flex;
382
- gap: 1.5rem;
383
425
  align-items: center;
384
- margin-top: 1rem;
426
+ gap: 8px;
385
427
  }
386
428
 
387
- .version-badge {
388
- display: inline-flex;
389
- align-items: center;
390
- gap: 0.5rem;
391
- padding: 0.375rem 0.75rem;
392
- background: var(--color-bg-secondary);
393
- border: 1px solid var(--color-border);
394
- border-radius: 0.5rem;
395
- font-size: 0.875rem;
396
- color: var(--color-text-muted);
429
+ .header-link {
430
+ padding: 8px 16px;
431
+ color: var(--text-secondary);
432
+ text-decoration: none;
433
+ border-radius: var(--radius-md);
434
+ font-size: 14px;
435
+ font-weight: 500;
436
+ transition: all 0.2s;
397
437
  }
398
438
 
399
- .openapi-badge {
400
- display: inline-flex;
439
+ .header-link:hover {
440
+ color: var(--text-primary);
441
+ background: var(--bg-hover);
442
+ }
443
+
444
+ .header-link.active {
445
+ color: var(--text-primary);
446
+ background: var(--bg-active);
447
+ }
448
+
449
+ .theme-toggle {
450
+ padding: 8px;
451
+ background: none;
452
+ border: none;
453
+ color: var(--text-secondary);
454
+ cursor: pointer;
455
+ border-radius: var(--radius-md);
456
+ transition: all 0.2s;
457
+ font-size: 20px;
458
+ display: flex;
401
459
  align-items: center;
402
- padding: 0.375rem 0.75rem;
403
- background: var(--color-success);
404
- color: white;
405
- border-radius: 0.5rem;
406
- font-size: 0.75rem;
407
- font-weight: 600;
408
- text-transform: uppercase;
409
- letter-spacing: 0.05em;
460
+ justify-content: center;
410
461
  }
411
462
 
463
+ .theme-toggle:hover {
464
+ background: var(--bg-hover);
465
+ color: var(--text-primary);
466
+ }
467
+
468
+ /* Layout */
412
469
  .container {
413
- max-width: 1400px;
470
+ max-width: 1600px;
414
471
  margin: 0 auto;
415
- padding: 2rem;
416
- display: grid;
417
- grid-template-columns: 280px 1fr;
418
- gap: 2rem;
472
+ display: flex;
473
+ gap: 0;
419
474
  }
420
475
 
476
+ /* Sidebar */
421
477
  .sidebar {
478
+ width: 280px;
479
+ flex-shrink: 0;
480
+ background: var(--bg-secondary);
481
+ border-right: 1px solid var(--border-default);
482
+ height: calc(100vh - 64px);
422
483
  position: sticky;
423
- top: 2rem;
424
- height: fit-content;
425
- max-height: calc(100vh - 4rem);
484
+ top: 64px;
426
485
  overflow-y: auto;
427
- background: var(--color-bg-secondary);
428
- border: 1px solid var(--color-border);
429
- border-radius: 0.75rem;
430
- padding: 1.5rem;
486
+ overflow-x: hidden;
431
487
  }
432
488
 
433
489
  .sidebar::-webkit-scrollbar {
@@ -435,239 +491,443 @@ const DEFAULT_TEMPLATE = `<!doctype html>
435
491
  }
436
492
 
437
493
  .sidebar::-webkit-scrollbar-track {
438
- background: var(--color-bg-secondary);
494
+ background: transparent;
439
495
  }
440
496
 
441
497
  .sidebar::-webkit-scrollbar-thumb {
442
- background: var(--color-surface);
498
+ background: var(--border-strong);
443
499
  border-radius: 3px;
444
500
  }
445
501
 
446
- .sidebar h3 {
447
- margin: 0 0 1rem;
448
- font-size: 0.875rem;
449
- text-transform: uppercase;
450
- letter-spacing: 0.05em;
451
- color: var(--color-text-muted);
452
- font-weight: 600;
502
+ .sidebar::-webkit-scrollbar-thumb:hover {
503
+ background: #4a4a4a;
453
504
  }
454
505
 
455
- .tag-group {
456
- margin-bottom: 1.5rem;
506
+ .sidebar-section {
507
+ padding: 24px 16px 16px;
508
+ border-bottom: 1px solid var(--border-subtle);
457
509
  }
458
510
 
459
- .tag-title {
511
+ .sidebar-section:last-child {
512
+ border-bottom: none;
513
+ }
514
+
515
+ .sidebar-title {
516
+ font-size: 11px;
460
517
  font-weight: 600;
461
- color: var(--color-text);
462
- margin-bottom: 0.5rem;
463
- font-size: 0.9rem;
518
+ text-transform: uppercase;
519
+ letter-spacing: 0.8px;
520
+ color: var(--text-tertiary);
521
+ margin-bottom: 12px;
522
+ padding: 0 12px;
464
523
  }
465
524
 
466
- .tag-links {
525
+ .sidebar-item {
467
526
  display: flex;
468
- flex-direction: column;
469
- gap: 0.25rem;
527
+ align-items: center;
528
+ gap: 10px;
529
+ padding: 9px 12px;
530
+ color: var(--text-secondary);
531
+ text-decoration: none;
532
+ border-radius: var(--radius-sm);
533
+ font-size: 13px;
534
+ font-weight: 500;
535
+ transition: all 0.15s;
536
+ cursor: pointer;
537
+ border: none;
538
+ background: none;
539
+ width: 100%;
540
+ text-align: left;
541
+ margin-bottom: 2px;
470
542
  }
471
543
 
472
- .tag-link {
473
- padding: 0.5rem 0.75rem;
474
- color: var(--color-text-muted);
475
- text-decoration: none;
476
- border-radius: 0.375rem;
477
- font-size: 0.875rem;
544
+ .sidebar-item:hover {
545
+ background: var(--bg-hover);
546
+ color: var(--text-primary);
547
+ }
548
+
549
+ .sidebar-item.active {
550
+ background: var(--bg-active);
551
+ color: var(--text-primary);
552
+ }
553
+
554
+ .sidebar-icon {
555
+ font-size: 16px;
556
+ opacity: 0.8;
557
+ }
558
+
559
+ .method-badge-nav {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ justify-content: center;
563
+ width: 45px;
564
+ padding: 2px 6px;
565
+ border-radius: 4px;
566
+ font-size: 9px;
567
+ font-weight: 700;
568
+ font-family: 'JetBrains Mono', monospace;
569
+ letter-spacing: 0.3px;
570
+ }
571
+
572
+ .sidebar-item-text {
573
+ flex: 1;
574
+ overflow: hidden;
575
+ text-overflow: ellipsis;
576
+ white-space: nowrap;
577
+ font-size: 12px;
578
+ }
579
+
580
+ /* Search */
581
+ .search-box {
582
+ padding: 16px;
583
+ border-bottom: 1px solid var(--border-subtle);
584
+ }
585
+
586
+ .search-input {
587
+ width: 100%;
588
+ padding: 10px 12px 10px 36px;
589
+ background: var(--bg-tertiary);
590
+ border: 1px solid var(--border-default);
591
+ border-radius: var(--radius-md);
592
+ color: var(--text-primary);
593
+ font-size: 13px;
594
+ font-family: inherit;
478
595
  transition: all 0.2s;
479
- display: block;
480
596
  }
481
597
 
482
- .tag-link:hover {
483
- background: var(--color-surface);
484
- color: var(--color-text);
485
- transform: translateX(4px);
598
+ .search-input:focus {
599
+ outline: none;
600
+ border-color: var(--accent-blue);
601
+ background: var(--bg-elevated);
602
+ }
603
+
604
+ .search-input::placeholder {
605
+ color: var(--text-tertiary);
486
606
  }
487
607
 
488
- .main-content {
608
+ .search-container {
609
+ position: relative;
610
+ }
611
+
612
+ .search-icon {
613
+ position: absolute;
614
+ left: 12px;
615
+ top: 50%;
616
+ transform: translateY(-50%);
617
+ color: var(--text-tertiary);
618
+ pointer-events: none;
619
+ }
620
+
621
+ /* Main Content */
622
+ .main {
623
+ flex: 1;
624
+ padding: 32px;
489
625
  min-width: 0;
626
+ background: var(--bg-primary);
490
627
  }
491
628
 
492
- .section-title {
493
- font-size: 1.5rem;
629
+ .main-header {
630
+ margin-bottom: 32px;
631
+ }
632
+
633
+ .main-title {
634
+ font-size: 32px;
494
635
  font-weight: 700;
495
- margin: 0 0 1.5rem;
496
- color: var(--color-text);
636
+ margin-bottom: 8px;
637
+ background: linear-gradient(135deg, var(--text-primary) 0%, var(--text-secondary) 100%);
638
+ -webkit-background-clip: text;
639
+ -webkit-text-fill-color: transparent;
640
+ background-clip: text;
641
+ }
642
+
643
+ .main-subtitle {
644
+ font-size: 15px;
645
+ color: var(--text-secondary);
646
+ }
647
+
648
+ .section {
649
+ margin-bottom: 48px;
497
650
  }
498
651
 
499
- .endpoints-section {
500
- margin-bottom: 3rem;
652
+ .section-title {
653
+ font-size: 24px;
654
+ font-weight: 700;
655
+ margin-bottom: 24px;
656
+ padding-bottom: 12px;
657
+ border-bottom: 1px solid var(--border-default);
658
+ display: flex;
659
+ align-items: center;
660
+ gap: 12px;
501
661
  }
502
662
 
663
+ /* Endpoint Card */
503
664
  .endpoint {
504
- background: var(--color-bg-secondary);
505
- border: 1px solid var(--color-border);
506
- border-radius: 0.75rem;
507
- margin-bottom: 1rem;
665
+ background: var(--bg-secondary);
666
+ border: 1px solid var(--border-default);
667
+ border-radius: var(--radius-lg);
668
+ margin-bottom: 16px;
508
669
  overflow: hidden;
509
- transition: all 0.3s;
670
+ transition: all 0.2s;
510
671
  }
511
672
 
512
673
  .endpoint:hover {
513
- border-color: var(--color-primary);
674
+ border-color: var(--border-strong);
514
675
  box-shadow: var(--shadow-md);
515
676
  }
516
677
 
517
678
  .endpoint-header {
518
- padding: 1.25rem;
679
+ padding: 20px 24px;
519
680
  cursor: pointer;
520
- display: flex;
521
- align-items: center;
522
- gap: 1rem;
523
- transition: background 0.2s;
681
+ user-select: none;
682
+ background: var(--bg-secondary);
683
+ transition: all 0.2s;
524
684
  }
525
685
 
526
686
  .endpoint-header:hover {
527
- background: rgba(59, 130, 246, 0.05);
687
+ background: var(--bg-tertiary);
688
+ }
689
+
690
+ .endpoint.expanded .endpoint-header {
691
+ background: var(--bg-tertiary);
692
+ border-bottom: 1px solid var(--border-default);
693
+ }
694
+
695
+ .endpoint-title-row {
696
+ display: flex;
697
+ align-items: center;
698
+ gap: 12px;
699
+ margin-bottom: 8px;
528
700
  }
529
701
 
530
702
  .method-badge {
531
703
  display: inline-flex;
532
704
  align-items: center;
533
705
  justify-content: center;
534
- min-width: 4rem;
535
- padding: 0.375rem 0.75rem;
536
- border-radius: 0.375rem;
537
- font-size: 0.75rem;
706
+ min-width: 72px;
707
+ padding: 6px 14px;
708
+ border-radius: var(--radius-sm);
709
+ font-size: 11px;
538
710
  font-weight: 700;
711
+ font-family: 'JetBrains Mono', monospace;
539
712
  text-transform: uppercase;
540
- letter-spacing: 0.05em;
713
+ letter-spacing: 0.5px;
541
714
  }
542
715
 
543
- .method-get { background: var(--color-primary); color: white; }
544
- .method-post { background: var(--color-success); color: white; }
545
- .method-put { background: var(--color-warning); color: white; }
546
- .method-delete { background: var(--color-error); color: white; }
547
- .method-patch { background: #8b5cf6; color: white; }
716
+ .method-get { background: var(--accent-green); color: var(--text-inverse); }
717
+ .method-post { background: var(--accent-blue); color: white; }
718
+ .method-put { background: var(--accent-yellow); color: var(--text-inverse); }
719
+ .method-patch { background: var(--accent-purple); color: white; }
720
+ .method-delete { background: var(--accent-red); color: white; }
548
721
 
549
722
  .endpoint-path {
550
- font-family: "JetBrains Mono", "Fira Code", "Courier New", monospace;
551
- font-size: 0.9rem;
552
- color: var(--color-text);
553
- font-weight: 500;
554
723
  flex: 1;
724
+ font-family: 'JetBrains Mono', monospace;
725
+ font-size: 14px;
726
+ font-weight: 500;
727
+ color: var(--text-primary);
728
+ overflow: hidden;
729
+ text-overflow: ellipsis;
730
+ white-space: nowrap;
555
731
  }
556
732
 
557
733
  .expand-icon {
558
- width: 20px;
559
- height: 20px;
734
+ color: var(--text-tertiary);
560
735
  transition: transform 0.3s;
561
- color: var(--color-text-muted);
736
+ font-size: 18px;
562
737
  }
563
738
 
564
739
  .endpoint.expanded .expand-icon {
565
740
  transform: rotate(180deg);
566
741
  }
567
742
 
743
+ .endpoint-summary {
744
+ color: var(--text-secondary);
745
+ font-size: 14px;
746
+ margin: 0;
747
+ line-height: 1.5;
748
+ }
749
+
750
+ .endpoint-tags {
751
+ display: flex;
752
+ gap: 8px;
753
+ margin-top: 12px;
754
+ flex-wrap: wrap;
755
+ }
756
+
757
+ .tag {
758
+ padding: 4px 10px;
759
+ background: var(--bg-elevated);
760
+ border: 1px solid var(--border-default);
761
+ border-radius: 12px;
762
+ font-size: 11px;
763
+ color: var(--text-secondary);
764
+ font-weight: 500;
765
+ }
766
+
767
+ /* Endpoint Body */
568
768
  .endpoint-body {
569
- max-height: 0;
570
- overflow: hidden;
571
- transition: max-height 0.3s ease-out;
769
+ display: none;
770
+ padding: 24px;
771
+ background: var(--bg-tertiary);
572
772
  }
573
773
 
574
774
  .endpoint.expanded .endpoint-body {
575
- max-height: 5000px;
576
- transition: max-height 0.5s ease-in;
775
+ display: block;
776
+ animation: slideDown 0.3s ease-out;
577
777
  }
578
778
 
579
- .endpoint-content {
580
- padding: 0 1.25rem 1.25rem;
581
- border-top: 1px solid var(--color-border);
779
+ @keyframes slideDown {
780
+ from {
781
+ opacity: 0;
782
+ transform: translateY(-8px);
783
+ }
784
+ to {
785
+ opacity: 1;
786
+ transform: translateY(0);
787
+ }
582
788
  }
583
789
 
584
- .endpoint-summary {
585
- color: var(--color-text-muted);
586
- margin: 1rem 0;
587
- font-size: 0.95rem;
790
+ .content-section {
791
+ margin-bottom: 28px;
792
+ }
793
+
794
+ .content-section:last-child {
795
+ margin-bottom: 0;
796
+ }
797
+
798
+ .content-title {
799
+ font-size: 13px;
800
+ font-weight: 600;
801
+ margin-bottom: 16px;
802
+ color: var(--text-primary);
803
+ text-transform: uppercase;
804
+ letter-spacing: 0.5px;
805
+ display: flex;
806
+ align-items: center;
807
+ gap: 8px;
588
808
  }
589
809
 
590
- .endpoint-description {
591
- color: var(--color-text-muted);
592
- margin: 0.5rem 0 1rem;
593
- font-size: 0.9rem;
810
+ .description-text {
811
+ color: var(--text-secondary);
812
+ margin: 0 0 20px;
813
+ line-height: 1.7;
814
+ font-size: 14px;
594
815
  }
595
816
 
596
- .tags {
817
+ /* Tabs */
818
+ .tabs {
597
819
  display: flex;
598
- gap: 0.5rem;
599
- flex-wrap: wrap;
600
- margin: 1rem 0;
820
+ gap: 4px;
821
+ margin-bottom: 16px;
822
+ background: var(--bg-elevated);
823
+ padding: 4px;
824
+ border-radius: var(--radius-md);
825
+ width: fit-content;
601
826
  }
602
827
 
603
- .tag {
604
- padding: 0.25rem 0.75rem;
605
- background: var(--color-surface);
606
- color: var(--color-primary);
607
- border-radius: 0.25rem;
608
- font-size: 0.75rem;
828
+ .tab {
829
+ padding: 8px 16px;
830
+ background: transparent;
831
+ border: none;
832
+ cursor: pointer;
833
+ font-size: 13px;
609
834
  font-weight: 500;
835
+ color: var(--text-secondary);
836
+ transition: all 0.2s;
837
+ border-radius: var(--radius-sm);
838
+ font-family: inherit;
610
839
  }
611
840
 
612
- .subsection {
613
- margin: 1.5rem 0;
841
+ .tab:hover {
842
+ color: var(--text-primary);
843
+ background: var(--bg-hover);
614
844
  }
615
845
 
616
- .subsection-title {
617
- font-size: 0.875rem;
618
- font-weight: 700;
619
- text-transform: uppercase;
620
- letter-spacing: 0.05em;
621
- color: var(--color-text-muted);
622
- margin-bottom: 0.75rem;
846
+ .tab.active {
847
+ color: var(--text-primary);
848
+ background: var(--bg-secondary);
849
+ }
850
+
851
+ .tab-content {
852
+ display: none;
623
853
  }
624
854
 
855
+ .tab-content.active {
856
+ display: block;
857
+ }
858
+
859
+ /* Code Block */
625
860
  .code-block {
626
- background: var(--color-bg);
627
- border: 1px solid var(--color-border);
628
- border-radius: 0.5rem;
861
+ position: relative;
862
+ background: var(--bg-primary);
863
+ border: 1px solid var(--border-default);
864
+ border-radius: var(--radius-md);
629
865
  overflow: hidden;
630
866
  }
631
867
 
632
868
  .code-header {
633
869
  display: flex;
634
- justify-content: space-between;
635
870
  align-items: center;
636
- padding: 0.75rem 1rem;
637
- background: rgba(0, 0, 0, 0.2);
638
- border-bottom: 1px solid var(--color-border);
871
+ justify-content: space-between;
872
+ padding: 10px 16px;
873
+ background: var(--bg-secondary);
874
+ border-bottom: 1px solid var(--border-default);
639
875
  }
640
876
 
641
877
  .code-language {
642
- font-size: 0.75rem;
643
- color: var(--color-text-muted);
644
- text-transform: uppercase;
878
+ font-size: 11px;
645
879
  font-weight: 600;
880
+ color: var(--text-tertiary);
881
+ text-transform: uppercase;
882
+ letter-spacing: 0.5px;
883
+ font-family: 'JetBrains Mono', monospace;
884
+ }
885
+
886
+ .code-actions {
887
+ display: flex;
888
+ gap: 8px;
646
889
  }
647
890
 
648
891
  .copy-btn {
649
- padding: 0.25rem 0.75rem;
650
- background: var(--color-surface);
651
- color: var(--color-text);
652
- border: none;
653
- border-radius: 0.25rem;
654
- font-size: 0.75rem;
892
+ padding: 6px 12px;
893
+ background: var(--bg-tertiary);
894
+ border: 1px solid var(--border-default);
895
+ color: var(--text-secondary);
896
+ border-radius: var(--radius-sm);
655
897
  cursor: pointer;
898
+ font-size: 11px;
899
+ font-weight: 600;
656
900
  transition: all 0.2s;
901
+ font-family: inherit;
902
+ display: flex;
903
+ align-items: center;
904
+ gap: 6px;
657
905
  }
658
906
 
659
907
  .copy-btn:hover {
660
- background: var(--color-primary);
908
+ background: var(--bg-hover);
909
+ color: var(--text-primary);
910
+ border-color: var(--border-strong);
911
+ }
912
+
913
+ .copy-btn:active {
914
+ transform: scale(0.95);
915
+ }
916
+
917
+ .copy-btn.copied {
918
+ background: var(--accent-green);
661
919
  color: white;
920
+ border-color: var(--accent-green);
662
921
  }
663
922
 
664
923
  pre {
665
924
  margin: 0;
666
- padding: 1rem;
925
+ padding: 16px;
667
926
  overflow-x: auto;
668
- font-family: "JetBrains Mono", "Fira Code", "Courier New", monospace;
669
- font-size: 0.85rem;
927
+ font-family: 'JetBrains Mono', monospace;
928
+ font-size: 13px;
670
929
  line-height: 1.6;
930
+ color: var(--text-primary);
671
931
  }
672
932
 
673
933
  pre::-webkit-scrollbar {
@@ -675,340 +935,659 @@ const DEFAULT_TEMPLATE = `<!doctype html>
675
935
  }
676
936
 
677
937
  pre::-webkit-scrollbar-track {
678
- background: var(--color-bg);
938
+ background: var(--bg-secondary);
679
939
  }
680
940
 
681
941
  pre::-webkit-scrollbar-thumb {
682
- background: var(--color-surface);
942
+ background: var(--border-strong);
683
943
  border-radius: 4px;
684
944
  }
685
945
 
686
- .json-key { color: #60a5fa; }
687
- .json-string { color: #34d399; }
688
- .json-number { color: #f59e0b; }
689
- .json-boolean { color: #a78bfa; }
690
- .json-null { color: #94a3b8; }
946
+ /* Response Status */
947
+ .response-item {
948
+ margin-bottom: 16px;
949
+ background: var(--bg-elevated);
950
+ border: 1px solid var(--border-default);
951
+ border-radius: var(--radius-md);
952
+ overflow: hidden;
953
+ }
691
954
 
692
- .response-status {
693
- display: inline-flex;
955
+ .response-item.status-2xx { border-left: 3px solid var(--accent-green); }
956
+ .response-item.status-4xx { border-left: 3px solid var(--accent-yellow); }
957
+ .response-item.status-5xx { border-left: 3px solid var(--accent-red); }
958
+
959
+ .response-header {
960
+ display: flex;
694
961
  align-items: center;
695
- gap: 0.5rem;
696
- padding: 0.5rem 0.75rem;
697
- background: var(--color-surface);
698
- border-radius: 0.375rem;
699
- margin-bottom: 0.75rem;
700
- font-family: "JetBrains Mono", monospace;
701
- font-size: 0.875rem;
962
+ gap: 12px;
963
+ padding: 16px;
964
+ background: var(--bg-secondary);
965
+ border-bottom: 1px solid var(--border-default);
702
966
  }
703
967
 
704
- .status-2xx { background: rgba(16, 185, 129, 0.1); color: var(--color-success); }
705
- .status-3xx { background: rgba(59, 130, 246, 0.1); color: var(--color-primary); }
706
- .status-4xx { background: rgba(245, 158, 11, 0.1); color: var(--color-warning); }
707
- .status-5xx { background: rgba(239, 68, 68, 0.1); color: var(--color-error); }
968
+ .status-code {
969
+ font-family: 'JetBrains Mono', monospace;
970
+ font-size: 13px;
971
+ font-weight: 700;
972
+ padding: 4px 10px;
973
+ border-radius: var(--radius-sm);
974
+ background: var(--bg-tertiary);
975
+ }
708
976
 
709
- .schemas-section {
710
- margin-top: 3rem;
711
- padding-top: 2rem;
712
- border-top: 2px solid var(--color-border);
977
+ .status-2xx .status-code { color: var(--accent-green); }
978
+ .status-4xx .status-code { color: var(--accent-yellow); }
979
+ .status-5xx .status-code { color: var(--accent-red); }
980
+
981
+ .response-description {
982
+ color: var(--text-secondary);
983
+ font-size: 14px;
984
+ flex: 1;
713
985
  }
714
986
 
715
- .schema-card {
716
- background: var(--color-bg-secondary);
717
- border: 1px solid var(--color-border);
718
- border-radius: 0.75rem;
719
- padding: 1.5rem;
720
- margin-bottom: 1rem;
987
+ .response-content {
988
+ padding: 16px;
721
989
  }
722
990
 
723
- .schema-title {
724
- font-size: 1.125rem;
725
- font-weight: 700;
726
- color: var(--color-text);
727
- margin-bottom: 1rem;
728
- font-family: "JetBrains Mono", monospace;
991
+ /* Schema Section */
992
+ .schema-section {
993
+ background: var(--bg-secondary);
994
+ border: 1px solid var(--border-default);
995
+ border-radius: var(--radius-lg);
996
+ padding: 24px;
997
+ margin-bottom: 20px;
998
+ }
999
+
1000
+ .schema-item {
1001
+ margin-bottom: 32px;
1002
+ padding-bottom: 32px;
1003
+ border-bottom: 1px solid var(--border-subtle);
729
1004
  }
730
1005
 
731
- .try-it {
732
- margin-top: 1.5rem;
733
- padding: 1rem;
734
- background: rgba(59, 130, 246, 0.05);
735
- border: 1px solid rgba(59, 130, 246, 0.2);
736
- border-radius: 0.5rem;
1006
+ .schema-item:last-child {
1007
+ margin-bottom: 0;
1008
+ padding-bottom: 0;
1009
+ border-bottom: none;
737
1010
  }
738
1011
 
739
- .try-it-title {
740
- font-size: 0.875rem;
1012
+ .schema-name {
1013
+ font-size: 20px;
741
1014
  font-weight: 700;
742
- color: var(--color-primary);
743
- margin-bottom: 0.75rem;
744
- text-transform: uppercase;
745
- letter-spacing: 0.05em;
1015
+ margin-bottom: 16px;
1016
+ color: var(--text-primary);
1017
+ font-family: 'JetBrains Mono', monospace;
746
1018
  }
747
1019
 
748
- .try-it-input {
1020
+ /* Property Table */
1021
+ .property-table {
749
1022
  width: 100%;
750
- padding: 0.75rem;
751
- background: var(--color-bg);
752
- border: 1px solid var(--color-border);
753
- border-radius: 0.375rem;
754
- color: var(--color-text);
755
- font-family: "JetBrains Mono", monospace;
756
- font-size: 0.875rem;
757
- margin-bottom: 0.75rem;
758
- }
759
-
760
- .try-it-btn {
761
- padding: 0.75rem 1.5rem;
762
- background: var(--color-primary);
763
- color: white;
764
- border: none;
765
- border-radius: 0.375rem;
1023
+ border-collapse: collapse;
1024
+ font-size: 13px;
1025
+ background: var(--bg-tertiary);
1026
+ border-radius: var(--radius-md);
1027
+ overflow: hidden;
1028
+ }
1029
+
1030
+ .property-table thead {
1031
+ background: var(--bg-elevated);
1032
+ }
1033
+
1034
+ .property-table th {
1035
+ text-align: left;
1036
+ padding: 14px 16px;
766
1037
  font-weight: 600;
767
- cursor: pointer;
768
- transition: all 0.2s;
769
- width: 100%;
1038
+ color: var(--text-primary);
1039
+ font-size: 12px;
1040
+ text-transform: uppercase;
1041
+ letter-spacing: 0.5px;
1042
+ border-bottom: 1px solid var(--border-default);
770
1043
  }
771
1044
 
772
- .try-it-btn:hover {
773
- background: var(--color-primary-dark);
774
- transform: translateY(-1px);
775
- box-shadow: var(--shadow-md);
1045
+ .property-table td {
1046
+ padding: 14px 16px;
1047
+ border-bottom: 1px solid var(--border-subtle);
1048
+ vertical-align: top;
1049
+ }
1050
+
1051
+ .property-table tbody tr:last-child td {
1052
+ border-bottom: none;
776
1053
  }
777
1054
 
778
- @media (max-width: 1024px) {
1055
+ .property-table tbody tr:hover {
1056
+ background: var(--bg-elevated);
1057
+ }
1058
+
1059
+ .property-name {
1060
+ font-family: 'JetBrains Mono', monospace;
1061
+ font-weight: 600;
1062
+ color: var(--text-primary);
1063
+ font-size: 13px;
1064
+ }
1065
+
1066
+ .property-type {
1067
+ font-family: 'JetBrains Mono', monospace;
1068
+ color: var(--accent-blue);
1069
+ font-size: 12px;
1070
+ font-weight: 500;
1071
+ }
1072
+
1073
+ .required-badge {
1074
+ display: inline-block;
1075
+ padding: 2px 8px;
1076
+ background: var(--accent-red);
1077
+ color: white;
1078
+ border-radius: 4px;
1079
+ font-size: 10px;
1080
+ font-weight: 700;
1081
+ margin-left: 8px;
1082
+ text-transform: uppercase;
1083
+ letter-spacing: 0.3px;
1084
+ }
1085
+
1086
+ /* Syntax Highlighting */
1087
+ .hljs-string { color: #a5d6ff; }
1088
+ .hljs-number { color: #79c0ff; }
1089
+ .hljs-literal { color: #ff7b72; }
1090
+ .hljs-attr { color: #7ee787; }
1091
+ .hljs-punctuation { color: #e6edf3; }
1092
+
1093
+ /* Utility Classes */
1094
+ .flex { display: flex; }
1095
+ .items-center { align-items: center; }
1096
+ .gap-2 { gap: 8px; }
1097
+ .gap-3 { gap: 12px; }
1098
+ .mb-2 { margin-bottom: 8px; }
1099
+ .mb-4 { margin-bottom: 16px; }
1100
+
1101
+ /* Responsive */
1102
+ @media (max-width: 1200px) {
779
1103
  .container {
780
- grid-template-columns: 1fr;
1104
+ flex-direction: column;
781
1105
  }
1106
+
782
1107
  .sidebar {
1108
+ width: 100%;
1109
+ height: auto;
783
1110
  position: static;
784
- max-height: none;
1111
+ border-right: none;
1112
+ border-bottom: 1px solid var(--border-default);
1113
+ }
1114
+ }
1115
+
1116
+ @media (max-width: 768px) {
1117
+ .main {
1118
+ padding: 20px 16px;
1119
+ }
1120
+
1121
+ .endpoint-header {
1122
+ padding: 16px;
1123
+ }
1124
+
1125
+ .endpoint-body {
1126
+ padding: 16px;
1127
+ }
1128
+
1129
+ .method-badge {
1130
+ min-width: 60px;
1131
+ font-size: 10px;
1132
+ }
1133
+
1134
+ .endpoint-path {
1135
+ font-size: 12px;
1136
+ }
1137
+
1138
+ .header-content {
1139
+ padding: 0 16px;
785
1140
  }
786
1141
  }
1142
+
1143
+ /* Animations */
1144
+ @keyframes fadeIn {
1145
+ from { opacity: 0; }
1146
+ to { opacity: 1; }
1147
+ }
1148
+
1149
+ .fade-in {
1150
+ animation: fadeIn 0.3s ease-out;
1151
+ }
1152
+
1153
+ /* Loading State */
1154
+ .loading {
1155
+ display: flex;
1156
+ align-items: center;
1157
+ justify-content: center;
1158
+ padding: 40px;
1159
+ color: var(--text-tertiary);
1160
+ }
1161
+
1162
+ .spinner {
1163
+ width: 40px;
1164
+ height: 40px;
1165
+ border: 3px solid var(--border-default);
1166
+ border-top-color: var(--accent-blue);
1167
+ border-radius: 50%;
1168
+ animation: spin 0.8s linear infinite;
1169
+ }
1170
+
1171
+ @keyframes spin {
1172
+ to { transform: rotate(360deg); }
1173
+ }
787
1174
  </style>
788
1175
  </head>
789
1176
  <body>
790
- <div class="header">
1177
+ <!-- Header -->
1178
+ <header class="header">
791
1179
  <div class="header-content">
792
- <h1><%= spec.info.title %></h1>
793
- <div class="header-info">
794
- <span class="version-badge">
795
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
796
- <path d="M8 1L10.5 6L16 7L12 11L13 16L8 13.5L3 16L4 11L0 7L5.5 6L8 1Z" fill="currentColor"/>
797
- </svg>
798
- Version <%= spec.info.version %>
799
- </span>
800
- <span class="openapi-badge">OpenAPI 3.0.0</span>
1180
+ <div class="header-brand">
1181
+ <span class="header-logo">🔷</span>
1182
+ <span class="header-title"><%= spec.info.title %></span>
801
1183
  </div>
1184
+ <nav class="header-nav">
1185
+ <a href="#" class="header-link active">Docs</a>
1186
+ <a href="#" class="header-link">API</a>
1187
+ <a href="#" class="header-link">GitHub</a>
1188
+ <button class="theme-toggle" onclick="toggleTheme()">☀️</button>
1189
+ </nav>
802
1190
  </div>
803
- </div>
804
-
1191
+ </header>
1192
+
1193
+ <!-- Main Container -->
805
1194
  <div class="container">
1195
+ <!-- Sidebar -->
806
1196
  <aside class="sidebar">
807
- <h3>Endpoints</h3>
808
- <%
809
- const paths = spec.paths || {};
810
- const groupedByTag = {};
811
-
812
- Object.entries(paths).forEach(([pathKey, ops]) => {
813
- Object.entries(ops).forEach(([verb, operation]) => {
814
- const tags = operation.tags || ['General'];
815
- tags.forEach(tag => {
816
- if (!groupedByTag[tag]) groupedByTag[tag] = [];
817
- groupedByTag[tag].push({ path: pathKey, method: verb, operation });
818
- });
819
- });
820
- });
821
- %>
1197
+ <!-- Search -->
1198
+ <div class="search-box">
1199
+ <div class="search-container">
1200
+ <span class="search-icon">🔍</span>
1201
+ <input
1202
+ type="text"
1203
+ class="search-input"
1204
+ placeholder="Search endpoints..."
1205
+ id="searchInput"
1206
+ oninput="filterEndpoints(this.value)"
1207
+ />
1208
+ </div>
1209
+ </div>
822
1210
 
823
- <% Object.entries(groupedByTag).forEach(([tag, endpoints]) => { %>
824
- <div class="tag-group">
825
- <div class="tag-title"><%= tag %></div>
826
- <div class="tag-links">
827
- <% endpoints.forEach(({ path, method }) => { %>
828
- <a href="#<%= method %>-<%= path.replace(/\//g, '-').replace(/[{}:]/g, '') %>" class="tag-link">
829
- <%= method.toUpperCase() %> <%= path %>
830
- </a>
831
- <% }) %>
832
- </div>
1211
+ <!-- Navigation -->
1212
+ <div class="sidebar-section">
1213
+ <div class="sidebar-title">Endpoints</div>
1214
+ <% const paths = spec.paths || {}; %>
1215
+ <% Object.entries(paths).forEach(([pathKey, ops]) => { %>
1216
+ <% Object.entries(ops).forEach(([verb, operation]) => { %>
1217
+ <button
1218
+ class="sidebar-item endpoint-nav-item"
1219
+ onclick="scrollToEndpoint('<%= pathKey %>-<%= verb %>')"
1220
+ data-path="<%= pathKey %>"
1221
+ data-method="<%= verb %>"
1222
+ >
1223
+ <span class="method-badge-nav method-<%= verb %>"><%= verb.toUpperCase() %></span>
1224
+ <span class="sidebar-item-text"><%= pathKey %></span>
1225
+ </button>
1226
+ <% }) %>
1227
+ <% }) %>
1228
+ </div>
1229
+
1230
+ <% if (spec.components?.schemas && Object.keys(spec.components.schemas).length > 0) { %>
1231
+ <div class="sidebar-section">
1232
+ <div class="sidebar-title">Schemas</div>
1233
+ <button class="sidebar-item" onclick="scrollToSchemas()">
1234
+ <span class="sidebar-icon">📦</span>
1235
+ <span class="sidebar-item-text">View All Schemas</span>
1236
+ </button>
833
1237
  </div>
834
- <% }) %>
1238
+ <% } %>
835
1239
  </aside>
836
-
837
- <main class="main-content">
838
- <div class="endpoints-section">
839
- <h2 class="section-title">API Endpoints</h2>
1240
+
1241
+ <!-- Main Content -->
1242
+ <main class="main">
1243
+ <!-- Header -->
1244
+ <div class="main-header">
1245
+ <h1 class="main-title"><%= spec.info.title %></h1>
1246
+ <p class="main-subtitle">Version <%= spec.info.version %> • OpenAPI 3.0.0</p>
1247
+ </div>
1248
+
1249
+ <!-- Endpoints Section -->
1250
+ <section class="section">
1251
+ <h2 class="section-title">
1252
+ <span>📡</span>
1253
+ <span>API Endpoints</span>
1254
+ </h2>
840
1255
 
841
1256
  <% Object.entries(paths).forEach(([pathKey, ops]) => { %>
842
1257
  <% Object.entries(ops).forEach(([verb, operation]) => { %>
843
- <div class="endpoint" id="<%= verb %>-<%= pathKey.replace(/\//g, '-').replace(/[{}:]/g, '') %>">
844
- <div class="endpoint-header" onclick="this.parentElement.classList.toggle('expanded')">
845
- <span class="method-badge method-<%= verb %>"><%= verb.toUpperCase() %></span>
846
- <span class="endpoint-path"><%= pathKey %></span>
847
- <svg class="expand-icon" viewBox="0 0 20 20" fill="currentColor">
848
- <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
849
- </svg>
850
- </div>
1258
+ <article
1259
+ class="endpoint"
1260
+ id="<%= pathKey %>-<%= verb %>"
1261
+ data-path="<%= pathKey %>"
1262
+ data-method="<%= verb %>"
1263
+ >
1264
+ <!-- Endpoint Header -->
1265
+ <header class="endpoint-header" onclick="toggleEndpoint(this)">
1266
+ <div class="endpoint-title-row">
1267
+ <span class="method-badge method-<%= verb %>"><%= verb.toUpperCase() %></span>
1268
+ <code class="endpoint-path"><%= pathKey %></code>
1269
+ <span class="expand-icon">▼</span>
1270
+ </div>
1271
+
1272
+ <% if (operation.summary) { %>
1273
+ <p class="endpoint-summary"><%= operation.summary %></p>
1274
+ <% } %>
1275
+
1276
+ <% if (operation.tags && operation.tags.length > 0) { %>
1277
+ <div class="endpoint-tags">
1278
+ <% operation.tags.forEach(tag => { %>
1279
+ <span class="tag"><%= tag %></span>
1280
+ <% }) %>
1281
+ </div>
1282
+ <% } %>
1283
+ </header>
851
1284
 
1285
+ <!-- Endpoint Body -->
852
1286
  <div class="endpoint-body">
853
- <div class="endpoint-content">
854
- <% if (operation.summary) { %>
855
- <div class="endpoint-summary"><strong>Summary:</strong> <%= operation.summary %></div>
856
- <% } %>
857
-
858
- <% if (operation.description) { %>
859
- <div class="endpoint-description"><%= operation.description %></div>
860
- <% } %>
861
-
862
- <% if (operation.tags && operation.tags.length > 0) { %>
863
- <div class="tags">
864
- <% operation.tags.forEach(tag => { %>
865
- <span class="tag"><%= tag %></span>
866
- <% }) %>
1287
+ <% if (operation.description) { %>
1288
+ <div class="content-section">
1289
+ <p class="description-text"><%= operation.description %></p>
1290
+ </div>
1291
+ <% } %>
1292
+
1293
+ <!-- Request Body -->
1294
+ <% if (operation.requestBody) { %>
1295
+ <div class="content-section">
1296
+ <h4 class="content-title">
1297
+ <span>📤</span>
1298
+ <span>Request Body</span>
1299
+ </h4>
1300
+
1301
+ <div class="tabs">
1302
+ <button class="tab active" onclick="switchTab(this, 'request-example-<%= pathKey %>-<%= verb %>')">
1303
+ Example
1304
+ </button>
1305
+ <button class="tab" onclick="switchTab(this, 'request-schema-<%= pathKey %>-<%= verb %>')">
1306
+ Schema
1307
+ </button>
867
1308
  </div>
868
- <% } %>
869
-
870
- <% if (operation.requestBody) { %>
871
- <div class="subsection">
872
- <div class="subsection-title">Request Body</div>
1309
+
1310
+ <div id="request-example-<%= pathKey %>-<%= verb %>" class="tab-content active">
873
1311
  <div class="code-block">
874
1312
  <div class="code-header">
875
1313
  <span class="code-language">JSON</span>
876
- <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1314
+ <div class="code-actions">
1315
+ <button class="copy-btn" onclick="copyCode(this)">
1316
+ <span>📋</span>
1317
+ <span>Copy</span>
1318
+ </button>
1319
+ </div>
1320
+ </div>
1321
+ <pre><%= JSON.stringify(operation.requestBody.content?.['application/json']?.example || operation.requestBody, null, 2) %></pre>
1322
+ </div>
1323
+ </div>
1324
+
1325
+ <div id="request-schema-<%= pathKey %>-<%= verb %>" class="tab-content">
1326
+ <div class="code-block">
1327
+ <div class="code-header">
1328
+ <span class="code-language">Schema</span>
1329
+ <div class="code-actions">
1330
+ <button class="copy-btn" onclick="copyCode(this)">
1331
+ <span>📋</span>
1332
+ <span>Copy</span>
1333
+ </button>
1334
+ </div>
877
1335
  </div>
878
- <pre><%= syntaxHighlight(JSON.stringify(operation.requestBody.content?.['application/json']?.example || operation.requestBody, null, 2)) %></pre>
1336
+ <pre><%= JSON.stringify(operation.requestBody.content?.['application/json']?.schema || {}, null, 2) %></pre>
879
1337
  </div>
880
1338
  </div>
881
- <% } %>
1339
+ </div>
1340
+ <% } %>
1341
+
1342
+ <!-- Responses -->
1343
+ <div class="content-section">
1344
+ <h4 class="content-title">
1345
+ <span>📥</span>
1346
+ <span>Responses</span>
1347
+ </h4>
882
1348
 
883
- <div class="subsection">
884
- <div class="subsection-title">Responses</div>
885
- <% Object.entries(operation.responses || {}).forEach(([status, response]) => { %>
886
- <div class="response-status status-<%= status.charAt(0) %>xx">
887
- <strong><%= status %></strong>
888
- <span><%= response.description || 'Response' %></span>
1349
+ <% const responses = operation.responses || {}; %>
1350
+ <% Object.entries(responses).forEach(([status, response]) => { %>
1351
+ <% const statusClass = status.startsWith('2') ? 'status-2xx' : status.startsWith('4') ? 'status-4xx' : status.startsWith('5') ? 'status-5xx' : ''; %>
1352
+ <div class="response-item <%= statusClass %>">
1353
+ <div class="response-header">
1354
+ <span class="status-code"><%= status %></span>
1355
+ <span class="response-description"><%= response.description || 'Response' %></span>
889
1356
  </div>
1357
+
890
1358
  <% if (response.content?.['application/json']) { %>
891
- <div class="code-block">
892
- <div class="code-header">
893
- <span class="code-language">JSON</span>
894
- <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1359
+ <div class="response-content">
1360
+ <div class="tabs">
1361
+ <button class="tab active" onclick="switchTab(this, 'response-example-<%= pathKey %>-<%= verb %>-<%= status %>')">
1362
+ Example
1363
+ </button>
1364
+ <button class="tab" onclick="switchTab(this, 'response-schema-<%= pathKey %>-<%= verb %>-<%= status %>')">
1365
+ Schema
1366
+ </button>
1367
+ </div>
1368
+
1369
+ <div id="response-example-<%= pathKey %>-<%= verb %>-<%= status %>" class="tab-content active">
1370
+ <div class="code-block">
1371
+ <div class="code-header">
1372
+ <span class="code-language">JSON</span>
1373
+ <div class="code-actions">
1374
+ <button class="copy-btn" onclick="copyCode(this)">
1375
+ <span>📋</span>
1376
+ <span>Copy</span>
1377
+ </button>
1378
+ </div>
1379
+ </div>
1380
+ <pre><%= JSON.stringify(response.content['application/json'].example || {}, null, 2) %></pre>
1381
+ </div>
1382
+ </div>
1383
+
1384
+ <div id="response-schema-<%= pathKey %>-<%= verb %>-<%= status %>" class="tab-content">
1385
+ <div class="code-block">
1386
+ <div class="code-header">
1387
+ <span class="code-language">Schema</span>
1388
+ <div class="code-actions">
1389
+ <button class="copy-btn" onclick="copyCode(this)">
1390
+ <span>📋</span>
1391
+ <span>Copy</span>
1392
+ </button>
1393
+ </div>
1394
+ </div>
1395
+ <pre><%= JSON.stringify(response.content['application/json'].schema || {}, null, 2) %></pre>
1396
+ </div>
895
1397
  </div>
896
- <pre><%= syntaxHighlight(JSON.stringify(response.content['application/json'].example || response.content['application/json'].schema || {}, null, 2)) %></pre>
897
1398
  </div>
898
1399
  <% } %>
899
- <% }) %>
900
- </div>
901
-
902
- <div class="try-it">
903
- <div class="try-it-title">Try it out</div>
904
- <input type="text" class="try-it-input" placeholder="Base URL (e.g., http://localhost:3000)" value="http://localhost:3000">
905
- <button class="try-it-btn" onclick="tryEndpoint(this, '<%= verb %>', '<%= pathKey %>')">Send Request</button>
906
- </div>
1400
+ </div>
1401
+ <% }) %>
907
1402
  </div>
908
1403
  </div>
909
- </div>
1404
+ </article>
910
1405
  <% }) %>
911
1406
  <% }) %>
912
- </div>
913
-
1407
+ </section>
1408
+
1409
+ <!-- Schemas Section -->
914
1410
  <% if (spec.components?.schemas && Object.keys(spec.components.schemas).length > 0) { %>
915
- <div class="schemas-section">
916
- <h2 class="section-title">Schemas</h2>
917
- <% Object.entries(spec.components.schemas).forEach(([schemaName, schema]) => { %>
918
- <div class="schema-card">
919
- <div class="schema-title"><%= schemaName %></div>
920
- <div class="code-block">
921
- <div class="code-header">
922
- <span class="code-language">Schema</span>
923
- <button class="copy-btn" onclick="copyCode(this)">Copy</button>
924
- </div>
925
- <pre><%= syntaxHighlight(JSON.stringify(schema, null, 2)) %></pre>
1411
+ <section class="section" id="schemas-section">
1412
+ <h2 class="section-title">
1413
+ <span>📦</span>
1414
+ <span>Data Schemas</span>
1415
+ </h2>
1416
+
1417
+ <div class="schema-section">
1418
+ <% Object.entries(spec.components.schemas).forEach(([schemaName, schema]) => { %>
1419
+ <div class="schema-item">
1420
+ <h3 class="schema-name"><%= schemaName %></h3>
1421
+
1422
+ <% if (schema.properties) { %>
1423
+ <table class="property-table">
1424
+ <thead>
1425
+ <tr>
1426
+ <th>Property</th>
1427
+ <th>Type</th>
1428
+ <th>Description</th>
1429
+ </tr>
1430
+ </thead>
1431
+ <tbody>
1432
+ <% Object.entries(schema.properties).forEach(([propName, propSchema]) => { %>
1433
+ <tr>
1434
+ <td>
1435
+ <span class="property-name"><%= propName %></span>
1436
+ <% if (schema.required && schema.required.includes(propName)) { %>
1437
+ <span class="required-badge">Required</span>
1438
+ <% } %>
1439
+ </td>
1440
+ <td>
1441
+ <span class="property-type"><%= propSchema.type || 'object' %></span>
1442
+ </td>
1443
+ <td class="description-text">
1444
+ <%= propSchema.description || '—' %>
1445
+ </td>
1446
+ </tr>
1447
+ <% }) %>
1448
+ </tbody>
1449
+ </table>
1450
+ <% } else { %>
1451
+ <div class="code-block">
1452
+ <div class="code-header">
1453
+ <span class="code-language">Schema</span>
1454
+ <div class="code-actions">
1455
+ <button class="copy-btn" onclick="copyCode(this)">
1456
+ <span>📋</span>
1457
+ <span>Copy</span>
1458
+ </button>
1459
+ </div>
1460
+ </div>
1461
+ <pre><%= JSON.stringify(schema, null, 2) %></pre>
1462
+ </div>
1463
+ <% } %>
926
1464
  </div>
927
- </div>
928
- <% }) %>
929
- </div>
1465
+ <% }) %>
1466
+ </div>
1467
+ </section>
930
1468
  <% } %>
931
1469
  </main>
932
1470
  </div>
933
-
1471
+
934
1472
  <script>
935
- function syntaxHighlight(json) {
936
- return json
937
- .replace(/&/g, '&amp;')
938
- .replace(/</g, '&lt;')
939
- .replace(/>/g, '&gt;')
940
- .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
941
- .replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>')
942
- .replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
943
- .replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
944
- .replace(/: (null)/g, ': <span class="json-null">$1</span>');
945
- }
946
-
1473
+ // Toggle endpoint expansion
1474
+ function toggleEndpoint(header) {
1475
+ const endpoint = header.closest('.endpoint');
1476
+ endpoint.classList.toggle('expanded');
1477
+ }
1478
+
1479
+ // Switch between tabs
1480
+ function switchTab(button, targetId) {
1481
+ const tabGroup = button.closest('.tabs');
1482
+ const parent = tabGroup.parentElement;
1483
+
1484
+ // Update tab buttons
1485
+ tabGroup.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
1486
+ button.classList.add('active');
1487
+
1488
+ // Update tab content
1489
+ parent.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1490
+ document.getElementById(targetId).classList.add('active');
1491
+ }
1492
+
1493
+ // Copy code to clipboard
947
1494
  function copyCode(button) {
948
- const codeBlock = button.closest('.code-block').querySelector('pre');
949
- const text = codeBlock.textContent;
1495
+ const codeBlock = button.closest('.code-block');
1496
+ const pre = codeBlock.querySelector('pre');
1497
+ const text = pre.textContent;
1498
+
950
1499
  navigator.clipboard.writeText(text).then(() => {
951
- button.textContent = 'Copied!';
952
- setTimeout(() => button.textContent = 'Copy', 2000);
1500
+ const textSpan = button.querySelector('span:last-child');
1501
+ const originalText = textSpan.textContent;
1502
+
1503
+ button.classList.add('copied');
1504
+ textSpan.textContent = 'Copied!';
1505
+
1506
+ setTimeout(() => {
1507
+ button.classList.remove('copied');
1508
+ textSpan.textContent = originalText;
1509
+ }, 2000);
953
1510
  });
954
1511
  }
955
-
956
- async function tryEndpoint(button, method, path) {
957
- const input = button.previousElementSibling;
958
- const baseUrl = input.value.trim();
959
-
960
- if (!baseUrl) {
961
- alert('Please enter a base URL');
962
- return;
1512
+
1513
+ // Scroll to endpoint
1514
+ function scrollToEndpoint(id) {
1515
+ const element = document.getElementById(id);
1516
+ if (element) {
1517
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' });
1518
+
1519
+ // Highlight the element
1520
+ element.style.animation = 'none';
1521
+ setTimeout(() => {
1522
+ element.style.animation = 'fadeIn 0.5s ease-out';
1523
+ }, 10);
1524
+
1525
+ if (!element.classList.contains('expanded')) {
1526
+ setTimeout(() => {
1527
+ element.querySelector('.endpoint-header').click();
1528
+ }, 300);
1529
+ }
963
1530
  }
964
-
965
- button.textContent = 'Sending...';
966
- button.disabled = true;
967
-
968
- try {
969
- const response = await fetch(baseUrl + path, {
970
- method: method.toUpperCase(),
971
- headers: {
972
- 'Content-Type': 'application/json'
973
- }
974
- });
975
-
976
- const data = await response.json();
977
- alert('Response (' + response.status + '):\\n' + JSON.stringify(data, null, 2));
978
- } catch (error) {
979
- alert('Error: ' + error.message);
980
- } finally {
981
- button.textContent = 'Send Request';
982
- button.disabled = false;
1531
+ }
1532
+
1533
+ // Scroll to schemas section
1534
+ function scrollToSchemas() {
1535
+ const element = document.getElementById('schemas-section');
1536
+ if (element) {
1537
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' });
983
1538
  }
984
1539
  }
985
-
986
- // Smooth scroll to anchor
987
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
988
- anchor.addEventListener('click', function (e) {
989
- e.preventDefault();
990
- const target = document.querySelector(this.getAttribute('href'));
991
- if (target) {
992
- target.scrollIntoView({ behavior: 'smooth', block: 'start' });
993
- target.classList.add('expanded');
1540
+
1541
+ // Filter endpoints by search
1542
+ function filterEndpoints(query) {
1543
+ const searchTerm = query.toLowerCase().trim();
1544
+ const navItems = document.querySelectorAll('.endpoint-nav-item');
1545
+ const endpoints = document.querySelectorAll('.endpoint');
1546
+
1547
+ navItems.forEach((item, index) => {
1548
+ const path = item.dataset.path.toLowerCase();
1549
+ const method = item.dataset.method.toLowerCase();
1550
+ const match = path.includes(searchTerm) || method.includes(searchTerm);
1551
+
1552
+ item.style.display = match ? 'flex' : 'none';
1553
+ if (endpoints[index]) {
1554
+ endpoints[index].style.display = match ? 'block' : 'none';
994
1555
  }
995
1556
  });
1557
+ }
1558
+
1559
+ // Theme toggle (placeholder)
1560
+ function toggleTheme() {
1561
+ alert('Theme toggle functionality can be implemented here');
1562
+ }
1563
+
1564
+ // Auto-expand first endpoint on load
1565
+ document.addEventListener('DOMContentLoaded', () => {
1566
+ const firstEndpoint = document.querySelector('.endpoint');
1567
+ if (firstEndpoint) {
1568
+ firstEndpoint.classList.add('expanded');
1569
+ }
1570
+ });
1571
+
1572
+ // Keyboard shortcuts
1573
+ document.addEventListener('keydown', (e) => {
1574
+ // Ctrl/Cmd + K to focus search
1575
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
1576
+ e.preventDefault();
1577
+ document.getElementById('searchInput').focus();
1578
+ }
1579
+
1580
+ // Escape to clear search
1581
+ if (e.key === 'Escape') {
1582
+ const searchInput = document.getElementById('searchInput');
1583
+ if (searchInput === document.activeElement) {
1584
+ searchInput.value = '';
1585
+ filterEndpoints('');
1586
+ searchInput.blur();
1587
+ }
1588
+ }
996
1589
  });
997
1590
  </script>
998
-
999
- <%
1000
- function syntaxHighlight(json) {
1001
- return json
1002
- .replace(/&/g, '&amp;')
1003
- .replace(/</g, '&lt;')
1004
- .replace(/>/g, '&gt;')
1005
- .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
1006
- .replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>')
1007
- .replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
1008
- .replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
1009
- .replace(/: (null)/g, ': <span class="json-null">$1</span>');
1010
- }
1011
- %>
1012
1591
  </body>
1013
1592
  </html>`;
1014
1593
  //# sourceMappingURL=doc-generator.js.map