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 +4 -4
- data/README.md +66 -4
- data/_includes/footer.html +1 -0
- data/_includes/image-modal.html +10 -0
- data/_includes/newsletter-signup-footer.html +78 -0
- data/_includes/newsletter-status-check.html +133 -0
- data/_includes/newsletter-subscribe-form.html +90 -0
- data/_includes/newsletter-unsubscribe-form.html +79 -0
- data/_includes/scripts.html +3 -0
- data/_layouts/default.html +3 -0
- data/_sass/component/_all-components.scss +3 -1
- data/_sass/component/_image-modal.scss +30 -0
- data/_sass/component/_newsletter.scss +272 -0
- data/assets/js/image-modal.js +21 -0
- metadata +14 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49dc10821e215dad9aad15bad0bf3c26cc3fff12be23eb45d87492bf04ad3b04
|
|
4
|
+
data.tar.gz: 4f4ddaac613bf52e88b6604a8c38e7578b4312385e3540c35f9b4f35417e4bf3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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", '~>
|
|
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
|
|
data/_includes/footer.html
CHANGED
|
@@ -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>
|
data/_includes/scripts.html
CHANGED
|
@@ -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>-->
|
data/_layouts/default.html
CHANGED
|
@@ -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
|
|
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-
|
|
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.
|
|
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: []
|