├── .gitignore ├── LICENSE ├── README.md ├── Website.py ├── backend.buildspec.yml ├── demo.gif ├── deploy-lambda.sh ├── deploy-s3.sh ├── deploy.buildspec.yml ├── frontend.buildspec.yml ├── index.py ├── package-lambda.sh ├── public ├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .yo-rc.json ├── README.md ├── gulp │ ├── tasks │ │ ├── browserSync.js │ │ ├── browserify.js │ │ ├── clean.js │ │ ├── copy.js │ │ ├── eslint.js │ │ ├── handlebars.js │ │ ├── imagemin.js │ │ ├── nunjucks.js │ │ ├── rev.js │ │ ├── sass.js │ │ └── watch.js │ └── utils.js ├── gulpfile.babel.js ├── karma.conf.js ├── package-lock.json ├── package.json └── src │ ├── README.md │ ├── _data │ └── README.md │ ├── _images │ ├── README.md │ └── floppy.jpg │ ├── _layouts │ ├── README.md │ ├── base.nunjucks │ └── partials │ │ ├── carbon-ad.nunjucks │ │ ├── google-analytics.nunjucks │ │ └── issue-embed.nunjucks │ ├── _modules │ └── README.md │ ├── _scripts │ ├── README.md │ └── main.js │ ├── _styles │ ├── README.md │ ├── _carbon-ad.scss │ ├── _github-fork.scss │ ├── _skeleton-alerts.scss │ └── main.sass │ ├── _templates │ ├── helpers.js │ ├── output.hbs │ └── partials │ │ └── _multilineTableRow.hbs │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.nunjucks │ ├── mstile-150x150.png │ ├── robots.txt │ ├── safari-pinned-tab.svg │ └── site.webmanifest └── serve.sh /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | function.zip 4 | .vscode 5 | __pycache__ 6 | v-env 7 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2019] [Brendon Body] 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fit on a Floppy 2 | 3 | Websites are getting bigger and bigger. The internet is getting faster and faster but not everywhere at the same pace. A floppy is a physical reminder of filesize. 4 | 5 | ![Demo](https://raw.githubusercontent.com/bbody/fit-on-a-floppy/master/demo.gif 6 | ) 7 | 8 | - [Website](https://fitonafloppy.website/) 9 | - [Blog Post](https://www.brendonbody.com/2019/11/13/fit-on-a-floppy/) 10 | - [Hacker News Discussion](https://news.ycombinator.com/item?id=21609386) 11 | 12 | ## Architecture 13 | 14 | - **CodePipeline** - CI & CD 15 | - **S3** - Webhosting 16 | - **Lambda** - Serverless hosting 17 | 18 | ## Built with 19 | 20 | - Python *3.6* - Serverless function 21 | - NodeJS *12* - Frontend build process 22 | - Gulp - Build process 23 | - Handlebars - In code templating 24 | - Nunjucks - HTML templating 25 | 26 | ## Scripts 27 | 28 | - `package-lambda.sh` - add source to a zip 29 | - `deploy-s3.sh` - deploy frontend to S3 30 | - `deploy-lambda.sh` - deploy function to Lamdba 31 | - `serve.sh` - serve frontend locally 32 | -------------------------------------------------------------------------------- /Website.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | import requests 3 | import json 4 | import urllib.parse 5 | import math 6 | 7 | class Website: 8 | def __init__(self, url): 9 | self.url = Website.parseURL(url) 10 | self.friendly_url = Website.parseFriendlyURL(url) 11 | self.js_files = [] 12 | self.css_files = [] 13 | self.image_files = [] 14 | self.favicon = False 15 | self.title = '' 16 | def parseWebsite(self): 17 | page = requests.get(self.url, stream=True) 18 | content = BeautifulSoup(page.content, 'html.parser') 19 | 20 | self.html_sizes = Website.calculate_file_size(page) 21 | 22 | self.setTitle(content) 23 | self.setFavicon(content) 24 | self.setJSFiles(content) 25 | self.setCSSFiles(content) 26 | self.setImageFiles(content) 27 | def setTitle(self, content): 28 | meta_title = content.select('meta[itemprop="name"]') 29 | 30 | if (len(meta_title) == 0): 31 | title = content.title.string 32 | else: 33 | title = meta_title[0]['content'] 34 | 35 | self.title = title 36 | def setFavicon(self, content): 37 | favicon_selector = content.select('link[rel="shortcut icon"]') 38 | 39 | if (len(favicon_selector) > 0): 40 | favicon = favicon_selector[0]['href'] 41 | else: 42 | favicon = False 43 | self.favicon = favicon 44 | def setJSFiles(self, content): 45 | js_query = content.select('script[src]') 46 | js_files = [] 47 | 48 | for js in js_query: 49 | if ((not js.has_attr('defer')) and (not js.has_attr('async'))): 50 | js_files.append(js) 51 | 52 | self.js_files = Website.parse_files('Scripts', self.url, js_files, 'src') 53 | def setCSSFiles(self, content): 54 | css_files = content.select('link[rel="stylesheet"]') 55 | self.css_files = Website.parse_files('Styles and Fonts', self.url, css_files, 'href') 56 | def setImageFiles(self, content): 57 | image_query = content.select('img') 58 | image_files = [] 59 | 60 | for image in image_query: 61 | if ((image.has_attr('src')) and (not image['src'].startswith('data:'))): 62 | image_files.append(image) 63 | 64 | self.image_files = Website.parse_files('Images', self.url, image_files, 'src') 65 | def getTotalSize(self): 66 | return self.html_sizes['bytes'] + self.css_files['total_size_bytes'] + self.js_files['total_size_bytes'] + self.image_files['total_size_bytes'] 67 | def getWebsiteContent(self): 68 | try: 69 | self.parseWebsite() 70 | totalSize = self.getTotalSize() 71 | 72 | data = { 73 | 'title': self.title, 74 | 'favicon': self.favicon, 75 | 'js_files': self.js_files, 76 | 'css_files': self.css_files, 77 | 'image_files': self.image_files, 78 | 'html_size_bytes': self.html_sizes['bytes'], 79 | 'html_size_kibibytes': self.html_sizes['kibibytes'], 80 | 'html_size_kilobytes': self.html_sizes['kilobytes'], 81 | 'url': self.url, 82 | 'friendly_url': self.friendly_url, 83 | 'total_size_bytes': totalSize, 84 | 'total_size_kibibytes': totalSize/Website.BYTES_PER_KIB, 85 | 'total_size_kilobytes': totalSize/Website.BYTES_PER_KB, 86 | 'floppy_size_bytes': Website.FLOPPY_SIZE, 87 | 'floppy_size_kibibytes': Website.FLOPPY_SIZE/Website.BYTES_PER_KIB, 88 | 'floppy_size_kilobytes': Website.FLOPPY_SIZE/Website.BYTES_PER_KB, 89 | 'floppies': math.ceil(totalSize/Website.FLOPPY_SIZE) 90 | } 91 | 92 | return { 93 | 'statusCode': str(200), 94 | 'body': json.dumps(data), 95 | } 96 | except Exception as e: 97 | print(e) 98 | return { 99 | 'statusCode': str(400), 100 | 'body': { 101 | 'error': 'Error in processing website' 102 | }, 103 | } 104 | # Static values and functions 105 | FLOPPY_SIZE = 1474560.0 106 | BYTES_PER_KIB = 1024.0 107 | BYTES_PER_KB = 1000.0 108 | @staticmethod 109 | def calculate_file_size(file_request): 110 | with file_request as response: 111 | size = sum(len(chunk) for chunk in response.iter_content(8)) 112 | 113 | return { 114 | 'bytes': size, 115 | 'kibibytes': size/Website.BYTES_PER_KIB, 116 | 'kilobytes': size/Website.BYTES_PER_KB 117 | } 118 | @staticmethod 119 | def get_total_size(files): 120 | totalSize = 0 121 | for f in files: 122 | totalSize += f['bytes'] 123 | return totalSize 124 | @staticmethod 125 | def parse_files(title, url, files, attribute): 126 | parsed_files = [] 127 | for f in files: 128 | file_name_components = f[attribute].split('/') 129 | filename = file_name_components[len(file_name_components) - 1] 130 | full_filename = f[attribute] 131 | 132 | if (('https://' not in full_filename) and ('http://' not in full_filename)): 133 | full_filename = urllib.parse.urljoin(url, full_filename) 134 | 135 | asset_file = requests.get(full_filename, stream=True) 136 | 137 | file_sizes = Website.calculate_file_size(asset_file) 138 | 139 | parsed_files.append({ 140 | 'file_path': full_filename, 141 | 'filename': filename, 142 | 'bytes': file_sizes['bytes'], 143 | 'kibibytes': file_sizes['kibibytes'], 144 | 'kilobytes': file_sizes['kilobytes'], 145 | }) 146 | total_size = Website.get_total_size(parsed_files) 147 | return { 148 | 'files': parsed_files, 149 | 'title': title, 150 | 'total_size_bytes': total_size, 151 | 'total_size_kibibytes': total_size / Website.BYTES_PER_KIB, 152 | 'total_size_kilobytes': total_size / Website.BYTES_PER_KB 153 | } 154 | @staticmethod 155 | def parseFriendlyURL(url): 156 | url = url.replace('https://', '') 157 | url = url.replace('http://', '') 158 | return url 159 | @staticmethod 160 | def parseURL(url): 161 | if (('https://' not in url) and ('http://' not in url)): 162 | return 'https://' + url 163 | return url -------------------------------------------------------------------------------- /backend.buildspec.yml: -------------------------------------------------------------------------------- 1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file. 2 | version: 0.2 3 | 4 | phases: 5 | install: 6 | runtime-versions: 7 | python: 3.7 8 | commands: 9 | - echo Installing Python 3.6 10 | - wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tar.xz 11 | - tar xJf Python-3.6.0.tar.xz 12 | - cd Python-3.6.0 13 | - ./configure 14 | - make 15 | - make install 16 | - mkdir package 17 | - echo Setup pip 18 | - python3 -m pip install --upgrade pip 19 | - cd ../ 20 | - echo Installing Python libraries 21 | - pip install --target ./package beautifulsoup4 22 | - pip install --target ./package urllib3 23 | - pip install --target ./package requests 24 | - cd ./package 25 | - zip -r9 ../function.zip ./ 26 | - cd .. 27 | build: 28 | commands: 29 | - echo Zip up application code 30 | - zip -g function.zip index.py 31 | - zip -g function.zip Website.py 32 | # Include only the files required for your application to run. 33 | # Do not use recursively include artifacts from node_modules directory as it will include unnecessary packages 34 | # used only for building and testing. 35 | # ExpressJS apps will need other artifact directories included (bin/*, public/*, routes/*, views/* etc). 36 | artifacts: 37 | files: 38 | - function.zip 39 | - deploy.buildspec.yml 40 | - deploy-lambda.sh -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/demo.gif -------------------------------------------------------------------------------- /deploy-lambda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | aws lambda update-function-code --function-name pageSize --region us-west-2 --zip-file fileb://./function.zip -------------------------------------------------------------------------------- /deploy-s3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm run --prefix ./public/ build 4 | npm run --prefix ./public/ deploy -------------------------------------------------------------------------------- /deploy.buildspec.yml: -------------------------------------------------------------------------------- 1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file. 2 | version: 0.2 3 | 4 | phases: 5 | install: 6 | runtime-versions: 7 | python: 3.7 8 | build: 9 | commands: 10 | - echo Deploy to Lambda 11 | - ./deploy-lambda.sh -------------------------------------------------------------------------------- /frontend.buildspec.yml: -------------------------------------------------------------------------------- 1 | # Do not change version. This is the version of aws buildspec, not the version of your buldspec file. 2 | version: 0.2 3 | 4 | phases: 5 | install: 6 | runtime-versions: 7 | nodejs: 10 8 | commands: 9 | - echo Installing source NPM dependencies... 10 | - npm --prefix ./public/ install 11 | build: 12 | commands: 13 | - echo Building website 14 | - npm run --prefix ./public/ build 15 | post_build: 16 | commands: 17 | - rm *.sh 18 | - rm .gitignore 19 | - rm *.yml 20 | - rm LICENSE README.md 21 | - rm *.py 22 | - mv public/build/* ./ 23 | - rm -r public 24 | artifacts: 25 | files: 26 | - '**/*' -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | import json 2 | from Website import Website 3 | 4 | origin = "https://fitonafloppy.website" 5 | 6 | def handler(event, context): 7 | url = json.loads(event['body'])['url'] 8 | is_https = json.loads(event['body'])['https'] 9 | 10 | url = url.replace('https://', '') 11 | url = url.replace('http://', '') 12 | 13 | if (is_https): 14 | url = "https://" + url 15 | else: 16 | url = "http://" + url 17 | 18 | print("Processing " + url) 19 | 20 | website = Website(url) 21 | response = website.getWebsiteContent() 22 | 23 | response['headers'] = { 24 | 'Content-Type': 'application/json', 25 | 'Access-Control-Allow-Origin': origin 26 | } 27 | 28 | return response 29 | 30 | if __name__ == '__main__': 31 | event = {"body": "{\"url\": \"www.brendonbody.com\", \"https\": true}"} 32 | print(json.dumps(handler(event, None), indent = 4, sort_keys=True)) 33 | -------------------------------------------------------------------------------- /package-lambda.sh: -------------------------------------------------------------------------------- 1 | zip function.zip index.py Website.py -------------------------------------------------------------------------------- /public/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /public/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["prettier"], 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | "globals": { 10 | "DocumentFragment": true, 11 | "it": true, 12 | "describe": true, 13 | "beforeEach": true, 14 | "runs": true, 15 | "spyOn": true, 16 | "spyOnEvent": true, 17 | "waitsFor": true, 18 | "expect": true, 19 | "afterEach": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /public/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build 4 | tmp 5 | .grunt 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /public/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-yeogurt": { 3 | "config": { 4 | "projectName": "Fit On A Floppy", 5 | "htmlOption": "nunjucks", 6 | "cssOption": "sass", 7 | "sassSyntax": "sass", 8 | "testFramework": "jasmine" 9 | }, 10 | "version": "3.0.1" 11 | } 12 | } -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | # Fit On A Floppy readme 2 | 3 | Generated on 2019-09-19 using 4 | [generator-yeogurt@3.0.1](https://github.com/larsonjj/generator-yeogurt) 5 | 6 | ## Description 7 | 8 | This is an example readme file. 9 | Describe your site/app here. 10 | 11 | ## Technologies used 12 | 13 | JavaScript 14 | 15 | - [Browserify](http://browserify.org/)with ES6/2015 support through [Babel](https://babeljs.io/) 16 | - [Node](https://nodejs.org/) 17 | 18 | Testing 19 | 20 | - [Karma](http://karma-runner.github.io/4.0/index.html) 21 | - [Jasmine](http://jasmine.github.io/) 22 | 23 | Styles 24 | 25 | - [Sass](http://sass-lang.com/) via ([node-sass](https://github.com/sass/node-sass)) 26 | 27 | Markup 28 | - [Nunjucks](https://mozilla.github.io/nunjucks/) 29 | 30 | Optimization 31 | 32 | - [Imagemin](https://github.com/imagemin/imagemin) 33 | - [Uglify](https://github.com/mishoo/UglifyJS) 34 | 35 | Server 36 | 37 | - [BrowserSync](http://www.browsersync.io/) 38 | 39 | Linting 40 | 41 | - [ESlint](http://eslint.org/) 42 | 43 | Automation 44 | 45 | - [Gulp](http://gulpjs.com) 46 | 47 | Code Management 48 | 49 | - [Editorconfig](http://editorconfig.org/) 50 | - [Git](https://git-scm.com/) 51 | 52 | ## Automated tasks 53 | 54 | This project uses [Gulp](http://gulpjs.com) and npm scripts (i.e. `npm run...`) to run automated tasks for development and production builds. 55 | The tasks are as follows: 56 | 57 | `npm run build`: Build a production version of your site/app 58 | 59 | `npm run serve`: Compiles preprocessors and boots up development server 60 | `npm run serve -- --open`: Same as `npm run serve` but will also open up site/app in your default browser 61 | `npm run serve:prod`: Same as `npm run serve` but will run all production tasks so you can view the site/app in it's final optimized form 62 | 63 | `npm test`: Lints all `*.js` file in the `source` folder using eslint and runs all `*.test.js` file unit tests through [Karma](http://karma-runner.github.io/0.13/index.html) and Jasmine 64 | `npm test -- --watch`: Same as `npm test` but will constantly watch `*.test.js` files and rerun tests when changes are detected 65 | 66 | **_Adding the `-- --debug` option to any npm script command to display extra debugging information (ex. data being loaded into your templates)_** 67 | -------------------------------------------------------------------------------- /public/gulp/tasks/browserSync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import gulp from 'gulp'; 4 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 5 | 6 | // BrowserSync 7 | gulp.task('browserSync', () => { 8 | return browserSync.init({ 9 | open: args.open ? 'local' : false, 10 | startPath: config.baseUrl, 11 | port: config.port || 3000, 12 | server: { 13 | baseDir: taskTarget, 14 | routes: (() => { 15 | let routes = {}; 16 | 17 | // Map base URL to routes 18 | routes[config.baseUrl] = taskTarget; 19 | 20 | return routes; 21 | })() 22 | } 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /public/gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import glob from 'glob'; 5 | import gulp from 'gulp'; 6 | import browserify from 'browserify'; 7 | import watchify from 'watchify'; 8 | import envify from 'envify'; 9 | import babelify from 'babelify'; 10 | import vsource from 'vinyl-source-stream'; 11 | import buffer from 'vinyl-buffer'; 12 | import gulpif from 'gulp-if'; 13 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 14 | 15 | let dirs = config.directories; 16 | let entries = config.entries; 17 | 18 | let browserifyTask = (files, done) => { 19 | return files.map(entry => { 20 | let dest = path.resolve(taskTarget); 21 | 22 | // Options 23 | let customOpts = { 24 | entries: [entry], 25 | debug: true, 26 | transform: [ 27 | babelify, // Enable ES6 features 28 | envify // Sets NODE_ENV for better optimization of npm packages 29 | ] 30 | }; 31 | 32 | let bundler = browserify(customOpts); 33 | 34 | if (!args.production) { 35 | // Setup Watchify for faster builds 36 | let opts = Object.assign({}, watchify.args, customOpts); 37 | bundler = watchify(browserify(opts)); 38 | } 39 | 40 | let rebundle = function() { 41 | let startTime = new Date().getTime(); 42 | bundler 43 | .bundle() 44 | .on('error', function(err) { 45 | plugins.util.log( 46 | plugins.util.colors.red('Browserify compile error:'), 47 | '\n', 48 | err.stack, 49 | '\n' 50 | ); 51 | this.emit('end'); 52 | }) 53 | .pipe(vsource(entry)) 54 | .pipe(buffer()) 55 | .pipe( 56 | gulpif(!args.production, plugins.sourcemaps.init({ loadMaps: true })) 57 | ) 58 | .pipe(gulpif(args.production, plugins.uglify())) 59 | .pipe( 60 | plugins.rename(function(filepath) { 61 | // Remove 'source' directory as well as prefixed folder underscores 62 | // Ex: 'src/_scripts' --> '/scripts' 63 | filepath.dirname = filepath.dirname 64 | .replace(dirs.source, '') 65 | .replace('_', ''); 66 | }) 67 | ) 68 | .pipe(gulpif(!args.production, plugins.sourcemaps.write('./'))) 69 | .pipe(gulp.dest(dest)) 70 | // Show which file was bundled and how long it took 71 | .on('end', function() { 72 | let time = (new Date().getTime() - startTime) / 1000; 73 | plugins.util.log( 74 | plugins.util.colors.cyan(entry) + 75 | ' was browserified: ' + 76 | plugins.util.colors.magenta(time + 's') 77 | ); 78 | done(); 79 | return browserSync.reload('*.js'); 80 | }); 81 | }; 82 | 83 | if (!args.production) { 84 | bundler.on('update', rebundle); // on any dep update, runs the bundler 85 | bundler.on('log', plugins.util.log); // output build logs to terminal 86 | } 87 | return rebundle(); 88 | }); 89 | }; 90 | 91 | // Browserify Task 92 | gulp.task('browserify', done => { 93 | glob(`${path.join(dirs.source, dirs.scripts)}/${entries.js}`, function( 94 | err, 95 | files 96 | ) { 97 | if (err) { 98 | throw new Error(err); 99 | } 100 | 101 | return browserifyTask(files, done); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /public/gulp/tasks/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import gulp from 'gulp'; 4 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 5 | import del from 'del'; 6 | 7 | let dirs = config.directories; 8 | 9 | // Clean 10 | gulp.task('clean', () => del([dirs.temporary, dirs.destination])); 11 | -------------------------------------------------------------------------------- /public/gulp/tasks/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import gulp from 'gulp'; 5 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 6 | 7 | let dirs = config.directories; 8 | let dest = path.join(taskTarget); 9 | 10 | // Copy 11 | gulp.task('copy', () => { 12 | return gulp.src([ 13 | '**/*', 14 | '!{**/\_*,**/\_*/**,*.md}', 15 | '!**/*.nunjucks' 16 | ], { cwd: dirs.source }) 17 | .pipe(plugins.changed(dest)) 18 | .pipe(gulp.dest(dest)); 19 | }); 20 | -------------------------------------------------------------------------------- /public/gulp/tasks/eslint.js: -------------------------------------------------------------------------------- 1 | /*eslint no-process-exit:0 */ 2 | 3 | 'use strict'; 4 | 5 | import path from 'path'; 6 | import gulpif from 'gulp-if'; 7 | import gulp from 'gulp'; 8 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 9 | 10 | let dirs = config.directories; 11 | 12 | // ESLint 13 | gulp.task('eslint', () => { 14 | return gulp 15 | .src( 16 | [ 17 | '../gulpfile.babel.js', 18 | '**/*.js', 19 | // Ignore all vendor folder files 20 | '!**/vendor/**/*' 21 | ], 22 | { cwd: dirs.source } 23 | ) 24 | .pipe(browserSync.reload({ stream: true, once: true })) 25 | .pipe( 26 | plugins.eslint({ 27 | useEslintrc: true 28 | }) 29 | ) 30 | .pipe(plugins.eslint.format()) 31 | .pipe(gulpif(!browserSync.active, plugins.eslint.failAfterError())) 32 | .on('error', function() { 33 | if (!browserSync.active) { 34 | process.exit(1); 35 | } 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /public/gulp/tasks/handlebars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var gulp = require('gulp'); 3 | var wrap = require('gulp-wrap'); 4 | var concat = require('gulp-concat'); 5 | var hb = require('handlebars'); 6 | var merge = require('merge-stream'); 7 | var handlebars = require('../../src/_templates/helpers.js')(hb); 8 | 9 | import { config, taskTarget, browserSync } from '../utils'; 10 | 11 | let dirs = config.directories; 12 | let entries = config.entries; 13 | 14 | let dest = path.join(taskTarget, dirs.scripts.replace(/^_/, '')); 15 | 16 | var gulp_handlebars = require('gulp-handlebars'); 17 | var wrap = require('gulp-wrap'); 18 | var declare = require('gulp-declare'); 19 | var concat = require('gulp-concat'); 20 | var uglify = require('gulp-uglify'); 21 | 22 | gulp.task('handlebars', function(){ 23 | var partials = gulp.src(entries.handlebars, { cwd: path.join(dirs.source, dirs.templates, 'partials')}) 24 | .pipe(gulp_handlebars({handlebars: handlebars})) 25 | .pipe(wrap('Handlebars.registerPartial(<%= processPartialName(file.relative) %>, Handlebars.template(<%= contents %>));', {}, { 26 | imports: { 27 | processPartialName: function(fileName) { 28 | // Strip the extension and the underscore 29 | // Escape the output with JSON.stringify 30 | return JSON.stringify(path.basename(fileName, '.js').substr(1)); 31 | } 32 | } 33 | })); 34 | 35 | var templates = gulp.src(entries.handlebars, { cwd: path.join(dirs.source, dirs.templates)}) 36 | .pipe(gulp_handlebars({handlebars: handlebars})) 37 | .pipe(wrap('Handlebars.template(<%= contents %>)')) 38 | .pipe(declare({ 39 | namespace: 'foaf', 40 | noRedeclare: true, // Avoid duplicate declarations 41 | })); 42 | 43 | return merge(partials, templates) 44 | .pipe(concat('templates.js')) 45 | .pipe(uglify()) 46 | .pipe(gulp.dest(dest)) 47 | .pipe(browserSync.stream({ match: 'scripts/templates**.js' })); 48 | }); -------------------------------------------------------------------------------- /public/gulp/tasks/imagemin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import gulpif from 'gulp-if'; 5 | import pngquant from 'imagemin-pngquant'; 6 | import gulp from 'gulp'; 7 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 8 | 9 | let dirs = config.directories; 10 | let dest = path.join(taskTarget, dirs.images.replace(/^_/, '')); 11 | 12 | // Imagemin 13 | gulp.task('imagemin', () => { 14 | return gulp 15 | .src('**/*.{jpg,jpeg,gif,svg,png}', { 16 | cwd: path.join(dirs.source, dirs.images) 17 | }) 18 | .pipe(plugins.changed(dest)) 19 | .pipe( 20 | gulpif( 21 | args.production, 22 | plugins.imagemin( 23 | [ 24 | plugins.imagemin.jpegtran({ progressive: true }), 25 | plugins.imagemin.svgo({ plugins: [{ removeViewBox: false }] }) 26 | ], 27 | { use: [pngquant({ speed: 10 })] } 28 | ) 29 | ) 30 | ) 31 | .pipe(gulp.dest(dest)); 32 | }); 33 | -------------------------------------------------------------------------------- /public/gulp/tasks/nunjucks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import foldero from 'foldero'; 6 | import nunjucks from 'gulp-nunjucks-html'; 7 | import yaml from 'js-yaml'; 8 | import gulp from 'gulp'; 9 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 10 | 11 | let dirs = config.directories; 12 | let dest = path.join(taskTarget); 13 | let dataPath = path.join(dirs.source, dirs.data); 14 | 15 | // Nunjucks template compile 16 | gulp.task('nunjucks', () => { 17 | let siteData = {}; 18 | if (fs.existsSync(dataPath)) { 19 | // Convert directory to JS Object 20 | siteData = foldero(dataPath, { 21 | recurse: true, 22 | whitelist: '(.*/)*.+.(json|ya?ml)$', 23 | loader: function loadAsString(file) { 24 | let json = {}; 25 | try { 26 | if (path.extname(file).match(/^.ya?ml$/)) { 27 | json = yaml.safeLoad(fs.readFileSync(file, 'utf8')); 28 | } else { 29 | json = JSON.parse(fs.readFileSync(file, 'utf8')); 30 | } 31 | } catch (e) { 32 | plugins.util.log(`Error Parsing DATA file: ${file}`); 33 | plugins.util.log('==== Details Below ===='); 34 | plugins.util.log(e); 35 | } 36 | return json; 37 | } 38 | }); 39 | } 40 | 41 | // Add --debug option to your gulp task to view 42 | // what data is being loaded into your templates 43 | if (args.debug) { 44 | plugins.util.log('==== DEBUG: site.data being injected to templates ===='); 45 | plugins.util.log(siteData); 46 | plugins.util.log('\n==== DEBUG: package.json config being injected to templates ===='); 47 | plugins.util.log(config); 48 | } 49 | 50 | return ( 51 | gulp 52 | // Ignore underscore prefix folders/files (ex. _custom-layout.nunjucks) 53 | .src(['**/*.nunjucks', '!{**/_*,**/_*/**}'], { cwd: dirs.source }) 54 | .pipe(plugins.changed(dest)) 55 | .pipe(plugins.plumber()) 56 | .pipe( 57 | plugins.data({ 58 | config: config, 59 | debug: !args.production, 60 | site: { 61 | data: siteData 62 | } 63 | }) 64 | ) 65 | .pipe( 66 | nunjucks({ 67 | searchPaths: [path.join(dirs.source)], 68 | ext: '.html' 69 | }).on('error', function(err) { 70 | plugins.util.log(err); 71 | }) 72 | ) 73 | .on('error', function(err) { 74 | plugins.util.log(err); 75 | }) 76 | .pipe( 77 | plugins.htmlmin({ 78 | collapseBooleanAttributes: true, 79 | conservativeCollapse: true, 80 | removeCommentsFromCDATA: true, 81 | removeEmptyAttributes: true, 82 | removeRedundantAttributes: true 83 | }) 84 | ) 85 | .pipe(gulp.dest(dest)) 86 | .on('end', browserSync.reload) 87 | ); 88 | }); 89 | -------------------------------------------------------------------------------- /public/gulp/tasks/rev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import gulpFilter from 'gulp-filter'; 5 | import gulpRev from 'gulp-rev'; 6 | import gulpRevDel from 'gulp-rev-delete-original'; 7 | import gulpRevRewrite from 'gulp-rev-rewrite'; 8 | import gulp from 'gulp'; 9 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 10 | 11 | let dirs = config.directories; 12 | let dest = path.join(taskTarget); 13 | 14 | // Copy 15 | gulp.task('rev', () => { 16 | // gulp-rev-rewrite will mangle binary files (images, etc), so ignore them 17 | const binaryAssetFilter = gulpFilter( 18 | ['**', '!**/*.{ico,png,jpg,jpeg,gif,webp}'], 19 | { restore: true } 20 | ); 21 | const htmlFilter = gulpFilter(['**', '!**/*.html'], { restore: true }); 22 | return gulp 23 | .src(`**/*.{js,css,html}`, { cwd: dirs.destination }) 24 | .pipe(htmlFilter) 25 | .pipe(gulpRev()) 26 | .pipe(htmlFilter.restore) 27 | .pipe(binaryAssetFilter) 28 | .pipe(gulpRevRewrite()) 29 | .pipe(binaryAssetFilter.restore) 30 | .pipe(gulp.dest(dest)) 31 | .pipe(gulpRevDel()) 32 | .pipe(gulpRev.manifest()) 33 | .pipe(gulp.dest(dest)); 34 | }); 35 | -------------------------------------------------------------------------------- /public/gulp/tasks/sass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import autoprefixer from 'autoprefixer'; 5 | import gulpif from 'gulp-if'; 6 | import gulp from 'gulp'; 7 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 8 | 9 | let dirs = config.directories; 10 | let entries = config.entries; 11 | let dest = path.join(taskTarget, dirs.styles.replace(/^_/, '')); 12 | 13 | // Sass compilation 14 | gulp.task('sass', () => { 15 | return gulp 16 | .src(entries.css, { cwd: path.join(dirs.source, dirs.styles) }) 17 | .pipe(plugins.plumber()) 18 | .pipe(gulpif(!args.production, plugins.sourcemaps.init({ loadMaps: true }))) 19 | .pipe( 20 | plugins.sass({ 21 | outputStyle: 'expanded', 22 | precision: 10, 23 | includePaths: [ 24 | path.join(dirs.source, dirs.styles), 25 | path.join(dirs.source, dirs.modules) 26 | ] 27 | }) 28 | ) 29 | .on('error', function(err) { 30 | plugins.util.log(err); 31 | }) 32 | .pipe(plugins.postcss([autoprefixer()])) 33 | .pipe( 34 | plugins.rename(function(path) { 35 | // Remove 'source' directory as well as prefixed folder underscores 36 | // Ex: 'src/_styles' --> '/styles' 37 | path.dirname = path.dirname.replace(dirs.source, '').replace('_', ''); 38 | }) 39 | ) 40 | .pipe(gulpif(args.production, plugins.cssnano({ rebase: false }))) 41 | .pipe(gulpif(!args.production, plugins.sourcemaps.write('./'))) 42 | .pipe(gulp.dest(dest)) 43 | .pipe(browserSync.stream({ match: '**/*.css' })); 44 | }); 45 | -------------------------------------------------------------------------------- /public/gulp/tasks/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import gulp from 'gulp'; 5 | import { plugins, args, config, taskTarget, browserSync } from '../utils'; 6 | 7 | let dirs = config.directories; 8 | 9 | // Watch task 10 | gulp.task('watch', (done) => { 11 | if (!args.production) { 12 | // Styles 13 | gulp.watch([ 14 | path.join(dirs.source, dirs.styles) + '/**/*.{scss,sass}', 15 | path.join(dirs.source, dirs.modules) + '/**/*.{scss,sass}' 16 | ], gulp.series('sass')); 17 | 18 | // Nunjucks Templates 19 | gulp.watch([ 20 | dirs.source + '/**/*.nunjucks', 21 | path.join(dirs.source, dirs.data) + '/**/*.{json,yaml,yml}' 22 | ], gulp.series('nunjucks')); 23 | 24 | 25 | // Copy 26 | gulp.watch([ 27 | dirs.source + '**/*', 28 | '!' + dirs.source + '/{**/\_*,**/\_*/**}', 29 | '!' + dirs.source + '/**/*.nunjucks' 30 | ], gulp.series('copy')); 31 | 32 | // Images 33 | gulp.watch([ 34 | path.join(dirs.source, dirs.images) + '/**/*.{jpg,jpeg,gif,svg,png}' 35 | ], gulp.series('imagemin')); 36 | 37 | // Handlebars 38 | 39 | gulp.watch([ 40 | path.join(dirs.source, dirs.templates) + '/**/*.hbs' 41 | ], gulp.series('handlebars')); 42 | 43 | // All other files 44 | gulp.watch([ 45 | dirs.temporary + '/**/*', 46 | '!' + dirs.temporary + '/**/*.{css,map,html,js}' 47 | ]).on('change', browserSync.reload); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /public/gulp/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import gulpLoadPlugins from 'gulp-load-plugins'; 4 | import browserSyncLib from 'browser-sync'; 5 | import pjson from '../package.json'; 6 | import minimist from 'minimist'; 7 | 8 | // Load all gulp plugins based on their names 9 | // EX: gulp-copy -> copy 10 | export const plugins = gulpLoadPlugins(); 11 | 12 | // Create karma server 13 | export const KarmaServer = require('karma').Server; 14 | 15 | // Get package.json custom configuration 16 | export const config = Object.assign({}, pjson.config); 17 | 18 | // Gather arguments passed to gulp commands 19 | export const args = minimist(process.argv.slice(2)); 20 | 21 | // Alias config directories 22 | export const dirs = config.directories; 23 | 24 | // Determine gulp task target destinations 25 | export const taskTarget = args.production ? dirs.destination : dirs.temporary; 26 | 27 | // Create a new browserSync instance 28 | export const browserSync = browserSyncLib.create(); 29 | -------------------------------------------------------------------------------- /public/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import gulp from 'gulp'; 5 | import glob from 'glob'; 6 | import { KarmaServer, args } from './gulp/utils'; 7 | 8 | // This will grab all js in the `gulp` directory 9 | // in order to load all gulp tasks 10 | glob.sync('./gulp/tasks/**/*.js').filter(function(file) { 11 | return (/\.(js)$/i).test(file); 12 | }).map(function(file) { 13 | require(file); 14 | }); 15 | 16 | // Build production-ready code 17 | gulp.task('build', gulp.series( 18 | gulp.parallel( 19 | 'copy', 20 | 'handlebars', 21 | 'imagemin', 22 | 'nunjucks', 23 | 'sass', 24 | 'browserify' 25 | ), 26 | 'rev' 27 | )); 28 | 29 | // Server tasks with watch 30 | gulp.task('serve', gulp.series( 31 | gulp.parallel( 32 | 'handlebars', 33 | 'imagemin', 34 | 'copy', 35 | 'nunjucks', 36 | 'sass', 37 | 'browserify', 38 | 'browserSync', 39 | 'watch' 40 | ) 41 | )); 42 | 43 | // Default task 44 | gulp.task('default', gulp.series('clean', 'build')); 45 | 46 | // Testing 47 | gulp.task('test', gulp.series('eslint', (done) => { 48 | new KarmaServer({ 49 | configFile: path.join(__dirname, '/karma.conf.js'), 50 | singleRun: !args.watch, 51 | autoWatch: args.watch 52 | }, done).start(); 53 | })); 54 | -------------------------------------------------------------------------------- /public/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | 'use strict'; 4 | var path = require('path'); 5 | var pjson = require('./package.json'); 6 | var config = pjson.config; 7 | var dirs = config.directories; 8 | var testFiles = path.join(__dirname, dirs.source) + '/**/*.test.js'; 9 | var preprocessors = {}; 10 | preprocessors[testFiles] = ['browserify']; 11 | const puppeteer = require('puppeteer'); 12 | process.env.CHROME_BIN = puppeteer.executablePath(); 13 | 14 | var karmaConf = function(config) { 15 | config.set({ 16 | // base path, that will be used to resolve files and exclude 17 | basePath: '', 18 | 19 | // testing framework to use (jasmine/mocha/qunit/...) 20 | frameworks: ['browserify', 'jasmine'], 21 | 22 | // list of files / patterns to load in the browser 23 | files: [ 24 | testFiles 25 | ], 26 | 27 | // list of files to exclude 28 | exclude: [], 29 | 30 | preprocessors: preprocessors, 31 | 32 | browserify: { 33 | debug: true, 34 | transform: [ 35 | require('envify'), 36 | require('babelify') 37 | ] 38 | }, 39 | 40 | // test results reporter to use 41 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 42 | reporters: ['progress'], 43 | 44 | // Setup to allow external devices to access karma tests 45 | // Change to '127.0.0.1' to disallow external access 46 | host: '0.0.0.0', 47 | 48 | // web server port 49 | port: 3012, 50 | 51 | // enable / disable colors in the output (reporters and logs) 52 | colors: true, 53 | 54 | 55 | // level of logging 56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 57 | logLevel: config.LOG_INFO, 58 | 59 | 60 | // enable / disable watching file and executing tests whenever any file changes 61 | autoWatch: false, 62 | 63 | 64 | // Start these browsers, currently available: 65 | // - Chrome 66 | // - ChromeHeadless 67 | // - ChromeCanary 68 | // - Firefox 69 | // - Opera 70 | // - Safari (only Mac) 71 | // - PhantomJS 72 | // - IE (only Windows) 73 | browsers: ['ChromeHeadless'], 74 | 75 | // If browser does not capture in given timeout [ms], kill it 76 | captureTimeout: 60000, 77 | 78 | // If browser does not have any activity for given timeout [ms], kill it 79 | browserNoActivityTimeout: 60000, 80 | 81 | // Continuous Integration mode 82 | // if true, it capture browsers, run tests and exit 83 | singleRun: false 84 | }); 85 | }; 86 | 87 | module.exports = karmaConf; 88 | -------------------------------------------------------------------------------- /public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fit-on-a-floppy", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "jquery": "~3.5.0", 6 | "normalize.css": "~8.0.1" 7 | }, 8 | "devDependencies": { 9 | "@babel/core": "~7.0.0", 10 | "@babel/preset-env": "~7.0.0", 11 | "@babel/register": "~7.4.4", 12 | "acorn": "~6.4.1", 13 | "autoprefixer": "~9.6.1", 14 | "babel-eslint": "~9.0.0", 15 | "babelify": "~10.0.0", 16 | "browser-sync": "~2.26.7", 17 | "browserify": "~16.3.0", 18 | "del": "~2.2.2", 19 | "envify": "~4.1.0", 20 | "eslint": "6.1.0", 21 | "eslint-config-prettier": "~6.0.0", 22 | "foldero": "~0.1.1", 23 | "glob": "~7.1.4", 24 | "gulp": "~4.0.2", 25 | "gulp-changed": "~4.0.0", 26 | "gulp-compile-handlebars": "^0.6.1", 27 | "gulp-concat": "^2.6.1", 28 | "gulp-cssnano": "~2.1.3", 29 | "gulp-data": "~1.3.1", 30 | "gulp-declare": "^0.3.0", 31 | "gulp-eslint": "~6.0.0", 32 | "gulp-filter": "6.0.0", 33 | "gulp-handlebars": "^5.0.2", 34 | "gulp-htmlmin": "~5.0.1", 35 | "gulp-if": "~2.0.2", 36 | "gulp-imagemin": "~6.0.0", 37 | "gulp-load-plugins": "~2.0.0", 38 | "gulp-nunjucks-html": "~2.0.0", 39 | "gulp-plumber": "~1.2.1", 40 | "gulp-postcss": "~8.0.0", 41 | "gulp-rename": "~1.4.0", 42 | "gulp-rev": "9.0.0", 43 | "gulp-rev-delete-original": "0.2.3", 44 | "gulp-rev-rewrite": "2.0.0", 45 | "gulp-sass": "~4.0.2", 46 | "gulp-sourcemaps": "~2.6.5", 47 | "gulp-uglify": "^3.0.2", 48 | "gulp-util": "~3.0.8", 49 | "gulp-wrap": "^0.15.0", 50 | "handlebars": "^4.2.0", 51 | "imagemin-pngquant": "~7.0.0", 52 | "imagemin-svgo": "~7.0.0", 53 | "jasmine-core": "3.4.0", 54 | "js-yaml": "~3.13.0", 55 | "karma": "~4.2.0", 56 | "karma-browserify": "~6.1.0", 57 | "karma-chrome-launcher": "~3.0.0", 58 | "karma-jasmine": "~2.0.1", 59 | "lodash": "~4.17.4", 60 | "minimist": "~1.2.0", 61 | "nunjucks": "~3.2.0", 62 | "prettier": "~1.16.4", 63 | "puppeteer": "1.19.0", 64 | "rimraf": "2.6.3", 65 | "vinyl-buffer": "~1.0.1", 66 | "vinyl-source-stream": "~2.0.0", 67 | "watchify": "~3.11.0 " 68 | }, 69 | "scripts": { 70 | "clean-deps": "npx rimraf node_modules", 71 | "clean": "npx gulp clean", 72 | "lint": "npx gulp eslint", 73 | "test": "npx gulp test", 74 | "format": "npx prettier --single-quote --write src/**/*.js", 75 | "serve": "NODE_ENV=development npx gulp serve", 76 | "serve:prod": "NODE_ENV=production npx gulp serve --production", 77 | "build": "gulp handlebars && NODE_ENV=production npx gulp --production", 78 | "deploy": "s3-deploy './build/**' --cwd './build/' --region=us-west-2 --bucket=fitonafloppy.website --deleteRemoved --gzip=xml,html,htm,js,css,ttf,otf,svg,txt" 79 | }, 80 | "engines": { 81 | "node": ">=8.16.0" 82 | }, 83 | "browserslist": [ 84 | "last 2 version", 85 | "> 5%", 86 | "safari 5", 87 | "ios 6", 88 | "android 4" 89 | ], 90 | "//": "CUSTOM CONFIGURATION", 91 | "config": { 92 | "//": "Entry files", 93 | "host": "127.0.0.1", 94 | "port": "3000", 95 | "baseUrl": "./", 96 | "directories": { 97 | "source": "src", 98 | "destination": "build", 99 | "temporary": "tmp", 100 | "//": "Directories relative to `source` directory", 101 | "modules": "_modules", 102 | "templates": "_templates", 103 | "layouts": "_layouts", 104 | "images": "_images", 105 | "styles": "_styles", 106 | "scripts": "_scripts", 107 | "data": "_data" 108 | }, 109 | "entries": { 110 | "js": "main**.js", 111 | "css": "main**.{sass,scss}", 112 | "handlebars": "**.hbs" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /public/src/README.md: -------------------------------------------------------------------------------- 1 | # Source 2 | 3 | This "Source" folder is where all of your files associated with this site will go 4 | and is considered the root ('/') of your site. 5 | This is also where all of your pages will be generated when using the [page subgenerator](#Subgenerator). 6 | 7 | ## Pages 8 | 9 | Pages are the main driver for static sites and also determine your site's routes. 10 | All page templates (except index.{pug,nunjucks}) should be placed in a folder named by your desired route. 11 | For example, a contact page would most likely be loaded at the `/contact` route. 12 | You would acheive this by creating the following structure: 13 | 14 | ``` 15 | └── src 16 | └── contact 17 | └── index.{pug,nunjucks} 18 | ``` 19 | 20 | ### Subgenerator 21 | 22 | You can easily create new pages using the built-in sub-generator like so: 23 | 24 | ``` 25 | yo yeogurt:page about 26 | ``` 27 | 28 | This will create the structure you saw above: 29 | 30 | ``` 31 | └── src 32 | └── about 33 | └── index.{pug,nunjucks} 34 | ``` 35 | 36 | So when you boot up your site and go to `/about` you will see your new page. 37 | 38 | ### Specifying a layout 39 | 40 | You can also create a new page that extends from a different layout file than `base.{pug,nunjucks}`. 41 | 42 | ``` 43 | yo yeogurt:page about --layout=two-col 44 | ``` 45 | -------------------------------------------------------------------------------- /public/src/_data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | This "Data" folder is the designated location for `json` and `yaml` file data 4 | that will be injected into your templates under the `site.data` property. 5 | 6 | ## Example 7 | 8 | If you have two data files in this data folder with the following contents: 9 | 10 | ``` 11 | └── _data 12 | ├── global.yml 13 | └── menu.json 14 | ``` 15 | 16 | ***global.yml*** 17 | 18 | ```yml 19 | siteName: Sample 20 | ``` 21 | 22 | ***menu.json*** 23 | 24 | ```json 25 | [{ 26 | "name": "Home" 27 | },{ 28 | "name": "About" 29 | }] 30 | ``` 31 | 32 | They would be converted to the following object: 33 | 34 | ```js 35 | { 36 | menu: [{ 37 | name: "Home" 38 | }, { 39 | name: "About" 40 | }], 41 | global: { 42 | siteName: "Sample" 43 | } 44 | } 45 | ``` 46 | 47 | And would then be injected into your template within the `site.data` property 48 | so you could access your data like so: 49 | 50 | ```nunjucks 51 |

{{site.data.global.siteName}}

52 | 57 | ``` 58 | -------------------------------------------------------------------------------- /public/src/_images/README.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | This "Images" folder is designated for all image files (jpg, jpeg, png, gif, svg). 4 | During development and production builds, images will be compressed using [imagemin](https://github.com/imagemin/imagemin). 5 | -------------------------------------------------------------------------------- /public/src/_images/floppy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/_images/floppy.jpg -------------------------------------------------------------------------------- /public/src/_layouts/README.md: -------------------------------------------------------------------------------- 1 | # Layouts 2 | 3 | This "Layouts" folder is designated for all page layouts. 4 | 5 | ## Example 6 | 7 | An example layout: 8 | 9 | ```nunjucks 10 | {% extends "base.nunjucks" %} 11 | 12 | //- Add extra stylesheets 13 | {% block stylesheets %}{% endblock %} 14 | 15 | {% block content %} 16 | //- Provides layout level markup 17 |
18 | {% block first %} 19 | //- Add first column content here 20 | {% endblock %} 21 | {% block second %} 22 | //- Add second column content here 23 | {% endblock %} 24 |
25 | {% endblock %} 26 | 27 | //- Add extra scripts 28 | {% block scripts %}{% endblock } 29 | ``` 30 | 31 | > NOTE: The `append stylesheets` and `append scripts` blocks allow you to add on any layout-specific scripts or stylesheets. 32 | > The `content` block is overriding the parent `base.pug` file's block by the same name since we are extending from it. 33 | > The `first` and `second` blocks can contain default markup, but also allow you to extend from this layout and overwrite them. 34 | > You can read more about extensions and blocks on the [Pug website](https://pugjs.org/api/reference.html) 35 | 36 | ## Sub-generator 37 | 38 | You can easily create new layouts using the built-in sub-generator like so: 39 | 40 | ````sh 41 | 42 | yo yeogurt:layout two-col 43 | 44 | ``` 45 | 46 | ### Extend from a layout other than `base` 47 | 48 | You can also create a new layout that extends from a different layout file than `base.pug`. 49 | 50 | ```sh 51 | 52 | yo yeogurt:layout three-col --layout=two-col 53 | 54 | ```` 55 | 56 | This new layout will look something like this: 57 | 58 | ```nunjucks 59 | {% extends "two-col.nunjucks" %} 60 | 61 | //- Add extra stylesheets 62 | {% block stylesheets %}{% endblock %} 63 | 64 | {% block content %} 65 | //- Provides layout level markup 66 |
67 | {% block three-col %}{% endblock %} 68 |
69 | {% endblock %} 70 | 71 | //- Add extra scripts 72 | {% block scripts %}{% endblock } 73 | ```` 74 | -------------------------------------------------------------------------------- /public/src/_layouts/base.nunjucks: -------------------------------------------------------------------------------- 1 | {# Custom Configuration #} 2 | {% block config %} 3 | {# Setup site's base URL to match the "baseUrl" key within `package.json` #} 4 | {# Otherwise default to relative pathing #} 5 | {% set baseUrl = config.baseUrl or './' %} 6 | {% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | Fit on a Floppy 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% block stylesheets %}{% endblock %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Fork me on GitHub 41 | 42 |
43 | {% block content %}{% endblock %} 44 | {% include "./partials/carbon-ad.nunjucks" %} 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {% include "./partials/google-analytics.nunjucks" %} 54 | 55 | {% block scripts %}{% endblock %} 56 | {% include "./partials/issue-embed.nunjucks" %} 57 | 58 | 59 | -------------------------------------------------------------------------------- /public/src/_layouts/partials/carbon-ad.nunjucks: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /public/src/_layouts/partials/google-analytics.nunjucks: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/src/_layouts/partials/issue-embed.nunjucks: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /public/src/_modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | This "Modules" folder is designated for reusable pieces of code that are used within layouts and pages. 4 | 5 | ## Example 6 | 7 | An example link module: 8 | 9 | ``` 10 | └── link 11 | ├── __tests__ 12 | | └── link.spec.js 13 | ├── link.{pug,nunjucks} 14 | ├── link.js 15 | └── link.scss 16 | ``` 17 | 18 | Each module should include a template, javascript file, stylesheet, and unit test file (if doing unit testing). 19 | These files should use the same name, i.e `link`. If you don't need one of the files in a module, feel free to delete it. 20 | 21 | ## Sub-generator 22 | 23 | You can easily create new modules using the built-in sub-generator like so: 24 | 25 | ``` 26 | yo yeogurt:module link 27 | ``` 28 | 29 | ### Atomic modules 30 | 31 | You can also create modules specific to [atomic design](http://patternlab.io/about.html) as well 32 | by using the `--atomic` option with the following values: `atom`, `molecule`, and `organism`: 33 | 34 | ``` 35 | yo yeogurt:module link --atomic=atom 36 | ``` 37 | 38 | This will place your new module within the corresponding atomic folder like the following: 39 | 40 | ``` 41 | └── atoms 42 | └── link 43 | ├── __tests__ 44 | | └── link.spec.js 45 | ├── link.{pug,nunjucks} 46 | ├── link.js 47 | └── link.scss 48 | ``` 49 | -------------------------------------------------------------------------------- /public/src/_scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | This "Scripts" folder is designated for all of your global JavaScript files. 4 | The key file in this folder is `main.js` as it is designated as your bootstrapping file (intializes all your scripts) and is included in the `base.pug` file 5 | 6 | By default, ES6/2015 features are enabled in your scripts by using [Babel](https://babeljs.io) 7 | 8 | ## Adding third-party script libraries 9 | Odds are that you will need to add some third party libraries to your project at some point. 10 | To do so, it is strongly recommended that you install them using [NPM](http://npmjs.com/): 11 | 12 | ``` 13 | npm install [package name] --save 14 | ``` 15 | 16 | Once installed, you can access scripts within your JavaScript files like so: 17 | 18 | ```js 19 | // Example using jquery 20 | 21 | import $ from 'jquery'; 22 | 23 | $(() => { 24 | console.log('Hello'); 25 | }); 26 | ``` 27 | 28 | #### Using Non-CommonJS modules with browserify-shim 29 | 30 | Sometimes you need to use libraries that attach themselves to the window object and don't work with browserify very well. 31 | In this case, you can use a transform called [browserify-shim](https://github.com/thlorenz/browserify-shim). 32 | 33 | ***Step 1: Install browserify-shim transform for browserify*** 34 | 35 | Browserify doesn't support Non-CommonJS scripts out of the box (jQuery plugins, window.* libs, etc), but you can install a transform called 'browserify-shim' to remedy that: 36 | 37 | ``` 38 | npm install --save-dev browserify-shim 39 | ``` 40 | 41 | ***Step 2: Install desired npm package*** 42 | 43 | Now you can install your desired npm package: 44 | 45 | ``` 46 | // Example: jQuery plugin 47 | 48 | npm install --save slick-carousel 49 | ``` 50 | 51 | ***Step 3: Setup browserify-shim*** 52 | 53 | Add the following to your `package.json` file: 54 | 55 | ```json 56 | "browserify": { 57 | "transform": [ "browserify-shim" ] 58 | }, 59 | "browser": { 60 | "slick-carousel": "./node_modules/slick-carousel/slick/slick.js" 61 | }, 62 | "browserify-shim": { 63 | "slick-carousel": { 64 | "exports": null, 65 | "depends": "jquery:$" 66 | } 67 | }, 68 | ``` 69 | > Note: [slick-carousel](http://kenwheeler.github.io/slick/) requires jQuery, hence the `"depends": "jquery:$"` 70 | 71 | ***Step 4: Import file to your project*** 72 | 73 | Now you can include your desired module/lib within your `src/_scripts/main.js` file: 74 | 75 | ```js 76 | import 'slick-carousel'; 77 | 78 | ... 79 | 80 | $('#someId').slick(); // Activates slick plugin 81 | ``` 82 | -------------------------------------------------------------------------------- /public/src/_scripts/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var endpointURL = 'https://7udyio7rpg.execute-api.us-west-2.amazonaws.com/default/pageSize'; 4 | var addHelpers = require('../_templates/helpers.js'); 5 | 6 | addHelpers(Handlebars); 7 | 8 | var ids = ['outputRow','inputRow']; 9 | 10 | function addClass(className) { 11 | for (var i = 0; i < ids.length; i++) { 12 | document.getElementById(ids[i]).classList.add(className); 13 | } 14 | } 15 | 16 | function removeClass(className) { 17 | for (var i = 0; i < ids.length; i++) { 18 | document.getElementById(ids[i]).classList.remove(className); 19 | } 20 | } 21 | 22 | function outputMessage(html) { 23 | document.getElementById('output').innerHTML = html; 24 | 25 | removeClass('loading'); 26 | addClass('loaded'); 27 | } 28 | 29 | function toggleAlert(show) { 30 | var alert = document.getElementById('alert'); 31 | alert.style.display = show ? 'block' : 'none'; 32 | } 33 | 34 | function showError() { 35 | var errorMessage = 'Unfortunately we couldn\'t load that website, can you recheck your URL or try again?'; 36 | toggleAlert(true); 37 | document.getElementById('alert').innerHTML = errorMessage; 38 | removeClass('loading'); 39 | } 40 | 41 | function showResults(data) { 42 | data.moreThanOneFloppy = data.floppies === 1; 43 | var html = window.foaf.output(data); 44 | 45 | outputMessage(html); 46 | } 47 | 48 | function checkIfItWillFit(url, protocol) { 49 | toggleAlert(false); 50 | document.getElementById('output').innerHTML = ''; 51 | document.getElementById('alert').innerHTML = ''; 52 | removeClass('loaded'); 53 | addClass('loading'); 54 | 55 | var xhr = new XMLHttpRequest(); 56 | xhr.open('POST', endpointURL); 57 | xhr.setRequestHeader('Content-Type', 'application/json'); 58 | xhr.onload = function() { 59 | if (xhr.status !== 200) { 60 | showError(); 61 | } else { 62 | var json = JSON.parse(xhr.responseText); 63 | showResults(json); 64 | var https = protocol === "https"; 65 | var newUrl = "?website=" + url + "&https=" + https; 66 | var title = "Fit on a Floppy - " + json.title; 67 | History.pushState({ website: url, https}, title, newUrl); 68 | document.title = title; 69 | } 70 | }; 71 | xhr.onerror = showError; 72 | xhr.send(JSON.stringify({ 73 | url, 74 | https: protocol === "https" 75 | })); 76 | } 77 | 78 | window.onSubmit = function (event) { 79 | event.preventDefault(); 80 | 81 | checkIfItWillFit(event.currentTarget[1].value, event.currentTarget[0].value); 82 | }; 83 | 84 | 85 | // Can't seem to set type="text" without it being stripped out by Nunjucks 86 | document.getElementById('website').setAttribute('type', 'text'); 87 | 88 | var urlParams = new URLSearchParams(window.location.search); 89 | 90 | if (urlParams.has('website') && urlParams.has('https')) { 91 | var website = urlParams.get('website'); 92 | var protocol = urlParams.get('https') === "false" ? "http" : "https"; 93 | document.getElementById('website').value = website; 94 | document.getElementById('protocol').value = protocol; 95 | checkIfItWillFit(website, protocol); 96 | } -------------------------------------------------------------------------------- /public/src/_styles/README.md: -------------------------------------------------------------------------------- 1 | # Styles 2 | 3 | This "Styles" folder is designated for all of your global stylesheet files. 4 | The key file in this folder is `main` as it is designated as your bootstrapping file (intializes/imports all your stylesheets) and is included in the `base.pug` file 5 | 6 | ## Adding third-party stylesheet libraries 7 | Odds are that you will need to add some third party libraries to your project at some point. 8 | To do so, it is strongly recommended that you install them using [NPM](http://npmjs.com/): 9 | 10 | ``` 11 | npm install [package name] --save 12 | ``` 13 | 14 | **Using SCSS:** 15 | 16 | ```scss 17 | // SCSS 18 | @import 'node_modules/bootstrap-sass-official/scss/bootstrap'; 19 | 20 | // CSS 21 | @import 'node_modules/normalize.css/normalize'; 22 | ``` 23 | 24 | **Using SASS:** 25 | 26 | ```sass 27 | // SASS 28 | @import node_modules/bootstrap-sass-official/scss/bootstrap 29 | 30 | // CSS 31 | @import node_modules/normalize.css/normalize 32 | ``` 33 | -------------------------------------------------------------------------------- /public/src/_styles/_carbon-ad.scss: -------------------------------------------------------------------------------- 1 | #carbonads { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | } 4 | 5 | #carbonads { 6 | display: flex; 7 | max-width: 330px; 8 | background-color: hsl(0, 0%, 98%); 9 | box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1); 10 | } 11 | 12 | #carbonads a { 13 | color: inherit; 14 | text-decoration: none; 15 | } 16 | 17 | #carbonads a:hover { 18 | color: inherit; 19 | } 20 | 21 | #carbonads span { 22 | position: relative; 23 | display: block; 24 | overflow: hidden; 25 | } 26 | 27 | #carbonads .carbon-wrap { 28 | display: flex; 29 | } 30 | 31 | .carbon-img { 32 | display: block; 33 | margin: 0; 34 | line-height: 1; 35 | } 36 | 37 | .carbon-img img { 38 | display: block; 39 | } 40 | 41 | .carbon-text { 42 | font-size: 13px; 43 | padding: 10px; 44 | line-height: 1.5; 45 | text-align: left; 46 | } 47 | 48 | .carbon-poweredby { 49 | display: block; 50 | padding: 8px 10px; 51 | background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, 0.025) 5px, hsla(0, 0%, 0%, 0.025) 10px) hsla(203, 11%, 95%, 0.4); 52 | text-align: center; 53 | text-transform: uppercase; 54 | letter-spacing: .5px; 55 | font-weight: 600; 56 | font-size: 9px; 57 | line-height: 1; 58 | } 59 | -------------------------------------------------------------------------------- /public/src/_styles/_github-fork.scss: -------------------------------------------------------------------------------- 1 | #forkongithub a { 2 | background: #33c3f0; 3 | color: #fff; 4 | text-decoration: none; 5 | font-family: arial,sans-serif; 6 | text-align: center; 7 | font-weight: bold; 8 | padding: 5px 40px; 9 | font-size: 1rem; 10 | line-height: 2rem; 11 | position: relative; 12 | transition: 0.5s; 13 | } 14 | 15 | #forkongithub a:hover { 16 | background: #0077A4; 17 | color: #fff; 18 | } 19 | 20 | #forkongithub a::before, #forkongithub a::after { 21 | content: ""; 22 | width: 100%; 23 | display: block; 24 | position: absolute; 25 | top: 1px; 26 | left: 0; 27 | height: 1px; 28 | background: #fff; 29 | } 30 | 31 | #forkongithub a::after { 32 | bottom: 1px; 33 | top: auto; 34 | } 35 | 36 | @media screen and (min-width: 600px) { 37 | #forkongithub { 38 | position: fixed; 39 | display: block; 40 | top: 0; 41 | right: 0; 42 | width: 200px; 43 | overflow: hidden; 44 | height: 200px; 45 | z-index: 9999; 46 | } 47 | 48 | #forkongithub a { 49 | width: 200px; 50 | position: absolute; 51 | top: 60px; 52 | right: -60px; 53 | transform: rotate(45deg); 54 | -webkit-transform: rotate(45deg); 55 | -ms-transform: rotate(45deg); 56 | -moz-transform: rotate(45deg); 57 | -o-transform: rotate(45deg); 58 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.8); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/src/_styles/_skeleton-alerts.scss: -------------------------------------------------------------------------------- 1 | // https://github.com/nathancahill/skeleton-alerts 2 | 3 | .alert { 4 | display: block; 5 | padding: 20px; 6 | border-left: 5px solid; 7 | } 8 | 9 | // .alert-success { 10 | // background-color: #D5F5E3; 11 | // border-left-color: #2ECC71; 12 | // color: #2ECC71; 13 | // } 14 | 15 | // .alert-info { 16 | // background-color: #D6EAF8; 17 | // border-left-color: #3498DB; 18 | // color: #3498DB; 19 | // } 20 | 21 | // .alert-warning { 22 | // background-color: #FCF3CF; 23 | // border-left-color: #F1C40F; 24 | // color: #F1C40F; 25 | // } 26 | 27 | .alert-error { 28 | background-color: #F2D7D5; 29 | border-left-color: #C0392B; 30 | color: #C0392B; 31 | } -------------------------------------------------------------------------------- /public/src/_styles/main.sass: -------------------------------------------------------------------------------- 1 | @import "skeleton-alerts" 2 | @import "carbon-ad" 3 | @import "github-fork" 4 | 5 | $grey: #E1E1E1 6 | body 7 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", Oxygen, Cantarell, sans-serif; 8 | 9 | #inputRow 10 | border-top: 1px solid $grey 11 | margin-top: 3.6rem 12 | padding-top: 1rem 13 | #outputRow 14 | .loader 15 | display: none 16 | border-top: 1px solid $grey 17 | &.loading 18 | min-height: 100px 19 | position: relative 20 | border: 1px solid $grey 21 | .loader 22 | margin-top: 15px 23 | display: block 24 | position: absolute 25 | top: 50% 26 | left: 50% 27 | transform: translate(-50%, -50%) 28 | margin: 0 29 | #output 30 | display: none 31 | &.loaded 32 | border: 0 33 | .loader 34 | display: none 35 | #output 36 | display: block 37 | #tipsRow 38 | margin-top: 3.6rem 39 | h3 40 | margin-bottom: 1rem 41 | #output 42 | display: none 43 | padding: 2.5rem 3rem 44 | border: 1px solid $grey 45 | border-radius: 4px 46 | margin: 0 .2rem 47 | 48 | .embed-container 49 | position: relative 50 | padding-bottom: 56.25% 51 | height: 0 52 | overflow: hidden 53 | max-width: 100% 54 | 55 | .embed-container iframe, .embed-container object, .embed-container embed 56 | position: absolute 57 | top: 0 58 | left: 0 59 | width: 100% 60 | height: 100% 61 | .loader, .loader:before, .loader:after 62 | background: #33C3F0 63 | -webkit-animation: load1 1s infinite ease-in-out 64 | animation: load1 1s infinite ease-in-out 65 | width: 0.25em 66 | height: 1em 67 | .loader 68 | color: #33C3F0 69 | text-indent: -9999em 70 | margin: 88px auto 71 | position: relative 72 | font-size: 11px 73 | -webkit-transform: translateZ(0) 74 | -ms-transform: translateZ(0) 75 | transform: translateZ(0) 76 | -webkit-animation-delay: -0.16s 77 | animation-delay: -0.16s 78 | 79 | &:before, &:after 80 | position: absolute 81 | top: 0 82 | content: '' 83 | &:before 84 | left: -1.5em 85 | -webkit-animation-delay: -0.32s 86 | animation-delay: -0.32s 87 | &:after 88 | left: 1.5em 89 | @-webkit-keyframes load1 90 | 0%, 80%, 100% 91 | box-shadow: 0 0 92 | height: 4em 93 | 40% 94 | box-shadow: 0 -2em 95 | height: 5em 96 | @keyframes load1 97 | 0%, 80%, 100% 98 | box-shadow: 0 0 99 | height: 4em 100 | 40% 101 | box-shadow: 0 -2em 102 | height: 5em 103 | 104 | hr 105 | margin-top: 0.5rem 106 | margin-top: 0.5rem 107 | ul 108 | margin-bottom: 0.5rem 109 | li 110 | margin-bottom: 0 111 | thead 112 | tr 113 | td 114 | font-weight: bold 115 | body 116 | padding-bottom: 50px 117 | @media (max-width: 800px) 118 | .hide-on-mobile 119 | display: none 120 | #content 121 | width: 100% 122 | h2 123 | margin-bottom: 1rem 124 | #output & 125 | margin-bottom: 0 126 | 127 | #ad 128 | margin-top: 2.5rem -------------------------------------------------------------------------------- /public/src/_templates/helpers.js: -------------------------------------------------------------------------------- 1 | // helpers.js 2 | function addHelpers(hb) { 3 | hb.registerHelper('showSize', function(bytes, kilobytes, kibibytes){ 4 | var result = ""; 5 | var sizeKilobytes = parseFloat(kilobytes).toFixed(2); 6 | var sizeKibibytes = parseFloat(kibibytes).toFixed(2); 7 | var sizeBytes = parseFloat(bytes); 8 | 9 | result += sizeKilobytes + "kB / "; 10 | result += sizeKibibytes + "KiB"; 11 | result = "" + result + ""; 12 | 13 | return result; 14 | }); 15 | 16 | hb.registerHelper('trim', function(str) { 17 | if (str.length > 20) { 18 | return str.substring(0, 17) + "..."; 19 | } 20 | return str; 21 | }); 22 | 23 | hb.registerHelper('rowspan', function(list) { 24 | return parseInt(list.length) + 2; 25 | }); 26 | 27 | hb.registerHelper('times', function(iterations, content) { 28 | var buff = []; 29 | for (var i = 0; i < iterations; i++) { 30 | buff.push(content); 31 | } 32 | return buff.join(' '); 33 | }); 34 | 35 | return hb; 36 | } 37 | 38 | module.exports = addHelpers; 39 | -------------------------------------------------------------------------------- /public/src/_templates/output.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if favicon}} 3 |
4 | {{title}} 5 |
6 |
7 | {{else}} 8 |
9 | {{/if}} 10 |

{{trim title}}

11 | 12 | 13 | {{friendly_url}} 14 | 15 | 16 |
17 |
18 |
19 |
20 | {{#if moreThanOneFloppy}} 21 |

✅ Your website can fit on a single floppy!

22 | {{else}} 23 |

❌ Your website cannot fit on a single floppy!

24 | {{/if}} 25 |

Note: Files are pre-gzipped sizes

26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {{> multilineTableRow js_files }} 56 | {{> multilineTableRow image_files }} 57 | {{> multilineTableRow css_files }} 58 | 59 |
TypeFilenameSize (kb)
All files 41 | {{{showSize total_size_bytes total_size_kilobytes total_size_kibibytes}}} fitting on {{floppies}} floppies 42 |
Floppy {{times floppies "💾"}} 47 | {{{showSize floppy_size_bytes floppy_size_kilobytes floppy_size_kibibytes}}} (Floppy Capacity) 48 |
HTML{{{showSize html_size_bytes html_size_kilobytes html_size_kibibytes}}}
60 |
-------------------------------------------------------------------------------- /public/src/_templates/partials/_multilineTableRow.hbs: -------------------------------------------------------------------------------- 1 | {{#if files.length}} 2 | 3 | 4 | {{title}} 5 | 6 | 7 | {{#each files as |file index| }} 8 | 9 | 10 | 11 | {{trim file.filename}} 12 | 13 | 14 | 15 | {{{showSize file.bytes file.kilobytes file.kibibytes}}} 16 | 17 | 18 | {{/each}} 19 | 20 | 21 | 22 | Total 23 | 24 | 25 | 26 | {{{showSize total_size_bytes total_size_kilobytes total_size_kibibytes}}} 27 | 28 | 29 | {{/if}} -------------------------------------------------------------------------------- /public/src/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/src/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/apple-touch-icon.png -------------------------------------------------------------------------------- /public/src/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/src/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/favicon-16x16.png -------------------------------------------------------------------------------- /public/src/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/favicon-32x32.png -------------------------------------------------------------------------------- /public/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/favicon.ico -------------------------------------------------------------------------------- /public/src/index.nunjucks: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/base.nunjucks' %} 2 | 3 | {% block content %} 4 |
5 |

Fit on a Floppy

6 | 1.44mb 7 | Floppy Disk capacity 8 |
9 |
10 |
11 |
12 |
13 | Webpages are getting bigger and bigger. The internet is getting faster and faster but not everywhere at the same pace. A floppy is a physical reminder of filesize. 14 |
15 | Assets Audited (On page load): 16 |
    17 |
  • HTML
  • 18 |
  • 19 | CSS/Fonts 20 |
  • 21 |
  • 22 | JavaScript 23 |
  • 24 |
  • 25 | Images (excluding inline data source) 26 |
  • 27 |
28 |
29 |
30 | How long to download on various mobile devices: 31 |
    32 |
  • 33 | 2G EDGE (0.1Mbit/s): 2 minutes 34 |
  • 35 |
  • 36 | 3G HSPA (~1.5Mbit/s): 8 seconds 37 |
  • 38 |
  • 39 | 4G LTE Category 4 (~15Mbit/s): less than 1 second 40 |
  • 41 |
  • 42 | 5G (~150Mbit/s): less than 1 second 43 |
  • 44 |
45 |
46 |
47 |
48 | 54 |
55 |
56 |
57 |
58 |

Will it fit?

59 |
60 |
61 | 62 |
63 |
64 |
65 | 69 |
70 |
71 | 72 |
73 |
74 | 77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |

Some tips to reduce page size

88 |
89 | 111 | 133 | 145 |
146 |
147 |
148 |

Audit tools

149 | 163 |
164 |
165 |

Other resources

166 | 180 |
181 |
182 |
183 | {% endblock %} 184 | -------------------------------------------------------------------------------- /public/src/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbody-old/fit-on-a-floppy/7635603f00ed1a130bee9271195dc17b89bc3bf7/public/src/mstile-150x150.png -------------------------------------------------------------------------------- /public/src/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: -------------------------------------------------------------------------------- /public/src/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/src/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fit On A Floppy", 3 | "short_name": "Fit On A Floppy", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /serve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Requires Node version 12 3 | 4 | npm run --prefix ./public/ serve --------------------------------------------------------------------------------