jekyll-theme-consulting 0.8.1 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 511a81cb11cf2afb43e0198dd231a54c93689a5f9319b0d5114bb920baef7171
4
- data.tar.gz: f3d3accc2b74fc873257a41765744478865773446b881e3d5559c5cdbfece89c
3
+ metadata.gz: 1303e48e96de64a397286ba1e967c908766059f74f63441b9558de6cad51e640
4
+ data.tar.gz: faea6567ccf1c4c9296b879be97cb3571896c0beef9e013b6585f0928e3827da
5
5
  SHA512:
6
- metadata.gz: bd06f485e3b390c43dcfa987c32adb4172bb274efefdc21f1d367477b49d9c977ed8fe2386ec16687865386fea0253af187579f9cda91a66f0509a47a4ec3515
7
- data.tar.gz: e30cef7142288c4bfa4d56921a8e23e754bc8d3d888cae1e5d4de3d62baeb2050e496e21ec10934c88ccc66caf6cd7832553f5320785f8fea5c21d4251553ad4
6
+ metadata.gz: 47a0d039df89f13940c29499c8490a5eb68586c29d38923a06e376ee44b4c2c5b551455ea2e031a0faf81c1458f884ceabad8ac12abf9f89163447ed7e51711a
7
+ data.tar.gz: daec8ff000a41334f72e321615945b1375007062e107f58cd01eeec71df8e8d78bf76636fce3b08e8a4b6491b4e588102267f96cca990210ac05b5d963274ac7
data/README.md CHANGED
@@ -24,6 +24,8 @@ You can preview the theme [here](https://moodule.github.io/jekyll-theme-consulti
24
24
 
25
25
  > **Light**: svg images, thumbs and lazy loading for raster images, few libraries
26
26
 
27
+ > **Performant**: parallel loading & execution of assets, support lazy-loading, use webp image format, optimal critical path
28
+
27
29
  > **Hardened**: form validation, recaptcha, verified libraries
28
30
 
29
31
  # Installation
@@ -80,6 +82,57 @@ The contact form is validated using [google's recaptcha plugin][recaptcha-docume
80
82
  First, you need to [sign your website up] to enable the plugin.
81
83
  Google will provide you with a **client-side integration key**: copy it to `_config.yml` under `recaptcha.sitekey`.
82
84
 
85
+ ## Images
86
+
87
+ ### Lazy-Loading
88
+
89
+ Images downloading & rendering can be defered until they enter the viewport.
90
+ This shortens the loading of the webpage, with no consequence on the displayed content since the images are off-screen.
91
+
92
+ There are 2 possibilities:
93
+ 1) using the scripts bundled with the gem
94
+ 2) depending on the browser
95
+
96
+ Browser support for lazy-loading is varying, so it is recommanded to opt for the first solution.
97
+
98
+ #### Using the gem functionality
99
+
100
+ - add `class="lazy-loading"`
101
+ - fill the attribute `src` with the path to the placeholder file
102
+ - fill the attribute `data-src` with the path to the content file
103
+
104
+ Before:
105
+ ```html
106
+ <img src="{{ 'assets/images/content.jpg' | absolute_url }}" />
107
+ ```
108
+
109
+ After:
110
+ ```html
111
+ <img class="lazy-loading" src="{{ 'assets/images/placeholder.jpg' | absolute_url }}" data-src="{{ 'assets/images/content.jpg' | absolute_url }}" />
112
+ ```
113
+
114
+ #### Using the browser functionality
115
+
116
+ - add `loading="lazy"`
117
+
118
+ Before:
119
+ ```html
120
+ <img src="{{ 'assets/images/content.jpg' | absolute_url }}" />
121
+ ```
122
+
123
+ After:
124
+ ```html
125
+ <img loading="lazy" src="{{ 'assets/images/content.jpg' | absolute_url }}" />
126
+ ```
127
+
128
+ > This functionality is not supported by all the browsers!
129
+
130
+ ### Compression and file formats
131
+
132
+ It is recommanded to use the `webp` format.
133
+
134
+ At a given dimension and compression level (quality) the webp files are at least half the size with a smoother feel.
135
+
83
136
  ## Publication
84
137
 
85
138
  ### On Github Pages
@@ -5,15 +5,16 @@
5
5
  {%- assign submit_input_id="contact-submit-button" -%}
6
6
  {%- assign reset_input_id="contact-reset-button" -%}
7
7
  {%- assign recaptcha_widget_id="recaptcha-checkbox" -%}
8
- {%- assign onsubmit_callback="sendContactMessage" -%}
8
+ {%- assign onsubmit_callback="processContactRequest" -%}
9
9
  {%- assign onreset_callback="resetContactForm" -%}
10
+ {%- assign onfocusout_callback="toggleSubmitInputElement" -%}
10
11
  <section id="contact" class="inverted" style="{{ include.style | default: nil }}">
11
12
  <header class="major">
12
13
  <h2>Contact Me</h2>
13
14
  </header>
14
15
  <div class="row gtr-200">
15
16
  <div class="col-6 col-12-medium col-12-small col-12-xsmall">
16
- {% include contact_form.html form_id=form_id name_input_id=name_input_id email_input_id=email_input_id message_input_id=message_input_id submit_input_id=submit_input_id reset_input_id=reset_input_id recaptcha_widget_id=recaptcha_widget_id onsubmit_callback=onsubmit_callback onreset_callback=onreset_callback %}
17
+ {% include contact_form.html form_id=form_id name_input_id=name_input_id email_input_id=email_input_id message_input_id=message_input_id submit_input_id=submit_input_id reset_input_id=reset_input_id recaptcha_widget_id=recaptcha_widget_id onsubmit_callback=onsubmit_callback onreset_callback=onreset_callback onfocusout_callback=onfocusout_callback %}
17
18
  </div>
18
19
  <div class="col-6 col-12-medium col-12-small col-12-xsmall">
19
20
  <ul class="contact">
@@ -26,4 +27,4 @@
26
27
  </div>
27
28
  </section>
28
29
 
29
- {% include contact_script.html form_id=form_id name_input_id=name_input_id email_input_id=email_input_id message_input_id=message_input_id submit_input_id=submit_input_id reset_input_id=reset_input_id recaptcha_widget_id=recaptcha_widget_id onsubmit_callback=onsubmit_callback onreset_callback=onreset_callback %}
30
+ {% include contact_script.html form_id=form_id name_input_id=name_input_id email_input_id=email_input_id message_input_id=message_input_id submit_input_id=submit_input_id reset_input_id=reset_input_id recaptcha_widget_id=recaptcha_widget_id onsubmit_callback=onsubmit_callback onreset_callback=onreset_callback onfocusout_callback=onfocusout_callback %}
@@ -1,24 +1,26 @@
1
- <form id="{{ include.form_id | 'contact-form' }}" method="post" action="#" onsubmit="{{ include.onsubmit_callback | 'sendContactMessage' }}()" onreset="{{ include.onreset_callback | 'resetContactForm' }}()" >
1
+ <form id="{{ include.form_id | default: 'contact-form' }}" method="post" action="javascript:void(0);" onsubmit="{{ include.onsubmit_callback | default: 'processContactRequest' }}()" onreset="{{ include.onreset_callback | default: 'resetContactForm' }}()" >
2
2
  <div class="row gtr-uniform">
3
3
  <div class="col-6 col-12-small col-12-xsmall">
4
- <input type="text" name="{{ include.name_input_id | 'contact-name' }}" id="{{ include.name_input_id | 'contact-name' }}" value="" placeholder="Name" required minlength="4" maxlength="64" />
4
+ <input type="text" name="{{ include.name_input_id | default: 'contact-name' }}" id="{{ include.name_input_id | default: 'contact-name' }}" value="" placeholder="Name" required minlength="4" maxlength="64" onfocusout="{{ include.onfocusout_callback | default: 'toggleSubmitInputElement' }}()" />
5
5
  </div>
6
6
  <div class="col-6 col-12-small col-12-xsmall">
7
- <input type="email" name="{{ include.email_input_id | 'contact-email' }}" id="{{ include.email_input_id | 'contact-email' }}" value="" placeholder="Email" required minlength="8" maxlength="64" />
7
+ <input type="email" name="{{ include.email_input_id | default: 'contact-email' }}" id="{{ include.email_input_id | default: 'contact-email' }}" value="" placeholder="Email" required minlength="8" maxlength="64" onfocusout="{{ include.onfocusout_callback | default: 'toggleSubmitInputElement' }}()" />
8
8
  </div>
9
9
  <!-- Break -->
10
10
  <div class="col-12">
