@terrymooreii/sia 2.1.0 → 2.1.2

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.
package/_config.yml CHANGED
@@ -32,3 +32,4 @@ pagination:
32
32
 
33
33
  server:
34
34
  port: 3000
35
+ showDrafts: false # Set to true to show draft posts in dev server
@@ -3,7 +3,10 @@
3
3
  {% block content %}
4
4
  <article class="post">
5
5
  <header class="post-header">
6
- <h1 class="post-title">{{ page.title }}</h1>
6
+ <h1 class="post-title">
7
+ {{ page.title }}
8
+ {% if page.draft %}<span class="draft-badge">Draft</span>{% endif %}
9
+ </h1>
7
10
 
8
11
  <div class="post-meta">
9
12
  <time datetime="{{ page.date | date('iso') }}">{{ page.date | date('long') }}</time>
@@ -11,6 +11,7 @@
11
11
  <article class="post-card">
12
12
  <h2 class="post-card-title">
13
13
  <a href="{{ post.url }}">{{ post.title }}</a>
14
+ {% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
14
15
  </h2>
15
16
  <div class="post-card-meta">
16
17
  <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
@@ -17,6 +17,7 @@
17
17
  <article class="post-card">
18
18
  <h3 class="post-card-title">
19
19
  <a href="{{ post.url }}">{{ post.title }}</a>
20
+ {% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
20
21
  </h3>
21
22
  <div class="post-card-meta">
22
23
  <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('short') }}</time>
@@ -11,6 +11,7 @@
11
11
  <article class="post-card">
12
12
  <h2 class="post-card-title">
13
13
  <a href="{{ post.url }}">{{ post.title or post.excerpt }}</a>
