jscom_ice 1.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d22b0ec82c7313d155016bf445a4fc15caacdd3fa6a8dc6033756c7c8cfe606
4
- data.tar.gz: 33405791ae488fb9bf4e41d7c5a89327623d08125a505d2895501e6068842659
3
+ metadata.gz: 5757d0be4848c97f09910726431394c801472f925e16836152e2d1e12e3ce5ae
4
+ data.tar.gz: a4583d6a5656492ed1f628ad50537f9e6ae04126d5308df120f19e206272b417
5
5
  SHA512:
6
- metadata.gz: 1b04dec36f48c2f8c712da8457a234edb16de9cadfe01e2b5e8a8241d6e1c0fa299b458e94c6cfe2c70b1d26b1525c379bedc8b0407501b8d45a4ecec3ced122
7
- data.tar.gz: 5d614331b9894d2507acb0a3cadee7e8a11f26cacfb4376dbbe53d04de1733b53c8cfa3f84b02381ca2b882de3e793c5496aa4234f3786be1940c966ce802975
6
+ metadata.gz: 679e6d2c448bf013cdb31eb37912be8feadf02d43f435786031392de4f9a6366775ec53a9216b0923b9c40cf075bb7c0540c64072a0a899ced0be0c4bd38749a
7
+ data.tar.gz: 4e86fcc53e0c1638dd54c4a918fb0bcc123207eb2fa3ad7f56955a0200215113d3b2fb041ad0d6302f10bd51a387c8e4f1a53af9fbca68fb7e4dcedf874274d8
data/README.md CHANGED
@@ -2,23 +2,85 @@
2
2
 
