├── .editorconfig ├── .gitignore ├── README.md ├── gulpfile.js ├── package.json └── src ├── blocks ├── footer.html └── header.html ├── index.html ├── js ├── script.js └── tinylib.js └── scss ├── _base.scss ├── _mixins.scss ├── _normalize.scss ├── _vars.scss ├── blocks ├── _attrs.scss ├── _code.scss ├── _flags.scss ├── _footer.scss ├── _github.scss ├── _header.scss ├── _panel.scss ├── _popup.scss ├── _svg.scss └── _wave-types.scss └── styles.scss /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .sass-cache/ 3 | .publish/ 4 | node_modules/ 5 | build/ 6 | assets/ 7 | npm-debug.log 8 | index.html 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wave maker 2 | 3 | Tool based on [SVG Arcs](https://www.w3.org/TR/SVG2/paths.html#PathDataEllipticalArcCommands). Let you understand SVG Arcs better and create some funny waves 4 | 5 | https://yoksel.github.io/wave-maker/ 6 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-ruby-sass'); 3 | var browserSync = require('browser-sync'); 4 | var reload = browserSync.reload; 5 | var include = require("gulp-include"); 6 | var postcss = require('gulp-postcss'); 7 | var autoprefixer = require('autoprefixer'); 8 | var cssnano = require('cssnano'); 9 | var rename = require('gulp-rename'); 10 | var mqpacker = require("css-mqpacker"); 11 | var imagemin = require('gulp-imagemin'); 12 | var pngquant = require('imagemin-pngquant'); 13 | var copy = require('gulp-copy'); 14 | var ghPages = require('gulp-gh-pages'); 15 | var colors = require('colors/safe'); 16 | var del = require('del'); 17 | var gulpSequence = require('gulp-sequence'); 18 | var babel = require("gulp-babel"); 19 | var sourcemaps = require("gulp-sourcemaps"); 20 | 21 | // SASS, AUTOPREFIXR, MINIMIZE 22 | gulp.task('sass', function() { 23 | var processors = [ 24 | autoprefixer({browsers: [ 25 | 'last 1 version', 26 | 'last 2 Chrome versions', 27 | 'last 2 Firefox versions', 28 | 'last 2 Opera versions', 29 | 'last 2 Edge versions' 30 | ]}), 31 | mqpacker() 32 | ]; 33 | 34 | console.log('⬤ Run ' + colors.yellow('Sass') + 35 | ' + ' + 36 | colors.green('Autoprefixer') + 37 | ' + ' + 38 | colors.cyan('Cssnano') + ' ⬤' 39 | ); 40 | 41 | return sass('src/scss/styles.scss') 42 | .pipe(postcss(processors)) 43 | .pipe(gulp.dest('assets/css')) 44 | .pipe(reload({ stream:true })) 45 | .pipe(postcss([cssnano()])) 46 | .pipe(rename('styles.min.css')) 47 | .pipe(gulp.dest('assets/css')); 48 | }); 49 | 50 | // JS 51 | gulp.task('js', function () { 52 | return gulp.src('src/js/**/*.js') 53 | .pipe(sourcemaps.init()) 54 | .pipe(babel()) 55 | .pipe(sourcemaps.write(".")) 56 | .pipe(gulp.dest('assets/js/')) 57 | .pipe(reload({ stream:true })); 58 | }); 59 | 60 | // IMAGES 61 | gulp.task('images', function () { 62 | console.log(colors.magenta('⬤ Optimize images... ⬤')); 63 | 64 | return gulp.src('src/img/*') 65 | .pipe(imagemin({ 66 | progressive: true, 67 | svgoPlugins: [{removeViewBox: false}], 68 | use: [pngquant()] 69 | })) 70 | .pipe(gulp.dest('assets/img')); 71 | }); 72 | 73 | // INCLUDE BLOCKS IN HTML 74 | gulp.task('include', function() { 75 | console.log(colors.blue('⬤ Include files to HTML... ⬤')); 76 | 77 | gulp.src('src/index.html') 78 | .pipe(include()) 79 | .on('error', console.log) 80 | .pipe(gulp.dest('.')) 81 | .pipe(reload({ stream:true })); 82 | }); 83 | 84 | // WATCH SASS, PREPROCESS AND RELOAD 85 | gulp.task('serve', ['sass'], function() { 86 | browserSync({ 87 | server: { 88 | baseDir: '.' 89 | } 90 | }); 91 | 92 | gulp.watch(['src/**/*.scss'], ['sass']); 93 | gulp.watch(['src/**/*.html'], ['include']); 94 | gulp.watch(['src/**/*.js'], ['js']); 95 | }); 96 | 97 | // CLEAN BUILD 98 | gulp.task('clean', function(){ 99 | del(['build/**']).then(paths => { 100 | console.log('⬤ Deleted files and folders:\n', paths.join('\n')); 101 | }); 102 | }); 103 | 104 | // CLEAN BUILD & COPY FILES TO IT 105 | gulp.task('copy', function() { 106 | console.log(colors.magenta('⬤ Copy files to build/... ⬤')); 107 | 108 | return gulp.src(['assets/**/*', '*.html']) 109 | .pipe(copy('build/')); 110 | }); 111 | 112 | gulp.task('build', function (cb) { 113 | gulpSequence('clean', ['js', 'sass', 'include'], 'copy', cb) 114 | }) 115 | 116 | // PUBLISH TO GITHUB PAGES 117 | gulp.task('ghPages', function() { 118 | console.log(colors.rainbow('⬤ Publish to Github Pages... ⬤')); 119 | 120 | return gulp.src('build/**/*') 121 | .pipe(ghPages()); 122 | }); 123 | 124 | gulp.task('default', function() { 125 | console.log(colors.rainbow('⬤ ================================ ⬤\n')); 126 | console.log(' AVAILABLE COMMANDS:'); 127 | console.log(' ' + colors.cyan('-------------------\n')); 128 | console.log(' ' + colors.yellow('npm start') + 129 | ' — run local server with watcher'); 130 | console.log(' ' + colors.green('npm run build') + 131 | ' — make build of the project'); 132 | console.log(' ' + colors.cyan('npm run deploy') + 133 | ' — make build and publish project to Github Pages'); 134 | console.log(colors.rainbow('\n⬤ ================================ ⬤')); 135 | }); 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wave-maker", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "author": "yoksel", 7 | "license": "ISC", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/yoksel/wave-maker.git" 11 | }, 12 | "devDependencies": { 13 | "autoprefixer": "^6.3.5", 14 | "babel-core": "^6.26.3", 15 | "babel-preset-env": "^1.7.0", 16 | "babel-preset-latest": "^6.24.1", 17 | "browser-sync": "^2.11.2", 18 | "colors": "^1.1.2", 19 | "css-mqpacker": "^4.0.1", 20 | "cssnano": "^3.5.2", 21 | "del": "^2.2.0", 22 | "editorconfig-tools": "^0.1.1", 23 | "gulp": "^3.9.1", 24 | "gulp-babel": "^7.0.1", 25 | "gulp-copy": "0.0.2", 26 | "gulp-gh-pages": "^0.5.4", 27 | "gulp-imagemin": "^2.4.0", 28 | "gulp-include": "^2.1.0", 29 | "gulp-postcss": "^6.1.0", 30 | "gulp-rename": "^1.2.2", 31 | "gulp-ruby-sass": "^2.0.6", 32 | "gulp-sequence": "^1.0.0", 33 | "gulp-sourcemaps": "^2.6.4", 34 | "imagemin-pngquant": "^4.2.2" 35 | }, 36 | "scripts": { 37 | "start": "npm run build && gulp serve", 38 | "img": "gulp images", 39 | "build": "gulp build", 40 | "deploy": "npm run build && gulp ghPages", 41 | "check": "editorconfig-tools check *", 42 | "fix": "editorconfig-tools fix *" 43 | }, 44 | "babel": { 45 | "presets": [ 46 | "env" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/blocks/footer.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/blocks/header.html: -------------------------------------------------------------------------------- 1 |
2 |

Project scaffold

3 | 4 | 9 |
10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wave Maker 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 | <path d=""/> 18 |
19 | 20 | 23 | 24 | 25 |
26 | 27 | 28 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 | flip the value for even 43 |
44 |
45 |
46 | 47 |
48 | 54 | 55 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/js/script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var doc = document; 4 | var $ = tinyLib; 5 | var isCmdPressed = false; 6 | 7 | var svg = $.get('.svg'); 8 | var targetPath = $.get('.shape-arc'); 9 | 10 | var popup = $.get('.popup'); 11 | var popupOpenedClass = 'popup--opened'; 12 | var popupToggle = $.get('.popup__toggle'); 13 | 14 | var code = $.get('.code'); 15 | var codeOutput = $.get('.code__textarea'); 16 | var codeButton = $.get('.code__toggle'); 17 | 18 | var waveTypesItems = $.get('.wave-types__items'); 19 | 20 | var pathCoordsAttrs = $.get('.path-coords__attrs'); 21 | var attrsClass = 'attrs'; 22 | var itemClass = attrsClass + '__item'; 23 | var itemLineClass = itemClass + '--line'; 24 | var inputClass = attrsClass + '__input'; 25 | var inputErrorClass = inputClass + '--error'; 26 | var labelClass = attrsClass + '__label'; 27 | var labelLineClass = labelClass + '--line'; 28 | var labelHiddenClass = labelClass + '--hidden'; 29 | var errorClass = attrsClass + '__error'; 30 | 31 | var pathCoordsList = [{ 32 | prop: 'startLetter' 33 | }, 34 | { 35 | prop: 'startX', 36 | desc: 'start X' 37 | }, 38 | { 39 | prop: 'startY', 40 | desc: 'start Y' 41 | }, 42 | { 43 | prop: 'arcLetter' 44 | }, 45 | { 46 | prop: 'rX', 47 | desc: 'rx', 48 | min: 0 49 | }, 50 | { 51 | prop: 'rY', 52 | desc: 'ry', 53 | min: 0 54 | }, 55 | { 56 | prop: 'xRot', 57 | desc: 'x-axis-rotation', 58 | }, 59 | { 60 | prop: 'largeArc', 61 | desc: 'large-arc-flag', 62 | min: 0, 63 | max: 1 64 | }, 65 | { 66 | prop: 'sweep', 67 | desc: 'sweep-flag', 68 | min: 0, 69 | max: 1 70 | }, 71 | { 72 | prop: 'endX', 73 | desc: 'end X' 74 | }, 75 | { 76 | prop: 'endY', 77 | desc: 'end Y' 78 | }, 79 | ]; 80 | 81 | var pathParams = $.get('.path-params'); 82 | var pathParamsList = [{ 83 | prop: 'repeat', 84 | desc: 'repeat', 85 | min: 0 86 | }, 87 | { 88 | prop: 'strokeWidth', 89 | desc: 'stroke-width', 90 | min: 1 91 | } 92 | ]; 93 | 94 | var flags = $.get('.flags'); 95 | var flagsList = [ 96 | { 97 | prop: 'rotateLargeArc', 98 | desc: 'larg-arc-flag', 99 | type: 'checkbox', 100 | disableCond: { 101 | prop: 'repeat', 102 | value: 0 103 | } 104 | }, 105 | { 106 | prop: 'rotateSweep', 107 | desc: 'sweep-flag', 108 | type: 'checkbox', 109 | disableCond: { 110 | prop: 'repeat', 111 | value: 0 112 | } 113 | } 114 | ]; 115 | 116 | var inputsToDisable = []; 117 | 118 | var wavesInputsList = { 119 | radiowave: { 120 | startX: 150, 121 | startY: 200, 122 | rX: 80, 123 | rY: 100, 124 | endY: 200, 125 | endX: 300, 126 | xRot: 0, 127 | largeArc: 0, 128 | sweep: 0, 129 | repeat: 4, 130 | rotateSweep: true, 131 | rotateLargeArc: false, 132 | strokeWidthBtn: 18 133 | }, 134 | seawave: { 135 | startX: 150, 136 | startY: 200, 137 | rX: 80, 138 | rY: 100, 139 | endX: 300, 140 | endY: 200, 141 | xRot: 0, 142 | largeArc: 0, 143 | sweep: 0, 144 | repeat: 4, 145 | repeatBtn: 2, 146 | rotateSweep: false, 147 | rotateLargeArc: false, 148 | strokeWidthBtn: 11 149 | }, 150 | lightbulbs: { 151 | startX: 150, 152 | startY: 200, 153 | rX: 80, 154 | rY: 80, 155 | endX: 250, 156 | endY: 200, 157 | xRot: 0, 158 | largeArc: 1, 159 | sweep: 1, 160 | repeat: 6, 161 | rotateSweep: true, 162 | rotateLargeArc: false, 163 | strokeWidthBtn: 21 164 | }, 165 | cursive: { 166 | startX: 150, 167 | startY: 200, 168 | rX: 20, 169 | rY: 90, 170 | endX: 300, 171 | endY: 200, 172 | xRot: 60, 173 | largeArc: 1, 174 | sweep: 0, 175 | repeat: 4, 176 | rotateSweep: true, 177 | rotateLargeArc: false, 178 | strokeWidthBtn: 20 179 | }, 180 | bubbles: { 181 | startX: 150, 182 | startY: 220, 183 | rX: 80, 184 | rY: 80, 185 | endX: 230, 186 | endY: 130, 187 | xRot: 0, 188 | largeArc: 0, 189 | sweep: 0, 190 | repeat: 7, 191 | rotateSweep: true, 192 | rotateLargeArc: true, 193 | strokeWidthBtn: 16 194 | }, 195 | leaves: { 196 | startX: 150, 197 | startY: 220, 198 | rX: 160, 199 | rY: 200, 200 | endX: 230, 201 | endY: 50, 202 | xRot: 0, 203 | largeArc: 0, 204 | sweep: 1, 205 | repeat: 7, 206 | rotateSweep: false, 207 | rotateLargeArc: false, 208 | strokeWidthBtn: 15 209 | }, 210 | circle: { 211 | hidden: true, 212 | startX: 200, 213 | startY: 50, 214 | rX: 100, 215 | rY: 100, 216 | endX: 200, 217 | endY: 300, 218 | xRot: 0, 219 | largeArc: 0, 220 | sweep: 0, 221 | repeat: 1, 222 | rotateSweep: false, 223 | rotateLargeArc: true 224 | } 225 | }; 226 | 227 | //--------------------------------------------- 228 | 229 | var Arc = function (params) { 230 | 231 | this.params = params; 232 | this.path = params.path || targetPath; 233 | this.hasControls = params.hasControls || false; 234 | this.startLetter = 'M'; 235 | this.arcLetter = 'A'; 236 | this.startX = params.startX || 150; 237 | this.startY = params.startY || 200; 238 | this.endX = params.endX || 400; 239 | this.endY = params.endY || 200; 240 | this.rX = params.rX || 130; 241 | this.rY = params.rY || 120; 242 | this.xRot = params.xRot || 0; 243 | this.largeArc = params.largeArc || 0; 244 | this.sweep = params.sweep || 0; 245 | this.repeat = params.repeat || 0; 246 | this.strokeWidth = params.strokeWidth || 5; 247 | this.pathCoordsInputs = []; 248 | 249 | this.rotateSweep = params.rotateSweep !== undefined ? params.rotateSweep : true; 250 | this.rotateLargeArc = params.rotateLargeArc !== undefined ? params.rotateLargeArc : false; 251 | 252 | if (this.hasControls) { 253 | this.addHelpers(); 254 | this.addControls(); 255 | 256 | } 257 | this.getPathCoords(); 258 | this.setPathCoords(); 259 | 260 | if (this.hasControls) { 261 | this.addPathParams({ 262 | list: pathCoordsList, 263 | target: pathCoordsAttrs, 264 | itemIsLine: false, 265 | labelIsHidden: true, 266 | }); 267 | this.addPathParams({ 268 | list: pathParamsList, 269 | target: pathParams, 270 | itemIsLine: true, 271 | labelIsHidden: false, 272 | }); 273 | this.addPathParams({ 274 | list: flagsList, 275 | target: flags, 276 | itemIsLine: true, 277 | labelIsHidden: false, 278 | }); 279 | 280 | this.addWaveInputs(); 281 | } 282 | }; 283 | 284 | //--------------------------------------------- 285 | 286 | Arc.prototype.getPathCoords = function () { 287 | this.pathCoordsSet = { 288 | startLetter: this.startLetter, 289 | startX: this.startX, 290 | startY: this.startY, 291 | arcLetter: this.arcLetter, 292 | rX: this.rX, 293 | rY: this.rY, 294 | xRot: this.xRot, 295 | largeArc: this.largeArc, 296 | sweep: this.sweep, 297 | endX: this.endX, 298 | endY: this.endY 299 | }; 300 | 301 | for (var key in this.pathCoordsSet) { 302 | var item = this.pathCoordsSet[key]; 303 | if (!isNaN(item)) { 304 | this.pathCoordsSet[key] = Math.round(item); 305 | } 306 | } 307 | 308 | var pathVals = Object.values(this.pathCoordsSet); 309 | this.pathCoords = pathVals.join(' '); 310 | }; 311 | 312 | //--------------------------------------------- 313 | 314 | Arc.prototype.setPathCoords = function () { 315 | this.path.attr({ 316 | 'd': this.pathCoords, 317 | 'stroke-width': this.strokeWidth 318 | }); 319 | this.path.rect = this.path.elem.getBBox(); 320 | 321 | this.addWaves(); 322 | 323 | if(this.hasControls) { 324 | this.setAllHelperArcParams(); 325 | this.setAllControlsParams(); 326 | this.updateCode(); 327 | } 328 | }; 329 | 330 | //--------------------------------------------- 331 | 332 | Arc.prototype.addControls = function () { 333 | var that = this; 334 | this.controls = { 335 | start: { 336 | coordsFunc: this.getStartCoords, 337 | fill: 'greenyellow', 338 | targets: { 339 | 'startX': getMouseX, 340 | 'startY': getMouseY 341 | } 342 | }, 343 | end: { 344 | coordsFunc: this.getEndCoords, 345 | fill: 'greenyellow', 346 | targets: { 347 | 'endX': getMouseX, 348 | 'endY': getMouseY 349 | } 350 | }, 351 | // I dont't know how to place them on path 352 | // with rotation and if startY != endY 353 | // rx: { 354 | // coordsFunc: this.getRxCoords, 355 | // targets: { 356 | // 'rX': this.getRX 357 | // } 358 | // }, 359 | // ry: { 360 | // coordsFunc: this.getRyCoords, 361 | // targets: { 362 | // 'rY': this.getRY 363 | // } 364 | // } 365 | }; 366 | 367 | for (var key in this.controls) { 368 | var set = this.controls[key]; 369 | set.elemSet = $.createNS('circle') 370 | .attr({ 371 | id: key, 372 | r: 7 373 | }) 374 | .addClass('point-control'); 375 | 376 | svg.append(set.elemSet); 377 | 378 | set.elemSet.elem.addEventListener('mousedown', function (event) { 379 | that.currentControl = that.controls[this.id]; 380 | doc.onmousemove = function (event) { 381 | that.drag(event); 382 | } 383 | }); 384 | } 385 | }; 386 | 387 | //--------------------------------------------- 388 | 389 | Arc.prototype.getStartCoords = function () { 390 | return { 391 | cx: this.startX ? this.startX : 0, 392 | cy: this.startY ? this.startY : 0 393 | } 394 | }; 395 | 396 | //--------------------------------------------- 397 | 398 | Arc.prototype.getEndCoords = function () { 399 | return { 400 | cx: this.endX ? this.endX : 0, 401 | cy: this.endY ? this.endY : 0 402 | } 403 | }; 404 | 405 | //--------------------------------------------- 406 | 407 | Arc.prototype.getRxCoords = function () { 408 | return { 409 | cx: (this.arcHelpers.flipBoth.rect.x + this.arcHelpers.flipBoth.rect.width), 410 | cy: (this.arcHelpers.flipBoth.rect.y + this.rY) 411 | } 412 | }; 413 | 414 | Arc.prototype.getRX = function (event) { 415 | var rect = this.arcHelpers.flipBoth.rect; 416 | var offset = event.offsetX - (rect.x + rect.width); 417 | var rX = this.rX + offset; 418 | return rX; 419 | }; 420 | 421 | //--------------------------------------------- 422 | 423 | Arc.prototype.getRyCoords = function () { 424 | return { 425 | cx: (this.path.rect.x + this.path.rect.width / 2), 426 | cy: (this.path.rect.y + this.path.rect.height) 427 | } 428 | }; 429 | 430 | Arc.prototype.getRY = function (event) { 431 | var rect = this.path.rect; 432 | var offset = event.offsetY - (rect.y + rect.height); 433 | var rY = this.rY + offset; 434 | return rY; 435 | } 436 | 437 | //--------------------------------------------- 438 | 439 | Arc.prototype.setAllControlsParams = function () { 440 | for (var key in this.controls) { 441 | var set = this.controls[key]; 442 | var coords = set.coordsFunc.apply(this); 443 | 444 | set.elemSet.attr({ 445 | cx: coords.cx, 446 | cy: coords.cy, 447 | }); 448 | } 449 | }; 450 | 451 | //--------------------------------------------- 452 | 453 | Arc.prototype.drag = function (event) { 454 | var x = event.offsetX; 455 | var y = event.offsetY; 456 | var targets = this.currentControl.targets; 457 | 458 | for (var target in targets) { 459 | this[target] = targets[target].call(this, event); 460 | } 461 | 462 | this.getPathCoords(); 463 | this.setPathCoords(); 464 | this.updateInputs(); 465 | 466 | doc.onmouseup = function () { 467 | doc.onmousemove = null; 468 | this.currentControl = null; 469 | }; 470 | }; 471 | 472 | //--------------------------------------------- 473 | 474 | Arc.prototype.addHelpers = function () { 475 | this.arcHelpers = {}; 476 | this.arcHelpers.flipBoth = this.addHelperArc({ 477 | id: 'arcHelper-flipSweepAndArc', 478 | flipSweep: true, 479 | flipLargeArc: true, 480 | // stroke: 'seagreen' 481 | }); 482 | this.arcHelpers.flipArc = this.addHelperArc({ 483 | id: 'arcHelper-flipArc', 484 | flipSweep: false, 485 | flipLargeArc: true, 486 | // stroke: 'orangered' 487 | }); 488 | this.arcHelpers.flipSweep = this.addHelperArc({ 489 | id: 'arcHelper-flipSweep', 490 | flipSweep: true, 491 | flipLargeArc: false, 492 | // stroke: 'royalblue' 493 | }); 494 | 495 | this.arcHelpers.list = [ 496 | this.arcHelpers.flipBoth, 497 | this.arcHelpers.flipArc, 498 | this.arcHelpers.flipSweep 499 | ]; 500 | }; 501 | 502 | //--------------------------------------------- 503 | 504 | Arc.prototype.addHelperArc = function (params) { 505 | var arcHelper = {}; 506 | arcHelper.params = params; 507 | arcHelper.elemSet = $.createNS('path') 508 | .attr({ 509 | 'id': params.id, 510 | 'fill': 'none', 511 | 'stroke': params.stroke || '#999' 512 | }); 513 | 514 | svg.prepend(arcHelper.elemSet); 515 | return arcHelper; 516 | }; 517 | 518 | //--------------------------------------------- 519 | 520 | Arc.prototype.setHelperArcParams = function (arcHelper) { 521 | var arcParamsSet = Object.assign({}, this.pathCoordsSet); 522 | 523 | if (arcHelper.params) { 524 | if (arcHelper.params.flipLargeArc) { 525 | arcParamsSet.largeArc = +!arcParamsSet.largeArc; 526 | } 527 | if (arcHelper.params.flipSweep) { 528 | arcParamsSet.sweep = +!arcParamsSet.sweep; 529 | } 530 | } 531 | 532 | var arcParams = Object.values(arcParamsSet).join(' '); 533 | 534 | arcHelper.elemSet.attr({ 535 | 'd': arcParams 536 | }); 537 | arcHelper.rect = arcHelper.elemSet.elem.getBBox(); 538 | }; 539 | 540 | //--------------------------------------------- 541 | 542 | Arc.prototype.setAllHelperArcParams = function () { 543 | var that = this; 544 | this.arcHelpers.list.map(function (item) { 545 | that.setHelperArcParams(item); 546 | }); 547 | }; 548 | 549 | //--------------------------------------------- 550 | 551 | Arc.prototype.changeValueByKeyboard = function (event, input, error) { 552 | if (!(event.keyCode == 38 || event.keyCode == 40)) { 553 | return; 554 | } 555 | 556 | var step = 1; 557 | 558 | if (event.shiftKey && (event.ctrlKey || isCmdPressed)) { 559 | step = 1000; 560 | } else if (event.ctrlKey || isCmdPressed) { 561 | step = 100; 562 | } else if (event.shiftKey) { 563 | step = 10; 564 | } 565 | 566 | if (event.keyCode === 38) { 567 | input.value = +input.value + step; 568 | } else { 569 | input.value = +input.value - step; 570 | } 571 | 572 | setInputWidth.apply(input); 573 | 574 | if (!checkValue.apply(input, [error])) { 575 | return false; 576 | } 577 | 578 | this[input.name] = input.value; 579 | this.getPathCoords(); 580 | this.setPathCoords(); 581 | }; 582 | 583 | //--------------------------------------------- 584 | 585 | Arc.prototype.updateInputs = function () { 586 | var that = this; 587 | 588 | this.pathCoordsInputs.forEach(function (item) { 589 | var name = item.elem.name; 590 | if (that[name] == null) { 591 | return; 592 | } 593 | var value = +item.elem.value; 594 | var newValue = that.pathCoordsSet[name] || that[name]; 595 | 596 | if (item.elem.type === 'checkbox') { 597 | item.elem.checked = !!that[name]; 598 | return; 599 | } 600 | 601 | item.elem.value = newValue; 602 | 603 | if (value !== newValue) { 604 | setInputWidth.apply(item.elem); 605 | } 606 | 607 | disableInputs.call(item.elem); 608 | }); 609 | }; 610 | 611 | //--------------------------------------------- 612 | 613 | Arc.prototype.createInput = function (item) { 614 | var name = item.prop; 615 | var value = this[name]; 616 | 617 | var input = $.create('input') 618 | .attr({ 619 | type: item.type || 'text', 620 | name: name, 621 | id: name, 622 | value: value 623 | }) 624 | .addClass([ 625 | inputClass, 626 | inputClass + '--' + name, 627 | inputClass + '--' + typeof (value) 628 | ]); 629 | 630 | if (item.min !== undefined && item.min !== null) { 631 | input.attr({ 632 | min: item.min 633 | }); 634 | } 635 | if (item.max) { 636 | input.attr({ 637 | max: item.max 638 | }); 639 | } 640 | if (typeof (value) === 'string') { 641 | input.attr({ 642 | 'disabled': '' 643 | }); 644 | } 645 | else if (typeof (value) === 'boolean' && value === true) { 646 | input.attr({ 647 | checked: value 648 | }); 649 | } 650 | 651 | 652 | if (item.disableCond) { 653 | var cond = item.disableCond; 654 | 655 | if (this[cond.prop] === cond.value) { 656 | input.elem.disabled = true; 657 | } 658 | 659 | inputsToDisable.push({ 660 | input: input, 661 | disableCond: item.disableCond 662 | }) 663 | } 664 | 665 | return input; 666 | }; 667 | 668 | //--------------------------------------------- 669 | 670 | Arc.prototype.createLabel = function (item, params) { 671 | var name = item.prop; 672 | var value = this[name]; 673 | 674 | var label = $.create('label') 675 | .attr({ 676 | for: name 677 | }) 678 | .addClass(labelClass); 679 | 680 | if (params.labelIsHidden) { 681 | label.addClass(labelHiddenClass); 682 | } 683 | if (params.itemIsLine) { 684 | label.addClass(labelLineClass); 685 | } 686 | label.html(item.desc); 687 | 688 | return label; 689 | }; 690 | 691 | //--------------------------------------------- 692 | 693 | Arc.prototype.createError = function (item) { 694 | if (item.min === undefined && item.max === undefined) { 695 | return null; 696 | } 697 | var error = $.create('span') 698 | .addClass(errorClass); 699 | 700 | return error; 701 | }; 702 | 703 | //--------------------------------------------- 704 | 705 | Arc.prototype.addPathParams = function (params) { 706 | var that = this; 707 | var list = params.list; 708 | var target = params.target; 709 | var items = []; 710 | 711 | list.forEach(function (item) { 712 | var name = item.prop; 713 | var value = that[name]; 714 | 715 | var input = that.createInput(item); 716 | 717 | var label = that.createLabel(item, params); 718 | 719 | var error = that.createError(item); 720 | 721 | var item = $.create('span') 722 | .addClass([ 723 | itemClass, 724 | itemClass + '--' + name 725 | ]) 726 | .append([input, label, error]); 727 | 728 | if (params.itemIsLine) { 729 | item.addClass(itemLineClass); 730 | } 731 | 732 | that.pathCoordsInputs.push(input); 733 | items.push(item); 734 | 735 | // Events 736 | input.elem.addEventListener('input', function () { 737 | if (this.type === 'checkbox') { 738 | return; 739 | } 740 | setInputWidth.apply(this); 741 | if (!checkValue.apply(this, [error])) { 742 | return false; 743 | } 744 | 745 | that[this.name] = this.value; 746 | that.getPathCoords(); 747 | that.setPathCoords(); 748 | disableInputs.call(this); 749 | }); 750 | 751 | input.elem.addEventListener('keydown', function (event) { 752 | if (this.type !== 'text') { 753 | return; 754 | } 755 | 756 | setIsCmd(event); 757 | that.changeValueByKeyboard(event, this, error); 758 | disableInputs.call(this); 759 | }); 760 | 761 | input.elem.addEventListener('keyup', function (event) { 762 | unSetIsCmd(event); 763 | }); 764 | 765 | input.elem.addEventListener('click', function (event) { 766 | if (this.type != 'checkbox') { 767 | return; 768 | } 769 | 770 | that[this.name] = this.checked; 771 | 772 | that.getPathCoords(); 773 | that.setPathCoords(); 774 | }); 775 | 776 | }); 777 | 778 | target.append(items); 779 | }; 780 | 781 | //--------------------------------------------- 782 | 783 | // context: input 784 | function disableInputs () { 785 | var inputId = this.id; 786 | var inputValue = +this.value; 787 | 788 | inputsToDisable.forEach(function (item) { 789 | var input = item.input; 790 | var cond = item.disableCond; 791 | 792 | if (inputId === cond.prop) { 793 | if (inputValue === cond.value) { 794 | input.elem.disabled = true; 795 | } 796 | else { 797 | input.elem.disabled = false; 798 | } 799 | } 800 | }); 801 | }; 802 | 803 | //--------------------------------------------- 804 | 805 | Arc.prototype.addWaves = function () { 806 | var wavesParamsSet = [this.pathCoords]; 807 | if (this.repeat === 0) { 808 | return; 809 | } 810 | 811 | for (var i = 0; i < this.repeat; i++) { 812 | wavesParamsSet.push(this.addWave(i)); 813 | } 814 | 815 | var wavesParams = wavesParamsSet.join(' '); 816 | this.path.attr({ 817 | 'd': wavesParams 818 | }); 819 | }; 820 | 821 | //--------------------------------------------- 822 | 823 | Arc.prototype.addWave = function (counter) { 824 | var arcParamsSet = {}; 825 | var waveWidth = this.pathCoordsSet.endX - this.pathCoordsSet.startX; 826 | 827 | for (var key in this.pathCoordsSet) { 828 | arcParamsSet[key] = this.pathCoordsSet[key]; 829 | } 830 | 831 | delete arcParamsSet['startLetter']; 832 | delete arcParamsSet['startX']; 833 | delete arcParamsSet['startY']; 834 | 835 | arcParamsSet['endX'] = this.pathCoordsSet.endX + (waveWidth * (counter + 1)); 836 | if (counter % 2 === 0) { 837 | if (this.rotateSweep) { 838 | arcParamsSet['sweep'] = +!this.pathCoordsSet.sweep; 839 | } 840 | if (this.rotateLargeArc) { 841 | arcParamsSet['largeArc'] = +!this.pathCoordsSet.largeArc; 842 | } 843 | 844 | arcParamsSet['endY'] = this.pathCoordsSet.startY; 845 | } 846 | 847 | var arcParamsVals = Object.values(arcParamsSet); 848 | var arcParams = arcParamsVals.join(' '); 849 | return arcParams; 850 | }; 851 | 852 | //--------------------------------------------- 853 | 854 | Arc.prototype.cloneParams = function () { 855 | var params = Object.assign({}, this.pathCoordsSet); 856 | params.repeat = this.repeat; 857 | params.rotateLargeArc = this.rotateLargeArc; 858 | params.rotateSweep = this.rotateSweep; 859 | params.strokeWidth = this.strokeWidth; 860 | 861 | return params; 862 | }; 863 | 864 | //--------------------------------------------- 865 | 866 | function getViewBoxByPath(path) { 867 | const bbBox = path.elem.getBBox(); 868 | const strokeWidth = +path.attr('stroke-width'); 869 | const width = bbBox.width; 870 | const height = bbBox.height; 871 | 872 | var viewBox = [ 873 | bbBox.x - strokeWidth, 874 | bbBox.y - strokeWidth, 875 | width + strokeWidth * 2, 876 | height + strokeWidth * 2 877 | ]; 878 | 879 | viewBox = viewBox.map(function (item) { 880 | return Math.round(item); 881 | }); 882 | viewBox = viewBox.join(' '); 883 | 884 | return viewBox; 885 | } 886 | 887 | //--------------------------------------------- 888 | 889 | Arc.prototype.getCode = function (params) { 890 | var params = params; 891 | var newParams = Object.assign({}, params); 892 | newParams.path = this.path.clone() 893 | 894 | if (newParams.strokeWidthBtn) { 895 | newParams.strokeWidth = newParams.strokeWidthBtn; 896 | } 897 | if (newParams.repeatBtn) { 898 | newParams.repeat = newParams.repeatBtn; 899 | } 900 | 901 | var newArc = new Arc(newParams); 902 | var newPath = newArc.path; 903 | var newPathElem = newPath.elem; 904 | newPathElem.removeAttribute('class'); 905 | 906 | var copyRect = newPathElem.getBBox(); 907 | var strokeWidth = +newArc.strokeWidth; 908 | var strokeWidthHalf = strokeWidth / 2; 909 | 910 | newArc.startX -= copyRect.x - strokeWidthHalf; 911 | newArc.startY -= copyRect.y - strokeWidthHalf; 912 | newArc.endX -= copyRect.x - strokeWidthHalf; 913 | newArc.endY -= copyRect.y - strokeWidthHalf; 914 | 915 | newArc.getPathCoords(); 916 | newArc.setPathCoords(); 917 | 918 | var viewBox = getViewBoxByPath(this.path); 919 | 920 | var result = `${newPathElem.outerHTML}`; 921 | return result; 922 | }; 923 | 924 | //--------------------------------------------- 925 | 926 | Arc.prototype.updateCode = function () { 927 | var output = this.getCode(this.cloneParams()); 928 | codeOutput.val(output); 929 | 930 | changeContentHeight.call(codeButton.elem); 931 | }; 932 | 933 | //--------------------------------------------- 934 | 935 | Arc.prototype.addWaveInputs = function () { 936 | var that = this; 937 | var prefix = 'wave-types'; 938 | var items = []; 939 | 940 | for (var key in wavesInputsList) { 941 | var params = wavesInputsList[key]; 942 | 943 | if (params.hidden) { 944 | continue; 945 | } 946 | var demoPath = this.getCode(params); 947 | 948 | var button = $.create('button') 949 | .attr({ 950 | type: 'button', 951 | name: prefix, 952 | id: key 953 | }) 954 | .html(demoPath) 955 | .addClass(prefix + '__button'); 956 | 957 | var item = $.create('div') 958 | .addClass(prefix + '__item') 959 | .append([button]); 960 | 961 | items.push(item); 962 | 963 | button.elem.addEventListener('click', function () { 964 | var params = wavesInputsList[this.id]; 965 | 966 | for (var key in params) { 967 | that[key] = params[key]; 968 | } 969 | 970 | that.getPathCoords(); 971 | that.setPathCoords(); 972 | that.updateInputs(); 973 | }); 974 | } 975 | 976 | waveTypesItems.append(items); 977 | 978 | // Fix viewBox on examples 979 | items.forEach(item => { 980 | const svg = $.get('svg', item.elem); 981 | const path = $.get('path', item.elem); 982 | const viewBox = getViewBoxByPath(path); 983 | 984 | svg.attr('viewBox', viewBox); 985 | }) 986 | }; 987 | 988 | //--------------------------------------------- 989 | 990 | function setInputWidth() { 991 | if (this.type !== 'text') { 992 | return; 993 | } 994 | 995 | this.style.minWidth = this.value.length * .65 + 'em'; 996 | } 997 | 998 | //--------------------------------------------- 999 | 1000 | function checkValue(errorElem) { 1001 | if (!errorElem) { 1002 | return true; 1003 | } 1004 | 1005 | errorElem.html(''); 1006 | this.classList.remove(inputErrorClass); 1007 | 1008 | if (isNaN(this.value)) { 1009 | errorElem.html('not a number'); 1010 | this.classList.add(inputErrorClass); 1011 | return false; 1012 | } else if (this.min && this.value < this.min) { 1013 | this.classList.add(inputErrorClass); 1014 | errorElem.html('minimum: ' + this.min); 1015 | return false; 1016 | } else if (this.max && this.value > this.max) { 1017 | this.classList.add(inputErrorClass); 1018 | errorElem.html('maximum: ' + this.max); 1019 | return false; 1020 | } 1021 | 1022 | return true; 1023 | } 1024 | 1025 | //--------------------------------------------- 1026 | 1027 | function setIsCmd(event) { 1028 | // Chrome || FF 1029 | if (event.keyCode == 91 || (event.key === 'Meta' && event.keyCode === 224)) { 1030 | isCmdPressed = true; 1031 | } 1032 | } 1033 | 1034 | function unSetIsCmd(event) { 1035 | // Chrome || FF 1036 | if (event.keyCode == 91 || (event.key === 'Meta' && event.keyCode === 224)) { 1037 | isCmdPressed = false; 1038 | } 1039 | } 1040 | doc.addEventListener('keyup', function (event) { 1041 | unSetIsCmd(event); 1042 | }); 1043 | 1044 | //--------------------------------------------- 1045 | 1046 | function getMouseX(event) { 1047 | return event.offsetX; 1048 | } 1049 | 1050 | function getMouseY(event) { 1051 | return event.offsetY; 1052 | } 1053 | 1054 | //--------------------------------------------- 1055 | 1056 | // Popup events 1057 | popup.forEach(function (item) { 1058 | item.elem.addEventListener('click', function (event) { 1059 | event.stopPropagation(); 1060 | }); 1061 | }); 1062 | 1063 | popupToggle.forEach(function (item) { 1064 | 1065 | item.elem.addEventListener('click', function (event) { 1066 | var parent = this.parentNode; 1067 | 1068 | if (parent.classList.contains(popupOpenedClass)) { 1069 | parent.classList.remove(popupOpenedClass); 1070 | } 1071 | else { 1072 | closeOpened(); 1073 | changeContentHeight.call(this); 1074 | 1075 | parent.classList.toggle(popupOpenedClass); 1076 | } 1077 | }); 1078 | }); 1079 | 1080 | 1081 | doc.addEventListener('click', function () { 1082 | closeOpened(); 1083 | }); 1084 | 1085 | function closeOpened() { 1086 | var popupPanel = $.get('.' + popupOpenedClass); 1087 | 1088 | if (popupPanel.elem) { 1089 | popupPanel.removeClass(popupOpenedClass); 1090 | } 1091 | 1092 | } 1093 | 1094 | function changeContentHeight() { 1095 | var parent = this.parentNode; 1096 | var container = parent.querySelector('.popup__container'); 1097 | var content = parent.querySelector('.popup__content'); 1098 | 1099 | // trick to get real scrollHeight 1100 | content.style.maxHeight = '0'; 1101 | container.style.maxHeight = (content.scrollHeight + 10) + 'px'; 1102 | content.style.maxHeight = null; 1103 | } 1104 | 1105 | //--------------------------------------------- 1106 | 1107 | var arc = new Arc({ 1108 | path: targetPath, 1109 | hasControls: true 1110 | }); 1111 | -------------------------------------------------------------------------------- /src/js/tinylib.js: -------------------------------------------------------------------------------- 1 | // Tiny library 2 | 3 | //--------------------------------- 4 | // http://checkman.io/blog/creating-a-javascript-library/ 5 | // http://code.tutsplus.com/tutorials/build-your-first-javascript-library--net-26796 6 | //http://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/ 7 | 8 | (function (window) { 9 | 10 | 'use strict'; 11 | 12 | function define_library() { 13 | 14 | var tinyLib = {}; 15 | var doc = document; 16 | var ns = 'http://www.w3.org/2000/svg'; 17 | 18 | function ElemSet(elem) { 19 | this.elem = elem; 20 | } 21 | 22 | //--------------------------------- 23 | 24 | tinyLib.get = function (selector, context) { 25 | 26 | var contextElem = context ? context : doc; 27 | if (context && context.elem) { 28 | contextElem = context.elem; 29 | } 30 | 31 | var nodeList = contextElem.querySelectorAll(selector); 32 | var elemsArr = Array.prototype.slice.call(nodeList); 33 | 34 | var elemsList = elemsArr.map(function (item) { 35 | return new ElemSet(item); 36 | }); 37 | 38 | if (elemsList.length === 1) { 39 | return elemsList[0]; 40 | } 41 | 42 | return elemsList; 43 | }; 44 | 45 | //--------------------------------- 46 | 47 | tinyLib.create = function (tagName) { 48 | var elem = doc.createElement(tagName); 49 | return new ElemSet(elem); 50 | }; 51 | 52 | //--------------------------------- 53 | 54 | tinyLib.createNS = function (tagName) { 55 | var elem = doc.createElementNS(ns, tagName); 56 | return new ElemSet(elem); 57 | }; 58 | 59 | //------------------------------ 60 | // ElemSet Methods 61 | 62 | ElemSet.prototype.addClass = function (classNames) { 63 | var elem = this.elem; 64 | 65 | if (typeof classNames === 'string') { 66 | classNames = [classNames]; 67 | } 68 | 69 | classNames.forEach(function (className) { 70 | elem.classList.add(className); 71 | }); 72 | 73 | return this; 74 | }; 75 | 76 | //--------------------------------- 77 | 78 | ElemSet.prototype.removeClass = function (classNames) { 79 | 80 | var elem = this.elem; 81 | 82 | if (typeof classNames === 'string') { 83 | classNames = [classNames]; 84 | } 85 | 86 | classNames.forEach(function (className) { 87 | elem.classList.remove(className); 88 | }); 89 | 90 | return this; 91 | }; 92 | 93 | //--------------------------------- 94 | 95 | ElemSet.prototype.toggleClass = function (className) { 96 | 97 | var elem = this.elem; 98 | elem.classList.toggle(className); 99 | 100 | return this; 101 | }; 102 | 103 | //--------------------------------- 104 | 105 | ElemSet.prototype.append = function (elem) { 106 | var itemsToAdd = inputElemToItemsList(elem); 107 | var that = this; 108 | 109 | itemsToAdd.forEach(function (item) { 110 | if (!item) { 111 | return; 112 | } 113 | that.elem.appendChild(item.elem); 114 | }); 115 | 116 | return this; 117 | }; 118 | 119 | //--------------------------------- 120 | 121 | ElemSet.prototype.prepend = function (elem) { 122 | var itemsToAdd = inputElemToItemsList(elem); 123 | var that = this; 124 | 125 | itemsToAdd.forEach(function (item) { 126 | if (!item) { 127 | return; 128 | } 129 | that.elem.insertBefore(item.elem, that.elem.firstChild); 130 | }); 131 | 132 | return this; 133 | }; 134 | 135 | //--------------------------------- 136 | 137 | ElemSet.prototype.clone = function () { 138 | var elemToClone = this.elem; 139 | var clonedElem = elemToClone.cloneNode(true); 140 | 141 | return new ElemSet(clonedElem); 142 | }; 143 | 144 | //--------------------------------- 145 | 146 | ElemSet.prototype.attr = function (attrName, attrVal) { 147 | 148 | var elem = this.elem; 149 | var attrSet = {}; 150 | 151 | if (attrVal) { 152 | attrSet[attrName] = attrVal; 153 | } else if (typeof attrName === 'object') { 154 | attrSet = attrName; 155 | } 156 | 157 | if (Object.keys(attrSet).length > 0) { 158 | for (var key in attrSet) { 159 | elem.setAttribute(key, attrSet[key]); 160 | } 161 | return this; 162 | } 163 | 164 | var out = elem.getAttribute(attrName); 165 | return out; 166 | 167 | }; 168 | 169 | //--------------------------------- 170 | 171 | ElemSet.prototype.val = function (content) { 172 | if (!content) { 173 | return this.elem.value; 174 | } 175 | 176 | this.elem.value = content; 177 | return this; 178 | }; 179 | 180 | //--------------------------------- 181 | 182 | ElemSet.prototype.html = function (content) { 183 | var elem = this.elem; 184 | 185 | if (content) { 186 | elem.innerHTML = content; 187 | return this; 188 | } 189 | 190 | return this; 191 | }; 192 | 193 | //--------------------------------- 194 | 195 | ElemSet.prototype.data = function (content) { 196 | var elem = this.elem; 197 | 198 | if (content) { 199 | // Input: list 200 | if (Array.isArray(content) === true) { 201 | var dataList = {}; 202 | 203 | content.forEach(function (item) { 204 | var data = elem.dataset[item]; 205 | if (data) { 206 | dataList[item] = data; 207 | } 208 | }); 209 | 210 | return dataList; 211 | } 212 | // Input: object 213 | else if (typeof content === 'object') { 214 | 215 | for (var key in content) { 216 | elem.dataset[key] = content[key]; 217 | } 218 | 219 | return elem.dataset; 220 | } 221 | // Input: string 222 | else if (typeof content === 'string') { 223 | var data = elem.dataset[content]; 224 | return data; 225 | } 226 | 227 | } 228 | 229 | return null; 230 | }; 231 | 232 | //--------------------------------- 233 | // Colored console output 234 | 235 | var consoleStyles = { 236 | 'h1': 'font: 2.5em/1 Arial; color: crimson;', 237 | 'h2': 'font: 2em/1 Arial; color: orangered;', 238 | 'h3': 'font: 1.6em/1 Arial; color: olivedrab;', 239 | 'h4': 'font: bold 1.3em/1 Arial; color: midnightblue', 240 | 'warn': 'padding: 0 .3rem; background: crimson; font: 2em/1 Arial; color: white' 241 | }; 242 | 243 | tinyLib.out = function (msg, style) { 244 | if (!style || !consoleStyles[style]) { 245 | style = ''; 246 | } 247 | console.log('%c' + msg, consoleStyles[style]); 248 | }; 249 | 250 | tinyLib.dir = function (msg) { 251 | console.dir(msg); 252 | }; 253 | 254 | //--------------------------------- 255 | 256 | function inputElemToItemsList(elem) { 257 | var elemToAdd = elem; 258 | var itemsToAdd = []; 259 | 260 | if (typeof elem === 'string') { 261 | elemToAdd = tinyLib.create(elem); 262 | itemsToAdd.push(elemToAdd); 263 | } else if (Array.isArray(elem) === true) { 264 | itemsToAdd = elem; 265 | } else { 266 | itemsToAdd.push(elemToAdd); 267 | } 268 | 269 | return itemsToAdd; 270 | } 271 | 272 | //--------------------------------- 273 | 274 | return tinyLib; 275 | } 276 | 277 | //--------------------------------- 278 | 279 | if (typeof tinyLib === 'undefined') { 280 | window.tinyLib = define_library(); 281 | } 282 | 283 | })(window); 284 | -------------------------------------------------------------------------------- /src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | HTML, 2 | BODY { 3 | height: 100%; 4 | } 5 | 6 | BODY { 7 | min-width: 600px; 8 | display: flex; 9 | background: #FFF; 10 | font: #{$font-size}/1.4 $font-family; 11 | color: $color-font; 12 | } 13 | 14 | A { 15 | color: $color-link; 16 | &:visited { 17 | color: $color-link-visited; 18 | } 19 | &:hover { 20 | color: $color-link-hover; 21 | } 22 | } 23 | 24 | UL { 25 | margin: 0; 26 | padding: 0; 27 | list-style: none; 28 | } 29 | 30 | H1, 31 | H2, 32 | H3, 33 | H4, 34 | H5 { 35 | font-weight: normal; 36 | } 37 | 38 | 39 | /* LAYOUT */ 40 | 41 | .header, 42 | .container, 43 | .footer { 44 | @include container-width; 45 | margin: 0; 46 | padding: 0; 47 | } 48 | 49 | .container { 50 | display: flex; 51 | @media ( max-width: $bp-tablet) { 52 | flex-direction: column; 53 | } 54 | } 55 | 56 | .main { 57 | display: flex; 58 | flex-direction: column; 59 | flex-grow: 1; 60 | } 61 | 62 | .aside { 63 | flex-basis: 200px; 64 | margin-left: 30px; 65 | @media ( max-width: $bp-tablet) { 66 | flex-basis: 0; 67 | margin-left: 0; 68 | margin-top: 1rem; 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin container-width { 2 | width: 100%; 3 | min-width: $bp-mobile; 4 | 5 | @media (min-width: $bp-tablet ) { 6 | min-width: $bp-tablet; 7 | } 8 | 9 | @media (min-width: $bp-desktop ) { 10 | min-width: $bp-desktop; 11 | } 12 | } 13 | 14 | @mixin clear { 15 | &:before, 16 | &:after { 17 | content: ""; 18 | display: table; 19 | clear: both; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/scss/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS. 6 | */ 7 | 8 | html { 9 | font-family: sans-serif; /* 1 */ 10 | -ms-text-size-adjust: 100%; /* 2 */ 11 | -webkit-text-size-adjust: 100%; /* 2 */ 12 | } 13 | 14 | /** 15 | * Remove the margin in all browsers (opinionated). 16 | */ 17 | 18 | body { 19 | margin: 0; 20 | } 21 | 22 | /* HTML5 display definitions 23 | ========================================================================== */ 24 | 25 | /** 26 | * Add the correct display in IE 9-. 27 | * 1. Add the correct display in Edge, IE, and Firefox. 28 | * 2. Add the correct display in IE. 29 | */ 30 | 31 | article, 32 | aside, 33 | details, /* 1 */ 34 | figcaption, 35 | figure, 36 | footer, 37 | header, 38 | main, /* 2 */ 39 | menu, 40 | nav, 41 | section, 42 | summary { /* 1 */ 43 | display: block; 44 | } 45 | 46 | /** 47 | * Add the correct display in IE 9-. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; 55 | } 56 | 57 | /** 58 | * Add the correct display in iOS 4-7. 59 | */ 60 | 61 | audio:not([controls]) { 62 | display: none; 63 | height: 0; 64 | } 65 | 66 | /** 67 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 68 | */ 69 | 70 | progress { 71 | vertical-align: baseline; 72 | } 73 | 74 | /** 75 | * Add the correct display in IE 10-. 76 | * 1. Add the correct display in IE. 77 | */ 78 | 79 | template, /* 1 */ 80 | [hidden] { 81 | display: none; 82 | } 83 | 84 | /* Links 85 | ========================================================================== */ 86 | 87 | /** 88 | * Remove the gray background on active links in IE 10. 89 | */ 90 | 91 | a { 92 | background-color: transparent; 93 | } 94 | 95 | /** 96 | * Remove the outline on focused links when they are also active or hovered 97 | * in all browsers (opinionated). 98 | */ 99 | 100 | a:active, 101 | a:hover { 102 | outline-width: 0; 103 | } 104 | 105 | /* Text-level semantics 106 | ========================================================================== */ 107 | 108 | /** 109 | * 1. Remove the bottom border in Firefox 39-. 110 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 111 | */ 112 | 113 | abbr[title] { 114 | border-bottom: none; /* 1 */ 115 | text-decoration: underline; /* 2 */ 116 | text-decoration: underline dotted; /* 2 */ 117 | } 118 | 119 | /** 120 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: inherit; 126 | } 127 | 128 | /** 129 | * Add the correct font weight in Chrome, Edge, and Safari. 130 | */ 131 | 132 | b, 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | /** 138 | * Add the correct font style in Android 4.3-. 139 | */ 140 | 141 | dfn { 142 | font-style: italic; 143 | } 144 | 145 | /** 146 | * Correct the font size and margin on `h1` elements within `section` and 147 | * `article` contexts in Chrome, Firefox, and Safari. 148 | */ 149 | 150 | h1 { 151 | font-size: 2em; 152 | margin: 0.67em 0; 153 | } 154 | 155 | /** 156 | * Add the correct background and color in IE 9-. 157 | */ 158 | 159 | mark { 160 | background-color: #ff0; 161 | color: #000; 162 | } 163 | 164 | /** 165 | * Add the correct font size in all browsers. 166 | */ 167 | 168 | small { 169 | font-size: 80%; 170 | } 171 | 172 | /** 173 | * Prevent `sub` and `sup` elements from affecting the line height in 174 | * all browsers. 175 | */ 176 | 177 | sub, 178 | sup { 179 | font-size: 75%; 180 | line-height: 0; 181 | position: relative; 182 | vertical-align: baseline; 183 | } 184 | 185 | sub { 186 | bottom: -0.25em; 187 | } 188 | 189 | sup { 190 | top: -0.5em; 191 | } 192 | 193 | /* Embedded content 194 | ========================================================================== */ 195 | 196 | /** 197 | * Remove the border on images inside links in IE 10-. 198 | */ 199 | 200 | img { 201 | border-style: none; 202 | } 203 | 204 | /** 205 | * Hide the overflow in IE. 206 | */ 207 | 208 | svg:not(:root) { 209 | overflow: hidden; 210 | } 211 | 212 | /* Grouping content 213 | ========================================================================== */ 214 | 215 | /** 216 | * 1. Correct the inheritance and scaling of font size in all browsers. 217 | * 2. Correct the odd `em` font sizing in all browsers. 218 | */ 219 | 220 | code, 221 | kbd, 222 | pre, 223 | samp { 224 | font-family: monospace, monospace; /* 1 */ 225 | font-size: 1em; /* 2 */ 226 | } 227 | 228 | /** 229 | * Add the correct margin in IE 8. 230 | */ 231 | 232 | figure { 233 | margin: 1em 40px; 234 | } 235 | 236 | /** 237 | * 1. Add the correct box sizing in Firefox. 238 | * 2. Show the overflow in Edge and IE. 239 | */ 240 | 241 | hr { 242 | box-sizing: content-box; /* 1 */ 243 | height: 0; /* 1 */ 244 | overflow: visible; /* 2 */ 245 | } 246 | 247 | /* Forms 248 | ========================================================================== */ 249 | 250 | /** 251 | * Change font properties to `inherit` in all browsers (opinionated). 252 | */ 253 | 254 | button, 255 | input, 256 | select, 257 | textarea { 258 | font: inherit; 259 | } 260 | 261 | /** 262 | * Restore the font weight unset by the previous rule. 263 | */ 264 | 265 | optgroup { 266 | font-weight: bold; 267 | } 268 | 269 | /** 270 | * Show the overflow in IE. 271 | * 1. Show the overflow in Edge. 272 | * 2. Show the overflow in Edge, Firefox, and IE. 273 | */ 274 | 275 | button, 276 | input, /* 1 */ 277 | select { /* 2 */ 278 | overflow: visible; 279 | } 280 | 281 | /** 282 | * Remove the margin in Safari. 283 | * 1. Remove the margin in Firefox and Safari. 284 | */ 285 | 286 | button, 287 | input, 288 | select, 289 | textarea { /* 1 */ 290 | margin: 0; 291 | } 292 | 293 | /** 294 | * Remove the inheritence of text transform in Edge, Firefox, and IE. 295 | * 1. Remove the inheritence of text transform in Firefox. 296 | */ 297 | 298 | button, 299 | select { /* 1 */ 300 | text-transform: none; 301 | } 302 | 303 | /** 304 | * Change the cursor in all browsers (opinionated). 305 | */ 306 | 307 | button, 308 | [type="button"], 309 | [type="reset"], 310 | [type="submit"] { 311 | cursor: pointer; 312 | } 313 | 314 | /** 315 | * Restore the default cursor to disabled elements unset by the previous rule. 316 | */ 317 | 318 | [disabled] { 319 | cursor: default; 320 | } 321 | 322 | /** 323 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 324 | * controls in Android 4. 325 | * 2. Correct the inability to style clickable types in iOS. 326 | */ 327 | 328 | button, 329 | html [type="button"], /* 1 */ 330 | [type="reset"], 331 | [type="submit"] { 332 | -webkit-appearance: button; /* 2 */ 333 | } 334 | 335 | /** 336 | * Remove the inner border and padding in Firefox. 337 | */ 338 | 339 | button::-moz-focus-inner, 340 | input::-moz-focus-inner { 341 | border: 0; 342 | padding: 0; 343 | } 344 | 345 | /** 346 | * Restore the focus styles unset by the previous rule. 347 | */ 348 | 349 | button:-moz-focusring, 350 | input:-moz-focusring { 351 | outline: 1px dotted ButtonText; 352 | } 353 | 354 | /** 355 | * Change the border, margin, and padding in all browsers (opinionated). 356 | */ 357 | 358 | fieldset { 359 | border: 1px solid #c0c0c0; 360 | margin: 0 2px; 361 | padding: 0.35em 0.625em 0.75em; 362 | } 363 | 364 | /** 365 | * 1. Correct the text wrapping in Edge and IE. 366 | * 2. Correct the color inheritance from `fieldset` elements in IE. 367 | * 3. Remove the padding so developers are not caught out when they zero out 368 | * `fieldset` elements in all browsers. 369 | */ 370 | 371 | legend { 372 | box-sizing: border-box; /* 1 */ 373 | color: inherit; /* 2 */ 374 | display: table; /* 1 */ 375 | max-width: 100%; /* 1 */ 376 | padding: 0; /* 3 */ 377 | white-space: normal; /* 1 */ 378 | } 379 | 380 | /** 381 | * Remove the default vertical scrollbar in IE. 382 | */ 383 | 384 | textarea { 385 | overflow: auto; 386 | } 387 | 388 | /** 389 | * 1. Add the correct box sizing in IE 10-. 390 | * 2. Remove the padding in IE 10-. 391 | */ 392 | 393 | [type="checkbox"], 394 | [type="radio"] { 395 | box-sizing: border-box; /* 1 */ 396 | padding: 0; /* 2 */ 397 | } 398 | 399 | /** 400 | * Correct the cursor style of increment and decrement buttons in Chrome. 401 | */ 402 | 403 | [type="number"]::-webkit-inner-spin-button, 404 | [type="number"]::-webkit-outer-spin-button { 405 | height: auto; 406 | } 407 | 408 | /** 409 | * Correct the odd appearance of search inputs in Chrome and Safari. 410 | */ 411 | 412 | [type="search"] { 413 | -webkit-appearance: textfield; 414 | } 415 | 416 | /** 417 | * Remove the inner padding and cancel buttons in Chrome on OS X and 418 | * Safari on OS X. 419 | */ 420 | 421 | [type="search"]::-webkit-search-cancel-button, 422 | [type="search"]::-webkit-search-decoration { 423 | -webkit-appearance: none; 424 | } 425 | -------------------------------------------------------------------------------- /src/scss/_vars.scss: -------------------------------------------------------------------------------- 1 | /* FONT */ 2 | $font-family: Georgia, serif; 3 | $font-size: 16px; 4 | 5 | /* COLORS */ 6 | $color-font: #222; 7 | $color-link: darkslategray; 8 | $color-link-visited: darkcyan; 9 | $color-link-hover: cadetblue; 10 | 11 | 12 | /* BREAKPOINTS */ 13 | $bp-mobile: 320px; 14 | $bp-tablet: 768px; 15 | $bp-desktop: 1000px; 16 | -------------------------------------------------------------------------------- /src/scss/blocks/_attrs.scss: -------------------------------------------------------------------------------- 1 | .attrs { 2 | display: flex; 3 | align-items: center; 4 | 5 | &__item { 6 | position: relative; 7 | 8 | &--line { 9 | display: flex; 10 | flex-direction: row; 11 | align-items: center; 12 | margin: 0 1em 0 0; 13 | } 14 | } 15 | 16 | &__input { 17 | min-width: 0; 18 | width: 1em; 19 | text-align: center; 20 | background: transparent; 21 | border: 2px solid transparent; 22 | margin: 0 1px; 23 | padding: 2px 0 1px; 24 | border-radius: 3px; 25 | line-height: 1; 26 | color: #FFF; 27 | transition: all .25s; 28 | font-size: 1.2rem; 29 | font-family: Courier New, Courier, monospace; 30 | 31 | &--boolean { 32 | position: absolute; 33 | opacity: 0; 34 | } 35 | 36 | &:focus { 37 | outline: 0; 38 | color: yellowgreen; 39 | border-color: currentColor; 40 | } 41 | 42 | &--string { 43 | width: 1em; 44 | color: lightseagreen; 45 | } 46 | 47 | &--number { 48 | width: 2em; 49 | color: gold; 50 | 51 | } 52 | 53 | &--largeArc, 54 | &--sweep, 55 | &--xRot, 56 | &--repeat, 57 | &--strokeWidth { 58 | width: 1em; 59 | } 60 | 61 | &--error, 62 | &--error:focus { 63 | color: orangered; 64 | border-color: currentColor; 65 | } 66 | } 67 | 68 | &__label { 69 | position: relative; 70 | display: flex; 71 | align-items: center; 72 | font: 16px/1 Arial, sans-serif; 73 | color: yellowgreen; 74 | transition: all .25s; 75 | order: -1; 76 | 77 | &--hidden { 78 | position: absolute; 79 | top: -1.3em; 80 | left: 50%; 81 | transform: translateX(-50%); 82 | opacity: 0; 83 | } 84 | 85 | &--line { 86 | margin-right: .5em; 87 | } 88 | } 89 | 90 | &__input--boolean:disabled ~ &__label { 91 | color: #333; 92 | } 93 | 94 | &__input--boolean ~ &__label::before { 95 | content: ''; 96 | display: block; 97 | width: 1em; 98 | height: 1em; 99 | margin-right: .5em; 100 | border-radius: 3px; 101 | border: 2px solid #222; 102 | } 103 | 104 | &__input--boolean ~ &__label::after { 105 | content: ''; 106 | display: block; 107 | position: absolute; 108 | top: 0; 109 | left: 0; 110 | width: 1em; 111 | height: 1em; 112 | margin: 2px; 113 | background-position: center center; 114 | background-repeat: no-repeat; 115 | } 116 | 117 | &__input--boolean:checked ~ &__label::after { 118 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 102 63'%3E%3Cpolyline fill='none' stroke='yellowgreen' stroke-width='15' points='1.938 20.461 37.484 60.129 100.637 2.238'/%3E%3C/svg%3E%0A"); 119 | } 120 | &__input--boolean:checked:disabled ~ &__label::after { 121 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 102 63'%3E%3Cpolyline fill='none' stroke='%23222' stroke-width='15' points='1.938 20.461 37.484 60.129 100.637 2.238'/%3E%3C/svg%3E%0A"); 122 | } 123 | 124 | &__input--boolean:focus ~ &__label::before { 125 | border-color: yellowgreen; 126 | } 127 | 128 | &__input:focus ~ &__label { 129 | opacity: 1; 130 | } 131 | 132 | &__input--error ~ &__label { 133 | color: orangered; 134 | } 135 | 136 | &__error { 137 | position: absolute; 138 | bottom: -1.3em; 139 | left: 50%; 140 | opacity: 0; 141 | transform: translateX(-50%); 142 | font: 16px/1 Arial, sans-serif; 143 | color: orangered; 144 | } 145 | 146 | &__item--line &__error { 147 | top: -1.3em; 148 | bottom: auto; 149 | } 150 | 151 | &__input--error:focus ~ &__error { 152 | opacity: 1; 153 | } 154 | 155 | &__text { 156 | margin-right: 20px; 157 | font: 16px/1 Arial, sans-serif; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/scss/blocks/_code.scss: -------------------------------------------------------------------------------- 1 | .code { 2 | position: relative; 3 | margin-left: 20px; 4 | 5 | &__textarea { 6 | background: transparent; 7 | border: 3px solid transparent; 8 | border-radius: 5px; 9 | font: inherit; 10 | font-size: 14px; 11 | line-height: 1.3; 12 | font-family: Courier New, Courier, monospace; 13 | 14 | &:focus { 15 | outline: 0; 16 | border-color: yellowgreen; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/scss/blocks/_flags.scss: -------------------------------------------------------------------------------- 1 | .flags { 2 | position: relative; 3 | margin-top: 10px; 4 | min-height: 30px; 5 | 6 | @media (min-width: 700px) { 7 | margin-top: 0; 8 | } 9 | 10 | &__text { 11 | position: absolute; 12 | left: 0; 13 | bottom: -1.3em; 14 | font-size: 14px; 15 | line-height: 1; 16 | color: #333; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scss/blocks/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | @include container-width; 3 | 4 | display: flex; 5 | 6 | margin: 1rem auto; 7 | padding: .5rem; 8 | 9 | background: beige; 10 | border: 1px solid rgba(0,0,0,.1); 11 | 12 | } 13 | 14 | .footer__items { 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | 19 | width: 100%; 20 | 21 | @media ( max-width: $bp-tablet ) { 22 | flex-direction: column; 23 | } 24 | } 25 | 26 | .socials { 27 | 28 | } 29 | .socials__item { 30 | display: inline-block; 31 | margin: 0 .5rem; 32 | } 33 | -------------------------------------------------------------------------------- /src/scss/blocks/_github.scss: -------------------------------------------------------------------------------- 1 | .github-link { 2 | position: absolute; 3 | right: 1rem; 4 | top: 50%; 5 | transform: translate(0,-50%); 6 | color: teal; 7 | transition: all .25s; 8 | 9 | &:visited { 10 | color: #333; 11 | } 12 | 13 | &:hover { 14 | color: yellowgreen; 15 | } 16 | } 17 | 18 | .github-icon { 19 | display: block; 20 | width: 24px; 21 | height: 24px; 22 | } -------------------------------------------------------------------------------- /src/scss/blocks/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | @include container-width; 3 | 4 | margin: 1rem auto; 5 | padding: .5rem; 6 | 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | 11 | @media ( max-width: $bp-tablet ) { 12 | flex-direction: column; 13 | } 14 | 15 | background: khaki; 16 | border: 1px solid rgba(0,0,0,.1); 17 | } 18 | 19 | .nav { 20 | 21 | } 22 | .nav__item { 23 | display: inline-block; 24 | margin: 0 .5rem; 25 | } 26 | -------------------------------------------------------------------------------- /src/scss/blocks/_panel.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | position: relative; 3 | min-height: 60px; 4 | padding: 2rem 3rem; 5 | box-sizing: border-box; 6 | background: #000; 7 | 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: wrap; 11 | justify-content: center; 12 | align-items: center; 13 | 14 | font-size: 16px; 15 | font-family: Arial, sans-serif; 16 | color: #555; 17 | 18 | &--codefont { 19 | font-size: 1.2rem; 20 | font-family: Courier New, Courier, monospace; 21 | } 22 | 23 | &__inner { 24 | display: flex; 25 | flex-direction: row; 26 | align-items: baseline; 27 | white-space: nowrap; 28 | line-height: 2; 29 | } 30 | 31 | &__column { 32 | margin-bottom: 1em; 33 | display: flex; 34 | flex-direction: row; 35 | flex-basis: 50%; 36 | 37 | &--props { 38 | flex-wrap: wrap; 39 | 40 | @media (min-width: 700px) { 41 | flex-wrap: nowrap; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/scss/blocks/_popup.scss: -------------------------------------------------------------------------------- 1 | .popup { 2 | position: relative; 3 | margin-left: 20px; 4 | align-self: baseline; 5 | 6 | &__toggle { 7 | height: 30px; 8 | padding: 0 10px; 9 | background: transparent; 10 | border: 2px solid #222; 11 | border-radius: 3px; 12 | line-height: 30px; 13 | font: 16px/1 Arial, sans-serif; 14 | color: yellowgreen; 15 | 16 | &:focus { 17 | outline: 0; 18 | border-color: currentColor; 19 | } 20 | } 21 | 22 | &__container { 23 | position: absolute; 24 | z-index: 20; 25 | bottom: 45px; 26 | left: 50%; 27 | transform: translateX(-50%); 28 | height: 0; 29 | min-height: 0; 30 | overflow: hidden; 31 | display: flex; 32 | border-radius: 5px; 33 | transition: all .4s; 34 | } 35 | 36 | &:last-child &__container { 37 | @media (max-width: 1000px) { 38 | left: auto; 39 | right: 0; 40 | transform: none; 41 | } 42 | } 43 | 44 | &--opened &__container { 45 | height: 300px; 46 | box-shadow: 0 0 10px rgba(0,0,0,.5), 47 | 0 0 15px rgba(0,0,0,.5); 48 | } 49 | 50 | &__content { 51 | width: 350px; 52 | display: flex; 53 | padding: 10px; 54 | background: rgba(255,255,255,.9); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/scss/blocks/_svg.scss: -------------------------------------------------------------------------------- 1 | .svg { 2 | position: relative; 3 | z-index: 10; 4 | width: 100%; 5 | flex-shrink: 0; 6 | flex-grow: 1; 7 | margin: auto; 8 | overflow: visible !important; 9 | box-sizing: border-box; 10 | border: 1px solid transparent; 11 | border-color: transparent #DDD #DDD transparent; 12 | background: 13 | linear-gradient(to bottom, #DDD 1px, transparent 0), 14 | linear-gradient(to right, #DDD 1px, transparent 0); 15 | background-size: 50px 50px; 16 | } 17 | 18 | .point-control { 19 | stroke: #000; 20 | stroke-width: 3; 21 | fill: greenyellow; 22 | cursor: pointer; 23 | transition: fill .25s; 24 | 25 | &:hover { 26 | fill: orangered; 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/scss/blocks/_wave-types.scss: -------------------------------------------------------------------------------- 1 | .wave-types { 2 | 3 | &__items { 4 | display: flex; 5 | flex-direction: row; 6 | flex-wrap: wrap; 7 | } 8 | 9 | &__item { 10 | margin: 3px; 11 | flex-basis: 40%; 12 | flex-grow: 1; 13 | } 14 | 15 | &__button { 16 | width: 100%; 17 | height: 60px; 18 | padding: 3px 10px; 19 | display: flex; 20 | background: transparent; 21 | border: 2px solid #999; 22 | border-radius: 3px; 23 | cursor: pointer; 24 | 25 | svg { 26 | width: 100%; 27 | height: 100%; 28 | } 29 | 30 | &:focus { 31 | outline: 0; 32 | color: yellowgreen; 33 | border-color: currentColor; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import '_vars'; 2 | @import '_mixins'; 3 | 4 | @import '_normalize'; 5 | @import '_base'; 6 | @import 'blocks/_github'; 7 | @import 'blocks/_svg'; 8 | 9 | @import 'blocks/_header'; 10 | @import 'blocks/_footer'; 11 | 12 | @import 'blocks/_panel'; 13 | @import 'blocks/_attrs'; 14 | @import 'blocks/_code'; 15 | @import 'blocks/_popup'; 16 | @import 'blocks/_wave-types'; 17 | @import 'blocks/_flags'; 18 | 19 | --------------------------------------------------------------------------------