diff --git a/.gitignore b/.gitignore index 40d356b94..c767370ce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules npm-debug.log .DS_STORE *.swp +tmp diff --git a/build.js b/build.js index c65d8135b..38350f9ef 100644 --- a/build.js +++ b/build.js @@ -1,12 +1,46 @@ -require('./src/header-build')() -require('./src/gallery-build')() -require('./src/resources-build')() -console.log('header build complete') -require('./src/table-of-styles-build')() -console.log('table of styles build complete') -require('./src/table-of-properties-build')() -console.log('table of properties build complete') -require('./src/home-build')() -console.log('home build complete') -//require('./src/components-build')() -//console.log('components build complete') +const chalk = require('chalk'); +const co = require('co'); +const prettyHrtime = require('pretty-hrtime'); + +const startTime = process.hrtime(); + +// require('./src/header-build')() +// require('./src/gallery-build')() +// require('./src/resources-build')() +// console.log('header build complete') +// require('./src/table-of-styles-build')() +// console.log('table of styles build complete') +// require('./src/table-of-properties-build')() +// console.log('table of properties build complete') +// require('./src/home-build')() +// console.log('home build complete') + +const componentsBuildIndex = require('./src/components-build-index'); +const componentsBuildPages = require('./src/components-build-pages'); +const componentsBuildScreenshots = require('./src/components-build-screenshots'); + +// See src/components-build-defaults for list of options that can be overriden +const options = { + // componentsGlobPattern: 'src/components/buttons/*.html', +}; + +// Note that componentsBuildIndex() generates the index *and* the JSON +// file listing all the components pages and screenshots should be built for. +// Scenario: if you are working on one component, uncomment and set componentsGlobPattern +// in the options above to only (re-)generate the index, pages, and screenshots for the +// corresponding category. When you are done, comment componentsGlobPattern back, comment +// the line below that generate the screenshots, then run the script -- this will +// re-create the full index page for all components, re-generate all the pages (since they need +// to cross-reference the new component), and use the previously generated screenshots as well +// as the new one (i.e. no need to re-generate *all* the screenshots unless you made +// modifications to the screenshots script itself). +co(function* generator() { + yield componentsBuildIndex(options); // <- builds component index page and JSON index + yield componentsBuildPages(options); // <- comment to skip building pages + yield componentsBuildScreenshots(options); // <- comment to skip building screenshots +}).then(() => { + const elapsed = process.hrtime(startTime); + console.log(chalk.green('All done'), chalk.dim(prettyHrtime(elapsed))); +}).catch((err) => { + console.log(err); +}); diff --git a/js/lazysizes.min.js b/js/lazysizes.min.js new file mode 100644 index 000000000..d24b1bbe1 --- /dev/null +++ b/js/lazysizes.min.js @@ -0,0 +1,2 @@ +/*! lazysizes - v3.0.0-rc2 */ +!function(a,b){var c=b(a,a.document);a.lazySizes=c,"object"==typeof module&&module.exports&&(module.exports=c)}(window,function(a,b){"use strict";if(b.getElementsByClassName){var c,d=b.documentElement,e=a.Date,f=a.HTMLPictureElement,g="addEventListener",h="getAttribute",i=a[g],j=a.setTimeout,k=a.requestAnimationFrame||j,l=a.requestIdleCallback,m=/^picture$/i,n=["load","error","lazyincluded","_lazyloaded"],o={},p=Array.prototype.forEach,q=function(a,b){return o[b]||(o[b]=new RegExp("(\\s|^)"+b+"(\\s|$)")),o[b].test(a[h]("class")||"")&&o[b]},r=function(a,b){q(a,b)||a.setAttribute("class",(a[h]("class")||"").trim()+" "+b)},s=function(a,b){var c;(c=q(a,b))&&a.setAttribute("class",(a[h]("class")||"").replace(c," "))},t=function(a,b,c){var d=c?g:"removeEventListener";c&&t(a,b),n.forEach(function(c){a[d](c,b)})},u=function(a,c,d,e,f){var g=b.createEvent("CustomEvent");return g.initCustomEvent(c,!e,!f,d||{}),a.dispatchEvent(g),g},v=function(b,d){var e;!f&&(e=a.picturefill||c.pf)?e({reevaluate:!0,elements:[b]}):d&&d.src&&(b.src=d.src)},w=function(a,b){return(getComputedStyle(a,null)||{})[b]},x=function(a,b,d){for(d=d||a.offsetWidth;df&&(f=0),a||9>f&&l?i():j(i,f))}},B=function(a){var b,c,d=99,f=function(){b=null,a()},g=function(){var a=e.now()-c;d>a?j(g,d-a):(l||f)(f)};return function(){c=e.now(),b||(b=j(g,d))}},C=function(){var f,k,l,n,o,x,C,E,F,G,H,I,J,K,L,M=/^img$/i,N=/^iframe$/i,O="onscroll"in a&&!/glebot/.test(navigator.userAgent),P=0,Q=0,R=0,S=-1,T=function(a){R--,a&&a.target&&t(a.target,T),(!a||0>R||!a.target)&&(R=0)},U=function(a,c){var e,f=a,g="hidden"==w(b.body,"visibility")||"hidden"!=w(a,"visibility");for(F-=c,I+=c,G-=c,H+=c;g&&(f=f.offsetParent)&&f!=b.body&&f!=d;)g=(w(f,"opacity")||1)>0,g&&"visible"!=w(f,"overflow")&&(e=f.getBoundingClientRect(),g=H>e.left&&Ge.top-1&&FR&&(a=f.length)){e=0,S++,null==K&&("expand"in c||(c.expand=d.clientHeight>500&&d.clientWidth>500?500:370),J=c.expand,K=J*c.expFactor),K>Q&&1>R&&S>2&&o>2&&!b.hidden?(Q=K,S=0):Q=o>1&&S>1&&6>R?J:P;for(;a>e;e++)if(f[e]&&!f[e]._lazyRace)if(O)if((p=f[e][h]("data-expand"))&&(m=1*p)||(m=Q),q!==m&&(C=innerWidth+m*L,E=innerHeight+m,n=-1*m,q=m),g=f[e].getBoundingClientRect(),(I=g.bottom)>=n&&(F=g.top)<=E&&(H=g.right)>=n*L&&(G=g.left)<=C&&(I||H||G||F)&&(l&&3>R&&!p&&(3>o||4>S)||U(f[e],m))){if(ba(f[e]),j=!0,R>9)break}else!j&&l&&!i&&4>R&&4>S&&o>2&&(k[0]||c.preloadAfterLoad)&&(k[0]||!p&&(I||H||G||F||"auto"!=f[e][h](c.sizesAttr)))&&(i=k[0]||f[e]);else ba(f[e]);i&&!j&&ba(i)}},W=A(V),X=function(a){r(a.target,c.loadedClass),s(a.target,c.loadingClass),t(a.target,Z)},Y=z(X),Z=function(a){Y({target:a.target})},$=function(a,b){try{a.contentWindow.location.replace(b)}catch(c){a.src=b}},_=function(a){var b,d,e=a[h](c.srcsetAttr);(b=c.customMedia[a[h]("data-media")||a[h]("media")])&&a.setAttribute("media",b),e&&a.setAttribute("srcset",e),b&&(d=a.parentNode,d.insertBefore(a.cloneNode(),a),d.removeChild(a))},aa=z(function(a,b,d,e,f){var g,i,k,l,o,q;(o=u(a,"lazybeforeunveil",b)).defaultPrevented||(e&&(d?r(a,c.autosizesClass):a.setAttribute("sizes",e)),i=a[h](c.srcsetAttr),g=a[h](c.srcAttr),f&&(k=a.parentNode,l=k&&m.test(k.nodeName||"")),q=b.firesLoad||"src"in a&&(i||g||l),o={target:a},q&&(t(a,T,!0),clearTimeout(n),n=j(T,2500),r(a,c.loadingClass),t(a,Z,!0)),l&&p.call(k.getElementsByTagName("source"),_),i?a.setAttribute("srcset",i):g&&!l&&(N.test(a.nodeName)?$(a,g):a.src=g),(i||l)&&v(a,{src:g})),a._lazyRace&&delete a._lazyRace,s(a,c.lazyClass),y(function(){(!q||a.complete)&&(q?T(o):R--,X(o))},!0)}),ba=function(a){var b,d=M.test(a.nodeName),e=d&&(a[h](c.sizesAttr)||a[h]("sizes")),f="auto"==e;(!f&&l||!d||!a.src&&!a.srcset||a.complete||q(a,c.errorClass))&&(b=u(a,"lazyunveilread").detail,f&&D.updateElem(a,!0,a.offsetWidth),a._lazyRace=!0,R++,aa(a,b,f,e,d))},ca=function(){if(!l){if(e.now()-x<999)return void j(ca,999);var a=B(function(){c.loadMode=3,W()});l=!0,c.loadMode=3,W(),i("scroll",function(){3==c.loadMode&&(c.loadMode=2),a()},!0)}};return{_:function(){x=e.now(),f=b.getElementsByClassName(c.lazyClass),k=b.getElementsByClassName(c.lazyClass+" "+c.preloadClass),L=c.hFac,i("scroll",W,!0),i("resize",W,!0),a.MutationObserver?new MutationObserver(W).observe(d,{childList:!0,subtree:!0,attributes:!0}):(d[g]("DOMNodeInserted",W,!0),d[g]("DOMAttrModified",W,!0),setInterval(W,999)),i("hashchange",W,!0),["focus","mouseover","click","load","transitionend","animationend","webkitAnimationEnd"].forEach(function(a){b[g](a,W,!0)}),/d$|^c/.test(b.readyState)?ca():(i("load",ca),b[g]("DOMContentLoaded",W),j(ca,2e4)),f.length?(V(),y._lsFlush()):W()},checkElems:W,unveil:ba}}(),D=function(){var a,d=z(function(a,b,c,d){var e,f,g;if(a._lazysizesWidth=d,d+="px",a.setAttribute("sizes",d),m.test(b.nodeName||""))for(e=b.getElementsByTagName("source"),f=0,g=e.length;g>f;f++)e[f].setAttribute("sizes",d);c.detail.dataAttr||v(a,c.detail)}),e=function(a,b,c){var e,f=a.parentNode;f&&(c=x(a,f,c),e=u(a,"lazybeforesizes",{width:c,dataAttr:!!b}),e.defaultPrevented||(c=e.detail.width,c&&c!==a._lazysizesWidth&&d(a,f,e,c)))},f=function(){var b,c=a.length;if(c)for(b=0;c>b;b++)e(a[b])},g=B(f);return{_:function(){a=b.getElementsByClassName(c.autosizesClass),i("resize",g)},checkElems:g,updateElem:e}}(),E=function(){E.i||(E.i=!0,D._(),C._())};return function(){var b,d={lazyClass:"lazyload",loadedClass:"lazyloaded",loadingClass:"lazyloading",preloadClass:"lazypreload",errorClass:"lazyerror",autosizesClass:"lazyautosizes",srcAttr:"data-src",srcsetAttr:"data-srcset",sizesAttr:"data-sizes",minSize:40,customMedia:{},init:!0,expFactor:1.5,hFac:.8,loadMode:2};c=a.lazySizesConfig||a.lazysizesConfig||{};for(b in d)b in c||(c[b]=d[b]);a.lazySizesConfig=c,j(function(){c.init&&E()})}(),{cfg:c,autoSizer:D,loader:C,init:E,uP:v,aC:r,rC:s,hC:q,fire:u,gW:x,rAF:y}}}); \ No newline at end of file diff --git a/js/ls.unveilhooks.min.js b/js/ls.unveilhooks.min.js new file mode 100644 index 000000000..4e5d88fcb --- /dev/null +++ b/js/ls.unveilhooks.min.js @@ -0,0 +1,2 @@ +/*! lazysizes - v3.0.0-rc2 */ +!function(a,b){"use strict";function c(a,c){if(!f[a]){var d=b.createElement(c?"link":"script"),e=b.getElementsByTagName("script")[0];c?(d.rel="stylesheet",d.href=a):d.src=a,f[a]=!0,f[d.src||d.href]=!0,e.parentNode.insertBefore(d,e)}}var d,e,f={};b.addEventListener&&(e=/\(|\)|'/,d=function(a,c){var d=b.createElement("img");d.onload=function(){d.onload=null,d.onerror=null,d=null,c()},d.onerror=d.onload,d.src=a,d&&d.complete&&d.onload&&d.onload()},addEventListener("lazybeforeunveil",function(a){var b,f,g,h;a.defaultPrevented||("none"==a.target.preload&&(a.target.preload="auto"),b=a.target.getAttribute("data-link"),b&&c(b,!0),b=a.target.getAttribute("data-script"),b&&c(b),b=a.target.getAttribute("data-require"),b&&(lazySizes.cfg.requireJs?lazySizes.cfg.requireJs([b]):c(b)),g=a.target.getAttribute("data-bg"),g&&(a.detail.firesLoad=!0,f=function(){a.target.style.backgroundImage="url("+(e.test(g)?JSON.stringify(g):g)+")",a.detail.firesLoad=!1,lazySizes.fire(a.target,"_lazyloaded",{},!0,!0)},d(g,f)),h=a.target.getAttribute("data-poster"),h&&(a.detail.firesLoad=!0,f=function(){a.target.poster=h,a.detail.firesLoad=!1,lazySizes.fire(a.target,"_lazyloaded",{},!0,!0)},d(h,f)))},!1))}(window,document); \ No newline at end of file diff --git a/package.json b/package.json index bcd45e178..3cfc7d54e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "doc:images": "watch 'node src/modules/images.js' src/templates/docs/images", "doc:bgsize": "watch 'node src/modules/background-size.js' src/templates/docs/background-size", "build:all": "node build.js && bash script.sh", - "cson": "node src/components-cson-build.js" + "cson": "node src/components-cson-build.js", + "copy:lazysizes": "cp node_modules/lazysizes/lazysizes.min.js js/", + "copy:lazysizes:plugins": "cp node_modules/lazysizes/plugins/unveilhooks/ls.unveilhooks.min.js js/", + "postinstall": "npm run copy:lazysizes && npm run copy:lazysizes:plugins" }, "repository": "tachyons-css/tachyons-css.github.io", "author": "mrmrs", @@ -33,6 +36,16 @@ "url": "https://github.com/tachyons-css/tachyons-css.github.io/issues" }, "dependencies": { + "chalk": "^1.1.3", + "co": "^4.6.0", + "express": "^4.14.0", + "filesize": "^3.3.0", + "imagemin": "^5.2.2", + "imagemin-mozjpeg": "^6.0.0", + "jimp": "^0.2.27", + "lazysizes": "^3.0.0-rc2", + "nightmare": "^2.8.1", + "pretty-hrtime": "^1.0.3", "tachyons-background-size": "^5.0.3", "tachyons-base": "^1.2.5", "tachyons-border-colors": "^4.2.2", @@ -81,7 +94,8 @@ "tachyons-white-space": "^4.0.1", "tachyons-widths": "^5.0.1", "tachyons-word-break": "3.0.1", - "tachyons-z-index": "^1.0.4" + "tachyons-z-index": "^1.0.4", + "tmp": "0.0.31" }, "devDependencies": { "autoprefixer": "^6.5.1", diff --git a/src/components-build-defaults.js b/src/components-build-defaults.js new file mode 100644 index 000000000..56c4d167a --- /dev/null +++ b/src/components-build-defaults.js @@ -0,0 +1,29 @@ +module.exports = { + // Components + componentsForNavPath: 'tmp/componentsForNav.json', // temporary file built by the index + componentsGlobPattern: 'src/components/**/*.html', // source components to process + componentsIndexPath: 'components/index.html', // target location of components index + componentsBuildPages: true, // false to skip building pages + componentsBuildScreenshots: true, // false to skip building screenshots + // Screenshots + screenshotName: 'screenshot.jpg', // name JPEG screenshot in each component dir + screenshotAspectRatio: '4x3', // Tachyon aspect ratio of screenshot in index + screenshotViewportWidth: 1024, // viewport width used for capture + screenshotViewportHeight: 768, // viewport height used for capture + screenshotTargetMinWidth: 400, // min width of target, resized screenshot + screenshotTargetMinHeight: 160, // min height of target, resized screenshot + mozjpegQuality: 90, // mozjpeg optimizer quality (default 75) + screenshotSelector: '[data-name="component-container"]', // DOM element to capture + // Misc + tachyonsCssPath: 'src/css/tachyons.css', + serverPort: 3333, + // Templates + analyticsTemplatePath: 'src/templates/ga.html', + componentsIndexTemplatePath: 'src/templates/components-index.html', + componentsTemplatePath: 'src/templates/components.html', + footerTemplatePath: 'src/templates/footer.html', + headerTemplatePath: 'src/templates/header.html', + headTemplatePath: 'src/templates/head.html', + highlightTemplatePath: 'src/templates/highlight.html', + lazysizesTemplate: 'src/templates/lazysizes.html', +}; diff --git a/src/components-build-index.js b/src/components-build-index.js new file mode 100644 index 000000000..92215ddf6 --- /dev/null +++ b/src/components-build-index.js @@ -0,0 +1,102 @@ +const _ = require('lodash'); +const chalk = require('chalk'); +const crypto = require('crypto'); +const fm = require('json-front-matter'); +const fs = require('fs'); +const glob = require('glob'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const prettyHrtime = require('pretty-hrtime'); +const rmHtmlExt = require('remove-html-extension'); +const titleize = require('titleize'); + +const defaults = require('./components-build-defaults'); + +const getTitle = (component) => { + const title = rmHtmlExt(component).replace('src/components/', '').replace(/(\/|_|-)/g, ' '); + return titleize(title); +}; + +const getName = component => titleize(getTitle(component.split('/')[3])); + +module.exports = _options => new Promise((resolve, reject) => { + const options = _.assign({}, defaults, _options); + const startTime = process.hrtime(); + glob(options.componentsGlobPattern, {}, (err, components) => { + console.log(chalk.magenta('Working on components index...')); + if (err) { + reject(err); + return; + } + + const npmPackage = JSON.parse(fs.readFileSync('package.json', 'utf8')); + + const componentsForNav = {}; + components.forEach((component) => { + const componentTokens = component.replace('src/components/', '').split('/'); + const category = componentTokens[0]; + + const componentHtml = fs.readFileSync(component, 'utf8'); + const fmParsed = fm.parse(componentHtml); + const frontMatter = fmParsed.attributes || {}; + const dir = component.replace('src/', '').replace('.html', ''); + + // Compute component signature based on the Tachyons version and the contents of the + // component itself. This can be used to bust the browser cache of screenshots. + const md5sum = crypto.createHash('md5'); + md5sum.update(npmPackage.version); + md5sum.update(componentHtml); + const signature = md5sum.digest('hex'); + + componentsForNav[category] = componentsForNav[category] || []; + componentsForNav[category].push({ + name: frontMatter.name || getName(component), + title: frontMatter.title || getTitle(component), + src: component, + path: `${dir}/index.html`, + href: `/${dir}/index.html`, + screenshot: { + path: `${dir}/${options.screenshotName}`, + href: `/${dir}/${options.screenshotName}?version=${signature}`, + }, + signature, + frontMatter, + }); + }); + + const categories = Object.keys(componentsForNav); + console.log( + '- Found', components.length, components.length > 1 ? 'components' : 'component', + 'in', categories.length, categories.length > 1 ? 'categories' : 'category' + ); + + mkdirp.sync(path.dirname(options.componentsForNavPath)); + fs.writeFileSync(options.componentsForNavPath, JSON.stringify(componentsForNav, undefined, 2)); + console.log('- Created navigation JSON:', options.componentsForNavPath); + + const analytics = fs.readFileSync(options.analyticsTemplatePath, 'utf8'); + const footer = fs.readFileSync(options.footerTemplatePath, 'utf8'); + const head = fs.readFileSync(options.headTemplatePath, 'utf8'); + const header = fs.readFileSync(options.headerTemplatePath, 'utf8'); + const componentsIndexTemplate = fs.readFileSync(options.componentsIndexTemplatePath, 'utf8'); + const lazysizesTemplate = fs.readFileSync(options.lazysizesTemplate, 'utf8'); + + const compiledPage = _.template(componentsIndexTemplate)({ + componentsForNav, + title: 'Components', + analytics, + footer, + head, + header, + lazysizesTemplate, + options, + }); + mkdirp.sync(path.dirname(options.componentsIndexPath)); + fs.writeFileSync(options.componentsIndexPath, compiledPage); + console.log('- Created index:', options.componentsIndexPath); + + const elapsed = process.hrtime(startTime); + console.log(chalk.magenta('Done with components index!'), chalk.dim(prettyHrtime(elapsed))); + resolve(); + }); // glob +}); // return promise diff --git a/src/components-build-pages.js b/src/components-build-pages.js new file mode 100644 index 000000000..a8aff16c3 --- /dev/null +++ b/src/components-build-pages.js @@ -0,0 +1,117 @@ +const _ = require('lodash'); +const atImport = require('postcss-import'); +const chalk = require('chalk'); +const co = require('co'); +const conditionals = require('postcss-conditionals'); +const cssstats = require('cssstats'); +const cssVariables = require('postcss-css-variables'); +const customMedia = require('postcss-custom-media'); +const escapeHtml = require('escape-html'); +const fm = require('json-front-matter'); +const fs = require('fs'); +const getClasses = require('get-classes-from-html'); +const mkdirp = require('mkdirp'); +const mqPacker = require('css-mqpacker'); +const path = require('path'); +const perfectionist = require('perfectionist'); +const postcss = require('postcss'); +const prettyHrtime = require('pretty-hrtime'); +const removeComments = require('postcss-discard-comments'); +const removeEmpty = require('postcss-discard-empty'); +const select = require('postcss-select'); + +const defaults = require('./components-build-defaults'); + +module.exports = _options => new Promise((resolve, reject) => { + const options = _.assign({}, defaults, _options); + const startTime = process.hrtime(); + console.log(chalk.magenta('Working on components pages...')); + if (options.componentsForNavPath === undefined || !fs.existsSync(options.componentsForNavPath)) { + reject('Can not find components nav JSON file'); + return; + } + if (!options.componentsBuildPages) { + console.log(chalk.dim('Skipped by request.')); + resolve(); + return; + } + const componentsForNav = JSON.parse(fs.readFileSync(options.componentsForNavPath, 'utf8')); + + const componentTemplate = fs.readFileSync(options.componentsTemplatePath, 'utf8'); + const analytics = fs.readFileSync(options.analyticsTemplatePath, 'utf8'); + const footer = fs.readFileSync(options.footerTemplatePath, 'utf8'); + const head = fs.readFileSync(options.headTemplatePath, 'utf8'); + const highlight = fs.readFileSync(options.highlightTemplatePath, 'utf8'); + + const tachyonsCss = fs.readFileSync(options.tachyonsCssPath, 'utf8'); + + const renderPromise = co(function* generator() { + // Unfortunately, can't use forEach() in generators, so let's for()... + const categories = Object.keys(componentsForNav); + for (let cat_idx = 0; cat_idx < categories.length; cat_idx += 1) { + const category = categories[cat_idx]; + console.log(chalk.yellow('- Processing category:'), category); + + for (let comp_idx = 0; comp_idx < componentsForNav[category].length; comp_idx += 1) { + const component = componentsForNav[category][comp_idx]; + const componentHtml = fs.readFileSync(component.src, 'utf8'); + const fmParsed = fm.parse(componentHtml); + + const frontMatter = _.assign({}, component.frontMatter); + frontMatter.title = component.title; + frontMatter.name = component.name; + frontMatter.bodyClass = frontMatter.bodyClass || ''; + frontMatter.classes = getClasses(fmParsed.body).map(klass => `.${klass}`); + frontMatter.componentHtml = componentHtml; + frontMatter.content = fmParsed.body; + frontMatter.escapedHtml = escapeHtml(fmParsed.body); + frontMatter.footer = footer; + frontMatter.analytics = analytics; + frontMatter.head = head; + frontMatter.highlight = highlight; + frontMatter.componentsForNav = componentsForNav; + + const moduleSrcs = {}; + const getModules = postcss.plugin('get-modules', () => (css) => { + css.walkRules((rule) => { + moduleSrcs[rule.source.input.from] = true; + }); + }); + + yield postcss([ + atImport(), + cssVariables(), + conditionals(), + customMedia(), + select(frontMatter.classes), + removeComments({ removeAll: true }), + mqPacker(), + removeEmpty(), + getModules(), + perfectionist(), + ]).process(tachyonsCss, { + from: options.tachyonsCssPath, + }).then((result) => { + frontMatter.componentCss = result.css; + frontMatter.stats = cssstats(frontMatter.componentCss); + + // TODO: Update me once src/ uses the npm modules + frontMatter.modules = Object.keys(moduleSrcs).map( + module => `tachyons-${module.split('/_')[1].replace('.css', '')}` + ); + + const compiledComponent = _.template(componentTemplate)(frontMatter); + mkdirp.sync(path.dirname(component.path)); + fs.writeFileSync(component.path, compiledComponent); + console.log(' * Created page for:', component.src); + }).catch((e) => { + console.log(e); + }); + } + } + }).then(() => { + const elapsed = process.hrtime(startTime); + console.log(chalk.magenta('Done with components pages!'), chalk.dim(prettyHrtime(elapsed))); + }); + resolve(renderPromise); +}); // return promise diff --git a/src/components-build-screenshots.js b/src/components-build-screenshots.js new file mode 100644 index 000000000..556519f81 --- /dev/null +++ b/src/components-build-screenshots.js @@ -0,0 +1,187 @@ +const _ = require('lodash'); +const chalk = require('chalk'); +const co = require('co'); +const express = require('express'); +const filesize = require('filesize'); +const fs = require('fs'); +const imagemin = require('imagemin'); +const imageminMozjpeg = require('imagemin-mozjpeg'); +const Jimp = require('jimp'); +const Nightmare = require('nightmare'); +const path = require('path'); +const prettyHrtime = require('pretty-hrtime'); +const tmp = require('tmp'); + +const defaults = require('./components-build-defaults'); + +const startServer = options => new Promise((resolve) => { + const app = express(); + app.set('port', options.serverPort); + app.use(express.static(path.join(__dirname, '..'))); + const server = app.listen(app.get('port'), () => { + console.log('- Started static HTML server on port:', server.address().port); + resolve(server); + }); +}); + +const initNightmare = () => Nightmare({ // eslint-disable-line + show: false, + frame: false, + useContentSize: true, + switches: { + // Unfortunately .viewport() sets the viewport size in *CSS pixels*. + // Let's force device pixel ratio to 1 otherwise screenshots will vary in size + // depending on the machine running this script (double for Retina Macbook Pro) + // https://github.com/segmentio/nightmare/issues/498#issuecomment-265948400 + 'force-device-scale-factor': '1', + }, +}); + +const formatFromSizeToSize = (from, to) => `(${filesize(from)} => ${filesize(to)})`; + +module.exports = _options => new Promise((resolve, reject) => { + const options = _.assign({}, defaults, _options); + const startTime = process.hrtime(); + console.log(chalk.magenta('Working on components screenshots...')); + if (options.componentsForNavPath === undefined || !fs.existsSync(options.componentsForNavPath)) { + reject('Can not find components nav JSON file'); + return; + } + if (!options.componentsBuildScreenshots) { + console.log(chalk.dim('Skipped by request.')); + resolve(); + return; + } + const componentsForNav = JSON.parse(fs.readFileSync(options.componentsForNavPath, 'utf8')); + + const nightmare = initNightmare(); + + let tmpTotalFileSize = 0; + let screenshotTotalFileSize = 0; + const renderPromise = co(function* generator() { + // Setup a quick static HTML server so that CSS is loaded correctly. + const server = yield startServer(options); + + // Unfortunately, can't use forEach() in generators, so let's for()... + const categories = Object.keys(componentsForNav); + for (let cat_idx = 0; cat_idx < categories.length; cat_idx += 1) { + const category = categories[cat_idx]; + console.log(chalk.yellow('- Processing category:'), category); + + for (let comp_idx = 0; comp_idx < componentsForNav[category].length; comp_idx += 1) { + const component = componentsForNav[category][comp_idx]; + let selector = options.screenshotSelector; + if (component.frontMatter.screenshot && component.frontMatter.screenshot.selector) { + selector = `${selector} ${component.frontMatter.screenshot.selector}`; + } + + // Grab the size of the component enclosing rectangle + // https://github.com/segmentio/nightmare/issues/498#issuecomment-189156529 + const componentRect = yield nightmare + .viewport(options.screenshotViewportWidth, options.screenshotViewportHeight) + // .wait(1000) + .goto(`http://localhost:${options.serverPort}${component.href}`) + .wait(selector) + .evaluate((_selector) => { + // Hide scrollbar that could pop up due to .scrollTo + const sheet = document.styleSheets[0]; + sheet.insertRule('::-webkit-scrollbar { display:none; }'); + const element = document.querySelector(_selector); + if (element) { + const rect = element.getBoundingClientRect(); + return { + x: Math.round(rect.left), + y: Math.round(rect.top), + width: Math.round(rect.width), + height: Math.round(rect.height), + }; + } + return false; + }, selector); + + // Capture the component + if (componentRect === false) { + console.log(chalk.red(' * FAILED to create screenshot:'), component.screenshot.name); + continue; // eslint-disable-line + } + + const screenshotDir = path.dirname(component.screenshot.path); + const tmpPngObj = tmp.fileSync({ dir: screenshotDir }); + componentRect.height = Math.min(componentRect.height, options.screenshotViewportHeight); + yield nightmare + // we can not use .screenshot() with componentRect, so constrain the viewport instead + .viewport( + componentRect.width || options.screenshotViewportWidth, + componentRect.height || options.screenshotViewportHeight + ).scrollTo(componentRect.y, componentRect.x) + // .wait(1000) + .screenshot(tmpPngObj.name); // do *not* use componentRect here or risk distortions + + // Resize and convert to JPEG, and optimize + const tmpJpegDirObj = tmp.dirSync({ dir: screenshotDir, unsafeCleanup: true }); + const tmpJpegPath = path.join(tmpJpegDirObj.name, path.basename(component.screenshot.path)); + const screenshot = yield Jimp.read(tmpPngObj.name); + yield new Promise((write_resolve, write_reject) => { + if (component.frontMatter.screenshot === undefined || + component.frontMatter.screenshot.autocrop !== false) { + screenshot.autocrop(false); + } + // Allow shrinking, up to a point + const scaleHeight = screenshot.bitmap.height <= options.screenshotTargetMinHeight + ? 0.0 : options.screenshotTargetMinHeight / screenshot.bitmap.height; + const scaleWidth = screenshot.bitmap.width <= options.screenshotTargetMinWidth + ? 0.0 : options.screenshotTargetMinWidth / screenshot.bitmap.width; + const scale = Math.max(scaleHeight, scaleWidth); + screenshot + .scale(scale > 0 ? scale : 1.0) + // Do not use .quality() here, default max quality helps optimizers below + .write(tmpJpegPath, (jimp_err) => { + if (jimp_err) { + write_reject(jimp_err); + } + // Optimize + imagemin([tmpJpegPath], screenshotDir, { + plugins: [ + imageminMozjpeg({ quality: options.mozjpegQuality }), + // imageminJpegRecompress(), // this guy is useless + // imageminJpegtran(), // this guy is useless + ], + }).then(() => { + write_resolve(); + }).catch((imagemin_err) => { + write_reject(imagemin_err); + }); + }); + }); + + const tmpFileSize = fs.statSync(tmpPngObj.name).size; + tmpTotalFileSize += tmpFileSize; + const screenshotFileSize = fs.statSync(component.screenshot.path).size; + screenshotTotalFileSize += screenshotFileSize; + + // Cleanup + tmpPngObj.removeCallback(); + fs.unlinkSync(tmpJpegPath); + tmpJpegDirObj.removeCallback(); + + console.log( + ' * Created screenshot:', + component.screenshot.path, + chalk.dim(formatFromSizeToSize(tmpFileSize, screenshotFileSize)) + ); + } // Loop over components + } // Loop over categories + + yield nightmare.end(); + server.close(); + console.log('- Closed static HTML server'); + const elapsed = process.hrtime(startTime); + console.log( + chalk.magenta('Done with components screenshots!'), + chalk.dim(formatFromSizeToSize(tmpTotalFileSize, screenshotTotalFileSize)), + chalk.dim(prettyHrtime(elapsed)) + ); + }); + + resolve(renderPromise); +}); // return promise diff --git a/src/components-build.js b/src/components-build.js deleted file mode 100644 index 822f5e71f..000000000 --- a/src/components-build.js +++ /dev/null @@ -1,127 +0,0 @@ -var fs = require('fs') -var _ = require('lodash') -var path = require('path') -var mkdirp = require('mkdirp') -var glob = require('glob') -var titleize = require('titleize') -var fm = require('json-front-matter') -var escapeHtml = require('escape-html') -var rmHtmlExt = require('remove-html-extension') -var getClasses = require('get-classes-from-html') -var postcss = require('postcss') -var select = require('postcss-select') -var atImport = require('postcss-import') -var conditionals = require('postcss-conditionals') -var removeComments = require('postcss-discard-comments') -var perfectionist = require('perfectionist') -var removeEmpty = require('postcss-discard-empty') -var cssVariables = require('postcss-css-variables') -var customMedia = require('postcss-custom-media') -var mqPacker = require('css-mqpacker') -var cssstats = require('cssstats') - -var tachyonsCss = fs.readFileSync('src/css/tachyons.css', 'utf8') -var footer = fs.readFileSync('src/templates/footer.html', 'utf8') -var analytics = fs.readFileSync('src/templates/ga.html', 'utf8') -var head = fs.readFileSync('src/templates/head.html', 'utf8') -var header = fs.readFileSync('src/templates/header.html', 'utf8') -var highlight = fs.readFileSync('src/templates/highlight.html', 'utf8') - -module.exports = function () { - glob('src/components/**/*.html', {}, function (err, components) { - if (err) { - console.error(err) - return - } - - var template = fs.readFileSync('src/templates/components.html', 'utf8') - var indexTemplate = fs.readFileSync('src/templates/components-index.html', 'utf8') - - var componentsForNav = {} - components.map(function (component) { - var componentTokens = component.replace('src/components/', '').split('/') - var category = componentTokens[0] - - componentsForNav[category] = componentsForNav[category] || [] - componentsForNav[category].push({ - href: component.replace('src', '').replace('.html', '') + '/index.html', - name: getName(component) - }) - }) - - var compiledPage = _.template(indexTemplate)({ - componentsForNav: componentsForNav, - title: 'Components', - analytics: analytics, - footer: footer, - head: head, - header: header, - }) - - mkdirp.sync('components') - fs.writeFileSync('components/index.html', compiledPage) - - components.forEach(function (component) { - var newDir = rmHtmlExt(component.replace('src/', '')) - var newFile = newDir + '/index.html' - var componentHtml = fs.readFileSync(component, 'utf8') - - var fmParsed = fm.parse(componentHtml) - var frontMatter = fmParsed.attributes || {} - frontMatter.bodyClass = frontMatter.bodyClass || '' - frontMatter.title = frontMatter.title || getTitle(component) - frontMatter.name = frontMatter.name || getName(component) - frontMatter.classes = getClasses(fmParsed.body).map(function(klass) { - return '.' + klass - }) - frontMatter.componentHtml = componentHtml - frontMatter.content = fmParsed.body - frontMatter.escapedHtml = escapeHtml(fmParsed.body) - frontMatter.footer = footer - frontMatter.analytics = analytics - frontMatter.head = head - frontMatter.highlight = highlight - frontMatter.componentsForNav = componentsForNav - - var moduleSrcs = {} - var getModules = postcss.plugin('get-modules', function () { - return function (css, result) { - css.walkRules(function (rule) { - moduleSrcs[rule.source.input.from] = true - }) - } - }) - - postcss([ - atImport(), cssVariables(), conditionals(), customMedia(), select(frontMatter.classes), - removeComments({ removeAll: true }), mqPacker(), removeEmpty(), getModules(), perfectionist() - ]).process(tachyonsCss, { - from: 'src/css/tachyons.css' - }).then(function (result) { - console.log('component css selection complete for', component) - frontMatter.componentCss = result.css - frontMatter.stats = cssstats(frontMatter.componentCss) - - // TODO: Update me once src/ uses the npm modules - frontMatter.modules = Object.keys(moduleSrcs).map(function (module) { - return 'tachyons-' + module.split('/_')[1].replace('.css', '') - }) - - var compiledPage = _.template(template)(frontMatter) - console.log('creating new dir', newDir) - mkdirp.sync(newDir) - fs.writeFileSync(newFile, compiledPage) - console.log('finished component build for', component) - }).catch(function (e) { console.log(e) }) - }) - }) -} - -function getTitle(component) { - var title = rmHtmlExt(component).replace('src/components/', '').replace(/(\/|_|-)/g, ' ') - return titleize(title) -} - -function getName(component) { - return titleize(getTitle(component.split('/')[3])) -} diff --git a/src/components-home-build.js b/src/components-home-build.js deleted file mode 100644 index b1081a93d..000000000 --- a/src/components-home-build.js +++ /dev/null @@ -1,73 +0,0 @@ -var fs = require('fs') -var _ = require('lodash') -var path = require('path') -var mkdirp = require('mkdirp') -var glob = require('glob') -var titleize = require('titleize') -var fm = require('json-front-matter') -var escapeHtml = require('escape-html') -var rmHtmlExt = require('remove-html-extension') -var getClasses = require('get-classes-from-html') -var postcss = require('postcss') -var select = require('postcss-select') -var atImport = require('postcss-import') -var conditionals = require('postcss-conditionals') -var removeComments = require('postcss-discard-comments') -var perfectionist = require('perfectionist') -var removeEmpty = require('postcss-discard-empty') -var cssVariables = require('postcss-css-variables') -var customMedia = require('postcss-custom-media') -var mqPacker = require('css-mqpacker') -var cssstats = require('cssstats') - -var tachyonsCss = fs.readFileSync('src/css/tachyons.css', 'utf8') -var footer = fs.readFileSync('src/templates/footer.html', 'utf8') -var analytics = fs.readFileSync('src/templates/ga.html', 'utf8') -var head = fs.readFileSync('src/templates/head.html', 'utf8') -var header = fs.readFileSync('src/templates/header.html', 'utf8') -var highlight = fs.readFileSync('src/templates/highlight.html', 'utf8') - -module.exports = function () { - glob('src/components/**/*.html', {}, function (err, components) { - if (err) { - console.error(err) - return - } - - var template = fs.readFileSync('src/templates/components.html', 'utf8') - var indexTemplate = fs.readFileSync('src/templates/components-index.html', 'utf8') - - var componentsForNav = {} - components.map(function (component) { - var componentTokens = component.replace('src/components/', '').split('/') - var category = componentTokens[0] - - componentsForNav[category] = componentsForNav[category] || [] - componentsForNav[category].push({ - href: component.replace('src', '').replace('.html', '') + '/index.html', - name: getName(component) - }) - }) - - var compiledPage = _.template(indexTemplate)({ - componentsForNav: componentsForNav, - title: 'Components', - analytics: analytics, - footer: footer, - head: head, - header: header, - }) - - mkdirp.sync('components') - fs.writeFileSync('components/index.html', compiledPage) - }) -} - -function getTitle(component) { - var title = rmHtmlExt(component).replace('src/components/', '').replace(/(\/|_|-)/g, ' ') - return titleize(title) -} - -function getName(component) { - return titleize(getTitle(component.split('/')[3])) -} diff --git a/src/components-nav-build.js b/src/components-nav-build.js deleted file mode 100644 index bd62be07d..000000000 --- a/src/components-nav-build.js +++ /dev/null @@ -1,61 +0,0 @@ -var fs = require('fs') -var _ = require('lodash') -var path = require('path') -var mkdirp = require('mkdirp') -var glob = require('glob') -var titleize = require('titleize') -var fm = require('json-front-matter') -var escapeHtml = require('escape-html') -var rmHtmlExt = require('remove-html-extension') -var getClasses = require('get-classes-from-html') -var postcss = require('postcss') -var select = require('postcss-select') -var atImport = require('postcss-import') -var conditionals = require('postcss-conditionals') -var removeComments = require('postcss-discard-comments') -var perfectionist = require('perfectionist') -var removeEmpty = require('postcss-discard-empty') -var cssVariables = require('postcss-css-variables') -var customMedia = require('postcss-custom-media') -var mqPacker = require('css-mqpacker') -var cssstats = require('cssstats') - -module.exports = function () { - glob('src/components/**/*.html', {}, function (err, components) { - if (err) { - console.error(err) - return - } - - var indexTemplate = fs.readFileSync('src/templates/components-nav-template.html', 'utf8') - - var componentsForNav = {} - components.map(function (component) { - var componentTokens = component.replace('src/components/', '').split('/') - var category = componentTokens[0] - - componentsForNav[category] = componentsForNav[category] || [] - componentsForNav[category].push({ - href: component.replace('src', '').replace('.html', '') + '/index.html', - name: getName(component) - }) - }) - - var compiledPage = _.template(indexTemplate)({ - componentsForNav: componentsForNav, - title: 'Components', - }) - - mkdirp.sync('components') - fs.writeFileSync('src/templates/components-nav.html', compiledPage) - }) -} - -function getTitle(component) { - var title = rmHtmlExt(component).replace('src/components/', '').replace(/(\/|_|-)/g, ' ') - return titleize(title) -} - -function getName(component) { - return titleize(getTitle(component.split('/')[3])) -} diff --git a/src/components/article-lists/title-preview-author-media-flipped.html b/src/components/article-lists/title-preview-author-media-flipped.html index c911fef70..f9f6455d2 100644 --- a/src/components/article-lists/title-preview-author-media-flipped.html +++ b/src/components/article-lists/title-preview-author-media-flipped.html @@ -26,7 +26,7 @@

Tech Giant Invests Huge Money to Build a Com

A whale takes up residence in a large body of water

This giant of a whale says it is ready to begin planning a new - swim later this afternoon. A powerful mammal that relies on fish and plankton instead + swim later this afternoon. A powerful mammal that relies on fish and plankton instead of hamburgers.

@@ -45,7 +45,7 @@

Archaeologists have found more than 40 tons of vinyl records, - some more than a five years old, shedding light on early hipster + some more than a five years old, shedding light on early hipster trends.

diff --git a/src/components/article-lists/title-preview-author-media.html b/src/components/article-lists/title-preview-author-media.html index 3291aad0b..8ec6714a2 100644 --- a/src/components/article-lists/title-preview-author-media.html +++ b/src/components/article-lists/title-preview-author-media.html @@ -53,7 +53,7 @@

Giant Whale Invests Huge Money to Bu Whale is the common name for a widely distributed and diverse group of fully aquatic placental marine mammals. They are an informal grouping within the infraorder Cetacea, usually - excluding dolphins and porpoises. + excluding dolphins and porpoises.

By Robin Darnell

diff --git a/src/components/articles/full-bleed-background.html b/src/components/articles/full-bleed-background.html index 3e05efc7d..d2b945dc0 100644 --- a/src/components/articles/full-bleed-background.html +++ b/src/components/articles/full-bleed-background.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}
diff --git a/src/components/articles/headline-title-text.html b/src/components/articles/headline-title-text.html index 23706afa6..fec6aab70 100644 --- a/src/components/articles/headline-title-text.html +++ b/src/components/articles/headline-title-text.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}