3
3
  A Jekyll theme for (johnsosoka.com)[https://johnsosoka.com]
4
4
 
5
- **NOTE:** This repository and theme are open source, but not really meant for public consumption. If you would like to use this
5
+ **NOTE:** This repository and theme are open source, but not really meant for public consumption. If you would like to use this
6
6
  theme, please fork the repository and make any changes you would like.
7
7
 
8
8
  ## Usage
9
9
 
10
- 1. Add to gemfile:
10
+ 1. Add to gemfile:
11
11
  ```
12
12
  group :jekyll_plugins do
13
13
  # jscom theme
14
- gem "jscom_ice", '~> 0.0.3'
15
-
14
+ gem "jscom_ice", '~> 1.0.1'
15
+
16
16
  end
17
17
  ```
18
18
  2. Add to _config.yml:
19
19
  ```
20
20
  theme: jscom_ice
21
21
  ```
22
+
23
+ ## Features
24
+
25
+ ### Newsletter Signup
26
+
27
+ The theme includes newsletter signup functionality that integrates with an API endpoint.
28
+
29
+ #### Footer Newsletter Signup
30
+
31
+ Add newsletter signup to your site footer with a compact, horizontal form. Configure in `_config.yml`:
32
+
33
+ ```yaml
34
+ # Newsletter Settings
35
+ newsletter:
36
+ api:
37
+ url: https://api.johnsosoka.com/v1/newsletter
38
+ footer:
39
+ enabled: true
40
+ title: "Newsletter Signup"
41
+ description: "Get random updates"
42
+ ```
43
+
44
+ **Configuration Options:**
45
+ - `newsletter.api.url` - Base URL for the newsletter API (required)
46
+ - `newsletter.footer.enabled` - Toggle footer signup form (default: false)
47
+ - `newsletter.footer.title` - Heading text for the footer signup
48
+ - `newsletter.footer.description` - Descriptive text below the heading
49
+
50
+ The footer signup form will appear between footer links and copyright text when enabled.
51
+
52
+ #### Dedicated Newsletter Page
53
+
54
+ Create a full-featured newsletter management page with subscribe, unsubscribe, and status check forms:
55
+
56
+ ```markdown
57
+ ---
58
+ layout: page
59
+ title: Newsletter
60
+ permalink: /newsletter/
61
+ ---
62
+
63
+ <div class="newsletter-page-container">
64
+ {% include newsletter-subscribe-form.html %}
65
+ {% include newsletter-unsubscribe-form.html %}
66
+ {% include newsletter-status-check.html %}
67
+ </div>
68
+ ```
69
+
70
+ **Available Includes:**
71
+ - `newsletter-subscribe-form.html` - Full subscription form with email and optional name fields
72
+ - `newsletter-unsubscribe-form.html` - Unsubscribe by email
73
+ - `newsletter-status-check.html` - Check subscription status with formatted results
74
+
75
+ #### API Integration
76
+
77
+ The newsletter components expect the following API endpoints:
78
+
79
+ - `POST /v1/newsletter` - Subscribe (body: `{"email": "user@example.com", "name": "Optional Name"}`)
80
+ - `DELETE /v1/newsletter` - Unsubscribe (body: `{"email": "user@example.com"}`)
81
+ - `GET /v1/newsletter/status?email=user@example.com` - Check status
82
+
83
+ Responses are displayed with Bootstrap-styled alerts for success/error messaging.
22
84
 
23
85
  ## Release
24
86
 
@@ -4,6 +4,7 @@
4
4
  <a class="mb-1 mx-2 footer_item" href="{{ link.url }}">{{ link.title }}</a>
5
5
  {% endfor %}
6
6
  </div>
7
+ {% include newsletter-signup-footer.html %}
7
8
  <small class="copyright-text">
8
9
  John Sosoka<br>
9
10
  &copy; {{site.site_est_year}} - {{ site.time | date: "%Y" }}
@@ -0,0 +1,78 @@
1
+ {% if site.newsletter.footer.enabled %}
2
+ <div class="newsletter-footer-signup">
3
+ <div class="newsletter-container">
4
+ <div class="newsletter-content">
5
+ <div class="newsletter-text">
6
+ <h5 class="newsletter-title">{{ site.newsletter.footer.title | default: "Stay Updated" }}</h5>
7
+ <p class="newsletter-description">{{ site.newsletter.footer.description | default: "Get notified about new blog posts and updates" }}</p>
8
+ </div>
9
+ <form id="newsletterFooterForm" class="newsletter-form">
10
+ <div id="newsletter-notification" class="alert mt-2" role="alert" style="display: none;"></div>
11
+ <div class="newsletter-input-group">
12
+ <input type="email"
13
+ class="form-control newsletter-email-input"
14
+ id="newsletter-email"
15
+ name="newsletter_email"
16
+ placeholder="Enter your email"
17
+ required>
18
+ <button type="submit" class="btn btn-primary newsletter-submit-btn" id="newsletter-submit">
19
+ Subscribe
20
+ </button>
21
+ </div>
22
+ </form>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <script src="/assets/js/jquery-3.7.0.min.js"></script>
28
+ <script>
29
+ function submitNewsletterFooter(email) {
30
+ $.ajax({
31
+ url: "{{ site.newsletter.api.url }}",
32
+ type: "POST",
33
+ contentType: "application/json",
34
+ data: JSON.stringify({
35
+ email: email,
36
+ name: "Newsletter Subscriber" // Default name for footer signups
37
+ }),
38
+ success: function(data) {
39
+ console.log(data);
40
+ $('#newsletter-notification')
41
+ .removeClass('alert-danger')
42
+ .addClass('alert-success')
43
+ .text('Successfully subscribed! Check your email.')
44
+ .show();
45
+ $('#newsletter-email').val('');
46
+ },
47
+ error: function(xhr) {
48
+ console.log("Error in the request:", xhr);
49
+ var errorMsg = 'Something went wrong. Please try again.';
50
+ if (xhr.responseJSON && xhr.responseJSON.error) {
51
+ errorMsg = xhr.responseJSON.error;
52
+ }
53
+ $('#newsletter-notification')
54
+ .removeClass('alert-success')
55
+ .addClass('alert-danger')
56
+ .text(errorMsg)
57
+ .show();
58
+ }
59
+ });
60
+ }
61
+
62
+ $('#newsletterFooterForm').on('submit', function(e) {
63
+ e.preventDefault();
64
+ var email = $('#newsletter-email').val().trim();
65
+
66
+ if (!email) {
67
+ $('#newsletter-notification')
68
+ .removeClass('alert-success')
69
+ .addClass('alert-danger')
70
+ .text('Please enter a valid email address.')
71
+ .show();
72
+ return;
73
+ }
74
+
75
+ submitNewsletterFooter(email);
76
+ });
77
+ </script>
78
+ {% endif %}
@@ -0,0 +1,133 @@
1
+ <div class="newsletter-section">
2
+ <h2 class="newsletter-section-title">Check Subscription Status</h2>
3
+ <p class="newsletter-section-description">
4
+ Enter your email address to check your current newsletter subscription status.
5
+ </p>
6
+
7
+ <form id="newsletterStatusForm">
8
+ <div id="status-notification" class="alert mt-3" role="alert" style="display: none;"></div>
9
+
10
+ <div class="form-group">
11
+ <input type="email"
12
+ class="form-control"
13
+ id="status-email"
14
+ name="status_email"
15
+ placeholder="Email Address"
16
+ required>
17
+ </div>
18
+
19
+ <div class="form-buttons">
20
+ <button type="submit" class="btn btn-primary" id="status-submit">Check Status</button>
21
+ </div>
22
+ </form>
23
+
24
+ <div id="status-result" class="newsletter-status-result" style="display: none;">
25
+ <div class="status-row">
26
+ <span class="status-label">Email:</span>
27
+ <span class="status-value" id="result-email"></span>
28
+ </div>
29
+ <div class="status-row">
30
+ <span class="status-label">Status:</span>
31
+ <span class="status-value" id="result-status"></span>
32
+ </div>
33
+ <div class="status-row" id="subscribed-at-row" style="display: none;">
34
+ <span class="status-label">Subscribed On:</span>
35
+ <span class="status-value" id="result-subscribed-at"></span>
36
+ </div>
37
+ <div class="status-row" id="updated-at-row" style="display: none;">
38
+ <span class="status-label">Last Updated:</span>
39
+ <span class="status-value" id="result-updated-at"></span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <script src="/assets/js/jquery-3.7.0.min.js"></script>
45
+ <script>
46
+ function checkNewsletterStatus(email) {
47
+ $.ajax({
48
+ url: "{{ site.newsletter.api.url }}/status?email=" + encodeURIComponent(email),
49
+ type: "GET",
50
+ success: function(data) {
51
+ console.log(data);
52
+ displayStatusResult(data);
53
+ $('#status-notification').hide();
54
+ },
55
+ error: function(xhr) {
56
+ console.log("Error in the request:", xhr);
57
+ var errorMsg = 'Something went wrong. Please try again.';
58
+ if (xhr.responseJSON && xhr.responseJSON.error) {
59
+ errorMsg = xhr.responseJSON.error;
60
+ }
61
+ $('#status-notification')
62
+ .removeClass('alert-success')
63
+ .addClass('alert-danger')
64
+ .text(errorMsg)
65
+ .show();
66
+ $('#status-result').hide();
67
+ }
68
+ });
69
+ }
70
+
71
+ function displayStatusResult(data) {
72
+ // Set email
73
+ $('#result-email').text(data.email);
74
+
75
+ // Set status with appropriate styling
76
+ var statusText = data.status.toUpperCase();
77
+ var statusElement = $('#result-status');
78
+ statusElement.removeClass('status-active status-inactive status-not-found');
79
+
80
+ if (data.status === 'active') {
81
+ statusElement.addClass('status-active').text(statusText);
82
+ } else if (data.status === 'inactive') {
83
+ statusElement.addClass('status-inactive').text(statusText);
84
+ } else {
85
+ statusElement.addClass('status-not-found').text(statusText);
86
+ }
87
+
88
+ // Show/hide timestamp rows based on data availability
89
+ if (data.subscribed_at) {
90
+ var subscribedDate = new Date(data.subscribed_at * 1000).toLocaleDateString('en-US', {
91
+ year: 'numeric',
92
+ month: 'long',
93
+ day: 'numeric'
94
+ });
95
+ $('#result-subscribed-at').text(subscribedDate);
96
+ $('#subscribed-at-row').show();
97
+ } else {
98
+ $('#subscribed-at-row').hide();
99
+ }
100
+
101
+ if (data.updated_at) {
102
+ var updatedDate = new Date(data.updated_at * 1000).toLocaleDateString('en-US', {
103
+ year: 'numeric',
104
+ month: 'long',
105
+ day: 'numeric'
106
+ });
107
+ $('#result-updated-at').text(updatedDate);
108
+ $('#updated-at-row').show();
109
+ } else {
110
+ $('#updated-at-row').hide();
111
+ }
112
+
113
+ // Show the result container
114
+ $('#status-result').show();
115
+ }
116
+
117
+ $('#newsletterStatusForm').on('submit', function(e) {
118
+ e.preventDefault();
119
+ var email = $('#status-email').val().trim();
120
+
121
+ if (!email) {
122
+ $('#status-notification')
123
+ .removeClass('alert-success')
124
+ .addClass('alert-danger')
125
+ .text('Please enter a valid email address.')
126
+ .show();
127
+ $('#status-result').hide();
128
+ return;
129
+ }
130
+
131
+ checkNewsletterStatus(email);
132
+ });
133
+ </script>
@@ -0,0 +1,90 @@
1
+ <div class="newsletter-section">
2
+ <h2 class="newsletter-section-title">Subscribe to Newsletter</h2>
3
+ <p class="newsletter-section-description">
4
+ Enter your email address to receive updates about new blog posts, projects, and announcements.
5
+ We respect your privacy and will never share your email address.
6
+ </p>
7
+
8
+ <form id="newsletterSubscribeForm">
9
+ <div id="subscribe-notification" class="alert mt-3" role="alert" style="display: none;"></div>
10
+
11
+ <div class="form-group">
12
+ <input type="email"
13
+ class="form-control"
14
+ id="subscribe-email"
15
+ name="subscribe_email"
16
+ placeholder="Email Address"
17
+ required>
18
+ </div>
19
+
20
+ <div class="form-group">
21
+ <input type="text"
22
+ class="form-control"
23
+ id="subscribe-name"
24
+ name="subscribe_name"
25
+ placeholder="Name (Optional)">
26
+ </div>
27
+
28
+ <div class="form-buttons">
29
+ <button type="submit" class="btn btn-primary" id="subscribe-submit">Subscribe to Newsletter</button>
30
+ </div>
31
+ </form>
32
+ </div>
33
+
34
+ <script src="/assets/js/jquery-3.7.0.min.js"></script>
35
+ <script>
36
+ function submitNewsletterSubscribe(email, name) {
37
+ $.ajax({
38
+ url: "{{ site.newsletter.api.url }}",
39
+ type: "POST",
40
+ contentType: "application/json",
41
+ data: JSON.stringify({
42
+ email: email,
43
+ name: name || "Newsletter Subscriber"
44
+ }),
45
+ success: function(data) {
46
+ console.log(data);
47
+ $('#subscribe-notification')
48
+ .removeClass('alert-danger')
49
+ .addClass('alert-success')
50
+ .text('Successfully subscribed! You will receive a confirmation shortly.')
51
+ .show();
52
+ clearSubscribeForm();
53
+ },
54
+ error: function(xhr) {
55
+ console.log("Error in the request:", xhr);
56
+ var errorMsg = 'Something went wrong. Please try again.';
57
+ if (xhr.responseJSON && xhr.responseJSON.error) {
58
+ errorMsg = xhr.responseJSON.error;
59
+ }
60
+ $('#subscribe-notification')
61
+ .removeClass('alert-success')
62
+ .addClass('alert-danger')
63
+ .text(errorMsg)
64
+ .show();
65
+ }
66
+ });
67
+ }
68
+
69
+ function clearSubscribeForm() {
70
+ $('#subscribe-email').val('');
71
+ $('#subscribe-name').val('');
72
+ }
73
+
74
+ $('#newsletterSubscribeForm').on('submit', function(e) {
75
+ e.preventDefault();
76
+ var email = $('#subscribe-email').val().trim();
77
+ var name = $('#subscribe-name').val().trim();
78
+
79
+ if (!email) {
80
+ $('#subscribe-notification')
81
+ .removeClass('alert-success')
82
+ .addClass('alert-danger')
83
+ .text('Please enter a valid email address.')
84
+ .show();
85
+ return;
86
+ }
87
+
88
+ submitNewsletterSubscribe(email, name);
89
+ });
90
+ </script>
@@ -0,0 +1,79 @@
1
+ <div class="newsletter-section">
2
+ <h2 class="newsletter-section-title">Unsubscribe from Newsletter</h2>
3
+ <p class="newsletter-section-description">
4
+ Sorry to see you go! Enter your email address below to unsubscribe from the newsletter.
5
+ You can always re-subscribe at any time.
6
+ </p>
7
+
8
+ <form id="newsletterUnsubscribeForm">
9
+ <div id="unsubscribe-notification" class="alert mt-3" role="alert" style="display: none;"></div>
10
+
11
+ <div class="form-group">
12
+ <input type="email"
13
+ class="form-control"
14
+ id="unsubscribe-email"
15
+ name="unsubscribe_email"
16
+ placeholder="Email Address"
17
+ required>
18
+ </div>
19
+
20
+ <div class="form-buttons">
21
+ <button type="submit" class="btn btn-secondary" id="unsubscribe-submit">Unsubscribe from Newsletter</button>
22
+ </div>
23
+ </form>
24
+ </div>
25
+
26
+ <script src="/assets/js/jquery-3.7.0.min.js"></script>
27
+ <script>
28
+ function submitNewsletterUnsubscribe(email) {
29
+ $.ajax({
30
+ url: "{{ site.newsletter.api.url }}",
31
+ type: "DELETE",
32
+ contentType: "application/json",
33
+ data: JSON.stringify({
34
+ email: email
35
+ }),
36
+ success: function(data) {
37
+ console.log(data);
38
+ $('#unsubscribe-notification')
39
+ .removeClass('alert-danger')
40
+ .addClass('alert-success')
41
+ .text('Successfully unsubscribed. You will no longer receive our newsletter.')
42
+ .show();
43
+ clearUnsubscribeForm();
44
+ },
45
+ error: function(xhr) {
46
+ console.log("Error in the request:", xhr);
47
+ var errorMsg = 'Something went wrong. Please try again.';
48
+ if (xhr.responseJSON && xhr.responseJSON.error) {
49
+ errorMsg = xhr.responseJSON.error;
50
+ }
51
+ $('#unsubscribe-notification')
52
+ .removeClass('alert-success')
53
+ .addClass('alert-danger')
54
+ .text(errorMsg)
55
+ .show();
56
+ }
57
+ });
58
+ }
59
+
60
+ function clearUnsubscribeForm() {
61
+ $('#unsubscribe-email').val('');
62
+ }
63
+
64
+ $('#newsletterUnsubscribeForm').on('submit', function(e) {
65
+ e.preventDefault();
66
+ var email = $('#unsubscribe-email').val().trim();
67
+
68
+ if (!email) {
69
+ $('#unsubscribe-notification')
70
+ .removeClass('alert-success')
71
+ .addClass('alert-danger')
72
+ .text('Please enter a valid email address.')
73
+ .show();
74
+ return;
75
+ }
76
+
77
+ submitNewsletterUnsubscribe(email);
78
+ });
79
+ </script>
@@ -3,6 +3,7 @@
3
3
  @import 'codeblock';
4
4
  @import 'contact';
5
5
  @import 'form';
6
+ @import 'newsletter';
6
7
  @import 'highlight';
7
8
  @import 'navbar';
8
9
  @import 'post-navigation';
@@ -0,0 +1,272 @@
1
+ // Newsletter component styling
2
+
3
+ // ===========================
4
+ // Footer Newsletter Signup
5
+ // ===========================
6
+ .newsletter-footer-signup {
7
+ margin: $spacing-xxl 0 $spacing-xl 0;
8
+ padding: $spacing-xl 0;
9
+ border-top: 1px solid rgba(138, 168, 255, 0.15);
10
+
11
+ .newsletter-container {
12
+ max-width: 800px;
13
+ margin: 0 auto;
14
+ padding: 0 $spacing-lg;
15
+ }
16
+
17
+ .newsletter-content {
18
+ text-align: center;
19
+ }
20
+
21
+ .newsletter-text {
22
+ margin-bottom: $spacing-lg;
23
+ }
24
+
25
+ .newsletter-title {
26
+ font-size: $large-font-size;
27
+ font-weight: $semibold-weight;
28
+ color: $dark-white;
29
+ margin-bottom: $spacing-sm;
30
+ }
31
+
32
+ .newsletter-description {
33
+ font-size: $base-font-size;
34
+ color: $darker-white;
35
+ margin-bottom: 0;
36
+ }
37
+
38
+ .newsletter-form {
39
+ // Override default form styling for compact footer form
40
+ margin: 0;
41
+ padding: 0;
42
+ background: transparent;
43
+ border: none;
44
+ box-shadow: none;
45
+ backdrop-filter: none;
46
+ -webkit-backdrop-filter: none;
47
+ }
48
+
49
+ .newsletter-input-group {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: $spacing-md;
53
+ max-width: 500px;
54
+ margin: 0 auto;
55
+
56
+ @media (min-width: 576px) {
57
+ flex-direction: row;
58
+ align-items: stretch;
59
+ }
60
+ }
61
+
62
+ .newsletter-email-input {
63
+ flex: 1;
64
+ margin-bottom: 0;
65
+ color: $dark-white !important;
66
+
67
+ &::placeholder {
68
+ color: $darker-white !important;
69
+ opacity: 0.6 !important;
70
+ }
71
+
72
+ @media (min-width: 576px) {
73
+ min-width: 300px;
74
+ }
75
+ }
76
+
77
+ .newsletter-submit-btn {
78
+ white-space: nowrap;
79
+ padding: $spacing-md $spacing-xl;
80
+
81
+ @media (min-width: 576px) {
82
+ flex-shrink: 0;
83
+ }
84
+ }
85
+
86
+ // Alert styling for footer
87
+ .alert {
88
+ margin-top: $spacing-md;
89
+ margin-bottom: 0;
90
+ padding: $spacing-md;
91
+ border-radius: 8px;
92
+ font-weight: $medium-weight;
93
+ text-align: center;
94
+ max-width: 500px;
95
+ margin-left: auto;
96
+ margin-right: auto;
97
+
98
+ &.alert-success {
99
+ background-color: rgba(111, 191, 115, 0.2);
100
+ border: 1px solid rgba(111, 191, 115, 0.4);
101
+ color: lighten($accent-green, 20%);
102
+ }
103
+
104
+ &.alert-danger {
105
+ background-color: rgba(251, 73, 52, 0.2);
106
+ border: 1px solid rgba(251, 73, 52, 0.4);
107
+ color: lighten(#fb4934, 20%);
108
+ }
109
+ }
110
+ }
111
+
112
+ // ===========================
113
+ // Full Page Newsletter Forms
114
+ // ===========================
115
+ .newsletter-page-container {
116
+ max-width: 800px;
117
+ margin: 0 auto;
118
+ padding: $spacing-xl;
119
+ }
120
+
121
+ .newsletter-section {
122
+ margin-bottom: $spacing-xxl;
123
+
124
+ &:last-child {
125
+ margin-bottom: 0;
126
+ }
127
+
128
+ .newsletter-section-title {
129
+ font-size: $xlarge-font-size;
130
+ font-weight: $bold-weight;
131
+ color: $dark-white;
132
+ margin-bottom: $spacing-md;
133
+ padding-bottom: $spacing-md;
134
+ border-bottom: 2px solid rgba(138, 168, 255, 0.2);
135
+ }
136
+
137
+ .newsletter-section-description {
138
+ font-size: $base-font-size;
139
+ color: $darker-white;
140
+ margin-bottom: $spacing-lg;
141
+ line-height: $base-line-height;
142
+ }
143
+ }
144
+
145
+ // Newsletter form specific styling (subscribe, unsubscribe, status check)
146
+ #newsletterSubscribeForm,
147
+ #newsletterUnsubscribeForm,
148
+ #newsletterStatusForm {
149
+ max-width: 600px;
150
+ margin: 0 auto;
151
+
152
+ .form-control {
153
+ margin-bottom: $spacing-lg;
154
+ }
155
+
156
+ .form-buttons {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: $spacing-md;
160
+ margin-top: $spacing-md;
161
+
162
+ .btn {
163
+ width: 100%;
164
+ }
165
+ }
166
+
167
+ // Single button (not in .form-buttons)
168
+ .btn:not(.form-buttons .btn) {
169
+ width: 100%;
170
+ margin-top: $spacing-md;
171
+ }
172
+
173
+ // Alert messages
174
+ .alert {
175
+ margin-top: $spacing-lg;
176
+ padding: $spacing-md;
177
+ border-radius: 8px;
178
+ font-weight: $medium-weight;
179
+
180
+ &.alert-success {
181
+ background-color: rgba(111, 191, 115, 0.2);
182
+ border: 1px solid rgba(111, 191, 115, 0.4);
183
+ color: lighten($accent-green, 20%);
184
+ }
185
+
186
+ &.alert-danger {
187
+ background-color: rgba(251, 73, 52, 0.2);
188
+ border: 1px solid rgba(251, 73, 52, 0.4);
189
+ color: lighten(#fb4934, 20%);
190
+ }
191
+
192
+ &.alert-info {
193
+ background-color: rgba(138, 168, 255, 0.2);
194
+ border: 1px solid rgba(138, 168, 255, 0.4);
195
+ color: lighten($darker-text-link-blue, 20%);
196
+ }
197
+ }
198
+ }
199
+
200
+ // Status check result display
201
+ .newsletter-status-result {
202
+ margin-top: $spacing-lg;
203
+ padding: $spacing-lg;
204
+ background: rgba(27, 29, 37, 0.5);
205
+ border: 1px solid rgba(138, 168, 255, 0.2);
206
+ border-radius: 12px;
207
+ backdrop-filter: blur(8px);
208
+ -webkit-backdrop-filter: blur(8px);
209
+
210
+ .status-row {
211
+ display: flex;
212
+ justify-content: space-between;
213
+ align-items: center;
214
+ padding: $spacing-sm 0;
215
+ border-bottom: 1px solid rgba(138, 168, 255, 0.1);
216
+
217
+ &:last-child {
218
+ border-bottom: none;
219
+ }
220
+
221
+ .status-label {
222
+ font-weight: $semibold-weight;
223
+ color: $darker-white;
224
+ }
225
+
226
+ .status-value {
227
+ color: $dark-white;
228
+
229
+ &.status-active {
230
+ color: lighten($accent-green, 20%);
231
+ font-weight: $semibold-weight;
232
+ }
233
+
234
+ &.status-inactive {
235
+ color: lighten(#fb4934, 20%);
236
+ font-weight: $semibold-weight;
237
+ }
238
+
239
+ &.status-not-found {
240
+ color: $darker-white;
241
+ font-style: italic;
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ // Mobile responsiveness
248
+ @media (max-width: 575px) {
249
+ .newsletter-footer-signup {
250
+ .newsletter-title {
251
+ font-size: $medium-font-size;
252
+ }
253
+
254
+ .newsletter-description {
255
+ font-size: $small-font-size;
256
+ }
257
+
258
+ .newsletter-input-group {
259
+ gap: $spacing-sm;
260
+ }
261
+ }
262
+
263
+ .newsletter-page-container {
264
+ padding: $spacing-md;
265
+ }
266
+
267
+ .newsletter-section {
268
+ .newsletter-section-title {
269
+ font-size: $large-font-size;
270
+ }
271
+ }
272
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jscom_ice
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Sosoka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-28 00:00:00.000000000 Z
11
+ date: 2025-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -82,6 +82,10 @@ files:
82
82
  - _includes/header.html
83
83
  - _includes/john_landing.html
84
84
  - _includes/navbar.html
85
+ - _includes/newsletter-signup-footer.html
86
+ - _includes/newsletter-status-check.html
87
+ - _includes/newsletter-subscribe-form.html
88
+ - _includes/newsletter-unsubscribe-form.html
85
89
  - _includes/post_navigation.html
86
90
  - _includes/posts.html
87
91
  - _includes/scripts.html
@@ -103,6 +107,7 @@ files:
103
107
  - _sass/component/_github-gist.scss
104
108
  - _sass/component/_highlight.scss
105
109
  - _sass/component/_navbar.scss
110
+ - _sass/component/_newsletter.scss
106
111
  - _sass/component/_post-navigation.scss
107
112
  - _sass/component/_posts.scss
108
113
  - _sass/component/_table.scss