├── .babelrc ├── .eslintrc ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug.md │ ├── feature.md │ └── support.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .sass-lint.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── css │ └── app.css ├── fonts │ └── text-for-mobile.4d297d.svg ├── images │ ├── dragndrop-icon.svg │ ├── favicons │ │ ├── favico-128.png │ │ ├── favico-152.png │ │ ├── favico-16.png │ │ ├── favico-167.png │ │ ├── favico-180.png │ │ ├── favico-192.png │ │ ├── favico-196.png │ │ └── favico-32.png │ ├── text-for-mobile.svg │ └── web-preview.jpg └── js │ └── app.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── images │ ├── dragndrop-icon.svg │ ├── favicons │ │ ├── favico-128.png │ │ ├── favico-152.png │ │ ├── favico-16.png │ │ ├── favico-167.png │ │ ├── favico-180.png │ │ ├── favico-192.png │ │ ├── favico-196.png │ │ └── favico-32.png │ ├── text-for-mobile.svg │ └── web-preview.jpg ├── index.html ├── js │ ├── chunks │ │ ├── changeTheme.js │ │ ├── downloadAsFile.js │ │ ├── dropzone.js │ │ ├── generateMeshPoints.js │ │ ├── isTouch.js │ │ ├── iterpritateSmoothness.js │ │ ├── loader.js │ │ ├── moveCanvas.js │ │ ├── saveResults.js │ │ ├── svg-test-string.js │ │ └── toggleControls.js │ └── index.js └── scss │ ├── _base.scss │ ├── _controls.scss │ ├── _dragndrop-zone.scss │ ├── _if-mobile.scss │ ├── _loader.scss │ ├── _mixins.scss │ ├── _svg.scss │ ├── _variables.scss │ ├── normalize.css │ └── styles.scss └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true, 5 | "jasmine": true, 6 | "jquery": true 7 | }, 8 | "rules": { 9 | "no-use-before-define": 0, 10 | "func-names": 0, 11 | "prefer-arrow-callback": 0, 12 | "no-var": 0, 13 | "max-len": 0, 14 | "guard-for-in": 0, 15 | "object-shorthand": 0, 16 | "no-restricted-syntax": 0, 17 | "prefer-template": 0, 18 | "import/no-amd": 0, 19 | "space-before-function-paren": 0, 20 | "jsx-a11y/href-no-hash": "off", 21 | "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }], 22 | "import/no-unresolved": 0, 23 | "import/extensions": 0 24 | }, 25 | "globals": { 26 | "browser": false, 27 | "window": true, 28 | "document": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at we@athlonproduction.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Athlon Frontend Webpack Boilerplate 2 | 3 | Thank you for contributing to this template! 4 | ========================================= 5 | 6 | ## Language 7 | 8 | English is the best language to communicate. 9 | 10 | ## Templates 11 | 12 | Please use issue/PR templates which are inserted automatically. 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Security Bug Bounty 4 | url: https://weareathlon.com/ 5 | about: Please report security vulnerabilities here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | ***What is the motivation / use case for changing the behavior?** 12 | 13 | Describe an example use case of this feature. 14 | 15 | **Describe the solution you'd like** 16 | 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | **Additional context** 24 | 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Describe an issue using this repository code 4 | 5 | --- 6 | 7 | **Is your support request related to a problem? Please describe.** 8 | 9 | A clear and concise description of what the problem is. 10 | 11 | 12 | **Describe alternatives you've considered** 13 | 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | 18 | Add any other context or screenshots about the support request here. 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | 3 | - [ ] The commit message follows our guidelines 4 | - [ ] Tests for the changes have been added (for bug fixes / features) 5 | - [ ] Docs have been added / updated (for bug fixes / features) 6 | 7 | * **What kind of change does this PR introduce?** 8 | 9 | (_Bug fix, feature, docs update, ..._) 10 | 11 | * **What is the current behavior?** 12 | 13 | (_You can also link to an open issue here_) 14 | 15 | * **What is the new behavior (_if this is a feature change_)?** 16 | 17 | (_Add description here_) 18 | 19 | * **Does this PR introduce a breaking change?** 20 | 21 | (_What changes might users need to make in their existing projects due to this PR?_) 22 | 23 | * **Other information**: 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Mac OS X 4 | .DS_Store 5 | ._.* 6 | ._* 7 | 8 | # Ignore local editor 9 | .project 10 | .settings 11 | .idea 12 | *.swp 13 | tags 14 | nbproject/* 15 | 16 | # Windows 17 | Thumbs.db 18 | 19 | npm-debug.log 20 | 21 | node_modules/ 22 | -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | files: 2 | # formatter: stylish 3 | # include: '**/*.s+(a|c)ss' 4 | ignore: 5 | - '/usr/local/lib/node_modules/**/*.scss' 6 | - './node_modules/**/*.scss' 7 | - 'src/scss/vendor/**/*.scss' 8 | - 'src/scss/base/_reset.scss' 9 | - 'src/scss/base/_print.scss' 10 | - 'src/scss/base/_fonts.scss' 11 | - 'src/scss/base/mixins.scss' 12 | - 'src/scss/dirty/_hacks.scss' 13 | - 'node_modules/**/*.s+(a|c)ss' 14 | - 'node_modules/grunt-sass/node_modules/node-sass/test/fixtures/watching/index.sass' 15 | 16 | rules: 17 | # Extends 18 | extends-before-mixins: 2 19 | extends-before-declarations: 2 20 | placeholder-in-extend: 2 21 | 22 | # Mixins 23 | mixins-before-declarations: 24 | - 2 25 | - 26 | exclude: ['bp'] 27 | 28 | # Line Spacing 29 | one-declaration-per-line: 2 30 | empty-line-between-blocks: 2 31 | single-line-per-selector: 2 32 | 33 | # Disallows 34 | no-attribute-selectors: 0 35 | no-color-keywords: 2 36 | no-color-literals: 2 37 | no-combinators: 0 38 | no-css-comments: 0 39 | no-debug: 2 40 | no-disallowed-properties: 0 41 | no-duplicate-properties: 2 42 | no-empty-rulesets: 2 43 | no-extends: 1 44 | no-ids: 2 45 | no-important: 0 46 | no-invalid-hex: 2 47 | no-mergeable-selectors: 2 48 | no-misspelled-properties: 2 49 | no-qualifying-elements: 50 | - 2 51 | - 52 | allow-element-with-attribute: true 53 | 54 | no-trailing-whitespace: 2 55 | no-trailing-zero: 2 56 | no-transition-all: 0 57 | no-universal-selectors: 2 58 | no-url-protocols: 1 59 | no-vendor-prefixes: 2 60 | no-warn: 2 61 | property-units: 0 62 | 63 | # Nesting 64 | force-attribute-nesting: 0 65 | force-element-nesting: 0 66 | force-pseudo-nesting: 0 67 | 68 | # Name Formats 69 | class-name-format: 2 70 | function-name-format: 2 71 | id-name-format: 2 72 | mixin-name-format: 2 73 | placeholder-name-format: 2 74 | variable-name-format: 2 75 | 76 | # Style Guide 77 | attribute-quotes: 2 78 | bem-depth: 0 79 | border-zero: 80 | - 2 81 | - 82 | convention: 'none' 83 | 84 | brace-style: 2 85 | clean-import-paths: 2 86 | empty-args: 0 87 | hex-length: 2 88 | hex-notation: 2 89 | indentation: 2 90 | leading-zero: 2 91 | nesting-depth: 0 92 | property-sort-order: 0 93 | pseudo-element: 2 94 | quotes: 2 95 | shorthand-values: 2 96 | url-quotes: 2 97 | variable-for-property: 0 98 | zero-unit: 2 99 | 100 | # Inner Spacing 101 | space-after-comma: 2 102 | space-before-colon: 2 103 | space-after-colon: 2 104 | space-before-brace: 2 105 | space-before-bang: 2 106 | space-after-bang: 2 107 | space-between-parens: 2 108 | space-around-operator: 2 109 | 110 | # Final Items 111 | trailing-semicolon: 2 112 | final-newline: 2 113 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - lts/* 5 | install: 6 | - npm ci 7 | script: 8 | - npm run lint-sass 9 | - npm run lint-js 10 | - npm run production 11 | deploy: 12 | provider: pages 13 | skip-cleanup: true 14 | github-token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable 15 | keep-history: true 16 | local-dir: dist 17 | on: 18 | branch: master 19 | node: 'lts/*' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Athlon DPS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warp SVG Online 2 | 3 | ![Repo Cover](https://raw.githubusercontent.com/PavelLaptev/warp-svg/master/src/images/web-preview.jpg) 4 | 5 | ## How to Use 6 | 7 | A simple online tool that allows you to warp 🌀, bend, and distort SVG files. 8 | 9 | ### [🎥 WARP SVG In action](https://pavellaptev.github.io/warp-svg/) 10 | 11 | You can use Figma online tool to prepare your SVG file. 12 | 13 | ### [📖 Tutorial](https://www.figma.com/file/RqhYd0CaFD2f9dvz0m360Z/Warp-SVG-Online?node-id=1%3A2) 14 | 15 | ### REFS 16 | Warp code based on [Warp.js](https://github.com/benjamminf/warpjs) and took from [this issue](https://github.com/benjamminf/warpjs/issues/11) 17 | -------------------------------------------------------------------------------- /dist/css/app.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&display=swap); 2 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 3 | /* Document 4 | ========================================================================== */ 5 | /** 6 | * 1. Correct the line height in all browsers. 7 | * 2. Prevent adjustments of font size after orientation changes in iOS. 8 | */ 9 | html { 10 | line-height: 1.15; 11 | /* 1 */ 12 | -webkit-text-size-adjust: 100%; 13 | /* 2 */ } 14 | 15 | /* Sections 16 | ========================================================================== */ 17 | /** 18 | * Remove the margin in all browsers. 19 | */ 20 | body { 21 | margin: 0; } 22 | 23 | /** 24 | * Render the `main` element consistently in IE. 25 | */ 26 | main { 27 | display: block; } 28 | 29 | /** 30 | * Correct the font size and margin on `h1` elements within `section` and 31 | * `article` contexts in Chrome, Firefox, and Safari. 32 | */ 33 | h1 { 34 | font-size: 2em; 35 | margin: 0.67em 0; } 36 | 37 | /* Grouping content 38 | ========================================================================== */ 39 | /** 40 | * 1. Add the correct box sizing in Firefox. 41 | * 2. Show the overflow in Edge and IE. 42 | */ 43 | hr { 44 | box-sizing: content-box; 45 | /* 1 */ 46 | height: 0; 47 | /* 1 */ 48 | overflow: visible; 49 | /* 2 */ } 50 | 51 | /** 52 | * 1. Correct the inheritance and scaling of font size in all browsers. 53 | * 2. Correct the odd `em` font sizing in all browsers. 54 | */ 55 | pre { 56 | font-family: monospace, monospace; 57 | /* 1 */ 58 | font-size: 1em; 59 | /* 2 */ } 60 | 61 | /* Text-level semantics 62 | ========================================================================== */ 63 | /** 64 | * Remove the gray background on active links in IE 10. 65 | */ 66 | a { 67 | background-color: transparent; } 68 | 69 | /** 70 | * 1. Remove the bottom border in Chrome 57- 71 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 72 | */ 73 | abbr[title] { 74 | border-bottom: none; 75 | /* 1 */ 76 | text-decoration: underline; 77 | /* 2 */ 78 | -webkit-text-decoration: underline dotted; 79 | text-decoration: underline dotted; 80 | /* 2 */ } 81 | 82 | /** 83 | * Add the correct font weight in Chrome, Edge, and Safari. 84 | */ 85 | b, 86 | strong { 87 | font-weight: bolder; } 88 | 89 | /** 90 | * 1. Correct the inheritance and scaling of font size in all browsers. 91 | * 2. Correct the odd `em` font sizing in all browsers. 92 | */ 93 | code, 94 | kbd, 95 | samp { 96 | font-family: monospace, monospace; 97 | /* 1 */ 98 | font-size: 1em; 99 | /* 2 */ } 100 | 101 | /** 102 | * Add the correct font size in all browsers. 103 | */ 104 | small { 105 | font-size: 80%; } 106 | 107 | /** 108 | * Prevent `sub` and `sup` elements from affecting the line height in 109 | * all browsers. 110 | */ 111 | sub, 112 | sup { 113 | font-size: 75%; 114 | line-height: 0; 115 | position: relative; 116 | vertical-align: baseline; } 117 | 118 | sub { 119 | bottom: -0.25em; } 120 | 121 | sup { 122 | top: -0.5em; } 123 | 124 | /* Embedded content 125 | ========================================================================== */ 126 | /** 127 | * Remove the border on images inside links in IE 10. 128 | */ 129 | img { 130 | border-style: none; } 131 | 132 | /* Forms 133 | ========================================================================== */ 134 | /** 135 | * 1. Change the font styles in all browsers. 136 | * 2. Remove the margin in Firefox and Safari. 137 | */ 138 | button, 139 | input, 140 | optgroup, 141 | select, 142 | textarea { 143 | font-family: inherit; 144 | /* 1 */ 145 | font-size: 100%; 146 | /* 1 */ 147 | line-height: 1.15; 148 | /* 1 */ 149 | margin: 0; 150 | /* 2 */ } 151 | 152 | /** 153 | * Show the overflow in IE. 154 | * 1. Show the overflow in Edge. 155 | */ 156 | button, 157 | input { 158 | /* 1 */ 159 | overflow: visible; } 160 | 161 | /** 162 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 163 | * 1. Remove the inheritance of text transform in Firefox. 164 | */ 165 | button, 166 | select { 167 | /* 1 */ 168 | text-transform: none; } 169 | 170 | /** 171 | * Correct the inability to style clickable types in iOS and Safari. 172 | */ 173 | button, 174 | [type="button"], 175 | [type="reset"], 176 | [type="submit"] { 177 | -webkit-appearance: button; } 178 | 179 | /** 180 | * Remove the inner border and padding in Firefox. 181 | */ 182 | button::-moz-focus-inner, 183 | [type="button"]::-moz-focus-inner, 184 | [type="reset"]::-moz-focus-inner, 185 | [type="submit"]::-moz-focus-inner { 186 | border-style: none; 187 | padding: 0; } 188 | 189 | /** 190 | * Restore the focus styles unset by the previous rule. 191 | */ 192 | button:-moz-focusring, 193 | [type="button"]:-moz-focusring, 194 | [type="reset"]:-moz-focusring, 195 | [type="submit"]:-moz-focusring { 196 | outline: 1px dotted ButtonText; } 197 | 198 | /** 199 | * Correct the padding in Firefox. 200 | */ 201 | fieldset { 202 | padding: 0.35em 0.75em 0.625em; } 203 | 204 | /** 205 | * 1. Correct the text wrapping in Edge and IE. 206 | * 2. Correct the color inheritance from `fieldset` elements in IE. 207 | * 3. Remove the padding so developers are not caught out when they zero out 208 | * `fieldset` elements in all browsers. 209 | */ 210 | legend { 211 | box-sizing: border-box; 212 | /* 1 */ 213 | color: inherit; 214 | /* 2 */ 215 | display: table; 216 | /* 1 */ 217 | max-width: 100%; 218 | /* 1 */ 219 | padding: 0; 220 | /* 3 */ 221 | white-space: normal; 222 | /* 1 */ } 223 | 224 | /** 225 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 226 | */ 227 | progress { 228 | vertical-align: baseline; } 229 | 230 | /** 231 | * Remove the default vertical scrollbar in IE 10+. 232 | */ 233 | textarea { 234 | overflow: auto; } 235 | 236 | /** 237 | * 1. Add the correct box sizing in IE 10. 238 | * 2. Remove the padding in IE 10. 239 | */ 240 | [type="checkbox"], 241 | [type="radio"] { 242 | box-sizing: border-box; 243 | /* 1 */ 244 | padding: 0; 245 | /* 2 */ } 246 | 247 | /** 248 | * Correct the cursor style of increment and decrement buttons in Chrome. 249 | */ 250 | [type="number"]::-webkit-inner-spin-button, 251 | [type="number"]::-webkit-outer-spin-button { 252 | height: auto; } 253 | 254 | /** 255 | * 1. Correct the odd appearance in Chrome and Safari. 256 | * 2. Correct the outline style in Safari. 257 | */ 258 | [type="search"] { 259 | -webkit-appearance: textfield; 260 | /* 1 */ 261 | outline-offset: -2px; 262 | /* 2 */ } 263 | 264 | /** 265 | * Remove the inner padding in Chrome and Safari on macOS. 266 | */ 267 | [type="search"]::-webkit-search-decoration { 268 | -webkit-appearance: none; } 269 | 270 | /** 271 | * 1. Correct the inability to style clickable types in iOS and Safari. 272 | * 2. Change font properties to `inherit` in Safari. 273 | */ 274 | ::-webkit-file-upload-button { 275 | -webkit-appearance: button; 276 | /* 1 */ 277 | font: inherit; 278 | /* 2 */ } 279 | 280 | /* Interactive 281 | ========================================================================== */ 282 | /* 283 | * Add the correct display in Edge, IE 10+, and Firefox. 284 | */ 285 | details { 286 | display: block; } 287 | 288 | /* 289 | * Add the correct display in all browsers. 290 | */ 291 | summary { 292 | display: list-item; } 293 | 294 | /* Misc 295 | ========================================================================== */ 296 | /** 297 | * Add the correct display in IE 10+. 298 | */ 299 | template { 300 | display: none; } 301 | 302 | /** 303 | * Add the correct display in IE 10. 304 | */ 305 | [hidden] { 306 | display: none; } 307 | 308 | :root { 309 | --border-width: 2px; 310 | --main-clr: #35ffce; 311 | --secondary-clr: #111; 312 | --background-clr: white; 313 | --text-clr-light: white; 314 | --text-clr-dark: #111; 315 | --btn-border-clr: var(--main-clr); 316 | --active-drag-clr: #f45; 317 | --checker-rect: rgba(0, 0, 0, 0.1); } 318 | 319 | .dark-theme { 320 | --main-clr: #111; 321 | --secondary-clr: #35ffce; 322 | --background-clr: rgb(34, 34, 34); 323 | --text-clr-light: #111; 324 | --text-clr-dark: white; 325 | --btn-border-clr: white; 326 | --checker-rect: rgba(255, 255, 255, 0.1); } 327 | 328 | *, 329 | *:before, 330 | *:after { 331 | box-sizing: border-box; 332 | -webkit-font-smoothing: antialiased; 333 | -moz-osx-font-smoothing: grayscale; 334 | text-rendering: optimizeLegibility; } 335 | 336 | *::-moz-selection { 337 | color: var(--secondary-clr); 338 | background: var(--main-clr); } 339 | 340 | *::selection { 341 | color: var(--secondary-clr); 342 | background: var(--main-clr); } 343 | 344 | body { 345 | overflow: hidden; } 346 | 347 | hr { 348 | border: none; 349 | border-top: 1px dashed var(--text-clr-light); 350 | opacity: 0.4; 351 | margin: 24px 0 10px; } 352 | 353 | #svg-element { 354 | overflow: visible; 355 | width: 100%; 356 | height: 100%; } 357 | 358 | #svg-container { 359 | position: relative; 360 | background-color: var(--background-clr); } 361 | 362 | #svg-control { 363 | width: 100%; 364 | height: 100%; 365 | overflow: visible; 366 | position: absolute; } 367 | #svg-control:hover #control-path { 368 | stroke: var(--active-drag-clr); 369 | opacity: 0.6; } 370 | #svg-control:hover .control-point { 371 | opacity: 1; 372 | fill: var(--active-drag-clr); } 373 | #svg-control.show { 374 | outline: 1px solid var(--checker-rect); } 375 | 376 | #scale-wrap { 377 | margin: auto; } 378 | 379 | #control-path { 380 | fill: none; 381 | stroke: var(--text-clr-dark); 382 | opacity: 0.15; } 383 | 384 | .control-point { 385 | opacity: 1; 386 | fill: var(--secondary-clr); } 387 | 388 | .dropzone-preview { 389 | z-index: 99999; 390 | position: absolute; 391 | pointer-events: none; 392 | width: 100%; 393 | height: 100%; 394 | opacity: 0; 395 | transition: all 0.2s ease; } 396 | .dropzone-preview .info { 397 | z-index: 99999; 398 | padding: 100px; 399 | border: var(--text-clr-dark) solid var(--border-width); 400 | transform: translate(-50%, -50%); 401 | background-color: var(--text-clr-light); 402 | position: absolute; 403 | top: 50%; 404 | left: 50%; 405 | display: flex; 406 | flex-direction: column; 407 | align-items: center; } 408 | .dropzone-preview .overlay { 409 | background: var(--main-clr); 410 | width: 100%; 411 | height: 100%; 412 | opacity: 0.8; } 413 | .dropzone-preview .caption { 414 | color: var(--text-clr-dark); 415 | line-height: 1.3; } 416 | .dropzone-preview .icon { 417 | display: inline-block; 418 | width: 230px; 419 | height: 176px; 420 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMyIiBoZWlnaHQ9IjE3OCIgdmlld0JveD0iMCAwIDIzMiAxNzgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjEiIHk9IjEiIHdpZHRoPSIyMzAiIGhlaWdodD0iMTQyIiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtZGFzaGFycmF5PSI0IDQiLz4KPHJlY3QgeD0iMTE4IiB5PSI3MCIgd2lkdGg9Ijc2IiBoZWlnaHQ9Ijg3IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxyZWN0IHg9IjExMiIgeT0iODciIHdpZHRoPSI2NyIgaGVpZ2h0PSIzNyIgZmlsbD0iIzUzRkZDMSIvPgo8cGF0aCBkPSJNMTI5Ljk2IDExMy4yNzZDMTI4LjYxMSAxMTMuMjc2IDEyNy40MzggMTEzLjA1NCAxMjYuNDQxIDExMi42MDlDMTI1LjQ0NSAxMTIuMTY0IDEyNC42NCAxMTEuNTU5IDEyNC4wMjYgMTEwLjc5MkwxMjYuMDI3IDEwOC41ODRDMTI2LjYxIDEwOS4yMjggMTI3LjI0NiAxMDkuNzAzIDEyNy45MzYgMTEwLjAxQzEyOC42NDIgMTEwLjMxNyAxMjkuMzU1IDExMC40NyAxMzAuMDc1IDExMC40N0MxMzAuOTAzIDExMC40NyAxMzEuNTMyIDExMC4yODYgMTMxLjk2MSAxMDkuOTE4QzEzMi4zOTEgMTA5LjU1IDEzMi42MDUgMTA5LjAyMSAxMzIuNjA1IDEwOC4zMzFDMTMyLjYwNSAxMDcuNzY0IDEzMi40MzcgMTA3LjMzNCAxMzIuMDk5IDEwNy4wNDNDMTMxLjc3NyAxMDYuNzUyIDEzMS4yMjUgMTA2LjU0NSAxMzAuNDQzIDEwNi40MjJMMTI4Ljc2NCAxMDYuMTQ2QzEyNy4zMjMgMTA1LjkwMSAxMjYuMjggMTA1LjM3MiAxMjUuNjM2IDEwNC41NTlDMTI0Ljk5MiAxMDMuNzMxIDEyNC42NyAxMDIuNzI3IDEyNC42NyAxMDEuNTQ2QzEyNC42NyA5OS45OTczIDEyNS4xNzYgOTguODAxMyAxMjYuMTg4IDk3Ljk1OEMxMjcuMiA5Ny4wOTkzIDEyOC42MTkgOTYuNjcgMTMwLjQ0MyA5Ni42N0MxMzEuNjg1IDk2LjY3IDEzMi43NjYgOTYuODYxNyAxMzMuNjg2IDk3LjI0NUMxMzQuNjA2IDk3LjYyODMgMTM1LjM1IDk4LjE0OTcgMTM1LjkxNyA5OC44MDlMMTMzLjk2MiAxMDAuOTk0QzEzMy41MzMgMTAwLjUxOSAxMzMuMDI3IDEwMC4xNTEgMTMyLjQ0NCA5OS44OUMxMzEuODYyIDk5LjYxNCAxMzEuMjAyIDk5LjQ3NiAxMzAuNDY2IDk5LjQ3NkMxMjguOTE4IDk5LjQ3NiAxMjguMTQzIDEwMC4xMTIgMTI4LjE0MyAxMDEuMzg1QzEyOC4xNDMgMTAxLjkzNyAxMjguMzEyIDEwMi4zNTEgMTI4LjY0OSAxMDIuNjI3QzEyOS4wMDIgMTAyLjkwMyAxMjkuNTY5IDEwMy4xMSAxMzAuMzUxIDEwMy4yNDhMMTMyLjAzIDEwMy41NDdDMTMzLjMzNCAxMDMuNzc3IDEzNC4zMzggMTA0LjI2OCAxMzUuMDQzIDEwNS4wMTlDMTM1Ljc0OSAxMDUuNzcgMTM2LjEwMSAxMDYuNzgyIDEzNi4xMDEgMTA4LjA1NUMxMzYuMTAxIDEwOC44MDYgMTM1Ljk3MSAxMDkuNTA0IDEzNS43MSAxMTAuMTQ4QzEzNS40NSAxMTAuNzc3IDEzNS4wNTkgMTExLjMyOSAxMzQuNTM3IDExMS44MDRDMTM0LjAxNiAxMTIuMjY0IDEzMy4zNzIgMTEyLjYyNCAxMzIuNjA1IDExMi44ODVDMTMxLjg1NCAxMTMuMTQ2IDEzMC45NzIgMTEzLjI3NiAxMjkuOTYgMTEzLjI3NlpNMTQxLjU2NiAxMTNMMTM3LjMxMSA5Ni45NDZIMTQxLjE1MkwxNDMuMDM4IDEwNS4xMTFMMTQzLjk4MSAxMDkuNzM0SDE0NC4xNjVMMTQ1LjEzMSAxMDUuMTExTDE0Ny4wNCA5Ni45NDZIMTUwLjY5N0wxNDYuNDQyIDExM0gxNDEuNTY2Wk0xNjAuMzQ5IDExMC43MjNIMTYwLjE0MkMxNjAuMDA0IDExMS4wNiAxNTkuODM1IDExMS4zODIgMTU5LjYzNiAxMTEuNjg5QzE1OS40MzYgMTExLjk5NiAxNTkuMTkxIDExMi4yNzIgMTU4LjkgMTEyLjUxN0MxNTguNjA4IDExMi43NDcgMTU4LjI2MyAxMTIuOTMxIDE1Ny44NjUgMTEzLjA2OUMxNTcuNDgxIDExMy4yMDcgMTU3LjA0NCAxMTMuMjc2IDE1Ni41NTQgMTEzLjI3NkMxNTUuNzcyIDExMy4yNzYgMTU1LjA3NCAxMTMuMDkyIDE1NC40NjEgMTEyLjcyNEMxNTMuODYzIDExMi4zNTYgMTUzLjM1NyAxMTEuODE5IDE1Mi45NDMgMTExLjExNEMxNTIuNTI5IDExMC40MDkgMTUyLjIxNCAxMDkuNTUgMTUyIDEwOC41MzhDMTUxLjggMTA3LjUyNiAxNTEuNzAxIDEwNi4zODQgMTUxLjcwMSAxMDUuMTExQzE1MS43MDEgMTAyLjM4MiAxNTIuMTk5IDEwMC4yOTYgMTUzLjE5NiA5OC44NTVDMTU0LjE5MiA5Ny4zOTgzIDE1NS43MzMgOTYuNjcgMTU3LjgxOSA5Ni42N0MxNTguNjQ3IDk2LjY3IDE1OS4zNzUgOTYuNzg1IDE2MC4wMDQgOTcuMDE1QzE2MC42MzIgOTcuMjI5NyAxNjEuMTY5IDk3LjU0NCAxNjEuNjE0IDk3Ljk1OEMxNjIuMDc0IDk4LjM1NjcgMTYyLjQ0OSA5OC44Mzk3IDE2Mi43NDEgOTkuNDA3QzE2My4wMzIgOTkuOTc0MyAxNjMuMjYyIDEwMC42MDMgMTYzLjQzMSAxMDEuMjkzTDE2MC4yMTEgMTAyLjA3NUMxNjAuMTE5IDEwMS43MzggMTYwLjAxMSAxMDEuNDE2IDE1OS44ODkgMTAxLjEwOUMxNTkuNzgxIDEwMC43ODcgMTU5LjYzNiAxMDAuNTAzIDE1OS40NTIgMTAwLjI1OEMxNTkuMjY4IDEwMC4wMTMgMTU5LjAzOCA5OS44MjEgMTU4Ljc2MiA5OS42ODNDMTU4LjQ4NiA5OS41Mjk3IDE1OC4xNDggOTkuNDUzIDE1Ny43NSA5OS40NTNDMTU2Ljg3NiA5OS40NTMgMTU2LjI1NSA5OS44MTMzIDE1NS44ODcgMTAwLjUzNEMxNTUuNTM0IDEwMS4yNTUgMTU1LjM1OCAxMDIuMjY3IDE1NS4zNTggMTAzLjU3VjEwNi4xOTJDMTU1LjM1OCAxMDcuNDk1IDE1NS41NDkgMTA4LjUzOCAxNTUuOTMzIDEwOS4zMkMxNTYuMzMxIDExMC4xMDIgMTU3LjAyOSAxMTAuNDkzIDE1OC4wMjYgMTEwLjQ5M0MxNTguODA4IDExMC40OTMgMTU5LjM5IDExMC4yNjMgMTU5Ljc3NCAxMDkuODAzQzE2MC4xNTcgMTA5LjM0MyAxNjAuMzQ5IDEwOC43NTMgMTYwLjM0OSAxMDguMDMyVjEwNi44NTlIMTU3LjYxMlYxMDQuNDIxSDE2My40MzFWMTEzSDE2MC4zNDlWMTEwLjcyM1oiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik0yMTIuNTUzIDE3Ni4xOTdIMTg2LjExOFYxNzAuMzk1TDE3My4zMjYgMTU5LjEzN0MxNzEuMzc3IDE1Ny40MjIgMTcwLjg3OSAxNTQuNTYyIDE3Mi4xMDEgMTUyLjI3MkMxNzMuNjk2IDE0OS4yODEgMTc3LjYwNSAxNDguMzc0IDE4MC4zMTYgMTUwLjQwOEwxNzkuODcyIDE0MC4yMDRDMTc5Ljc2IDEzNy42MzIgMTgxLjcyMiAxMzUuNDQyIDE4NC4yOSAxMzUuMjcxQzE4Ny4wMjQgMTM1LjA4OSAxODkuMzQyIDEzNy4yNTcgMTg5LjM0MiAxMzkuOTk4VjEzOC4xNThDMTg5LjM0MiAxMzUuMzA5IDE5MS42NTEgMTMzIDE5NC41IDEzM0MxOTcuMzQ5IDEzMyAxOTkuNjU4IDEzNS4zMDkgMTk5LjY1OCAxMzguMTU4VjEzOC44MDNDMTk5LjY1OCAxMzUuOTU0IDIwMS45NjcgMTMzLjY0NSAyMDQuODE2IDEzMy42NDVDMjA3LjY2NCAxMzMuNjQ1IDIwOS45NzQgMTM1Ljk1NCAyMDkuOTc0IDEzOC44MDNWMTQxLjM4MkMyMDkuOTc0IDEzOC44ODkgMjExLjk5NCAxMzYuODY4IDIxNC40ODcgMTM2Ljg2OEMyMTYuOTc5IDEzNi44NjggMjE5IDEzOC44ODkgMjE5IDE0MS4zODJWMTYwLjU4MkMyMTkgMTYzLjc4MyAyMTcuMDUxIDE2Ni42NjEgMjE0LjA4IDE2Ny44NUMyMTMuMTU3IDE2OC4yMTkgMjEyLjU1MyAxNjkuMTEyIDIxMi41NTMgMTcwLjEwNVYxNzYuMTk3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE4Ni4xMTggMTc2LjE5N0gyMTIuNTUzVjE3MC4xMDVDMjEyLjU1MyAxNjkuMTEyIDIxMy4xNTcgMTY4LjIxOSAyMTQuMDggMTY3Ljg1VjE2Ny44NUMyMTcuMDUxIDE2Ni42NjEgMjE5IDE2My43ODMgMjE5IDE2MC41ODJWMTQxLjM4MkMyMTkgMTM4Ljg4OSAyMTYuOTc5IDEzNi44NjggMjE0LjQ4NyAxMzYuODY4VjEzNi44NjhDMjExLjk5NCAxMzYuODY4IDIwOS45NzQgMTM4Ljg4OSAyMDkuOTc0IDE0MS4zODJWMTQzLjk2MVYxMzguODAzQzIwOS45NzQgMTM1Ljk1NCAyMDcuNjY0IDEzMy42NDUgMjA0LjgxNiAxMzMuNjQ1VjEzMy42NDVDMjAxLjk2NyAxMzMuNjQ1IDE5OS42NTggMTM1Ljk1NCAxOTkuNjU4IDEzOC44MDNWMTQyLjY3MVYxMzguMTU4QzE5OS42NTggMTM1LjMwOSAxOTcuMzQ5IDEzMyAxOTQuNSAxMzNWMTMzQzE5MS42NTEgMTMzIDE4OS4zNDIgMTM1LjMwOSAxODkuMzQyIDEzOC4xNThWMTQyLjY3MVYxMzkuOTk4QzE4OS4zNDIgMTM3LjI1NyAxODcuMDI0IDEzNS4wODkgMTg0LjI5IDEzNS4yNzFWMTM1LjI3MUMxODEuNzIyIDEzNS40NDIgMTc5Ljc2IDEzNy42MzIgMTc5Ljg3MiAxNDAuMjA0TDE4MC4zMTYgMTUwLjQwOFYxNTAuNDA4QzE3Ny42MDUgMTQ4LjM3NCAxNzMuNjk2IDE0OS4yODEgMTcyLjEwMSAxNTIuMjcyVjE1Mi4yNzJDMTcwLjg3OSAxNTQuNTYyIDE3MS4zNzcgMTU3LjQyMiAxNzMuMzI2IDE1OS4xMzdMMTg2LjExOCAxNzAuMzk1VjE3Ni4xOTdaIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE5MS45MjEgMTUxLjA1M1YxNjAuMDc5IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE5OS42NTggMTUxLjA1M1YxNjAuMDc5IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTIwNy4zOTUgMTUxLjA1M1YxNjAuMDc5IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==); 421 | background-size: cover; 422 | background-repeat: no-repeat; } 423 | .dropzone-preview.show { 424 | opacity: 1; } 425 | 426 | .actions { 427 | z-index: 9999; 428 | position: absolute; 429 | bottom: 20px; 430 | left: 20px; 431 | display: flex; 432 | flex-direction: column; 433 | max-width: 380px; } 434 | .actions a { 435 | color: var(--main-clr); } 436 | 437 | .content-block { 438 | cursor: default; 439 | background: var(--secondary-clr); 440 | padding: 24px; 441 | margin-bottom: 8px; 442 | color: var(--text-clr-light); 443 | font-size: 14px; 444 | overflow: hidden; 445 | position: relative; } 446 | .content-block ul { 447 | padding-left: 24px; 448 | margin: 0; 449 | list-style: none; } 450 | .content-block ul li:before { 451 | position: absolute; 452 | content: "\2022"; 453 | color: var(--main-clr); 454 | font-weight: bold; 455 | display: inline-block; 456 | width: 1em; 457 | margin-left: -24px; } 458 | .content-block p { 459 | margin-top: 8px; 460 | margin-bottom: 16px; 461 | line-height: 1.4; } 462 | .content-block h3 { 463 | font-size: 14px; 464 | opacity: 0.4; 465 | margin-top: 0; 466 | margin-bottom: 20px; 467 | text-transform: uppercase; } 468 | .content-block.hide { 469 | opacity: 0; 470 | height: 0; 471 | padding: 0 24px; 472 | margin-bottom: 0; } 473 | 474 | .action-buttons { 475 | display: flex; } 476 | 477 | .close-btn { 478 | cursor: pointer; 479 | width: 30px; 480 | height: 30px; 481 | position: absolute; 482 | right: 12px; 483 | top: 8px; 484 | opacity: 0.4; 485 | transition: all 0.1s ease; } 486 | .close-btn:after { 487 | pointer-events: none; 488 | content: ""; 489 | width: 16px; 490 | height: 3px; 491 | background: var(--text-clr-light); 492 | display: inline-block; 493 | position: absolute; 494 | top: 50%; 495 | left: 50%; 496 | transform: translate(-50%, -50%); } 497 | .close-btn:hover { 498 | opacity: 1; } 499 | 500 | #show-settings { 501 | margin-right: 8px; } 502 | 503 | #show-info { 504 | text-transform: lowercase; 505 | margin-right: 8px; } 506 | 507 | #save-result-btn { 508 | margin-right: 8px; } 509 | 510 | #load-svg-btn { 511 | margin-right: 8px; } 512 | 513 | #load-svg-input { 514 | display: none; } 515 | 516 | .range-selector { 517 | margin-bottom: 32px; } 518 | .range-selector input { 519 | width: 100%; 520 | margin-top: 20px; } 521 | .range-selector input[type="range"] { 522 | width: 100%; 523 | background-color: transparent; 524 | -webkit-appearance: none; } 525 | .range-selector input[type="range"]:focus { 526 | outline: none; } 527 | .range-selector input[type="range"]::-webkit-slider-runnable-track { 528 | background: var(--main-clr); 529 | border: 0.2px solid rgba(0, 1, 1, 0); 530 | width: 100%; 531 | height: 4px; 532 | cursor: pointer; } 533 | .range-selector input[type="range"]::-webkit-slider-thumb { 534 | margin-top: -8px; 535 | width: 32px; 536 | height: 18px; 537 | background: #ffffff; 538 | border-radius: 0; 539 | border: var(--border-width) solid #000000; 540 | cursor: pointer; 541 | -webkit-appearance: none; } 542 | .range-selector input[type="range"]:focus::-webkit-slider-runnable-track { 543 | background: var(--main-clr); } 544 | .range-selector input[type="range"]::-moz-range-track { 545 | background: var(--main-clr); 546 | border: 0.2px solid rgba(0, 1, 1, 0); 547 | width: 100%; 548 | height: 4px; 549 | cursor: pointer; } 550 | .range-selector input[type="range"]::-moz-range-thumb { 551 | width: 32px; 552 | height: 18px; 553 | background: #ffffff; 554 | border-radius: 0; 555 | border: var(--border-width) solid #000000; 556 | cursor: pointer; } 557 | .range-selector input[type="range"]::-ms-track { 558 | background: transparent; 559 | border-color: transparent; 560 | border-width: 5px 0; 561 | color: transparent; 562 | width: 100%; 563 | height: 4px; 564 | cursor: pointer; } 565 | .range-selector input[type="range"]::-ms-fill-lower { 566 | background: var(--main-clr); 567 | border: 0.2px solid rgba(0, 1, 1, 0); } 568 | .range-selector input[type="range"]::-ms-fill-upper { 569 | background: var(--main-clr); 570 | border: 0.2px solid rgba(0, 1, 1, 0); } 571 | .range-selector input[type="range"]::-ms-thumb { 572 | width: 32px; 573 | height: 18px; 574 | background: #ffffff; 575 | border-radius: 0; 576 | border: var(--border-width) solid #000000; 577 | cursor: pointer; 578 | margin-top: 0px; } 579 | .range-selector input[type="range"]:focus::-ms-fill-lower { 580 | background: var(--main-clr); } 581 | .range-selector input[type="range"]:focus::-ms-fill-upper { 582 | background: var(--main-clr); } 583 | 584 | .checkbox-block { 585 | display: flex; 586 | flex-direction: column; 587 | padding: 0 0 10px; } 588 | .checkbox-block input[type="checkbox"] { 589 | display: none; } 590 | .checkbox-block input[type="checkbox"] + label { 591 | display: flex; 592 | align-items: center; 593 | position: relative; 594 | cursor: pointer; 595 | padding: 0; } 596 | .checkbox-block input[type="checkbox"] + label:before { 597 | content: ""; 598 | position: relative; 599 | margin-right: 18px; 600 | display: inline-block; 601 | width: 24px; 602 | height: 24px; 603 | background: white; 604 | border: var(--border-width) solid var(--text-clr-light); } 605 | .checkbox-block input[type="checkbox"]:checked + label:before { 606 | background: var(--main-clr); } 607 | .checkbox-block input[type="checkbox"]:checked + label:after { 608 | content: ""; 609 | width: 20px; 610 | height: var(--border-width); 611 | background: var(--secondary-clr); 612 | display: inline-block; 613 | position: absolute; 614 | bottom: 11px; 615 | left: 2px; 616 | transform: rotate(-45deg); } 617 | 618 | .switcher { 619 | z-index: 999; 620 | position: absolute; 621 | top: 16px; 622 | right: 16px; 623 | -webkit-user-select: none; 624 | -moz-user-select: none; 625 | -ms-user-select: none; 626 | user-select: none; } 627 | .switcher input[type="checkbox"] { 628 | display: none; } 629 | .switcher input[type="checkbox"] + label { 630 | text-transform: uppercase; 631 | display: flex; 632 | align-items: center; 633 | position: relative; 634 | cursor: pointer; 635 | padding: 0; 636 | color: var(--secondary-clr); } 637 | .switcher input[type="checkbox"] + label:after { 638 | content: ""; 639 | position: absolute; 640 | top: 50%; 641 | transform: translateY(-50%); 642 | left: -42px; 643 | display: inline-block; 644 | width: 14px; 645 | height: 20px; 646 | background: white; 647 | border: var(--border-width) solid var(--secondary-clr); 648 | transition: all 0.08s ease-in-out; } 649 | .switcher input[type="checkbox"] + label:before { 650 | content: ""; 651 | position: absolute; 652 | left: -42px; 653 | margin-right: 10px; 654 | display: inline-block; 655 | width: 32px; 656 | height: 20px; 657 | background: var(--main-clr); 658 | border: var(--border-width) solid var(--secondary-clr); } 659 | .switcher input[type="checkbox"]:checked + label:after { 660 | left: -24px; } 661 | 662 | .primary-btn { 663 | cursor: pointer; 664 | -webkit-user-select: none; 665 | -moz-user-select: none; 666 | -ms-user-select: none; 667 | user-select: none; 668 | font-family: "IBM Plex Mono", monospace; 669 | font-size: 15px; 670 | text-transform: uppercase; 671 | padding: 0 16px; 672 | height: 54px; 673 | background: white; 674 | color: var(--secondary-clr); 675 | transition: all 0.05s ease-in-out; 676 | border: var(--border-width) solid var(--secondary-clr); 677 | color: #111; } 678 | .primary-btn:focus { 679 | outline: none; } 680 | .primary-btn:hover { 681 | background: var(--secondary-clr) !important; 682 | color: var(--main-clr) !important; } 683 | .primary-btn:hover svg { 684 | fill: var(--main-clr) !important; } 685 | .primary-btn.active { 686 | background: var(--main-clr); 687 | border: var(--border-width) solid var(--main-clr); 688 | color: var(--text-clr-dark); } 689 | .primary-btn.active:hover { 690 | background: var(--main-clr) !important; 691 | border: var(--border-width) solid var(--text-clr-dark) !important; 692 | color: var(--secondary-clr) !important; } 693 | 694 | .secondary-btn { 695 | cursor: pointer; 696 | -webkit-user-select: none; 697 | -moz-user-select: none; 698 | -ms-user-select: none; 699 | user-select: none; 700 | font-family: "IBM Plex Mono", monospace; 701 | font-size: 15px; 702 | text-transform: uppercase; 703 | padding: 0 16px; 704 | height: 54px; 705 | background: white; 706 | color: var(--secondary-clr); 707 | transition: all 0.05s ease-in-out; 708 | border: var(--border-width) solid var(--main-clr); 709 | color: #111; } 710 | .secondary-btn:focus { 711 | outline: none; } 712 | .secondary-btn:hover { 713 | background: var(--secondary-clr) !important; 714 | color: var(--main-clr) !important; } 715 | .secondary-btn:hover svg { 716 | fill: var(--main-clr) !important; } 717 | .secondary-btn.active { 718 | background: var(--main-clr); 719 | border: var(--border-width) solid var(--main-clr); 720 | color: var(--text-clr-dark); } 721 | .secondary-btn.active > svg { 722 | fill: var(--text-clr-dark); } 723 | .secondary-btn.active:hover { 724 | background: var(--main-clr) !important; 725 | border: var(--border-width) solid var(--text-clr-dark) !important; 726 | color: var(--secondary-clr) !important; } 727 | .secondary-btn.active:hover > svg { 728 | fill: var(--text-clr-dark) !important; } 729 | 730 | #loader { 731 | z-index: 999999; 732 | position: absolute; 733 | width: 100vw; 734 | height: 100vh; 735 | background: white; 736 | display: flex; } 737 | 738 | #loader-spinner { 739 | display: inline-block; 740 | width: 60px; 741 | height: 60px; 742 | background-color: var(--secondary-clr); 743 | margin: auto; 744 | -webkit-animation-name: transform-animation; 745 | animation-name: transform-animation; 746 | -webkit-animation-duration: 0.5s; 747 | animation-duration: 0.5s; 748 | -webkit-animation-iteration-count: infinite; 749 | animation-iteration-count: infinite; 750 | -webkit-animation-direction: alternate; 751 | animation-direction: alternate; } 752 | 753 | @-webkit-keyframes transform-animation { 754 | from { 755 | background-color: var(--secondary-clr); 756 | transform: scale(0.1) rotate(0deg); } 757 | to { 758 | background-color: var(--main-clr); 759 | transform: scale(0.9) rotate(45deg); 760 | border-radius: 100%; } } 761 | 762 | @keyframes transform-animation { 763 | from { 764 | background-color: var(--secondary-clr); 765 | transform: scale(0.1) rotate(0deg); } 766 | to { 767 | background-color: var(--main-clr); 768 | transform: scale(0.9) rotate(45deg); 769 | border-radius: 100%; } } 770 | 771 | #if-mobile { 772 | position: absolute; 773 | top: 50%; 774 | left: 50%; 775 | transform: translate(-50%, -50%); 776 | z-index: 99999; 777 | width: 90%; 778 | height: 90%; 779 | background: white; 780 | border: 8px solid var(--main-clr); } 781 | #if-mobile .icon { 782 | width: 100%; 783 | height: 100%; 784 | background-size: 60%; 785 | background-position: center; 786 | background-image: url(../fonts/text-for-mobile.4d297d.svg); } 787 | 788 | #app { 789 | display: flex; 790 | flex-direction: column; 791 | height: 100vh; 792 | width: 100%; 793 | font-family: "IBM Plex Mono", monospace; 794 | background-color: var(--background-clr); } 795 | 796 | .links { 797 | display: flex; 798 | flex-wrap: wrap; 799 | padding: 16px 0 8px; } 800 | 801 | .social-media { 802 | display: inline-block; 803 | margin-right: 20px; 804 | display: flex; 805 | align-items: center; 806 | text-decoration: none; } 807 | .social-media span { 808 | color: var(--main-clr); 809 | margin: 0 8px; 810 | font-size: 14px; 811 | transition: all 0.1s ease; } 812 | .social-media svg { 813 | fill: var(--main-clr); 814 | transform: scale(0.8); 815 | transition: all 0.1s ease; } 816 | .social-media:hover span { 817 | color: white; } 818 | .social-media:hover svg { 819 | fill: white; } 820 | 821 | .checkerboard-pattern { 822 | background-image: linear-gradient(45deg, var(--checker-rect) 25%, transparent 25%), linear-gradient(-45deg, var(--checker-rect) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--checker-rect) 75%), linear-gradient(-45deg, transparent 75%, var(--checker-rect) 75%) !important; 823 | background-size: 20px 20px; 824 | background-position: 0 0, 0 10px, 10px -10px, -10px 0px; } 825 | 826 | -------------------------------------------------------------------------------- /dist/fonts/text-for-mobile.4d297d.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/images/dragndrop-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/images/favicons/favico-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-128.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-152.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-16.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-167.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-180.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-192.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-196.png -------------------------------------------------------------------------------- /dist/images/favicons/favico-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/favicons/favico-32.png -------------------------------------------------------------------------------- /dist/images/text-for-mobile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /dist/images/web-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/dist/images/web-preview.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | 19 | Warp SVG Online 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 | 43 |
44 |
45 |
46 |
47 |

Drop SVG file here

48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 |
56 | 57 |
58 |
59 |
60 |

How To Use:

61 |
    62 |
  • 63 |

    Use this tutorial to avoid artifacts

    66 |
  • 67 |
  • 68 |

    Drag'n'Drop to upload your SVG File

    69 |
  • 70 |
  • 71 |

    Hold 'Space' and pan to move around the canvas

    72 |
  • 73 |
  • 74 |

    Scroll to zoom

    75 |
  • 76 |
77 |
78 | 101 |
102 | 103 |
104 |
105 |

Settings:

106 |
107 | 108 | 109 |
110 |
111 | 113 | 114 |
115 |
116 | 117 | 118 |
119 |
120 |
121 | 124 | 130 | 133 | 134 | 137 |
138 |
139 | 140 |
141 |
142 | 143 | 144 | 145 | 146 |
147 |
148 | 149 |
150 | 151 | 152 | 153 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "warp-svg-online", 3 | "version": "1.0.1", 4 | "description": "Warp and distort SVG online", 5 | "scripts": { 6 | "build": "webpack --mode=development", 7 | "watch": "webpack --mode=development --watch", 8 | "watch:externalServer": "webpack --mode=development --watch --externalServer", 9 | "bundle": "npm ci && npm run watch", 10 | "bundle:externalServer": "npm ci && npm run watch:externalServer", 11 | "production": "cross-env NODE_ENV=production webpack --mode=production", 12 | "lint-sass": "sass-lint -v -q --format=compact", 13 | "lint-js": "eslint --ext .js src/js/" 14 | }, 15 | "keywords": [ 16 | "webpack", 17 | "boilerplate", 18 | "template", 19 | "setup" 20 | ], 21 | "author": "Pavel Laptev", 22 | "license": "MIT", 23 | "engines": { 24 | "node": ">=10.13.0" 25 | }, 26 | "dependencies": { 27 | "autoprefixer": "^9.8.4", 28 | "calculate-aspect-ratio": "0.1.3", 29 | "gsap": "3.3.3", 30 | "warpjs": "1.0.8" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.10.3", 34 | "@babel/preset-env": "^7.10.3", 35 | "ajv": "^6.12.2", 36 | "babel-loader": "^8.1.0", 37 | "browser-sync": "^2.26.7", 38 | "browser-sync-webpack-plugin": "2.2.2", 39 | "clean-webpack-plugin": "^3.0.0", 40 | "copy-webpack-plugin": "^5.1.1", 41 | "cross-env": "^7.0.2", 42 | "css-loader": "^3.6.0", 43 | "cssnano": "^4.1.10", 44 | "eslint": "^6.8.0", 45 | "eslint-config-airbnb": "^18.2.0", 46 | "eslint-plugin-import": "^2.21.2", 47 | "eslint-plugin-jsx-a11y": "^6.3.1", 48 | "eslint-plugin-react": "^7.20.0", 49 | "eslint-plugin-react-hooks": "^1.7.0", 50 | "fibers": "^4.0.3", 51 | "file-loader": "^5.1.0", 52 | "html-webpack-plugin": "^3.2.0", 53 | "imagemin-webpack-plugin": "^2.4.2", 54 | "mini-css-extract-plugin": "^0.9.0", 55 | "node-sass": "^4.14.1", 56 | "optimize-css-assets-webpack-plugin": "^5.0.3", 57 | "postcss-loader": "^3.0.0", 58 | "sass": "^1.26.9", 59 | "sass-lint": "^1.13.1", 60 | "sass-loader": "^8.0.2", 61 | "style-loader": "^1.2.1", 62 | "terser-webpack-plugin": "^2.3.7", 63 | "url-loader": "^3.0.0", 64 | "webpack": "^4.43.0", 65 | "webpack-cli": "^3.3.12" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer'), 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /src/images/dragndrop-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/images/favicons/favico-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-128.png -------------------------------------------------------------------------------- /src/images/favicons/favico-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-152.png -------------------------------------------------------------------------------- /src/images/favicons/favico-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-16.png -------------------------------------------------------------------------------- /src/images/favicons/favico-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-167.png -------------------------------------------------------------------------------- /src/images/favicons/favico-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-180.png -------------------------------------------------------------------------------- /src/images/favicons/favico-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-192.png -------------------------------------------------------------------------------- /src/images/favicons/favico-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-196.png -------------------------------------------------------------------------------- /src/images/favicons/favico-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/favicons/favico-32.png -------------------------------------------------------------------------------- /src/images/text-for-mobile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/web-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PavelLaptev/warp-svg/056fb1cc736f2d9e29257602fcf4c37d300053e0/src/images/web-preview.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | 19 | Warp SVG Online 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 | 43 |
44 |
45 |
46 |
47 |

Drop SVG file here

48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 |
56 | 57 |
58 |
59 |
60 |

How To Use:

61 |
    62 |
  • 63 |

    Use this tutorial to avoid artifacts

    66 |
  • 67 |
  • 68 |

    Drag'n'Drop to upload your SVG File

    69 |
  • 70 |
  • 71 |

    Hold 'Space' and pan to move around the canvas

    72 |
  • 73 |
  • 74 |

    Scroll to zoom

    75 |
  • 76 |
77 |
78 | 101 |
102 | 103 |
104 |
105 |

Settings:

106 |
107 | 108 | 109 |
110 |
111 | 113 | 114 |
115 |
116 | 117 | 118 |
119 |
120 |
121 | 124 | 130 | 133 | 134 | 137 |
138 |
139 | 140 |
141 |
142 | 143 | 144 | 145 | 146 |
147 |
148 | 149 |
150 | 151 | 152 | 153 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /src/js/chunks/changeTheme.js: -------------------------------------------------------------------------------- 1 | const changeTheme = () => { 2 | const app = document.getElementById('app'); 3 | const swither = document.getElementById('switch-theme'); 4 | 5 | swither.addEventListener('change', (e) => { 6 | app.classList.toggle('dark-theme'); 7 | }); 8 | }; 9 | 10 | export default changeTheme; 11 | -------------------------------------------------------------------------------- /src/js/chunks/downloadAsFile.js: -------------------------------------------------------------------------------- 1 | const downloadAsFile = (content, filename, contentType) => { 2 | if (!contentType) contentType = 'application/octet-stream'; 3 | const a = document.createElement('a'); 4 | const blob = new Blob([content], { type: contentType }); 5 | a.href = window.URL.createObjectURL(blob); 6 | a.download = filename; 7 | a.click(); 8 | }; 9 | 10 | export default downloadAsFile; 11 | -------------------------------------------------------------------------------- /src/js/chunks/dropzone.js: -------------------------------------------------------------------------------- 1 | const dropZone = (callback) => { 2 | const loadBtn = document.getElementById('load-svg-btn'); 3 | const loadInput = document.getElementById('load-svg-input'); 4 | 5 | const toggleDropZone = (e, hideZonePreview) => { 6 | e.stopPropagation(); 7 | e.preventDefault(); 8 | dropZonePreview.classList.toggle('show'); 9 | // hideZonePreview(dropZonePreview); 10 | typeof hideZonePreview === 'function' && hideZonePreview(dropZonePreview); 11 | }; 12 | 13 | const dropEvent = (e, file, hideZonePreview) => { 14 | toggleDropZone(e, hideZonePreview); 15 | 16 | const fileReader = new FileReader(); 17 | fileReader.readAsText(file); 18 | 19 | // console.log(e.type); 20 | fileReader.onload = () => { 21 | try { 22 | callback(fileReader.result); 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | }; 27 | }; 28 | 29 | const dropZonePreview = document.getElementsByClassName( 30 | 'dropzone-preview', 31 | )[0]; 32 | 33 | const clickOnLoadInput = () => { 34 | loadInput.click(); 35 | }; 36 | 37 | window.addEventListener('dragenter', toggleDropZone); 38 | window.addEventListener('dragleave', toggleDropZone); 39 | window.addEventListener('dragover', (e) => e.preventDefault()); 40 | window.addEventListener('dragstart', (e) => e.preventDefault()); 41 | window.addEventListener('drageend', (e) => e.preventDefault()); 42 | window.addEventListener('drop', (e) => dropEvent(e, e.dataTransfer.files[0])); 43 | 44 | loadBtn.addEventListener('click', clickOnLoadInput); 45 | loadInput.addEventListener('change', (e) => dropEvent(e, e.target.files[0], (zone) => { zone.classList.toggle('show'); })); 46 | }; 47 | 48 | export default dropZone; 49 | -------------------------------------------------------------------------------- /src/js/chunks/generateMeshPoints.js: -------------------------------------------------------------------------------- 1 | const generateMeshPoints = (width, height, amount) => { 2 | const checkAndRoundNumber = (length, index) => (length / amount) * index; 3 | 4 | const myArray = [...Array(amount).keys()]; 5 | 6 | const myleft = myArray.map((item, i) => [0, checkAndRoundNumber(height, i)]); 7 | 8 | const myBottom = myArray.map((item, i) => [checkAndRoundNumber(width, i), height]); 9 | 10 | const myRight = myArray 11 | .map((item, i) => [width, checkAndRoundNumber(height, ++i)]) 12 | .reverse(); 13 | 14 | const myTop = [...Array(amount).keys()] 15 | .map((item, i) => [checkAndRoundNumber(width, ++i), 0]) 16 | .reverse(); 17 | 18 | return [...myleft, ...myBottom, ...myRight, ...myTop]; 19 | }; 20 | 21 | export default generateMeshPoints; 22 | -------------------------------------------------------------------------------- /src/js/chunks/isTouch.js: -------------------------------------------------------------------------------- 1 | const isTouch = () => { 2 | if ((('ontouchstart' in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) && window.screen.width <= 600) { 3 | console.log('touch event is true'); 4 | return true; 5 | } 6 | console.log('touch event is false'); 7 | return false; 8 | }; 9 | 10 | export default isTouch; 11 | -------------------------------------------------------------------------------- /src/js/chunks/iterpritateSmoothness.js: -------------------------------------------------------------------------------- 1 | const iterpritateSmoothness = (val) => { 2 | const newVal = Number(val); 3 | if (newVal === 0) { 4 | return 400; 5 | } 6 | if (newVal === 100) { 7 | return 200; 8 | } 9 | if (newVal === 200) { 10 | return 80; 11 | } 12 | if (newVal === 300) { 13 | return 30; 14 | } 15 | if (newVal === 400) { 16 | return 10; 17 | } 18 | return null; 19 | }; 20 | 21 | export default iterpritateSmoothness; 22 | -------------------------------------------------------------------------------- /src/js/chunks/loader.js: -------------------------------------------------------------------------------- 1 | const loader = () => { 2 | window.onload = function () { 3 | document.getElementById('app').style.opacity = 1; 4 | document.getElementById('loader').style.display = 'none'; 5 | }; 6 | }; 7 | 8 | export default loader; 9 | -------------------------------------------------------------------------------- /src/js/chunks/moveCanvas.js: -------------------------------------------------------------------------------- 1 | import Draggable from 'gsap/Draggable'; 2 | 3 | const moveCanvas = (movingElement) => { 4 | const canvasDrag = Draggable.create(movingElement, { 5 | trigger: document.body, 6 | cursor: 'auto', 7 | }); 8 | 9 | canvasDrag[0].disable(); 10 | 11 | // "SPACE" KEY PRESSED 12 | document.addEventListener('keydown', function (e) { 13 | if (e.code === 'Space') { 14 | canvasDrag[0].enable(); 15 | document.body.style.cursor = 'grab'; 16 | } 17 | }); 18 | 19 | document.addEventListener('keyup', function (e) { 20 | if (e.code === 'Space') { 21 | canvasDrag[0].disable(); 22 | document.body.style.cursor = 'default'; 23 | } 24 | }); 25 | }; 26 | 27 | export default moveCanvas; 28 | -------------------------------------------------------------------------------- /src/js/chunks/saveResults.js: -------------------------------------------------------------------------------- 1 | import downloadAsFile from './downloadAsFile'; 2 | 3 | const saveResult = (btn, content) => { 4 | btn.onclick = () => { 5 | downloadAsFile( 6 | new XMLSerializer().serializeToString(content), 7 | 'warped-svg.svg', 8 | ); 9 | }; 10 | }; 11 | 12 | export default saveResult; 13 | -------------------------------------------------------------------------------- /src/js/chunks/svg-test-string.js: -------------------------------------------------------------------------------- 1 | export const testSVG = 2 | ''; 3 | -------------------------------------------------------------------------------- /src/js/chunks/toggleControls.js: -------------------------------------------------------------------------------- 1 | const toggleControls = () => { 2 | const showInfoBtn = document.getElementById('show-info'); 3 | const infoBlock = document.getElementById('info-block'); 4 | const showSettingsBtn = document.getElementById('show-settings'); 5 | const settingsBlock = document.getElementById('settings-block'); 6 | 7 | showInfoBtn.addEventListener('click', (e) => { 8 | showInfoBtn.classList.toggle('active'); 9 | infoBlock.classList.toggle('hide'); 10 | }); 11 | 12 | showSettingsBtn.addEventListener('click', (e) => { 13 | showSettingsBtn.classList.toggle('active'); 14 | settingsBlock.classList.toggle('hide'); 15 | }); 16 | 17 | infoBlock 18 | .getElementsByClassName('close-btn')[0] 19 | .addEventListener('click', () => { 20 | showInfoBtn.classList.toggle('active'); 21 | infoBlock.classList.toggle('hide'); 22 | }); 23 | 24 | settingsBlock 25 | .getElementsByClassName('close-btn')[0] 26 | .addEventListener('click', () => { 27 | showSettingsBtn.classList.toggle('active'); 28 | settingsBlock.classList.toggle('hide'); 29 | }); 30 | }; 31 | 32 | export default toggleControls; 33 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | import "../scss/styles.scss"; 3 | import Warp from "warpjs"; 4 | import gsap from "gsap"; 5 | import Draggable from "gsap/Draggable"; 6 | 7 | import dropZone from "./chunks/dropzone"; 8 | import generateMeshPoints from "./chunks/generateMeshPoints"; 9 | import saveResult from "./chunks/saveResults"; 10 | import moveCanvas from "./chunks/moveCanvas"; 11 | import toggleControls from "./chunks/toggleControls"; 12 | import changeTheme from "./chunks/changeTheme"; 13 | import iterpritateSmoothness from "./chunks/iterpritateSmoothness"; 14 | import loader from "./chunks/loader"; 15 | 16 | import { testSVG } from "./chunks/svg-test-string"; 17 | 18 | /// ///////////////////////////////////////////////////////////////// 19 | /// /////////// Register GSAP Draggable and Loader ////////////////// 20 | /// ///////////////////////////////////////////////////////////////// 21 | loader(); 22 | gsap.registerPlugin(Draggable); 23 | 24 | /// ///////////////////////////////////////////////////////////////// 25 | /// ///////////////////// Initial Variables ///////////////////////// 26 | /// ///////////////////////////////////////////////////////////////// 27 | let svgString = testSVG; 28 | let zoom = 1; 29 | const draggableControlPonts = []; 30 | 31 | const app = document.getElementById("app"); 32 | const svgContainer = document.getElementById("svg-container"); 33 | const svgElement = document.getElementById("svg-element"); 34 | const svgControl = document.getElementById("svg-control"); 35 | const zoomElement = document.getElementById("scale-wrap"); 36 | 37 | const actions = { 38 | meshComplexity: document.getElementById("mesh-complexity"), 39 | meshInterpolation: document.getElementById("interpolation-complexity"), 40 | showOriginalBox: document.getElementById("show-original-box-btn"), 41 | }; 42 | 43 | let width = svgContainer.clientWidth; 44 | let height = svgContainer.clientHeight; 45 | let complexityLevel = actions.meshComplexity.value; 46 | let interpolationLevel = iterpritateSmoothness(actions.meshInterpolation.value); 47 | 48 | /// ///////////////////////////////////////////////////////////////// 49 | /// ///////////////////// Parse SVG String ////////////////////////// 50 | /// ///////////////////////////////////////////////////////////////// 51 | function parseSVGString(svgString) { 52 | const svgDOM = new DOMParser() 53 | .parseFromString(svgString, "image/svg+xml") 54 | .getElementsByTagName("svg")[0]; 55 | 56 | const parsedSVGWidth = svgDOM.attributes.width 57 | ? Number(svgDOM.attributes.width.value.replace(/^.*?(\d+).*/, "$1")) 58 | : 500; 59 | const parsedSVGheight = svgDOM.attributes.height 60 | ? Number(svgDOM.attributes.height.value.replace(/^.*?(\d+).*/, "$1")) 61 | : 500; 62 | 63 | height = Math.round(parsedSVGheight); 64 | width = Math.round(parsedSVGWidth); 65 | svgContainer.style.height = `${Math.round(height)}px`; 66 | svgContainer.style.width = `${Math.round(width)}px`; 67 | 68 | svgDOM.attributes.viewBox 69 | ? svgElement.setAttribute("viewBox", svgDOM.attributes.viewBox.value) 70 | : false; 71 | svgDOM.attributes.fill 72 | ? svgElement.setAttribute("fill", svgDOM.attributes.fill.value) 73 | : svgElement.setAttribute("fill", "inherit"); 74 | 75 | svgElement.setAttribute("preserveAspectRatio", "xMidYMin meet"); 76 | svgElement.innerHTML = svgDOM.innerHTML.toString(); 77 | } 78 | 79 | /// ///////////////////////////////////////////////////////////////// 80 | /// ///////////////////// Initial function ////////////////////////// 81 | /// ///////////////////////////////////////////////////////////////// 82 | function init(firstInit = false) { 83 | const controlPath = document.getElementById("control-path"); 84 | parseSVGString(svgString); 85 | zoomElement.style.transform = "scale(1)"; 86 | zoom = 1; 87 | 88 | // Need to interpolate first, so angles remain sharp 89 | const warp = new Warp(svgElement); 90 | warp.interpolate(interpolationLevel); 91 | 92 | // Start with a rectangle, then distort it later 93 | let controlPoints = generateMeshPoints( 94 | width, 95 | height, 96 | Number(complexityLevel) 97 | ); 98 | 99 | // Compute weights from control points 100 | warp.transform(function (v0, V = controlPoints) { 101 | const A = []; 102 | const W = []; 103 | const L = []; 104 | 105 | // Find angles 106 | for (let i = 0; i < V.length; i++) { 107 | const j = (i + 1) % V.length; 108 | 109 | const vi = V[i]; 110 | const vj = V[j]; 111 | 112 | const r0i = Math.sqrt((v0[0] - vi[0]) ** 2 + (v0[1] - vi[1]) ** 2); 113 | const r0j = Math.sqrt((v0[0] - vj[0]) ** 2 + (v0[1] - vj[1]) ** 2); 114 | const rij = Math.sqrt((vi[0] - vj[0]) ** 2 + (vi[1] - vj[1]) ** 2); 115 | 116 | const dn = 2 * r0i * r0j; 117 | const r = (r0i ** 2 + r0j ** 2 - rij ** 2) / dn; 118 | 119 | A[i] = isNaN(r) ? 0 : Math.acos(Math.max(-1, Math.min(r, 1))); 120 | } 121 | 122 | // Find weights 123 | for (let j = 0; j < V.length; j++) { 124 | const i = (j > 0 ? j : V.length) - 1; 125 | 126 | // const vi = V[i]; 127 | const vj = V[j]; 128 | 129 | const r = Math.sqrt((vj[0] - v0[0]) ** 2 + (vj[1] - v0[1]) ** 2); 130 | 131 | W[j] = (Math.tan(A[i] / 2) + Math.tan(A[j] / 2)) / r; 132 | } 133 | 134 | // Normalise weights 135 | const Ws = W.reduce((a, b) => a + b, 0); 136 | for (let i = 0; i < V.length; i++) { 137 | L[i] = W[i] / Ws; 138 | } 139 | 140 | // Save weights to the point for use when transforming 141 | return [...v0, ...L]; 142 | }); 143 | 144 | // Warp function 145 | function reposition([x, y, ...W], V = controlPoints) { 146 | let nx = 0; 147 | let ny = 0; 148 | 149 | // Recreate the points using mean value coordinates 150 | for (let i = 0; i < V.length; i++) { 151 | nx += W[i] * V[i][0]; 152 | ny += W[i] * V[i][1]; 153 | } 154 | 155 | return [nx, ny, ...W]; 156 | } 157 | 158 | // Draw control shape 159 | function drawControlShape(element = controlPath, V = controlPoints) { 160 | const path = [`M${V[0][0]} ${V[0][1]}`]; 161 | 162 | for (let i = 1; i < V.length; i++) { 163 | path.push(`L${V[i][0]} ${V[i][1]}`); 164 | } 165 | 166 | path.push("Z"); 167 | element.setAttribute("d", path.join("")); 168 | } 169 | 170 | // Draw control point 171 | function drawPoint(element, pos = { x: 0, y: 0 }, index) { 172 | const point = document.createElementNS( 173 | "http://www.w3.org/2000/svg", 174 | "circle" 175 | ); 176 | point.setAttributeNS(null, "class", "control-point"); 177 | point.setAttributeNS(null, "cx", pos.x); 178 | point.setAttributeNS(null, "cy", pos.y); 179 | point.setAttributeNS(null, "r", 6); 180 | element.appendChild(point); 181 | 182 | draggableControlPonts.push(point); 183 | 184 | Draggable.create(point, { 185 | type: "x,y", 186 | onDrag: function () { 187 | const relativeX = 188 | (this.pointerX - svgControl.getBoundingClientRect().left) / zoom; 189 | const relativeY = 190 | (this.pointerY - svgControl.getBoundingClientRect().top) / zoom; 191 | 192 | controlPoints[index] = [relativeX, relativeY]; 193 | drawControlShape(); 194 | warp.transform(reposition); 195 | }, 196 | }); 197 | } 198 | 199 | // Place control points 200 | function drawControlPoints(element = svgControl, V = controlPoints) { 201 | V.map((i, index) => { 202 | drawPoint(element, { x: i[0], y: i[1] }, index); 203 | return null; 204 | }); 205 | } 206 | 207 | // if this is the first launch 208 | if (firstInit) { 209 | controlPoints = [ 210 | [-70, -5], 211 | [-2, 136], 212 | [-90, 200], 213 | [20, 380], 214 | [150, 260], 215 | [400, 400], 216 | [490, 250], 217 | [400, 90], 218 | [260, 6], 219 | [470, 80], 220 | [360, -40], 221 | [80, -90], 222 | ]; 223 | } 224 | drawControlShape(); 225 | drawControlPoints(); 226 | warp.transform(reposition); 227 | } 228 | 229 | /// ////// 230 | const createNewControlPath = () => { 231 | svgControl.innerHTML = ""; 232 | const newControlPath = document.createElementNS( 233 | "http://www.w3.org/2000/svg", 234 | "path" 235 | ); 236 | newControlPath.setAttributeNS(null, "id", "control-path"); 237 | svgControl.appendChild(newControlPath); 238 | }; 239 | 240 | /// ////// 241 | dropZone((result) => { 242 | svgString = result; 243 | createNewControlPath(); 244 | init(); 245 | }); 246 | 247 | /// //// 248 | document.addEventListener("wheel", function (e) { 249 | const controlPath = document.getElementById("control-path"); 250 | if (e.deltaY > 0) { 251 | zoomElement.style.transform = `scale(${(zoom += 0.02)})`; 252 | controlPath.style.strokeWidth = `${1 / zoom}px`; 253 | // console.log(svgControl.querySelectorAll('circle')); 254 | } else if (zoomElement.getBoundingClientRect().width >= 30) { 255 | zoomElement.style.transform = `scale(${(zoom -= 0.02)})`; 256 | controlPath.style.strokeWidth = `${1 / zoom}px`; 257 | } 258 | draggableControlPonts.map((i) => { 259 | if (i.getBoundingClientRect().height > 6) { 260 | i.setAttribute("r", 6 / zoom); 261 | } 262 | }); 263 | }); 264 | 265 | /// ////// 266 | actions.meshComplexity.addEventListener( 267 | "change", 268 | (e) => { 269 | complexityLevel = e.target.value; 270 | createNewControlPath(); 271 | init(); 272 | }, 273 | false 274 | ); 275 | 276 | /// ///// 277 | actions.showOriginalBox.addEventListener( 278 | "change", 279 | () => { 280 | svgControl.classList.toggle("show"); 281 | app.classList.toggle("checkerboard-pattern"); 282 | }, 283 | false 284 | ); 285 | 286 | // ///// 287 | actions.meshInterpolation.addEventListener( 288 | "change", 289 | (e) => { 290 | interpolationLevel = iterpritateSmoothness(e.target.value); 291 | createNewControlPath(); 292 | init(); 293 | }, 294 | false 295 | ); 296 | 297 | // Initial calling 298 | changeTheme(); 299 | moveCanvas(svgContainer); 300 | saveResult(document.getElementById("save-result-btn"), svgElement); 301 | toggleControls(); 302 | init(true); 303 | console.log(` 304 | ▄ ▄ 305 | ▌▒█ ▄▀▒▌ 306 | ▌▒▒█ ▄▀▒▒▒▐ 307 | ▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐ 308 | ▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐ 309 | ▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌ 310 | ▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌ 311 | ▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐ 312 | ▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌ 313 | ▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌ 314 | ▌▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒▐ 315 | ▐▒▒▐▀▐▀▒░▄▄▒▄▒▒▒▒▒▒░▒░▒░▒▒▒▒▌ 316 | ▐▒▒▒▀▀▄▄▒▒▒▄▒▒▒▒▒▒▒▒░▒░▒░▒▒▐ 317 | ▌▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒░▒░▒░▒░▒▒▒▌ 318 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▒▄▒▒▐ 319 | ▀▄▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▄▒▒▒▒▌ 320 | ▀▄▒▒▒▒▒▒▒▒▒▒▄▄▄▀▒▒▒▒▄▀ 321 | ▀▄▄▄▄▄▄▀▀▀▒▒▒▒▒▄▄▀ 322 | ▒▒▒▒▒▒▒▒▒▒▀▀ 323 | 324 | ░░░░░░░ █░█ ▄▀█ █░░ █░░ █▀█ ░  ░░░░░░░ 325 | ░░░░░░░ █▀█ █▀█ █▄▄ █▄▄ █▄█ █  ░░░░░░░ 326 | `); 327 | -------------------------------------------------------------------------------- /src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | text-rendering: optimizeLegibility; 8 | } 9 | 10 | *::selection { 11 | color: var(--secondary-clr); 12 | background: var(--main-clr); 13 | } 14 | 15 | body { 16 | overflow: hidden; 17 | } 18 | 19 | hr { 20 | border: none; 21 | border-top: 1px dashed var(--text-clr-light); 22 | opacity: 0.4; 23 | margin: 24px 0 10px; 24 | } 25 | -------------------------------------------------------------------------------- /src/scss/_controls.scss: -------------------------------------------------------------------------------- 1 | // 2 | .actions { 3 | // user-select: none; 4 | z-index: 9999; 5 | position: absolute; 6 | bottom: 20px; 7 | left: 20px; 8 | display: flex; 9 | flex-direction: column; 10 | max-width: 380px; 11 | a { 12 | color: var(--main-clr); 13 | } 14 | } 15 | 16 | .content-block { 17 | @include content-block; 18 | } 19 | 20 | .action-buttons { 21 | display: flex; 22 | } 23 | 24 | .close-btn { 25 | cursor: pointer; 26 | width: 30px; 27 | height: 30px; 28 | position: absolute; 29 | right: 12px; 30 | top: 8px; 31 | opacity: 0.4; 32 | transition: all 0.1s ease; 33 | &:after { 34 | pointer-events: none; 35 | content: ""; 36 | width: 16px; 37 | height: 3px; 38 | background: var(--text-clr-light); 39 | display: inline-block; 40 | position: absolute; 41 | top: 50%; 42 | left: 50%; 43 | transform: translate(-50%, -50%); 44 | } 45 | &:hover { 46 | opacity: 1; 47 | } 48 | } 49 | 50 | #show-settings { 51 | margin-right: 8px; 52 | } 53 | 54 | #show-info { 55 | text-transform: lowercase; 56 | margin-right: 8px; 57 | } 58 | 59 | #save-result-btn { 60 | margin-right: 8px; 61 | } 62 | 63 | #load-svg-btn { 64 | margin-right: 8px; 65 | } 66 | 67 | #load-svg-input { 68 | display: none; 69 | } 70 | 71 | .range-selector { 72 | margin-bottom: 32px; 73 | input { 74 | width: 100%; 75 | margin-top: 20px; 76 | } 77 | input[type="range"] { 78 | width: 100%; 79 | background-color: transparent; 80 | -webkit-appearance: none; 81 | } 82 | input[type="range"]:focus { 83 | outline: none; 84 | } 85 | input[type="range"]::-webkit-slider-runnable-track { 86 | background: var(--main-clr); 87 | border: 0.2px solid rgba(0, 1, 1, 0); 88 | width: 100%; 89 | height: 4px; 90 | cursor: pointer; 91 | } 92 | input[type="range"]::-webkit-slider-thumb { 93 | margin-top: -8px; 94 | width: 32px; 95 | height: 18px; 96 | background: #ffffff; 97 | border-radius: 0; 98 | border: var(--border-width) solid #000000; 99 | cursor: pointer; 100 | -webkit-appearance: none; 101 | } 102 | input[type="range"]:focus::-webkit-slider-runnable-track { 103 | background: var(--main-clr); 104 | } 105 | input[type="range"]::-moz-range-track { 106 | background: var(--main-clr); 107 | border: 0.2px solid rgba(0, 1, 1, 0); 108 | width: 100%; 109 | height: 4px; 110 | cursor: pointer; 111 | } 112 | input[type="range"]::-moz-range-thumb { 113 | width: 32px; 114 | height: 18px; 115 | background: #ffffff; 116 | border-radius: 0; 117 | border: var(--border-width) solid #000000; 118 | cursor: pointer; 119 | } 120 | input[type="range"]::-ms-track { 121 | background: transparent; 122 | border-color: transparent; 123 | border-width: 5px 0; 124 | color: transparent; 125 | width: 100%; 126 | height: 4px; 127 | cursor: pointer; 128 | } 129 | input[type="range"]::-ms-fill-lower { 130 | background: var(--main-clr); 131 | border: 0.2px solid rgba(0, 1, 1, 0); 132 | } 133 | input[type="range"]::-ms-fill-upper { 134 | background: var(--main-clr); 135 | border: 0.2px solid rgba(0, 1, 1, 0); 136 | } 137 | input[type="range"]::-ms-thumb { 138 | width: 32px; 139 | height: 18px; 140 | background: #ffffff; 141 | border-radius: 0; 142 | border: var(--border-width) solid #000000; 143 | cursor: pointer; 144 | margin-top: 0px; 145 | } 146 | input[type="range"]:focus::-ms-fill-lower { 147 | background: var(--main-clr); 148 | } 149 | input[type="range"]:focus::-ms-fill-upper { 150 | background: var(--main-clr); 151 | } 152 | } 153 | 154 | .checkbox-block { 155 | display: flex; 156 | flex-direction: column; 157 | padding: 0 0 10px; 158 | input[type="checkbox"] { 159 | display: none; 160 | & + label { 161 | display: flex; 162 | align-items: center; 163 | position: relative; 164 | cursor: pointer; 165 | padding: 0; 166 | } 167 | & + label:before { 168 | content: ""; 169 | position: relative; 170 | // margin-top: 16px; 171 | margin-right: 18px; 172 | display: inline-block; 173 | width: 24px; 174 | height: 24px; 175 | background: white; 176 | border: var(--border-width) solid var(--text-clr-light); 177 | } 178 | } 179 | input[type="checkbox"]:checked { 180 | & + label { 181 | &:before { 182 | background: var(--main-clr); 183 | } 184 | &:after { 185 | content: ""; 186 | width: 20px; 187 | height: var(--border-width); 188 | background: var(--secondary-clr); 189 | display: inline-block; 190 | position: absolute; 191 | bottom: 11px; 192 | left: 2px; 193 | transform: rotate(-45deg); 194 | } 195 | } 196 | } 197 | } 198 | 199 | ////// 200 | .switcher { 201 | z-index: 999; 202 | position: absolute; 203 | top: 16px; 204 | right: 16px; 205 | user-select: none; 206 | input[type="checkbox"] { 207 | display: none; 208 | & + label { 209 | text-transform: uppercase; 210 | display: flex; 211 | align-items: center; 212 | position: relative; 213 | cursor: pointer; 214 | padding: 0; 215 | color: var(--secondary-clr); 216 | } 217 | & + label:after { 218 | content: ""; 219 | position: absolute; 220 | top: 50%; 221 | transform: translateY(-50%); 222 | left: -42px; 223 | display: inline-block; 224 | width: 14px; 225 | height: 20px; 226 | background: white; 227 | border: var(--border-width) solid var(--secondary-clr); 228 | transition: all 0.08s ease-in-out; 229 | } 230 | & + label:before { 231 | content: ""; 232 | position: absolute; 233 | left: -42px; 234 | margin-right: 10px; 235 | display: inline-block; 236 | width: 32px; 237 | height: 20px; 238 | background: var(--main-clr); 239 | border: var(--border-width) solid var(--secondary-clr); 240 | } 241 | } 242 | input[type="checkbox"]:checked { 243 | & + label { 244 | &:after { 245 | left: -24px; 246 | } 247 | } 248 | } 249 | } 250 | 251 | .primary-btn { 252 | @include btn-mixin(var(--secondary-clr), white, var(--secondary-clr)); 253 | border: var(--border-width) solid var(--secondary-clr); 254 | color: #111; 255 | &.active { 256 | background: var(--main-clr); 257 | border: var(--border-width) solid var(--main-clr); 258 | color: var(--text-clr-dark); 259 | &:hover { 260 | background: var(--main-clr) !important; 261 | border: var(--border-width) solid var(--text-clr-dark) !important; 262 | color: var(--secondary-clr) !important; 263 | } 264 | } 265 | } 266 | 267 | .secondary-btn { 268 | @include btn-mixin(var(--secondary-clr), white, var(--secondary-clr)); 269 | border: var(--border-width) solid var(--main-clr); 270 | color: #111; 271 | &.active { 272 | background: var(--main-clr); 273 | border: var(--border-width) solid var(--main-clr); 274 | color: var(--text-clr-dark); 275 | > svg { 276 | fill: var(--text-clr-dark); 277 | } 278 | &:hover { 279 | background: var(--main-clr) !important; 280 | border: var(--border-width) solid var(--text-clr-dark) !important; 281 | color: var(--secondary-clr) !important; 282 | > svg { 283 | fill: var(--text-clr-dark) !important; 284 | } 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/scss/_dragndrop-zone.scss: -------------------------------------------------------------------------------- 1 | .dropzone-preview { 2 | z-index: 99999; 3 | position: absolute; 4 | pointer-events: none; 5 | width: 100%; 6 | height: 100%; 7 | opacity: 0; 8 | transition: all 0.2s ease; 9 | .info { 10 | z-index: 99999; 11 | padding: 100px; 12 | border: var(--text-clr-dark) solid var(--border-width); 13 | transform: translate(-50%, -50%); 14 | background-color: var(--text-clr-light); 15 | position: absolute; 16 | top: 50%; 17 | left: 50%; 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | } 22 | .overlay { 23 | background: var(--main-clr); 24 | width: 100%; 25 | height: 100%; 26 | opacity: 0.8; 27 | } 28 | .caption { 29 | color: var(--text-clr-dark); 30 | line-height: 1.3; 31 | } 32 | .icon { 33 | display: inline-block; 34 | width: 230px; 35 | height: 176px; 36 | background-image: url(../images/dragndrop-icon.svg); 37 | background-size: cover; 38 | background-repeat: no-repeat; 39 | } 40 | &.show { 41 | opacity: 1; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/scss/_if-mobile.scss: -------------------------------------------------------------------------------- 1 | #if-mobile { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | z-index: 99999; 7 | width: 90%; 8 | height: 90%; 9 | background: rgba($color: #fff, $alpha: 1); 10 | border: 8px solid var(--main-clr); 11 | .icon { 12 | width: 100%; 13 | height: 100%; 14 | background-size: 60%; 15 | background-position: center; 16 | background-image: url(../images/text-for-mobile.svg); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scss/_loader.scss: -------------------------------------------------------------------------------- 1 | #loader { 2 | z-index: 999999; 3 | position: absolute; 4 | width: 100vw; 5 | height: 100vh; 6 | background: white; 7 | display: flex; 8 | } 9 | 10 | #loader-spinner { 11 | display: inline-block; 12 | width: 60px; 13 | height: 60px; 14 | background-color: var(--secondary-clr); 15 | margin: auto; 16 | animation-name: transform-animation; 17 | animation-duration: 0.5s; 18 | animation-iteration-count: infinite; 19 | animation-direction: alternate; 20 | } 21 | 22 | @keyframes transform-animation { 23 | from { 24 | background-color: var(--secondary-clr); 25 | transform: scale(0.1) rotate(0deg); 26 | } 27 | to { 28 | background-color: var(--main-clr); 29 | transform: scale(0.9) rotate(45deg); 30 | border-radius: 100%; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin content-block { 2 | cursor: default; 3 | background: var(--secondary-clr); 4 | padding: 24px; 5 | margin-bottom: 8px; 6 | color: var(--text-clr-light); 7 | font-size: 14px; 8 | overflow: hidden; 9 | position: relative; 10 | // max-width: 320px; 11 | ul { 12 | padding-left: 24px; 13 | margin: 0; 14 | list-style: none; 15 | li { 16 | &:before { 17 | position: absolute; 18 | content: "\2022"; 19 | color: var(--main-clr); 20 | font-weight: bold; 21 | display: inline-block; 22 | width: 1em; 23 | margin-left: -24px; 24 | } 25 | } 26 | } 27 | p { 28 | margin-top: 8px; 29 | margin-bottom: 16px; 30 | line-height: 1.4; 31 | } 32 | h3 { 33 | font-size: 14px; 34 | opacity: 0.4; 35 | margin-top: 0; 36 | margin-bottom: 20px; 37 | text-transform: uppercase; 38 | } 39 | &.hide { 40 | opacity: 0; 41 | height: 0; 42 | padding: 0 24px; 43 | margin-bottom: 0; 44 | } 45 | } 46 | 47 | @mixin btn-mixin($font-clr, $bck-clr, $hover-bck-clr) { 48 | cursor: pointer; 49 | user-select: none; 50 | font-family: "IBM Plex Mono", monospace; 51 | font-size: 15px; 52 | text-transform: uppercase; 53 | padding: 0 16px; 54 | height: 54px; 55 | background: $bck-clr; 56 | color: $font-clr; 57 | transition: all 0.05s ease-in-out; 58 | &:focus { 59 | outline: none; 60 | } 61 | &:hover { 62 | background: $hover-bck-clr !important; 63 | color: var(--main-clr) !important; 64 | svg { 65 | fill: var(--main-clr) !important; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/scss/_svg.scss: -------------------------------------------------------------------------------- 1 | #svg-element { 2 | overflow: visible; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | #svg-container { 8 | position: relative; 9 | background-color: var(--background-clr); 10 | } 11 | 12 | #svg-control { 13 | width: 100%; 14 | height: 100%; 15 | overflow: visible; 16 | position: absolute; 17 | &:hover { 18 | #control-path { 19 | stroke: var(--active-drag-clr); 20 | opacity: 0.6; 21 | } 22 | .control-point { 23 | opacity: 1; 24 | fill: var(--active-drag-clr); 25 | } 26 | } 27 | &.show { 28 | outline: 1px solid var(--checker-rect); 29 | } 30 | } 31 | 32 | #scale-wrap { 33 | margin: auto; 34 | } 35 | 36 | #control-path { 37 | fill: none; 38 | stroke: var(--text-clr-dark); 39 | // stroke-width: 1px; 40 | opacity: 0.15; 41 | } 42 | 43 | .control-point { 44 | opacity: 1; 45 | fill: var(--secondary-clr); 46 | // stroke-width: 2px; 47 | } 48 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --border-width: 2px; 3 | --main-clr: #35ffce; 4 | --secondary-clr: #111; 5 | --background-clr: white; 6 | --text-clr-light: white; 7 | --text-clr-dark: #111; 8 | --btn-border-clr: var(--main-clr); 9 | --active-drag-clr: #f45; 10 | --checker-rect: rgba(0, 0, 0, 0.1); 11 | } 12 | 13 | .dark-theme { 14 | --main-clr: #111; 15 | --secondary-clr: #35ffce; 16 | --background-clr: rgb(34, 34, 34); 17 | --text-clr-light: #111; 18 | --text-clr-dark: white; 19 | --btn-border-clr: white; 20 | --checker-rect: rgba(255, 255, 255, 0.1); 21 | } 22 | -------------------------------------------------------------------------------- /src/scss/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import "./normalize"; 2 | @import "./_variables"; 3 | @import "./mixins"; 4 | @import "./_base"; 5 | @import "./svg"; 6 | @import "./dragndrop-zone"; 7 | @import "./controls"; 8 | @import "./loader"; 9 | @import "./if-mobile"; 10 | @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&display=swap"); 11 | 12 | #app { 13 | display: flex; 14 | flex-direction: column; 15 | height: 100vh; 16 | width: 100%; 17 | font-family: "IBM Plex Mono", monospace; 18 | background-color: var(--background-clr); 19 | } 20 | 21 | // 22 | .links { 23 | display: flex; 24 | flex-wrap: wrap; 25 | padding: 16px 0 8px; 26 | } 27 | 28 | .social-media { 29 | display: inline-block; 30 | margin-right: 20px; 31 | display: flex; 32 | align-items: center; 33 | text-decoration: none; 34 | span { 35 | color: var(--main-clr); 36 | margin: 0 8px; 37 | font-size: 14px; 38 | transition: all 0.1s ease; 39 | } 40 | svg { 41 | fill: var(--main-clr); 42 | transform: scale(0.8); 43 | transition: all 0.1s ease; 44 | } 45 | &:hover { 46 | span { 47 | color: white; 48 | } 49 | svg { 50 | fill: white; 51 | } 52 | } 53 | } 54 | 55 | .checkerboard-pattern { 56 | background-image: linear-gradient( 57 | 45deg, 58 | var(--checker-rect) 25%, 59 | transparent 25% 60 | ), 61 | linear-gradient(-45deg, var(--checker-rect) 25%, transparent 25%), 62 | linear-gradient(45deg, transparent 75%, var(--checker-rect) 75%), 63 | linear-gradient(-45deg, transparent 75%, var(--checker-rect) 75%) !important; 64 | background-size: 20px 20px; 65 | background-position: 0 0, 0 10px, 10px -10px, -10px 0px; 66 | } 67 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assets Config file 3 | */ 4 | 5 | const serverConfiguration = { 6 | internal: { 7 | server: { 8 | baseDir: './', 9 | }, 10 | port: 3000, 11 | }, 12 | external: { 13 | proxy: 'http://localhost:9000/path/to/project/', 14 | }, 15 | }; 16 | 17 | const path = require('path'); 18 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 19 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); 20 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 21 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 22 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 23 | const TerserPlugin = require('terser-webpack-plugin'); 24 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 25 | const ImageMinPlugin = require('imagemin-webpack-plugin').default; 26 | 27 | let targetServerConfiguration = serverConfiguration.internal; 28 | 29 | const config = function (env, args) { 30 | if (args.externalServer !== undefined && args.externalServer) { 31 | targetServerConfiguration = serverConfiguration.external; 32 | } 33 | 34 | return { 35 | entry: { 36 | app: './src/js/index.js', 37 | }, 38 | output: { 39 | filename: 'js/[name].js', 40 | path: path.resolve(__dirname, 'dist'), 41 | }, 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.scss$/, 46 | use: [ 47 | 'style-loader', 48 | MiniCssExtractPlugin.loader, 49 | 'css-loader', 50 | 'postcss-loader', 51 | 'sass-loader', 52 | ], 53 | }, 54 | { 55 | test: /\.js$/, 56 | exclude: /(node_modules|bower_components)/, 57 | loader: 'babel-loader', 58 | }, 59 | { 60 | test: /\.(png|gif|jpg|jpeg)$/, 61 | use: [ 62 | { 63 | loader: 'url-loader', 64 | options: { 65 | name: 'images/design/[name].[hash:6].[ext]', 66 | publicPath: '../', 67 | limit: 8192, 68 | }, 69 | }, 70 | ], 71 | }, 72 | { 73 | test: /\.(eot|svg|ttf|woff|woff2)$/, 74 | use: [ 75 | { 76 | loader: 'url-loader', 77 | options: { 78 | name: 'fonts/[name].[hash:6].[ext]', 79 | publicPath: '../', 80 | limit: 8192, 81 | }, 82 | }, 83 | ], 84 | }, 85 | ], 86 | }, 87 | optimization: { 88 | minimizer: [ 89 | new TerserPlugin({ 90 | parallel: true, 91 | }), 92 | new OptimizeCssAssetsPlugin({}), 93 | ], 94 | }, 95 | watchOptions: { 96 | poll: 1000, 97 | ignored: /node_modules/, 98 | }, 99 | plugins: [ 100 | new BrowserSyncPlugin({ 101 | ...targetServerConfiguration, 102 | files: ['src/*'], 103 | ghostMode: { 104 | clicks: false, 105 | location: false, 106 | forms: false, 107 | scroll: false, 108 | }, 109 | injectChanges: true, 110 | logFileChanges: true, 111 | logLevel: 'debug', 112 | logPrefix: 'wepback', 113 | notify: true, 114 | reloadDelay: 0, 115 | }), 116 | new HtmlWebpackPlugin({ 117 | inject: true, 118 | hash: false, 119 | filename: '../index.html', 120 | template: path.resolve(__dirname, 'src', 'index.html'), 121 | }), 122 | new MiniCssExtractPlugin({ 123 | filename: 'css/[name].css', 124 | }), 125 | new ImageMinPlugin({ test: /\.(jpg|jpeg|png|gif|svg)$/i }), 126 | new CleanWebpackPlugin({ 127 | /** 128 | * Some plugins used do not correctly save to webpack's asset list. 129 | * Disable automatic asset cleaning until resolved 130 | */ 131 | cleanStaleWebpackAssets: false, 132 | verbose: true, 133 | }), 134 | new CopyWebpackPlugin([ 135 | { 136 | from: path.resolve(__dirname, 'src', 'images'), 137 | to: path.resolve(__dirname, 'dist', 'images'), 138 | toType: 'dir', 139 | }, 140 | ]), 141 | ], 142 | }; 143 | }; 144 | 145 | module.exports = config; 146 | --------------------------------------------------------------------------------