14
+ {% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
14
15
  </h2>
15
16
  <div class="post-card-meta">
16
17
  <time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
@@ -341,6 +341,10 @@ img {
341
341
  font-weight: 600;
342
342
  margin: 0 0 var(--spacing-sm);
343
343
  line-height: 1.3;
344
+ display: flex;
345
+ align-items: center;
346
+ flex-wrap: wrap;
347
+ gap: var(--spacing-sm);
344
348
  }
345
349
 
346
350
  .post-card-title a {
@@ -396,6 +400,10 @@ img {
396
400
  letter-spacing: -0.02em;
397
401
  margin: 0 0 var(--spacing-md);
398
402
  line-height: 1.2;
403
+ display: flex;
404
+ align-items: center;
405
+ flex-wrap: wrap;
406
+ gap: var(--spacing-sm);
399
407
  }
400
408
 
401
409
  .post-meta {
@@ -566,12 +574,29 @@ img {
566
574
  font-size: 0.9375rem;
567
575
  }
568
576
 
569
- .tags-page {
570
- /* Tags page layout */
577
+ /* ===== Draft Badge ===== */
578
+ .draft-badge {
579
+ display: inline-flex;
580
+ align-items: center;
581
+ padding: 0.125em 0.4em;
582
+ background: #f59e0b;
583
+ color: white;
584
+ font-size: 0.65rem;
585
+ font-weight: 600;
586
+ text-transform: uppercase;
587
+ letter-spacing: 0.05em;
588
+ border-radius: var(--radius-sm);
589
+ line-height: 1;
590
+ flex-shrink: 0;
591
+ }
592
+
593
+ [data-theme="dark"] .draft-badge {
594
+ background: #fbbf24;
595
+ color: #1a202c;
571
596
  }
572
597
 
573
- .tags-detail {
574
- /* Tag detail sections */
598
+ .prose .draft-badge {
599
+ font-size: 0.6rem;
575
600
  }
576
601
 
577
602
  .tag-section {
@@ -793,6 +818,46 @@ img {
793
818
  box-shadow: var(--shadow-md);
794
819
  }
795
820
 
821
+ /* YouTube Embed Styles */
822
+ .prose .youtube-embed {
823
+ position: relative;
824
+ padding-bottom: 56.25%; /* 16:9 aspect ratio */
825
+ height: 0;
826
+ overflow: hidden;
827
+ margin: 1.5em 0;
828
+ border-radius: var(--radius-md);
829
+ box-shadow: var(--shadow-md);
830
+ }
831
+
832
+ .prose .youtube-embed iframe {
833
+ position: absolute;
834
+ top: 0;
835
+ left: 0;
836
+ width: 100%;
837
+ height: 100%;
838
+ border: 0;
839
+ }
840
+
841
+ /* Giphy Embed Styles */
842
+ .prose .giphy-embed {
843
+ position: relative;
844
+ padding-bottom: 100%; /* Giphy embeds are typically square */
845
+ height: 0;
846
+ overflow: hidden;
847
+ margin: 1.5em 0;
848
+ border-radius: var(--radius-md);
849
+ box-shadow: var(--shadow-md);
850
+ }
851
+
852
+ .prose .giphy-embed iframe {
853
+ position: absolute;
854
+ top: 0;
855
+ left: 0;
856
+ width: 100%;
857
+ height: 100%;
858
+ border: 0;
859
+ }
860
+
796
861
  .prose a {
797
862
  text-decoration: underline;
798
863
  text-decoration-color: var(--color-border);
@@ -828,6 +893,240 @@ img {
828
893
  font-weight: 600;
829
894
  }
830
895
 
896
+ /* ===== Alert Boxes ===== */
897
+ .prose .markdown-alert {
898
+ margin: 1.5em 0;
899
+ padding: var(--spacing-md) var(--spacing-lg);
900
+ border-left: 4px solid;
901
+ border-radius: var(--radius-md);
902
+ background: var(--color-bg-alt);
903
+ }
904
+
905
+ .prose .markdown-alert-title {
906
+ display: flex;
907
+ align-items: center;
908
+ gap: var(--spacing-sm);
909
+ margin: 0 0 var(--spacing-sm) 0;
910
+ font-weight: 600;
911
+ font-style: normal;
912
+ }
913
+
914
+ .prose .markdown-alert-title svg {
915
+ flex-shrink: 0;
916
+ width: 16px;
917
+ height: 16px;
918
+ fill: currentColor;
919
+ }
920
+
921
+ /* Make SVG icons lighter in dark mode */
922
+ [data-theme="dark"] .prose .markdown-alert-title svg {
923
+ fill: currentColor;
924
+ }
925
+
926
+ .prose .markdown-alert p:last-child {
927
+ margin-bottom: 0;
928
+ }
929
+
930
+ /* Note Alert */
931
+ .prose .markdown-alert-note {
932
+ border-left-color: #0969da;
933
+ background: rgba(9, 105, 218, 0.1);
934
+ }
935
+
936
+ .prose .markdown-alert-note .markdown-alert-title {
937
+ color: #0969da;
938
+ }
939
+
940
+ [data-theme="dark"] .prose .markdown-alert-note {
941
+ background: rgba(9, 105, 218, 0.15);
942
+ border-left-color: #58a6ff;
943
+ }
944
+
945
+ [data-theme="dark"] .prose .markdown-alert-note .markdown-alert-title {
946
+ color: #58a6ff;
947
+ }
948
+
949
+ [data-theme="dark"] .prose .markdown-alert-note .markdown-alert-title svg {
950
+ fill: #58a6ff;
951
+ }
952
+
953
+ /* Tip Alert */
954
+ .prose .markdown-alert-tip {
955
+ border-left-color: #1a7f37;
956
+ background: rgba(26, 127, 55, 0.1);
957
+ }
958
+
959
+ .prose .markdown-alert-tip .markdown-alert-title {
960
+ color: #1a7f37;
961
+ }
962
+
963
+ [data-theme="dark"] .prose .markdown-alert-tip {
964
+ background: rgba(26, 127, 55, 0.15);
965
+ border-left-color: #3fb950;
966
+ }
967
+
968
+ [data-theme="dark"] .prose .markdown-alert-tip .markdown-alert-title {
969
+ color: #3fb950;
970
+ }
971
+
972
+ [data-theme="dark"] .prose .markdown-alert-tip .markdown-alert-title svg {
973
+ fill: #3fb950;
974
+ }
975
+
976
+ /* Important Alert */
977
+ .prose .markdown-alert-important {
978
+ border-left-color: #8250df;
979
+ background: rgba(130, 80, 223, 0.1);
980
+ }
981
+
982
+ .prose .markdown-alert-important .markdown-alert-title {
983
+ color: #8250df;
984
+ }
985
+
986
+ [data-theme="dark"] .prose .markdown-alert-important {
987
+ background: rgba(130, 80, 223, 0.15);
988
+ border-left-color: #a371f7;
989
+ }
990
+
991
+ [data-theme="dark"] .prose .markdown-alert-important .markdown-alert-title {
992
+ color: #a371f7;
993
+ }
994
+
995
+ [data-theme="dark"] .prose .markdown-alert-important .markdown-alert-title svg {
996
+ fill: #a371f7;
997
+ }
998
+
999
+ /* Warning Alert */
1000
+ .prose .markdown-alert-warning {
1001
+ border-left-color: #9a6700;
1002
+ background: rgba(154, 103, 0, 0.1);
1003
+ }
1004
+
1005
+ .prose .markdown-alert-warning .markdown-alert-title {
1006
+ color: #9a6700;
1007
+ }
1008
+
1009
+ [data-theme="dark"] .prose .markdown-alert-warning {
1010
+ background: rgba(154, 103, 0, 0.15);
1011
+ border-left-color: #d29922;
1012
+ }
1013
+
1014
+ [data-theme="dark"] .prose .markdown-alert-warning .markdown-alert-title {
1015
+ color: #d29922;
1016
+ }
1017
+
1018
+ [data-theme="dark"] .prose .markdown-alert-warning .markdown-alert-title svg {
1019
+ fill: #d29922;
1020
+ }
1021
+
1022
+ /* Caution Alert */
1023
+ .prose .markdown-alert-caution {
1024
+ border-left-color: #cf222e;
1025
+ background: rgba(207, 34, 46, 0.1);
1026
+ }
1027
+
1028
+ .prose .markdown-alert-caution .markdown-alert-title {
1029
+ color: #cf222e;
1030
+ }
1031
+
1032
+ [data-theme="dark"] .prose .markdown-alert-caution {
1033
+ background: rgba(207, 34, 46, 0.15);
1034
+ border-left-color: #f85149;
1035
+ }
1036
+
1037
+ [data-theme="dark"] .prose .markdown-alert-caution .markdown-alert-title {
1038
+ color: #f85149;
1039
+ }
1040
+
1041
+ [data-theme="dark"] .prose .markdown-alert-caution .markdown-alert-title svg {
1042
+ fill: #f85149;
1043
+ }
1044
+
1045
+ /* ===== Task Lists (Checkboxes) ===== */
1046
+ .prose ul.contains-task-list,
1047
+ .prose ol.contains-task-list {
1048
+ list-style: none;
1049
+ padding-left: 0;
1050
+ }
1051
+
1052
+ .prose .task-list-item {
1053
+ display: flex;
1054
+ align-items: flex-start;
1055
+ gap: var(--spacing-sm);
1056
+ margin-bottom: 0.5em;
1057
+ }
1058
+
1059
+ .prose .task-list-item input[type="checkbox"] {
1060
+ margin-top: 0.25em;
1061
+ cursor: pointer;
1062
+ width: 1em;
1063
+ height: 1em;
1064
+ flex-shrink: 0;
1065
+ accent-color: var(--color-primary);
1066
+ }
1067
+
1068
+ .prose .task-list-item input[type="checkbox"]:checked {
1069
+ accent-color: var(--color-primary);
1070
+ }
1071
+
1072
+ /* ===== Strikethrough ===== */
1073
+ .prose del,
1074
+ .prose s {
1075
+ text-decoration: line-through;
1076
+ text-decoration-color: var(--color-text-muted);
1077
+ opacity: 0.7;
1078
+ }
1079
+
1080
+ /* ===== Footnotes ===== */
1081
+ .prose section[data-footnotes],
1082
+ .prose .footnotes {
1083
+ margin-top: 2em;
1084
+ padding-top: var(--spacing-lg);
1085
+ border-top: 1px solid var(--color-border);
1086
+ }
1087
+
1088
+ .prose section[data-footnotes] h2,
1089
+ .prose .footnotes h2 {
1090
+ font-size: 1rem;
1091
+ font-weight: 600;
1092
+ margin-bottom: var(--spacing-md);
1093
+ color: var(--color-text-muted);
1094
+ }
1095
+
1096
+ .prose section[data-footnotes] ol,
1097
+ .prose .footnotes ol {
1098
+ padding-left: 1.5em;
1099
+ font-size: 0.9375rem;
1100
+ color: var(--color-text-muted);
1101
+ }
1102
+
1103
+ .prose section[data-footnotes] li,
1104
+ .prose .footnotes li {
1105
+ margin-bottom: var(--spacing-sm);
1106
+ }
1107
+
1108
+ .prose .footnote-ref {
1109
+ font-size: 0.875em;
1110
+ vertical-align: super;
1111
+ text-decoration: none;
1112
+ color: var(--color-primary);
1113
+ margin-left: 0.2em;
1114
+ }
1115
+
1116
+ .prose .footnote-ref:hover {
1117
+ text-decoration: underline;
1118
+ }
1119
+
1120
+ .prose .footnote-backref {
1121
+ text-decoration: none;
1122
+ color: var(--color-primary);
1123
+ margin-left: var(--spacing-xs);
1124
+ }
1125
+
1126
+ .prose .footnote-backref:hover {
1127
+ text-decoration: underline;
1128
+ }
1129
+
831
1130
  /* ===== Empty State ===== */
832
1131
  .empty-message {
833
1132
  text-align: center;
@@ -325,6 +325,10 @@ img {
325
325
  font-weight: 600;
326
326
  margin: 0 0 var(--spacing-xs);
327
327
  line-height: 1.4;
328
+ display: flex;
329
+ align-items: center;
330
+ flex-wrap: wrap;
331
+ gap: var(--spacing-sm);
328
332
  }
329
333
 
330
334
  .post-card-title a {
@@ -378,6 +382,10 @@ img {
378
382
  letter-spacing: -0.02em;
379
383
  margin: 0 0 var(--spacing-md);
380
384
  line-height: 1.3;
385
+ display: flex;
386
+ align-items: center;
387
+ flex-wrap: wrap;
388
+ gap: var(--spacing-sm);
381
389
  }
382
390
 
383
391
  .post-meta {
@@ -547,12 +555,29 @@ img {
547
555
  font-size: 0.875rem;
548
556
  }
549
557
 
550
- .tags-page {
551
- /* Tags page layout */
558
+ /* ===== Draft Badge ===== */
559
+ .draft-badge {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ padding: 0.125em 0.4em;
563
+ background: #f59e0b;
564
+ color: white;
565
+ font-size: 0.65rem;
566
+ font-weight: 600;
567
+ text-transform: uppercase;
568
+ letter-spacing: 0.05em;
569
+ border-radius: var(--radius-sm);
570
+ line-height: 1;
571
+ flex-shrink: 0;
552
572
  }
553
573
 
554
- .tags-detail {
555
- /* Tag detail sections */
574
+ [data-theme="dark"] .draft-badge {
575
+ background: #fbbf24;
576
+ color: #0a0a0a;
577
+ }
578
+
579
+ .prose .draft-badge {
580
+ font-size: 0.6rem;
556
581
  }
557
582
 
558
583
  .tag-section {
@@ -771,6 +796,44 @@ img {
771
796
  border-radius: var(--radius-md);
772
797
  }
773
798
 
799
+ /* YouTube Embed Styles */
800
+ .prose .youtube-embed {
801
+ position: relative;
802
+ padding-bottom: 56.25%; /* 16:9 aspect ratio */
803
+ height: 0;
804
+ overflow: hidden;
805
+ margin: 1.25em 0;
806
+ border-radius: var(--radius-md);
807
+ }
808
+
809
+ .prose .youtube-embed iframe {
810
+ position: absolute;
811
+ top: 0;
812
+ left: 0;
813
+ width: 100%;
814
+ height: 100%;
815
+ border: 0;
816
+ }
817
+
818
+ /* Giphy Embed Styles */
819
+ .prose .giphy-embed {
820
+ position: relative;
821
+ padding-bottom: 100%; /* Giphy embeds are typically square */
822
+ height: 0;
823
+ overflow: hidden;
824
+ margin: 1.25em 0;
825
+ border-radius: var(--radius-md);
826
+ }
827
+
828
+ .prose .giphy-embed iframe {
829
+ position: absolute;
830
+ top: 0;
831
+ left: 0;
832
+ width: 100%;
833
+ height: 100%;
834
+ border: 0;
835
+ }
836
+
774
837
  .prose a {
775
838
  text-decoration: underline;
776
839
  text-decoration-color: var(--color-border);
@@ -806,6 +869,240 @@ img {
806
869
  font-weight: 600;
807
870
  }
808
871
 
872
+ /* ===== Alert Boxes ===== */
873
+ .prose .markdown-alert {
874
+ margin: 1.25em 0;
875
+ padding: var(--spacing-md) var(--spacing-lg);
876
+ border-left: 3px solid;
877
+ border-radius: var(--radius-md);
878
+ background: var(--color-bg-alt);
879
+ }
880
+
881
+ .prose .markdown-alert-title {
882
+ display: flex;
883
+ align-items: center;
884
+ gap: var(--spacing-sm);
885
+ margin: 0 0 var(--spacing-sm) 0;
886
+ font-weight: 600;
887
+ font-style: normal;
888
+ }
889
+
890
+ .prose .markdown-alert-title svg {
891
+ flex-shrink: 0;
892
+ width: 16px;
893
+ height: 16px;
894
+ fill: currentColor;
895
+ }
896
+
897
+ /* Make SVG icons lighter in dark mode */
898
+ [data-theme="dark"] .prose .markdown-alert-title svg {
899
+ fill: currentColor;
900
+ }
901
+
902
+ .prose .markdown-alert p:last-child {
903
+ margin-bottom: 0;
904
+ }
905
+
906
+ /* Note Alert */
907
+ .prose .markdown-alert-note {
908
+ border-left-color: #0969da;
909
+ background: rgba(9, 105, 218, 0.08);
910
+ }
911
+
912
+ .prose .markdown-alert-note .markdown-alert-title {
913
+ color: #0969da;
914
+ }
915
+
916
+ [data-theme="dark"] .prose .markdown-alert-note {
917
+ background: rgba(9, 105, 218, 0.12);
918
+ border-left-color: #58a6ff;
919
+ }
920
+
921
+ [data-theme="dark"] .prose .markdown-alert-note .markdown-alert-title {
922
+ color: #58a6ff;
923
+ }
924
+
925
+ [data-theme="dark"] .prose .markdown-alert-note .markdown-alert-title svg {
926
+ fill: #58a6ff;
927
+ }
928
+
929
+ /* Tip Alert */
930
+ .prose .markdown-alert-tip {
931
+ border-left-color: #1a7f37;
932
+ background: rgba(26, 127, 55, 0.08);
933
+ }
934
+
935
+ .prose .markdown-alert-tip .markdown-alert-title {
936
+ color: #1a7f37;
937
+ }
938
+
939
+ [data-theme="dark"] .prose .markdown-alert-tip {
940
+ background: rgba(26, 127, 55, 0.12);
941
+ border-left-color: #3fb950;
942
+ }
943
+
944
+ [data-theme="dark"] .prose .markdown-alert-tip .markdown-alert-title {
945
+ color: #3fb950;
946
+ }
947
+
948
+ [data-theme="dark"] .prose .markdown-alert-tip .markdown-alert-title svg {
949
+ fill: #3fb950;
950
+ }
951
+
952
+ /* Important Alert */
953
+ .prose .markdown-alert-important {
954
+ border-left-color: #8250df;
955
+ background: rgba(130, 80, 223, 0.08);
956
+ }
957
+
958
+ .prose .markdown-alert-important .markdown-alert-title {
959
+ color: #8250df;
960
+ }
961
+
962
+ [data-theme="dark"] .prose .markdown-alert-important {
963
+ background: rgba(130, 80, 223, 0.12);
964
+ border-left-color: #a371f7;
965
+ }
966
+
967
+ [data-theme="dark"] .prose .markdown-alert-important .markdown-alert-title {
968
+ color: #a371f7;
969
+ }
970
+
971
+ [data-theme="dark"] .prose .markdown-alert-important .markdown-alert-title svg {
972
+ fill: #a371f7;
973
+ }
974
+
975
+ /* Warning Alert */
976
+ .prose .markdown-alert-warning {
977
+ border-left-color: #9a6700;
978
+ background: rgba(154, 103, 0, 0.08);
979
+ }
980
+
981
+ .prose .markdown-alert-warning .markdown-alert-title {
982
+ color: #9a6700;
983
+ }
984
+
985
+ [data-theme="dark"] .prose .markdown-alert-warning {
986
+ background: rgba(154, 103, 0, 0.12);
987
+ border-left-color: #d29922;
988
+ }
989
+
990
+ [data-theme="dark"] .prose .markdown-alert-warning .markdown-alert-title {
991
+ color: #d29922;
992
+ }
993
+
994
+ [data-theme="dark"] .prose .markdown-alert-warning .markdown-alert-title svg {
995
+ fill: #d29922;
996
+ }
997
+
998
+ /* Caution Alert */
999
+ .prose .markdown-alert-caution {
1000
+ border-left-color: #cf222e;
1001
+ background: rgba(207, 34, 46, 0.08);
1002
+ }
1003
+
1004
+ .prose .markdown-alert-caution .markdown-alert-title {
1005
+ color: #cf222e;
1006
+ }
1007
+
1008
+ [data-theme="dark"] .prose .markdown-alert-caution {
1009
+ background: rgba(207, 34, 46, 0.12);
1010
+ border-left-color: #f85149;
1011
+ }
1012
+
1013
+ [data-theme="dark"] .prose .markdown-alert-caution .markdown-alert-title {
1014
+ color: #f85149;
1015
+ }
1016
+
1017
+ [data-theme="dark"] .prose .markdown-alert-caution .markdown-alert-title svg {
1018
+ fill: #f85149;
1019
+ }
1020
+
1021
+ /* ===== Task Lists (Checkboxes) ===== */
1022
+ .prose ul.contains-task-list,
1023
+ .prose ol.contains-task-list {
1024
+ list-style: none;
1025
+ padding-left: 0;
1026
+ }
1027
+
1028
+ .prose .task-list-item {
1029
+ display: flex;
1030
+ align-items: flex-start;
1031
+ gap: var(--spacing-sm);
1032
+ margin-bottom: 0.35em;
1033
+ }
1034
+
1035
+ .prose .task-list-item input[type="checkbox"] {
1036
+ margin-top: 0.2em;
1037
+ cursor: pointer;
1038
+ width: 1em;
1039
+ height: 1em;
1040
+ flex-shrink: 0;
1041
+ accent-color: var(--color-primary);
1042
+ }
1043
+
1044
+ .prose .task-list-item input[type="checkbox"]:checked {
1045
+ accent-color: var(--color-primary);
1046
+ }
1047
+
1048
+ /* ===== Strikethrough ===== */
1049
+ .prose del,
1050
+ .prose s {
1051
+ text-decoration: line-through;
1052
+ text-decoration-color: var(--color-text-muted);
1053
+ opacity: 0.7;
1054
+ }
1055
+
1056
+ /* ===== Footnotes ===== */
1057
+ .prose section[data-footnotes],
1058
+ .prose .footnotes {
1059
+ margin-top: 2em;
1060
+ padding-top: var(--spacing-lg);
1061
+ border-top: 1px solid var(--color-border);
1062
+ }
1063
+
1064
+ .prose section[data-footnotes] h2,
1065
+ .prose .footnotes h2 {
1066
+ font-size: 0.95rem;
1067
+ font-weight: 600;
1068
+ margin-bottom: var(--spacing-md);
1069
+ color: var(--color-text-muted);
1070
+ }
1071
+
1072
+ .prose section[data-footnotes] ol,
1073
+ .prose .footnotes ol {
1074
+ padding-left: 1.5em;
1075
+ font-size: 0.9rem;
1076
+ color: var(--color-text-muted);
1077
+ }
1078
+
1079
+ .prose section[data-footnotes] li,
1080
+ .prose .footnotes li {
1081
+ margin-bottom: var(--spacing-sm);
1082
+ }
1083
+
1084
+ .prose .footnote-ref {
1085
+ font-size: 0.85em;
1086
+ vertical-align: super;
1087
+ text-decoration: none;
1088
+ color: var(--color-primary);
1089
+ margin-left: 0.2em;
1090
+ }
1091
+
1092
+ .prose .footnote-ref:hover {
1093
+ text-decoration: underline;
1094
+ }
1095
+
1096
+ .prose .footnote-backref {
1097
+ text-decoration: none;
1098
+ color: var(--color-primary);
1099
+ margin-left: var(--spacing-xs);
1100
+ }
1101
+
1102
+ .prose .footnote-backref:hover {
1103
+ text-decoration: underline;
1104
+ }
1105
+
809
1106
  /* ===== Empty State ===== */
810
1107
  .empty-message {
811
1108
  text-align: center;
package/lib/build.js CHANGED
@@ -201,6 +201,15 @@ export async function build(options = {}) {
201
201
  // Load configuration
202
202
  const config = loadConfig(options.rootDir || process.cwd());
203
203
 
204
+ // Only show drafts in dev mode if explicitly enabled in config
205
+ // For production builds, always disable showDrafts
206
+ if (!options.devMode) {
207
+ if (config.server) {
208
+ config.server.showDrafts = false;
209
+ }
210
+ }
211
+ // If devMode is true, use the config value (which defaults to false)
212
+
204
213
  // Clean output directory if requested
205
214
  if (options.clean !== false) {
206
215
  cleanOutput(config);
package/lib/config.js CHANGED
@@ -40,7 +40,8 @@ const defaultConfig = {
40
40
  size: 10
41
41
  },
42
42
  server: {
43
- port: 3000
43
+ port: 3000,
44
+ showDrafts: false // Show draft posts when using dev server
44
45
  }
45
46
  };
46
47
 
package/lib/content.js CHANGED
@@ -120,6 +120,130 @@ marked.setOptions({
120
120
  breaks: false
121
121
  });
122
122
 
123
+ /**
124
+ * Extract YouTube video ID from various URL formats
125
+ * Supports:
126
+ * - https://www.youtube.com/watch?v=VIDEO_ID
127
+ * - https://youtu.be/VIDEO_ID
128
+ * - https://www.youtube.com/embed/VIDEO_ID
129
+ * - https://youtube.com/watch?v=VIDEO_ID
130
+ * - VIDEO_ID (if it's just an 11-character alphanumeric string)
131
+ */
132
+ function extractYouTubeId(url) {
133
+ if (!url) return null;
134
+
135
+ // If it's just a video ID (11 characters, alphanumeric)
136
+ if (/^[a-zA-Z0-9_-]{11}$/.test(url)) {
137
+ return url;
138
+ }
139
+
140
+ // Match various YouTube URL patterns
141
+ const patterns = [
142
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/,
143
+ /youtube\.com\/.*[?&]v=([a-zA-Z0-9_-]{11})/
144
+ ];
145
+
146
+ for (const pattern of patterns) {
147
+ const match = url.match(pattern);
148
+ if (match && match[1]) {
149
+ return match[1];
150
+ }
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ /**
157
+ * Convert YouTube URLs in HTML to responsive embeds
158
+ */
159
+ function embedYouTubeVideos(html) {
160
+ if (!html) return html;
161
+
162
+ // Pattern to match YouTube links in HTML
163
+ // Matches: <a href="...youtube...">...</a>
164
+ const linkPattern = /<a\s+[^>]*href=["']([^"']*youtube[^"']*)["'][^>]*>([^<]*)<\/a>/gi;
165
+
166
+ return html.replace(linkPattern, (match, url, linkText) => {
167
+ const videoId = extractYouTubeId(url);
168
+ if (videoId) {
169
+ // Return responsive YouTube embed
170
+ return `<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/${videoId}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
171
+ }
172
+ return match; // Return original if not a valid YouTube URL
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Extract Giphy GIF ID from various URL formats
178
+ * Supports:
179
+ * - https://giphy.com/gifs/ID
180
+ * - https://gph.is/g/ID
181
+ * - https://giphy.com/embed/ID
182
+ * - https://media.giphy.com/media/ID/giphy.gif (extracts ID)
183
+ */
184
+ function extractGiphyId(url) {
185
+ if (!url) return null;
186
+
187
+ // Match Giphy URL patterns
188
+ const patterns = [
189
+ /giphy\.com\/gifs\/([a-zA-Z0-9]+)/,
190
+ /giphy\.com\/embed\/([a-zA-Z0-9]+)/,
191
+ /gph\.is\/g\/([a-zA-Z0-9]+)/,
192
+ /media\.giphy\.com\/media\/([a-zA-Z0-9]+)\//
193
+ ];
194
+
195
+ for (const pattern of patterns) {
196
+ const match = url.match(pattern);
197
+ if (match && match[1]) {
198
+ return match[1];
199
+ }
200
+ }
201
+
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Convert Giphy URLs in HTML to responsive embeds
207
+ */
208
+ function embedGiphyGifs(html) {
209
+ if (!html) return html;
210
+
211
+ // Pattern to match Giphy links in HTML
212
+ const linkPattern = /<a\s+[^>]*href=["']([^"']*giphy[^"']*)["'][^>]*>([^<]*)<\/a>/gi;
213
+
214
+ return html.replace(linkPattern, (match, url, linkText) => {
215
+ const gifId = extractGiphyId(url);
216
+ if (gifId) {
217
+ // Return responsive Giphy embed
218
+ return `<div class="giphy-embed"><iframe src="https://giphy.com/embed/${gifId}" frameBorder="0" class="giphy-embed" allowFullScreen></iframe></div>`;
219
+ }
220
+ return match;
221
+ });
222
+ }
223
+
224
+ // Override the link renderer to handle YouTube URLs
225
+ const renderer = marked.getRenderer();
226
+ const originalLink = renderer.link.bind(renderer);
227
+
228
+ renderer.link = (token) => {
229
+ const videoId = extractYouTubeId(token.href);
230
+
231
+ if (videoId) {
232
+ // Return responsive YouTube embed instead of a link
233
+ return `<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/${videoId}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
234
+ }
235
+
236
+ const gifId = extractGiphyId(token.href);
237
+
238
+ if (gifId) {
239
+ // Return responsive Giphy embed instead of a link
240
+ return `<div class="giphy-embed"><iframe src="https://giphy.com/embed/${gifId}" frameBorder="0" class="giphy-embed" allowFullScreen></iframe></div>`;
241
+ }
242
+
243
+ // Use default link rendering for non-YouTube/Giphy links
244
+ return originalLink(token);
245
+ };
246
+
123
247
  /**
124
248
  * Generate a URL-friendly slug from a string
125
249
  */
@@ -172,7 +296,11 @@ export function parseContent(filePath) {
172
296
  const { data: frontMatter, content: markdown } = matter(content);
173
297
 
174
298
  // Parse markdown to HTML
175
- const html = marked.parse(markdown);
299
+ let html = marked.parse(markdown);
300
+
301
+ // Convert any remaining YouTube/Giphy links to embeds (handles autolinked URLs)
302
+ html = embedYouTubeVideos(html);
303
+ html = embedGiphyGifs(html);
176
304
 
177
305
  // Get slug from front matter or filename
178
306
  const slug = frontMatter.slug || getSlugFromFilename(filePath);
@@ -283,7 +411,15 @@ export function loadCollection(config, collectionName) {
283
411
  return null;
284
412
  }
285
413
  })
286
- .filter(item => item !== null && !item.draft);
414
+ .filter(item => {
415
+ if (item === null) return false;
416
+ // Include drafts if showDrafts is enabled in server config
417
+ if (item.draft && config.server?.showDrafts) {
418
+ return true;
419
+ }
420
+ // Otherwise, exclude drafts
421
+ return !item.draft;
422
+ });
287
423
 
288
424
  // Sort items
289
425
  const sortBy = collectionConfig.sortBy || 'date';
package/lib/server.js CHANGED
@@ -188,7 +188,7 @@ function setupWatcher(config, wss) {
188
188
  console.log('šŸ”„ Rebuilding...\n');
189
189
 
190
190
  try {
191
- await build({ clean: false, rootDir: config.rootDir });
191
+ await build({ clean: false, rootDir: config.rootDir, devMode: true });
192
192
  notifyReload(wss);
193
193
  } catch (err) {
194
194
  console.error('āŒ Rebuild failed:', err.message);
@@ -213,8 +213,8 @@ export async function startServer(options = {}) {
213
213
 
214
214
  console.log('\n⚔ Sia - Development Server\n');
215
215
 
216
- // Initial build
217
- await build({ clean: true, rootDir: config.rootDir });
216
+ // Initial build with devMode enabled to show drafts if configured
217
+ await build({ clean: true, rootDir: config.rootDir, devMode: true });
218
218
 
219
219
  // Create servers
220
220
  const httpServer = createHttpServer(config, wsPort);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrymooreii/sia",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "A simple, powerful static site generator with markdown, front matter, and Nunjucks templates",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
package/readme.md CHANGED
@@ -131,8 +131,18 @@ pagination:
131
131
 
132
132
  server:
133
133
  port: 3000
134
+ showDrafts: false # Set to true to show draft posts in dev server
134
135
  ```
135
136
 
137
+ ### Server Configuration
138
+
139
+ | Option | Description | Default |
140
+ |-------|-------------|---------|
141
+ | `port` | Port number for development server | `3000` |
142
+ | `showDrafts` | Show draft posts when using `sia dev` | `false` |
143
+
144
+ When `showDrafts` is set to `true`, draft posts (posts with `draft: true` in front matter) will be included in the development server build. This is useful for previewing draft content locally. Drafts are always excluded from production builds.
145
+
136
146
  ## Front Matter
137
147
 
138
148
  Each markdown file can have YAML front matter:
@@ -158,7 +168,7 @@ excerpt: "Custom excerpt text"
158
168
  | `tags` | Array of tags |
159
169
  | `layout` | Template to use |
160
170
  | `permalink` | Custom URL |
161
- | `draft` | If true, excluded from build |
171
+ | `draft` | If true, excluded from build (unless `server.showDrafts` is enabled) |
162
172
  | `excerpt` | Custom excerpt |
163
173
 
164
174
  ## Markdown Features
@@ -248,6 +258,55 @@ Plain URLs are automatically converted to clickable links:
248
258
  Visit https://example.com for more info.
249
259
  ```
250
260
 
261
+ ### Media Embeds
262
+
263
+ Sia automatically converts YouTube and Giphy URLs into responsive embeds.
264
+
265
+ #### YouTube Videos
266
+
267
+ YouTube videos can be embedded using any of these methods:
268
+
269
+ **As a markdown link:**
270
+ ```markdown
271
+ [Watch this video](https://www.youtube.com/watch?v=dQw4w9WgXcQ)
272
+ ```
273
+
274
+ **As a plain URL (auto-linked):**
275
+ ```markdown
276
+ https://www.youtube.com/watch?v=dQw4w9WgXcQ
277
+ ```
278
+
279
+ **Using short URL format:**
280
+ ```markdown
281
+ https://youtu.be/dQw4w9WgXcQ
282
+ ```
283
+
284
+ All of these formats are automatically converted to responsive YouTube embeds.
285
+
286
+ #### Giphy GIFs
287
+
288
+ Giphy GIFs can be embedded in two ways:
289
+
290
+ **As a direct image link (standard markdown):**
291
+ ```markdown
292
+ ![Alt text](https://media.giphy.com/media/ID/giphy.gif)
293
+ ```
294
+
295
+ **As a Giphy share URL (auto-embedded):**
296
+ ```markdown
297
+ [Check this out](https://giphy.com/gifs/ID)
298
+ ```
299
+
300
+ Or just paste the URL:
301
+ ```markdown
302
+ https://giphy.com/gifs/ID
303
+ ```
304
+
305
+ Giphy share URLs are automatically converted to responsive embeds. Supported formats include:
306
+ - `https://giphy.com/gifs/ID`
307
+ - `https://gph.is/g/ID`
308
+ - `https://giphy.com/embed/ID`
309
+
251
310
  ### GitHub Flavored Markdown
252
311
 
253
312
  Full GFM support including: