jekyll-theme-consulting 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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