11
- <textarea name="{{ include.message_input_id | 'contact-message' }}" id="{{ include.message_input_id | 'contact-message' }}" placeholder="Enter your message" rows="6" required minlength="16" maxlength="256" >Please get back to me!</textarea>
11
+ <textarea name="{{ include.message_input_id | default: 'contact-message' }}" id="{{ include.message_input_id | default: 'contact-message' }}" placeholder="Enter your message" rows="6" required minlength="16" maxlength="256" onfocusout="{{ include.onfocusout_callback | default: 'toggleSubmitInputElement' }}()" >Please get back to me!</textarea>
12
12
  </div>
13
13
  <!-- Break -->
14
14
  <div class="col-12">
15
15
  <ul class="actions">
16
- <li><input type="submit" id="{{ include.submit_input_id | 'contact-submit-button' }}" value="Send Message" class="primary button solid fa-envelope" disabled/></li>
17
- <li><input type="reset" id="{{ include.reset_input_id | 'contact-reset-button' }}" value="Reset" class="icon button fa-undo" /></li>
18
- </ul>
19
- <ul class="actions">
20
- <li><div id="{{ include.recaptcha_widget_id | 'recaptcha-checkbox' }}" ></div></li>
16
+ <li><input type="submit" id="{{ include.submit_input_id | default: 'contact-submit-button' }}" value="Send Message" class="primary button solid fa-envelope" disabled/></li>
17
+ <li><input type="reset" id="{{ include.reset_input_id | default: 'contact-reset-button' }}" value="Reset" class="icon button fa-undo" /></li>
21
18
  </ul>
22
19
  </div>
23
20
  </div>
21
+ <div id="modal-recaptcha-window" class="modal" tabIndex="-1">
22
+ <div class="inner">
23
+ <div id="{{ include.recaptcha_widget_id | default: 'recaptcha-checkbox' }}" ></div>
24
+ </div>
25
+ </div>
24
26
  </form>
@@ -4,6 +4,7 @@
4
4
  const emailInputElement = document.getElementById("{{ include.email_input_id | default: 'contact-email' }}");
5
5
  const messageInputElement = document.getElementById("{{ include.message_input_id | default: 'contact-message' }}");
6
6
  const submitInputElement = document.getElementById("{{ include.submit_input_id | default: 'contact-submit-button' }}");
7
+ const modalWindowElement = document.getElementById("{{ include.modal_window_id | default: 'modal-recaptcha-window' }}");
7
8
  var recaptchaWidget;
8
9
 
9
10
  const isInputValid = function() {
@@ -11,42 +12,82 @@
11
12
  && emailInputElement.checkValidity()
12
13
  && messageInputElement.checkValidity())
13
14
  };
14
-
15
- const isRecaptchaValid = function() {
16
- var responseToken = grecaptcha.getResponse(recaptchaWidget);
17
- return true;
18
- };
19
15
 
20
16
  const disableSubmitInputElement = function (disabled = true) {
21
17
  submitInputElement.disabled = disabled;
22
18
  }
23
19
 
