From 5de4e5ea8b77b86e5635a39f6b6f450a8779425c Mon Sep 17 00:00:00 2001
From: Carmine DiChiara <carmine.dichiara@committed2action.com>
Date: Sat, 16 Jan 2021 10:33:31 +0000
Subject: [PATCH] Added reCAPTCHA to Formspree and support for Netlify forms

---
 layouts/_default/single.html  |    2 
 layouts/partials/sidebar.html |    2 
 i18n/zh-cn.toml               |   27 +++
 i18n/zh-tw.toml               |   29 +++
 i18n/dk.toml                  |   29 +++
 assets/css/style.css          |   36 +++
 i18n/es.toml                  |   29 +++
 i18n/fi.toml                  |   29 +++
 README.md                     |   32 +++
 i18n/de.toml                  |   29 +++
 i18n/fa.toml                  |   29 +++
 i18n/pt-br.toml               |   27 +++
 layouts/_default/list.html    |    2 
 i18n/en.toml                  |   29 +++
 layouts/partials/navbar.html  |    2 
 layouts/index.html            |    4 
 i18n/it.toml                  |   29 +++
 i18n/fr.toml                  |   29 +++
 layouts/partials/contact.html |  106 +++++++++++
 19 files changed, 473 insertions(+), 28 deletions(-)

diff --git a/README.md b/README.md
index c3155e2..5302c0f 100644
--- a/README.md
+++ b/README.md
@@ -129,7 +129,6 @@
 [params]
 doNotLoadAnimations = true # Animations are loaded by default
 ```
-
 ### Control the date Format
 You can change the default date formating for the `list.html`, the `single.html` and the `index.html`. Simply configure the matching parameters.
 ```toml
@@ -267,9 +266,38 @@
 Step 1: Configure the `contactFormAction` in the `config.toml`
 ```toml
 [params]
-#contactFormAction = "https://formspree.io/f/your-form-hash-here"
+contactFormAction = "https://formspree.io/f/your-form-hash-here"
 ```
 Step 2: Activate the `contact: true` or  `contact=true` in the frontmatter of a page. See `exampleSite/content/contact.html` as an example.
