jscom_ice 1.0.1 → 1.2.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: 49dc10821e215dad9aad15bad0bf3c26cc3fff12be23eb45d87492bf04ad3b04
4
+ data.tar.gz: 4f4ddaac613bf52e88b6604a8c38e7578b4312385e3540c35f9b4f35417e4bf3
5
5
  SHA512:
6
- metadata.gz: 1b04dec36f48c2f8c712da8457a234edb16de9cadfe01e2b5e8a8241d6e1c0fa299b458e94c6cfe2c70b1d26b1525c379bedc8b0407501b8d45a4ecec3ced122
7
- data.tar.gz: 5d614331b9894d2507acb0a3cadee7e8a11f26cacfb4376dbbe53d04de1733b53c8cfa3f84b02381ca2b882de3e793c5496aa4234f3786be1940c966ce802975
6
+ metadata.gz: e83046aa3ea6e4afeab5ca6d742d16b0e9957834188b7426c90e5b0248dcb064455eeccad9b523017590eaf8f803afe170705e510b517dc5775be7e96e02ba39
7
+ data.tar.gz: '088e769d3bf5de44fd6b35dc5e7124bb1bcf5dc083f6362cd1e501441c6b247e6860af29ae2d681fd997881d45a066571ec8a1fb25c65964dcc23010c3c44b9f'
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,10 @@
1
+ <!-- Image Modal - click-to-expand for post images -->
2
+ <div class="modal fade" id="imageModal" tabindex="-1" aria-hidden="true">
3
+ <div class="modal-dialog modal-dialog-centered modal-xl">
4
+ <div class="modal-content bg-transparent border-0">
5
+ <div class="modal-body p-0 text-center">
6
+ <img src="" alt="" class="img-fluid" id="modalImage">
7
+ </div>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -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>
@@ -15,5 +15,8 @@ Scripts that are loaded throughout the website
15
15
  <!-- Navbar scroll behavior -->
16
16
  <script src="/assets/js/navbar.js"></script>
17
17
 
18
+ <!-- Image modal click-to-expand -->
19
+ <script src="/assets/js/image-modal.js"></script>
20
+
18
21
  <!-- TODO contact form page & move this to more local include? (it's only needed on 1 page)-->
19
22
  <!--<script src="/assets/js/contact.js"></script>-->
@@ -32,6 +32,9 @@
32
32
  </footer>
33
33
  </div>
34
34
 
35
+ <!-- Image Modal for click-to-expand -->
36
+ {% include image-modal.html %}
37
+
35
38
  <!-- Shared/Common Scripts -->
36
39
  {% include scripts.html %}
37
40
  </body>
@@ -3,10 +3,12 @@
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';
9
10
  @import 'posts';
10
11
  @import 'table';
11
12
  @import "carousel";
12
- @import "github-gist";
13
+ @import "github-gist";
14
+ @import "image-modal";
@@ -0,0 +1,30 @@
1
+ // Image Modal - Click-to-expand styling
2
+
3
+ // Clickable images in post content
4
+ .page-content img,
5
+ .post img {
6
+ cursor: pointer;
7
+ transition: opacity 0.2s ease;
8
+
9
+ &:hover {
10
+ opacity: 0.9;
11
+ }
12
+ }
13
+
14
+ // Modal styling
15
+ #imageModal {
16
+ .modal-content {
17
+ background: transparent;
18
+ border: none;
19
+ }
20
+
21
+ .modal-body {
22
+ padding: 0;
23
+ }
24
+
25
+ img {
26
+ max-height: 90vh;
27
+ width: auto;
28
+ max-width: 100%;
29
+ }
30
+ }
@@ -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
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Image Modal - Click-to-expand functionality for post images
3
+ * Uses Bootstrap 5 modal component
4
+ */
5
+ document.addEventListener('DOMContentLoaded', function() {
6
+ var modal = document.getElementById('imageModal');
7
+ if (!modal) return;
8
+
9
+ var modalImg = document.getElementById('modalImage');
10
+ var bsModal = new bootstrap.Modal(modal);
11
+
12
+ // Target images within post content
13
+ document.querySelectorAll('.page-content img, .post img').forEach(function(img) {
14
+ img.style.cursor = 'pointer';
15
+ img.addEventListener('click', function() {
16
+ modalImg.src = this.src;
17
+ modalImg.alt = this.alt;
18
+ bsModal.show();
19
+ });
20
+ });
21
+ });
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Sosoka
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-28 00:00:00.000000000 Z
11
+ date: 2025-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -80,8 +80,13 @@ files:
80
80
  - _includes/embed-audio.html
81
81
  - _includes/footer.html
82
82
  - _includes/header.html
83
+ - _includes/image-modal.html
83
84
  - _includes/john_landing.html
84
85
  - _includes/navbar.html
86
+ - _includes/newsletter-signup-footer.html
87
+ - _includes/newsletter-status-check.html
88
+ - _includes/newsletter-subscribe-form.html
89
+ - _includes/newsletter-unsubscribe-form.html
85
90
  - _includes/post_navigation.html
86
91
  - _includes/posts.html
87
92
  - _includes/scripts.html
@@ -102,7 +107,9 @@ files:
102
107
  - _sass/component/_form.scss
103
108
  - _sass/component/_github-gist.scss
104
109
  - _sass/component/_highlight.scss
110
+ - _sass/component/_image-modal.scss
105
111
  - _sass/component/_navbar.scss
112
+ - _sass/component/_newsletter.scss
106
113
  - _sass/component/_post-navigation.scss
107
114
  - _sass/component/_posts.scss
108
115
  - _sass/component/_table.scss
@@ -119,6 +126,7 @@ files:
119
126
  - assets/js/bootstrap.js.map
120
127
  - assets/js/bootstrap.min.js
121
128
  - assets/js/bootstrap.min.js.map
129
+ - assets/js/image-modal.js
122
130
  - assets/js/jquery-3.7.0.min.js
123
131
  - assets/js/navbar.js
124
132
  - assets/js/popper.min.js
@@ -126,7 +134,7 @@ homepage: https://github.com/johnsosoka/jscom-ice
126
134
  licenses:
127
135
  - GPL-3.0
128
136
  metadata: {}
129
- post_install_message:
137
+ post_install_message:
130
138
  rdoc_options: []
131
139
  require_paths:
132
140
  - lib
@@ -141,8 +149,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
149
  - !ruby/object:Gem::Version
142
150
  version: '0'
143
151
  requirements: []
144
- rubygems_version: 3.4.19
145
- signing_key:
152
+ rubygems_version: 3.0.3.1
153
+ signing_key:
146
154
  specification_version: 4
147
155
  summary: Simple Dark Jekyll Theme
148
156
  test_files: []