diff --git a/app/index.js b/app/index.js
index aec98bc4..b3822e21 100644
--- a/app/index.js
+++ b/app/index.js
@@ -44,13 +44,18 @@ app.get('/example/', function (req, res) {
app.post('/',
upload.single('theme'),
function (req, res, next) {
- var zip = {
+ const zip = {
path: req.file.path,
name: req.file.originalname
};
+ const options = {
+ checkVersion: req.body.version || 'latest'
+ };
+
debug('Uploaded: ' + zip.name + ' to ' + zip.path);
+ debug('Version to check: ' + options.checkVersion);
- gscan.checkZip(zip)
+ gscan.checkZip(zip, options)
.then(function processResult(theme) {
debug('Checked: ' + zip.name);
res.theme = theme;
@@ -67,8 +72,11 @@ app.post('/',
});
},
function doRender(req, res) {
+ const options = {
+ checkVersion: req.body.version || 'latest'
+ };
debug('Formatting result');
- var result = gscan.format(res.theme);
+ const result = gscan.format(res.theme, options);
debug('Rendering result');
scanHbs.handlebars.logger.level = 0;
res.render('result', result);
diff --git a/app/public/gscan.css b/app/public/gscan.css
index 9de20db1..640a02ac 100644
--- a/app/public/gscan.css
+++ b/app/public/gscan.css
@@ -1 +1 @@
-a,abbr,acronym,address,applet,article,aside,audio,big,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,ul,var,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:"";content:none}img{max-width:100%}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}*,:after,:before{box-sizing:inherit}a{background-color:transparent}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;color:inherit;font:inherit}button{overflow:visible;border:none}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input:focus{outline:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{padding:0;border:0}textarea{overflow:auto}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}html{overflow-y:scroll;font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{overflow-x:hidden}body{color:#52636b;font-family:Whitney SSm A,Whitney SSm B,sans-serif;font-size:1.45rem;line-height:1.6em;font-weight:400;font-style:normal;letter-spacing:0;text-rendering:optimizeLegibility;background:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-moz-font-feature-settings:"liga" on}::-moz-selection{text-shadow:none;background:#cbeafb}::selection{text-shadow:none;background:#cbeafb}hr{display:block;margin:2em 0;padding:0;height:1px;border:0;border-top:1px solid #e5eff5}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{margin:0;padding:0;border:0}blockquote,dl,ol,p,ul{margin:0 0 1.5em}ol,ul{padding-left:2.5em}ol ol,ol ul,ul ol,ul ul{margin:.5em 0 1em;padding-left:2em}ul{list-style:disc}ol{list-style:decimal}li{margin:.5em 0;line-height:1.6em}dt{float:left;margin:0 20px 0 0;width:120px;color:#343f44;font-weight:500;text-align:right}dd{margin:0 0 5px;text-align:left}blockquote{margin:1.6em 0;padding:0 1.6em;border-left:.5em solid #e5eff5}blockquote p{margin:.8em 0;font-size:1.2em;font-weight:300}blockquote small{display:inline-block;margin:.8em 0 .8em 1.5em;font-size:.9em;opacity:.8}blockquote small:before{content:"\2014 \00A0"}blockquote cite{font-weight:700}blockquote cite a{font-weight:400}a{color:#26a8ed;text-decoration:none;transition:all .5s ease}a:hover{text-decoration:underline;transition:all .2s ease}.hidden{display:none!important}.visuallyhidden{position:absolute;overflow:hidden;clip:rect(0 0 0 0);margin:-1px;padding:0;width:1px;height:1px;border:0}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{position:static;overflow:visible;clip:auto;margin:0;width:auto;height:auto}.invisible{visibility:hidden}.sr-only{position:absolute;overflow:hidden;clip:rect(0,0,0,0);margin:-1px;padding:0;width:1px;height:1px;border:0}.sr-only-focusable:focus{z-index:4;overflow:visible;clip:auto;margin:0;padding:0 10px;width:auto;height:auto;color:#333;line-height:49px;font-weight:700;text-decoration:none;background-color:#f5f5f5}.right{float:right}.left{float:left}.clearfix:after,.clearfix:before{content:" ";display:table}.clearfix:after{clear:both}.gh-table{box-sizing:border-box;margin:1.6em 0;max-width:100%;width:100%;background-color:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box}.gh-table td,.gh-table th{padding:8px;border-top:1px solid #e2edf2;line-height:20px;text-align:left;vertical-align:top}.gh-table th{color:#7d878a}.gh-table caption+thead tr:first-child td,.gh-table caption+thead tr:first-child th,.gh-table colgroup+thead tr:first-child td,.gh-table colgroup+thead tr:first-child th,.gh-table thead:first-child tr:first-child td,.gh-table thead:first-child tr:first-child th{border-top:0}.gh-table tbody+tbody{border-top:2px solid #e2edf2}.gh-table .gh-table .gh-table{background-color:#fff}.gh-table tbody>tr:nth-child(odd)>td,.gh-table tbody>tr:nth-child(odd)>th{background-color:#f4f8fa}.gh-table.plain tbody>tr:nth-child(odd)>td,.gh-table.plain tbody>tr:nth-child(odd)>th{background:transparent}h1,h2,h3,h4,h5,h6{margin-top:0;color:#242628;line-height:1.15;font-weight:800;text-rendering:optimizeLegibility}h1{margin:0 0 .5em;font-size:2.6rem;font-weight:600}@media (max-width:500px){h1{font-size:2.2rem}}h2{margin:1.5em 0 .5em;font-size:2rem;font-weight:500}@media (max-width:500px){h2{font-size:1.8rem}}h3{margin:1.5em 0 .5em;font-size:1.8rem;font-weight:500}@media (max-width:500px){h3{font-size:1.7rem}}h4{margin:1.5em 0 .5em;font-size:1.6rem;font-weight:500}h5,h6{margin:1.5em 0 .5em;font-size:1.4rem;font-weight:500}.supermassive-h1{margin-top:40px;color:#fa3a57;font-size:12vw;font-weight:100}p code{padding:0 5px 2px;line-height:1;font-weight:400!important;background:#d0ecfb;border-radius:4px}.gh-anchor{color:inherit}.gh-anchor,.gh-anchor:hover{text-decoration:none}.gh-header{padding:30px 0 60px;text-align:center}.gh-header h1,.gh-mainhead{margin:0;font-size:4.4rem;font-weight:300}.gh-mainhead{color:inherit;letter-spacing:-2px}.gh-subhead{margin:.3em 0 0;font-size:2rem;line-height:1.3em;opacity:.7}@media (max-width:660px){.gh-header{padding:5vw 10vw}.gh-header h1,.gh-mainhead{font-size:6vw}.gh-subhead{font-size:1.6rem}}p+p>.gh-morelink{position:relative;top:-10px}.gh-morelink svg{margin-left:3px;height:.5em}.gh-badge{display:inline-block;padding:2px 3px 3px;border:1px solid #97bd38;color:#fff;font-size:.8em;line-height:1em;font-weight:600;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,.1);white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:linear-gradient(#a9d142,#9ec738);border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.gh-badge-blue{border:1px solid #3da4db;background:linear-gradient(#57baf0,#3dabe6)}.gh-badge-outline{border-color:#dae0e2;color:#bdc7cc;font-weight:400;background:transparent;box-shadow:none}.gh-customerlogos{opacity:.7}.gh-signupbox-section{border-top:1px solid #edf4f8;border-bottom:1px solid #edf4f8;background:linear-gradient(#f8fafc,#f4f8fb)}.gh-signupbox{-ms-flex-pack:justify;justify-content:space-between;margin:0 -20px;padding:20px;font-size:1.6rem;line-height:1.3em;background:#fff;border-radius:6px;box-shadow:0 0 1px rgba(0,0,0,.1),0 2px 6px rgba(0,0,0,.03)}.gh-signup-actions,.gh-signupbox{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-signup-actions{-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0;margin-left:20px;max-width:300px}.gh-signup-actions .gh-btn{-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.gh-signup-actions .gh-btn-blue{-ms-flex-positive:1;flex-grow:1;margin-right:10px}.gh-signup-actions .gh-btn-blue span{width:100%;text-align:center}@media (max-width:1040px){.gh-signupbox{margin:0}.gh-signupbox-text-extension{display:none}}@media (max-width:620px){.gh-signupbox{-ms-flex-direction:column;flex-direction:column;text-align:center}.gh-signup-actions{margin:15px 0 0;max-width:none;width:100%}}.gh-ghostpro{display:inline-block;white-space:nowrap}.gh-ghostpro svg{height:31px}.gh-ghostpro .gh-badge{margin-left:4px;font-size:1.2rem;text-transform:uppercase}.gh-btn{display:inline-block;outline:none;border:1px solid #ccdfeb;color:#738a94;text-decoration:none!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;fill:#738a94;border-radius:5px;transition:none;-webkit-font-smoothing:subpixel-antialiased}.gh-btn span{display:block;padding:0 14px;height:37px;font-size:1.3rem;line-height:37px;font-weight:400;text-align:center;border-radius:4px}.gh-btn.disabled,.gh-btn[disabled],fieldset[disabled] .gh-btn{box-shadow:none;opacity:.8;cursor:not-allowed;pointer-events:none}.gh-btn-blue{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#3da1d6,#2288bf);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4fb7f0,#29a0e0 60%,#29a0e0 90%,#36a6e2);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-green{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#99bf38,#83a333);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-green span{background:linear-gradient(#a9d145,#97bc38 60%,#97bc38 90%,#96ba3b);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-green:active,.gh-btn-green:focus{background:#83a333}.gh-btn-green:active span,.gh-btn-green:focus span{background:#97bc38;box-shadow:none}.gh-btn-red{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#d64f30,#b33a1e);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-red span{background:linear-gradient(#f06242,#dc411e 60%,#dc411e 90%,#e24a28);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-red:active,.gh-btn-red:focus{background:#9d331b}.gh-btn-red:active span,.gh-btn-red:focus span{background:#dc411e;box-shadow:none}.gh-btn-black{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#323232,#171717);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-black span{background:linear-gradient(#454545,#2a2c2d 60%,#2a2c2d 90%,#313435);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-black:active,.gh-btn-black:focus{background:#0a0a0a}.gh-btn-black:active span,.gh-btn-black:focus span{background:#2a2c2d;box-shadow:none}.gh-btn-grey{padding:1px;border:0;color:#343f44;text-shadow:0 1px 0 #fff;fill:#343f44;background:linear-gradient(#ced9df,#bdc7cc);box-shadow:0 1px 0 rgba(0,0,0,.05);transition:none!important}.gh-btn-grey span{background:linear-gradient(#f8fafc,#f6f8f9);box-shadow:inset 0 1px 0 #fff}.gh-btn-grey:active,.gh-btn-grey:focus{background:#bdc7cc}.gh-btn-grey:active span,.gh-btn-grey:focus span{background:#f3f5f7;box-shadow:none}.gh-btn-white{background:#fff}.gh-btn-white span{height:38px}.gh-btn-block{display:block;width:100%}.gh-btn-icon span{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-btn-icon svg{margin-right:10px;height:15px}.gh-spinner{position:relative;display:inline-block;width:18px;height:18px;border:4px solid rgba(0,0,0,.2);border-radius:100%;animation:a 1s linear infinite}.gh-spinner:before{content:"";display:block;margin-top:9px;width:4px;height:4px;background:rgba(0,0,0,.6);border-radius:100%}@keyframes a{0%{transform:rotate(0deg)}50%{transform:rotate(180deg)}to{transform:rotate(1turn)}}.gh-btn-group{margin-top:3rem}.gh-btn-group .gh-btn:first-of-type{margin-right:15px}.gh-btn-group .gh-btn span{font-size:1.4rem}.gh-form-group{position:relative;margin-bottom:1.6em;max-width:500px;width:100%;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.gh-form-group p{margin:4px 0 0;color:#b1b1b1;font-size:1.3rem}.gh-form-group h3{margin-bottom:1.6em;font-size:1.5rem}.gh-form-group label{margin-bottom:4px}.gh-form-group-horizontal{display:-ms-flexbox;display:flex}.gh-form-group-horizontal .gh-form-group+.gh-form-group{margin-left:15px}@media (max-width:550px){.gh-form-group{max-width:100%}}.gh-input-icon{position:relative}.gh-input-icon input{padding-left:35px}.gh-input-icon svg{position:absolute;top:50%;left:12px;z-index:2;height:14px;fill:#a6bac5;transform:translateY(-7px)}.gh-input-icon-lock svg{transform:translateY(-8px)}.gh-input,.gh-select,select{display:block;padding:12px;width:100%;border:1px solid #dae2e7;color:#738a94;font-size:1.3rem;line-height:1em;font-weight:400;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;border-radius:5px;transition:border-color .15s linear;-webkit-appearance:none}.gh-select,select{cursor:pointer}textarea{min-width:250px;min-height:10rem;max-width:500px;width:100%;height:auto;line-height:1.5;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;resize:vertical}textarea.gh-input{line-height:1.5em}.gh-input.focus,.gh-input:focus,.gh-select:focus,select:focus,textarea:focus{outline:0;border-color:#becdd5}.for-checkbox label,.for-radio label{display:block;padding-bottom:4px;cursor:pointer}.for-checkbox label p,.for-radio label p{overflow:auto;color:#000;font-weight:400}.for-checkbox label:hover p,.for-radio label:hover p{color:#738a94}.for-checkbox input,.for-radio input{position:absolute;top:0;right:0;bottom:0;left:-9999px}.for-checkbox .input-toggle-component,.for-radio .input-toggle-component{position:relative;top:1px;display:inline-block;float:left;margin-right:7px;width:18px;height:18px;border:1px solid #dfe1e3;background:#f7f7f7}.for-checkbox label:hover input:not(:checked)+.input-toggle-component,.for-radio label:hover input:not(:checked)+.input-toggle-component{border-color:#c5d2d9}.for-checkbox .input-toggle-component{border-radius:5px;transition:background .15s ease-in-out,border-color .15s ease-in-out}.for-checkbox .input-toggle-component:before{content:"";position:absolute;top:4px;left:3px;width:10px;height:6px;border:2px solid #fff;border-top:none;border-right:none;opacity:0;transition:opacity .15s ease-in-out;transform:rotate(-45deg)}.for-checkbox input:checked+.input-toggle-component{border-color:#88ae29;background:#a4d037}.for-checkbox input:checked+.input-toggle-component:before{opacity:1}.for-radio .input-toggle-component{border-radius:100px;transition:background .15s ease-in-out,border-color .15s ease-in-out}.for-radio .input-toggle-component:before{content:"";position:absolute;top:4px;left:4px;width:8px;height:8px;background:#fff;border-radius:100%;opacity:0;transition:opacity .15s ease-in-out}.for-radio input:checked+.input-toggle-component{border-color:#88ae29;background:#a4d037}.for-radio input:checked+.input-toggle-component:before{opacity:1}.gh-select{position:relative;display:block;overflow:hidden;padding:0;max-width:100%;width:100%;border-width:0}.gh-select select{padding:8px 10px;outline:none;line-height:normal;text-indent:.01px;text-overflow:"";background:#fff;appearance:none;-webkit-appearance:none;-moz-appearance:window}.gh-select select::-ms-expand{display:none}.gh-select select:-moz-focusring{color:transparent;text-shadow:0 0 0 #000}.gh-error .gh-input{border-color:#f05230}.gh-error .gh-input-icon svg{fill:#f05230}.gh-error-message{display:block;margin:10px 0 0;color:#f05230;font-size:1.2rem;line-height:1.4em}.gh-success .gh-input{border-color:#a4d037}.gh-success .gh-input-icon svg{fill:#8eb62b}.hamburger{display:-ms-flexbox;display:flex;overflow:visible;margin:0;padding:2px 0;border:0;color:inherit;font:inherit;text-transform:none;background-color:transparent;cursor:pointer;transition:opacity .15s linear,filter .15s linear}.hamburger-box{position:relative;display:inline-block;width:20px;height:13px}.hamburger-inner{top:50%;display:block;margin-top:-2px}.hamburger-inner,.hamburger-inner:after,.hamburger-inner:before{position:absolute;width:20px;height:1px;background-color:#738a94;border-radius:4px;transition:transform .15s ease}.hamburger-inner:after,.hamburger-inner:before{content:"";display:block}.hamburger-inner:before{top:-6px}.hamburger-inner:after{bottom:-6px}.hamburger--collapse .hamburger-inner{top:auto;bottom:0;transition-delay:.15s;transition-timing-function:cubic-bezier(.55,.055,.675,.19);transition-duration:.15s}.hamburger--collapse .hamburger-inner:after{top:-12px;transition:top .3s cubic-bezier(.33333,.66667,.66667,1) .3s,opacity .1s linear}.hamburger--collapse .hamburger-inner:before{transition:top .12s cubic-bezier(.33333,.66667,.66667,1) .3s,transform .15s cubic-bezier(.55,.055,.675,.19)}.gh-mobilehead-open .hamburger-inner,.gh-mobilehead-open .hamburger-inner:after,.gh-mobilehead-open .hamburger-inner:before{background-color:#343f44}.gh-mobilehead-open .hamburger-inner{transition-delay:.32s;transition-timing-function:cubic-bezier(.215,.61,.355,1);transform:translate3d(0,-6px,0) rotate(-45deg)}.gh-mobilehead-open .hamburger-inner:after{top:0;opacity:0;transition:top .3s cubic-bezier(.33333,0,.66667,.33333),opacity .1s linear .27s}.gh-mobilehead-open .hamburger-inner:before{top:0;transition:top .12s cubic-bezier(.33333,0,.66667,.33333) .18s,transform .15s cubic-bezier(.215,.61,.355,1) .42s;transform:rotate(-90deg)}.gh-viewport{position:relative}.inner{margin:0 auto;max-width:1030px;width:100%}.gh-head{position:relative;z-index:4;margin-bottom:40px;padding:0 4vw;width:100%;border-bottom:1px solid #e5eff5;background:hsla(0,0%,100%,.97)}@media (max-width:600px){.gh-head{padding-right:15px;padding-left:15px}}.gh-navbar{-ms-flex-pack:justify;justify-content:space-between;height:55px;font-size:13px}.gh-navbar,.gh-navbar-left,.gh-navbar-right{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-nav-logo{-ms-flex-negative:0;flex-shrink:0;display:-ms-flexbox!important;display:flex!important;-ms-flex-align:center;align-items:center;margin-left:-12px;color:#738a94}.gh-nav-item.gh-nav-logo:hover{color:inherit}.gh-nav-logo .ghost-svg{width:auto;height:25px}.gh-nav-logo-suffix{position:relative;margin:0 0 0 12px;padding:0 0 0 11px;font-size:1.6rem;font-weight:400;text-shadow:none;transition:none}.gh-nav-logo-suffix:before{content:"";position:absolute;top:0;left:0;display:block;width:1px;height:23px;background:#d3e4ee;transform:rotate(25deg)}.gh-navbar-item{-ms-flex-negative:0;flex-shrink:0;display:block;padding:10px 12px;color:#738a94;font-weight:500;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-drop.active .gh-drop-trigger,.gh-navbar-item-active,.gh-navbar-item:hover{color:#343f44;text-decoration:none}.gh-navbar-item:hover .gh-nav-logo-suffix{color:inherit}.gh-more-drop svg{margin-left:3px;width:7px;height:auto}.gh-more-drop .gh-dropdown{right:-73px;width:146px;font-size:1.2rem;text-shadow:none}.gh-more-drop .gh-dropdown:after{right:66px}.gh-more-drop .gh-dropdown:before{right:64px}.gh-more-drop .gh-badge{font-size:1rem}.gh-navbar-btn{margin-left:10px;color:#738a94;transition:all .5s ease}.gh-navbar-btn span{padding:8px 10px;min-width:66px;height:auto;line-height:1;text-align:center}.gh-navbar-btn:hover{border-color:#3eb0ef;color:#3da1d6;transition:all .2s ease}.gh-signin-drop svg{margin-left:5px;width:7px;height:auto}.gh-head-transparent .gh-head{border:none;text-shadow:rgba(0,0,0,.1) 0 1px 1px;background:transparent}.gh-head-transparent .gh-head .ghost-svg g,.gh-head-transparent .gh-head .ghost-svg path{fill:#fff}.gh-head-transparent .gh-head .gh-navbar-item{color:hsla(0,0%,100%,.85)}.gh-head-transparent .gh-head .gh-drop.active .gh-drop-trigger,.gh-head-transparent .gh-head .gh-navbar-item:hover{color:#fff}.gh-head-transparent .gh-head .gh-more-drop .gh-dropdown{top:110%;box-shadow:0 3px 8px rgba(0,0,0,.15),0 0 2px rgba(0,0,0,.2)}.gh-head-transparent .gh-head .gh-navbar-btn{border-color:hsla(0,0%,100%,.6);color:hsla(0,0%,100%,.8)}.gh-head-transparent .gh-head .gh-navbar-btn:hover{border-color:#fff;color:#fff}.gh-head-transparent .gh-hero{margin-top:calc(-6vw - 55px)}.gh-head-transparent .gh-hero .gh-btn-outline{border-color:hsla(0,0%,100%,.7);color:hsla(0,0%,100%,.8);transition:all .5s ease}.gh-head-transparent .gh-hero .gh-btn-outline:hover{border-color:#fff;color:#fff;transition:all .15s ease}.gh-mobilehead{position:absolute;top:0;left:0;z-index:3;display:none;overflow:hidden;width:100%;height:48px;border-bottom:1px solid rgba(0,0,0,.05);background:hsla(0,0%,100%,.97);box-shadow:inset 0 -1px 0 0 #fff,0 1px 5px rgba(0,0,0,.02);transition:all .5s ease-out,background 1s ease-out;transition-delay:.2s;-webkit-backdrop-filter:blur(3px)}.gh-mobilenavbar{position:relative;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:center;align-items:center;height:48px;font-size:13px}.gh-nav-burger{display:block;padding:15px;color:#738a94;font-size:25px;line-height:1}.gh-nav-burger,.gh-nav-burger:hover{text-decoration:none}.gh-mobilenavbar .gh-nav-logo{position:absolute;top:0;right:50%;display:block;margin-right:-33px;padding:13px 0}.gh-mobilenavbar .ghost-svg{width:auto;height:23px}.gh-mobilenavbar .gh-login-link{display:block;padding:14px 15px;font-size:1.5rem}.gh-mobilenavbar .gh-login-link:hover{text-decoration:none}.gh-mobilemenu{padding:2.7vw 14vw}.gh-mobilemenu a{display:block;border-bottom:1px solid #e5eff5;color:#738a94;font-size:1.8rem;line-height:4.8rem;font-weight:200;opacity:0;transition:transform .3s cubic-bezier(.4,.01,.165,.99),opacity .2s cubic-bezier(.4,.01,.165,.99);transition-delay:.4s;transform:scale(1.1) translateY(-25px)}.gh-mobilemenu a:first-child{transition-delay:.56s}.gh-mobilemenu a:nth-child(2){transition-delay:.49s}.gh-mobilemenu a:nth-child(3){transition-delay:.42s}.gh-mobilemenu a:nth-child(4){transition-delay:.35s}.gh-mobilemenu a:nth-child(5){transition-delay:.28s}.gh-mobilemenu a:nth-child(6){transition-delay:.21s}.gh-mobilemenu a:nth-child(7){transition-delay:.14s}.gh-mobilemenu a:nth-child(8){transition-delay:.07s}.gh-mobilemenu a:hover{color:#3eb0ef;text-decoration:none}.gh-mobilemenu a.active{color:#343f44;font-weight:400}.gh-mobilehead-open{position:fixed;top:0;right:0;left:0;z-index:5;overflow:hidden;margin:0;height:100vh;transition:all .3s ease-in,background .5s ease-in;transition-delay:.3s}.gh-mobilehead-open .gh-mobilemenu a{opacity:1;transition:transform .6s cubic-bezier(.4,.01,.165,.99),opacity .9s cubic-bezier(.4,.01,.165,.99);transform:scale(1) translateY(0)}.gh-mobilehead-open .gh-mobilemenu a:nth-child(8){transition-delay:.56s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(7){transition-delay:.49s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(6){transition-delay:.42s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(5){transition-delay:.35s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(4){transition-delay:.28s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(3){transition-delay:.21s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(2){transition-delay:.14s}.gh-mobilehead-open .gh-mobilemenu a:first-child{transition-delay:.07s}@media (max-width:660px){.gh-viewport{padding-top:68px}.gh-head{display:none}.gh-mobilehead{display:block}}.gh-main{padding:0 4vw}.gh-section{margin:0 -4vw;padding:60px 4vw}@media (max-width:600px){.gh-section{padding:10vw 4vw}}.gh-section-dark{color:#fff;text-shadow:rgba(0,0,0,.1) 0 1px 2px}.gh-section-dark h2{color:#fff}.gh-section-dark h3{color:#fff;font-size:1.8rem}.gh-section-dark p{opacity:.8}.gh-section-dark a{color:#5fbef2}.gh-section-dark a.gh-anchor{color:#fff}.gh-section h3 .gh-badge{margin-left:4px;padding:2px;padding-bottom:2px;font-size:.7em;vertical-align:top}.gh-section-head{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;margin-bottom:1em;text-align:center}.gh-section-head h2{margin:1em 0 .3em;font-size:2.6rem;font-weight:400}@media (max-width:660px){.gh-section-head h2{font-size:4.8vw}}.gh-section-head p{margin:0;font-size:2rem;line-height:1.4em;font-weight:300;opacity:.8}@media (max-width:660px){.gh-section-head p{font-size:1.6rem}}.gh-1col{margin:0 auto;max-width:720px;font-size:1.8rem;line-height:1.65em}@media (max-width:660px){.gh-1col{font-size:1.6rem}}.gh-grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between;margin:0 -40px -10px;padding:0 20px}.gh-grid-item{-ms-flex:1 0 300px;flex:1 0 300px;padding:20px}@media (max-width:934px){.gh-grid-item{padding:5px 20px}}@media (max-width:620px){.gh-grid{-ms-flex-direction:column;flex-direction:column;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.gh-grid-item{position:relative;-ms-flex:1 0 auto;flex:1 0 auto;padding:0 20px 0 65px}.gh-grid-item svg{position:absolute;top:9px;left:23px;width:24px;height:auto}}.gh-grid-item svg{height:30px;fill:#ad26b4}.gh-grid-blue .gh-grid-item svg{fill:#3eb0ef}.gh-grid-green .gh-grid-item svg{fill:#a4d037}.gh-grid-backups svg{margin-bottom:-3px;height:33px}.gh-grid-seo svg{margin-top:2px;margin-bottom:2px;height:26px}.gh-grid-item h3{margin:1em 0 .3em;font-size:1.6rem}.gh-biggrid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between;margin:4vw auto 0;padding:0 20px}.gh-biggrid-item{position:relative;-ms-flex:1 0 300px;flex:1 0 300px;margin:15px 30px;padding:0 0 0 70px;font-size:1.6rem}.gh-biggrid-item h3{margin:0 0 5px;color:#fecd35}.gh-biggrid-item svg{position:absolute;top:0;left:0;width:40px;height:auto;fill:#fff}.gh-biggrid-item a{color:#fff;font-weight:500;text-decoration:underline}.gh-foot{padding:3vw 4vw 5vw;font-size:1.3rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#fff}.gh-foot a{color:#738a94;text-decoration:none}.gh-foot a:hover{color:#343f44;text-decoration:none}.gh-foot-content{padding-top:20px;border-top:1px solid #e5eff5}.gh-foot-content,.gh-foot-nav{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.gh-foot-nav{padding-left:60px;width:100%}.gh-foot-col{padding:0 10px}.gh-foot-col h4{margin:5px 0 10px;font-size:1.4rem}.gh-foot-col ul{margin:0;padding:0;list-style:none}.gh-foot-col ul a{display:block;padding:3px 0;min-width:90px}.gh-foot-col li{margin:0}.gh-foot-col .gh-badge{position:relative;top:-1px}.gh-foot-mast{position:relative;-ms-flex:0 0 300px;flex:0 0 300px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start}.gh-foot-mast svg{display:inline-block;width:auto;height:25px}.gh-foot-mast-text{margin:10px 0;color:#738a94;line-height:1.5em}.gh-langswitch .gh-drop-trigger{display:inline-block;padding:4px 8px 4px 0;color:#26a8ed}.gh-langswitch .gh-drop-trigger span{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-langswitch svg{margin-left:4px;width:7px;height:auto}.gh-langswitch .gh-dropdown{right:-65px;width:130px}.gh-langswitch .gh-dropdown .gh-badge-outline{transition:all .4s ease}.gh-langswitch a:hover .gh-badge-outline{border-color:#576870;color:#576870}.gh-langswitch .active a{color:#3eb0ef}.gh-langswitch .active .gh-badge-outline,.gh-langswitch .active a:hover .gh-badge-outline{border-color:#3eb0ef;color:#3eb0ef}.gh-foot-social{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-foot-social a{display:block;padding:7px}.gh-foot-social a:first-of-type{padding-left:0}.gh-foot-social svg{width:auto;height:14px;fill:#92a3ab;transition:all .3s ease}.gh-foot-social a:hover svg{fill:#343f44}.gh-foot-copyright{position:absolute;bottom:3px;left:0;margin-top:5px;color:#738a94;font-size:1.1rem}@media (max-width:965px){.gh-foot-col:last-of-type{display:none}}@media (max-width:800px){.gh-foot{text-align:center}.gh-foot-content{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}.gh-foot-nav{-ms-flex-order:1;order:1;padding:0;max-width:none}.gh-foot-col{padding:0 10px}.gh-foot-col:last-of-type{display:block}.gh-foot-mast{-ms-flex-order:2;order:2;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;margin:20px 0;padding:20px;max-width:450px;width:auto;border-top:1px solid #e5eff5;text-align:center}.gh-foot-copyright{position:static}}@media (max-width:550px){.gh-foot-content{padding:0;border:none}.gh-foot-mast{-ms-flex:0 0 auto;flex:0 0 auto;margin-top:0;padding:20px 20px 0}.gh-foot-nav{display:none}}.gh-foot-floating .gh-foot{padding-top:2vw}.gh-foot-floating .gh-foot-content,.gh-foot-floating .gh-foot-mast{border-top:none}.gh-foot-min{padding:0 4vw 4vw;border-top:1px solid #e5eff5;background:linear-gradient(#f8fafc,#f4f8fb)}.gh-foot-min .gh-foot-content{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:20px 0;border-top:none}.gh-foot-min-logo svg{width:auto;height:22px}.gh-foot-min-nav{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;margin-left:-15px}.gh-foot-min-back{margin-right:-15px}.gh-foot-min-item{padding:0 15px}@media (max-width:660px){.gh-foot-min .gh-foot-content{-ms-flex-direction:row;flex-direction:row}.gh-foot-min-item:nth-child(1n+2){display:none}}.gh-nav-logo,.gh-nav-logo:hover{color:#26a8ed}.gh-gscan-head{margin-bottom:6vw;text-align:center}.gh-subhead.warning{color:#f05230;margin-top:.5em}.report-body{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:start;align-items:flex-start}.gh-section.report{padding-top:0}.report-section{padding:0 60px 0 0}.report-section.results{-ms-flex-positive:1;flex-grow:1;width:60%}.report-section.meta{-ms-flex-positive:1;flex-grow:1;overflow:hidden;margin-top:25px;padding:20px;width:40%;border:1px solid #e5eff5;border-radius:5px}.image-container{margin:-20px -20px 20px;border-bottom:1px solid #e5eff5}.info{-ms-flex-positive:2;flex-grow:2;width:100%}.report-meta ul{margin:0;padding:0;list-style:none}.image-container img{max-width:100%}.report-heading{-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:center;align-items:center;margin-bottom:1.5em;font-size:36px}.report-heading,.result-summary{display:-ms-flexbox;display:flex}.result-summary .title{margin-left:10px}.report h3,.report h4{line-height:1.6}.report h3{display:block;margin:0 5px 6px;font-size:1.17em;font-weight:700}.report h4{margin:0;font-size:16px;font-weight:400;opacity:1}.toggle-details .hide{display:none}.toggle-details{position:relative;display:inline-block;overflow:hidden;margin-top:2px;padding:0;height:20px;font-size:15px;opacity:1;cursor:pointer}.level-group{margin-top:10px}.details.hide{display:none}.indicator{display:inline-block;margin-right:6px;width:30px;height:30px;color:#fff;line-height:1.5;font-weight:700;text-align:center;background:transparent;border-radius:100%}.score{padding:0 4px;color:#fff}.indicator.error:before,.indicator.warning:before{content:"!";margin-top:-2px}.indicator.recommendation:before{content:"?"}.indicator.passing:before{content:"\2713\0020";margin-left:-2px}.indicator.error{border:2px solid #f05230;color:#ee3e17}.score.error{background:#f05230}.indicator.warning,.recommendation{border:2px solid #fecd35;color:#febf01}.score.warning{background:#f2a925}.indicator.passing{border:2px solid #a4d037;color:#98c22e}.score.passing{background:#a4d037}.features li{list-style-type:none}.features li:before{content:"\2713\0020"}.tags li{display:inline;list-style-type:none}.tags li:after{content:","}.tags li:last-child:after{content:"."}button[type=submit].button-add:disabled,button[type=submit].button-add:disabled:hover{color:#b3b9ba;background:#eeefef;cursor:not-allowed}.rule-group{padding-bottom:30px;border-bottom:1px solid #e5eff5}.rule{padding:10px 0;transition:all .3s ease}.rule>p{margin:0}.features ul{padding:0}.rule.expanded{margin:0 0 10px -20px;padding:20px;background:#f8fafc;border-radius:4px}.gh-section-gscan-uploader{min-height:70vh}.gh-gscan-uploader{margin-bottom:6vw;text-align:center}@media (max-width:500px){.report-heading{-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-pack:center;justify-content:center}.result-summary{-ms-flex:1 1 380px;flex:1 1 380px;margin-bottom:1em}}
+a,abbr,acronym,address,applet,article,aside,audio,big,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,ul,var,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:"";content:none}img{max-width:100%}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}*,:after,:before{box-sizing:inherit}a{background-color:transparent}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;color:inherit;font:inherit}button{overflow:visible;border:none;background:transparent}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input:focus{outline:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{padding:0;border:0}textarea{overflow:auto}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}html{overflow-y:scroll;font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body,html{overflow-x:hidden}body{color:#52636b;font-family:Whitney SSm A,Whitney SSm B,sans-serif;font-size:1.45rem;line-height:1.6em;font-weight:400;font-style:normal;letter-spacing:0;text-rendering:optimizeLegibility;background:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-moz-font-feature-settings:"liga" on}::-moz-selection{text-shadow:none;background:#cbeafb}::selection{text-shadow:none;background:#cbeafb}hr{display:block;margin:2em 0;padding:0;height:1px;border:0;border-top:1px solid #e5eff5}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{margin:0;padding:0;border:0}blockquote,dl,ol,p,ul{margin:0 0 1.5em}ol,ul{padding-left:2.5em}ol ol,ol ul,ul ol,ul ul{margin:.5em 0 1em;padding-left:2em}ul{list-style:disc}ol{list-style:decimal}li{margin:.5em 0;line-height:1.6em}dt{float:left;margin:0 20px 0 0;width:120px;color:#343f44;font-weight:500;text-align:right}dd{margin:0 0 5px;text-align:left}blockquote{margin:1.6em 0;padding:0 1.6em;border-left:.5em solid #e5eff5}blockquote p{margin:.8em 0;font-size:1.2em;font-weight:300}blockquote small{display:inline-block;margin:.8em 0 .8em 1.5em;font-size:.9em;opacity:.8}blockquote small:before{content:"\2014 \00A0"}blockquote cite{font-weight:700}blockquote cite a{font-weight:400}a{color:#26a8ed;text-decoration:none;transition:all .5s ease}a:hover{text-decoration:underline;transition:all .2s ease}.hidden{display:none!important}.visuallyhidden{position:absolute;overflow:hidden;clip:rect(0 0 0 0);margin:-1px;padding:0;width:1px;height:1px;border:0}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{position:static;overflow:visible;clip:auto;margin:0;width:auto;height:auto}.invisible{visibility:hidden}.sr-only{position:absolute;overflow:hidden;clip:rect(0,0,0,0);margin:-1px;padding:0;width:1px;height:1px;border:0}.sr-only-focusable:focus{z-index:900;overflow:visible;clip:auto;margin:0;padding:0 10px;width:auto;height:auto;color:#333;line-height:49px;font-weight:700;text-decoration:none;background-color:#f5f5f5}.right{float:right}.left{float:left}.clearfix:after,.clearfix:before{content:" ";display:table}.clearfix:after{clear:both}.gh-table{box-sizing:border-box;margin:1.6em 0;max-width:100%;width:100%;background-color:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box}.gh-table td,.gh-table th{padding:8px;border-top:1px solid #e2edf2;line-height:20px;text-align:left;vertical-align:top}.gh-table th{color:#7d878a}.gh-table caption+thead tr:first-child td,.gh-table caption+thead tr:first-child th,.gh-table colgroup+thead tr:first-child td,.gh-table colgroup+thead tr:first-child th,.gh-table thead:first-child tr:first-child td,.gh-table thead:first-child tr:first-child th{border-top:0}.gh-table tbody+tbody{border-top:2px solid #e2edf2}.gh-table .gh-table .gh-table{background-color:#fff}.gh-table tbody>tr:nth-child(odd)>td,.gh-table tbody>tr:nth-child(odd)>th{background-color:#f4f8fa}.gh-table.plain tbody>tr:nth-child(odd)>td,.gh-table.plain tbody>tr:nth-child(odd)>th{background:transparent}h1,h2,h3,h4,h5,h6{margin-top:0;color:#242628;line-height:1.15;font-weight:800;text-rendering:optimizeLegibility}h1{margin:0 0 .5em;font-size:2.6rem;font-weight:600}@media (max-width:500px){h1{font-size:2.2rem}}h2{margin:1.5em 0 .5em;font-size:2rem;font-weight:500}@media (max-width:500px){h2{font-size:1.8rem}}h3{margin:1.5em 0 .5em;font-size:1.8rem;font-weight:500}@media (max-width:500px){h3{font-size:1.7rem}}h4{margin:1.5em 0 .5em;font-size:1.6rem;font-weight:500}h5,h6{margin:1.5em 0 .5em;font-size:1.4rem;font-weight:500}.supermassive-h1{margin-top:40px;color:#fa3a57;font-size:12vw;font-weight:100}p code{padding:0 5px 2px;line-height:1;font-weight:400!important;background:#d0ecfb;border-radius:4px}.gh-anchor{color:inherit}.gh-anchor,.gh-anchor:hover{text-decoration:none}.gh-header{padding:80px 0;text-align:center}.gh-header h1{margin:0;font-size:4.4rem;font-weight:500}.gh-mainhead{margin:0;color:inherit;font-size:4.4rem;font-weight:300;letter-spacing:-2px}.gh-subhead{margin:.3em 0 0;font-size:2rem;line-height:1.3em;opacity:.7}@media (max-width:660px){.gh-header{padding:5vw 10vw}.gh-header h1,.gh-mainhead{font-size:6vw}.gh-subhead{font-size:1.6rem}}p+p>.gh-morelink{position:relative;top:-10px}.gh-morelink svg{margin-left:3px;height:.5em}.gh-badge{display:inline-block;padding:2px 3px 3px;border:1px solid #97bd38;color:#fff;font-size:.8em;line-height:1em;font-weight:600;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,.1);white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:linear-gradient(#a4d037,#97bd38);border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.gh-badge-blue{border:1px solid #3da4db;background:linear-gradient(#57baf0,#3dabe6)}.gh-badge-outline{border-color:#dae0e2;color:#bdc7cc;font-weight:400;background:transparent;box-shadow:none}.gh-customerlogos{display:block;opacity:.7}.gh-signupbox-section{border-top:1px solid #edf4f8;border-bottom:1px solid #edf4f8;background:linear-gradient(#f8fafc,#f4f8fb)}.gh-signupbox{-ms-flex-pack:justify;justify-content:space-between;margin:0 -20px;padding:20px;font-size:1.6rem;line-height:1.3em;background:#fff;border-radius:6px;box-shadow:0 0 1px rgba(0,0,0,.1),0 2px 6px rgba(0,0,0,.03)}.gh-signup-actions,.gh-signupbox{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-signup-actions{-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0;margin-left:20px;max-width:300px}.gh-signup-actions .gh-btn{-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.gh-signup-actions .gh-btn-blue{-ms-flex-positive:1;flex-grow:1;margin-right:10px}.gh-signup-actions .gh-btn-blue span{width:100%;text-align:center}@media (max-width:1040px){.gh-signupbox{margin:0}.gh-signupbox-text-extension{display:none}}@media (max-width:620px){.gh-signupbox{-ms-flex-direction:column;flex-direction:column;text-align:center}.gh-signup-actions{margin:15px 0 0;max-width:none;width:100%}}.gh-ghostpro{display:inline-block;white-space:nowrap}.gh-ghostpro svg{height:31px}.gh-ghostpro .gh-badge{margin-left:4px;padding-top:4px;font-size:1.2rem;text-transform:uppercase}.gh-btn{display:inline-block;outline:none;border:1px solid #ccdfeb;color:#738a94;text-decoration:none!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;fill:#738a94;border-radius:5px;transition:none;-webkit-font-smoothing:subpixel-antialiased}.gh-btn span{display:block;padding:0 14px;height:37px;font-size:1.3rem;line-height:37px;font-weight:400;text-align:center;border-radius:4px}.gh-btn.disabled,.gh-btn[disabled],fieldset[disabled] .gh-btn{box-shadow:none;opacity:.8;cursor:not-allowed;pointer-events:none}.gh-btn-blue{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#3da1d6,#2288bf);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4fb7f0,#29a0e0 60%,#29a0e0 90%,#36a6e2);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-blue-outline{color:#26a8ed;border-color:#3eb0ef}.gh-btn-green{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#99bf38,#83a333);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-green span{background:linear-gradient(#a9d145,#97bc38 60%,#97bc38 90%,#96ba3b);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-green:active,.gh-btn-green:focus{background:#83a333}.gh-btn-green:active span,.gh-btn-green:focus span{background:#97bc38;box-shadow:none}.gh-btn-green-outline{color:#98c22e;border-color:#a4d037}.gh-btn-red{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#d64f30,#b33a1e);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-red span{background:linear-gradient(#f06242,#dc411e 60%,#dc411e 90%,#e24a28);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-red:active,.gh-btn-red:focus{background:#9d331b}.gh-btn-red:active span,.gh-btn-red:focus span{background:#dc411e;box-shadow:none}.gh-btn-red-outline{color:#ee3e17;border-color:#f05230}.gh-btn-black{padding:1px;border:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.1);fill:#fff;background:linear-gradient(#323232,#171717);box-shadow:0 1px 0 rgba(0,0,0,.12);transition:none!important}.gh-btn-black span{background:linear-gradient(#454545,#2a2c2d 60%,#2a2c2d 90%,#313435);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.gh-btn-black:active,.gh-btn-black:focus{background:#0a0a0a}.gh-btn-black:active span,.gh-btn-black:focus span{background:#2a2c2d;box-shadow:none}.gh-btn-black-outline{color:#2a3337;border-color:#343f44}.gh-btn-grey{padding:1px;border:0;color:#343f44;text-shadow:0 1px 0 #fff;fill:#343f44;background:linear-gradient(#ced9df,#bdc7cc);box-shadow:0 1px 0 rgba(0,0,0,.05);transition:none!important}.gh-btn-grey span{background:linear-gradient(#f8fafc,#f6f8f9);box-shadow:inset 0 1px 0 #fff}.gh-btn-grey:active,.gh-btn-grey:focus{background:#bdc7cc}.gh-btn-grey:active span,.gh-btn-grey:focus span{background:#f3f5f7;box-shadow:none}.gh-btn-white{background:#fff}.gh-btn-white span{height:38px}.gh-btn-block{display:block;width:100%}.gh-btn-icon span{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.gh-btn-icon svg{margin-right:10px;height:15px}.gh-spinner{position:relative;display:inline-block;margin-right:8px;width:18px;height:18px;border:4px solid rgba(0,0,0,.2);border-radius:100%;vertical-align:text-bottom;animation:a 1s linear infinite}.gh-spinner:before{content:"";display:block;margin-top:9px;width:4px;height:4px;background:rgba(0,0,0,.6);border-radius:100%}.gh-btn-green .gh-spinner{border-color:hsla(0,0%,100%,.2)}.gh-btn-green .gh-spinner:before{background:hsla(0,0%,100%,.6)}@keyframes a{0%{transform:rotate(0deg)}50%{transform:rotate(180deg)}to{transform:rotate(1turn)}}svg.retry-animated{stroke:#fff;animation:b .5s ease-in-out forwards}@keyframes b{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.gh-btn-group{margin-top:3rem}.gh-btn-group .gh-btn:first-of-type{margin-right:15px}.gh-btn-group .gh-btn span{font-size:1.4rem}.gh-form-group{position:relative;margin-bottom:1.6em;max-width:500px;width:100%;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.gh-form-group p{margin:4px 0 0;color:#b1b1b1;font-size:1.3rem}.gh-form-group h3{margin-bottom:1.6em;font-size:1.5rem}.gh-form-group label{margin-bottom:4px}.gh-form-group-horizontal{display:-ms-flexbox;display:flex}.gh-form-group-horizontal .gh-form-group+.gh-form-group{margin-left:15px}@media (max-width:550px){.gh-form-group{max-width:100%}}.gh-input,.gh-select,select{display:block;padding:12px;width:100%;border:1px solid #dae2e7;color:#687e87;font-size:1.3rem;line-height:1em;font-weight:400;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;border-radius:5px;transition:border-color .15s linear;-webkit-appearance:none}.gh-select,select{cursor:pointer}::-webkit-input-placeholder{color:#a9b6bc;font-weight:200}:-ms-input-placeholder{color:#a9b6bc;font-weight:200}textarea{min-width:250px;min-height:10rem;max-width:500px;width:100%;height:auto;line-height:1.5;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;resize:vertical}textarea.gh-input{line-height:1.5em}.gh-input.focus,.gh-input.stripe-focus,.gh-input:focus,.gh-select:focus,select:focus,textarea:focus{outline:0;border-color:#becdd5}.gh-input:-webkit-autofill{transition:background-color 100000000s;-webkit-text-fill-color:#687e87;-webkit-animation:1ms void-animation-out}.gh-input.stripe-autofill{background-color:#fff!important}.gh-input-icon{position:relative}.gh-input-icon input{padding-left:35px}.gh-input-icon select{padding-right:35px}.gh-input-icon svg{position:absolute;top:50%;left:12px;z-index:100;height:14px;fill:#a6bac5;transform:translateY(-7px)}.gh-select.gh-input-icon svg{right:12px;left:auto}.gh-input-icon-lock svg{transform:translateY(-8px)}.gh-input-tooltip{position:relative}.gh-input-tooltip input{padding-right:35px}.gh-input-tooltip button{position:absolute;top:0;right:5px;z-index:100;overflow:hidden;height:41px}.gh-input-tooltip button svg{height:14px;fill:#a6bac5}.for-checkbox label,.for-radio label{display:block;padding-bottom:4px;cursor:pointer}.for-checkbox label p,.for-radio label p{overflow:auto;color:#000;font-weight:400}.for-checkbox label:hover p,.for-radio label:hover p{color:#738a94}.for-checkbox input,.for-radio input{position:absolute;top:0;right:0;bottom:0;left:-9999px}.for-checkbox .input-toggle-component,.for-radio .input-toggle-component{position:relative;top:1px;display:inline-block;float:left;margin-right:7px;width:18px;height:18px;border:1px solid #dfe1e3;background:#f7f7f7}.for-checkbox label:hover input:not(:checked)+.input-toggle-component,.for-radio label:hover input:not(:checked)+.input-toggle-component{border-color:#c5d2d9}.for-checkbox .input-toggle-component{border-radius:5px;transition:background .15s ease-in-out,border-color .15s ease-in-out}.for-checkbox .input-toggle-component:before{content:"";position:absolute;top:4px;left:3px;width:10px;height:6px;border:2px solid #fff;border-top:none;border-right:none;opacity:0;transition:opacity .15s ease-in-out;transform:rotate(-45deg)}.for-checkbox input:checked+.input-toggle-component{border-color:#88ae29;background:#a4d037}.for-checkbox input:checked+.input-toggle-component:before{opacity:1}.for-radio .input-toggle-component{border-radius:100px;transition:background .15s ease-in-out,border-color .15s ease-in-out}.for-radio .input-toggle-component:before{content:"";position:absolute;top:4px;left:4px;width:8px;height:8px;background:#fff;border-radius:100%;opacity:0;transition:opacity .15s ease-in-out}.for-radio input:checked+.input-toggle-component{border-color:#88ae29;background:#a4d037}.for-radio input:checked+.input-toggle-component:before{opacity:1}.gh-select{position:relative;display:block;overflow:hidden;padding:0;max-width:100%;width:100%;border-width:0}.gh-select select{padding:8px 10px;outline:none;line-height:normal;text-indent:.01px;text-overflow:"";background:#fff;appearance:none;-webkit-appearance:none;-moz-appearance:window}.gh-select select::-ms-expand{display:none}.gh-select select:-moz-focusring{color:transparent;text-shadow:0 0 0 #000}.gh-error .gh-input,.gh-error select{border-color:#f05230}.gh-error .gh-input-icon svg,.gh-error .gh-input-tooltip button svg{fill:#f05230}.gh-error-message{display:block;margin:10px 0 0;color:#f05230;font-size:1.2rem;line-height:1.4em}.gh-form-group .gh-response{position:absolute;top:-4px;right:0;margin:0;color:#a6b0b3;font-size:1.1rem;font-weight:400;text-align:right}.gh-form-group.gh-error .gh-response,.gh-main-error.gh-error{color:#f05230}.gh-flow-loading{margin-top:5px;font-size:1.4rem}.gh-success .gh-input,.gh-success select{border-color:#a4d037}.gh-success .gh-input-icon svg{fill:#8eb62b}.gh-success .gh-input-tooltip button{display:none}.hamburger{display:-ms-flexbox;display:flex;overflow:visible;margin:0;padding:2px 0;border:0;color:inherit;font:inherit;text-transform:none;background-color:transparent;cursor:pointer;transition:opacity .15s linear,filter .15s linear}.hamburger-box{position:relative;display:inline-block;width:20px;height:13px}.hamburger-inner{top:50%;display:block;margin-top:-2px}.hamburger-inner,.hamburger-inner:after,.hamburger-inner:before{position:absolute;width:20px;height:1px;background-color:#738a94;border-radius:4px;transition:transform .15s ease}.hamburger-inner:after,.hamburger-inner:before{content:"";display:block}.hamburger-inner:before{top:-6px}.hamburger-inner:after{bottom:-6px}.hamburger--collapse .hamburger-inner{top:auto;bottom:0;transition-delay:.15s;transition-timing-function:cubic-bezier(.55,.055,.675,.19);transition-duration:.15s}.hamburger--collapse .hamburger-inner:after{top:-12px;transition:top .3s cubic-bezier(.33333,.66667,.66667,1) .3s,opacity .1s linear}.hamburger--collapse .hamburger-inner:before{transition:top .12s cubic-bezier(.33333,.66667,.66667,1) .3s,transform .15s cubic-bezier(.55,.055,.675,.19)}.gh-mobilehead-open .hamburger-inner,.gh-mobilehead-open .hamburger-inner:after,.gh-mobilehead-open .hamburger-inner:before{background-color:#343f44}.gh-mobilehead-open .hamburger-inner{transition-delay:.32s;transition-timing-function:cubic-bezier(.215,.61,.355,1);transform:translate3d(0,-6px,0) rotate(-45deg)}.gh-mobilehead-open .hamburger-inner:after{top:0;opacity:0;transition:top .3s cubic-bezier(.33333,0,.66667,.33333),opacity .1s linear .27s}.gh-mobilehead-open .hamburger-inner:before{top:0;transition:top .12s cubic-bezier(.33333,0,.66667,.33333) .18s,transform .15s cubic-bezier(.215,.61,.355,1) .42s;transform:rotate(-90deg)}.gh-viewport{position:relative;min-height:100%}.inner{margin:0 auto;max-width:1030px;width:100%}.gh-head{position:relative;z-index:900;margin-bottom:40px;padding:0 4vw;width:100%;border-bottom:1px solid #e5eff5;background:hsla(0,0%,100%,.97)}@media (max-width:600px){.gh-head{padding-right:15px;padding-left:15px}}.gh-navbar{-ms-flex-pack:justify;justify-content:space-between;height:55px;font-size:13px}.gh-navbar,.gh-navbar-left,.gh-navbar-right{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-nav-logo{-ms-flex-negative:0;flex-shrink:0;display:-ms-flexbox!important;display:flex!important;-ms-flex-align:center;align-items:center;margin-left:-15px;color:#738a94}.gh-nav-item.gh-nav-logo:hover{color:inherit}.gh-nav-logo .ghost-svg{width:auto;height:25px}.gh-nav-logo-suffix{position:relative;margin:0 0 0 12px;padding:0 0 0 11px;font-size:1.6rem;font-weight:400;text-shadow:none;transition:none}.gh-nav-logo-suffix:before{content:"";position:absolute;top:0;left:0;display:block;width:1px;height:23px;background:#d3e4ee;transform:rotate(25deg)}.gh-navbar-item{-ms-flex-negative:0;flex-shrink:0;display:block;padding:10px 15px;color:#738a94;font-weight:500;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-drop.active .gh-drop-trigger,.gh-navbar-item-active,.gh-navbar-item:hover{color:#343f44;text-decoration:none}.gh-navbar-item:hover .gh-nav-logo-suffix{color:inherit}.gh-more-drop svg{margin-left:3px;width:7px;height:auto}.gh-more-drop .gh-dropdown{right:-73px;width:146px;font-size:1.2rem;text-shadow:none}.gh-more-drop .gh-dropdown:after{right:66px}.gh-more-drop .gh-dropdown:before{right:64px}.gh-more-drop .gh-badge{font-size:1rem}.gh-navbar-btn{margin-left:12px;color:#738a94;transition:all .5s ease}.gh-navbar-btn span{padding:8px 10px;min-width:66px;height:auto;line-height:1;text-align:center}.gh-navbar-btn:hover{border-color:#3eb0ef;color:#3da1d6;transition:all .2s ease}.gh-signin-drop svg{margin-left:5px;width:7px;height:auto}.gh-head-transparent .gh-head{border:none;text-shadow:rgba(0,0,0,.1) 0 1px 1px;background:transparent}.gh-head-transparent .gh-head .ghost-svg g,.gh-head-transparent .gh-head .ghost-svg path{fill:#fff}.gh-head-transparent .gh-head .gh-navbar-item{color:hsla(0,0%,100%,.85)}.gh-head-transparent .gh-head .gh-drop.active .gh-drop-trigger,.gh-head-transparent .gh-head .gh-navbar-item:hover{color:#fff}.gh-head-transparent .gh-head .gh-more-drop .gh-dropdown{top:110%;box-shadow:0 3px 8px rgba(0,0,0,.15),0 0 2px rgba(0,0,0,.2)}.gh-head-transparent .gh-head .gh-navbar-btn{border-color:hsla(0,0%,100%,.6);color:hsla(0,0%,100%,.8)}.gh-head-transparent .gh-head .gh-navbar-btn:hover{border-color:#fff;color:#fff}.gh-head-transparent .gh-hero{margin-top:calc(-6vw - 55px)}@media (max-width:660px){.gh-head-transparent .gh-hero{margin-top:-20px}}.gh-head-transparent .gh-hero .gh-btn-outline{border-color:hsla(0,0%,100%,.7);color:hsla(0,0%,100%,.8);transition:all .5s ease}.gh-head-transparent .gh-hero .gh-btn-outline:hover{border-color:#fff;color:#fff;transition:all .15s ease}.gh-mobilehead{position:absolute;top:0;left:0;z-index:200;display:none;overflow:hidden;width:100%;height:48px;border-bottom:1px solid rgba(0,0,0,.05);background:hsla(0,0%,100%,.97);box-shadow:inset 0 -1px 0 0 #fff,0 1px 5px rgba(0,0,0,.02);transition:all .5s ease-out,background 1s ease-out;transition-delay:.2s;-webkit-backdrop-filter:blur(3px)}.gh-mobilenavbar{position:relative;z-index:10;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:center;align-items:center;height:48px;font-size:13px}.gh-nav-burger{display:block;padding:15px;color:#738a94;font-size:25px;line-height:1}.gh-nav-burger,.gh-nav-burger:hover{text-decoration:none}.gh-mobilenavbar .gh-nav-logo{position:absolute;top:0;right:50%;display:block;padding:13px 0;transform:translateX(50%)}.gh-mobilenavbar .ghost-svg{width:auto;height:23px}.gh-mobilenavbar .gh-login-link{display:block;padding:14px 15px;font-size:1.5rem}.gh-mobilenavbar .gh-login-link:hover{text-decoration:none}.gh-mobilemenu{padding:2.7vw 14vw}.gh-mobilemenu a{display:block;border-bottom:1px solid #e5eff5;color:#738a94;font-size:1.8rem;line-height:4.8rem;font-weight:200;opacity:0;transition:transform .3s cubic-bezier(.4,.01,.165,.99),opacity .2s cubic-bezier(.4,.01,.165,.99);transition-delay:.4s;transform:scale(1.1) translateY(-25px)}.gh-mobilemenu a:first-child{transition-delay:.56s}.gh-mobilemenu a:nth-child(2){transition-delay:.49s}.gh-mobilemenu a:nth-child(3){transition-delay:.42s}.gh-mobilemenu a:nth-child(4){transition-delay:.35s}.gh-mobilemenu a:nth-child(5){transition-delay:.28s}.gh-mobilemenu a:nth-child(6){transition-delay:.21s}.gh-mobilemenu a:nth-child(7){transition-delay:.14s}.gh-mobilemenu a:nth-child(8){transition-delay:.07s}.gh-mobilemenu a:hover{color:#3eb0ef;text-decoration:none}.gh-mobilemenu a.active{color:#343f44;font-weight:400}.gh-mobilehead-open{position:fixed;top:0;right:0;left:0;z-index:9999;overflow:hidden;margin:0;height:100vh;transition:all .3s ease-in,background .5s ease-in;transition-delay:.3s}.gh-mobilehead-open .gh-mobilemenu a{opacity:1;transition:transform .6s cubic-bezier(.4,.01,.165,.99),opacity .9s cubic-bezier(.4,.01,.165,.99);transform:scale(1) translateY(0)}.gh-mobilehead-open .gh-mobilemenu a:nth-child(8){transition-delay:.56s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(7){transition-delay:.49s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(6){transition-delay:.42s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(5){transition-delay:.35s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(4){transition-delay:.28s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(3){transition-delay:.21s}.gh-mobilehead-open .gh-mobilemenu a:nth-child(2){transition-delay:.14s}.gh-mobilehead-open .gh-mobilemenu a:first-child{transition-delay:.07s}@media (max-width:660px){.gh-viewport{padding-top:68px}.gh-head{display:none}.gh-mobilehead{display:block}}.gh-main{padding:0 4vw}.gh-section{margin:0 -4vw;padding:60px 4vw}@media (max-width:600px){.gh-section{padding:10vw 4vw}}.gh-section-white{background:#fff}.gh-section-dark{color:#fff;text-shadow:rgba(0,0,0,.1) 0 1px 2px}.gh-section-dark h2{color:#fff}.gh-section-dark h3{color:#fff;font-size:1.8rem}.gh-section-dark p{opacity:.8}.gh-section-dark a{color:#5fbef2}.gh-section-dark a.gh-anchor{color:#fff}.gh-section h3 .gh-badge{margin-left:4px;padding:2px;padding-bottom:2px;font-size:.7em;vertical-align:top}.gh-section-head{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;margin-bottom:1em;text-align:center}.gh-section-head h2{margin:1em 0 .3em;font-size:2.6rem;font-weight:400}@media (max-width:660px){.gh-section-head h2{font-size:4.8vw}}.gh-section-head p{margin:0;font-size:2rem;line-height:1.4em;font-weight:300;opacity:.8}@media (max-width:660px){.gh-section-head p{font-size:1.6rem}}.gh-1col{margin:0 auto;max-width:720px;font-size:1.8rem;line-height:1.65em}@media (max-width:660px){.gh-1col{font-size:1.6rem}}.gh-grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between;margin:0 -40px -10px;padding:0 20px}.gh-grid-item{-ms-flex:1 0 300px;flex:1 0 300px;padding:20px}@media (max-width:934px){.gh-grid-item{padding:5px 20px}}@media (max-width:620px){.gh-grid{-ms-flex-direction:column;flex-direction:column;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.gh-grid-item{position:relative;-ms-flex:1 0 auto;flex:1 0 auto;padding:0 20px 0 65px}.gh-grid-item svg{position:absolute;top:9px;left:23px;width:24px;height:auto}}.gh-grid-item svg{height:30px;fill:#ad26b4}.gh-grid-blue .gh-grid-item svg{fill:#3eb0ef}.gh-grid-green .gh-grid-item svg{fill:#a4d037}.gh-grid-backups svg{margin-bottom:-3px;height:33px}.gh-grid-seo svg{margin-top:2px;margin-bottom:2px;height:26px}.gh-grid-item h3{margin:1em 0 .3em;font-size:1.6rem}.gh-biggrid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:justify;justify-content:space-between;margin:4vw auto 0;padding:0 20px}.gh-biggrid-item{position:relative;-ms-flex:1 0 300px;flex:1 0 300px;margin:15px 30px;padding:0 0 0 70px;font-size:1.6rem}.gh-biggrid-item h3{margin:0 0 5px;color:#fecd35}.gh-biggrid-item svg{position:absolute;top:0;left:0;width:40px;height:auto;fill:#fff}.gh-biggrid-item a{color:#fff;font-weight:500;text-decoration:underline}@media (max-width:620px){.gh-biggrid-item{margin:15px auto;padding:0 20px 0 65px}.gh-biggrid-item svg{width:auto;height:30px}}.gh-foot{padding:3vw 4vw 5vw;font-size:1.3rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#fff}.gh-foot a{color:#738a94;text-decoration:none}.gh-foot a:hover{color:#343f44;text-decoration:none}.gh-foot-content{padding-top:20px;border-top:1px solid #e5eff5}.gh-foot-content,.gh-foot-nav{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.gh-foot-nav{padding-left:60px;width:100%}.gh-foot-col{padding:0 10px}.gh-foot-col h4{margin:5px 0 10px;font-size:1.4rem}.gh-foot-col ul{margin:0;padding:0;list-style:none}.gh-foot-col ul a{display:block;padding:3px 0;min-width:90px}.gh-foot-col li{margin:0}.gh-foot-col .gh-badge{position:relative;top:-1px}.gh-foot-mast{position:relative;-ms-flex:0 0 300px;flex:0 0 300px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start}.gh-foot-mast svg{display:inline-block;width:auto;height:25px}.gh-foot-mast-text{margin:10px 0;color:#738a94;line-height:1.5em}.gh-langswitch .gh-drop-trigger{display:inline-block;padding:4px 8px 4px 0;color:#26a8ed}.gh-langswitch .gh-drop-trigger span{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-langswitch svg{margin-left:4px;width:7px;height:auto}.gh-langswitch .gh-dropdown{right:-65px;width:130px}.gh-langswitch .gh-dropdown .gh-badge-outline{transition:all .4s ease}.gh-langswitch a:hover .gh-badge-outline{border-color:#576870;color:#576870}.gh-langswitch .active a{color:#3eb0ef}.gh-langswitch .active .gh-badge-outline,.gh-langswitch .active a:hover .gh-badge-outline{border-color:#3eb0ef;color:#3eb0ef}.gh-foot-social{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.gh-foot-social a{display:block;padding:7px}.gh-foot-social a:first-of-type{padding-left:0}.gh-foot-social svg{width:auto;height:14px;fill:#92a3ab;transition:all .3s ease}.gh-foot-social a:hover svg{fill:#343f44}.gh-foot-copyright{position:absolute;bottom:3px;left:0;margin-top:5px;color:#738a94;font-size:1.1rem}@media (max-width:965px){.gh-foot-col:last-of-type{display:none}}@media (max-width:800px){.gh-foot{text-align:center}.gh-foot-content{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}.gh-foot-nav{-ms-flex-order:1;order:1;padding:0;max-width:none}.gh-foot-col{padding:0 10px}.gh-foot-col:last-of-type{display:block}.gh-foot-mast{-ms-flex-order:2;order:2;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;margin:20px 0;padding:20px;max-width:450px;width:auto;border-top:1px solid #e5eff5;text-align:center}.gh-foot-copyright{position:static}}@media (max-width:550px){.gh-foot-content{padding:0;border:none}.gh-foot-mast{-ms-flex:0 0 auto;flex:0 0 auto;margin-top:0;padding:20px 20px 0}.gh-foot-nav{display:none}}.gh-foot-floating .gh-foot{padding-top:2vw}.gh-foot-floating .gh-foot-content,.gh-foot-floating .gh-foot-mast{border-top:none}.gh-foot-min{padding:0 4vw 4vw;border-top:1px solid #e5eff5;background:linear-gradient(#f8fafc,#f4f8fb)}.gh-foot-min .gh-foot-content{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:20px 0;border-top:none}.gh-foot-min-logo svg{width:auto;height:22px}.gh-foot-min-nav{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;margin-left:-15px}.gh-foot-min-back{margin-right:-15px}.gh-foot-min-item{padding:0 15px}@media (max-width:660px){.gh-foot-min .gh-foot-content{-ms-flex-direction:row;flex-direction:row}.gh-foot-min-item:nth-child(1n+2){display:none}}.gh-nav-logo,.gh-nav-logo:hover{color:#26a8ed}.gh-gscan-head{margin-bottom:6vw;text-align:center}.gh-subhead.warning{margin-top:.5em;color:#f05230}.report-body{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:start;align-items:flex-start}.gh-section.report{padding-top:0}.report-section{padding:0 60px 0 0}.report-section.results{-ms-flex-positive:1;flex-grow:1;width:60%}.report-section.meta{-ms-flex-positive:1;flex-grow:1;overflow:hidden;margin-top:25px;padding:20px;width:40%;border:1px solid #e5eff5;border-radius:5px}.image-container{margin:-20px -20px 20px;border-bottom:1px solid #e5eff5}.info{-ms-flex-positive:2;flex-grow:2;width:100%}.report-meta ul{margin:0;padding:0;list-style:none}.image-container img{max-width:100%}.report-heading{-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:center;align-items:center;margin-bottom:1.5em;font-size:36px}.report-heading,.result-summary{display:-ms-flexbox;display:flex}.result-summary .title{margin-left:10px}.report h3,.report h4{line-height:1.6}.report h3{display:block;margin:0 5px 6px;font-size:1.17em;font-weight:700}.report h4{margin:0;font-size:16px;font-weight:400;opacity:1}.toggle-details .hide{display:none}.toggle-details{position:relative;display:inline-block;overflow:hidden;margin-top:2px;padding:0;height:20px;font-size:15px;opacity:1;cursor:pointer}.level-group{margin-top:10px}.details.hide{display:none}.indicator{display:inline-block;margin-right:6px;width:30px;height:30px;color:#fff;line-height:1.5;font-weight:700;text-align:center;background:transparent;border-radius:100%}.score{padding:0 4px;color:#fff}.indicator.error:before,.indicator.warning:before{content:"!";margin-top:-2px}.indicator.recommendation:before{content:"?"}.indicator.passing:before{content:"\2713\0020";margin-left:-2px}.indicator.error{border:2px solid #f05230;color:#ee3e17}.score.error{background:#f05230}.indicator.warning,.recommendation{border:2px solid #fecd35;color:#febf01}.score.warning{background:#f2a925}.indicator.passing{border:2px solid #a4d037;color:#98c22e}.score.passing{background:#a4d037}.features li{list-style-type:none}.features li:before{content:"\2713\0020"}.tags li{display:inline;list-style-type:none}.tags li:after{content:","}.tags li:last-child:after{content:"."}button[type=submit].button-add:disabled,button[type=submit].button-add:disabled:hover{color:#b3b9ba;background:#eeefef;cursor:not-allowed}.rule-group{padding-bottom:30px;border-bottom:1px solid #e5eff5}.rule{padding:10px 0;transition:all .3s ease}.rule>p{margin:0}.features ul{padding:0}.rule.expanded{margin:0 0 10px -20px;padding:20px;background:#f8fafc;border-radius:4px}.gh-section-gscan-uploader{min-height:70vh}.gh-gscan-uploader{margin-bottom:6vw;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.gh-gscan-uploader-form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.gh-gscan-uploader-form label{display:block;margin:0 0 8px;font-size:1.3rem;line-height:1.3em;font-weight:400}.gh-gscan-uploader-form .gh-select{line-height:1em;padding:12px}@media (max-width:500px){.report-heading{-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-pack:center;justify-content:center}.result-summary{-ms-flex:1 1 380px;flex:1 1 380px;margin-bottom:1em}}
\ No newline at end of file
diff --git a/app/tpl/index.hbs b/app/tpl/index.hbs
index 840cd70f..2735ab35 100644
--- a/app/tpl/index.hbs
+++ b/app/tpl/index.hbs
@@ -3,16 +3,28 @@
Upload a zip to check for errors, deprecations and other compatibility issues. GScan validates your theme based on the Ghost 1.0 requirements. GScan validates your theme based on the Ghost 2.0 requirements by default. You can select a different version if you need to check your theme for a previous major version.Scan Your Theme
{{pageUrl}}
with {{page_url}}
',
fatal: true,
details: 'The helper {{pageUrl}}
was replaced with {{page_url}}
.
' +
- 'Find more information about the {{page_url}}
helper here.'
+ 'Find more information about the {{page_url}}
helper here.',
+ regex: /{{\s*?pageUrl\b[\w\s='"]*?}}/ig,
+ helper: '{{pageUrl}}'
},
'GS001-DEPR-MD': {
level: 'error',
rule: 'The usage of {{meta_description}}
in HTML head
is no longer required',
details: 'The usage of {{meta_description}}
in the HTML head
tag is no longer required because Ghost outputs this for you automatically in {{ghost_head}}
.
' +
- 'Check out the documentation for {{meta_description}}
here.
' +
- 'To see, what else is rendered with the {{ghost_head}}
helper, look here.'
+ 'Check out the documentation for {{meta_description}}
here.
' +
+ 'To see, what else is rendered with the {{ghost_head}}
helper, look here.',
+ regex: /{{image}} helper was replaced with the {{img_url}}
helper.
' +
'Depending on the context of the {{img_url}}
helper you would need to use e. g. {{#post}}
{{img_url feature_image}}
{{/post}}
to render the feature image of the blog post.
' +
'
If you are using {{if image}}
, then you have to replace it with e.g. {{if feature_image}}.
' +
- '
Find more information about the {{img_url}}
helper here and ' +
- 'read more about Ghost\'s usage of contexts here.'
+ '
Find more information about the {{img_url}}
helper here and ' +
+ 'read more about Ghost\'s usage of contexts here.',
+ regex: /{{\s*?image\b[\w\s='"]*?}}/g,
+ helper: '{{image}}'
},
'GS001-DEPR-COV': {
level: 'error',
@@ -105,8 +111,10 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
. To render the cover image in author context, you need to use
' +
'{{#author}}
{{cover_image}}
{{/author}}
' +
- 'See the object attributes of author
here.
' +
- 'To render the cover image of your blog, just use {{@blog.cover_image}}
. See here.'
+ 'See the object attributes of author
here.
' +
+ 'To render the cover image of your blog, just use {{@blog.cover_image}}
. See here.',
+ regex: /{{\s*?cover\s*?}}/g,
+ helper: '{{cover}}'
},
'GS001-DEPR-AIMG': {
level: 'error',
@@ -114,7 +122,9 @@ rules = {
fatal: true,
details: 'The image
attribute in author context was replaced with profile_image
.
' +
'Instead of {{author.image}}
you need to use {{author.profile_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?author\.image\s*?}}/g,
+ helper: '{{author.image}}'
},
'GS001-DEPR-PIMG': {
level: 'error',
@@ -122,7 +132,9 @@ rules = {
fatal: true,
details: 'The image
attribute in post context was replaced with feature_image
.
' +
'Instead of {{post.image}}
you need to use {{post.feature_image}}
.
' +
- 'See the object attributes of post
here.'
+ 'See the object attributes of post
here.',
+ regex: /{{\s*?post\.image\s*?}}/g,
+ helper: '{{post.image}}'
},
'GS001-DEPR-BC': {
level: 'error',
@@ -130,7 +142,9 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
.
' +
'Instead of {{@blog.cover}}
you need to use {{@blog.cover_image}}
.
' +
- 'See here.'
+ 'See here.',
+ regex: /{{\s*?@blog\.cover\s*?}}/g,
+ helper: '{{@blog.cover}}'
},
'GS001-DEPR-AC': {
level: 'error',
@@ -138,7 +152,9 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
.
' +
'Instead of {{author.cover}}
you need to use {{author.cover_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?author\.cover\s*?}}/g,
+ helper: '{{author.cover}}'
},
'GS001-DEPR-TIMG': {
level: 'error',
@@ -146,7 +162,9 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{tag.image}}
you need to use {{tag.feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?tag\.image\s*?}}/g,
+ helper: '{{tag.image}}'
},
'GS001-DEPR-PAIMG': {
level: 'error',
@@ -154,7 +172,9 @@ rules = {
fatal: true,
details: 'The image
attribute in author context was replaced with feature_image
.
' +
'Instead of {{post.author.image}}
you need to use {{post.author.feature_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?post\.author\.image\s*?}}/g,
+ helper: '{{post.author.image}}'
},
'GS001-DEPR-PAC': {
level: 'error',
@@ -162,7 +182,9 @@ rules = {
fatal: true,
details: 'The cover
attribute in author context was replaced with cover_image
.
' +
'Instead of {{post.author.cover}}
you need to use {{post.author.cover_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?post\.author\.cover\s*?}}/g,
+ helper: '{{post.author.cover}}'
},
'GS001-DEPR-PTIMG': {
level: 'error',
@@ -170,7 +192,9 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{post.tags.[#].image}}
you need to use {{post.tags.[#].feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?post\.tags\.\[[0-9]+\]\.image\s*?}}/g,
+ helper: '{{post.tags.[#].image}}'
},
'GS001-DEPR-TSIMG': {
level: 'error',
@@ -178,7 +202,9 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{tags.[#].image}}
you need to use {{tags.[#].feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?tags\.\[[0-9]+\]\.image\s*?}}/g,
+ helper: '{{tags.[#].image}}'
},
'GS001-DEPR-CON-IMG': {
level: 'error',
@@ -198,7 +224,9 @@ rules = {
'{{#tag}}
' +
' {{#if feature_image}}
{{feature_image}}
{{/if}}
' +
'{{/tag}}
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?#if\s*?image\s*?}}/g,
+ helper: '{{#if image}}'
},
'GS001-DEPR-CON-COV': {
level: 'error',
@@ -206,8 +234,10 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
. To check for the cover image in author context, you need to use
' +
'{{#if cover_image}}
{{cover_image}}
{{/if}}
' +
- 'See the object attributes of author
here.
' +
- 'To check for the cover image of your blog, just use {{#if @blog.cover_image}}
. See here.'
+ 'See the object attributes of author
here.
' +
+ 'To check for the cover image of your blog, just use {{#if @blog.cover_image}}
. See here.',
+ regex: /{{\s*?#if\s*?cover\s*?}}/g,
+ helper: '{{#if cover}}'
},
'GS001-DEPR-CON-BC': {
level: 'error',
@@ -215,7 +245,9 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
.
' +
'Instead of {{#if @blog.cover}}
you need to use {{#if @blog.cover_image}}
.
' +
- 'See here.'
+ 'See here.',
+ regex: /{{\s*?#if\s*?@blog\.cover\s*?}}/g,
+ helper: '{{#if @blog.cover}}'
},
'GS001-DEPR-CON-AC': {
level: 'error',
@@ -223,7 +255,9 @@ rules = {
fatal: true,
details: 'The cover
attribute was replaced with cover_image
.
' +
'Instead of {{#if author.cover}}
you need to use {{#if author.cover_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?#if\s*?author\.cover\s*?}}/g,
+ helper: '{{#if author.cover}}'
},
'GS001-DEPR-CON-AIMG': {
level: 'error',
@@ -231,7 +265,9 @@ rules = {
fatal: true,
details: 'The image
attribute in author context was replaced with profile_image
.
' +
'Instead of {{#if author.image}}
you need to use {{#if author.profile_image}}
.
' +
- 'See the object attributes of author
here.'
+ 'See the object attributes of author
here.',
+ regex: /{{\s*?#if\s*?author\.image\s*?}}/g,
+ helper: '{{#if author.image}}'
},
'GS001-DEPR-CON-TIMG': {
level: 'error',
@@ -239,7 +275,9 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{#if tag.image}}
you need to use {{#if tag.feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?#if\s*?tag\.image\s*?}}/g,
+ helper: '{{#if tag.image}}'
},
'GS001-DEPR-CON-PTIMG': {
level: 'error',
@@ -247,7 +285,9 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{#if post.tags.[#].image}}
you need to use {{#if post.tags.[#].feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?#if\s*?post\.tags\.\[[0-9]+\].image\s*?}}/g,
+ helper: '{{#if posts.tags.[#].image}}'
},
'GS001-DEPR-CON-TSIMG': {
level: 'error',
@@ -255,38 +295,53 @@ rules = {
fatal: true,
details: 'The image
attribute in tag context was replaced with feature_image
.
' +
'Instead of {{#if tags.[#].image}}
you need to use {{#if tags.[#].feature_image}}
.
' +
- 'See the object attributes of tags
here.'
+ 'See the object attributes of tags
here.',
+ regex: /{{\s*?#if\s*?tags\.\[[0-9]+\].image\s*?}}/g,
+ helper: '{{#if tags.[#].image}}'
},
'GS001-DEPR-PPP': {
level: 'error',
rule: 'Replace {{@blog.posts_per_page}}
with {{@config.posts_per_page}}
',
details: 'The global {{@blog.posts_per_page}}
property was replaced with {{@config.posts_per_page}}
.
' +
- 'Read here about the attribute and ' +
- 'check here where you can customise the posts per page setting, as this is now adjustable in your theme.'
+ 'Read here about the attribute and ' +
+ 'check here where you can customise the posts per page setting, as this is now adjustable in your theme.',
+ regex: /{{\s*?@blog\.posts_per_page\s*?}}/g,
+ helper: '{{@blog.posts_per_page}}'
},
'GS001-DEPR-C0H': {
level: 'error',
rule: 'Replace {{content words="0"}}
with the {{img_url}}
helper',
details: 'The {{content words="0"}}
hack doesn\'t work anymore (and was never supported). Please use the {{img_url}}
helper to render images.
' +
- 'Find more information about the {{img_url}}
helper here and '
+ 'Find more information about the {{img_url}}
helper here and ',
+ regex: /{{\s*?content words=("|')0("|')\s*?}}/g,
+ helper: '{{content words="0"}}'
},
'GS001-DEPR-CSS-AT': {
level: 'error',
rule: 'Replace .archive-template
with the .paged
CSS class',
details: 'The .archive-template
CSS class was replaced with the .paged
. Please replace this in your stylesheet.
' +
- 'See the context table to check which classes Ghost uses for each context.'
+ 'See the context table to check which classes Ghost uses for each context.',
+ regex: /\.archive-template[\s{]/g,
+ className: '.archive-template',
+ css: true
},
'GS001-DEPR-CSS-PA': {
level: 'error',
rule: 'Replace .page
with the .page-template
css class',
details: 'The .page
CSS class was replaced with the .page-template
. Please replace this in your stylesheet.
' +
- 'See the context table to check which classes Ghost uses for each context.'
+ 'See the context table to check which classes Ghost uses for each context.',
+ regex: /\.page[\s{]/g,
+ className: '.page',
+ css: true
},
'GS001-DEPR-CSS-PATS': {
level: 'error',
rule: 'Replace .page-template-slug
with the .page-slug
css class',
details: 'The .page-template-slug
CSS class was replaced with the .page-slug
. Please replace this in your stylesheet.
' +
- 'See the context table to check which classes Ghost uses for each context.'
+ 'See the context table to check which classes Ghost uses for each context.',
+ regex: /\.page-template-\w+[\s{]/g,
+ className: '.page-template-slug',
+ css: true
},
'GS002-DISQUS-ID': {
level: 'error',
@@ -297,10 +352,12 @@ rules = {
'To resolve this, we\'ve added a new {{comment_id}}
helper that will output the old ID ' +
'for posts that have been imported from LTS, and the new ID for new posts. ' +
'The Disqus embed must be updated from this.page.identifier = \'ghost-{{id}}\';
to ' +
- 'this.page.identifier = \'ghost-{{comment_id}}\';
to ensure Disqus continues to work.'
+ 'this.page.identifier = \'ghost-{{comment_id}}\';
to ensure Disqus continues to work.',
+ regex: /(page\.|disqus_)identifier\s?=\s?['"].*?({{\s*?id\s*?}}).*?['"];?/g
},
'GS002-ID-HELPER': {
level: 'recommendation',
+ version: 'v1',
rule: 'The output of {{id}}
changed between Ghost LTS and 1.0.0, you may need to use {{comment_id}}
instead.',
details:
'The output of {{id}}
has changed between Ghost LTS and 1.0.0. ' +
@@ -308,7 +365,8 @@ rules = {
'We\'ve added a new {{comment_id}}
helper that will output the old ID ' +
'for posts that have been imported from LTS, and the new ID for new posts. ' +
'If you need the old ID to be output on imported posts, then you will need to use ' +
- '{{comment_id}}
rather than {{id}}
.'
+ '{{comment_id}}
rather than {{id}}
.',
+ regex: /({{\s*?id\s*?}})/g
},
'GS005-TPL-ERR': {
level: 'error',
@@ -395,26 +453,30 @@ rules = {
rule: 'A template file called index.hbs
must be present',
fatal: true,
details: 'Your theme must have a template file called index.hbs
.
' +
- 'Read here more about the required template structure and index.hbs
in particular.'
+ 'Read here more about the required template structure and index.hbs
in particular.',
+ path: 'index.hbs'
},
'GS020-POST-REQ': {
level: 'error',
rule: 'A template file called post.hbs
must be present',
fatal: true,
details: 'Your theme must have a template file called index.hbs
.
' +
- 'Read here more about the required template structure and post.hbs
in particular.'
+ 'Read here more about the required template structure and post.hbs
in particular.',
+ path: 'post.hbs'
},
'GS020-DEF-REC': {
level: 'recommendation',
rule: 'Provide a default layout template called default.hbs',
details: 'It is recommended that your theme has a template file called default.hbs
.
' +
- 'Read here more about the recommended template structure and default.hbs
in particular.'
+ 'Read here more about the recommended template structure and default.hbs
in particular.',
+ path: 'default.hbs'
},
'GS030-ASSET-REQ': {
level: 'warning',
rule: 'Assets such as CSS & JS must use the {{asset}}
helper',
details: 'The listed files should be included using the {{asset}}
helper.
' +
- 'For more information, please see the {{asset}}
helper documentation.'
+ 'For more information, please see the {{asset}}
helper documentation.',
+ regex: /(src|href)=['"](.*?\/assets\/.*?)['"]/gmi
},
'GS030-ASSET-SYM': {
level: 'error',
@@ -428,14 +490,16 @@ rules = {
rule: 'The helper {{ghost_head}}
should be present',
details: 'The {{ghost_head}}
helper should be present in your theme. It outputs many useful things, such as "code injection" scripts, structured data, canonical links, meta description etc.
' +
'The helper belongs just before the tag in your
default.hbs
template.
' +
- 'For more details, please see the {{ghost_head}}
helper documentation.'
+ 'For more details, please see the {{ghost_head}}
helper documentation.',
+ helper: 'ghost_head'
},
'GS040-GF-REQ': {
level: 'warning',
rule: 'The helper {{ghost_foot}}
should be present',
details: 'The {{ghost_foot}}
helper should be present in your theme. It outputs scripts as saved in "code injection" scripts.
' +
'The helper belongs just before the
tag in your default.hbs
template.
' +
- 'For more details, please see the {{ghost_foot}}
helper documentation.'
+ 'For more details, please see the {{ghost_foot}}
helper documentation.',
+ helper: 'ghost_foot'
}
};
diff --git a/test/001-deprecations.test.js b/test/001-deprecations.test.js
index 162d6b47..fc77ad94 100644
--- a/test/001-deprecations.test.js
+++ b/test/001-deprecations.test.js
@@ -3,168 +3,342 @@ var should = require('should'), // eslint-disable-line no-unused-vars
utils = require('./utils');
describe('001 Deprecations', function () {
- it('[failure] theme is invalid', function (done) {
- utils.testCheck(thisCheck, '001-deprecations/invalid').then(function (output) {
- output.should.be.a.ValidThemeObject();
-
- output.results.fail.should.be.an.Object().with.keys(
- 'GS001-DEPR-PURL',
- 'GS001-DEPR-MD',
- 'GS001-DEPR-IMG',
- 'GS001-DEPR-COV',
- 'GS001-DEPR-AIMG',
- 'GS001-DEPR-PIMG',
- 'GS001-DEPR-PAIMG',
- 'GS001-DEPR-PAC',
- 'GS001-DEPR-PTIMG',
- 'GS001-DEPR-TSIMG',
- 'GS001-DEPR-PPP',
- 'GS001-DEPR-C0H',
- 'GS001-DEPR-BC',
- 'GS001-DEPR-CON-BC',
- 'GS001-DEPR-AC',
- 'GS001-DEPR-CON-AC',
- 'GS001-DEPR-CON-AIMG',
- 'GS001-DEPR-CON-PTIMG',
- 'GS001-DEPR-CON-TSIMG',
- 'GS001-DEPR-CON-IMG',
- 'GS001-DEPR-CON-COV',
- 'GS001-DEPR-CON-TIMG',
- 'GS001-DEPR-TIMG',
- 'GS001-DEPR-CSS-AT',
- 'GS001-DEPR-CSS-PA',
- 'GS001-DEPR-CSS-PATS'
- );
-
- // pageUrl
- output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject();
- output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(3);
-
- // meta_description in
- output.results.fail['GS001-DEPR-MD'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-MD'].failures.length.should.eql(1); - - // {{image}} - output.results.fail['GS001-DEPR-IMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-IMG'].failures.length.should.eql(2); - - // {{cover}} - output.results.fail['GS001-DEPR-COV'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-COV'].failures.length.should.eql(3); - - // {{author.image}} - output.results.fail['GS001-DEPR-AIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-AIMG'].failures.length.should.eql(2); - - // {{post.image}} - output.results.fail['GS001-DEPR-PIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PIMG'].failures.length.should.eql(1); - - // {{@blog.cover}} - output.results.fail['GS001-DEPR-BC'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-BC'].failures.length.should.eql(1); - - // {{author.cover}} - output.results.fail['GS001-DEPR-AC'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-AC'].failures.length.should.eql(2); - - // {{post.author.cover}} - output.results.fail['GS001-DEPR-PAC'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PAC'].failures.length.should.eql(1); - - // {{post.author.image}} - output.results.fail['GS001-DEPR-PAIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PAIMG'].failures.length.should.eql(1); - - // {{tag.image}} - output.results.fail['GS001-DEPR-TIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-TIMG'].failures.length.should.eql(1); - - // {{posts.tags.[4].image}} - output.results.fail['GS001-DEPR-PTIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PTIMG'].failures.length.should.eql(1); - - // {{tags.[4].image}} - output.results.fail['GS001-DEPR-TSIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-TSIMG'].failures.length.should.eql(1); - - // {{#if image}} - output.results.fail['GS001-DEPR-CON-IMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CON-IMG'].failures.length.should.eql(1); - - // {{#if cover}} - output.results.fail['GS001-DEPR-CON-COV'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CON-COV'].failures.length.should.eql(1); - - // {{#if tag.image}} - output.results.fail['GS001-DEPR-CON-TIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CON-TIMG'].failures.length.should.eql(1); - - // {{#if tags.[#].image}} - output.results.fail['GS001-DEPR-CON-TSIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CON-TSIMG'].failures.length.should.eql(1); - - // {{#if post.tags.[#].image}} - output.results.fail['GS001-DEPR-CON-PTIMG'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CON-PTIMG'].failures.length.should.eql(1); - - // {{@blog.posts_per_page}} - output.results.fail['GS001-DEPR-PPP'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PPP'].failures.length.should.eql(1); - - // {{content word="0"}} - output.results.fail['GS001-DEPR-C0H'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-C0H'].failures.length.should.eql(2); - - // css class .page - output.results.fail['GS001-DEPR-CSS-PA'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CSS-PA'].failures.length.should.eql(2); - - // css class .page-template-{slug} - output.results.fail['GS001-DEPR-CSS-PATS'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CSS-PATS'].failures.length.should.eql(2); - - // css class .achive-template - output.results.fail['GS001-DEPR-CSS-AT'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-CSS-AT'].failures.length.should.eql(1); - - output.results.pass.should.be.an.Object().which.is.empty(); - - done(); - }).catch(done); - }); - - it('[success] should show no error if no deprecated helpers used', function (done) { - utils.testCheck(thisCheck, '001-deprecations/valid').then(function (output) { - output.should.be.a.ValidThemeObject(); - - output.results.fail.should.be.an.Object().which.is.empty(); - output.results.pass.should.be.an.Array().with.lengthOf(26); - - done(); - }).catch(done); + describe('older version passed', function () { + const options = {checkVersion: 'v1'}; + + it('[failure] theme is invalid', function (done) { + utils.testCheck(thisCheck, '001-deprecations/invalid', options).then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().with.keys( + 'GS001-DEPR-PURL', + 'GS001-DEPR-MD', + 'GS001-DEPR-IMG', + 'GS001-DEPR-COV', + 'GS001-DEPR-AIMG', + 'GS001-DEPR-PIMG', + 'GS001-DEPR-PAIMG', + 'GS001-DEPR-PAC', + 'GS001-DEPR-PTIMG', + 'GS001-DEPR-TSIMG', + 'GS001-DEPR-PPP', + 'GS001-DEPR-C0H', + 'GS001-DEPR-BC', + 'GS001-DEPR-CON-BC', + 'GS001-DEPR-AC', + 'GS001-DEPR-CON-AC', + 'GS001-DEPR-CON-AIMG', + 'GS001-DEPR-CON-PTIMG', + 'GS001-DEPR-CON-TSIMG', + 'GS001-DEPR-CON-IMG', + 'GS001-DEPR-CON-COV', + 'GS001-DEPR-CON-TIMG', + 'GS001-DEPR-TIMG', + 'GS001-DEPR-CSS-AT', + 'GS001-DEPR-CSS-PA', + 'GS001-DEPR-CSS-PATS' + ); + + // pageUrl + output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(3); + + // meta_description in
+ output.results.fail['GS001-DEPR-MD'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-MD'].failures.length.should.eql(1); + + // {{image}} + output.results.fail['GS001-DEPR-IMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-IMG'].failures.length.should.eql(2); + + // {{cover}} + output.results.fail['GS001-DEPR-COV'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-COV'].failures.length.should.eql(3); + + // {{author.image}} + output.results.fail['GS001-DEPR-AIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-AIMG'].failures.length.should.eql(2); + + // {{post.image}} + output.results.fail['GS001-DEPR-PIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PIMG'].failures.length.should.eql(1); + + // {{@blog.cover}} + output.results.fail['GS001-DEPR-BC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-BC'].failures.length.should.eql(1); + + // {{author.cover}} + output.results.fail['GS001-DEPR-AC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-AC'].failures.length.should.eql(2); + + // {{post.author.cover}} + output.results.fail['GS001-DEPR-PAC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PAC'].failures.length.should.eql(1); + + // {{post.author.image}} + output.results.fail['GS001-DEPR-PAIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PAIMG'].failures.length.should.eql(1); + + // {{tag.image}} + output.results.fail['GS001-DEPR-TIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-TIMG'].failures.length.should.eql(1); + + // {{posts.tags.[4].image}} + output.results.fail['GS001-DEPR-PTIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PTIMG'].failures.length.should.eql(1); + + // {{tags.[4].image}} + output.results.fail['GS001-DEPR-TSIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-TSIMG'].failures.length.should.eql(1); + + // {{#if image}} + output.results.fail['GS001-DEPR-CON-IMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-IMG'].failures.length.should.eql(1); + + // {{#if cover}} + output.results.fail['GS001-DEPR-CON-COV'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-COV'].failures.length.should.eql(1); + + // {{#if tag.image}} + output.results.fail['GS001-DEPR-CON-TIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-TIMG'].failures.length.should.eql(1); + + // {{#if tags.[#].image}} + output.results.fail['GS001-DEPR-CON-TSIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-TSIMG'].failures.length.should.eql(1); + + // {{#if post.tags.[#].image}} + output.results.fail['GS001-DEPR-CON-PTIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-PTIMG'].failures.length.should.eql(1); + + // {{@blog.posts_per_page}} + output.results.fail['GS001-DEPR-PPP'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PPP'].failures.length.should.eql(1); + + // {{content word="0"}} + output.results.fail['GS001-DEPR-C0H'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-C0H'].failures.length.should.eql(2); + + // css class .page + output.results.fail['GS001-DEPR-CSS-PA'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-PA'].failures.length.should.eql(2); + + // css class .page-template-{slug} + output.results.fail['GS001-DEPR-CSS-PATS'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-PATS'].failures.length.should.eql(2); + + // css class .achive-template + output.results.fail['GS001-DEPR-CSS-AT'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-AT'].failures.length.should.eql(1); + + output.results.pass.should.be.an.Object().which.is.empty(); + + done(); + }).catch(done); + }); + + it('[success] should show no error if no deprecated helpers used', function (done) { + utils.testCheck(thisCheck, '001-deprecations/valid', options).then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().which.is.empty(); + output.results.pass.should.be.an.Array().with.lengthOf(26); + + done(); + }).catch(done); + }); + + it('[mixed] should pass and fail when some rules pass and others fail', function (done) { + utils.testCheck(thisCheck, '001-deprecations/mixed', options).then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().with.keys( + 'GS001-DEPR-PURL', + 'GS001-DEPR-MD', + 'GS001-DEPR-IMG', + 'GS001-DEPR-COV', + 'GS001-DEPR-PIMG', + 'GS001-DEPR-BC', + 'GS001-DEPR-TIMG', + 'GS001-DEPR-C0H' + ); + + output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(2); + output.results.pass.should.be.an.Array().with.lengthOf(18); + + done(); + }).catch(done); + }); + + // TODO: write this as soon as we have new rules + it('does not check for latest versions checks'); }); - it('[mixed] should pass and fail when some rules pass and others fail', function (done) { - utils.testCheck(thisCheck, '001-deprecations/mixed').then(function (output) { - output.should.be.a.ValidThemeObject(); - - output.results.fail.should.be.an.Object().with.keys( - 'GS001-DEPR-PURL', - 'GS001-DEPR-MD', - 'GS001-DEPR-IMG', - 'GS001-DEPR-COV', - 'GS001-DEPR-PIMG', - 'GS001-DEPR-BC', - 'GS001-DEPR-TIMG', - 'GS001-DEPR-C0H' - ); - - output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject(); - output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(2); - output.results.pass.should.be.an.Array().with.lengthOf(18); - - done(); - }).catch(done); + describe('latest version', function () { + it('[failure] theme is invalid', function (done) { + utils.testCheck(thisCheck, '001-deprecations/invalid').then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().with.keys( + 'GS001-DEPR-PURL', + 'GS001-DEPR-MD', + 'GS001-DEPR-IMG', + 'GS001-DEPR-COV', + 'GS001-DEPR-AIMG', + 'GS001-DEPR-PIMG', + 'GS001-DEPR-PAIMG', + 'GS001-DEPR-PAC', + 'GS001-DEPR-PTIMG', + 'GS001-DEPR-TSIMG', + 'GS001-DEPR-PPP', + 'GS001-DEPR-C0H', + 'GS001-DEPR-BC', + 'GS001-DEPR-CON-BC', + 'GS001-DEPR-AC', + 'GS001-DEPR-CON-AC', + 'GS001-DEPR-CON-AIMG', + 'GS001-DEPR-CON-PTIMG', + 'GS001-DEPR-CON-TSIMG', + 'GS001-DEPR-CON-IMG', + 'GS001-DEPR-CON-COV', + 'GS001-DEPR-CON-TIMG', + 'GS001-DEPR-TIMG', + 'GS001-DEPR-CSS-AT', + 'GS001-DEPR-CSS-PA', + 'GS001-DEPR-CSS-PATS' + ); + + // pageUrl + output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(3); + + // meta_description in
+ output.results.fail['GS001-DEPR-MD'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-MD'].failures.length.should.eql(1); + + // {{image}} + output.results.fail['GS001-DEPR-IMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-IMG'].failures.length.should.eql(2); + + // {{cover}} + output.results.fail['GS001-DEPR-COV'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-COV'].failures.length.should.eql(3); + + // {{author.image}} + output.results.fail['GS001-DEPR-AIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-AIMG'].failures.length.should.eql(2); + + // {{post.image}} + output.results.fail['GS001-DEPR-PIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PIMG'].failures.length.should.eql(1); + + // {{@blog.cover}} + output.results.fail['GS001-DEPR-BC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-BC'].failures.length.should.eql(1); + + // {{author.cover}} + output.results.fail['GS001-DEPR-AC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-AC'].failures.length.should.eql(2); + + // {{post.author.cover}} + output.results.fail['GS001-DEPR-PAC'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PAC'].failures.length.should.eql(1); + + // {{post.author.image}} + output.results.fail['GS001-DEPR-PAIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PAIMG'].failures.length.should.eql(1); + + // {{tag.image}} + output.results.fail['GS001-DEPR-TIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-TIMG'].failures.length.should.eql(1); + + // {{posts.tags.[4].image}} + output.results.fail['GS001-DEPR-PTIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PTIMG'].failures.length.should.eql(1); + + // {{tags.[4].image}} + output.results.fail['GS001-DEPR-TSIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-TSIMG'].failures.length.should.eql(1); + + // {{#if image}} + output.results.fail['GS001-DEPR-CON-IMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-IMG'].failures.length.should.eql(1); + + // {{#if cover}} + output.results.fail['GS001-DEPR-CON-COV'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-COV'].failures.length.should.eql(1); + + // {{#if tag.image}} + output.results.fail['GS001-DEPR-CON-TIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-TIMG'].failures.length.should.eql(1); + + // {{#if tags.[#].image}} + output.results.fail['GS001-DEPR-CON-TSIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-TSIMG'].failures.length.should.eql(1); + + // {{#if post.tags.[#].image}} + output.results.fail['GS001-DEPR-CON-PTIMG'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CON-PTIMG'].failures.length.should.eql(1); + + // {{@blog.posts_per_page}} + output.results.fail['GS001-DEPR-PPP'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PPP'].failures.length.should.eql(1); + + // {{content word="0"}} + output.results.fail['GS001-DEPR-C0H'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-C0H'].failures.length.should.eql(2); + + // css class .page + output.results.fail['GS001-DEPR-CSS-PA'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-PA'].failures.length.should.eql(2); + + // css class .page-template-{slug} + output.results.fail['GS001-DEPR-CSS-PATS'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-PATS'].failures.length.should.eql(2); + + // css class .achive-template + output.results.fail['GS001-DEPR-CSS-AT'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-CSS-AT'].failures.length.should.eql(1); + + output.results.pass.should.be.an.Object().which.is.empty(); + + done(); + }).catch(done); + }); + + it('[success] should show no error if no deprecated helpers used', function (done) { + utils.testCheck(thisCheck, '001-deprecations/valid').then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().which.is.empty(); + output.results.pass.should.be.an.Array().with.lengthOf(26); + + done(); + }).catch(done); + }); + + it('[mixed] should pass and fail when some rules pass and others fail', function (done) { + utils.testCheck(thisCheck, '001-deprecations/mixed').then(function (output) { + output.should.be.a.ValidThemeObject(); + + output.results.fail.should.be.an.Object().with.keys( + 'GS001-DEPR-PURL', + 'GS001-DEPR-MD', + 'GS001-DEPR-IMG', + 'GS001-DEPR-COV', + 'GS001-DEPR-PIMG', + 'GS001-DEPR-BC', + 'GS001-DEPR-TIMG', + 'GS001-DEPR-C0H' + ); + + output.results.fail['GS001-DEPR-PURL'].should.be.a.ValidFailObject(); + output.results.fail['GS001-DEPR-PURL'].failures.length.should.eql(2); + output.results.pass.should.be.an.Array().with.lengthOf(18); + + done(); + }).catch(done); + }); }); }); diff --git a/test/005-template-compile.test.js b/test/005-template-compile.test.js index eb06ae1f..cc8c4da0 100644 --- a/test/005-template-compile.test.js +++ b/test/005-template-compile.test.js @@ -3,8 +3,10 @@ var should = require('should'), // eslint-disable-line no-unused-vars thisCheck = require('../lib/checks/005-template-compile'); describe('Template compile', function () { + const options = {checkVersion: 'v1'}; + it('should output empty array for a theme with no templates', function (done) { - utils.testCheck(thisCheck, 'is-empty').then(function (output) { + utils.testCheck(thisCheck, 'is-empty', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.fail.should.be.an.Object().which.is.empty(); @@ -16,7 +18,7 @@ describe('Template compile', function () { }); it('should output empty array for a theme with valid templates', function (done) { - utils.testCheck(thisCheck, '005-compile/valid').then(function (output) { + utils.testCheck(thisCheck, '005-compile/valid', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.fail.should.be.an.Object().which.is.empty(); @@ -29,7 +31,7 @@ describe('Template compile', function () { }); it('should output errors for a theme with invalid templates', function (done) { - utils.testCheck(thisCheck, '005-compile/invalid').then(function (output) { + utils.testCheck(thisCheck, '005-compile/invalid', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().which.is.empty(); @@ -57,7 +59,7 @@ describe('Template compile', function () { }); it('theme with partials and unknown helper', function (done) { - utils.testCheck(thisCheck, 'theme-with-partials').then(function (output) { + utils.testCheck(thisCheck, 'theme-with-partials', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.fail.should.be.an.Object().with.keys('GS005-TPL-ERR'); diff --git a/test/010-package-json.test.js b/test/010-package-json.test.js index 72222072..e9793dac 100644 --- a/test/010-package-json.test.js +++ b/test/010-package-json.test.js @@ -3,8 +3,10 @@ var should = require('should'), // eslint-disable-line no-unused-vars thisCheck = require('../lib/checks/010-package-json'); describe('010: package.json', function () { + const options = {checkVersion: 'v1'}; + it('should output error for missing package.json', function (done) { - utils.testCheck(thisCheck, 'is-empty').then(function (output) { + utils.testCheck(thisCheck, 'is-empty', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(0); @@ -29,7 +31,7 @@ describe('010: package.json', function () { }); it('should output error for missing package.json', function (done) { - utils.testCheck(thisCheck, 'is-empty').then(function (output) { + utils.testCheck(thisCheck, 'is-empty', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(0); @@ -54,7 +56,7 @@ describe('010: package.json', function () { }); it('should output error for invalid package.json (parsing)', function (done) { - utils.testCheck(thisCheck, '010-packagejson/parse-error').then(function (output) { + utils.testCheck(thisCheck, '010-packagejson/parse-error', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(0); @@ -82,7 +84,7 @@ describe('010: package.json', function () { }); it('valid fields', function (done) { - utils.testCheck(thisCheck, '010-packagejson/fields-are-valid').then(function (theme) { + utils.testCheck(thisCheck, '010-packagejson/fields-are-valid', options).then(function (theme) { theme.should.be.a.ValidThemeObject(); theme.results.pass.should.eql([ @@ -105,7 +107,7 @@ describe('010: package.json', function () { }); it('invalid fields', function (done) { - utils.testCheck(thisCheck, '010-packagejson/fields-are-invalid').then(function (theme) { + utils.testCheck(thisCheck, '010-packagejson/fields-are-invalid', options).then(function (theme) { theme.should.be.a.ValidThemeObject(); theme.results.pass.should.eql([ @@ -129,7 +131,7 @@ describe('010: package.json', function () { }); it('missing fields', function (done) { - utils.testCheck(thisCheck, '010-packagejson/fields-are-missing').then(function (theme) { + utils.testCheck(thisCheck, '010-packagejson/fields-are-missing', options).then(function (theme) { theme.should.be.a.ValidThemeObject(); theme.results.pass.should.eql([ @@ -153,7 +155,7 @@ describe('010: package.json', function () { }); it('bad config (ppp: -3 > 0)', function (done) { - utils.testCheck(thisCheck, '010-packagejson/bad-config').then(function (theme) { + utils.testCheck(thisCheck, '010-packagejson/bad-config', options).then(function (theme) { theme.should.be.a.ValidThemeObject(); theme.results.pass.should.eql([ @@ -177,7 +179,7 @@ describe('010: package.json', function () { }); it('bad config 2 (ppp: 0 > 0)', function (done) { - utils.testCheck(thisCheck, '010-packagejson/bad-config-2').then(function (theme) { + utils.testCheck(thisCheck, '010-packagejson/bad-config-2', options).then(function (theme) { theme.should.be.a.ValidThemeObject(); theme.results.pass.should.eql([ diff --git a/test/020-theme-structure.test.js b/test/020-theme-structure.test.js index 8a5a5dc4..87cf1af2 100644 --- a/test/020-theme-structure.test.js +++ b/test/020-theme-structure.test.js @@ -4,8 +4,10 @@ var should = require('should'), // eslint-disable-line no-unused-vars thisCheck = require('../lib/checks/020-theme-structure'); describe('Theme structure', function () { + const options = {checkVersion: 'v1'}; + it('should fail all rules if no files present', function (done) { - utils.testCheck(thisCheck, 'is-empty').then(function (output) { + utils.testCheck(thisCheck, 'is-empty', options).then(function (output) { output.should.be.a.ValidThemeObject(); // Should not pass any rules @@ -21,7 +23,7 @@ describe('Theme structure', function () { }); it('should pass and fail when some rules pass and others fail', function (done) { - utils.testCheck(thisCheck, '020-structure/mixed').then(function (output) { + utils.testCheck(thisCheck, '020-structure/mixed', options).then(function (output) { output.should.be.a.ValidThemeObject(); // Should pass the index rule @@ -38,7 +40,7 @@ describe('Theme structure', function () { }); it('should still fail with just a recommendation', function (done) { - utils.testCheck(thisCheck, '020-structure/recommendation').then(function (output) { + utils.testCheck(thisCheck, '020-structure/recommendation', options).then(function (output) { output.should.be.a.ValidThemeObject(); // Should not pass any rules diff --git a/test/030-assets.test.js b/test/030-assets.test.js index 9bb72ce0..f5cd5b5a 100644 --- a/test/030-assets.test.js +++ b/test/030-assets.test.js @@ -3,8 +3,10 @@ var should = require('should'), // eslint-disable-line no-unused-vars thisCheck = require('../lib/checks/030-assets'); describe('Assets', function () { + const options = {checkVersion: 'v1'}; + it('should show a warning for missing asset helper when an asset is detected', function (done) { - utils.testCheck(thisCheck, '030-assets/missing').then(function (output) { + utils.testCheck(thisCheck, '030-assets/missing', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(1); @@ -22,7 +24,7 @@ describe('Assets', function () { }); it('should pass when asset helper is present', function (done) { - utils.testCheck(thisCheck, '030-assets/valid').then(function (output) { + utils.testCheck(thisCheck, '030-assets/valid', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.fail.should.be.an.Object().which.is.empty(); @@ -36,7 +38,7 @@ describe('Assets', function () { }); it('should show error when symlink is present', function (done) { - utils.testCheck(thisCheck, '030-assets/symlink').then(function (output) { + utils.testCheck(thisCheck, '030-assets/symlink', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(1); diff --git a/test/040-ghost-head-foot.test.js b/test/040-ghost-head-foot.test.js index ed12b205..1fd46b83 100644 --- a/test/040-ghost-head-foot.test.js +++ b/test/040-ghost-head-foot.test.js @@ -3,8 +3,10 @@ var should = require('should'), // eslint-disable-line no-unused-vars thisCheck = require('../lib/checks/040-ghost-head-foot'); describe('Ghost head & foot', function () { + const options = {checkVersion: 'v1'}; + it('should show warnings for missing ghost head & foot helpers when no .hbs files are present', function (done) { - utils.testCheck(thisCheck, 'is-empty').then(function (output) { + utils.testCheck(thisCheck, 'is-empty', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().which.is.empty(); @@ -19,7 +21,7 @@ describe('Ghost head & foot', function () { }); it('should show warnings for missing ghost head & foot helpers when they are not in any .hbs file', function (done) { - utils.testCheck(thisCheck, '040-head-foot/missing').then(function (output) { + utils.testCheck(thisCheck, '040-head-foot/missing', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().which.is.empty(); @@ -34,7 +36,7 @@ describe('Ghost head & foot', function () { }); it('should output nothing when ghost head & foot helpers are present', function (done) { - utils.testCheck(thisCheck, '040-head-foot/valid').then(function (output) { + utils.testCheck(thisCheck, '040-head-foot/valid', options).then(function (output) { output.should.be.a.ValidThemeObject(); output.results.pass.should.be.an.Array().with.lengthOf(2); diff --git a/test/general.test.js b/test/general.test.js index 5e3a75d7..5196d652 100644 --- a/test/general.test.js +++ b/test/general.test.js @@ -1,17 +1,17 @@ -var should = require('should'), - sinon = require('sinon'), - path = require('path'), - rewire = require('rewire'), - _ = require('lodash'), - pfs = require('../lib/promised-fs'), - checkZip = require('../lib').checkZip, - themePath = require('./utils').themePath, - readZip = require('../lib/read-zip'), - readTheme = rewire('../lib/read-theme'), - checker = require('../lib/checker'), - format = require('../lib/format'), - - sandbox = sinon.sandbox.create(); +const should = require('should'); +const sinon = require('sinon'); +const path = require('path'); +const rewire = require('rewire'); +const _ = require('lodash'); +const pfs = require('../lib/promised-fs'); +const checkZip = require('../lib').checkZip; +const themePath = require('./utils').themePath; +const readZip = require('../lib/read-zip'); +const readTheme = rewire('../lib/read-theme'); +const checker = require('../lib/checker'); +const format = require('../lib/format'); + +const sandbox = sinon.sandbox.create(); process.env.NODE_ENV = 'testing'; @@ -29,7 +29,7 @@ function testReadZip(name) { } describe('Zip file handler can read zip files', function () { - after(function (done) { + after((done) => { pfs.remove('./test/tmp', function (err) { done(err); }); @@ -40,90 +40,96 @@ describe('Zip file handler can read zip files', function () { }); it('Flat example: zip without folder should unzip and callback with a path', function (done) { - testReadZip('flat-example.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.not.match(/flat-example$/); - zip.path.should.eql(zip.origPath); - zip.origName.should.eql('flat-example'); - done(); - }).catch(done); + testReadZip('flat-example.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.not.match(/flat-example$/); + zip.path.should.eql(zip.origPath); + zip.origName.should.eql('flat-example'); + done(); + }).catch(done); }); it('Simple example: zip with same-name folder should unzip and callback with a path, resolving base dir', function (done) { - testReadZip('example.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.match(/\/example$/); - zip.path.should.not.eql(zip.origPath); - zip.origName.should.eql('example'); - done(); - }).catch(done); + testReadZip('example.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.match(/\/example$/); + zip.path.should.not.eql(zip.origPath); + zip.origName.should.eql('example'); + done(); + }).catch(done); }); it('Bad example: zip with dif-name folder should unzip and callback with a path, resolving base dir', function (done) { - testReadZip('bad-example.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.match(/\/bad-example-folder/); - zip.path.should.not.eql(zip.origPath); - zip.origName.should.eql('bad-example'); - done(); - }).catch(done); + testReadZip('bad-example.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.match(/\/bad-example-folder/); + zip.path.should.not.eql(zip.origPath); + zip.origName.should.eql('bad-example'); + done(); + }).catch(done); }); it('Nested example: zip with nested folders should unzip and callback with a path, resolving base dir', function (done) { - testReadZip('nested-example.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.match(/\/nested-example\/bad-example-folder$/); - zip.path.should.not.eql(zip.origPath); - zip.origName.should.eql('nested-example'); - done(); - }).catch(done); + testReadZip('nested-example.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.match(/\/nested-example\/bad-example-folder$/); + zip.path.should.not.eql(zip.origPath); + zip.origName.should.eql('nested-example'); + done(); + }).catch(done); }); it('Multi example: complex zip should unzip and callback with a path, resolving base dir', function (done) { - testReadZip('multi-example.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.match(/\/multi-example\/theme\/theme-name/); - zip.path.should.not.eql(zip.origPath); - zip.origName.should.eql('multi-example'); - done(); - }).catch(done); + testReadZip('multi-example.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.match(/\/multi-example\/theme\/theme-name/); + zip.path.should.not.eql(zip.origPath); + zip.origName.should.eql('multi-example'); + done(); + }).catch(done); }); // If the zip file does not contain index.hbs, we return the standard path, and our checks will report the errors it('No index.hbs example: zip should unzip and callback with a path', function (done) { - testReadZip('not-a-theme.zip').then(function (zip) { - zip.path.should.be.a.String; - zip.origPath.should.be.a.String; - zip.name.should.be.a.String; - zip.origName.should.be.a.String; - zip.path.should.not.match(/not-a-theme$/); - zip.path.should.eql(zip.origPath); - zip.origName.should.eql('not-a-theme'); - done(); - }).catch(done); + testReadZip('not-a-theme.zip') + .then((zip) => { + zip.path.should.be.a.String; + zip.origPath.should.be.a.String; + zip.name.should.be.a.String; + zip.origName.should.be.a.String; + zip.path.should.not.match(/not-a-theme$/); + zip.path.should.eql(zip.origPath); + zip.origName.should.eql('not-a-theme'); + done(); + }).catch(done); }); }); describe('check zip', function () { describe('ensure ignored assets are getting ignored', function () { it('default', function () { - return checkZip(themePath('030-assets/ignored.zip'), {keepExtractedDir: true}) - .then(function (theme) { + return checkZip(themePath('030-assets/ignored.zip'), {keepExtractedDir: true, checkVersion: 'v1'}) + .then((theme) => { theme.files.length.should.eql(1); theme.files[0].file.should.match(/default\.hbs/); @@ -135,8 +141,8 @@ describe('check zip', function () { }); it('Don\'t remove files if theme not in tmp directory', function () { - return checker(themePath('030-assets/ignored')) - .then(function (theme) { + return checker(themePath('030-assets/ignored'), {checkVersion: 'v1'}) + .then((theme) => { theme.files.length.should.eql(1); theme.files[0].file.should.match(/default\.hbs/); @@ -151,7 +157,7 @@ describe('check zip', function () { describe('Read theme', function () { it('returns correct result', function (done) { - readTheme(themePath('is-empty')).then(function (theme) { + readTheme(themePath('is-empty')).then((theme) => { theme.should.be.a.ValidThemeObject(); theme.files.should.eql([ @@ -163,12 +169,12 @@ describe('Read theme', function () { }); it('Can read partials', function (done) { - readTheme(themePath('theme-with-partials')).then(function (theme) { + readTheme(themePath('theme-with-partials')).then((theme) => { theme.should.be.a.ValidThemeObject(); theme.files.should.be.an.Array().with.lengthOf(7); - var fileNames = _.map(theme.files, function (file) { + const fileNames = _.map(theme.files, function (file) { return _.pickBy(file, function (value, key) { return key === 'file' || key === 'ext'; }); @@ -187,7 +193,7 @@ describe('Read theme', function () { }); it('Can extract custom templates', function (done) { - readTheme(themePath('theme-with-custom-templates')).then(function (theme) { + readTheme(themePath('theme-with-custom-templates')).then((theme) => { theme.should.be.a.ValidThemeObject(); theme.files.should.be.an.Array().with.lengthOf(11); @@ -262,7 +268,7 @@ describe('Read Hbs Files', function () { it('can read partials with POSIX paths', function (done) { // This roughly matches Example I - var exampleI = [ + const exampleI = [ {file: 'index.hbs', ext: '.hbs'}, {file: 'package.json', ext: '.json'}, {file: 'partialsbroke.hbs', ext: '.hbs'}, @@ -276,7 +282,7 @@ describe('Read Hbs Files', function () { readTheme.__get__('readHbsFiles')({ files: exampleI, path: 'fake/example-i' - }).then(function (result) { + }).then((result) => { result.partials.should.be.an.Array().with.lengthOf(2); result.partials.should.eql(['mypartial', 'subfolder/test']); done(); @@ -285,7 +291,7 @@ describe('Read Hbs Files', function () { it('can read partials with windows paths', function (done) { // This matches Example I, but on Windows - var exampleI = [ + const exampleI = [ {file: 'index.hbs', ext: '.hbs'}, {file: 'package.json', ext: '.json'}, {file: 'partialsbroke.hbs', ext: '.hbs'}, @@ -298,7 +304,7 @@ describe('Read Hbs Files', function () { files: exampleI, path: 'fake\\example-i' }) - .then(function (result) { + .then((result) => { result.partials.should.be.an.Array().with.lengthOf(2); result.partials.should.eql(['mypartial', 'subfolder\\test']); done(); @@ -308,7 +314,42 @@ describe('Read Hbs Files', function () { describe('Checker', function () { it('returns a valid theme when running all checks', function (done) { - checker(themePath('is-empty')).then(function (theme) { + checker(themePath('is-empty')).then((theme) => { + theme.should.be.a.ValidThemeObject(); + + theme.files.should.eql([ + {file: '.gitkeep', ext: '.gitkeep'}, + {file: 'README.md', ext: '.md'} + ]); + + theme.results.pass.should.be.an.Array().with.lengthOf(31); + theme.results.pass.should.containEql('GS005-TPL-ERR', 'GS030-ASSET-REQ', 'GS030-ASSET-SYM'); + + theme.results.fail.should.be.an.Object().with.keys( + 'GS010-PJ-REQ', + 'GS010-PJ-PARSE', + 'GS010-PJ-NAME-REQ', + 'GS010-PJ-NAME-LC', + 'GS010-PJ-NAME-HY', + 'GS010-PJ-VERSION-SEM', + 'GS010-PJ-VERSION-REQ', + 'GS010-PJ-AUT-EM-VAL', + 'GS010-PJ-AUT-EM-REQ', + 'GS010-PJ-CONF-PPP', + 'GS020-INDEX-REQ', + 'GS020-POST-REQ', + 'GS020-DEF-REC', + 'GS040-GH-REQ', + 'GS040-GF-REQ' + ); + + done(); + }); + }); + + // TODO: this needs to be activated as soon as we have new checks for the ruleset + it.skip('checks for an older version if passed', function (done) { + checker(themePath('is-empty'), {checkVersion: 'v1'}).then((theme) => { theme.should.be.a.ValidThemeObject(); theme.files.should.eql([ @@ -342,7 +383,7 @@ describe('Checker', function () { }); it('should not follow symlinks', function (done) { - checker(themePath('030-assets/symlink2')).then(function (theme) { + checker(themePath('030-assets/symlink2')).then((theme) => { theme.should.be.a.ValidThemeObject(); theme.files.should.containEql({file: 'assets/mysymlink', ext: undefined}); theme.results.fail.should.containEql('GS030-ASSET-SYM'); @@ -354,7 +395,7 @@ describe('Checker', function () { describe('format', function () { it('assert sorting', function (done) { - checker(themePath('005-compile/invalid')).then(function (theme) { + checker(themePath('005-compile/invalid')).then((theme) => { theme = format(theme); theme.results.error[0].fatal.should.eql(true); @@ -366,7 +407,7 @@ describe('format', function () { }); it('assert sorting', function (done) { - checker(themePath('is-empty')).then(function (theme) { + checker(themePath('is-empty')).then((theme) => { theme = format(theme); theme.results.error[0].fatal.should.eql(true); diff --git a/test/utils.js b/test/utils.js index a767b0f4..f2687911 100644 --- a/test/utils.js +++ b/test/utils.js @@ -56,7 +56,7 @@ testCheck = function testCheck(checkLib, themeId, options) { var themePath = getThemePath(themeId); return readTheme(themePath).then(function runCheck(theme) { - return checkLib.call(this, theme, themePath, options); + return checkLib.call(this, theme, options, themePath); }); };