├── .gitignore ├── README.md ├── css └── base.css ├── favicon.ico ├── img ├── 1.jpg └── 2.jpg ├── index.html ├── index2.html └── js ├── demo1.js ├── demo2.js ├── imagesloaded.pkgd.min.js └── zxcvbn.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Password Strength Visualization 2 | 3 | Visual feedback for password strength on an image based on [Colibro's sign up form](https://app.colibro.com/accounts-register/register). 4 | 5 | ![password](https://tympanus.net/codrops/wp-content/uploads/2018/04/PasswordStrengthVisualization.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=34613) 8 | 9 | [Demo](http://tympanus.net/Development/PasswordStrengthVisualization/) 10 | 11 | ## Credits 12 | 13 | - Idea from [this Reddit entry](https://www.reddit.com/r/web_design/comments/8bzbue/the_stronger_your_password_the_more_of_the/?st=jg1t6m9c&sh=e057ce1f) 14 | - [Password Strength 'meter'](https://css-tricks.com/password-strength-meter/) by Pankaj Parashar 15 | - [zxcvbn](https://github.com/dropbox/zxcvbn) by Daniel Lowe Wheeler 16 | - Images from [Unsplash.com](https://unsplash.com/) 17 | - [imagesLoaded](http://imagesloaded.desandro.com/) by Dave DeSandro 18 | - [Pixelate](http://jsfiddle.net/u6apxgfk/390/) by Ken Fyrstenberg, Epistemex, License: [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) 19 | - [Fullscreen image in Canvas](https://codepen.io/bassta/pen/OPVzyB) by Chrysto Panayotov 20 | 21 | 22 | ## License 23 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 24 | 25 | ## Misc 26 | 27 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [Google+](https://plus.google.com/101095823814290637419), [GitHub](https://github.com/codrops), [Pinterest](http://www.pinterest.com/codrops/), [Instagram](https://www.instagram.com/codropsss/) 28 | 29 | 30 | [© Codrops 2018](http://www.codrops.com) 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} 2 | *, 3 | *::after, 4 | *::before { 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; 10 | min-height: 100vh; 11 | color: #57585c; 12 | color: var(--color-text); 13 | background-color: #fff; 14 | background-color: var(--color-bg); 15 | height: 100vh; 16 | overflow: hidden; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | } 20 | 21 | .demo-1 { 22 | --color-text: #000; 23 | --color-bg: #fff; 24 | --color-content-bg: #fff; 25 | --color-link: #000; 26 | --color-link-hover: #4bb3e7; 27 | --color-info: #000; 28 | --color-form-alt: #909090; 29 | --color-form-button-bg: #000; 30 | --color-form-button: #fff; 31 | --color-demo: #fff; 32 | --color-demo-hover: #fff; 33 | --color-github: #fff; 34 | --color-github-hover: #fff; 35 | } 36 | 37 | .demo-2 { 38 | --color-text: #585858; 39 | --color-bg: #000; 40 | --color-content-bg: #1a1b21; 41 | --color-link: #f0f0f0; 42 | --color-link-hover: #4bb3e7; 43 | --color-info: #585858; 44 | --color-form-alt: #909090; 45 | --color-form-button-bg: #000000; 46 | --color-form-button: #ffffff; 47 | --color-demo: #fff; 48 | --color-demo-hover: #fff; 49 | --color-github: #fff; 50 | --color-github-hover: #fff; 51 | } 52 | 53 | .js body.render { 54 | opacity: 1; 55 | } 56 | 57 | /* Page Loader */ 58 | .js .loading::before { 59 | content: ''; 60 | position: fixed; 61 | z-index: 100000; 62 | top: 0; 63 | left: 0; 64 | width: 100%; 65 | height: 100%; 66 | background: var(--color-bg); 67 | } 68 | 69 | .js .loading::after { 70 | content: ''; 71 | position: fixed; 72 | z-index: 100000; 73 | top: 50%; 74 | left: 50%; 75 | width: 60px; 76 | height: 60px; 77 | margin: -30px 0 0 -30px; 78 | pointer-events: none; 79 | border-radius: 50%; 80 | opacity: 0.4; 81 | background: var(--color-link); 82 | animation: loaderAnim 0.7s linear infinite alternate forwards; 83 | } 84 | 85 | @keyframes loaderAnim { 86 | to { 87 | opacity: 1; 88 | transform: scale3d(0.5,0.5,1); 89 | } 90 | } 91 | 92 | a { 93 | text-decoration: underline; 94 | color: var(--color-link); 95 | outline: none; 96 | } 97 | 98 | a:hover, 99 | a:focus { 100 | color: var(--color-link-hover); 101 | outline: none; 102 | text-decoration: none; 103 | } 104 | 105 | .hidden { 106 | position: absolute; 107 | overflow: hidden; 108 | width: 0; 109 | height: 0; 110 | pointer-events: none; 111 | } 112 | 113 | /* Icons */ 114 | .icon { 115 | display: block; 116 | width: 1.5em; 117 | height: 1.5em; 118 | margin: 0 auto; 119 | fill: currentColor; 120 | } 121 | 122 | main { 123 | position: relative; 124 | width: 100%; 125 | display: flex; 126 | flex-wrap: wrap; 127 | } 128 | 129 | .content { 130 | position: relative; 131 | min-height: 100vh; 132 | } 133 | 134 | .content--main { 135 | grid-template-columns: 50% 50%; 136 | } 137 | 138 | .content--full { 139 | width: 100vw; 140 | } 141 | 142 | .content--side { 143 | background: var(--color-content-bg); 144 | width: 550px; 145 | margin: 0; 146 | padding: 5vmin; 147 | display: flex; 148 | flex-direction: column; 149 | overflow: hidden; 150 | } 151 | 152 | .content--side + .content--side { 153 | justify-content: space-between; 154 | width: calc(100vw - 550px); 155 | } 156 | 157 | .poster { 158 | position: absolute; 159 | top: 0; 160 | left: 0; 161 | width: 100%; 162 | height: 100%; 163 | background-size: cover; 164 | background-position: 50% 50%; 165 | } 166 | 167 | /* Header */ 168 | .codrops-header { 169 | position: relative; 170 | z-index: 100; 171 | display: flex; 172 | flex-wrap: wrap; 173 | flex-direction: row; 174 | align-items: flex-start; 175 | align-items: center; 176 | align-self: start; 177 | grid-area: header; 178 | justify-self: start; 179 | margin: 0 0 1rem 0; 180 | } 181 | 182 | .codrops-header__title { 183 | font-size: 1.5em; 184 | font-weight: bold; 185 | margin: 0; 186 | padding: 0; 187 | } 188 | 189 | .info { 190 | flex: none; 191 | width: 100%; 192 | margin: 1rem 0; 193 | color: var(--color-info); 194 | } 195 | 196 | .github { 197 | display: block; 198 | position: relative; 199 | color: var(--color-github); 200 | margin: 0 0 0 auto; 201 | } 202 | 203 | .github:hover, 204 | .github:focus { 205 | color: var(--color-github-hover); 206 | } 207 | 208 | .demos { 209 | position: relative; 210 | display: block; 211 | text-align: right; 212 | margin: 0 0 0 auto; 213 | } 214 | 215 | .demo { 216 | font-weight: bold; 217 | color: var(--color-demo); 218 | } 219 | 220 | .demo:not(:last-child) { 221 | margin-right: 1.5rem; 222 | } 223 | 224 | .demo:hover, 225 | .demo:focus { 226 | color: var(--color-demo-hover); 227 | } 228 | 229 | .demo span { 230 | white-space: nowrap; 231 | text-transform: lowercase; 232 | pointer-events: none; 233 | } 234 | 235 | a.demo--current { 236 | pointer-events: none; 237 | text-decoration: none; 238 | } 239 | 240 | /* Top Navigation Style */ 241 | .codrops-links { 242 | position: relative; 243 | display: flex; 244 | justify-content: center; 245 | margin: 0 1em 0 0; 246 | text-align: center; 247 | white-space: nowrap; 248 | } 249 | 250 | .codrops-icon { 251 | display: inline-block; 252 | } 253 | 254 | .codrops-icon:first-child { 255 | margin-right: 0.5rem; 256 | } 257 | 258 | .canvas-wrap { 259 | width: 100%; 260 | height: 100%; 261 | position: absolute; 262 | top: 0; 263 | left: 0; 264 | } 265 | 266 | canvas { 267 | image-rendering: optimizeSpeed; 268 | image-rendering: -moz-crisp-edges; 269 | image-rendering: -webkit-optimize-contrast; 270 | image-rendering: -o-crisp-edges; 271 | image-rendering: crisp-edges; 272 | -ms-interpolation-mode: nearest-neighbor; 273 | width: 100%; 274 | height: 100%; 275 | } 276 | 277 | .form { 278 | margin: auto 0 0 0; 279 | position: relative; 280 | } 281 | 282 | .form__title { 283 | font-size: 1.5rem; 284 | margin: 0 0 3rem; 285 | } 286 | 287 | .form__item { 288 | display: grid; 289 | grid-template-columns: 1fr 2fr; 290 | align-items: center; 291 | } 292 | 293 | .form__item:not(:last-child) { 294 | margin: 0 0 1.5rem; 295 | } 296 | 297 | .form__item--actions { 298 | color: var(--color-form-alt); 299 | margin-top: 3rem; 300 | font-size: 0.8rem; 301 | grid-template-columns: 3fr 1fr; 302 | } 303 | 304 | .form__label { 305 | font-weight: bold; 306 | } 307 | 308 | .form__input { 309 | padding: 1rem; 310 | width: 100%; 311 | border: 1px solid #ddd; 312 | font-weight: bold; 313 | } 314 | 315 | .form__input-wrap { 316 | position: relative; 317 | } 318 | 319 | .form__input:focus { 320 | border-color: #000; 321 | outline: none; 322 | } 323 | 324 | .form__password-strength { 325 | position: absolute; 326 | font-size: 0.75rem; 327 | } 328 | 329 | .form__link { 330 | font-weight: bold; 331 | white-space: nowrap; 332 | } 333 | 334 | .form__button { 335 | padding: 1rem; 336 | font-weight: bold; 337 | font-size: 1rem; 338 | border: 0; 339 | background: var(--color-form-button-bg); 340 | color: var(--color-form-button); 341 | } 342 | 343 | .form__button:focus { 344 | outline: none; 345 | } 346 | 347 | .demo-2 main { 348 | flex-direction: row-reverse; 349 | } 350 | 351 | .demo-2 .github, 352 | .demo-2 .demos { 353 | margin: 0 auto 0 0; 354 | } 355 | 356 | @media screen and (max-width: 45em) { 357 | body { 358 | overflow: auto; 359 | } 360 | .content { 361 | height: auto; 362 | min-height: 0; 363 | } 364 | .content--side { 365 | width: 100vw; 366 | } 367 | .content--fixed { 368 | position: relative; 369 | z-index: 1000; 370 | display: block; 371 | padding: 0.85em; 372 | } 373 | .content--side + .content--side { 374 | width: 100vw; 375 | height: 100vw; 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/PasswordStrengthVisualization/f45c0104f26cc8d587e2e5f113bb9b916d3da713/favicon.ico -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/PasswordStrengthVisualization/f45c0104f26cc8d587e2e5f113bb9b916d3da713/img/1.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/PasswordStrengthVisualization/f45c0104f26cc8d587e2e5f113bb9b916d3da713/img/2.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Password Strength Visualization | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 |
30 |
31 |
32 | 36 |

Password Strength Visualization

37 |

Based on Colibro's sign up form. Read more about the idea.

38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |

53 |
54 |
55 |
56 | Already have an account? Login here 57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 | 65 |
66 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Password Strength Visualization | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 |
30 |
31 |
32 | 36 |

Password Strength Visualization

37 |

Based on Colibro's sign up form. Read more about the idea.

38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |

53 |
54 |
55 |
56 | Already have an account? Login here 57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 | 65 |
66 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /js/demo1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo1.js 3 | * http://www.codrops.com 4 | * 5 | * Licensed under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Copyright 2018, Codrops 9 | * http://www.codrops.com 10 | */ 11 | { 12 | const passwordInput = document.querySelector('#password'); 13 | const passwordFeedback = document.querySelector('#strength-output'); 14 | const strengthStr = { 15 | 0: 'Worst', 16 | 1: 'Bad', 17 | 2: 'Weak', 18 | 3: 'Good', 19 | 4: 'Strong' 20 | } 21 | const canvasWrapper = document.querySelector('.canvas-wrap'); 22 | const canvas = canvasWrapper.querySelector('canvas'); 23 | const poster = document.querySelector('.poster'); 24 | const posterImg = poster.style.backgroundImage.match(/\((.*?)\)/)[1].replace(/('|")/g,''); 25 | imagesLoaded(poster, { background: true }, () => { 26 | document.body.classList.remove('loading'); 27 | }); 28 | 29 | // The following code was taken and modified from http://jsfiddle.net/u6apxgfk/390/ 30 | // (C) Ken Fyrstenberg, Epistemex, License: CC3.0-attr 31 | 32 | // and merged with https://codepen.io/bassta/pen/OPVzyB?editors=1010 33 | 34 | const ctx = canvas.getContext('2d'); 35 | const img = new Image(); 36 | let imgRatio; 37 | let wrapperRatio; 38 | let newWidth; 39 | let newHeight; 40 | let newX; 41 | let newY; 42 | 43 | let pxFactor = 1; 44 | 45 | img.src = posterImg; 46 | img.onload = () => { 47 | const imgWidth = img.width; 48 | const imgHeight = img.height; 49 | imgRatio = imgWidth / imgHeight; 50 | setCanvasSize(); 51 | render(); 52 | }; 53 | 54 | const setCanvasSize = () => { 55 | canvas.width = canvasWrapper.offsetWidth; 56 | canvas.height = canvasWrapper.offsetHeight; 57 | }; 58 | 59 | const render = () => { 60 | const w = canvasWrapper.offsetWidth; 61 | const h = canvasWrapper.offsetHeight; 62 | 63 | newWidth = w; 64 | newHeight = h; 65 | newX = 0; 66 | newY = 0; 67 | wrapperRatio = newWidth / newHeight; 68 | 69 | if ( wrapperRatio > imgRatio ) { 70 | newHeight = Math.round(w / imgRatio); 71 | newY = (h - newHeight) / 2; 72 | } 73 | else { 74 | newWidth = Math.round(h * imgRatio); 75 | newX = (w - newWidth) / 2; 76 | } 77 | 78 | // pxFactor will depend on the current typed password. 79 | // values will be in the range [1,100]. 80 | const size = pxFactor * 0.01; 81 | 82 | // turn off image smoothing - this will give the pixelated effect 83 | ctx.mozImageSmoothingEnabled = size === 1 ? true : false; 84 | ctx.webkitImageSmoothingEnabled = size === 1 ? true : false; 85 | ctx.imageSmoothingEnabled = size === 1 ? true : false; 86 | 87 | ctx.clearRect(0, 0, canvas.width, canvas.height); 88 | // draw original image to the scaled size 89 | ctx.drawImage(img, 0, 0, w*size, h*size); 90 | // then draw that scaled image thumb back to fill canvas 91 | // As smoothing is off the result will be pixelated 92 | ctx.drawImage(canvas, 0, 0, w*size, h*size, newX, newY, newWidth+.05*w, newHeight+.05*h); 93 | }; 94 | 95 | window.addEventListener('resize', () => { 96 | setCanvasSize(); 97 | render(); 98 | }); 99 | 100 | passwordInput.addEventListener('input', () => { 101 | const val = passwordInput.value; 102 | const result = zxcvbn(val); 103 | // We want to reveal the image as the password gets stronger. Since the zxcvbn.score has 104 | // only 5 different values (0-4) we will use the zxcvbn.guesses_log10 output. 105 | // The guesses_log10 will be >= 11 when the password is considered strong, 106 | // so we want to map a factor of 1 (all pixelated) to 100 (clear image) to 107 | // a value of 0 to 11 of guesses_log10. 108 | // This result will be used in the render function. 109 | pxFactor = 99/11*Math.min(11,Math.round(result.guesses_log10)) + 1; 110 | 111 | // so we see most of the time pixels rather than approaching a clear image sooner.. 112 | if ( pxFactor != 1 && pxFactor != 100 ) { 113 | pxFactor -= pxFactor/100*50; 114 | } 115 | 116 | passwordFeedback.innerHTML = val !== '' ? `Password strength: ${strengthStr[result.score]}` : ''; 117 | render(); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /js/demo2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo3.js 3 | * http://www.codrops.com 4 | * 5 | * Licensed under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Copyright 2018, Codrops 9 | * http://www.codrops.com 10 | */ 11 | { 12 | const passwordInput = document.querySelector('#password'); 13 | const passwordFeedback = document.querySelector('#strength-output'); 14 | const strengthStr = { 15 | 0: 'Worst', 16 | 1: 'Bad', 17 | 2: 'Weak', 18 | 3: 'Good', 19 | 4: 'Strong' 20 | } 21 | const canvasWrapper = document.querySelector('.canvas-wrap'); 22 | const canvas = canvasWrapper.querySelector('canvas'); 23 | const poster = document.querySelector('.poster'); 24 | const posterImg = poster.style.backgroundImage.match(/\((.*?)\)/)[1].replace(/('|")/g,''); 25 | imagesLoaded(poster, { background: true }, () => { 26 | document.body.classList.remove('loading'); 27 | }); 28 | 29 | // The following code was taken and modified from http://jsfiddle.net/u6apxgfk/390/ 30 | // (C) Ken Fyrstenberg, Epistemex, License: CC3.0-attr 31 | 32 | // and merged with https://codepen.io/bassta/pen/OPVzyB?editors=1010 33 | 34 | const ctx = canvas.getContext('2d'); 35 | const img = new Image(); 36 | let imgRatio; 37 | let wrapperRatio; 38 | let newWidth; 39 | let newHeight; 40 | let newX; 41 | let newY; 42 | 43 | let pxFactor = 100; 44 | 45 | img.src = posterImg; 46 | img.onload = () => { 47 | const imgWidth = img.width; 48 | const imgHeight = img.height; 49 | imgRatio = imgWidth / imgHeight; 50 | setCanvasSize(); 51 | render(); 52 | }; 53 | 54 | const setCanvasSize = () => { 55 | canvas.width = canvasWrapper.offsetWidth; 56 | canvas.height = canvasWrapper.offsetHeight; 57 | }; 58 | 59 | const render = () => { 60 | const w = canvasWrapper.offsetWidth; 61 | const h = canvasWrapper.offsetHeight; 62 | 63 | newWidth = w; 64 | newHeight = h; 65 | newX = 0; 66 | newY = 0; 67 | wrapperRatio = newWidth / newHeight; 68 | 69 | if ( wrapperRatio > imgRatio ) { 70 | newHeight = Math.round(w / imgRatio); 71 | newY = (h - newHeight) / 2; 72 | } 73 | else { 74 | newWidth = Math.round(h * imgRatio); 75 | newX = (w - newWidth) / 2; 76 | } 77 | 78 | // pxFactor will depend on the current typed password. 79 | // values will be in the range [1,100]. 80 | const size = pxFactor * 0.01; 81 | 82 | // turn off image smoothing - this will give the pixelated effect 83 | ctx.mozImageSmoothingEnabled = size === 1 ? true : false; 84 | ctx.webkitImageSmoothingEnabled = size === 1 ? true : false; 85 | ctx.imageSmoothingEnabled = size === 1 ? true : false; 86 | 87 | ctx.clearRect(0, 0, canvas.width, canvas.height); 88 | // draw original image to the scaled size 89 | ctx.drawImage(img, 0, 0, w*size, h*size); 90 | // then draw that scaled image thumb back to fill canvas 91 | // As smoothing is off the result will be pixelated 92 | ctx.drawImage(canvas, 0, 0, w*size, h*size, newX, newY, newWidth+.05*w, newHeight+.05*h); 93 | }; 94 | 95 | window.addEventListener('resize', () => { 96 | setCanvasSize(); 97 | render(); 98 | }); 99 | 100 | passwordInput.addEventListener('input', () => { 101 | const val = passwordInput.value; 102 | const result = zxcvbn(val); 103 | // We want to reveal the image as the password gets stronger. Since the zxcvbn.score has 104 | // only 5 different values (0-4) we will use the zxcvbn.guesses_log10 output. 105 | // The guesses_log10 will be >= 11 when the password is considered strong, 106 | // so we want to map a factor of 3 (pixelated) to 100 (clear image) to 107 | // a value of 0 to 11 of guesses_log10. 108 | // This result will be used in the render function. 109 | pxFactor = -97/11*Math.min(11,Math.round(result.guesses_log10)) + 100 ; 110 | 111 | // so we see most of the time pixels rather than approaching a clear image sooner.. 112 | if ( pxFactor != 3 && pxFactor != 100 ) { 113 | pxFactor -= pxFactor/100*50; 114 | } 115 | 116 | passwordFeedback.innerHTML = val !== '' ? `Password strength: ${strengthStr[result.score]}` : ''; 117 | render(); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /js/imagesloaded.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v4.1.4 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | !function(e,t){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",t):"object"==typeof module&&module.exports?module.exports=t():e.EvEmitter=t()}("undefined"!=typeof window?window:this,function(){function e(){}var t=e.prototype;return t.on=function(e,t){if(e&&t){var i=this._events=this._events||{},n=i[e]=i[e]||[];return n.indexOf(t)==-1&&n.push(t),this}},t.once=function(e,t){if(e&&t){this.on(e,t);var i=this._onceEvents=this._onceEvents||{},n=i[e]=i[e]||{};return n[t]=!0,this}},t.off=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){var n=i.indexOf(t);return n!=-1&&i.splice(n,1),this}},t.emitEvent=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){i=i.slice(0),t=t||[];for(var n=this._onceEvents&&this._onceEvents[e],o=0;o