+
+Step 3: If you wish to use a Google reCAPTCHA v2, you must [go to a Google Admin console](https://www.google.com/recaptcha/about/) and create a reCAPTCHA:
+Make sure it is a reCAPTCHA v2. You must then [go to your Formspree account](https://help.formspree.io/hc/en-us/articles/360022811154-Adding-a-custom-reCAPTCHA-key) and enter the secret key that you were given in the appropriate location.
+Step 4: Enter the reCAPTCHA site key in `config.toml`:
+```toml
+[params]
+contactFormReCaptchaSiteKey = "your-site-key"
+```
+### Netlify Contact Form on the Contact Page
+Netlify forms only work when your site is being actively hosted by Netlify - unlike Formspree, testing your form locally before committing to Netlify will not work.
+Step 1: Configure the `contactFormType` in the `config.toml`. Netlify does not require a `contactFormAction` configured.
+```toml
+[params]
+contactFormType = "netlify"
+```
+Step 2: Set `contact: true` or  `contact=true` in the frontmatter of a page. See `exampleSite/content/contact.html` as an example.
+
+Step 3: If you wish to use a Google reCAPTCHA v2, you must [go to a Google Admin console](https://www.google.com/recaptcha/about/) and create a reCAPTCHA.
+Make sure it is a reCAPTCHA v2. You must then [go to your Formspree account](https://help.formspree.io/hc/en-us/articles/360022811154-Adding-a-custom-reCAPTCHA-key) and enter the secret key that you were given in the appropriate location.
+
+Step 4: Enter the reCAPTCHA site key in `config.toml`:
+```toml
+[params]
+contactFormReCaptchaSiteKey = "your-site-key"
+```
+Step 5: [Go to your Netlify account](https://www.netlify.com/blog/2018/05/23/bring-your-own-recaptcha-to-netlify-forms/) and let them know both the site key and the secret key for your custom reCAPTCHA.
+
+Step 6: You will probably want to set up some [form notification](https://docs.netlify.com/forms/notifications/).
+
 ### Twitter Cards support
 
 In order to use the full functionality of Twitter cards, you will have to define a couple of settings in the `config.toml` and the frontmatter of a page.
diff --git a/assets/css/style.css b/assets/css/style.css
index 70dbb98..b3cdb4a 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -14,8 +14,10 @@
     --tag-color: #424242;
     --blockquote-text-color: #858585;
     --blockquote-border-color: #dfe2e5;
+    --error-color: #ff3939;
     --thumbnail-height: 15em;
     scroll-padding-top: 100px;
+    --font-family: 'Verdana', sans-serif;
 }
 
 html[data-theme='dark'] {
@@ -32,6 +34,7 @@
     --tag-color: rgb(191, 191, 191);
     --blockquote-text-color: #808080;
     --blockquote-border-color: #424242;
+    --error-color: #ff3939;
 }
 
 html {
@@ -41,7 +44,7 @@
 
 body {
     color: var(--body-color);
-    font-family: 'Verdana', sans-serif;
+    font-family: var(--font-family);
     font-size: 15px;
     width: 100%;
     margin: 0 auto 30px auto;
@@ -130,40 +133,48 @@
 
 @-webkit-keyframes fadeInDown {
     0% {
+        opacity: 0;
         -webkit-transform: translateY(-20px);
     }
 
     100% {
+        opacity: 1;
         -webkit-transform: translateY(0);
     }
 }
 
 @-moz-keyframes fadeInDown {
     0% {
+        opacity: 0;
         -moz-transform: translateY(-20px);
     }
 
     100% {
+        opacity: 1;
         -moz-transform: translateY(0);
     }
 }
 
 @-o-keyframes fadeInDown {
     0% {
+        opacity: 0;
         -o-transform: translateY(-20px);
     }
 
     100% {
+        opacity: 1;
         -o-transform: translateY(0);
     }
 }
 
 @keyframes fadeInDown {
     0% {
+        opacity: 0;
         transform: translateY(-20px);
     }
 
     100% {
+        opacity: 1;
         transform: translateY(0);
     }
 }
@@ -1052,7 +1063,7 @@
     border: 1px solid var(--form-border-color);
     color: var(--body-color);
 }
-.form-style ul li    .field-style:focus {
+.form-style ul li .field-style:focus {
     box-shadow: 0 0 5px;
     border:1px solid;
 }
@@ -1068,6 +1079,10 @@
 .form-style ul li input.align-right {
     float:right;
 }
+.form-style input,
+.form-style textarea {
+    font-family: var(--font-family);
+}
 .form-style ul li textarea {
     background-color: var(--bg-color);
     border: 1px solid var(--form-border-color);
@@ -1075,7 +1090,6 @@
     width: 100%;
     height: auto;
 }
-.form-style ul li input[type="button"], 
 .form-style ul li input[type="submit"] {
     background-color: var(--bg-color);
     border: 1px solid var(--form-border-color);
@@ -1085,10 +1099,20 @@
     text-decoration: none;
     width: 100%;
 }
-.form-style ul li input[type="button"]:hover, 
 .form-style ul li input[type="submit"]:hover {
     background-color: var(--bg-color);
     border: 1px solid var(--form-button-hover-border-color);
 }
-
-/* (CONTACT) FORM END */
\ No newline at end of file
+.form-style .form-row {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    flex: 1;
+}
+.form-feedback {
+    text-align: center;
+}
+.form-feedback[feedback-success="false"] {
+    color: var(--error-color);
+}
+/* (CONTACT) FORM END */
diff --git a/i18n/de.toml b/i18n/de.toml
index 0ed8625..668e4b8 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "Kommentare"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Bitte geben Sie Ihren Namen an"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Bitte geben Sie eine gültige E-Mail Adresse ein"
+
+[message]
+other = "Nachricht"
+
+[message_error]
+other = "Bitte teilen Sie uns mit, warum Sie sich an uns wenden"
+
 [send]
-other = "Senden"
\ No newline at end of file
+other = "Senden"
+
+[form_captcha_error]
+other = "Captcha-Fehler; bitte versuchen Sie es später noch einmal"
+
+[form_error]
+other = "Fataler Fehler, Kontakt nicht hergestellt - bitte melden Sie sich auf anderem Wege"
+
+[form_success]
+other = "Thank für Ihre Kontaktaufnahme. Wir werden uns bald bei Ihnen melden."
diff --git a/i18n/dk.toml b/i18n/dk.toml
index 1ed7e77..18465f1 100644
--- a/i18n/dk.toml
+++ b/i18n/dk.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "kommentar"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Please provide your name"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Please enter a valid email address"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Please let us know why you are contacting us"
+
 [send]
-other = "Sende"
\ No newline at end of file
+other = "Sende"
+
+[form_captcha_error]
+other = "Captcha error; please try again later"
+
+[form_error]
+other = "Fatal error, contact not made - please reach out some other way"
+
+[form_success]
+other = "Thank you for reaching out! We will be touch soon"
diff --git a/i18n/en.toml b/i18n/en.toml
index db43452..e59793d 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "comments"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Please provide your name"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Please enter a valid email address"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Please let us know why you are contacting us"
+
 [send]
-other = "Send"
\ No newline at end of file
+other = "Send"
+
+[form_captcha_error]
+other = "Captcha error; please try again later"
+
+[form_error]
+other = "Fatal error, contact not made - please reach out in some other way"
+
+[form_success]
+other = "Thank you for reaching out! We will be touch soon"
diff --git a/i18n/es.toml b/i18n/es.toml
index a95542a..07db312 100644
--- a/i18n/es.toml
+++ b/i18n/es.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "comentarios"
 
+[name]
+other = "Nombre"
+
+[name_error]
+other = "Por favor, indique su nombre"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Por favor, introduzca una dirección de correo electrónico válida"
+
+[message]
+other = "Mensaje"
+
+[message_error]
+other = "Por favor, háganos saber por qué está contactando con nosotros"
+
 [send]
-other = "Enviar"
\ No newline at end of file
+other = "Enviar"
+
+[form_captcha_error]
+other = "Error de Captcha; por favor, inténtelo de nuevo más tarde"
+
+[form_error]
+other = "Error fatal, contacto no realizado - por favor, contacte de otra manera"
+
+[form_success]
+other = "¡Gracias por tender la mano! Pronto nos tocará"
diff --git a/i18n/fa.toml b/i18n/fa.toml
index 2911b2c..e65a259 100644
--- a/i18n/fa.toml
+++ b/i18n/fa.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "نظرات"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Please provide your name"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Please enter a valid email address"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Please let us know why you are contacting us"
+
 [send]
-other = "ارسال"
\ No newline at end of file
+other = "ارسال"
+
+[form_captcha_error]
+other = "Captcha error; please try again later"
+
+[form_error]
+other = "Fatal error, contact not made - please reach out some other way"
+
+[form_success]
+other = "Thank you for reaching out! We will be touch soon"
diff --git a/i18n/fi.toml b/i18n/fi.toml
index 622de2c..036fb7a 100644
--- a/i18n/fi.toml
+++ b/i18n/fi.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "kommentit"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Please provide your name"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Please enter a valid email address"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Please let us know why you are contacting us"
+
 [send]
-other = "Lähetä"
\ No newline at end of file
+other = "Lähetä"
+
+[form_captcha_error]
+other = "Captcha error; please try again later"
+
+[form_error]
+other = "Fatal error, contact not made - please reach out some other way"
+
+[form_success]
+other = "Thank you for reaching out! We will be touch soon"
diff --git a/i18n/fr.toml b/i18n/fr.toml
index b97fd5a..16d481d 100644
--- a/i18n/fr.toml
+++ b/i18n/fr.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "commentaire"
 
+[name]
+other = "Nom"
+
+[name_error]
+other = "Veuillez indiquer votre nom"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Veuillez saisir une adresse électronique valide"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Veuillez nous indiquer la raison pour laquelle vous nous contactez"
+
 [send]
-other = "Envoyer"
\ No newline at end of file
+other = "Envoyer"
+
+[form_captcha_error]
+other = "Erreur Captcha ; veuillez réessayer plus tard"
+
+[form_error]
+other = "Erreur fatale, contact non établi - veuillez prendre contact d'une autre manière"
+
+[form_success]
+other = "Merci de nous avoir contactés ! Nous vous contacterons bientôt"
diff --git a/i18n/it.toml b/i18n/it.toml
index 9f6020c..bc4eea1 100644
--- a/i18n/it.toml
+++ b/i18n/it.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "commenti"
 
+[name]
+other = "Nome"
+
+[name_error]
+other = "Si prega di fornire il proprio nome"
+
+[email]
+other = "e-mail"
+
+[email_error]
+other = "Inserire un indirizzo e-mail valido"
+
+[message]
+other = "Messaggio"
+
+[message_error]
+other = "Per favore, fateci sapere perché ci contattate"
+
 [send]
-other = "Spedire"
\ No newline at end of file
+other = "Spedire"
+
+[form_captcha_error]
+other = "Errore Captcha; si prega di riprovare più tardi"
+
+[form_error]
+other = "Errore fatale, contatto non effettuato - si prega di contattare in altro modo"
+
+[form_success]
+other = "Grazie per averci raggiunto! Ci sentiremo presto"
diff --git a/i18n/pt-br.toml b/i18n/pt-br.toml
index 2456fa9..477c939 100644
--- a/i18n/pt-br.toml
+++ b/i18n/pt-br.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "comentários"
 
+[name]
+other = "Nome"
+
+[name_error]
+other = "Por favor, forneça seu nome"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Por favor, digite um endereço de e-mail válido"
+
+[message]
+other = "Mensagem"
+
+[message_error]
+other = "Por favor nos informe por que você está entrando em contato conosco"
+
 [send]
 other = "Enviar"
+
+[form_captcha_error]
+other = "Captcha error; por favor, tente novamente mais tarde"
+
+[form_error]
+other = "Erro fatal, contato não feito - por favor, procure de outra forma"
+
+[form_success]
+other = "Obrigado por estender a mão! Em breve estaremos em contato"
diff --git a/i18n/zh-cn.toml b/i18n/zh-cn.toml
index 62c1293..d67558e 100755
--- a/i18n/zh-cn.toml
+++ b/i18n/zh-cn.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "评论"
 
+[name]
+other = "名称"
+
+[name_error]
+other = "请提供您的姓名"
+
+[email]
+other = "电子邮件"
+
+[email_error]
+other = "请输入有效的电子邮件地址"
+
+[message]
+other = "留言内容"
+
+[message_error]
+other = "请告诉我们您联系我们的原因。"
+
 [send]
 other = "发送"
+
+[form_captcha_error]
+other = "验证码错误,请稍后再试"
+
+[form_error]
+other = "致命的错误,没有取得联系--请以其他方式联系。"
+
+[form_success]
+other = "谢谢您的联系!我们会尽快与您联系。我们会尽快联系您"
diff --git a/i18n/zh-tw.toml b/i18n/zh-tw.toml
index 87e7e69..c63526f 100644
--- a/i18n/zh-tw.toml
+++ b/i18n/zh-tw.toml
@@ -20,5 +20,32 @@
 [comments]
 other = "註解"
 
+[name]
+other = "Name"
+
+[name_error]
+other = "Please provide your name"
+
+[email]
+other = "Email"
+
+[email_error]
+other = "Please enter a valid email address"
+
+[message]
+other = "Message"
+
+[message_error]
+other = "Please let us know why you are contacting us"
+
 [send]
-other = "發送"
\ No newline at end of file
+other = "發送"
+
+[form_captcha_error]
+other = "Captcha error; please try again later"
+
+[form_error]
+other = "Fatal error, contact not made - please reach out some other way"
+
+[form_success]
+other = "Thank you for reaching out! We will be touch soon"
diff --git a/layouts/_default/list.html b/layouts/_default/list.html
index cad95c8..b502037 100644
--- a/layouts/_default/list.html
+++ b/layouts/_default/list.html
@@ -1,5 +1,5 @@
 {{ define "main" }}
-    <div class="archive {{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+    <div class="archive {{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
         <ul class="list-with-title">
             {{ range .Data.Pages.GroupByDate "2006" }}
                 <div class="listing-title">{{ .Key }}</div>
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index b4ff807..f7e360b 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -1,5 +1,5 @@
 {{ define "main" }}
-    <div class="post {{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+    <div class="post {{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
         <div class="post-content">
             {{ if .Params.thumbnail }}
             <img class="post-thumbnail" src="{{ .Params.thumbnail | absURL }}" alt="Thumbnail image">
diff --git a/layouts/index.html b/layouts/index.html
index c957852..f0d2ae9 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -1,6 +1,6 @@
 {{ define "main" }}
 
-    <div class="post {{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+    <div class="post {{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
         <!-- (Optional) Home
             -- on top of `mainSections` content (aka posts) ;
             -- as declared in content/_index.md
@@ -13,7 +13,7 @@
     {{ $paginator := .Paginate (where .Site.RegularPages "Type" "in" .Site.Params.mainSections) }}
     {{ range $paginator.Pages }}
 
-        <div class="post {{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+        <div class="post {{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
             {{ if .Params.thumbnail }}
             <div class="post-thumbnail">
                 <a href="{{ .RelPermalink }}">
diff --git a/layouts/partials/contact.html b/layouts/partials/contact.html
index e598114..53c33b1 100644
--- a/layouts/partials/contact.html
+++ b/layouts/partials/contact.html
@@ -1,19 +1,115 @@
+{{ $jquery := resources.Get "js/jquery.js" }}
+<script type="text/javascript" src="{{ $jquery.Permalink }}"></script>
 
 <div class="contact-form">
-  <form class="form-style" method="POST" action="{{ .Site.Params.contactFormAction }}" data-toggle="validator">
+  <form id="contact-form" name="Contact Form" class="form-style" method="POST" action="{{ if eq .Site.Params.contactFormType "netlify" }}/{{ else }}{{ .Site.Params.contactFormAction }}{{ end }}"
+    {{ with .Site.Params.contactFormReCaptchaSiteKey }} data-netlify-recaptcha="true"{{ end }}
+    {{ if eq .Site.Params.contactFormType "netlify" }} data-netlify="true" netlify-honeypot="my-lovely-house"{{ end }}>
     <ul>
       <li>
-        <input class="field-style field-full" type="text" name="username" id="username" placeholder="Name" required>
+        <input class="field-style field-full" type="text" name="username" id="username" placeholder="{{ i18n "name" }}" oninvalid="setCustomValidity('{{ i18n "name_error" }}')" oninput="setCustomValidity('')" required>
       </li>
       <li>
-        <input class="field-style field-full" type="email" id="email" name="email" placeholder="Email" required>
+        <input class="field-style field-full" type="email" name="email" id="email" placeholder="{{ i18n "email" }}" pattern="^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" oninvalid="setCustomValidity('{{ i18n "email_error" }}')" oninput="setCustomValidity('')" required>
       </li>
       <li>
-        <textarea class="field-style" name="message" id="message" rows="6" placeholder="{{ i18n "message" }}"></textarea>
+        <textarea class="field-style" name="message" id="message" rows="6" placeholder="{{ i18n "message" }}" oninvalid="setCustomValidity('{{ i18n "message_error" }}')" oninput="setCustomValidity('')" required></textarea>
       </li>
       <li>
-        <input class="field-style" type="submit" value="{{ i18n "send" }}"></input>
+        <input class="field-style" id="form-submit" type="submit" value="{{ i18n "send" }}">
       </li>
+      <li>
+        <div class="form-feedback" id="submit-feedback"></div>
+      </li>
+      {{ if eq .Site.Params.contactFormType "netlify"}}
+        <input name="form-name" value="Contact Form" type="hidden">
+        <input class="field-style" name="my-lovely-house" id="my-lovely-house" type="hidden">
+      {{ end }}
+      {{ with .Site.Params.contactFormReCaptchaSiteKey }}
+      <li>
+        <div id="g-recaptcha"></div>
+      </li>
+      {{ end }}
     </ul>
   </form>
 </div>
+
+<script>
+  // Sets feedback message.
+  function setFeedback(message = '', ok = true) {
+    const feedback = $('#submit-feedback');
+
+    feedback.attr('feedback-success', ok);
+    feedback.text(message);
+  }
+
+  // This hanlder takes care of submission post-captcha.
+  function onSubmit() {
+    // Verify if we have a recaptcha at all before checking.
+    if($('.g-recaptcha')[0] != null &&
+      grecaptcha.getResponse().length == 0) { 
+      setFeedback('{{ i18n "form_captcha_error" }}', false);
+    } else {
+      // Formspree requires json; ajax will do the conversion.
+      const form = $('#contact-form');
+      const message = form.serialize();
+      const dataType = ('{{ .Site.Params.ContactFormType }}' === 'netlify')
+        ? 'application/x-www-form-urlencoded' : 'json';
+
+      $.ajax({
+          url: form.prop('action'),
+          method: 'POST',
+          data: message,
+          dataType,
+          complete: (xhr, status) => {
+            // Netlify asks for urlencoded data but sends back HTML. This causes a
+            // data type mismatch that Ajax flags as an error ... I believe the solution is to just
+            // screen for the false negative in this combined handler.
+            // https://stackoverflow.com/questions/16230624/ajax-call-fires-error-event-but-returns-200-ok/16230794
+            if (status === 'error' && xhr.status !== 200) {
+              setFeedback('{{ i18n "form_error" }}', false);
+            } else {
+              setFeedback('{{ i18n "form_success" }}');
+            }
+          },
+      });
+
+      // Enable button.
+      $('#form-submit').prop('disabled', false);
+    }
+  }
+
+  // Initial contact form submit is disabled, and optionally kicks off captcha.
+  $('#contact-form').submit((e) => {
+    // We're doing the submission to avoid the redirect for free Formspree accounts.
+    e.preventDefault();
+    setFeedback('');
+    // Disable submit button for the time being,
+    $('#form-submit').prop('disabled', true);
+  
+    // Make sure we have a captcha to execute before trying to do so.
+    if ($('.g-recaptcha')[0] !== null) {
+      grecaptcha.execute();
+    } else {
+      onSubmit();
+    }
+  });
+</script>
+
+{{ with .Site.Params.contactFormReCaptchaSiteKey }}
+  <script>
+      // Captcha rendered dynamically to deal with some rendering issues with
+      // theme and with animation.
+      function captchaLoadCallback() {
+        grecaptcha.render( 'g-recaptcha', {
+          sitekey: '{{ . }}',
+          callback: onSubmit,
+          size: 'invisible',
+          badge: 'bottomright',
+          theme: $('html').attr('data-theme'),
+        });
+      }
+  </script>
+  <!-- Explicit rendering of recaptcha to deal with data-theme and animation issues -->
+  <script type="text/javascript" src="https://www.google.com/recaptcha/api.js?onload=captchaLoadCallback&render=explicit" async defer></script>
+{{ end }}
diff --git a/layouts/partials/navbar.html b/layouts/partials/navbar.html
index 9c4c28e..5d2cfed 100644
--- a/layouts/partials/navbar.html
+++ b/layouts/partials/navbar.html
@@ -1,4 +1,4 @@
-<div class="page-top {{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+<div class="page-top {{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
     <a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false">
         <span aria-hidden="true"></span>
         <span aria-hidden="true"></span>
diff --git a/layouts/partials/sidebar.html b/layouts/partials/sidebar.html
index 22afa5d..39c5133 100644
--- a/layouts/partials/sidebar.html
+++ b/layouts/partials/sidebar.html
@@ -1,4 +1,4 @@
-<div class="sidebar{{ with .Site.Params.doNotLoadAnimations }} . {{ else }} animated fadeInDown {{ end }}">
+<div class="sidebar{{ if and (or (not (isset .Site.Params "doNotLoadAnimations")) (and (isset .Site.Params "doNotLoadAnimations") (not .Site.Params.doNotLoadAnimations))) (or (not (isset .Page.Params "animation")) .Page.Params.animation) }} animated fadeInDown {{ end }}">
     <div class="logo-title">
         <div class="title">
             <img src="{{ .Site.Params.profilePicture | absURL }}" alt="profile picture">

--
Gitblit v1.10.0