Title

diff --git a/src/components/articles/large-title-text.html b/src/components/articles/large-title-text.html index bbd376593..f17ba1e09 100644 --- a/src/components/articles/large-title-text.html +++ b/src/components/articles/large-title-text.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}

Title

diff --git a/src/components/articles/left-title-top-border.html b/src/components/articles/left-title-top-border.html index 4a9f7a98b..5136406c5 100644 --- a/src/components/articles/left-title-top-border.html +++ b/src/components/articles/left-title-top-border.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}
diff --git a/src/components/articles/left-title.html b/src/components/articles/left-title.html index 1f60eb7cb..07ec55611 100644 --- a/src/components/articles/left-title.html +++ b/src/components/articles/left-title.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}
diff --git a/src/components/articles/single-column-large-title.html b/src/components/articles/single-column-large-title.html index 172ac2f5f..ab470a87d 100644 --- a/src/components/articles/single-column-large-title.html +++ b/src/components/articles/single-column-large-title.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}
@@ -16,12 +19,12 @@

A Night of Taking P Before it burned to the ground, the structure filled a small beach inlet below the Cliff House, also owned by Adolph Sutro at the time. Shortly after closing, a fire in 1966 destroyed the building - while it was in the process of being demolished. + while it was in the process of being demolished.

During high tides, water would flow directly into the pools from the - nearby ocean, recycling the two million US gallons of water in about an hour. + nearby ocean, recycling the two million US gallons of water in about an hour.

