├── .eslintrc.json ├── .glitch-assets ├── LICENSE.md ├── README.md ├── app.js ├── data-sync.js ├── generators ├── cellular-automata.js ├── circle-packing.js ├── cubic-disarray.js ├── joy-division.js ├── mondrian.js ├── tiled-lines.js ├── triangular-mesh.js └── un-deux-trois.js ├── helpers └── general.js ├── package-lock.json ├── package.json ├── public ├── css │ ├── css │ │ └── styles.css │ └── styles.css ├── js │ └── scripts.min.js └── libs │ ├── bootstrap │ ├── bootstrap.min.css │ └── bootstrap.min.js │ └── moment │ └── moment.min.js ├── routes ├── image.js ├── index.js └── stats.js ├── server.js ├── src ├── css │ ├── _layout.scss │ ├── _misc.scss │ ├── _typography.scss │ ├── _variables.scss │ └── styles.scss └── scripts │ ├── lazy-load-images.js │ └── scripts.js ├── views ├── 404.handlebars ├── home.handlebars ├── layouts │ └── main.handlebars ├── partials │ └── footer.handlebars └── stats.handlebars └── watch.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2018, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "env": { 10 | "browser": true, 11 | "node": true, 12 | "jquery": true, 13 | "worker": true, 14 | "mocha": true, 15 | "jest": true, 16 | "es6": true 17 | }, 18 | "extends": "eslint:recommended", 19 | "rules": { 20 | "no-undef": "error" 21 | }, 22 | "globals": { 23 | "helpers": true, 24 | "d3": true, 25 | "c3": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.glitch-assets: -------------------------------------------------------------------------------- 1 | {"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"} 2 | {"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"} 3 | {"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"} 4 | {"uuid":"adSBq97hhhpFNUna","deleted":true} 5 | {"uuid":"adSBq97hhhpFNUnb","deleted":true} 6 | {"uuid":"adSBq97hhhpFNUnc","deleted":true} 7 | {"name":"null.png","date":"2018-09-13T21:12:59.652Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fnull.png","type":"image/png","size":5255,"imageWidth":128,"imageHeight":128,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fnull.png","thumbnailWidth":128,"thumbnailHeight":128,"dominantColor":"rgb(76,172,220)","uuid":"y4bX37K73cQTbTjE"} 8 | {"name":"2017.csv","date":"2019-01-21T15:12:16.368Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2F2017.csv","type":"application/vnd.ms-excel","size":29536,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2F2017.csv","thumbnailWidth":210,"thumbnailHeight":210,"dominantColor":"rgba(255, 236, 186, 0.60)","uuid":"wOOvgPOSuM8xW7vR"} 9 | {"uuid":"wOOvgPOSuM8xW7vR","deleted":true} 10 | {"uuid":"y4bX37K73cQTbTjE","deleted":true} 11 | {"name":"2017.csv","date":"2019-01-21T15:14:07.948Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2F2017.csv","type":"application/vnd.ms-excel","size":29536,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2F2017.csv","thumbnailWidth":210,"thumbnailHeight":210,"dominantColor":"rgba(14, 11, 107, 0.60)","uuid":"3SjGFdBJhJ9IzYNp"} 12 | {"name":"world-happiness-2017.csv","date":"2019-01-22T04:09:42.044Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fworld-happiness-2017.csv","type":"application/vnd.ms-excel","size":29536,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fworld-happiness-2017.csv","thumbnailWidth":210,"thumbnailHeight":210,"dominantColor":"rgba(156, 13, 196, 0.60)","uuid":"kUKUzdQ56vNvq69E"} 13 | {"uuid":"3SjGFdBJhJ9IzYNp","deleted":true} 14 | {"name":"botwiki-donations-patreon.png","date":"2019-01-24T03:20:58.231Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fbotwiki-donations-patreon.png","type":"image/png","size":46005,"imageWidth":1423,"imageHeight":547,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fbotwiki-donations-patreon.png","thumbnailWidth":330,"thumbnailHeight":127,"dominantColor":"rgb(252,252,252)","uuid":"4AdM1AYFonILCskS"} 15 | {"uuid":"4AdM1AYFonILCskS","deleted":true} 16 | {"name":"botwiki-donations-patreon.png","date":"2019-01-24T03:22:12.865Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fbotwiki-donations-patreon.png","type":"image/png","size":36612,"imageWidth":1379,"imageHeight":445,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fbotwiki-donations-patreon.png","thumbnailWidth":330,"thumbnailHeight":107,"dominantColor":"rgb(252,252,252)","uuid":"jfZFWni8pSFi7faf"} 17 | {"name":"world-happiness-gdp.png","date":"2019-01-24T03:30:23.215Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fworld-happiness-gdp.png","type":"image/png","size":43247,"imageWidth":1435,"imageHeight":577,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fworld-happiness-gdp.png","thumbnailWidth":330,"thumbnailHeight":133,"dominantColor":"rgb(252,252,252)","uuid":"3XP7wnCfLNmS40Qn"} 18 | {"name":"c3js-starter-project.png","date":"2019-01-24T03:41:08.497Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fc3js-starter-project.png","type":"image/png","size":96156,"imageWidth":720,"imageHeight":720,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fc3js-starter-project.png","thumbnailWidth":330,"thumbnailHeight":330,"dominantColor":"rgb(36,124,180)","uuid":"SDLyedkgn3nAizja"} 19 | {"name":"c3js-glitch-data-visualization-intro.png","date":"2019-01-24T03:48:05.049Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fc3js-glitch-data-visualization-intro.png","type":"image/png","size":68016,"imageWidth":1198,"imageHeight":435,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fc3js-glitch-data-visualization-intro.png","thumbnailWidth":330,"thumbnailHeight":120,"dominantColor":"rgb(36,124,180)","uuid":"EXUltnkNHU2lMkBh"} 20 | {"name":"can-money-buy-happiness-chart-c3js.gif","date":"2019-01-24T11:22:59.750Z","url":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fcan-money-buy-happiness-chart-c3js.gif","type":"image/gif","size":882676,"imageWidth":2560,"imageHeight":1198,"thumbnail":"https://cdn.glitch.com/bb24b6d8-027c-40bd-a846-8f00555863f5%2Fthumbnails%2Fcan-money-buy-happiness-chart-c3js.gif","thumbnailWidth":330,"thumbnailHeight":155,"dominantColor":"rgb(252,252,252)","uuid":"iSFwY3ElJ4jojQh0"} 21 | {"uuid":"iSFwY3ElJ4jojQh0","deleted":true} 22 | {"uuid":"3XP7wnCfLNmS40Qn","deleted":true} 23 | {"uuid":"SDLyedkgn3nAizja","deleted":true} 24 | {"uuid":"EXUltnkNHU2lMkBh","deleted":true} 25 | {"uuid":"kUKUzdQ56vNvq69E","deleted":true} 26 | {"uuid":"jfZFWni8pSFi7faf","deleted":true} 27 | {"name":"popular-twitter-bots-chart-animated.gif","date":"2019-10-07T12:59:54.341Z","url":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fpopular-twitter-bots-chart-animated.gif","type":"image/gif","size":117668,"imageWidth":600,"imageHeight":338,"thumbnail":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fthumbnails%2Fpopular-twitter-bots-chart-animated.gif","thumbnailWidth":330,"thumbnailHeight":186,"uuid":"y34I33rkxveCLNkz"} 28 | {"name":"chart.png","date":"2019-10-07T13:00:35.415Z","url":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fchart.png","type":"image/png","size":296496,"imageWidth":2366,"imageHeight":1148,"thumbnail":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fthumbnails%2Fchart.png","thumbnailWidth":330,"thumbnailHeight":161,"uuid":"d9SVdalkuFH0u06N"} 29 | {"name":"chart-900px.png","date":"2019-10-07T13:01:06.057Z","url":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fchart-900px.png","type":"image/png","size":111548,"imageWidth":900,"imageHeight":437,"thumbnail":"https://cdn.glitch.com/eafa115d-c307-4f55-8c74-1140bb1d0ef7%2Fthumbnails%2Fchart-900px.png","thumbnailWidth":330,"thumbnailHeight":161,"uuid":"aB5bQQq4avpNRYJg"} 30 | {"uuid":"y34I33rkxveCLNkz","deleted":true} 31 | {"uuid":"d9SVdalkuFH0u06N","deleted":true} 32 | {"uuid":"aB5bQQq4avpNRYJg","deleted":true} 33 | {"name":"generative.png","date":"2019-12-23T23:32:48.954Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fgenerative.png","type":"image/png","size":2874,"imageWidth":400,"imageHeight":400,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fgenerative.png","thumbnailWidth":330,"thumbnailHeight":330,"uuid":"2Gehu9oAnoymntjW"} 34 | {"name":"example-05.png","date":"2020-01-05T20:59:40.933Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-05.png","type":"image/png","size":12539,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-05.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"8sIj9Htj9OpYbgyq"} 35 | {"name":"example-01.png","date":"2020-01-05T20:59:41.010Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-01.png","type":"image/png","size":2277,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-01.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"TsIcpiz0cLwmbAiB"} 36 | {"name":"example-06.png","date":"2020-01-05T20:59:41.259Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-06.png","type":"image/png","size":47234,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-06.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"k6hKOeoKg1gFXqq4"} 37 | {"name":"example-02.png","date":"2020-01-05T20:59:41.460Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-02.png","type":"image/png","size":20520,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-02.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"hjqnNAjn9WX2cBrr"} 38 | {"name":"example-07.png","date":"2020-01-05T20:59:41.888Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-07.png","type":"image/png","size":52916,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-07.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"GO2ZE9wl7OObatlC"} 39 | {"name":"example-09.png","date":"2020-01-05T20:59:41.962Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-09.png","type":"image/png","size":7429,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-09.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"1W9uLycFyy2JMlvR"} 40 | {"name":"example-10.png","date":"2020-01-05T20:59:42.160Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-10.png","type":"image/png","size":47005,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-10.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"semeRZ2BxbncW1eL"} 41 | {"name":"example-12.png","date":"2020-01-05T20:59:42.419Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-12.png","type":"image/png","size":1965,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-12.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"uoumwLwC8HtfROtl"} 42 | {"name":"example-13.png","date":"2020-01-05T20:59:42.552Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-13.png","type":"image/png","size":48228,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-13.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"UDFtKaEEdmZicdEB"} 43 | {"name":"example-11.png","date":"2020-01-05T20:59:42.598Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-11.png","type":"image/png","size":1534,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-11.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"hAZXCKwA6OEuYxxD"} 44 | {"name":"example-14.png","date":"2020-01-05T20:59:42.857Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-14.png","type":"image/png","size":146514,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-14.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"KqyDcc3u4RnJTSb5"} 45 | {"name":"example-03.png","date":"2020-01-05T20:59:43.212Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-03.png","type":"image/png","size":131886,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-03.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"dQhkHoq9GF4T80Hm"} 46 | {"name":"example-04.png","date":"2020-01-05T20:59:43.413Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-04.png","type":"image/png","size":80582,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-04.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"H2cUpF2YU6zYWdeR"} 47 | {"name":"example-08.png","date":"2020-01-05T20:59:43.750Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fexample-08.png","type":"image/png","size":140259,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fexample-08.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"HTtHZYBvZVvt1aBq"} 48 | {"name":"mondrian.png","date":"2020-01-07T00:35:56.384Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fmondrian.png","type":"image/png","size":2161,"imageWidth":600,"imageHeight":300,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fthumbnails%2Fmondrian.png","thumbnailWidth":330,"thumbnailHeight":165,"uuid":"JBEwQ0t4kKB1VpEL"} 49 | {"name":"indiewebcamp.png","date":"2020-12-07T18:27:42.423Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Findiewebcamp.png","type":"image/png","size":6297,"imageWidth":140,"imageHeight":140,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Findiewebcamp.png","thumbnailWidth":140,"thumbnailHeight":140,"uuid":"SqafQtYSPJPgHjLt"} 50 | {"name":"fight-fascism.png","date":"2020-12-07T18:27:42.423Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Ffight-fascism.png","type":"image/png","size":7279,"imageWidth":140,"imageHeight":140,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Ffight-fascism.png","thumbnailWidth":140,"thumbnailHeight":140,"uuid":"38Z2gpmfb0iIOeqG"} 51 | {"name":"blm.png","date":"2020-12-07T18:27:42.461Z","url":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fblm.png","type":"image/png","size":7337,"imageWidth":140,"imageHeight":140,"thumbnail":"https://cdn.glitch.com/4e796d0c-e4ff-46fc-a546-de1ccc1e8663%2Fblm.png","thumbnailWidth":140,"thumbnailHeight":140,"uuid":"fHhki9Y7uz0S3IqY"} 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stefan Bohacek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Random cellular automata generative art](https://generative-placeholders.glitch.me/image?width=800&height=300&style=cellular-automata&cells=80&colors=2) 2 | 3 | # Generative Placeholders 4 | 5 | Use generative art for your placeholder images. 6 | 7 | Visit the project page for [examples and instructions](https://generative-placeholders.glitch.me/). -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'), 2 | express = require('express'), 3 | compression = require('compression'), 4 | exphbs = require('express-handlebars'), 5 | bodyParser = require('body-parser'), 6 | sassMiddleware = require('node-sass-middleware'), 7 | babelify = require('express-babelify-middleware'), 8 | helpers = require(__dirname + '/helpers/general.js'), 9 | app = express(); 10 | 11 | app.use(compression()); 12 | 13 | app.use(sassMiddleware({ 14 | // debug: true, 15 | src: __dirname + '/src', 16 | dest: path.join(__dirname, 'public'), 17 | outputStyle: 'compressed' 18 | })); 19 | 20 | app.use(express.static('public')); 21 | 22 | app.use(bodyParser.urlencoded({ 23 | extended: true 24 | })); 25 | 26 | app.use(bodyParser.json()); 27 | 28 | app.use('/js/', babelify('src/scripts/', { 29 | minify: true 30 | })); 31 | 32 | app.engine('handlebars', exphbs({defaultLayout: 'main'})); 33 | app.set('views', __dirname + '/views'); 34 | app.set('view engine', 'handlebars'); 35 | 36 | app.use(function(req, res, next){ 37 | if (req.headers['x-forwarded-proto'].indexOf('https') === -1){ 38 | return res.redirect('https://' + req.headers.host + req.url); 39 | } 40 | else{ 41 | return next(); 42 | } 43 | }); 44 | 45 | app.use('/', require('./routes/index.js')) 46 | app.use('/image', require('./routes/image.js')) 47 | app.use('/stats', require('./routes/stats.js')) 48 | 49 | app.get('/js/helpers.js', function (req, res) { 50 | res.sendFile(path.join(__dirname + '/helpers/general.js')); 51 | }); 52 | 53 | module.exports = app; 54 | -------------------------------------------------------------------------------- /data-sync.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | helpers = require(__dirname + '/helpers/general.js'), 3 | Twit = require('twit'), 4 | dataFilePath = __dirname + '/data/data.json', 5 | botListFilePath = __dirname + '/data/bots.json'; 6 | 7 | let dataSync = { 8 | reload: function(cb){ 9 | console.log('reloading data...'); 10 | 11 | let twit = new Twit({ 12 | consumer_key: process.env.TWITTER_CONSUMER_KEY, 13 | consumer_secret: process.env.TWITTER_CONSUMER_SECRET, 14 | app_only_auth: true 15 | }); 16 | 17 | twit.get('lists/members', { 18 | owner_screen_name: process.env.TWITTER_USERNAME, 19 | slug: process.env.TWITTER_LIST_NAME, 20 | count: 5000, 21 | skip_status: true 22 | }, function(err, data, response) { 23 | if (err){ 24 | console.log('error:', err); 25 | } 26 | else if (data && data.users){ 27 | let bots = []; 28 | data.users.forEach( function (user){ 29 | bots.push(user.screen_name); 30 | }); 31 | 32 | console.log(bots); 33 | 34 | if (bots && bots.length){ 35 | 36 | twit.get('users/lookup', { 37 | screen_name: bots.join(',') 38 | }, function(err, data, response) { 39 | if (err){ 40 | console.log(err); 41 | } else { 42 | let updatedBotData = []; 43 | data.forEach(function(bot){ 44 | updatedBotData.push({ 45 | id_str: bot.id_str, 46 | name: bot.name, 47 | screen_name: bot.screen_name, 48 | description: bot.description, 49 | followers_count: bot.followers_count, 50 | friends_count: bot.friends_count, 51 | created_at: bot.created_at, 52 | last_tweet_date: bot.status ? bot.status.created_at : null, 53 | favourites_count: bot.favourites_count, 54 | verified: bot.verified, 55 | statuses_count: bot.statuses_count, 56 | profile_background_image_url_https: bot.profile_background_image_url_https, 57 | profile_image_url_https: bot.profile_image_url_https 58 | }); 59 | }); 60 | 61 | // console.log(updatedBotData); 62 | 63 | fs.writeFile(dataFilePath, JSON.stringify({ 64 | last_update: Date.now(), 65 | data: updatedBotData.sort(helpers.sortByFollowersCount) 66 | }), function (err) { 67 | if (err){ 68 | console.log(err) 69 | } else { 70 | console.log('data file updated'); 71 | } 72 | }); 73 | if (cb){ 74 | cb(null, updatedBotData); 75 | } 76 | } 77 | }); 78 | } 79 | } 80 | }); 81 | }, 82 | sync: function(cb){ 83 | let botData = fs.readFileSync(dataFilePath), 84 | dataSync = this; 85 | 86 | try{ 87 | botData = JSON.parse(botData); 88 | } catch (err){ console.log(err) } 89 | 90 | if (botData){ 91 | // console.log(botData); 92 | botData.data = botData.data.sort(helpers.sortByFollowersCount); 93 | 94 | let syncIntervalMinutes = 60; 95 | 96 | if (process.env.SYNC_INTERVAL_MINUTES && process.env.SYNC_INTERVAL_MINUTES > 15){ 97 | syncIntervalMinutes = parseInt(process.env.SYNC_INTERVAL_MINUTES); 98 | } 99 | 100 | var todayDate = new Date(), 101 | lastCheckDate = new Date(botData.last_update), 102 | expirationDate = new Date(lastCheckDate.getTime() + (syncIntervalMinutes * 60000)); 103 | 104 | console.log({ 105 | 'lastCheckDate': lastCheckDate, 106 | 'syncIntervalMinutes' : `${syncIntervalMinutes} (${ (syncIntervalMinutes/60).toFixed(3) } hours)`, 107 | 'expirationDate': expirationDate, 108 | 'todayDate': todayDate, 109 | 'dataExpired': todayDate > expirationDate, 110 | // 'botData': botData 111 | }); 112 | 113 | if (!botData || !botData.data || !botData.data.length || todayDate > expirationDate){ 114 | /* Update data */ 115 | dataSync.reload(function(err, data){ 116 | cb(null, data); 117 | }); 118 | } else { 119 | if (cb){ 120 | cb(null, botData); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | module.exports = dataSync; 128 | -------------------------------------------------------------------------------- /generators/cellular-automata.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | helpers = require(__dirname + '/../helpers/general.js'); 4 | 5 | module.exports = function(options, cb) { 6 | /* 7 | Based on https://generativeartistry.com/tutorials/circle-packing/ 8 | */ 9 | console.log('evolving cells...'); 10 | 11 | var width = options.width || 1184, 12 | height = options.height || 506, 13 | size = width, 14 | colors = options.colors || ['000', 'fff'], 15 | canvas = Canvas.createCanvas(width, height), 16 | ctx = canvas.getContext('2d'); 17 | 18 | ctx.lineWidth = helpers.getRandomInt(1,4); 19 | ctx.fillStyle = `#${colors[0]}`; 20 | ctx.strokeStyle = `#${colors[1]}`; 21 | ctx.fillRect(0, 0, canvas.width, canvas.height); 22 | 23 | const cells_across = options.cells // Number of cells horizontally in the grid 24 | const cell_scale = width / cells_across; // Size of each cell 25 | const cells_down = height / cell_scale; // Number of cells vertically in the grid 26 | 27 | const rule = get_rule(helpers.getRandomInt(0, 255)); // The rule to display 28 | 29 | function get_bit(num, pos){ 30 | return (num >> pos) & 1; 31 | } 32 | 33 | // Combines 3 bits into an integer between 0 and 7 34 | function combine(b1, b2, b3){ 35 | return (b1 << 2) + (b2 << 1) + (b3 << 0); 36 | } 37 | 38 | // Returns given number in the form of a tertiary function (a rule) 39 | function get_rule(num){ 40 | return (b1, b2, b3) => get_bit(num, combine(b1, b2, b3)); 41 | } 42 | 43 | function draw_rule(ctx, rule, scale, width, height) { 44 | let row = random_initial_row(width); 45 | for (let i = 0; i < height; i++) { 46 | draw_row(ctx, row, scale); 47 | row = next_row(row, rule); 48 | } 49 | } 50 | 51 | function draw_row(ctx, row, scale) { 52 | ctx.save(); 53 | row.forEach(cell => { 54 | ctx.fillStyle = cell === 1 ? '#' + colors[0] : '#' + colors[1]; 55 | ctx.fillRect(0, 0, scale, scale); 56 | ctx.translate(scale, 0); 57 | }); 58 | ctx.restore(); 59 | ctx.translate(0, scale); 60 | } 61 | 62 | function next_row(old, rule) { 63 | return old.map((_, i) => rule(old[i - 1], old[i], old[i + 1])); 64 | } 65 | 66 | function initial_row(width) { 67 | const initial_row = Array(width).fill(0); 68 | initial_row[Math.floor(width / 2)] = 1; 69 | 70 | return initial_row; 71 | } 72 | 73 | function random_initial_row(width) { 74 | return Array.from(Array(width), _ => Math.floor(Math.random() * 2)); 75 | } 76 | 77 | 78 | draw_rule(ctx, rule, cell_scale, cells_across, cells_down); 79 | 80 | if (cb){ 81 | cb(null, { 82 | data: canvas.toBuffer().toString('base64') 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /generators/circle-packing.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | helpers = require(__dirname + '/../helpers/general.js'); 4 | 5 | module.exports = function(options, cb) { 6 | /* 7 | Based on https://generativeartistry.com/tutorials/circle-packing/ 8 | */ 9 | console.log('packing circles...'); 10 | 11 | var width = options.width || 1184, 12 | height = options.height || 506, 13 | size = width, 14 | colors = options.colors || ['000', 'fff'], 15 | canvas = Canvas.createCanvas(width, height), 16 | ctx = canvas.getContext('2d'); 17 | 18 | ctx.lineWidth = helpers.getRandomInt(1,4); 19 | ctx.fillStyle = `#${colors[0]}`; 20 | ctx.strokeStyle = `#${colors[1]}`; 21 | ctx.fillRect(0, 0, canvas.width, canvas.height); 22 | 23 | var circles = []; 24 | var minRadius = 2; 25 | var maxRadius = 100; 26 | var totalCircles = 500; 27 | 28 | var createCircleAttempts = 500; 29 | 30 | function doesCircleHaveACollision(circle) { 31 | for(var i = 0; i < circles.length; i++) { 32 | var otherCircle = circles[i]; 33 | var a = circle.radius + otherCircle.radius; 34 | var x = circle.x - otherCircle.x; 35 | var y = circle.y - otherCircle.y; 36 | 37 | if (a >= Math.sqrt((x*x) + (y*y))) { 38 | return true; 39 | } 40 | } 41 | 42 | if ( circle.x + circle.radius >= width || 43 | circle.x - circle.radius <= 0 ) { 44 | return true; 45 | } 46 | 47 | if (circle.y + circle.radius >= height || 48 | circle.y-circle.radius <= 0 ) { 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | function createAndDrawCircle() { 56 | 57 | var newCircle; 58 | var circleSafeToDraw = false; 59 | for( var tries = 0; tries < createCircleAttempts; tries++) { 60 | newCircle = { 61 | x: Math.floor(Math.random() * width), 62 | y: Math.floor(Math.random() * height), 63 | radius: minRadius 64 | } 65 | 66 | if(doesCircleHaveACollision(newCircle)) { 67 | continue; 68 | } else { 69 | circleSafeToDraw = true; 70 | break; 71 | } 72 | } 73 | 74 | if(!circleSafeToDraw) { 75 | return; 76 | } 77 | 78 | for(var radiusSize = minRadius; radiusSize < maxRadius; radiusSize++) { 79 | newCircle.radius = radiusSize; 80 | if(doesCircleHaveACollision(newCircle)){ 81 | newCircle.radius-- 82 | break; 83 | } 84 | } 85 | 86 | circles.push(newCircle); 87 | ctx.beginPath(); 88 | ctx.arc(newCircle.x, newCircle.y, newCircle.radius, 0, 2*Math.PI); 89 | ctx.stroke(); 90 | } 91 | 92 | for( var i = 0; i < totalCircles; i++ ) { 93 | createAndDrawCircle(); 94 | } 95 | 96 | if (cb){ 97 | cb(null, { 98 | data: canvas.toBuffer().toString('base64') 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /generators/cubic-disarray.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | img_path_png = './.data/temp.png', 4 | img_path_gif = './.data/temp.gif', 5 | helpers = require(__dirname + '/../helpers/general.js'); 6 | 7 | module.exports = function(options, cb) { 8 | /* 9 | Based on http://generativeartistry.com/tutorials/cubic-disarray/ 10 | */ 11 | console.log('making cubes...'); 12 | var width = options.width || 1184, 13 | height = options.height || 506, 14 | colors = options.colors || ['000', 'fff'], 15 | canvas = Canvas.createCanvas(width, height), 16 | ctx = canvas.getContext('2d'); 17 | 18 | ctx.lineWidth = helpers.getRandomInt(1,4); 19 | ctx.fillStyle = `#${colors[0]}`; 20 | ctx.strokeStyle = `#${colors[1]}`; 21 | ctx.fillRect(0, 0, canvas.width, canvas.height); 22 | 23 | var size = height; 24 | var squareSize = 40; 25 | var randomDisplacement = 15; 26 | var rotateMultiplier = 20; 27 | 28 | function draw(width, height) { 29 | ctx.beginPath(); 30 | ctx.rect(-width/2, -height/2, width, height); 31 | ctx.stroke(); 32 | } 33 | 34 | for( var i = squareSize; i <= width - squareSize; i += squareSize) { 35 | for( var j = squareSize; j <= height - squareSize; j+= squareSize ) { 36 | var plusOrMinus = Math.random() < 0.5 ? -1 : 1; 37 | var rotateAmt = j / size * Math.PI / 180 * plusOrMinus * Math.random() * rotateMultiplier; 38 | 39 | plusOrMinus = Math.random() < 0.5 ? -1 : 1; 40 | var translateAmt = j / size * plusOrMinus * Math.random() * randomDisplacement; 41 | 42 | ctx.save(); 43 | ctx.translate( i + translateAmt, j) 44 | ctx.rotate(rotateAmt); 45 | draw(squareSize, squareSize); 46 | ctx.restore(); 47 | } 48 | } 49 | 50 | const out = fs.createWriteStream(img_path_png); 51 | const stream = canvas.createPNGStream(); 52 | stream.pipe(out); 53 | 54 | out.on('finish', function(){ 55 | if (cb){ 56 | cb(null, { 57 | path: img_path_png, 58 | data: canvas.toBuffer().toString('base64') 59 | }); 60 | } 61 | }); 62 | 63 | } -------------------------------------------------------------------------------- /generators/joy-division.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | img_path_png = './.data/temp.png', 4 | img_path_gif = './.data/temp.gif', 5 | helpers = require(__dirname + '/../helpers/general.js'); 6 | 7 | module.exports = function(options, cb) { 8 | /* 9 | Based on http://generativeartistry.com/tutorials/joy-division/ 10 | */ 11 | console.log('making waves...'); 12 | var width = options.width || 1184, 13 | height = options.height || 506, 14 | maxHeight = height - 100, 15 | colors = options.colors || ['000', 'fff'], 16 | canvas = Canvas.createCanvas(width, height), 17 | ctx = canvas.getContext("2d"); 18 | 19 | ctx.lineWidth = helpers.getRandomInt(1,4); 20 | ctx.fillStyle = `#${colors[0]}`; 21 | ctx.strokeStyle = `#${colors[1]}`; 22 | ctx.fillRect(0, 0, canvas.width, canvas.height); 23 | 24 | var step = helpers.getRandomInt(8, 12); 25 | var startStep = 40; 26 | var lines = []; 27 | 28 | // Create the lines 29 | for( var i = step; i <= height - step; i += step) { 30 | 31 | var line = []; 32 | for( var j = startStep; j <= height - step; j+= step ) { 33 | var distanceToCenter = Math.abs(j - height / 2); 34 | var variance = Math.max(height / 2 - 50 - distanceToCenter, 0); 35 | 36 | var random = Math.random() * variance / 2 * -1; 37 | var point = {x: j+width/2-height/2, y: i + random}; 38 | line.push(point) 39 | } 40 | lines.push(line); 41 | } 42 | 43 | // Do the drawing 44 | for(var i = step; i < lines.length; i++) { 45 | 46 | ctx.beginPath(); 47 | ctx.moveTo(lines[i][0].x, lines[i][0].y) 48 | for( var j = 0; j < lines[i].length - 2; j++) { 49 | var xc = (lines[i][j].x + lines[i][j + 1].x) / 2; 50 | var yc = (lines[i][j].y + lines[i][j + 1].y) / 2; 51 | ctx.quadraticCurveTo(lines[i][j].x, lines[i][j].y, xc, yc); 52 | } 53 | 54 | ctx.quadraticCurveTo(lines[i][j].x, lines[i][j].y, lines[i][j + 1].x, lines[i][j + 1].y); 55 | ctx.fill(); 56 | 57 | ctx.stroke(); 58 | } 59 | 60 | const out = fs.createWriteStream(img_path_png); 61 | const stream = canvas.createPNGStream(); 62 | stream.pipe(out); 63 | 64 | out.on('finish', function(){ 65 | if (cb){ 66 | cb(null, { 67 | path: img_path_png, 68 | data: canvas.toBuffer().toString('base64') 69 | }); 70 | } 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /generators/mondrian.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | Canvas = require('canvas'), 3 | helpers = require(__dirname + '/../helpers/general.js'); 4 | 5 | module.exports = function(options, cb) { 6 | /* 7 | Based on https://generativeartistry.com/tutorials/piet-mondrian/ 8 | */ 9 | console.log('packing circles...'); 10 | 11 | let width = options.width || 1184, 12 | height = options.height || 506, 13 | size = width, 14 | colors = options.colors || ['#D40920', '#1356A2', '#F7D842'], 15 | canvas = Canvas.createCanvas(width, height), 16 | ctx = canvas.getContext('2d'); 17 | 18 | ctx.lineWidth = helpers.getRandomInt(1,4); 19 | ctx.fillStyle = `#${colors[0]}`; 20 | ctx.strokeStyle = `#${colors[1]}`; 21 | ctx.fillRect(0, 0, canvas.width, canvas.height); 22 | 23 | let squares = [{ 24 | x: 0, 25 | y: 0, 26 | width: width, 27 | height: height 28 | }]; 29 | 30 | canvas.width = width; 31 | canvas.height = height; 32 | ctx.lineWidth = 8; 33 | 34 | var step = size / 7; 35 | var white = '#F2F5F1'; 36 | 37 | function splitSquaresWith(coordinates) { 38 | const { x, y } = coordinates; 39 | 40 | for (let i = squares.length - 1; i >= 0; i--) { 41 | const square = squares[i]; 42 | 43 | if (x && x > square.x && x < square.x + square.width) { 44 | if(Math.random() > 0.5) { 45 | squares.splice(i, 1); 46 | splitOnX(square, x); 47 | } 48 | } 49 | 50 | if (y && y > square.y && y < square.y + square.height) { 51 | if(Math.random() > 0.5) { 52 | squares.splice(i, 1); 53 | splitOnY(square, y); 54 | } 55 | } 56 | } 57 | } 58 | 59 | function splitOnX(square, splitAt) { 60 | let squareA = { 61 | x: square.x, 62 | y: square.y, 63 | width: square.width - (square.width - splitAt + square.x), 64 | height: square.height 65 | }; 66 | 67 | let squareB = { 68 | x: splitAt, 69 | y: square.y, 70 | width: square.width - splitAt + square.x, 71 | height: square.height 72 | }; 73 | 74 | squares.push(squareA); 75 | squares.push(squareB); 76 | } 77 | 78 | function splitOnY(square, splitAt) { 79 | let squareA = { 80 | x: square.x, 81 | y: square.y, 82 | width: square.width, 83 | height: square.height - (square.height - splitAt + square.y) 84 | }; 85 | 86 | let squareB = { 87 | x: square.x, 88 | y: splitAt, 89 | width: square.width, 90 | height: square.height - splitAt + square.y 91 | }; 92 | 93 | squares.push(squareA); 94 | squares.push(squareB); 95 | } 96 | 97 | 98 | for (let i = 0; i < size; i += step) { 99 | splitSquaresWith({ y: i }); 100 | splitSquaresWith({ x: i }); 101 | } 102 | 103 | for (let i = 0; i < colors.length; i++) { 104 | squares[Math.floor(Math.random() * squares.length)].color = colors[i]; 105 | } 106 | for (let i = 0; i < squares.length; i++) { 107 | ctx.beginPath(); 108 | ctx.rect( 109 | squares[i].x, 110 | squares[i].y, 111 | squares[i].width, 112 | squares[i].height 113 | ); 114 | if(squares[i].color) { 115 | ctx.fillStyle = squares[i].color; 116 | } else { 117 | ctx.fillStyle = white 118 | } 119 | ctx.fill() 120 | ctx.stroke(); 121 | } 122 | 123 | if (cb){ 124 | cb(null, { 125 | data: canvas.toBuffer().toString('base64') 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /generators/tiled-lines.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | img_path_png = './.data/temp.png', 4 | img_path_gif = './.data/temp.gif', 5 | helpers = require(__dirname + '/../helpers/general.js'); 6 | 7 | module.exports = function(options, cb) { 8 | /* 9 | Based on http://generativeartistry.com/tutorials/tiled-lines/ 10 | */ 11 | console.log('drawing lines...'); 12 | var width = options.width || 1184, 13 | height = options.height || 506, 14 | colors = options.colors || ['000', 'fff'], 15 | canvas = Canvas.createCanvas(width, height), 16 | ctx = canvas.getContext('2d'); 17 | 18 | ctx.lineWidth = helpers.getRandomInt(1,4); 19 | ctx.fillStyle = `#${colors[0]}`; 20 | ctx.strokeStyle = `#${colors[1]}`; 21 | ctx.fillRect(0, 0, canvas.width, canvas.height); 22 | 23 | var step = helpers.getRandomInt(15,25); 24 | 25 | function draw(x, y, width, height) { 26 | var leftToRight = Math.random() >= 0.5; 27 | 28 | if( leftToRight ) { 29 | ctx.moveTo(x, y); 30 | ctx.lineTo(x + width, y + height); 31 | } else { 32 | ctx.moveTo(x + width, y); 33 | ctx.lineTo(x, y + height); 34 | } 35 | 36 | ctx.stroke(); 37 | } 38 | 39 | for( var x = 0; x < width; x += step) { 40 | for( var y = 0; y < height; y+= step ) { 41 | draw(x, y, step, step); 42 | } 43 | } 44 | 45 | 46 | const out = fs.createWriteStream(img_path_png); 47 | const stream = canvas.createPNGStream(); 48 | stream.pipe(out); 49 | 50 | out.on('finish', function(){ 51 | if (cb){ 52 | cb(null, { 53 | path: img_path_png, 54 | data: canvas.toBuffer().toString('base64') 55 | }); 56 | } 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /generators/triangular-mesh.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | img_path_png = './.data/temp.png', 4 | img_path_gif = './.data/temp.gif', 5 | helpers = require(__dirname + '/../helpers/general.js'); 6 | 7 | module.exports = function(options, cb) { 8 | /* 9 | Based on https://generativeartistry.com/tutorials/triangular-mesh/ 10 | */ 11 | console.log('making triangles...'); 12 | var width = options.width || 1184, 13 | height = options.height || 506, 14 | gap = options.gap || width / helpers.getRandomInt(10,20), 15 | colors = options.colors || ['000', 'fff'], 16 | canvas = Canvas.createCanvas(width, height), 17 | ctx = canvas.getContext('2d'); 18 | 19 | ctx.lineWidth = 1; 20 | ctx.fillStyle = `#${colors[0]}`; 21 | ctx.strokeStyle = `#${colors[1]}`; 22 | ctx.fillRect(0, 0, canvas.width, canvas.height); 23 | 24 | var line, dot, 25 | odd = false, 26 | lines = []; 27 | 28 | for (var y = -2 * gap / 2; y <= 2 * height; y+= gap) { 29 | odd = !odd 30 | line = [] 31 | for (var x = -2 * gap / 4; x <= 2 * width; x+= gap) { 32 | line.push({ 33 | x: x + (Math.random()*.8 - .4) * gap + (odd ? gap/2 : 0), 34 | y: y + (Math.random()*.8 - .4) * gap, 35 | }) 36 | } 37 | lines.push(line) 38 | } 39 | 40 | function drawTriangle(pointA, pointB, pointC) { 41 | ctx.beginPath(); 42 | ctx.moveTo(pointA.x, pointA.y); 43 | ctx.lineTo(pointB.x, pointB.y); 44 | ctx.lineTo(pointC.x, pointC.y); 45 | ctx.lineTo(pointA.x, pointA.y); 46 | ctx.closePath(); 47 | ctx.fillStyle = '#' + helpers.randomFromArray(options.colors); 48 | ctx.fill(); 49 | ctx.stroke(); 50 | } 51 | 52 | var dotLine; 53 | odd = true; 54 | 55 | for (var y = 0; y < lines.length - 1; y++) { 56 | odd = !odd 57 | dotLine = [] 58 | for (var i = 0; i < lines[y].length; i++) { 59 | dotLine.push(odd ? lines[y][i] : lines[y+1][i]) 60 | dotLine.push(odd ? lines[y+1][i] : lines[y][i]) 61 | } 62 | for (var i = 0; i < dotLine.length - 2; i++) { 63 | drawTriangle(dotLine[i], dotLine[i+1], dotLine[i+2]) 64 | } 65 | } 66 | 67 | const out = fs.createWriteStream(img_path_png); 68 | const stream = canvas.createPNGStream(); 69 | stream.pipe(out); 70 | 71 | out.on('finish', function(){ 72 | if (cb){ 73 | cb(null, { 74 | path: img_path_png, 75 | data: canvas.toBuffer().toString('base64') 76 | }); 77 | } 78 | }); 79 | } -------------------------------------------------------------------------------- /generators/un-deux-trois.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | Canvas = require('canvas'), 3 | img_path_png = './.data/temp.png', 4 | img_path_gif = './.data/temp.gif', 5 | helpers = require(__dirname + '/../helpers/general.js'); 6 | 7 | module.exports = function(options, cb) { 8 | /* 9 | Based on https://generativeartistry.com/tutorials/un-deux-trois/ 10 | */ 11 | 12 | console.log('un, duex, trois...'); 13 | 14 | var width = options.width || 1184, 15 | height = options.height || 506, 16 | colors = options.colors || ['000', 'fff'], 17 | canvas = Canvas.createCanvas(width, height), 18 | ctx = canvas.getContext('2d'); 19 | 20 | ctx.lineWidth = helpers.getRandomInt(1,4); 21 | ctx.fillStyle = `#${colors[0]}`; 22 | ctx.strokeStyle = `#${colors[1]}`; 23 | ctx.fillRect(0, 0, canvas.width, canvas.height); 24 | ctx.lineCap = 'round'; 25 | 26 | var step = helpers.getRandomInt(15,25); 27 | var aThirdOfHeight = height/3; 28 | 29 | function draw(x, y, width, height, positions) { 30 | ctx.save(); 31 | ctx.translate(x + width/2, y + height/2) 32 | ctx.rotate(Math.random() * 5); 33 | ctx.translate(-width/2, -height/2) 34 | 35 | for(var i = 0; i <= positions.length; i++) { 36 | ctx.beginPath(); 37 | ctx.moveTo(positions[i] * width, 0); 38 | ctx.lineTo(positions[i] * width, height); 39 | ctx.stroke(); 40 | } 41 | 42 | ctx.restore(); 43 | } 44 | 45 | for( var y = step; y < width - step; y += step) { 46 | for( var x = step; x < width - step; x+= step ) { 47 | if( y < aThirdOfHeight) { 48 | draw(x, y, step, step, [0.5]); 49 | } else if ( y < aThirdOfHeight * 2) { 50 | draw(x, y, step, step, [0.2, 0.8]); 51 | } else { 52 | draw(x, y, step, step, [0.1, 0.5, 0.9]); 53 | } 54 | } 55 | } 56 | 57 | const out = fs.createWriteStream(img_path_png); 58 | const stream = canvas.createPNGStream(); 59 | stream.pipe(out); 60 | 61 | out.on('finish', function(){ 62 | if (cb){ 63 | cb(null, { 64 | path: img_path_png, 65 | data: canvas.toBuffer().toString('base64') 66 | }); 67 | } 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /helpers/general.js: -------------------------------------------------------------------------------- 1 | const helpers = { 2 | ready: function(fn) { 3 | if (document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading'){ 4 | fn(); 5 | } else { 6 | document.addEventListener('DOMContentLoaded', fn); 7 | } 8 | }, 9 | loadData: function(options, cb){ 10 | let statusIndicator = document.getElementById('status'); 11 | if (statusIndicator){ 12 | statusIndicator.innerHTML = 'Loading data...'; 13 | } 14 | function loadDataFromUrl(url, cb){ 15 | let xhr = new XMLHttpRequest(); 16 | xhr.onreadystatechange = function(){ 17 | 18 | if (xhr.readyState === XMLHttpRequest.DONE) { 19 | if (xhr.status === 200) { 20 | let data = JSON.parse(xhr.responseText); 21 | 22 | if (statusIndicator){ 23 | statusIndicator.parentNode.removeChild(statusIndicator); 24 | } 25 | 26 | if (data){ 27 | if (typeof Storage !== 'undefined'){ 28 | /* Set how long we want to cache the data for, in minutes. */ 29 | const expirationMinutes = 10; 30 | let inXMinutes = new Date(new Date().getTime() + expirationMinutes * 60 * 1000); 31 | 32 | localStorage.setItem('data', JSON.stringify(data)); 33 | localStorage.setItem('data_expiration', inXMinutes); 34 | } 35 | 36 | if (cb){ 37 | cb(null, data); 38 | } 39 | } 40 | else{ 41 | throw 'data not loaded'; 42 | } 43 | } else { 44 | if (cb){ 45 | cb(xhr); 46 | } 47 | } 48 | } 49 | }; 50 | xhr.open("GET", url, true); 51 | xhr.send(); 52 | } 53 | 54 | if (typeof Storage !== 'undefined'){ 55 | let data = localStorage.getItem('data'); 56 | const dataExpiration = Date.parse(localStorage.getItem('data_expiration')), 57 | dateNow = new Date(); 58 | 59 | if (dataExpiration && data){ 60 | if (dateNow > dataExpiration){ 61 | loadDataFromUrl(options.url, cb); 62 | } else { 63 | try{ 64 | data = JSON.parse(data); 65 | if (statusIndicator){ 66 | statusIndicator.parentNode.removeChild(statusIndicator); 67 | } 68 | if (cb){ 69 | cb(null, data); 70 | } 71 | } catch (err){ 72 | if (cb){ 73 | cb(err, null); 74 | } 75 | } 76 | } 77 | } 78 | else{ 79 | loadDataFromUrl(options.url, cb); 80 | } 81 | } else { 82 | loadDataFromUrl(options.url, cb); 83 | } 84 | }, 85 | randomFromArray: function(arr) { 86 | return arr[Math.floor(Math.random()*arr.length)]; 87 | }, 88 | getRandomInt: function(min, max) { 89 | return Math.floor(Math.random() * (max - min + 1)) + min; 90 | }, 91 | getRandomRange: function(min, max, fixed) { 92 | return (Math.random() * (max - min) + min).toFixed(fixed) * 1; 93 | }, 94 | getRandomHex: function(includePound) { 95 | return (includePound ? '#' : '') + Math.random().toString(16).slice(2, 8).toUpperCase(); 96 | }, 97 | shadeColor: function(color, percent) { 98 | // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors 99 | let f = parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; 100 | return `#${(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1)}`; 101 | }, 102 | colorOpacity: function(color, opacity) { 103 | /* A helper function that takes an rgb color and adds opacity to it. */ 104 | opacity = 105 | opacity || 106 | 0.5 /* If no value is passed to the function, 0.5 will be used as a default value. */; 107 | return color.replace("rgb", "rgba").replace(")", "," + opacity + ")"); 108 | }, 109 | numberWithCommas: function(x){ 110 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 111 | }, 112 | sortByFollowersCount: function(a, b){ 113 | if (parseInt(a.followers_count) < parseInt(b.followers_count)){ 114 | return -1; 115 | } 116 | if (parseInt(a.followers_count) > parseInt(b.followers_count)){ 117 | return 1; 118 | } 119 | return 0; 120 | } 121 | }; 122 | 123 | if (typeof module !== 'undefined'){ 124 | /* This is to make the file usable both in node and on the front end. */ 125 | module.exports = helpers; 126 | } 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generative-placeholders", 3 | "version": "0.0.1", 4 | "description": "Use generative art as your placeholder images..", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.16.3", 11 | "express-handlebars": "^3.0.0", 12 | "body-parser": "^1.18.3", 13 | "request": "^2.88.0", 14 | "node-sass-middleware": "^0.11.0", 15 | "express-babelify-middleware": "^0.2.1", 16 | "babel-preset-es2015": "^6.24.1", 17 | "canvas": "^2.0.0-alpha.5", 18 | "pngjs": "^3.3.2", 19 | "underscore": "^1.8.3", 20 | "color-scheme": "^1.0.1", 21 | "roughjs": "^2.2.4", 22 | "poisson-disc-sampler": "^1.1.0", 23 | "nice-color-palettes": "^3.0.0", 24 | "compression": "^1.7.4", 25 | "sqlite3": "^4.2.0" 26 | }, 27 | "engines": { 28 | "node": "13.x" 29 | }, 30 | "repository": { 31 | "url": "https://glitch.com/edit/#!/glitch-starter-project" 32 | }, 33 | "license": "MIT", 34 | "keywords": [ 35 | "node" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /public/css/css/styles.css: -------------------------------------------------------------------------------- 1 | body{font-size:30px;color:red}body{color:red} 2 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | img{max-width:100%}footer .container{max-width:800px}pre{white-space:normal}img.main-example{min-height:40vh;object-fit:cover}body{color:#363636;font-family:'Roboto', sans-serif;font-size:1.2rem}header p{font-size:1.7rem}h1,h2,h3,h4,h5{font-family:'Playfair Display', serif;margin:10px 0}h1{font-size:3rem}h2{font-size:2.5rem}h3{font-size:2rem}h4{font-size:1.7rem}pre,code{font-family:'Source Code Pro', monospace}.example{cursor:pointer}.lazy-load{opacity:0;transform:translate(0, 30px) scale(1.05)}.lazy-loaded{opacity:1;transform:translate(0, 0) scale(1)}.lazy-load,.lazy-loaded{transition:transform 0.2s, scale 0.3s, opacity 0.4s} 2 | -------------------------------------------------------------------------------- /public/js/scripts.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbohacek/generative-placeholders/0239b484edf54325c266661535dc259c21ed4539/public/js/scripts.min.js -------------------------------------------------------------------------------- /public/libs/bootstrap/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.0.0 (https://getbootstrap.com) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); 7 | //# sourceMappingURL=bootstrap.min.js.map -------------------------------------------------------------------------------- /public/libs/moment/moment.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n>>0,s=0;sSe(e)?(r=e+1,o-Se(e)):(r=e,o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(Se(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),F("week",5),F("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=D(e)});function je(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=D(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var $e="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qe=ae;var Je=ae;var Be=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=he(o[t]),u[t]=he(u[t]),l[t]=he(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Xe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)+L(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+L(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+L(this.minutes(),2)+L(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),C("hour","h"),F("hour",13),ue("a",et),ue("A",et),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=D(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=D(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i))});var tt,nt=Te("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:He,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){var t=null;if(!it[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=tt._abbr,require("./locale/"+e),ut(t)}catch(e){}return it[e]}function ut(e,t){var n;return e&&((n=l(t)?ht(e):lt(e,t))?tt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tt._abbr}function lt(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ot(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new P(x(s,t)),rt[e]&&rt[e].forEach(function(e){lt(e.name,e.config)}),ut(e),it[e]}function ht(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tt;if(!o(e)){if(t=ot(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return tt}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ct(e._a[me],s[me]),(e._dayOfYear>Se(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[ve]&&0===e._a[pe]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o);if(u){for(g(e).iso=!0,t=0,n=gt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},mn.isUtc=Et,mn.isUTC=Et,mn.zoneAbbr=function(){return this._isUTC?"UTC":""},mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},mn.dates=n("dates accessor is deprecated. Use date instead.",un),mn.months=n("months accessor is deprecated. Use month instead",Ue),mn.years=n("years accessor is deprecated. Use year instead",Oe),mn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),mn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Ot(e))._a){var t=e._isUTC?y(e._a):bt(e._a);this._isDSTShifted=this.isValid()&&0 0 && reqWidth <= 2000){ 56 | width = reqWidth; 57 | } 58 | } 59 | 60 | if (req.query.height){ 61 | const reqHeight = parseInt(req.query.height); 62 | if (reqHeight > 0 && reqHeight <= 2000){ 63 | height = reqHeight; 64 | } 65 | } 66 | 67 | const options = { 68 | style: req.query.style ? req.query.style : 'cellular-automata', 69 | width: width, 70 | height: height, 71 | colors: colorPalette.map(function(color){ return color.replace('#', ''); }) 72 | } 73 | 74 | if (options.style === 'circles'){ 75 | generators.circle_packing(options, function(error, img){ 76 | serveImage(res, error, img); 77 | }); 78 | } else if (options.style === 'triangles'){ 79 | if (req.query.gap){ 80 | options.gap = parseInt(req.query.gap); 81 | } 82 | generators.triangular_mesh(options, function(error, img){ 83 | serveImage(res, error, img); 84 | }); 85 | } else if (options.style === 'tiles'){ 86 | /* Very slow! */ 87 | generators.tiled_lines(options, function(error, img){ 88 | serveImage(res, error, img); 89 | }); 90 | } else if (options.style === 'cellular-automata'){ 91 | 92 | if (req.query.cells && parseInt(req.query.cells) > 0){ 93 | options.cells = parseInt(req.query.cells); 94 | if (options.cells > 200){ 95 | options.cells = 200; 96 | } 97 | } else { 98 | options.cells = helpers.getRandomInt(50, 100); 99 | } 100 | 101 | generators.cellular_automata(options, function(error, img){ 102 | serveImage(res, error, img); 103 | }); 104 | } else if (options.style === 'cubic-disarray'){ 105 | generators.cubic_disarray(options, function(error, img){ 106 | serveImage(res, error, img); 107 | }); 108 | } else if (options.style === 'joy-division'){ 109 | generators.joy_division(options, function(error, img){ 110 | serveImage(res, error, img); 111 | }); 112 | } else if (options.style === '123'){ 113 | generators.un_deux_trois(options, function(error, img){ 114 | serveImage(res, error, img); 115 | }); 116 | } else if (options.style === 'mondrian'){ 117 | options.colors = ['#D40920', '#1356A2', '#F7D842']; 118 | 119 | generators.mondrian(options, function(error, img){ 120 | serveImage(res, error, img); 121 | }); 122 | } 123 | 124 | // const referer = req.headers.referer; 125 | 126 | // if (req.query.style && referer && referer.indexOf('generative-placeholders.glitch.me') === -1){ 127 | // const filePath = `${__dirname}/../.data/stats.json`, 128 | // date = new Date().toISOString().slice(0, 10), 129 | // style = req.query.style; 130 | 131 | // fs.readFile(filePath, 'utf8', function (err, data) { 132 | // if (err || !data){ 133 | // console.log('error reading stats data:', err); 134 | // data = {}; 135 | // data[date] = {}; 136 | // data[date][style] = 1; 137 | // data[date]['all'] = 1; 138 | // } else { 139 | // try{ 140 | // data = JSON.parse(data); 141 | // data[date] = data[date] || {}; 142 | // data[date][style] = data[date][style] || 0; 143 | // data[date]['all'] = data[date]['all'] || 0; 144 | 145 | // console.log('stats data:', data); 146 | 147 | // if (data[date][style]){ 148 | // data[date][style]++; 149 | // } else { 150 | // data[date][style] = 1; 151 | // } 152 | 153 | // data[date]['all']++; 154 | 155 | // } catch(err){ console.log('error parsing stats data:', err) /* data = {}*/ } 156 | 157 | // } 158 | 159 | // fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8'); 160 | 161 | 162 | // }); 163 | // } 164 | }); 165 | 166 | module.exports = router; 167 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'), 2 | fs = require('fs'), 3 | palettes = require('nice-color-palettes'), 4 | router = express.Router(); 5 | 6 | router.get('/', (req, res) => { 7 | res.render('../views/home.handlebars', { 8 | project_name: process.env.PROJECT_DOMAIN, 9 | head_scripts: process.env.HEAD_SCRIPTS, 10 | palettes: palettes, 11 | timestamp: Date.now() 12 | }); 13 | }); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /routes/stats.js: -------------------------------------------------------------------------------- 1 | const express = require('express'), 2 | fs = require('fs'), 3 | palettes = require('nice-color-palettes'), 4 | router = express.Router(); 5 | 6 | router.get('/', (req, res) => { 7 | const filePath = `${__dirname}/../.data/stats.json`; 8 | 9 | fs.readFile(filePath, 'utf8', (err, data) => { 10 | 11 | try{ 12 | data = JSON.parse(data); 13 | } catch (err){ 14 | data = {}; 15 | } 16 | 17 | res.render('../views/stats.handlebars', { 18 | data: data, 19 | project_name: process.env.PROJECT_DOMAIN, 20 | sc_project: process.env.SC_PROJECT, 21 | sc_security: process.env.SC_SECURITY, 22 | palettes: palettes, 23 | timestamp: Date.now() 24 | }); 25 | }); 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const app = require(__dirname + '/app.js'); 2 | 3 | const listener = app.listen(process.env.PORT, () => { 4 | console.log(`app is running on port ${listener.address().port}...`); 5 | }); 6 | -------------------------------------------------------------------------------- /src/css/_layout.scss: -------------------------------------------------------------------------------- 1 | img{ 2 | max-width: 100%; 3 | } 4 | 5 | footer{ 6 | .container{ 7 | max-width: 800px; 8 | } 9 | } 10 | 11 | pre{ 12 | white-space: normal; 13 | } 14 | 15 | img.main-example{ 16 | min-height: 40vh; 17 | object-fit: cover; 18 | } 19 | -------------------------------------------------------------------------------- /src/css/_misc.scss: -------------------------------------------------------------------------------- 1 | .example{ 2 | cursor: pointer; 3 | } 4 | 5 | .lazy-load{ 6 | opacity: 0; 7 | transform: translate(0,30px) scale(1.05); 8 | } 9 | 10 | .lazy-loaded{ 11 | opacity: 1; 12 | transform: translate(0,0) scale(1); 13 | } 14 | 15 | .lazy-load, 16 | .lazy-loaded{ 17 | transition: transform 0.2s, scale 0.3s, opacity 0.4s; 18 | } 19 | -------------------------------------------------------------------------------- /src/css/_typography.scss: -------------------------------------------------------------------------------- 1 | body{ 2 | color: $black; 3 | font-family: 'Roboto', sans-serif; 4 | font-size: 1.2rem; 5 | } 6 | 7 | header{ 8 | p{ 9 | font-size: 1.7rem; 10 | } 11 | } 12 | 13 | h1, h2, h3, h4, h5{ 14 | font-family: 'Playfair Display', serif; 15 | margin: 10px 0; 16 | } 17 | 18 | h1{ 19 | font-size: 3rem; 20 | } 21 | 22 | h2{ 23 | font-size: 2.5rem; 24 | } 25 | 26 | h3{ 27 | font-size: 2rem; 28 | } 29 | 30 | h4{ 31 | font-size: 1.7rem; 32 | } 33 | 34 | pre, code{ 35 | font-family: 'Source Code Pro', monospace; 36 | } 37 | -------------------------------------------------------------------------------- /src/css/_variables.scss: -------------------------------------------------------------------------------- 1 | $black: #363636; -------------------------------------------------------------------------------- /src/css/styles.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "layout"; 3 | @import "typography"; 4 | @import "misc"; 5 | -------------------------------------------------------------------------------- /src/scripts/lazy-load-images.js: -------------------------------------------------------------------------------- 1 | /* 2 | Lazy Load Images without jQuery 3 | http://kaizau.github.com/Lazy-Load-Images-without-jQuery/ 4 | Original by Mike Pulaski - http://www.mikepulaski.com 5 | Modified by Kai Zau - http://kaizau.com 6 | */ 7 | (function() { 8 | var addEventListener = 9 | window.addEventListener || 10 | function(n, f) { 11 | window.attachEvent("on" + n, f); 12 | }, 13 | removeEventListener = 14 | window.removeEventListener || 15 | function(n, f, b) { 16 | window.detachEvent("on" + n, f); 17 | }; 18 | 19 | // For IE7 compatibility 20 | // Adapted from http://www.quirksmode.org/js/findpos.html 21 | function getOffsetTop(el) { 22 | var val = 0; 23 | if (el.offsetParent) { 24 | do { 25 | val += el.offsetTop; 26 | } while ((el = el.offsetParent)); 27 | return val; 28 | } 29 | } 30 | 31 | var lazyLoader = { 32 | cache: [], 33 | mobileScreenSize: 500, 34 | //tinyGif: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 35 | 36 | addObservers: function() { 37 | addEventListener("scroll", lazyLoader.throttledLoad); 38 | addEventListener("resize", lazyLoader.throttledLoad); 39 | }, 40 | 41 | removeObservers: function() { 42 | removeEventListener("scroll", lazyLoader.throttledLoad, false); 43 | removeEventListener("resize", lazyLoader.throttledLoad, false); 44 | }, 45 | 46 | throttleTimer: new Date().getTime(), 47 | 48 | throttledLoad: function() { 49 | var now = new Date().getTime(); 50 | if (now - lazyLoader.throttleTimer >= 200) { 51 | lazyLoader.throttleTimer = now; 52 | lazyLoader.loadVisibleImages(); 53 | } 54 | }, 55 | 56 | loadVisibleImages: function() { 57 | var scrollY = window.pageYOffset || document.documentElement.scrollTop; 58 | var pageHeight = 59 | window.innerHeight || document.documentElement.clientHeight; 60 | var range = { 61 | min: scrollY - 200, 62 | max: scrollY + pageHeight + 200 63 | }; 64 | 65 | var i = 0; 66 | while (i < lazyLoader.cache.length) { 67 | var image = lazyLoader.cache[i]; 68 | var imagePosition = getOffsetTop(image); 69 | var imageHeight = image.height || 0; 70 | 71 | if ( 72 | imagePosition >= range.min - imageHeight && 73 | imagePosition <= range.max 74 | ) { 75 | var mobileSrc = image.getAttribute("data-src-mobile"); 76 | 77 | // image.onload = function() { 78 | // this.className = this.className.replace(/(^|\s+)lazy-load(\s+|$)/, '$1lazy-loaded$2'); 79 | // }; 80 | // Temporary fix for Safari! 81 | 82 | image.className = image.className.replace( 83 | /(^|\s+)lazy-load(\s+|$)/, 84 | "$1lazy-loaded$2" 85 | ); 86 | 87 | if (mobileSrc && screen.width <= lazyLoader.mobileScreenSize) { 88 | image.src = mobileSrc; 89 | } else { 90 | image.src = image.getAttribute("data-src"); 91 | } 92 | 93 | image.removeAttribute("data-src"); 94 | image.removeAttribute("data-src-mobile"); 95 | 96 | lazyLoader.cache.splice(i, 1); 97 | continue; 98 | } 99 | 100 | i++; 101 | } 102 | 103 | if (lazyLoader.cache.length === 0) { 104 | lazyLoader.removeObservers(); 105 | } 106 | }, 107 | 108 | init: function() { 109 | // Patch IE7- (querySelectorAll) 110 | if (!document.querySelectorAll) { 111 | document.querySelectorAll = function(selector) { 112 | var doc = document, 113 | head = doc.documentElement.firstChild, 114 | styleTag = doc.createElement("STYLE"); 115 | head.appendChild(styleTag); 116 | doc.__qsaels = []; 117 | styleTag.styleSheet.cssText = 118 | selector + "{x:expression(document.__qsaels.push(this))}"; 119 | window.scrollBy(0, 0); 120 | return doc.__qsaels; 121 | }; 122 | } 123 | 124 | addEventListener("load", function _lazyLoaderInit() { 125 | var imageNodes = document.querySelectorAll("img[data-src]"); 126 | 127 | for (var i = 0; i < imageNodes.length; i++) { 128 | var imageNode = imageNodes[i]; 129 | 130 | // Add a placeholder if one doesn't exist 131 | //imageNode.src = imageNode.src || lazyLoader.tinyGif; 132 | 133 | lazyLoader.cache.push(imageNode); 134 | } 135 | 136 | lazyLoader.addObservers(); 137 | lazyLoader.loadVisibleImages(); 138 | 139 | removeEventListener("load", _lazyLoaderInit, false); 140 | }); 141 | } 142 | }; 143 | 144 | lazyLoader.init(); 145 | })(); 146 | -------------------------------------------------------------------------------- /src/scripts/scripts.js: -------------------------------------------------------------------------------- 1 | document.addEventListener( 'DOMContentLoaded', function(event){ 2 | document.querySelectorAll( 'pre code' ).forEach( function( block ){ 3 | hljs.highlightBlock( block ); 4 | }); 5 | let examples = document.querySelectorAll( '.example' ); 6 | 7 | examples.forEach( function( example ){ 8 | example.addEventListener( 'click', function( event ){ 9 | event.preventDefault(); 10 | let dataSrc = this.dataset.imgsrc; 11 | if ( dataSrc ){ 12 | this.src = dataSrc; 13 | this.dataset.imgsrc = ''; 14 | } else { 15 | this.src = this.src + '&'; 16 | } 17 | return false; 18 | } ); 19 | } ) 20 | }); 21 | -------------------------------------------------------------------------------- /views/404.handlebars: -------------------------------------------------------------------------------- 1 |
2 |

3 | Page not found. 4 |

5 |
-------------------------------------------------------------------------------- /views/home.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 | Generative Placeholders 4 |

5 | Generative Placeholders 6 |

7 |

8 | Use generative art as your image placeholders. 9 |

10 |
11 |

12 | Created by Stefan Bohacek. 13 | View source 14 | More projects 15 |

16 |
17 |

18 | Basic usage 19 |

20 |
21 |
22 | Example generative placeholder image 23 | 26 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300">
27 |

28 | Hint: Click the preview to generate a new image. 29 |

30 |
31 |
32 |

33 | Choose the style, tweak the look 34 |

35 | 36 |

37 | Cellular automata 38 |

39 |

Pattern made up of colored cells (this is the default style).

40 |
    41 |
  • style=cellular-automata
  • 42 |
  • cells=X number of cells on each line
  • 43 |
  • colors=X color palette (see available palettes)
  • 44 |
45 |
46 |
47 | Example generative placeholder image 48 | 51 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=cellular-automata&cells=10">
52 |
53 |
54 | Example generative placeholder image 55 | 58 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=cellular-automata&cells=50">
59 |
60 |
61 |

62 | Triangles 63 |

64 |

Colorful triangle mesh.

65 |
    66 |
  • style=triangles
  • 67 |
  • gap=X controls the size of the triangles
  • 68 |
  • colors=X color palette (see available palettes)
  • 69 |
70 |
71 |
72 | Example generative placeholder image 73 | 76 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=triangles&gap=100">
77 |
78 |
79 | Example generative placeholder image 80 | 83 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=triangles&gap=30">
84 |
85 |
86 |
87 |

88 | Mondrian 89 |

90 |

Art in the style of Piet Mondrian.

91 |
    92 |
  • style=mondrian
  • 93 |
94 | 95 |
96 |
97 | Example generative placeholder image in the style of Piet Mondrian 98 | 101 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=mondrian">
102 |
103 |
104 | 105 | 106 |

107 | Tiles 108 |

109 |

Mazes created using the 10 PRINT Commodore 64 generative art program.

110 | 114 | 115 |
116 |
117 | Example tiling generative placeholder image 118 | 121 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=tiles">
122 |
123 |
124 | 125 | 126 | 127 |

128 | Cubic Disarray 129 |

130 |

Inspired by the art of Georg Nees.

131 | 135 | 136 |
137 |
138 | Example cubic disarray generative placeholder image 139 | 142 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=cubic-disarray">
143 |
144 |
145 | 146 | 147 | 148 |

149 | Joy Division 150 |

151 |

Inspired by Joy Division's Unknown Pleasures album cover.

152 | 156 | 157 |
158 |
159 | Example Joy Division generative placeholder image 160 | 163 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=joy-division">
164 |
165 |
166 | 167 | 168 | 169 |

170 | 123 171 |

172 |

Inspired by Vera Molnar's Un Deux Trois artwork.

173 | 177 | 178 |
179 |
180 | Example generative placeholder image inspired by Vera Molnar's Un Deux Trois artwork 181 | 184 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=123">
185 |
186 |
187 | 188 |

189 | Circles 190 |

191 |

Circles packed together.

192 | 196 | 197 |
198 |
199 | Example circle-packing generative placeholder image 200 | 203 |
<img src="https://generative-placeholders.glitch.me/image?width=600&height=300&style=circles">
204 |
205 |
206 | 207 |

Resources

208 | 225 |

Questions and tips

226 | 227 |

Need help or want to share feedback or suggestions? Feel free to reach out via email or on Mastodon!

228 | 229 |

When using multiple images on the same page, how do I make each look different?

230 | 231 |

When a browser sees the same URL again, it tries to load the resource (image, JavaScript or a CSS file, etc) from the browser's cache, avoiding making the same request again.

232 |

To make each image random, you can add an extra parameter that will be ignored by this site, but will make your browser think you are requesting a new image. This can be anything, for example:

233 |
234 |     <img src="https://generative-placeholders.glitch.me/image?width=600&height=300&img=01">
235 |     <img src="https://generative-placeholders.glitch.me/image?width=600&height=300&img=02">
236 |     <img src="https://generative-placeholders.glitch.me/image?width=600&height=300&img=03">
237 |   
238 | 239 |

I need bigger images

240 | 241 |

The maximum image size is 2000x2000 pixels. If you need images larger than that, consider remixing this project and removing the conditions inside the routes/image.js file on lines 50 and 57.

242 |

Adding title and alt attributes

243 |

If you intend to use the images outside of small demos, be sure to include title and alt attributes.

244 | 245 |

Available palettes

246 | 247 |

248 | The image generator uses the nice-color-palettes node package to pick a random color palette. You can choose a specific palette by passing an index of one of the top 100 palettes as the colors parameter. 249 |

250 | 251 |
252 | {{#each palettes}} 253 |
254 |
255 |
256 |
colors={{@index}}
257 |
258 | {{#each this}} 259 |
260 | {{/each}} 261 |
262 |
263 |
264 |
265 | {{/each}} 266 |
267 |
268 | -------------------------------------------------------------------------------- /views/layouts/main.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Generative Placeholders 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 43 | 44 | 45 | 46 | 47 | 48 | {{{head_scripts}}} 49 | 50 | 51 |
52 | 55 | {{{body}}} 56 |
57 |
58 | {{> footer }} 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /views/partials/footer.handlebars: -------------------------------------------------------------------------------- 1 | 43 | -------------------------------------------------------------------------------- /views/stats.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 | Generative Placeholders 4 |

5 | Generative Placeholders 6 |

7 |

8 | Usage statistics 9 |

10 |
11 |
12 |
13 |
14 | 15 | {{#each data}} 16 |

{{@key}}

17 |
    18 | {{#each this}} 19 |
  • {{@key}}: {{this}}
  • 20 | {{/each}} 21 |
22 | {{/each}} 23 | 24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /watch.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": { 3 | "include": ["^package\\.json$"] 4 | }, 5 | "restart": { 6 | "exclude": ["^public/", "^dist/"], 7 | "include": [ 8 | "^src/", 9 | "^routes/", 10 | "^helpers/", 11 | "\\.js$", 12 | "\\.scss$", 13 | "\\.handlebars$", 14 | "\\.json$", 15 | ".env" 16 | ] 17 | }, 18 | "throttle": 2000 19 | } 20 | --------------------------------------------------------------------------------