24
- const {{ include.onreset_callback | 'resetContactForm' }} = function() {
20
+ const {{ include.onfocusout_callback | default: 'toggleSubmitInputElement' }} = function() {
21
+ if (isInputValid()) {
22
+ disableSubmitInputElement(false);
23
+ } else {
24
+ disableSubmitInputElement();
25
+ }
26
+ };
27
+
28
+ const showModalWindow = function() {
29
+ modalWindowElement.classList.add('visible');
30
+ modalWindowElement.classList.add('loaded');
31
+ modalWindowElement.focus();
32
+ }
33
+
34
+ const hideModalWindow = function() {
35
+ modalWindowElement.classList.remove('loaded');
36
+ modalWindowElement.classList.remove('visible');
37
+ contactFormElement.focus();
38
+ }
39
+
40
+ const {{ include.onsubmit_callback | default: 'processContactRequest()' }} = function() {
41
+ if (grecaptcha.getResponse(recaptchaWidget).length > 0) {
42
+ sendContactMessage();
43
+ } else {
44
+ showModalWindow();
45
+ }
46
+ };
47
+
48
+ const {{ include.onreset_callback | default: 'resetContactForm' }} = function() {
25
49
  contactFormElement.reset();
26
50
  disableSubmitInputElement();
27
51
  };
28
52
 
29
- const {{ include.onsubmit_callback | 'sendContactMessage()' }} = function() {
30
- encodeURIComponent(nameInputElement.value);
31
- encodeURIComponent(emailInputElement.value);
32
- encodeURIComponent(messageInputElement.value);
53
+ const sendContactMessage = function() {
54
+ const recaptchaToken = grecaptcha.getResponse(recaptchaWidget);
55
+
56
+ const data = {
57
+ name: encodeURIComponent(nameInputElement.value),
58
+ email: encodeURIComponent(emailInputElement.value),
59
+ message: encodeURIComponent(messageInputElement),
60
+ token: recaptchaToken
61
+ };
62
+
63
+ hideModalWindow();
64
+
65
+ if (recaptchaToken.length > 0) {
66
+ fetch("{{ '/contact' | absolute_url }}", {
67
+ method: 'POST',
68
+ mode: 'same-origin',
69
+ headers: {'Content-Type': 'application/json',},
70
+ body: JSON.stringify(data),
71
+ })
72
+ .then((response) => {
73
+ alert('Message sent!');
74
+ })
75
+ .catch((error) => {
76
+ console.error('Error:', error);
77
+ });
78
+ }
33
79
  };
34
80
 
35
- var onloadCallback = function() {
36
- var verifyForm = function(response) {
37
- if (isInputValid() && isRecaptchaValid()) {
38
- disableSubmitInputElement(false);
39
- } else {
40
- disableSubmitInputElement();
41
- }
42
- };
43
- recaptchaWidget = grecaptcha.render('{{ include.recaptcha_widget_id | 'recaptcha-checkbox' }}', {
81
+ var onRecaptchaLoadCallback = function() {
82
+ recaptchaWidget = grecaptcha.render("{{ include.recaptcha_widget_id | default: 'recaptcha-checkbox' }}", {
44
83
  'sitekey' : '{{ site.recaptcha.sitekey }}',
45
84
  'theme' : 'dark',
46
- 'callback' : verifyForm,
47
- 'expired-callback': disableSubmitInputElement,
48
- 'size' : 'compact'
85
+ 'size' : 'compact',
86
+ 'callback' : sendContactMessage
49
87
  });
50
88
  };
89
+
90
+ modalWindowElement.addEventListener('click', hideModalWindow);
91
+ modalWindowElement.addEventListener('keyup', hideModalWindow);
51
92
  </script>
52
- <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
93
+ <script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoadCallback&render=explicit" async defer></script>
@@ -1,54 +1,56 @@
1
1
  // Misc.
2
- $misc: (
3
- z-index-base: 10000
4
- );
2
+ $misc: (
3
+ z-index-base: 10000,
4
+ lightbox-opacity: 0.875
5
+ );
5
6
 
6
7
  // Duration.
7
- $duration: (
8
- nav: 0.5s,
9
- transition: 0.2s
10
- );
8
+ $duration: (
9
+ nav: 0.5s,
10
+ transition: 0.2s,
11
+ lightbox-fadein: 0.5s
12
+ );
11
13
 
12
14
  // Size.
13
- $size: (
14
- border-radius: 0.375em,
15
- element-height: 2.75em,
16
- element-margin: 2em,
17
- sidebar-width: 26em,
18
- sidebar-width-alt: 24em,
19
- gutter: 3em
20
- );
15
+ $size: (
16
+ border-radius: 0.375em,
17
+ element-height: 2.75em,
18
+ element-margin: 2em,
19
+ sidebar-width: 26em,
20
+ sidebar-width-alt: 24em,
21
+ gutter: 3em
22
+ );
21
23
 
22
24
  // Font.
23
- $font: (
24
- family: ('Open Sans', sans-serif),
25
- family-heading: ('Roboto Slab', serif),
26
- family-fixed: ('Courier New', monospace),
27
- weight: 400,
28
- weight-bold: 600,
29
- weight-heading: 700,
30
- weight-heading-alt: 400,
31
- kerning-heading: 0.075em
32
- );
25
+ $font: (
26
+ family: ('Open Sans', sans-serif),
27
+ family-heading: ('Roboto Slab', serif),
28
+ family-fixed: ('Courier New', monospace),
29
+ weight: 400,
30
+ weight-bold: 600,
31
+ weight-heading: 700,
32
+ weight-heading-alt: 400,
33
+ kerning-heading: 0.075em
34
+ );
33
35
 
34
36
  // Palette.
35
- $palette: (
36
- bg: #ffffff,
37
- bg-alt: #f5f6f7,
38
- bg-inverted: #6666ff,
39
- bg-alt-inverted: #223344,
40
- fg: #000000,
41
- fg-bold: #3d4449,
42
- fg-light: #9fa3a6,
43
- fg-inverted: #ffffff,
44
- fg-bold-inverted: #ffffff,
45
- fg-light-inverted: #ffffff,
46
- border: rgba(210,215,217,0.75),
47
- border-bg: transparentize(#e6ebed, 0.75),
48
- border-inverted: #ffffff,
49
- border-bg-inverted: #ffffff,
50
- accent: #6666ff,
51
- accent-cp: #ffdd44,
52
- accent-inverted: #ffffff,
53
- accent-cp-inverted: #6666ff,
54
- );
37
+ $palette: (
38
+ bg: #ffffff,
39
+ bg-alt: #f5f6f7,
40
+ bg-inverted: #6666ff,
41
+ bg-alt-inverted: #223344,
42
+ fg: #000000,
43
+ fg-bold: #3d4449,
44
+ fg-light: #9fa3a6,
45
+ fg-inverted: #ffffff,
46
+ fg-bold-inverted: #ffffff,
47
+ fg-light-inverted: #ffffff,
48
+ border: rgba(210,215,217,0.75),
49
+ border-bg: transparentize(#e6ebed, 0.75),
50
+ border-inverted: #ffffff,
51
+ border-bg-inverted: #ffffff,
52
+ accent: #6666ff,
53
+ accent-cp: #ffdd44,
54
+ accent-inverted: #ffffff,
55
+ accent-cp-inverted: #6666ff,
56
+ );
@@ -16,6 +16,7 @@
16
16
  @import 'main/components/row';
17
17
  @import 'main/components/section';
18
18
  @import 'main/components/form';
19
+ @import 'main/components/modal';
19
20
  @import 'main/components/box';
20
21
  @import 'main/components/icon';
21
22
  @import 'main/components/image';
@@ -0,0 +1,50 @@
1
+ .modal {
2
+ @include vendor('display', 'flex');
3
+ @include vendor('align-items', 'center');
4
+ @include vendor('justify-content', 'center');
5
+ @include vendor('pointer-events', 'none');
6
+ @include vendor('user-select', 'none');
7
+ @include vendor('transition', (
8
+ 'opacity #{_duration(lightbox-fadein)} ease',
9
+ 'visibility #{_duration(lightbox-fadein)}',
10
+ 'z-index #{_duration(lightbox-fadein)}'
11
+ ));
12
+ position: fixed;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ opacity: 0;
18
+ outline: 0;
19
+ visibility: none;
20
+ z-index: 0;
21
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
22
+ background-color: transparentize(_palette(bg), 1 - _misc(lightbox-opacity));
23
+
24
+ .inner {
25
+ @include vendor('transform', 'translateY(0.75rem)');
26
+ @include vendor('transition', (
27
+ 'opacity #{_duration(lightbox-fadein) * 0.5} ease',
28
+ 'transform #{_duration(lightbox-fadein) * 0.5} ease'
29
+ ));
30
+ opacity: 0;
31
+ }
32
+
33
+ &.visible {
34
+ @include vendor('pointer-events', 'auto');
35
+ opacity: 1;
36
+ visibility: visible;
37
+ z-index: _misc(z-index-base) + 1000;
38
+ }
39
+
40
+ &.loaded {
41
+ .inner {
42
+ @include vendor('transform', 'translateY(0)');
43
+ @include vendor('transition', (
44
+ 'opacity #{_duration(lightbox-fadein)} ease',
45
+ 'transform #{_duration(lightbox-fadein)} ease'
46
+ ));
47
+ opacity: 1;
48
+ }
49
+ }
50
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-consulting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Mougeolle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-19 00:00:00.000000000 Z
11
+ date: 2020-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -153,6 +153,7 @@ files:
153
153
  - _sass/main/components/_image.scss
154
154
  - _sass/main/components/_list.scss
155
155
  - _sass/main/components/_mini-posts.scss
156
+ - _sass/main/components/_modal.css
156
157
  - _sass/main/components/_pagination.scss
157
158
  - _sass/main/components/_posts.scss
158
159
  - _sass/main/components/_row.scss