@@ -35,7 +38,7 @@

A Night of Taking P

During low tides, a powerful turbine water pump, built inside a cave at sea level, could be switched on from a control - room and could fill the tanks at a rate of 6,000 US gallons a minute, + room and could fill the tanks at a rate of 6,000 US gallons a minute, recycling all the water in five hours.

diff --git a/src/components/articles/title-highlight-header-cover.html b/src/components/articles/title-highlight-header-cover.html index 3bab1ba58..0ad2dcb61 100644 --- a/src/components/articles/title-highlight-header-cover.html +++ b/src/components/articles/title-highlight-header-cover.html @@ -1,5 +1,8 @@ {{{ -"bodyClass" : "bg-white" +"bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
@@ -9,7 +12,7 @@

Too many tools and frameworks -

+

The definitive guide to the javascript tooling landscape in 2015.

By Adam Morse
diff --git a/src/components/articles/title-text-image.html b/src/components/articles/title-text-image.html index 79e5f9747..20e9e1150 100644 --- a/src/components/articles/title-text-image.html +++ b/src/components/articles/title-text-image.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}

Title Text with Image

diff --git a/src/components/articles/title-text.html b/src/components/articles/title-text.html index 26072ec11..8318a5945 100644 --- a/src/components/articles/title-text.html +++ b/src/components/articles/title-text.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "contain" + } }}}

