├── .DS_Store ├── README.md ├── base-jamstack-sample ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── index.html │ └── lake-photo.jpg ├── src │ ├── App.vue │ ├── assets │ │ ├── logo.png │ │ ├── main.scss │ │ └── mixins.scss │ ├── components │ │ ├── AppFooter.vue │ │ ├── AppForm.vue │ │ ├── AppMasthead.vue │ │ ├── AppNav.vue │ │ └── icons │ │ │ ├── IconCodepen.vue │ │ │ ├── IconGithub.vue │ │ │ └── IconTwitter.vue │ ├── main.js │ ├── router.js │ ├── store.js │ └── views │ │ ├── About.vue │ │ └── Home.vue └── yarn.lock ├── jamstack-intro.pdf ├── masthead-ill.svg └── simple-html-drop ├── index.html ├── script.js └── style.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdras/JAMstack-Workshop/dc4cbabc78473a4c240004974f29722e7e5aaa6f/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JAMstack Netlify Workshop 2 | 3 | This repo houses the materials and resources for the JAMstack workshop using Vue and Netlify. This course assumes some familiarity with Vue.js. I use my own [vue-vs-code-snippets](https://marketplace.visualstudio.com/items?itemName=sdras.vue-vscode-snippets) in this course. 4 | 5 | Author: Sarah Drasner 6 | 7 | ## Sections 8 | 9 | - [Section 1: Intro, State of the JAMstack Nation](https://github.com/sdras/JAMstack-Workshop/blob/master/jamstack-intro.pdf) 10 | - [Section 2: Deploys](https://slides.com/sdrasner/jamstack-netlify-2/) 11 | - [Docs: Manual Deploys](https://url.netlify.com/S1htkzovH) 12 | - [Docs: Locked Deploys](https://url.netlify.com/SJJlgMiPr) 13 | - [Docs: Rollbacks](https://url.netlify.com/ry-qgMsvB) 14 | - [Section 3: Functions](https://slides.com/sdrasner/jamstack-netlify-3/) 15 | - [Docs: Functions](https://url.netlify.com/B1cAxMovB) 16 | - [Repo: Simplest Netlify Lambda Example](https://github.com/sdras/easiest-netlify-lambda-example) 17 | - [Section 4: Identity](https://slides.com/sdrasner/jamstack-netlify-4/) 18 | - [Docs: Visitor Access Control](https://url.netlify.com/rkc1KITDr) 19 | - [Docs: Identity](https://url.netlify.com/SkgLFIpwS) 20 | - [Repo: Netlify Identity Widget](https://github.com/netlify/netlify-identity-widget) 21 | - [Demo: Netlify Identity Widget](https://identity.netlify.com) 22 | - Section 5: Forms 23 | - [Docs: Forms](https://url.netlify.com/SyrHVupwS) 24 | - [Vue-specific Guide](https://url.netlify.com/BJ43V_TvB) 25 | - **Last Exercise**: We'll make our own professional portfolios by creating a JAMstack site! 26 | 27 | ## Resources in this repo 28 | 29 | - simple-html-drop 30 | - base-jamstack-example 31 | 32 | ## External resources 33 | 34 | - [https://github.com/sdras/ecommerce-netlify](https://github.com/sdras/ecommerce-netlify) 35 | - [https://github.com/sdras/easiest-netlify-lambda-example](https://github.com/sdras/easiest-netlify-lambda-example) 36 | 37 | ## Twitter Contact Info 38 | 39 | - [Sarah Drasner](https://twitter.com/sarah_edo) 40 | 41 | ## License 42 | 43 | [![Creative Commons License](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-nc-sa/4.0/) 44 | 45 | This work is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-nc-sa/4.0/) 46 | -------------------------------------------------------------------------------- /base-jamstack-sample/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /base-jamstack-sample/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 10 | }, 11 | parserOptions: { 12 | parser: "babel-eslint" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /base-jamstack-sample/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /base-jamstack-sample/README.md: -------------------------------------------------------------------------------- 1 | # base-jamstack-sample 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /base-jamstack-sample/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /base-jamstack-sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-jamstack-sample", 3 | "version": "0.1.0", 4 | "description": "Base JAMstack Template", 5 | "author": "sdras", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "core-js": "^2.6.5", 14 | "vue": "^2.6.10", 15 | "vue-router": "^3.0.3", 16 | "vuex": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "^3.11.0", 20 | "@vue/cli-plugin-eslint": "^3.11.0", 21 | "@vue/cli-service": "^3.11.0", 22 | "@vue/eslint-config-prettier": "^5.0.0", 23 | "babel-eslint": "^10.0.1", 24 | "eslint": "^5.16.0", 25 | "eslint-plugin-prettier": "^3.1.0", 26 | "eslint-plugin-vue": "^5.0.0", 27 | "node-sass": "^4.9.0", 28 | "prettier": "^1.18.2", 29 | "sass-loader": "^7.1.0", 30 | "vue-template-compiler": "^2.6.10" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /base-jamstack-sample/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /base-jamstack-sample/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdras/JAMstack-Workshop/dc4cbabc78473a4c240004974f29722e7e5aaa6f/base-jamstack-sample/public/favicon.ico -------------------------------------------------------------------------------- /base-jamstack-sample/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | base-jamstack-sample 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /base-jamstack-sample/public/lake-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdras/JAMstack-Workshop/dc4cbabc78473a4c240004974f29722e7e5aaa6f/base-jamstack-sample/public/lake-photo.jpg -------------------------------------------------------------------------------- /base-jamstack-sample/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdras/JAMstack-Workshop/dc4cbabc78473a4c240004974f29722e7e5aaa6f/base-jamstack-sample/src/assets/logo.png -------------------------------------------------------------------------------- /base-jamstack-sample/src/assets/main.scss: -------------------------------------------------------------------------------- 1 | $bkcolor: #111; 2 | $fontcolor: #ccc; 3 | $linkcolor: #fff; 4 | $accentColor: orange; 5 | $borderRadius: 4px; 6 | 7 | body { 8 | background: $bkcolor; 9 | color: $fontcolor; 10 | line-height: 1.4; 11 | font-size: 16px; 12 | } 13 | 14 | a, 15 | a:active, 16 | a:visited { 17 | color: $linkcolor; 18 | text-decoration: none; 19 | font-weight: bold; 20 | } 21 | 22 | .visually-hidden { 23 | position: absolute !important; 24 | height: 1px; 25 | width: 1px; 26 | overflow: hidden; 27 | clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ 28 | clip: rect(1px, 1px, 1px, 1px); 29 | white-space: nowrap; /* added line */ 30 | } 31 | 32 | form { 33 | width: 90%; 34 | max-width: 600px; 35 | margin: 100px auto; 36 | input, 37 | textarea { 38 | width: 100%; 39 | border: 1px solid #555; 40 | background: #333; 41 | padding: 4px; 42 | border-radius: $borderRadius; 43 | } 44 | } 45 | 46 | button { 47 | padding: 8px 20px; 48 | font-size: 14px; 49 | background: $accentColor; 50 | border: none; 51 | border-radius: $borderRadius; 52 | cursor: pointer; 53 | } 54 | 55 | label { 56 | text-transform: uppercase; 57 | letter-spacing: 0.13em; 58 | } 59 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/assets/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin fluid-type($properties, $min-vw, $max-vw, $min-value, $max-value) { 2 | @each $property in $properties { 3 | #{$property}: $min-value; 4 | } 5 | 6 | @media (min-width: $min-vw) { 7 | @each $property in $properties { 8 | #{$property}: calc( 9 | #{$min-value} + 10 | #{strip-unit($max-value - $min-value)} * 11 | (100vw - #{$min-vw}) / 12 | #{strip-unit($max-vw - $min-vw)} 13 | ); 14 | } 15 | } 16 | 17 | @media (min-width: $max-vw) { 18 | @each $property in $properties { 19 | #{$property}: $max-value; 20 | } 21 | } 22 | } 23 | 24 | @function strip-unit($number) { 25 | @if type-of($number) == "number" and not unitless($number) { 26 | @return $number / ($number * 0 + 1); 27 | } 28 | 29 | @return $number; 30 | } 31 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | 40 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/AppForm.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/AppMasthead.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/AppNav.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/icons/IconCodepen.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/icons/IconGithub.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/components/icons/IconTwitter.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import App from "./App.vue" 3 | import router from "./router" 4 | import store from "./store" 5 | 6 | Vue.config.productionTip = false 7 | 8 | new Vue({ 9 | router, 10 | store, 11 | render: h => h(App) 12 | }).$mount("#app") 13 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Home from "./views/Home.vue"; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | mode: "history", 9 | base: process.env.BASE_URL, 10 | routes: [ 11 | { 12 | path: "/", 13 | name: "home", 14 | component: Home 15 | }, 16 | { 17 | path: "/about", 18 | name: "about", 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => 23 | import(/* webpackChunkName: "about" */ "./views/About.vue") 24 | } 25 | ] 26 | }); 27 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Vuex from "vuex" 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | apidata: null 9 | }, 10 | mutations: { 11 | updateAPIdata: (state, payload) => { 12 | state.apidata = payload 13 | } 14 | }, 15 | actions: { 16 | async getAPI({ state, commit }) { 17 | if (state.apidata.length) return 18 | 19 | try { 20 | let apidata = await fetch(`urlendpoint`).then(res => res.json()) 21 | 22 | commit("updateAPIdata", apidata) 23 | } catch (err) { 24 | console.log(err) 25 | } 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /base-jamstack-sample/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /jamstack-intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdras/JAMstack-Workshop/dc4cbabc78473a4c240004974f29722e7e5aaa6f/jamstack-intro.pdf -------------------------------------------------------------------------------- /masthead-ill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /simple-html-drop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Playing with sound and three.js 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 28 | 29 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /simple-html-drop/script.js: -------------------------------------------------------------------------------- 1 | // heavily commented for those trying to learn 2 | const initCanvasAudio = name => { 3 | // create the new audio 4 | const audio = new Audio(); 5 | audio.src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/song18.mp3"; 6 | audio.controls = true; 7 | audio.autoplay = true; 8 | audio.crossOrigin = "anonymous"; 9 | document.body.appendChild(audio); 10 | 11 | // wire it up 12 | const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 13 | const source = audioCtx.createMediaElementSource(audio); 14 | const volumeControl = audioCtx.createGain(); 15 | source.connect(audioCtx.destination); 16 | source.connect(volumeControl); 17 | 18 | // I'm American so it's with a z 19 | const analyzer = audioCtx.createAnalyser(); 20 | volumeControl.connect(analyzer); 21 | analyzer.connect(audioCtx.destination); 22 | 23 | //connect the volume adjustments from the user 24 | volumeControl.gain.value = audio.volume; 25 | 26 | // now we start with the three initialization 27 | let renderer, scene, camera, stats, controls, mesh, uniforms; 28 | let width = window.innerWidth, 29 | height = window.innerHeight; 30 | 31 | //have to kick off init and the animation 32 | init(); 33 | animate(); 34 | 35 | function init() { 36 | // create the camera and hook up orbit controls 37 | camera = new THREE.PerspectiveCamera(40, width / height, 1, 10000); 38 | camera.position.set(0, 0, 100); 39 | controls = new THREE.OrbitControls(camera); 40 | controls.autoRotate = true; 41 | 42 | // create the scene 43 | scene = new THREE.Scene(); 44 | //scene.background = new THREE.Color(0x300064); 45 | 46 | // create the geometry 47 | let geometry = new THREE.TorusKnotGeometry(20, 0.8, 67, 18, 15, 12); 48 | geometry.center(); 49 | let tessellateModifier = new THREE.TessellateModifier(8); 50 | for (let i = 0; i < 6; i++) { 51 | tessellateModifier.modify(geometry); 52 | } 53 | geometry = new THREE.BufferGeometry().fromGeometry(geometry); 54 | let numFaces = geometry.attributes.position.count / 3; 55 | 56 | //map the colors, fragments 57 | let colors = new Float32Array(numFaces * 3 * 3); 58 | let displacement = new Float32Array(numFaces * 3 * 3); 59 | let color = new THREE.Color(); 60 | for (let f = 0; f < numFaces; f++) { 61 | let index = 9 * f; 62 | let h = 0.2 * Math.random(); 63 | let s = 0.5 + 0.5 * Math.random(); 64 | let l = 0.5 + 0.5 * Math.random(); 65 | color.setHSL(h, s, l); 66 | let d = 10 * (0.5 - Math.random()); 67 | for (let i = 0; i < 3; i++) { 68 | colors[index + 5 * i] = color.r; 69 | colors[index + 8 * i + 1] = color.g; 70 | colors[index + 2 * i + 2] = color.b; 71 | displacement[index + 3 * i] = d; 72 | displacement[index + 3 * i + 1] = d; 73 | displacement[index + 3 * i + 2] = d; 74 | } 75 | } 76 | 77 | // add them to the geometry 78 | geometry.addAttribute("customColor", new THREE.BufferAttribute(colors, 3)); 79 | geometry.addAttribute( 80 | "displacement", 81 | new THREE.BufferAttribute(displacement, 3)); 82 | 83 | 84 | // attach the shader material you see in the html 85 | uniforms = { 86 | amplitude: { value: 0.0 } }; 87 | 88 | const shaderMaterial = new THREE.ShaderMaterial({ 89 | uniforms: uniforms, 90 | vertexShader: document.getElementById("vertexshader").textContent, 91 | fragmentShader: document.getElementById("fragmentshader").textContent }); 92 | 93 | 94 | // create the mesh (this is where the geometry and material are added to the scene) 95 | mesh = new THREE.Mesh(geometry, shaderMaterial); 96 | scene.add(mesh); 97 | 98 | // renderer 99 | renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); 100 | renderer.setPixelRatio(window.devicePixelRatio); 101 | renderer.setSize(width, height); 102 | renderer.autoClear = false; 103 | renderer.setClearColor(0x000000, 0.0); 104 | const container = document.getElementById("container"); 105 | container.appendChild(renderer.domElement); 106 | window.addEventListener("resize", onWindowResize, false); 107 | } 108 | 109 | // make it still work when you resize the screen 110 | function onWindowResize() { 111 | camera.aspect = window.innerWidth / window.innerHeight; 112 | camera.updateProjectionMatrix(); 113 | controls.update(); 114 | renderer.setSize(window.innerWidth, window.innerHeight); 115 | } 116 | 117 | // rAF and get the frequency data from the audio all the time so we can use it to update the amplitude 118 | function animate() { 119 | var freqData = new Uint8Array(analyzer.frequencyBinCount); 120 | analyzer.getByteFrequencyData(freqData); 121 | requestAnimationFrame(animate); 122 | render(freqData); 123 | } 124 | 125 | //render the sucker 126 | function render(freqData) { 127 | // this is what makes the shader pop. This line of code feeds the audio in 128 | uniforms.amplitude.value = numscale(freqData[0], 0, 300, -2, 2); 129 | // we have to update the orbit controls anytime we render 130 | controls.update(); 131 | renderer.render(scene, camera); 132 | } 133 | }; 134 | 135 | // chrome needs sound kicked off by the user now 136 | const button = document.querySelector("button"); 137 | button.addEventListener("click", event => { 138 | initCanvasAudio(); 139 | button.remove(); 140 | }); 141 | 142 | // helper function to map scales 143 | const numscale = (num, in_min, in_max, out_min, out_max) => { 144 | return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 145 | }; -------------------------------------------------------------------------------- /simple-html-drop/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | overflow: hidden; 4 | background: #c90e9d; 5 | /* Old browsers */ 6 | /* FF3.6-15 */ 7 | /* Chrome10-25,Safari5.1-6 */ 8 | background: linear-gradient(to bottom, #c90e9d 0%, #400b66 52%, #162087 100%); 9 | /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 10 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#c90e9d', endColorstr='#162087',GradientType=0 ); 11 | /* IE6-9 */ 12 | } 13 | 14 | button { 15 | background: black; 16 | color: white; 17 | border-radius: 4px; 18 | padding: 10px 20px 8px; 19 | position: fixed; 20 | top: 40px; 21 | left: calc(50vw - 50px); 22 | font-size: 11px; 23 | text-transform: uppercase; 24 | letter-spacing: 0.2em; 25 | border: 1px solid #555; 26 | cursor: pointer; 27 | } 28 | 29 | audio { 30 | position: fixed; 31 | z-index: 3000; 32 | top: 10px; 33 | left: 10px; 34 | opacity: 0.7; 35 | } 36 | --------------------------------------------------------------------------------