Title

diff --git a/src/components/avatars/circle-border.html b/src/components/avatars/circle-border.html index 11aebcfb1..6b00fe4ab 100644 --- a/src/components/avatars/circle-border.html +++ b/src/components/avatars/circle-border.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/avatars/circle.html b/src/components/avatars/circle.html index 19c7dbbc3..65c1888ff 100644 --- a/src/components/avatars/circle.html +++ b/src/components/avatars/circle.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/avatars/double-ring.html b/src/components/avatars/double-ring.html index 31edc6526..079cd6edf 100644 --- a/src/components/avatars/double-ring.html +++ b/src/components/avatars/double-ring.html @@ -1,5 +1,8 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
avatar diff --git a/src/components/avatars/rounded-large.html b/src/components/avatars/rounded-large.html index 99f6602b3..c2f79b91a 100644 --- a/src/components/avatars/rounded-large.html +++ b/src/components/avatars/rounded-large.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/avatars/rounded-medium.html b/src/components/avatars/rounded-medium.html index 8ffd8023a..39582145a 100644 --- a/src/components/avatars/rounded-medium.html +++ b/src/components/avatars/rounded-medium.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/avatars/rounded-small.html b/src/components/avatars/rounded-small.html index 0de7fc4c5..faa7c7d5f 100644 --- a/src/components/avatars/rounded-small.html +++ b/src/components/avatars/rounded-small.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/avatars/square.html b/src/components/avatars/square.html index cf9aaed81..d4ae1eed1 100644 --- a/src/components/avatars/square.html +++ b/src/components/avatars/square.html @@ -1,9 +1,12 @@ {{{ - "bodyClass" : "bg-white" + "bodyClass" : "bg-white", + "screenshot" : { + "background-size" : "auto" + } }}}
- avatar
diff --git a/src/components/banners/basic.html b/src/components/banners/basic.html index 0693f679d..0e5d2f42b 100644 --- a/src/components/banners/basic.html +++ b/src/components/banners/basic.html @@ -1,11 +1,14 @@ {{{ - "bodyClass" : "bg-white pt5" + "bodyClass" : "bg-white pt5", + "screenshot" : { + "autocrop": false + } }}}

This is a tagline. For x. -

+

This will change things. And we want you to be involved. This text needs to be longer for testing sake. @@ -14,11 +17,11 @@

Sign up for beta access or learn more about x.

- Sign Up - Learn More diff --git a/src/components/banners/info.html b/src/components/banners/info.html index 8eb1dbcc7..c8eea9dd5 100644 --- a/src/components/banners/info.html +++ b/src/components/banners/info.html @@ -1,5 +1,9 @@ {{{ - "bodyClass" : "bg-white pt5" + "bodyClass" : "bg-white pt5", + "screenshot" : { + "background-size" : "auto", + "autocrop": false + } }}}