diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7dca639 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + check: + name: Test & Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests and build + run: npm run check diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..789ebbf --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [master, main] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dist + + deploy: + name: Deploy + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 789e86d..1eae0cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -/nbproject/private/ -nbproject/ -node_modules/* \ No newline at end of file +dist/ +node_modules/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 9517a29..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "vendor/rtcoder/colorpicker"] - path = vendor/rtcoder/colorpicker - url = https://github.com/rtcoder/Color-Picker.git diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/WebDraft.iml b/.idea/WebDraft.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/WebDraft.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..55e8f10 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,9 @@ + + + + WDRAFT + Wdraft + wdraft + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f4cf887 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 8cdaa6e..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,54 +0,0 @@ -module.exports = function (grunt) { - grunt.initConfig({ - less: { - development: { - options: { - paths: ["./less"], - yuicompress: true, - sourceMap: true - }, - files: { - "./build/css/style.css": "./less/style.less" - } - } - }, - cssmin: { - options: { - mergeIntoShorthands: false, - roundingPrecision: -1 - }, - target: { - files: { - './build/css/style.min.css': './build/css/style.css' - } - } - }, - concat: { - options: { - separator: "\n", - }, - dist: { - src: ["./js/modules/*", "./js/*"], - dest: './build/js/built.js', - }, - }, - watch: { - lessCompile: { - files: "./less/*", - tasks: ["less"] - }, - minifyCss: { - files: "./build/css/style.css", - tasks: ["cssmin"] - }, - concatJs: { - files: ["./js/*", "./js/modules/*"], - tasks: ["concat"] - }, - } - }); - grunt.loadNpmTasks('grunt-contrib-less'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-concat'); -}; \ No newline at end of file diff --git a/README.md b/README.md index 22f31ca..e2584de 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,132 @@ # WebDraft -App for drawing + +[![CI](https://github.com/rtcoder/WebDraft/actions/workflows/ci.yml/badge.svg)](https://github.com/rtcoder/WebDraft/actions/workflows/ci.yml) + +A fast, lightweight browser-based image editor built with vanilla TypeScript and the Canvas 2D API — no frameworks, no bloat, just a crisp drawing experience that loads instantly. + +--- + +## Why WebDraft? + +- **No installation** — open in any modern browser and start drawing +- **Offline-ready** — works without an internet connection after the first load +- **Privacy-first** — your images never leave your machine +- **Snappy** — built directly on Canvas 2D with zero runtime dependencies + +--- + +## Features + +### Drawing tools + +| Tool | Description | +|------|-------------| +| Brush | Freehand painting with configurable size and hardness | +| Eraser | Remove pixels with smooth edges | +| Bucket fill | Flood-fill with tolerance control | +| Line | Straight lines with adjustable thickness | +| Rectangle | Outlined or filled rectangles | +| Ellipse | Outlined or filled ellipses | +| Move | Reposition the active layer | +| Eyedropper | Sample any colour from the canvas | +| Text | Rich text with live HTML editing | + +### Brush & style +- Stroke and fill colour with independent opacity +- Shadow: enable/disable, colour, blur, X/Y offset +- Configurable brush size +- Web sensitivity for pressure-like variation + +### Rich text editing +- Live `contenteditable` editor overlaid on the canvas +- Floating toolbar: **bold**, *italic*, underline, font family, font size, colour, alignment +- Edits render to canvas with full word-wrap matching the layer bounds + +### Layers +- Add, delete, rename, and reorder layers +- Per-layer visibility toggle and opacity control +- Independent image data per layer — non-destructive editing + +### Image operations +- Resize canvas with custom width/height +- Invert colours +- Mirror horizontally or vertically +- Rotate 90° left or right + +### Canvas & viewport +- Zoom: 10 % – 800 % in discrete steps +- Ctrl + scroll wheel to zoom in/out +- Ctrl + `=` / `+` — zoom in +- Ctrl + `-` — zoom out +- Ctrl + `0` — reset zoom to 100 % +- Zoom percentage indicator in the menu bar + +### File & export +- Save/open projects in `.wdraft` format (JSON, preserves all layers and state) +- Import images onto a new layer (PNG, JPG, …) +- Export the flattened canvas as PNG + +### Undo / redo +- Full history stack +- Ctrl + Z — undo +- Ctrl + Y — redo + +### Localisation +- English and Polish UI +- Language auto-detected from `navigator.languages` +- Switcher in the menu bar + +--- + +## Keyboard shortcuts + +| Shortcut | Action | +|----------|--------| +| Ctrl + Z | Undo | +| Ctrl + Y | Redo | +| Ctrl + S | Save project | +| Ctrl + O | Open project | +| Ctrl + scroll | Zoom in / out | +| Ctrl + = / + | Zoom in | +| Ctrl + - | Zoom out | +| Ctrl + 0 | Reset zoom | + +--- + +## Getting started + +```bash +git clone https://github.com/rtcoder/WebDraft.git +cd WebDraft +npm install +npm run dev +``` + +Then open [http://localhost:5173](http://localhost:5173). + +### Build + +```bash +npm run build +``` + +### Type-check + build (CI) + +```bash +npm run check +``` + +--- + +## Tech stack + +- **TypeScript** — strict, zero-`any` codebase +- **Vite** — instant HMR dev server and optimised production build +- **Canvas 2D API** — all rendering, no WebGL +- **Zero runtime dependencies** + +--- + +## License + +MIT diff --git a/build/css/style.css b/build/css/style.css deleted file mode 100644 index d70deb6..0000000 --- a/build/css/style.css +++ /dev/null @@ -1,1025 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Open+Sans); -input[type=checkbox] + label { - cursor: pointer; - color: #A9A9A9; - width: 100%; - background: transparent; - text-align: left; - border: none; - border-radius: 0px; -} -input[type=checkbox] + label .fa-circle, -input[type=checkbox] + label .fa-check-circle { - float: left; - text-align: center; - width: 30px; - vertical-align: middle; - margin: 0px 0px 0px 10px; - font-size: 18px; - height: 20px; - line-height: 20px; -} -input[type=checkbox] + label .fa-check-circle { - display: none; -} -input[type=checkbox] + label #l { - margin-left: 10px; - height: 20px; - line-height: 20px; -} -input[type=checkbox] + label span { - width: 100%; - height: 20px; - text-align: left; -} -input[type=checkbox] + label span.label { - width: auto; -} -input[type=checkbox]:checked + label { - font-weight: bold; - color: #0052CC; -} -input[type=checkbox]:checked + label .fa-circle { - display: none; -} -input[type=checkbox]:checked + label .fa-check-circle { - display: block; -} -input[type=checkbox]:hover + label { - color: #FFFFFF; -} -input[type=checkbox]:hover + label .fa-circle { - display: none; -} -input[type=checkbox]:hover + label .fa-check-circle { - display: block; -} -input[type=range] { - -webkit-appearance: none; - width: calc(100% - 20px); - margin: 0 10px; -} -input[type=range]::-webkit-slider-runnable-track { - width: 230px; - height: 10px; - background: #DDDDDD; - border: none; - border-radius: 0px; -} -input[type=range]::-webkit-slider-thumb { - -webkit-appearance: none; - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: #0052CC; -} -input[type=range]::-moz-range-track { - width: 230px; - height: 10px; - background: #DDDDDD; - border: none; -} -input[type=range]::-moz-range-thumb { - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: #0052CC; -} -input[type=range]::-ms-track { - width: 230px; - height: 10px; - background: transparent; - border-color: transparent; - border-width: 0; - color: transparent; -} -input[type=range]::-ms-thumb { - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: #0052CC; -} -input[type=range]:-moz-focusring { - outline: none; -} -input[type=range]::-ms-fill-lower { - background: #DDDDDD; -} -input[type=range]::-ms-fill-upper { - background: #DDDDDD; -} -input[type=range]:focus { - outline: none; -} -input[type=range]:focus::-ms-fill-lower { - background: #888888; -} -input[type=range]:focus::-webkit-slider-runnable-track { - background: #CCCCCC; -} -input[type=range]:focus::-ms-fill-upper { - background: #CCCCCC; -} -input[type=color] { - visibility: hidden; - width: 0px; - height: 0px; - position: absolute; -} -input#fileUploaderInput { - display: none; -} -input#isFillSet { - display: none; -} -input#isShadow { - display: none; -} -#toolsGroup { - width: 200px; - height: calc(100vh - 20px); - background: #2C2B2B; - float: left; -} -#toolsGroup #previewColorSampler { - width: 100%; - height: 60px; - background: 0 30px url('/pic/transparent.png'); - z-index: 1; - display: none; -} -#toolsGroup #previewColorSampler #textColorSampler { - width: 100%; - float: left; - height: 30px; - background: #2C2B2B; - line-height: 30px; - text-align: center; - font-size: 13px; - color: #EEEEEE; -} -#toolsGroup #previewColorSampler #colorBoxSampler { - width: 100%; - height: 30px; - float: left; -} -#toolsGroup .tools { - width: 100%; - background: #2C2B2B; - float: left; - z-index: 4; - color: #A9A9A9; -} -#toolsGroup .tools .title { - width: 100%; - height: 20px; - line-height: 20px; - float: left; - font-size: 15px; - text-align: center; - background: #EEEEEE; - color: #2E3436; -} -#toolsGroup .tools .title .fas, -#toolsGroup .tools .title .fab, -#toolsGroup .tools .title .far { - position: relative; - height: inherit; - line-height: inherit; - left: 0; - top: 0; - text-align: center; -} -#toolsGroup .tools #toolsBtns { - width: 100%; - float: left; -} -#toolsGroup .tools #toolsBtns .primaryTool, -#toolsGroup .tools #toolsBtns .paintTools { - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: #A9A9A9; - cursor: pointer; -} -#toolsGroup .tools #toolsBtns .primaryTool .fas, -#toolsGroup .tools #toolsBtns .paintTools .fas, -#toolsGroup .tools #toolsBtns .primaryTool .fab, -#toolsGroup .tools #toolsBtns .paintTools .fab, -#toolsGroup .tools #toolsBtns .primaryTool .far, -#toolsGroup .tools #toolsBtns .paintTools .far { - font-size: 15px; -} -#toolsGroup .tools #toolsBtns .primaryTool:hover, -#toolsGroup .tools #toolsBtns .paintTools:hover { - color: #FFFFFF; -} -#toolsGroup .tools #toolsBtns .paintTools.active { - cursor: default; - color: #0052CC; - background: #1C1B1B; -} -#toolsGroup .tools #toolsBtns .primaryTool:active { - cursor: default; - color: #0052CC; - background: #1C1B1B; -} -#toolsGroup .tools #radioBtns { - width: 100%; - float: left; -} -#toolsGroup .tools #radioBtns #shadowInput, -#toolsGroup .tools #radioBtns #fillShapeInput { - width: 100%; - line-height: 20px; - float: left; - padding: 5px 0px; - font-size: 12px; -} -#toolsGroup .tools #radioBtns #shadowInput *, -#toolsGroup .tools #radioBtns #fillShapeInput * { - cursor: pointer; - float: left; -} -#toolsGroup .tools #radioBtns #fillShapeInput { - display: none; -} -#toolsGroup .tools #colorPicker { - width: 100%; - float: left; -} -#toolsGroup .tools #colorPicker .colorTab { - width: 100%; - height: 25px; - line-height: 25px; - font-size: 14px; - cursor: pointer; -} -#toolsGroup .tools #colorPicker .colorTab .color { - border-radius: 100%; - width: 15px; - height: 15px; - margin: 5px 10px; - float: left; - border: 1px solid #CCCCCC; -} -#toolsGroup .tools #colorPicker .colorTab#generalColor .color { - background: #000000; -} -#toolsGroup .tools #colorPicker .colorTab#shadowColor { - display: none; -} -#toolsGroup .tools #colorPicker .colorTab#shadowColor .color { - background: #232324; -} -#toolsGroup .tools #colorPicker .colorTab#fillColor { - display: none; -} -#toolsGroup .tools #colorPicker .colorTab#fillColor .color { - background: #FFFFFF; -} -#toolsGroup .tools #colorPicker .colorTab:hover { - color: #0052CC; - background: #1C1B1B; -} -#textOptions { - display: none; -} -#textOptions #alignBtns { - width: 100%; - float: left; -} -#textOptions #alignBtns .textPostionTool { - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: #A9A9A9; - cursor: pointer; -} -#textOptions #alignBtns .textPostionTool .fas, -#textOptions #alignBtns .textPostionTool .fab, -#textOptions #alignBtns .textPostionTool .far { - font-size: 15px; -} -#textOptions #alignBtns .textPostionTool:hover, -#textOptions #alignBtns .textPostionTool.active { - color: #FFFFFF; -} -#textOptions #fontStyleBtns { - width: 100%; - float: left; -} -#textOptions #fontStyleBtns .styleTool { - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: #A9A9A9; - cursor: pointer; -} -#textOptions #fontStyleBtns .styleTool .fas, -#textOptions #fontStyleBtns .styleTool .fab, -#textOptions #fontStyleBtns .styleTool .far { - font-size: 15px; -} -#textOptions #fontStyleBtns .styleTool:hover, -#textOptions #fontStyleBtns .styleTool.active { - color: #FFFFFF; -} -#textRectangle { - display: none; - position: absolute; - z-index: 2; - border: 1px solid #000000; - font-size: 8mm; - cursor: move; -} -#layers .title { - width: 100%; - height: 20px; - line-height: 20px; - float: left; - font-size: 15px; - text-align: center; - background: #EEEEEE; - color: #2E3436; -} -#layers #layersBtns { - width: 100%; - background: #2C2B2B; - float: left; - z-index: 4; - color: #A9A9A9; -} -#layers #layersBtns .layerTool { - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: #A9A9A9; - cursor: pointer; -} -#layers #layersBtns .layerTool#addLayer { - color: #16c600; -} -#layers #layersBtns .layerTool#delLayer { - color: #C50000; -} -#layers #layersBtns .layerTool:hover { - color: #FFFFFF; -} -#layers #layersBtns .layerTool .fas, -#layers #layersBtns .layerTool .fab, -#layers #layersBtns .layerTool .far { - font-size: 15px; -} -#layers #listLayers { - width: 100%; - background: #2C2B2B; - float: left; - z-index: 4; - color: #A9A9A9; - max-height: calc(50vh - 170px); - overflow: auto; - top: 50px; -} -#layers #listLayers .layerView { - position: relative; - width: 100%; - height: 60px; - margin: 10px auto; - background: transparent; - text-align: center; - cursor: pointer; -} -#layers #listLayers .layerView.active { - cursor: default; - background-color: rgba(0, 0, 0, 0.1); -} -#layers #listLayers .layerView.active:hover { - cursor: default; - background-color: rgba(0, 0, 0, 0.1); -} -#layers #listLayers .layerView.active .imgLayerContainer { - border-color: #0021CC; -} -#layers #listLayers .layerView:hover { - background-color: rgba(0, 0, 0, 0.3); -} -#layers #listLayers .layerView .imgLayerContainer { - float: left; - border: 1px solid #CCCCCC; - width: 60px; - height: 60px; -} -#layers #listLayers .layerView .imgLayerContainer img.imgLayer { - float: left; - max-width: 58px; - max-height: 58px; -} -#layers #listLayers .layerView .hideLayer, -#layers #listLayers .layerView .showLayer { - float: left; - margin: 21px 5px; - width: 18px; - height: 18px; - line-height: 18px !important; - text-align: center; - cursor: pointer; -} -#layers #listLayers .layerView .showLayer { - color: #C50000; -} -#layers #listLayers .layerView .showLayer:hover { - color: #16c600; -} -#layers #listLayers .layerView .hideLayer { - color: #16c600; -} -#layers #listLayers .layerView .hideLayer:hover { - color: #C50000; -} -#sliders { - width: 100%; - float: left; -} -#sliders .sliderContainer { - width: 100%; - height: 45px; - float: left; -} -#sliders .sliderContainer#shadow_slider { - display: none; -} -#sliders .sliderContainer#sensitivityPoints_slider { - display: none; -} -#sliders .sliderContainer#fillOpacity_slider { - display: none; -} -#sliders .sliderContainer .sliderVal { - width: 100%; - height: 20px; - margin-top: 5px; - line-height: 20px; - word-wrap: break-word; - font-size: 13px; - float: left; - text-align: center; -} -#info { - width: 350px; - height: 100vh; - z-index: 5; - position: absolute; - display: none; - overflow: auto; - background: #2C2B2B; - border-left: 1px solid #A9A9A9; - color: #FFFFFF; - top: 0px; - right: 0px; -} -@media all and (max-width: 400px) { - #info { - width: 100vw; - } -} -#info .close-info { - position: absolute; - right: 0; - cursor: pointer; - margin: 5px; -} -#info .info-section { - width: 100%; - float: left; - margin: 5px 0px; -} -#info .info-section .info-section-title { - width: 100%; - float: left; - text-align: center; -} -#info .info-section .info-section-line { - width: 100%; - float: left; -} -#info .info-section .info-section-line .info-section-shortcut { - width: 30%; - float: left; - padding: 2px 5px; - text-align: right; -} -#info .info-section .info-section-line .info-section-shortcut span { - padding: 0 5px; - min-width: 30px; - height: 20px; - border-radius: 2px; - margin: 15px 5px; - text-align: center; - line-height: 20px; - font-size: 12px; - background: #F1F1F1; - border: 1px solid #CCCCCC; - color: #2C2B2B; -} -#info .info-section .info-section-line .info-section-shortcut-describe { - width: 70%; - float: left; - padding: 2px 5px; -} -#resizer { - width: 300px; - height: 210px; - position: absolute; - top: 0; - left: 0; - background: #2C2B2B; - display: none; - padding: 10px; - z-index: 4; - margin-left: calc((100vw - 300px) / 2); - margin-top: calc((100vh - 210px) / 2); -} -#resizer input[type=number] { - width: 250px; - height: 30px; - margin: 10px 15px; - border: none; - cursor: text; - text-align: center; - float: left; -} -#resizer #resizeinfo { - width: 250px; - height: 30px; - line-height: 30px; - text-align: center; - color: #CCCCCC; - float: left; - margin: 0px 20px; -} -#resizer .buttonAction { - float: left; - width: 120px; - height: 30px; - line-height: 30px; - font-size: 15px; - margin: 10px 10px; - text-align: center; - background: #CCCCCC; - cursor: pointer; -} -#resizer .buttonAction:hover { - background: #0052CC; - color: #CCCCCC; -} -#hint { - width: 100vw; - height: 50px; - position: absolute; - bottom: 20px; - left: 0; - background: #2C2B2B; - display: none; -} -#hint .hintGroup { - width: 100%; - height: 100%; - float: left; - display: none; -} -#hint .hintGroup .keyboardButton { - float: left; - padding: 0 5px; - min-width: 30px; - height: 20px; - background: #F1F1F1; - box-sizing: border-box; - border: 1px solid #CCCCCC; - border-radius: 2px; - margin: 15px 5px; - color: #2C2B2B; - text-align: center; - line-height: 20px; - font-size: 12px; -} -#hint .hintGroup .describeHint { - width: auto; - float: left; - height: 50px; - line-height: 50px; - margin: 0 5px 0 0; - color: #F1F1E4; - font-size: 12px; -} -#shadowSquare { - width: 200px; - height: 200px; - position: relative; - background: rgba(0, 0, 0, 0.4); - float: left; - display: none; -} -#shadowSquare #shadowDot { - cursor: move; - background: #FFFFFF; - border-radius: 100%; - width: 10px; - height: 10px; - position: absolute; - top: 95px; - left: 95px; - z-index: 2; -} -#shadowSquare::after, -#shadowSquare::before { - position: absolute; - content: ' '; - background: #A9A9A9; - z-index: 1; -} -#shadowSquare::after { - width: 200px; - height: 1px; - top: 50%; - left: 0px; -} -#shadowSquare::before { - width: 1px; - height: 200px; - top: 0%; - left: 50%; -} -#statusbar { - width: 100%; - background: #2C2B2B; - height: 20px; - line-height: 20px; - float: left; - position: absolute; - bottom: 0; - font-size: 13px; - color: #CCCCCC; -} -#statusbar #mousePosition { - float: left; - width: 100px; - height: inherit; - line-height: inherit; - text-align: center; -} -#statusbar #author { - height: inherit; - line-height: inherit; - float: right; - right: 0; - margin-right: 20px; -} -#statusbar #author a { - color: #EEEEEE; - cursor: pointer; -} -#camera { - box-shadow: 0px 0px 5px 2px #6f6f6f; - width: 740px; - max-width: 100vw; - height: 560px; - max-height: 100vh; - background: #2C2B2B; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - position: absolute; - z-index: 99; - display: none; -} -#camera.opened { - display: block; -} -#camera.fullscreen { - width: 100vw; - height: 100vh; - top: 0; - left: 0; - transform: translate(0, 0); -} -#camera.fullscreen .cameraContent .videoContainer { - width: 100vw; - height: calc(100vh - 130px); -} -#camera.fullscreen .cameraContent .filters { - width: 100vw; - height: 50px; - line-height: 50px; -} -#camera.fullscreen .cameraContent .filters label span.filter { - margin: 15px 5px; -} -#camera #cameraHeader { - width: 100%; - height: 30px; -} -#camera #cameraHeader #cameraTitle { - width: calc(100% - 70px); - float: left; - height: 30px; - line-height: 30px; - padding: 0px 5px; - color: #FFFFFF; -} -#camera #cameraHeader button { - width: 30px; - height: 30px; - border: none; - display: inline-block; - cursor: pointer; - float: right; -} -#camera #cameraHeader button i { - cursor: pointer; -} -#camera #cameraHeader button#closeCamera { - background: #C50000; - color: #FFFFFF; -} -#camera #cameraHeader button#switchFullscreen { - background: #0021CC; - color: #FFFFFF; - margin-right: 10px; -} -#camera .cameraContent { - width: 100%; - float: left; - margin-top: 10px; -} -#camera .cameraContent .videoContainer { - width: calc(100% - 100px); - float: left; - position: relative; -} -#camera .cameraContent .videoContainer canvas#snapImage, -#camera .cameraContent .videoContainer canvas#frame, -#camera .cameraContent .videoContainer canvas#additional, -#camera .cameraContent .videoContainer video { - width: 100%; - max-width: 100vw; - max-height: calc(100vh - 130px); - float: left; -} -#camera .cameraContent .videoContainer canvas#snapImage, -#camera .cameraContent .videoContainer canvas#frame { - display: none; -} -#camera .cameraContent .videoContainer canvas#additional { - display: block; - position: absolute; - top: 0; - left: 0; -} -#camera .cameraContent .filters { - width: 100px; - float: left; -} -#camera .cameraContent .filters label input { - display: none; -} -#camera .cameraContent .filters label input:checked + span { - background: #0052CC; - color: #FFFFFF; -} -#camera .cameraContent .filters label span.filter { - width: 70px; - height: 20px; - margin: 0px 15px 5px; - cursor: pointer; - background: #EEEEEE; - line-height: 20px; - text-align: center; - font-size: 13px; - float: left; -} -#camera .cameraContent #cameraButtons { - float: left; - width: 100%; - height: 50px; - line-height: 50px; -} -#camera .cameraContent #cameraButtons button { - min-width: 50px; - border: none; - display: none; - cursor: pointer; -} -#camera .cameraContent #cameraButtons button * { - cursor: pointer; -} -#camera .cameraContent #cameraButtons button#snap { - display: inline-block; -} -#camera .cameraContent #cameraButtons button#applySnap { - background: #129a01; - color: #FFFFFF; -} -#camera .cameraContent #cameraButtons button#cancelSnap { - background: #C50000; - color: #FFFFFF; -} -#camera .cameraContent #cameraButtons button#saveSnapOnComputer { - background: #0052CC; - color: #FFFFFF; -} -#camera.snapped #cameraButtons button { - display: inline-block; -} -#camera.snapped #cameraButtons button#snap { - display: none; -} -#camera.snapped .cameraContent .videoContainer canvas#additional, -#camera.snapped .cameraContent .videoContainer video#video { - display: none; -} -#camera.snapped .cameraContent .videoContainer canvas#snapImage { - display: block; -} -menu#contextmenu { - display: none; - width: 230px; - height: auto; - position: fixed; - top: 50%; - left: 50%; - background: #fff; - z-index: 10; -} -menu#contextmenu ul li { - cursor: pointer; - float: left; - height: 30px; - line-height: 30px; - position: relative; - width: 100%; - background: #fff; - color: #000000; -} -menu#contextmenu ul li * { - cursor: pointer; -} -menu#contextmenu ul li .content { - width: 100%; - float: left; -} -menu#contextmenu ul li .content span { - height: inherit; - float: left; -} -menu#contextmenu ul li .content span.icon { - width: 20px; -} -menu#contextmenu ul li .content span.icon i { - float: left; - width: 20px; - height: 30px; - line-height: 30px; -} -menu#contextmenu ul li .content span.text { - width: calc(100% - 130px); -} -menu#contextmenu ul li .content span.shortcut { - text-align: right; - text-transform: uppercase; - width: 70px; - font-size: 11px; - color: #2E3436; -} -menu#contextmenu ul li .content span.arrow { - color: #2E3436; - width: 10px; - margin: 0px 5px; - font-size: 11px; - visibility: hidden; - opacity: 0; -} -menu#contextmenu ul li .submenu { - width: 230px; - position: absolute; - display: none; - top: 0; - left: 230px; -} -menu#contextmenu ul li .submenu + .content span.arrow.left-arrow { - visibility: hidden; - opacity: 0; -} -menu#contextmenu ul li .submenu + .content span.arrow.right-arrow { - visibility: visible; - opacity: 1; - float: right; -} -menu#contextmenu ul li.disabled { - color: #888888; -} -menu#contextmenu ul li:not(.disabled):hover { - color: #0052CC; - background-color: #dedede; -} -menu#contextmenu ul li:not(.disabled):hover .submenu { - display: block; - color: #000000; -} -menu#contextmenu.left-submenu ul li .submenu { - left: -230px; -} -menu#contextmenu.left-submenu ul li .submenu + .content span.arrow.left-arrow { - visibility: visible; - opacity: 1; -} -menu#contextmenu.left-submenu ul li .submenu + .content span.arrow.right-arrow { - visibility: hidden; - opacity: 0; -} -* { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: default; - font-family: 'Open Sans', sans-serif; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - margin: 0; - padding: 0; -} -ul { - list-style: none; -} -html body { - top: 0; - left: 0; - width: 100%; - height: 100%; - float: left; - position: fixed; - overflow: hidden; -} -html body #content { - float: left; - background: #373636; - overflow: auto; - width: calc(100vw - 200px); - height: calc(100vh - 20px); -} -html body #content #drawHandler { - border: 1px dotted #0052CC; - position: relative; - overflow: hidden; -} -html body #content #drawHandler #draw, -html body #content #drawHandler #drawHandler, -html body #content #drawHandler #eventHandler { - width: 600px; - height: 400px; - overflow: hidden; - position: relative; - /*cursor: crosshair;*/ -} -html body #content #drawHandler #eventHandler { - z-index: 3; - position: absolute; -} -html body #content #drawHandler #draw canvas { - position: absolute; - cursor: crosshair; - border: 1px dotted #0052CC; -} -html body #content #drawHandler #draw canvas.invisible { - visibility: hidden; -} -html body #eraseRect, -html body #prepareRect, -html body #prepareCircle, -html body #selectRectangle { - display: none; - position: absolute; - z-index: 2; - border: 1px solid #000000; -} -html body #selectRectangle { - cursor: move; -} -html body #eraseRect { - border-radius: 100%; - border-color: #CCCCCC; -} -/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/build/css/style.css.map b/build/css/style.css.map deleted file mode 100644 index f9e240b..0000000 --- a/build/css/style.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["less/inputs.less","less/tools.less","less/text-options.less","less/layers.less","less/sliders.less","less/info.less","less/resizer.less","less/hints.less","less/shadow-changer.less","less/statusbar.less","less/camera.less","less/contextmenu.less","./less/style.less"],"names":[],"mappings":";AAKQ,KADH,eACI;EACG,eAAA;EACA,cAAA;EACA,WAAA;EACA,uBAAA;EACA,gBAAA;EACA,YAAA;EACA,kBAAA;;AAPJ,KADH,eACI,QAQG;AARJ,KADH,eACI,QAQe;EACR,WAAA;EACA,kBAAA;EACA,WAAA;EACA,sBAAA;EACA,wBAAA;EACA,eAAA;EACA,YAAA;EACA,iBAAA;;AAhBR,KADH,eACI,QAkBG;EACI,aAAA;;AAnBR,KADH,eACI,QAqBG;EACI,iBAAA;EACA,YAAA;EACA,iBAAA;;AAxBR,KADH,eACI,QA0BG;EACI,WAAA;EACA,YAAA;EACA,gBAAA;;AACA,KA/BX,eACI,QA0BG,KAIK;EACG,WAAA;;AAKR,KArCP,eAoCI,QACI;EACG,iBAAA;EACA,cAAA;;AAFJ,KArCP,eAoCI,QACI,QAGG;EACI,aAAA;;AAJR,KArCP,eAoCI,QACI,QAMG;EACI,cAAA;;AAKR,KAjDP,eAgDI,MACI;EACG,cAAA;;AADJ,KAjDP,eAgDI,MACI,QAEG;EACI,aAAA;;AAHR,KAjDP,eAgDI,MACI,QAKG;EACI,cAAA;;AAKhB,KAAC;EACG,wBAAA;EACA,OAAO,iBAAP;EACA,cAAA;;AACA,KAJH,YAII;EACG,YAAA;EACA,YAAA;EACA,mBAAA;EACA,YAAA;EACA,kBAAA;;AAEJ,KAXH,YAWI;EACG,wBAAA;EACA,YAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,mBAAA;;AAEJ,KAnBH,YAmBI;EACG,YAAA;EACA,YAAA;EACA,mBAAA;EACA,YAAA;;AAEJ,KAzBH,YAyBI;EACG,YAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,mBAAA;;AAEJ,KAhCH,YAgCI;EACG,YAAA;EACA,YAAA;EACA,uBAAA;EACA,yBAAA;EACA,eAAA;EACA,kBAAA;;AAEJ,KAxCH,YAwCI;EACG,YAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,mBAAA;;AAEJ,KA/CH,YA+CI;EACG,aAAA;;AAEJ,KAlDH,YAkDI;EACG,mBAAA;;AAEJ,KArDH,YAqDI;EACG,mBAAA;;AAEJ,KAxDH,YAwDI;EACG,aAAA;;AACA,KA1DP,YAwDI,MAEI;EACG,mBAAA;;AAEJ,KA7DP,YAwDI,MAKI;EACG,mBAAA;;AAEJ,KAhEP,YAwDI,MAQI;EACG,mBAAA;;AAIZ,KAAC;EACG,kBAAA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;;AAEJ,KAAC;EACG,aAAA;;AAEJ,KAAC;EACG,aAAA;;AAEJ,KAAC;EACG,aAAA;;AC/IR;EACI,YAAA;EACA,QAAQ,kBAAR;EACA,mBAAA;EACA,WAAA;;AAJJ,WAKI;EACI,WAAA;EACA,YAAA;EACA,uBAAuB,uBAAvB;EACA,UAAA;EACA,aAAA;;AAVR,WAKI,qBAMI;EACI,WAAA;EACA,WAAA;EACA,YAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,eAAA;EACA,cAAA;;AAnBZ,WAKI,qBAgBI;EACI,WAAA;EACA,YAAA;EACA,WAAA;;AAxBZ,WA2BI;EACI,WAAA;EACA,mBAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;;AAhCR,WA2BI,OAMI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,WAAA;EACA,eAAA;EACA,kBAAA;EACA,mBAAA;EACA,cAAA;;AAzCZ,WA2BI,OAMI,OASI;AA1CZ,WA2BI,OAMI,OASU;AA1ClB,WA2BI,OAMI,OASgB;EACR,kBAAA;EACA,eAAA;EACA,oBAAA;EACA,OAAA;EACA,MAAA;EACA,kBAAA;;AAhDhB,WA2BI,OAwBI;EACI,WAAA;EACA,WAAA;;AArDZ,WA2BI,OAwBI,WAGI;AAtDZ,WA2BI,OAwBI,WAII;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;;AA9DhB,WA2BI,OAwBI,WAGI,aASI;AA/DhB,WA2BI,OAwBI,WAII,YAQI;AA/DhB,WA2BI,OAwBI,WAGI,aASU;AA/DtB,WA2BI,OAwBI,WAII,YAQU;AA/DtB,WA2BI,OAwBI,WAGI,aASgB;AA/D5B,WA2BI,OAwBI,WAII,YAQgB;EACR,eAAA;;AAEJ,WAvCZ,OAwBI,WAGI,aAYK;AAAD,WAvCZ,OAwBI,WAII,YAWK;EACG,cAAA;;AAIJ,WA5CZ,OAwBI,WAmBI,YACK;EACG,eAAA;EACA,cAAA;EACA,mBAAA;;AAIJ,WAnDZ,OAwBI,WA0BI,aACK;EACG,eAAA;EACA,cAAA;EACA,mBAAA;;AAjFpB,WA2BI,OA0DI;EACI,WAAA;EACA,WAAA;;AAvFZ,WA2BI,OA0DI,WAGI;AAxFZ,WA2BI,OA0DI,WAGkB;EACV,WAAA;EACA,iBAAA;EACA,WAAA;EACA,gBAAA;EACA,eAAA;;AA7FhB,WA2BI,OA0DI,WAGI,aAMI;AA9FhB,WA2BI,OA0DI,WAGkB,gBAMV;EACI,eAAA;EACA,WAAA;;AAhGpB,WA2BI,OA0DI,WAcI;EACI,aAAA;;AApGhB,WA2BI,OA4EI;EACI,WAAA;EACA,WAAA;;AAzGZ,WA2BI,OA4EI,aAGI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;;AA/GhB,WA2BI,OA4EI,aAGI,UAMI;EACI,mBAAA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;EACA,WAAA;EACA,yBAAA;;AAEJ,WA7FZ,OA4EI,aAGI,UAcK,aACG;EACI,mBAAA;;AAGR,WAlGZ,OA4EI,aAGI,UAmBK;EACG,aAAA;;AADJ,WAlGZ,OA4EI,aAGI,UAmBK,YAEG;EACI,mBAAA;;AAGR,WAxGZ,OA4EI,aAGI,UAyBK;EACG,aAAA;;AADJ,WAxGZ,OA4EI,aAGI,UAyBK,UAEG;EACI,mBAAA;;AAGR,WA9GZ,OA4EI,aAGI,UA+BK;EACG,cAAA;EACA,mBAAA;;AC3IpB;EACI,aAAA;;AADJ,YAEI;EACI,WAAA;EACA,WAAA;;AAJR,YAEI,WAGI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;;AAZZ,YAEI,WAGI,iBAQI;AAbZ,YAEI,WAGI,iBAQU;AAblB,YAEI,WAGI,iBAQgB;EACR,eAAA;;AAEJ,YAdR,WAGI,iBAWK;AAAQ,YAdjB,WAGI,iBAWc;EACN,cAAA;;AAjBhB,YAqBI;EACI,WAAA;EACA,WAAA;;AAvBR,YAqBI,eAGI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;;AA/BZ,YAqBI,eAGI,WAQI;AAhCZ,YAqBI,eAGI,WAQU;AAhClB,YAqBI,eAGI,WAQgB;EACR,eAAA;;AAEJ,YAdR,eAGI,WAWK;AAAQ,YAdjB,eAGI,WAWc;EACN,cAAA;;AAMhB;EACI,aAAA;EACA,kBAAA;EACA,UAAA;EACA,yBAAA;EACA,cAAA;EACA,YAAA;;AChDJ,OACI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,WAAA;EACA,eAAA;EACA,kBAAA;EACA,mBAAA;EACA,cAAA;;AATR,OAWI;EACI,WAAA;EACA,mBAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;;AAhBR,OAWI,YAMI;EACI,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;;AACA,OAdR,YAMI,WAQK;EACG,cAAA;;AAEJ,OAjBR,YAMI,WAWK;EACG,cAAA;;AAEJ,OApBR,YAMI,WAcK;EACG,cAAA;;AAhChB,OAWI,YAMI,WAiBI;AAlCZ,OAWI,YAMI,WAiBU;AAlClB,OAWI,YAMI,WAiBgB;EACR,eAAA;;AAnChB,OAuCI;EACI,WAAA;EACA,mBAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,YAAY,kBAAZ;EACA,cAAA;EACA,SAAA;;AA/CR,OAuCI,YASI;EACI,kBAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,uBAAA;EAEA,kBAAA;EACA,eAAA;;AACA,OAlBR,YASI,WASK;EACG,eAAA;EACA,oCAAA;;AACA,OArBZ,YASI,WASK,OAGI;EACG,eAAA;EACA,oCAAA;;AALR,OAlBR,YASI,WASK,OAOG;EACI,qBAAA;;AAGR,OA7BR,YASI,WAoBK;EACG,oCAAA;;AArEhB,OAuCI,YASI,WAuBI;EACI,WAAA;EACA,yBAAA;EACA,WAAA;EACA,YAAA;;AA3EhB,OAuCI,YASI,WAuBI,mBAKI,IAAG;EACC,WAAA;EACA,eAAA;EACA,gBAAA;;AA/EpB,OAuCI,YASI,WAkCI;AAlFZ,OAuCI,YASI,WAkCgB;EACR,WAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,4BAAA;EACA,kBAAA;EACA,eAAA;;AAzFhB,OAuCI,YASI,WA4CI;EACI,cAAA;;AACA,OAvDZ,YASI,WA4CI,WAEK;EACG,cAAA;;AA/FpB,OAuCI,YASI,WAkDI;EACI,cAAA;;AACA,OA7DZ,YASI,WAkDI,WAEK;EACG,cAAA;;ACrGpB;EACI,WAAA;EACA,WAAA;;AAFJ,QAGI;EACI,WAAA;EACA,YAAA;EACA,WAAA;;AACA,QAJJ,iBAIK;EACG,aAAA;;AAEJ,QAPJ,iBAOK;EACG,aAAA;;AAEJ,QAVJ,iBAUK;EACG,aAAA;;AAdZ,QAGI,iBAaI;EACI,WAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;EACA,qBAAA;EACA,eAAA;EACA,WAAA;EACA,kBAAA;;ACxBZ;EACI,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,8BAAA;EACA,cAAA;EACA,QAAA;EACA,UAAA;;AACA,gBAAiC;EA+CpC;IA9CO,YAAA;;;AAbR,KAeI;EACI,kBAAA;EACA,QAAA;EACA,eAAA;EACA,WAAA;;AAnBR,KAqBI;EACI,WAAA;EACA,WAAA;EACA,eAAA;;AAxBR,KAqBI,cAII;EACI,WAAA;EACA,WAAA;EACA,kBAAA;;AA5BZ,KAqBI,cASI;EACI,WAAA;EACA,WAAA;;AAhCZ,KAqBI,cASI,mBAGI;EACI,UAAA;EACA,WAAA;EACA,gBAAA;EACA,iBAAA;;AArChB,KAqBI,cASI,mBAGI,uBAKI;EACI,cAAA;EACA,eAAA;EACA,YAAA;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;EACA,cAAA;;AAjDpB,KAqBI,cASI,mBAsBI;EACI,UAAA;EACA,WAAA;EACA,gBAAA;;ACtDhB;EACI,YAAA;EACA,aAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,mBAAA;EACA,aAAA;EACA,aAAA;EACA,UAAA;EACA,aAAa,yBAAb;EACA,YAAY,yBAAZ;;AAXJ,QAYI,MAAK;EACD,YAAA;EACA,YAAA;EACA,iBAAA;EACA,YAAA;EACA,YAAA;EACA,kBAAA;EACA,WAAA;;AAnBR,QAqBI;EACI,YAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,cAAA;EACA,WAAA;EACA,gBAAA;;AA5BR,QA8BI;EACI,WAAA;EACA,YAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,iBAAA;EACA,kBAAA;EACA,mBAAA;EACA,eAAA;;AACA,QAVJ,cAUK;EACG,mBAAA;EACA,cAAA;;AC1CZ;EACI,YAAA;EACA,YAAA;EACA,kBAAA;EACA,YAAA;EACA,OAAA;EACA,mBAAA;EACA,aAAA;;AAPJ,KAQI;EACI,WAAA;EACA,YAAA;EACA,WAAA;EACA,aAAA;;AAZR,KAQI,WAKI;EACI,WAAA;EACA,cAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,cAAA;EACA,kBAAA;EACA,iBAAA;EACA,eAAA;;AA1BZ,KAQI,WAoBI;EACI,WAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,iBAAA;EACA,cAAA;EACA,eAAA;;ACnCZ;EACI,YAAA;EACA,aAAA;EACA,kBAAA;EACA,8BAAA;EACA,WAAA;EACA,aAAA;;AANJ,aAOI;EACI,YAAA;EACA,mBAAA;EACA,mBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;EACA,SAAA;EACA,UAAA;EACA,UAAA;;AAEJ,aAAC;AAAS,aAAC;EACP,kBAAA;EACA,SAAS,GAAT;EACA,mBAAA;EACA,UAAA;;AAEJ,aAAC;EACG,YAAA;EACA,WAAA;EACA,QAAA;EACA,SAAA;;AAEJ,aAAC;EACG,UAAA;EACA,aAAA;EACA,OAAA;EACA,SAAA;;ACnCR;EACI,WAAA;EACA,mBAAA;EACA,YAAA;EACA,iBAAA;EACA,WAAA;EACA,kBAAA;EACA,SAAA;EACA,eAAA;EACA,cAAA;;AATJ,UAUI;EACI,WAAA;EACA,YAAA;EACA,eAAA;EACA,oBAAA;EACA,kBAAA;;AAfR,UAiBI;EACI,eAAA;EACA,oBAAA;EACA,YAAA;EACA,QAAA;EACA,kBAAA;;AAtBR,UAiBI,QAMI;EACI,cAAA;EACA,eAAA;;AC1BZ;EACE,mCAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAW,qBAAX;EACA,kBAAA;EACA,WAAA;EACA,aAAA;;AACA,OAAC;EACC,cAAA;;AAEF,OAAC;EACC,YAAA;EACA,aAAA;EACA,MAAA;EACA,OAAA;EACA,WAAW,eAAX;;AALF,OAAC,WAMC,eACE;EACE,YAAA;EACA,2BAAA;;AATN,OAAC,WAMC,eAKE;EACE,YAAA;EACA,YAAA;EACA,iBAAA;;AAdN,OAAC,WAMC,eAKE,SAIE,MACE,KAAI;EACF,gBAAA;;AAjCZ,OAuCE;EACE,WAAA;EACA,YAAA;;AAzCJ,OAuCE,cAGE;EACE,wBAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,gBAAA;EACA,cAAA;;AAhDN,OAuCE,cAWE;EACE,WAAA;EACA,YAAA;EACA,YAAA;EACA,qBAAA;EACA,eAAA;EACA,YAAA;;AAxDN,OAuCE,cAWE,OAOE;EACE,eAAA;;AAEF,OArBJ,cAWE,OAUG;EACC,mBAAA;EACA,cAAA;;AAEF,OAzBJ,cAWE,OAcG;EACC,mBAAA;EACA,cAAA;EACA,kBAAA;;AAnER,OAuEE;EACE,WAAA;EACA,WAAA;EACA,gBAAA;;AA1EJ,OAuEE,eAIE;EACE,yBAAA;EACA,WAAA;EACA,kBAAA;;AA9EN,OAuEE,eAIE,gBAIE,OAAM;AA/EZ,OAuEE,eAIE,gBAKE,OAAM;AAhFZ,OAuEE,eAIE,gBAME,OAAM;AAjFZ,OAuEE,eAIE,gBAOE;EACE,WAAA;EACA,gBAAA;EACA,+BAAA;EACA,WAAA;;AAtFR,OAuEE,eAIE,gBAaE,OAAM;AAxFZ,OAuEE,eAIE,gBAcE,OAAM;EACJ,aAAA;;AA1FR,OAuEE,eAIE,gBAiBE,OAAM;EACJ,cAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;;AAhGR,OAuEE,eA4BE;EACE,YAAA;EACA,WAAA;;AArGN,OAuEE,eA4BE,SAGE,MACE;EACE,aAAA;;AACA,OAlCR,eA4BE,SAGE,MACE,MAEG,QAAS;EACR,mBAAA;EACA,cAAA;;AA3GZ,OAuEE,eA4BE,SAGE,MAQE,KAAI;EACF,WAAA;EACA,YAAA;EACA,oBAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,eAAA;EACA,WAAA;;AAvHV,OAuEE,eAoDE;EACE,WAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;;AA/HN,OAuEE,eAoDE,eAKE;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,eAAA;;AApIR,OAuEE,eAoDE,eAKE,OAKE;EACE,eAAA;;AAEF,OAjEN,eAoDE,eAKE,OAQG;EACC,qBAAA;;AAEF,OApEN,eAoDE,eAKE,OAWG;EACC,mBAAA;EACA,cAAA;;AAEF,OAxEN,eAoDE,eAKE,OAeG;EACC,mBAAA;EACA,cAAA;;AAEF,OA5EN,eAoDE,eAKE,OAmBG;EACC,mBAAA;EACA,cAAA;;AAOR,OAAC,QAEC,eACE;EACE,qBAAA;;AACA,OALL,QAEC,eACE,OAEG;EACC,aAAA;;AANR,OAAC,QAUC,eACE,gBACE,OAAM;AAZZ,OAAC,QAUC,eACE,gBAEE,MAAK;EACH,aAAA;;AAdR,OAAC,QAUC,eACE,gBAKE,OAAM;EACJ,cAAA;;AC7KV,IAAI;EACA,aAAA;EACA,YAAA;EACA,YAAA;EACA,eAAA;EACA,QAAA;EACA,SAAA;EACA,gBAAA;EACA,WAAA;;AARJ,IAAI,YASA,GACI;EACI,eAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,WAAA;EACA,gBAAA;EACA,cAAA;;AAlBZ,IAAI,YASA,GACI,GASI;EACI,eAAA;;AApBhB,IAAI,YASA,GACI,GAYI;EACI,WAAA;EACA,WAAA;;AAxBhB,IAAI,YASA,GACI,GAYI,SAGI;EACI,eAAA;EACA,WAAA;;AACA,IA5BhB,YASA,GACI,GAYI,SAGI,KAGK;EACG,WAAA;;AADJ,IA5BhB,YASA,GACI,GAYI,SAGI,KAGK,KAEG;EACI,WAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;;AAGR,IArChB,YASA,GACI,GAYI,SAGI,KAYK;EACG,yBAAA;;AAEJ,IAxChB,YASA,GACI,GAYI,SAGI,KAeK;EACG,iBAAA;EACA,yBAAA;EACA,WAAA;EACA,eAAA;EACA,cAAA;;AAEJ,IA/ChB,YASA,GACI,GAYI,SAGI,KAsBK;EACG,cAAA;EACA,WAAA;EACA,eAAA;EACA,eAAA;EACA,kBAAA;EACA,UAAA;;AArDxB,IAAI,YASA,GACI,GA+CI;EACI,YAAA;EACA,kBAAA;EACA,aAAA;EACA,MAAA;EACA,WAAA;;AAIY,IAlExB,YASA,GACI,GA+CI,SAMK,WACG,KACK,MACI;EACG,kBAAA;EACA,UAAA;;AAEJ,IAtExB,YASA,GACI,GA+CI,SAMK,WACG,KACK,MAKI;EACG,mBAAA;EACA,UAAA;EACA,YAAA;;AAMpB,IA/ER,YASA,GACI,GAqEK;EACG,cAAA;;AAEJ,IAlFR,YASA,GACI,GAwEK,IAAI,WAAW;EACZ,cAAA;EACA,yBAAA;;AAFJ,IAlFR,YASA,GACI,GAwEK,IAAI,WAAW,MAGZ;EACI,cAAA;EACA,cAAA;;AAKhB,IA5FA,YA4FC,aACG,GACI,GACI;EACI,YAAA;;AAIY,IApG5B,YA4FC,aACG,GACI,GACI,SAEK,WACG,KACK,MACI;EACG,mBAAA;EACA,UAAA;;AAEJ,IAxG5B,YA4FC,aACG,GACI,GACI,SAEK,WACG,KACK,MAKI;EACG,kBAAA;EACA,UAAA;;AC7FpC;EACI,sBAAA;EACA,yBAAA;EACA,qBAAA;EACA,iBAAA;EACA,eAAA;EACA,aAAa,uBAAb;EACA,sBAAA;EACA,2BAAA;EACA,8BAAA;EACA,SAAA;EACA,UAAA;;AAEJ;EACI,gBAAA;;AAGJ,IACI;EACI,MAAA;EACA,OAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,gBAAA;;AARR,IACI,KAQI;EACI,WAAA;EACA,mBAAA;EACA,cAAA;EACA,OAAO,mBAAP;EACA,QAAQ,kBAAR;;AAdZ,IACI,KAQI,SAMI;EACI,0BAAA;EACA,kBAAA;EACA,gBAAA;;AAlBhB,IACI,KAQI,SAMI,aAII;AAnBhB,IACI,KAQI,SAMI,aAIW;AAnBvB,IACI,KAQI,SAMI,aAIyB;EACjB,YAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;;;AAvBpB,IACI,KAQI,SAMI,aAWI;EACI,UAAA;EACA,kBAAA;;AA5BpB,IACI,KAQI,SAMI,aAeI,MACI;EACI,kBAAA;EACA,iBAAA;EACA,0BAAA;;AACA,IAlCpB,KAQI,SAMI,aAeI,MACI,OAIK;EACG,kBAAA;;AApC5B,IACI,KAyCI;AA1CR,IACI,KA0CI;AA3CR,IACI,KA2CI;AA5CR,IACI,KA4CI;EACI,aAAA;EACA,kBAAA;EACA,UAAA;EACA,yBAAA;;AAjDZ,IACI,KAkDI;EACI,YAAA;;AApDZ,IACI,KAqDI;EACI,mBAAA;EACA,qBAAA"} \ No newline at end of file diff --git a/build/css/style.min.css b/build/css/style.min.css deleted file mode 100644 index e6ff274..0000000 --- a/build/css/style.min.css +++ /dev/null @@ -1 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Open+Sans);input[type=checkbox]+label{cursor:pointer;color:#a9a9a9;width:100%;background:0 0;text-align:left;border:none;border-radius:0}input[type=checkbox]+label .fa-check-circle,input[type=checkbox]+label .fa-circle{float:left;text-align:center;width:30px;vertical-align:middle;margin:0 0 0 10px;font-size:18px;height:20px;line-height:20px}input[type=checkbox]+label .fa-check-circle{display:none}input[type=checkbox]+label #l{margin-left:10px;height:20px;line-height:20px}input[type=checkbox]+label span{width:100%;height:20px;text-align:left}input[type=checkbox]+label span.label{width:auto}input[type=checkbox]:checked+label{font-weight:700;color:#0052cc}input[type=checkbox]:checked+label .fa-circle{display:none}input[type=checkbox]:checked+label .fa-check-circle{display:block}input[type=checkbox]:hover+label{color:#fff}input[type=checkbox]:hover+label .fa-circle{display:none}input[type=checkbox]:hover+label .fa-check-circle{display:block}input[type=range]{-webkit-appearance:none;width:calc(100% - 20px);margin:0 10px}input[type=range]::-webkit-slider-runnable-track{width:230px;height:10px;background:#ddd;border:none;border-radius:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:10px;width:10px;border-radius:0;background:#0052cc}input[type=range]::-moz-range-track{width:230px;height:10px;background:#ddd;border:none}input[type=range]::-moz-range-thumb{border:none;height:10px;width:10px;border-radius:0;background:#0052cc}input[type=range]::-ms-track{width:230px;height:10px;background:0 0;border-color:transparent;border-width:0;color:transparent}input[type=range]::-ms-thumb{border:none;height:10px;width:10px;border-radius:0;background:#0052cc}input[type=range]:-moz-focusring{outline:0}input[type=range]::-ms-fill-lower{background:#ddd}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]:focus{outline:0}input[type=range]:focus::-ms-fill-lower{background:#888}input[type=range]:focus::-webkit-slider-runnable-track{background:#ccc}input[type=range]:focus::-ms-fill-upper{background:#ccc}input[type=color]{visibility:hidden;width:0;height:0;position:absolute}input#fileUploaderInput{display:none}input#isFillSet{display:none}input#isShadow{display:none}#toolsGroup{width:200px;height:calc(100vh - 20px);background:#2c2b2b;float:left}#toolsGroup #previewColorSampler{width:100%;height:60px;background:0 30px url(/pic/transparent.png);z-index:1;display:none}#toolsGroup #previewColorSampler #textColorSampler{width:100%;float:left;height:30px;background:#2c2b2b;line-height:30px;text-align:center;font-size:13px;color:#eee}#toolsGroup #previewColorSampler #colorBoxSampler{width:100%;height:30px;float:left}#toolsGroup .tools{width:100%;background:#2c2b2b;float:left;z-index:4;color:#a9a9a9}#toolsGroup .tools .title{width:100%;height:20px;line-height:20px;float:left;font-size:15px;text-align:center;background:#eee;color:#2e3436}#toolsGroup .tools .title .fab,#toolsGroup .tools .title .far,#toolsGroup .tools .title .fas{position:relative;height:inherit;line-height:inherit;left:0;top:0;text-align:center}#toolsGroup .tools #toolsBtns{width:100%;float:left}#toolsGroup .tools #toolsBtns .paintTools,#toolsGroup .tools #toolsBtns .primaryTool{width:50px;height:30px;line-height:30px;text-align:center;float:left;color:#a9a9a9;cursor:pointer}#toolsGroup .tools #toolsBtns .paintTools .fab,#toolsGroup .tools #toolsBtns .paintTools .far,#toolsGroup .tools #toolsBtns .paintTools .fas,#toolsGroup .tools #toolsBtns .primaryTool .fab,#toolsGroup .tools #toolsBtns .primaryTool .far,#toolsGroup .tools #toolsBtns .primaryTool .fas{font-size:15px}#toolsGroup .tools #toolsBtns .paintTools:hover,#toolsGroup .tools #toolsBtns .primaryTool:hover{color:#fff}#toolsGroup .tools #toolsBtns .paintTools.active{cursor:default;color:#0052cc;background:#1c1b1b}#toolsGroup .tools #toolsBtns .primaryTool:active{cursor:default;color:#0052cc;background:#1c1b1b}#toolsGroup .tools #radioBtns{width:100%;float:left}#toolsGroup .tools #radioBtns #fillShapeInput,#toolsGroup .tools #radioBtns #shadowInput{width:100%;line-height:20px;float:left;padding:5px 0;font-size:12px}#toolsGroup .tools #radioBtns #fillShapeInput *,#toolsGroup .tools #radioBtns #shadowInput *{cursor:pointer;float:left}#toolsGroup .tools #radioBtns #fillShapeInput{display:none}#toolsGroup .tools #colorPicker{width:100%;float:left}#toolsGroup .tools #colorPicker .colorTab{width:100%;height:25px;line-height:25px;font-size:14px;cursor:pointer}#toolsGroup .tools #colorPicker .colorTab .color{border-radius:100%;width:15px;height:15px;margin:5px 10px;float:left;border:1px solid #ccc}#toolsGroup .tools #colorPicker .colorTab#generalColor .color{background:#000}#toolsGroup .tools #colorPicker .colorTab#shadowColor{display:none}#toolsGroup .tools #colorPicker .colorTab#shadowColor .color{background:#232324}#toolsGroup .tools #colorPicker .colorTab#fillColor{display:none}#toolsGroup .tools #colorPicker .colorTab#fillColor .color{background:#fff}#toolsGroup .tools #colorPicker .colorTab:hover{color:#0052cc;background:#1c1b1b}#textOptions{display:none}#textOptions #alignBtns{width:100%;float:left}#textOptions #alignBtns .textPostionTool{width:50px;height:30px;line-height:30px;text-align:center;float:left;color:#a9a9a9;cursor:pointer}#textOptions #alignBtns .textPostionTool .fab,#textOptions #alignBtns .textPostionTool .far,#textOptions #alignBtns .textPostionTool .fas{font-size:15px}#textOptions #alignBtns .textPostionTool.active,#textOptions #alignBtns .textPostionTool:hover{color:#fff}#textOptions #fontStyleBtns{width:100%;float:left}#textOptions #fontStyleBtns .styleTool{width:50px;height:30px;line-height:30px;text-align:center;float:left;color:#a9a9a9;cursor:pointer}#textOptions #fontStyleBtns .styleTool .fab,#textOptions #fontStyleBtns .styleTool .far,#textOptions #fontStyleBtns .styleTool .fas{font-size:15px}#textOptions #fontStyleBtns .styleTool.active,#textOptions #fontStyleBtns .styleTool:hover{color:#fff}#textRectangle{display:none;position:absolute;z-index:2;border:1px solid #000;font-size:8mm;cursor:move}#layers .title{width:100%;height:20px;line-height:20px;float:left;font-size:15px;text-align:center;background:#eee;color:#2e3436}#layers #layersBtns{width:100%;background:#2c2b2b;float:left;z-index:4;color:#a9a9a9}#layers #layersBtns .layerTool{width:50px;height:30px;line-height:30px;text-align:center;float:left;color:#a9a9a9;cursor:pointer}#layers #layersBtns .layerTool#addLayer{color:#16c600}#layers #layersBtns .layerTool#delLayer{color:#c50000}#layers #layersBtns .layerTool:hover{color:#fff}#layers #layersBtns .layerTool .fab,#layers #layersBtns .layerTool .far,#layers #layersBtns .layerTool .fas{font-size:15px}#layers #listLayers{width:100%;background:#2c2b2b;float:left;z-index:4;color:#a9a9a9;max-height:calc(50vh - 170px);overflow:auto;top:50px}#layers #listLayers .layerView{position:relative;width:100%;height:60px;margin:10px auto;background:0 0;text-align:center;cursor:pointer}#layers #listLayers .layerView.active{cursor:default;background-color:rgba(0,0,0,.1)}#layers #listLayers .layerView.active:hover{cursor:default;background-color:rgba(0,0,0,.1)}#layers #listLayers .layerView.active .imgLayerContainer{border-color:#0021cc}#layers #listLayers .layerView:hover{background-color:rgba(0,0,0,.3)}#layers #listLayers .layerView .imgLayerContainer{float:left;border:1px solid #ccc;width:60px;height:60px}#layers #listLayers .layerView .imgLayerContainer img.imgLayer{float:left;max-width:58px;max-height:58px}#layers #listLayers .layerView .hideLayer,#layers #listLayers .layerView .showLayer{float:left;margin:21px 5px;width:18px;height:18px;line-height:18px!important;text-align:center;cursor:pointer}#layers #listLayers .layerView .showLayer{color:#c50000}#layers #listLayers .layerView .showLayer:hover{color:#16c600}#layers #listLayers .layerView .hideLayer{color:#16c600}#layers #listLayers .layerView .hideLayer:hover{color:#c50000}#sliders{width:100%;float:left}#sliders .sliderContainer{width:100%;height:45px;float:left}#sliders .sliderContainer#shadow_slider{display:none}#sliders .sliderContainer#sensitivityPoints_slider{display:none}#sliders .sliderContainer#fillOpacity_slider{display:none}#sliders .sliderContainer .sliderVal{width:100%;height:20px;margin-top:5px;line-height:20px;word-wrap:break-word;font-size:13px;float:left;text-align:center}#info{width:350px;height:100vh;z-index:5;position:absolute;display:none;overflow:auto;background:#2c2b2b;border-left:1px solid #a9a9a9;color:#fff;top:0;right:0}@media all and (max-width:400px){#info{width:100vw}}#info .close-info{position:absolute;right:0;cursor:pointer;margin:5px}#info .info-section{width:100%;float:left;margin:5px 0}#info .info-section .info-section-title{width:100%;float:left;text-align:center}#info .info-section .info-section-line{width:100%;float:left}#info .info-section .info-section-line .info-section-shortcut{width:30%;float:left;padding:2px 5px;text-align:right}#info .info-section .info-section-line .info-section-shortcut span{padding:0 5px;min-width:30px;height:20px;border-radius:2px;margin:15px 5px;text-align:center;line-height:20px;font-size:12px;background:#f1f1f1;border:1px solid #ccc;color:#2c2b2b}#info .info-section .info-section-line .info-section-shortcut-describe{width:70%;float:left;padding:2px 5px}#resizer{width:300px;height:210px;position:absolute;top:0;left:0;background:#2c2b2b;display:none;padding:10px;z-index:4;margin-left:calc((100vw - 300px)/ 2);margin-top:calc((100vh - 210px)/ 2)}#resizer input[type=number]{width:250px;height:30px;margin:10px 15px;border:none;cursor:text;text-align:center;float:left}#resizer #resizeinfo{width:250px;height:30px;line-height:30px;text-align:center;color:#ccc;float:left;margin:0 20px}#resizer .buttonAction{float:left;width:120px;height:30px;line-height:30px;font-size:15px;margin:10px 10px;text-align:center;background:#ccc;cursor:pointer}#resizer .buttonAction:hover{background:#0052cc;color:#ccc}#hint{width:100vw;height:50px;position:absolute;bottom:20px;left:0;background:#2c2b2b;display:none}#hint .hintGroup{width:100%;height:100%;float:left;display:none}#hint .hintGroup .keyboardButton{float:left;padding:0 5px;min-width:30px;height:20px;background:#f1f1f1;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;margin:15px 5px;color:#2c2b2b;text-align:center;line-height:20px;font-size:12px}#hint .hintGroup .describeHint{width:auto;float:left;height:50px;line-height:50px;margin:0 5px 0 0;color:#f1f1e4;font-size:12px}#shadowSquare{width:200px;height:200px;position:relative;background:rgba(0,0,0,.4);float:left;display:none}#shadowSquare #shadowDot{cursor:move;background:#fff;border-radius:100%;width:10px;height:10px;position:absolute;top:95px;left:95px;z-index:2}#shadowSquare::after,#shadowSquare::before{position:absolute;content:' ';background:#a9a9a9;z-index:1}#shadowSquare::after{width:200px;height:1px;top:50%;left:0}#shadowSquare::before{width:1px;height:200px;top:0;left:50%}#statusbar{width:100%;background:#2c2b2b;height:20px;line-height:20px;float:left;position:absolute;bottom:0;font-size:13px;color:#ccc}#statusbar #mousePosition{float:left;width:100px;height:inherit;line-height:inherit;text-align:center}#statusbar #author{height:inherit;line-height:inherit;float:right;right:0;margin-right:20px}#statusbar #author a{color:#eee;cursor:pointer}#camera{box-shadow:0 0 5px 2px #6f6f6f;width:740px;max-width:100vw;height:560px;max-height:100vh;background:#2c2b2b;top:50%;left:50%;transform:translate(-50%,-50%);position:absolute;z-index:99;display:none}#camera.opened{display:block}#camera.fullscreen{width:100vw;height:100vh;top:0;left:0;transform:translate(0,0)}#camera.fullscreen .cameraContent .videoContainer{width:100vw;height:calc(100vh - 130px)}#camera.fullscreen .cameraContent .filters{width:100vw;height:50px;line-height:50px}#camera.fullscreen .cameraContent .filters label span.filter{margin:15px 5px}#camera #cameraHeader{width:100%;height:30px}#camera #cameraHeader #cameraTitle{width:calc(100% - 70px);float:left;height:30px;line-height:30px;padding:0 5px;color:#fff}#camera #cameraHeader button{width:30px;height:30px;border:none;display:inline-block;cursor:pointer;float:right}#camera #cameraHeader button i{cursor:pointer}#camera #cameraHeader button#closeCamera{background:#c50000;color:#fff}#camera #cameraHeader button#switchFullscreen{background:#0021cc;color:#fff;margin-right:10px}#camera .cameraContent{width:100%;float:left;margin-top:10px}#camera .cameraContent .videoContainer{width:calc(100% - 100px);float:left;position:relative}#camera .cameraContent .videoContainer canvas#additional,#camera .cameraContent .videoContainer canvas#frame,#camera .cameraContent .videoContainer canvas#snapImage,#camera .cameraContent .videoContainer video{width:100%;max-width:100vw;max-height:calc(100vh - 130px);float:left}#camera .cameraContent .videoContainer canvas#frame,#camera .cameraContent .videoContainer canvas#snapImage{display:none}#camera .cameraContent .videoContainer canvas#additional{display:block;position:absolute;top:0;left:0}#camera .cameraContent .filters{width:100px;float:left}#camera .cameraContent .filters label input{display:none}#camera .cameraContent .filters label input:checked+span{background:#0052cc;color:#fff}#camera .cameraContent .filters label span.filter{width:70px;height:20px;margin:0 15px 5px;cursor:pointer;background:#eee;line-height:20px;text-align:center;font-size:13px;float:left}#camera .cameraContent #cameraButtons{float:left;width:100%;height:50px;line-height:50px}#camera .cameraContent #cameraButtons button{min-width:50px;border:none;display:none;cursor:pointer}#camera .cameraContent #cameraButtons button *{cursor:pointer}#camera .cameraContent #cameraButtons button#snap{display:inline-block}#camera .cameraContent #cameraButtons button#applySnap{background:#129a01;color:#fff}#camera .cameraContent #cameraButtons button#cancelSnap{background:#c50000;color:#fff}#camera .cameraContent #cameraButtons button#saveSnapOnComputer{background:#0052cc;color:#fff}#camera.snapped #cameraButtons button{display:inline-block}#camera.snapped #cameraButtons button#snap{display:none}#camera.snapped .cameraContent .videoContainer canvas#additional,#camera.snapped .cameraContent .videoContainer video#video{display:none}#camera.snapped .cameraContent .videoContainer canvas#snapImage{display:block}menu#contextmenu{display:none;width:230px;height:auto;position:fixed;top:50%;left:50%;background:#fff;z-index:10}menu#contextmenu ul li{cursor:pointer;float:left;height:30px;line-height:30px;position:relative;width:100%;background:#fff;color:#000}menu#contextmenu ul li *{cursor:pointer}menu#contextmenu ul li .content{width:100%;float:left}menu#contextmenu ul li .content span{height:inherit;float:left}menu#contextmenu ul li .content span.icon{width:20px}menu#contextmenu ul li .content span.icon i{float:left;width:20px;height:30px;line-height:30px}menu#contextmenu ul li .content span.text{width:calc(100% - 130px)}menu#contextmenu ul li .content span.shortcut{text-align:right;text-transform:uppercase;width:70px;font-size:11px;color:#2e3436}menu#contextmenu ul li .content span.arrow{color:#2e3436;width:10px;margin:0 5px;font-size:11px;visibility:hidden;opacity:0}menu#contextmenu ul li .submenu{width:230px;position:absolute;display:none;top:0;left:230px}menu#contextmenu ul li .submenu+.content span.arrow.left-arrow{visibility:hidden;opacity:0}menu#contextmenu ul li .submenu+.content span.arrow.right-arrow{visibility:visible;opacity:1;float:right}menu#contextmenu ul li.disabled{color:#888}menu#contextmenu ul li:not(.disabled):hover{color:#0052cc;background-color:#dedede}menu#contextmenu ul li:not(.disabled):hover .submenu{display:block;color:#000}menu#contextmenu.left-submenu ul li .submenu{left:-230px}menu#contextmenu.left-submenu ul li .submenu+.content span.arrow.left-arrow{visibility:visible;opacity:1}menu#contextmenu.left-submenu ul li .submenu+.content span.arrow.right-arrow{visibility:hidden;opacity:0}*{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;cursor:default;font-family:'Open Sans',sans-serif;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;margin:0;padding:0}ul{list-style:none}html body{top:0;left:0;width:100%;height:100%;float:left;position:fixed;overflow:hidden}html body #content{float:left;background:#373636;overflow:auto;width:calc(100vw - 200px);height:calc(100vh - 20px)}html body #content #drawHandler{border:1px dotted #0052cc;position:relative;overflow:hidden}html body #content #drawHandler #draw,html body #content #drawHandler #drawHandler,html body #content #drawHandler #eventHandler{width:600px;height:400px;overflow:hidden;position:relative}html body #content #drawHandler #eventHandler{z-index:3;position:absolute}html body #content #drawHandler #draw canvas{position:absolute;cursor:crosshair;border:1px dotted #0052cc}html body #content #drawHandler #draw canvas.invisible{visibility:hidden}html body #eraseRect,html body #prepareCircle,html body #prepareRect,html body #selectRectangle{display:none;position:absolute;z-index:2;border:1px solid #000}html body #selectRectangle{cursor:move}html body #eraseRect{border-radius:100%;border-color:#ccc} \ No newline at end of file diff --git a/build/js/built.js b/build/js/built.js deleted file mode 100644 index e69e81a..0000000 --- a/build/js/built.js +++ /dev/null @@ -1,2049 +0,0 @@ -class Camera { - constructor() { - this.cameraStream = null; - this.filters = { - 'sepia': false, - 'noise': false, - 'greyscale': false, - 'negative': false - }; - let $this = this; - this.filterInterval = null; - $.get('parts/camera.part.html', function (data) { - $('body').append('
'); - - $('#camera').html(data); - - $('#snap').click($this.snap); - $('#saveSnapOnComputer').click($this.saveOnComputer); - $('#applySnap').click($this.applySnap); - $('#cancelSnap').click($this.cancelSnap); - $('#switchFullscreen').click(function (e) { - $('#camera').toggleClass('fullscreen'); - }) - $('#closeCamera').click(function () { - $this.stop(); - }); - $('.filter-checkbox').change(function (e) { - let filterName = $(this).parent().find('.filter').data('id'); - $this.filters[filterName] = $(this).is(':checked'); - }); - }); - } - - init() { - let $this = this; - $('#camera').addClass('opened'); - let video = document.getElementById('video'); - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) { - video.src = window.URL.createObjectURL(stream); - video.play(); - $this.cameraStream = stream; - $('#camera #cameraTitle').text($this.cameraStream.getTracks()[0].label); - $this.setFilter(); - }); - } - } - - stop() { - let $this = this; - let track = $this.cameraStream.getTracks()[0]; - track.stop(); - $this.cameraStream = null; - $('#camera').removeClass('opened'); - } - - snap() { - let snapImage = document.getElementById('snapImage'); - let snapImageContext = snapImage.getContext('2d'); - let video = document.getElementById('video'); - let additionalLayer = document.getElementById('additional'); - let additionalLayerContext = additionalLayer.getContext('2d'); - let videoImageData = additionalLayerContext.getImageData(0, 0, additionalLayer.width, additionalLayer.height); - snapImageContext.putImageData(videoImageData, 0, 0); - $('#camera').addClass('snapped'); - } - - saveOnComputer() { - file.downloadFromCamera('snapImage'); - } - - cancelSnap() { - let snapImage = document.getElementById('snapImage'); - let snapImageContext = snapImage.getContext('2d'); - snapImageContext.clearRect(0, 0, snapImage.width, snapImage.height); - $('#camera').removeClass('snapped'); - if (this.filterInterval) { - clearInterval(this.filterInterval); - } - } - - applySnap() { - let snapImage = document.getElementById('snapImage'); - layers.newLayer(); - layers.setLayerSize(layers.activeId, snapImage.width, snapImage.height); - webDraft.positionElements(); - ctx.drawImage(snapImage, 0, 0); - layers.saveState(); - $('#camera').removeClass('snapped'); - } - - setFilter() { - let additionalLayer = document.getElementById('additional'); - let additionalLayerContext = additionalLayer.getContext('2d'); - let frame = document.getElementById('frame'); - let frameContext = frame.getContext('2d'); - let video = document.getElementById('video'); - let videoImageData = additionalLayerContext.getImageData(0, 0, additionalLayer.width, additionalLayer.height); - if (this.filterInterval) { - clearInterval(this.filterInterval); - } - let $this = this; - setInterval(function (e) { - frameContext.drawImage(video, 0, 0, video.width, video.height); - videoImageData = frameContext.getImageData(0, 0, video.width, video.height); - - for (let i in $this.filters) { - if ($this.filters[i]) { - videoImageData = $this[i + 'Filter'](videoImageData); - } - } - - additionalLayerContext.putImageData(videoImageData, 0, 0); - }, 1) - } - - negativeFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - pixels[i] = 255 - pixels[i]; // red - pixels[i + 1] = 255 - pixels[i + 1]; // green - pixels[i + 2] = 255 - pixels[i + 2]; // blue - } - - return imageData; - } - - greyscaleFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let r = pixels[i]; - let g = pixels[i + 1]; - let b = pixels[i + 2]; - let avg = 0.3 * r + 0.59 * g + 0.11 * b; - - pixels[i] = avg; // red - pixels[i + 1] = avg; // green - pixels[i + 2] = avg; // blue - } - - return imageData; - } - - noiseFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let rand = (0.5 - Math.random()) * 100; - pixels[i] += rand; // red - pixels[i + 1] += rand; // green - pixels[i + 2] += rand; // blue - } - - return imageData; - } - - sepiaFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let r = pixels[i]; - let g = pixels[i + 1]; - let b = pixels[i + 2]; - let avg = 0.3 * r + 0.59 * g + 0.11 * b; - - pixels[i] = avg + 100; // red - pixels[i + 1] = avg + 50; // green - pixels[i + 2] = avg; // blue - } - - return imageData; - } -} - -class Contextmenu { - constructor(items) { - this.items = items; - let $this = this; - $('body').append(''); - $('#contextmenu').html(''); - for (let i in items) { - let li = this.buildItem(this.items[i]); - - $('#contextmenu ul').append(li); - - if (this.items[i].onclick) { - $('#contextmenu ul li').last().click(function (e) { - $this.items[i].onclick(); - $this.hide(e); - }); - } - - if (this.items[i].submenu && this.items[i].submenu.length > 0) { - $('#contextmenu ul li').last().prepend(''); - for (let j in this.items[i].submenu) { - - let li = this.buildItem(this.items[i].submenu[j]); - - $('#contextmenu ul li ul.submenu').append(li); - - if (this.items[i].submenu[j].onclick) { - $('#contextmenu ul li').last().click(function (e) { - $this.items[i].submenu[j].onclick(); - $this.hide(e); - }); - } - } - } - } - - $('body').contextmenu(function (e) { - e.preventDefault(); - $this.show(e); - }).click(function (e) { - if (!$('#contextmenu').is(":hover")) { - $this.hide(e); - } - }); - } - show(e) { - let t = e.pageY; - let l = e.pageX; - $('#contextmenu').removeClass('left-submenu'); - if (t + $('#contextmenu').height() > $(window).height()) { - t = $(window).height() - $('#contextmenu').height(); - } - if (l + $('#contextmenu').width() > $(window).width()) { - l = $(window).width() - $('#contextmenu').width(); - } - if (l + ($('#contextmenu').width() * 2) > $(window).width()) { - $('#contextmenu').addClass('left-submenu'); - } - $('#contextmenu').css({ - top: t + "px", - left: l + "px" - }).show(); - } - hide(e) { - $('#contextmenu').hide(); - } - buildItem(item) { - let arrowLeft = ''; - let arrowRight = ''; - let shortcut = ''; - let text = ''; - let icon = '' + arrowLeft + icon + text + shortcut + arrowRight + ''; - } -} -class Draw { - drawStyle() { - ctx.lineWidth = webDraft.size; - - if (webDraft.selectedTool === RECTANGLE) { - ctx.lineJoin = ctx.lineCap = 'miter'; - } else { - ctx.lineJoin = ctx.lineCap = 'round'; - } - - if (shapes.fill.isSet === true) { - ctx.fillStyle = hexToRgba(shapes.fill.color, shapes.fill.opacity); - } else { - ctx.fillStyle = "transparent"; - } - - if (webDraft.selectedTool === ERASER) { - ctx.globalCompositeOperation = "destination-out"; - } else { - ctx.globalCompositeOperation = "source-over"; - } - - if (webDraft.shadow.isShadow === true && webDraft.selectedTool !== ERASER) { - ctx.shadowBlur = webDraft.shadow.blur; - ctx.shadowColor = webDraft.shadow.color; - ctx.shadowOffsetX = webDraft.shadow.offsetX; - ctx.shadowOffsetY = webDraft.shadow.offsetY; - } else { - ctx.shadowBlur = 0; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - } - - ctx.strokeStyle = webDraft.color;//line color - } - drawing() { - ctx.beginPath(); - ctx.moveTo(webDraft.mPosition.x, webDraft.mPosition.y); - this.drawStyle(); - ctx.lineTo(webDraft.mPosition.x, webDraft.mPosition.y); - ctx.stroke(); - } - drawWeb() { - ctx.beginPath(); - this.drawStyle(); - ctx.moveTo(points[layers.activeId][points[layers.activeId].length - 2].x, points[layers.activeId][points[layers.activeId].length - 2].y); - ctx.lineTo(points[layers.activeId][points[layers.activeId].length - 1].x, points[layers.activeId][points[layers.activeId].length - 1].y); - ctx.stroke(); - let dx, dy, d; - let len = points[layers.activeId].length; - for (let i = 0; i < len; i++) { - dx = points[layers.activeId][i].x - points[layers.activeId][points[layers.activeId].length - 1].x; - dy = points[layers.activeId][i].y - points[layers.activeId][points[layers.activeId].length - 1].y; - d = dx * dx + dy * dy; - if (d < webDraft.sensitivityPoints) { - ctx.beginPath(); - this.drawStyle(); - ctx.moveTo(points[layers.activeId][points[layers.activeId].length - 1].x + (dx * 0.2), points[layers.activeId][points[layers.activeId].length - 1].y + (dy * 0.2)); - ctx.lineTo(points[layers.activeId][i].x - (dx * 0.2), points[layers.activeId][i].y - (dy * 0.2)); - ctx.stroke(); - } - } - } -} -var events = { - color: function () { - $('#generalColor').colorpicker({ - onSelect: function (color) { - $('#generalColor .color').css({ - 'background': color.hex - }); - webDraft.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - $('#shadowColor').colorpicker({ - onSelect: function (color) { - $('#shadowColor .color').css({ - 'background': color.hex - }); - webDraft.shadow.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - $('#fillColor').colorpicker({ - onSelect: function (color) { - $('#fillColor .color').css({ - 'background': color.hex - }); - shapes.fill.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - }, - info: function () { - $(".close-info").click(function () { - $("#info").hide(); - }); - }, - buttons: function () { - $("input[type=checkbox]#isShadow").change(function () { - webDraft.shadow.isShadow = $(this).is(":checked");//return true if is :checked or false if not - - if (webDraft.shadow.isShadow) { - $("#shadowColor, #shadow_slider, #shadowSquare").show(); - } else { - $("#shadowColor, #shadow_slider, #shadowSquare").hide(); - } - }); - $("input[type=checkbox]#isFillSet").change(function () { - shapes.fill.isSet = $(this).is(":checked");//return true if is :checked or false if not - - if (shapes.fill.isSet) { - $("#fillColor, #fillOpacity_slider").show(); - } else { - $("#fillColor, #fillOpacity_slider").hide(); - } - }); - - $(".paintTool").click(function () { - $(".paintTool").removeClass("active"); - $(this).addClass("active"); - - var thisId = $(this).attr("id"); - - webDraft.selectedTool = thisId; - - if (thisId === COLORSAMPLER) { - $("#previewColorSampler").show(); - } else { - $("#previewColorSampler").hide(); - } - - if (thisId === WEB) { - $("#sensitivityPoints_slider").show(); - } else { - $("#sensitivityPoints_slider").hide(); - } - - if (thisId === ERASER) { - $("#eraseRect").show(); - $("#draw, #drawHandler, #eventHandler").css({"cursor": "none"}); - } else { - $("#eraseRect").hide(); - $("#draw, #drawHandler, #eventHandler").css({"cursor": "default"}); - } - - if (thisId !== RECTANGLE) { - $("#prepareRect").hide(); - } - - if (thisId !== CIRCLE) { - $("#prepareCircle").hide(); - } - - if (thisId !== SELECT) { - $("#selectRectangle").hide(); - $("#hint, .hintGroup#selecting").hide(); - } else { - $("#hint, .hintGroup#selecting").show(); - } - - if (thisId !== TEXT) { - $("#textRectangle, #textOptions").hide(); - } - - if (thisId === RECTANGLE || thisId === CIRCLE) { - $("#fillShapeInput").show(); - } else { - $("#fillShapeInput").hide(); - $("input#isFillSet").attr('checked', false).change(); - } - }); - }, - sliders: function () { - //changing size - $("input[type=range]#pointSize").on('mousemove touchmove', function () { - webDraft.size = $(this).val(); - $("#pointSizeValue").text("size:" + $(this).val() + "px"); - }); - //changing shadow blur - $("input[type=range]#ShadowBlur").on('mousemove touchmove', function () { - webDraft.shadow.blur = $(this).val(); - $("#ShadowBlurValue").text("blur:" + $(this).val() + "px"); - }); - //changing sensitivity of web points - $("input[type=range]#sensitivityPoints").on('mousemove touchmove', function () { - webDraft.sensitivityPoints = $(this).val(); - $("#sensitivityPointsValue").text("sensitivity:" + Math.floor($(this).val() / 100) + "%"); - }); - //changing fill opacity - $("input[type=range]#fillOpacity").on('mousemove touchmove', function () { - shapes.fill.opacity = $(this).val(); - $("#fillOpacityValue").text("fill opacity:" + Math.floor($(this).val()) + "%"); - }); - } - -}; -class File { - constructor() { - $('body').append(''); - } - - upload(event) { - let input = event.target; - let widthImg; - let heightImg; - let output = new Image(); - - output.src = URL.createObjectURL(input.files[0]); - - output.onload = function () { - widthImg = this.width; - heightImg = this.height; - - layers.newLayer(); - layers.setLayerSize(layers.activeId, widthImg, heightImg); - webDraft.positionElements(); - - ctx.drawImage(output, 0, 0); - - layers.saveState(); - keys.O = false; - }; - } - download() { - $(webDraft.draw.selectorId).append(''); - - let temp_c = document.getElementById("tmpCanvas"); - let temp_ctx = temp_c.getContext("2d"); - $("#tmpCanvas").width(webDraft.draw.width).height(webDraft.draw.height); - - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string" && layers.list[i].visible === true) { - let imgData = document.getElementById(layers.list[i].id); - let top = parseInt($("#" + layers.list[i].id).css("top")); - let left = parseInt($("#" + layers.list[i].id).css("left")); - temp_ctx.drawImage(imgData, top, left); - } - } - - temp_c.toBlob(function (blob) { - saveAs(blob, "WebDraft-image.png"); - }); - - $("#tmpCanvas").remove(); - } - downloadFromCamera(id = null) { - let imgData = document.getElementById(id); - $(webDraft.draw.selectorId).append(''); - - let temp_c = document.getElementById("tmpCanvas"); - let temp_ctx = temp_c.getContext("2d"); - $("#tmpCanvas").width(imgData.width).height(imgData.height); - - temp_ctx.drawImage(imgData, 0, 0); - - temp_c.toBlob(function (blob) { - saveAs(blob, "WebDraft-camera-photo.png"); - }); - - $("#tmpCanvas").remove(); - - } -} -var keys = { - Enter: false, //press Enter - Esc: false, //press Escape (Esc) - f11: false, //press F11 - f12: false, //press F12 - delete: false, //press delete - C: false, - X: false, - V: false, - O: false, - I: false -}; - -$(document) - .keydown(function (event) { - if (webDraft.selectedTool !== TEXT && $('#resizer').is('hidden')) { - event.preventDefault(); - } - - switch (event.keyCode) { - case 13 : - keys.Enter = true; - break; - case 27 : - keys.Esc = true; - break; - case 46 : - keys.delete = true; - break; - case 67 : - keys.C = true; - break; - case 73 : - keys.I = true; - break; - case 79 : - keys.O = true; - break; - case 83 : - keys.S = true; - break; - case 86 : - keys.V = true; - break; - case 88 : - keys.X = true; - break; - case 122 : - keys.f11 = true; - break; - case 123 : - keys.f12 = true; - break; - } - if (keys.delete) { - if (webDraft.selectedTool === SELECT) { - select.delSelectedPart(); - } - if (event.ctrlKey) { - webDraft.clear(); - } - } - - if (keys.f12 || keys.f11 || event.ctrlKey || keys.delete) { - event.preventDefault(); - } - - if (keys.C) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.copySelectedPart(); - } - } - - if (keys.I) { - if (event.ctrlKey) { - $("#info").toggle(); - } - } - - if (keys.X) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.cutSelectedPart(); - } - } - - if (keys.V) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.pasteSelectedPart(); - } - } - if (keys.O) { - if (event.ctrlKey) { - $("#fileUploader").click(); - } - } - if (keys.S) { - if (event.ctrlKey) { - file.download(); - } - } - - }) - .keyup(function (event) { - switch (event.keyCode) { - case 13 : - keys.Enter = false; - break; - case 27 : - keys.Esc = false; - break; - case 46 : - keys.delete = false; - break; - case 67 : - keys.C = false; - break; - case 73 : - keys.I = false; - break; - case 79 : - keys.O = false; - break; - case 83 : - keys.S = false; - break; - case 86 : - keys.V = false; - break; - case 88 : - keys.X = false; - break; - case 122 : - keys.f11 = false; - break; - case 123 : - keys.f12 = false; - break; - } - }); - -$(window).bind('mousewheel DOMMouseScroll', function (event) { - if (event.ctrlKey === true) { - event.preventDefault(); - - if (event.originalEvent.wheelDelta / 120 > 0) { - if (webDraft.size < 250) { - webDraft.size += 2; - } else { - webDraft.size = 250; - } - } else { - if (webDraft.size > 1) { - webDraft.size -= 2; - } else { - webDraft.size = 1; - } - } - $("#eraseRect").css({ - "width": webDraft.size, - "height": webDraft.size, - "top": event.pageY - (webDraft.size / 2) + "px", - "left": event.pageX - (webDraft.size / 2) + "px" - }); - $("input#pointSize").val(webDraft.size); - $("#pointSizeValue").text("size:" + webDraft.size + "px"); - } - - if (event.altKey === true) { - // event.preventDefault(); - } -}); -class Layers { - constructor() { - $.get('parts/layers.part.html', function (data) { - $('#layers').html(data); - layers.newLayer(); - }); - - this.activeId = ""; - this.list = []; - } - setLayerPosition(layerId, top, left) { - $("canvas#" + layerId).css({ - "top": top, - "left": left - }); - } - setLayerSize(layerId, width, height) { - let image = { - id: new Array(), - img: new Array() - }; - - let active = layers.activeId; - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - image.img[i] = ctx.getImageData(0, 0, webDraft.draw.width, webDraft.draw.height); - image.id[i] = layers.list[i].id; - } - } - - if (layerId === "") { - webDraft.draw.width = width; - webDraft.draw.height = height; - $("canvas.canvas-draw").attr({ - "width": webDraft.draw.width, - "height": webDraft.draw.height - }); - } else { - $("canvas#" + layerId).attr({ - "width": width, - "height": height - }); - webDraft.draw.width = 0; - webDraft.draw.height = 0; - $("canvas.canvas-draw").each(function () { - let w = parseInt($(this).attr("width")); - let h = parseInt($(this).attr("height")); - if (webDraft.draw.width < w) - webDraft.draw.width = w; - - if (webDraft.draw.height < h) - webDraft.draw.height = h; - }); - } - - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - ctx.putImageData(image.img[i], 0, 0); - layers.saveState(); - } - } - - if (active !== "") - layers.select(active); - } - saveState() { - let imgSrc = document.getElementById(layers.activeId).toDataURL(); - $(".layerView[data-id=" + layers.activeId + "]").find("img").attr("src", imgSrc).show(); - } - newLayer() { - let j; - if (isNaN(parseInt($(".layerView:last").attr("id")))) { - j = 0; - } else { - j = parseInt($(".layerView:last").attr("id")) + 1; - } - - let countViews = $("#listLayers").children(".layerView").length; - - if (countViews < 15) { - randomId = webDraft.makeid(); - - $(webDraft.draw.selectorId).append(''); - $("#listLayers").append(` -
-
- -
-
- -
` - ); - - let new_layer = { - visible: true, - id: randomId, - top: 0, - left: 0 - }; - layers.list.push(new_layer); - - $(".layerView").click(function () { - if (!$(this).hasClass("hidden")) { - $(".layerView").removeClass("active"); - $(this).addClass("active"); - - let identifier = $(this).attr("data-id"); - - layers.select(identifier); - } - }); - $(".hideLayer").click(function () { - let nr = $(this).parent(".layerView").attr("id"); - - $(this).hide(); - $(this) - .parent(".layerView") - .find(".showLayer") - .css({"display": "block"}); - - layers.hide(nr); - }); - $(".showLayer").click(function () { - let nr = $(this).parent(".layerView").attr("id"); - - $(this).hide(); - $(this) - .parent(".layerView") - .find(".hideLayer") - .css({"display": "block"}); - - layers.show(nr); - }); - $(".layerView[data-id=" + randomId + "]").click(); - points[randomId] = []; - - } - } - delete() { - let identifier = $(".layerView.active").attr("data-id"); - let nr = $(".layerView.active").attr("id"); - - let countViews = $(".layerView").length; - - if (countViews > 1) { - let i = parseInt(nr); - $(".layerView#" + i).remove(); - $("canvas#" + identifier).remove(); - for (i in layers.list) { - if (!$('canvas#' + layers.list[i].id).length) { - layers.list.splice(i, 1); - } - } - - let j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - - webDraft.draw.width = 0; - webDraft.draw.height = 0; - $("canvas.canvas-draw").each(function () { - const w = parseInt($(this).attr("width")); - const h = parseInt($(this).attr("height")); - if (webDraft.draw.width < w) - webDraft.draw.width = w; - - if (webDraft.draw.height < h) - webDraft.draw.height = h; - }); - $(".layerView").first().click(); - webDraft.positionElements(); - } - } - hide(nr) { - let i = parseInt(nr); - - $(".layerView#" + i).addClass("hidden"); - $("canvas#" + layers.list[i].id).addClass("invisible"); - - let j = 0; - $("canvas.canvas-draw").not('#snapImage').each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - // $(".layerView#" + i).next().not(".hidden").click() - } - show(nr) { - let i = parseInt(nr); - - $(".layerView#" + i).removeClass("hidden"); - $("canvas#" + layers.list[i].id).removeClass("invisible"); - - let j = 0; - $("canvas.canvas-draw").not('#snapImage').each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - } - select(id) { - if (!$(".layerView[data-id=" + id + "]").hasClass("hidden")) { - canvas = document.getElementById(id); - ctx = canvas.getContext('2d'); - - layers.activeId = id; - } - } - rotate(angle) { - let image = new Image(); - image.src = canvas.toDataURL(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(canvas.width / 2, canvas.height / 2); - ctx.rotate(angle * Math.PI / 180); - ctx.drawImage(image, -image.width / 2, -image.width / 2); - ctx.restore(); - layers.saveState(); - } - mirror(direction) { - if (direction === 'horizontal' || direction === 'vertical') { - let image = new Image(); - image.src = canvas.toDataURL(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(canvas.width / 2, canvas.height / 2); - if (direction === 'vertical') - ctx.scale(-1, 1); - else if (direction === 'horizontal') - ctx.scale(1, -1); - ctx.drawImage(image, -image.width / 2, -image.height / 2); - ctx.restore(); - layers.saveState(); - } - } - negative() { - let destX = 0; - let destY = 0; - - let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - pixels[i] = 255 - pixels[i]; // red - pixels[i + 1] = 255 - pixels[i + 1]; // green - pixels[i + 2] = 255 - pixels[i + 2]; // blue - // i+3 is alpha (the fourth element) - } - - // overwrite original image - ctx.putImageData(imageData, 0, 0);//add the function call in the imageObj.onload - layers.saveState(); - } - moveUp() { - let firstID = $(".layerView").first().attr("data-id"); - if (firstID !== layers.activeId) { - let thisNr = $(".layerView[data-id=" + layers.activeId + "]").attr("id"); - let prevNr = parseInt(thisNr) - 1; - let prevId = $(".layerView#" + prevNr).attr("data-id"); - let thisLayer = $(".layerView#" + thisNr); - let prevLayer = $(".layerView#" + prevNr); - - thisLayer.insertBefore(".layerView#" + prevNr); - thisLayer.removeAttr("id").attr("id", prevNr); - prevLayer.removeAttr("id").attr("id", thisNr); - - $("canvas#" + layers.activeId).insertBefore("canvas#" + prevId); - - let j = 0; - $("canvas.canvas-draw").each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - return true; - } - return false; - } - moveDown() { - let lastID = $(".layerView").last().attr("data-id"); - if (lastID !== layers.activeId) { - let thisNr = $(".layerView[data-id=" + layers.activeId + "]").attr("id"); - let nextNr = parseInt(thisNr) + 1; - let nextId = $(".layerView#" + nextNr).attr("data-id"); - let thisLayer = $(".layerView#" + thisNr); - let prevLayer = $(".layerView#" + nextNr); - - thisLayer.insertAfter(".layerView#" + nextNr); - thisLayer.removeAttr("id").attr("id", nextNr); - prevLayer.removeAttr("id").attr("id", thisNr); - - $("canvas#" + layers.activeId).insertAfter("canvas#" + nextId); - let j = 0; - $("canvas.canvas-draw").each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - return true; - } - return false; - } -} -class Resizer { - constructor() { - let $this = this; - $.get('parts/resizer.part.html', function (data) { - $('#resizer').html(data); - - $("#resizer input[type=number]").keyup(function (e) { - $this.onkeyup(e); - }); - }); - } - show() { - $("#resizer").show(); - $("input[type=number]#drawWidth").val(webDraft.draw.width); - $("input[type=number]#drawHeight").val(webDraft.draw.height); - $("#resizeinfo").html(webDraft.draw.width + " " + webDraft.draw.height); - } - cancel() { - $("#resizer").hide(); - $("input[type=number]#drawWidth").val(webDraft.draw.width); - $("input[type=number]#drawHeight").val(webDraft.draw.height); - } - apply() { - $("#resizer").hide(); - let w, h; - if ($("#allLayersResizing").is(":checked")) { - w = $("input[type=number]#drawWidth").val(); - h = $("input[type=number]#drawHeight").val(); - - layers.setLayerSize('', w, h); - } else { - w = $("input[type=number]#drawWidth").val(); - h = $("input[type=number]#drawHeight").val(); - - layers.setLayerSize(layers.activeId, w, h); - } - webDraft.positionElements(); - } - onkeyup(e) { - this.onchange(); - - if (e.keyCode === 13) { - $("#apply").click(); - } - } - onchange() { - let xSize = parseInt($("input[type=number]#drawWidth").val()); - let ySize = parseInt($("input[type=number]#drawHeight").val()); - $("#resizeinfo").html(xSize + " " + ySize); - } -} -class Select { - constructor() { - $('#drawHandler').append('
'); - this.hoverSelectRectangle = false; - this.isSelecting = false; - this.startSelectPoints = { - x: 0, - y: 0 - }; - } - initSelect() { - this.startSelectPoints = { - x: webDraft.mPosition.x, - y: webDraft.mPosition.y - }; - } - startSelect() { - let x, y, width, height; - if (this.startSelectPoints.x <= webDraft.mPosition.x) { - x = this.startSelectPoints.x; - width = webDraft.mPosition.x - this.startSelectPoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startSelectPoints.x - webDraft.mPosition.x; - } - if (this.startSelectPoints.y <= webDraft.mPosition.y) { - y = this.startSelectPoints.y; - height = webDraft.mPosition.y - this.startSelectPoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startSelectPoints.y - webDraft.mPosition.y; - } - $("#selectRectangle") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "width": width + "px", - "height": height + "px", - "border": "1px dashed #fff", - "background": "transparent" - }); - this.isSelecting = true; - } - selectOpt() { - this.isSelecting = false; - } - delSelectedPart() { - if ($("#selectRectangle").css("background-image") === "none") {//if selectRectangle is empty then clear part of image hovered by it - xpos = parseInt($("#selectRectangle").css("left")); - ypos = parseInt($("#selectRectangle").css("top")); - ctx.clearRect(xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height()); - } else { //else clear only selectRectangle background - $("#selectRectangle").css({"background": "transparent"}); - } - - layers.saveState(); - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px", - "background": "transparent" - }) - .width(0) - .height(0) - .hide(); - } - copySelectedPart() { - if ($("#selectRectangle").css("background-image") === "none") { - $(webDraft.draw.selectorId).append(''); - - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var testC = document.getElementById("tmpCanvas"); - var testCtx = testC.getContext('2d'); - - testCtx.drawImage(canvas, xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height(), 0, 0, $("#selectRectangle").width(), $("#selectRectangle").height()); - - var bgImg = testC.toDataURL(); - - $("#tmpCanvas").remove(); - $("#selectRectangle").css({"background": "url(" + bgImg + ") -1px -1px no-repeat"}); - } - } - cutSelectedPart() { - $(webDraft.draw.selectorId).append(''); - - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var testC = document.getElementById("tmpCanvas"); - var testCtx = testC.getContext('2d'); - - testCtx.drawImage(canvas, xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height(), 0, 0, $("#selectRectangle").width(), $("#selectRectangle").height()); - - var bgImg = testC.toDataURL(); - - $("#tmpCanvas").remove(); - $("#selectRectangle").css({"background": "url(" + bgImg + ") -1px -1px no-repeat "}); - - ctx.clearRect(xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height()); - layers.saveState(); - } - pasteSelectedPart() { - if ($("#selectRectangle").css("background-image") !== "none") { - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var img = new Image(); - var bg = $("#selectRectangle") - .css("background-image") - .replace('url(', '') - .replace(')', '') - .replace('"', '') - .replace('"', ''); - img.onload = function () { - ctx.drawImage(img, xpos, ypos); //save part of image when loaded - - layers.saveState(); //and then update layer preview - }; - img.src = bg; - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px", - "background": "transparent" - }) - .width(0) - .height(0) - .hide(); - } - } -} -; -class Shapes { - constructor() { - $('#drawHandler').append(` -
-
`); - this.startShapePoints = { - x: 0, - y: 0 - }; - this.fill = { - isSet: false, - color: "#ffffff", - opacity: 100 - }; - } - - startShape() { - this.startShapePoints.x = webDraft.mPosition.x; - this.startShapePoints.y = webDraft.mPosition.y; - } - prepareRect() { - let x, y, width, height; - if (this.startShapePoints.x <= webDraft.mPosition.x) { - x = this.startShapePoints.x; - width = webDraft.mPosition.x - this.startShapePoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startShapePoints.x - webDraft.mPosition.x; - } - if (this.startShapePoints.y <= webDraft.mPosition.y) { - y = this.startShapePoints.y; - height = webDraft.mPosition.y - this.startShapePoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startShapePoints.y - webDraft.mPosition.y; - } - $("#prepareRect") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "width": width + "px", - "height": height + "px", - "border": webDraft.size + "px solid " + webDraft.color - }); - if (this.fill.isSet) { - $("#prepareRect").css({"background": hexToRgba(this.fill.color, this.fill.opacity)}); - } else { - $("#prepareRect").css({"background": "transparent"}); - } - if (webDraft.shadow.isShadow) { - $("#prepareRect").css({"box-shadow": webDraft.shadow.offsetX + "px " + webDraft.shadow.offsetY + "px " + webDraft.shadow.blur + "px " + webDraft.shadow.color}); - } else { - $("#prepareRect").css({"box-shadow": "none"}); - } - } - drawRect() { - let x, y, width, height; - if (this.startShapePoints.x >= 0 && this.startShapePoints.y >= 0) { - if (this.startShapePoints.x <= webDraft.mPosition.x) { - x = this.startShapePoints.x; - width = webDraft.mPosition.x - this.startShapePoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startShapePoints.x - webDraft.mPosition.x; - } - if (this.startShapePoints.y <= webDraft.mPosition.y) { - y = this.startShapePoints.y; - height = webDraft.mPosition.y - this.startShapePoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startShapePoints.y - webDraft.mPosition.y; - } - $("#prepareRect").hide(); - ctx.beginPath(); - draw.drawStyle(); - ctx.rect(x, y, width, height); - ctx.fill(); - ctx.stroke(); - } - this.startShapePoints.x = -1; - this.startShapePoints.y = -1; - } - prepareCircle() { - let x, y, width, height, radius; - x = this.startShapePoints.x; - y = this.startShapePoints.y; - - if (x <= webDraft.mPosition.x) { - width = webDraft.mPosition.x - x; - } else { - width = x - webDraft.mPosition.x; - } - - if (y <= webDraft.mPosition.y) { - height = webDraft.mPosition.y - y; - } else { - height = y - webDraft.mPosition.y; - } - - if (width > height) { - radius = width; - } else { - radius = height; - } - $("#prepareCircle") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) - radius + "px", - "left": x + parseInt($(canvas).css('left')) - radius + "px", - "width": radius * 2 + "px", - "height": radius * 2 + "px", - "border": webDraft.size + "px solid " + webDraft.color, - "border-radius": "100%" - }); - - if (this.fill.isSet) { - $("#prepareCircle").css({"background": hexToRgba(this.fill.color, this.fill.opacity)}); - } else { - $("#prepareCircle").css({"background": "transparent"}); - } - - if (webDraft.shadow.isShadow) { - $("#prepareCircle").css({"box-shadow": webDraft.shadow.offsetX + "px " + webDraft.shadow.offsetY + "px " + webDraft.shadow.blur + "px " + webDraft.shadow.color}); - } else { - $("#prepareCircle").css({"box-shadow": "none"}); - } - } - drawCircle() { - let x, y, width, height, radius; - x = this.startShapePoints.x; - y = this.startShapePoints.y; - - if (x >= 0 && y >= 0) { - - if (x <= webDraft.mPosition.x) { - width = webDraft.mPosition.x - x; - } else { - width = x - webDraft.mPosition.x; - } - if (y <= webDraft.mPosition.y) { - height = webDraft.mPosition.y - y; - } else { - height = y - webDraft.mPosition.y; - } - if (width > height) { - radius = width; - } else { - radius = height; - } - $("#prepareCircle").hide(); - - ctx.beginPath(); - draw.drawStyle(); - ctx.arc(x, y, radius, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.stroke(); - } - this.startShapePoints.x = -1; - this.startShapePoints.y = -1; - } -} -class Text { - constructor() { - $('#drawHandler').append('
'); - - $.get('parts/text-options.part.html', function (data) { - $('#textOptions').html(data); - - - $("#selectFontType").change(function () { - $("#textRectangle").css("font-family", $(this).val()); - }); - $("#selectFontSize").change(function () { - $("#textRectangle").css("font-size", $(this).val() + "px"); - }); - $(".textPostionTool").click(function () { - $(".textPostionTool").removeClass("active"); - $(this).addClass("active"); - $("#textRectangle").css("text-align", $(this).attr('id')); - }); - $(".styleTool").click(function () { - $(this).toggleClass("active"); - switch ($(this).attr("id")) { - case 'bold': - if ($(this).hasClass('active')) - $("#textRectangle").css("font-weight", 'bold'); - else - $("#textRectangle").css("font-weight", 'normal'); - break; - case 'italic': - if ($(this).hasClass('active')) - $("#textRectangle").css("font-style", 'italic'); - else - $("#textRectangle").css("font-style", 'normal'); - break; - case 'underline': - case 'line-through': - var textDecoration = ""; - $(".textDecoration.active").each(function () { - textDecoration += $(this).attr("id") + " "; - }); - if ($(".textDecoration.active").length > 0) - $("#textRectangle").css("text-decoration", textDecoration); - else - $("#textRectangle").css("text-decoration", 'none'); - break; - default: - - } - }); - }); - this.hoverSelectRectangle = false; - this.isSelecting = false; - this.startSelectPoints = { - x: 0, - y: 0 - }; - } - initSelect() { - this.startTextPoints = { - x: webDraft.mPosition.x, - y: webDraft.mPosition.y - }; - console.log(this.startTextPoints) - } - startSelect() { - if ($("#textRectangle").empty()) { - let x, y, width, height; - if (this.startTextPoints.x <= webDraft.mPosition.x) { - x = this.startTextPoints.x; - width = webDraft.mPosition.x - this.startTextPoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startTextPoints.x - webDraft.mPosition.x; - } - if (this.startTextPoints.y <= webDraft.mPosition.y) { - y = this.startTextPoints.y; - height = webDraft.mPosition.y - this.startTextPoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startTextPoints.y - webDraft.mPosition.y; - } - $("#textRectangle") - .empty() - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "min-height": height + "px", - "width": width + "px", - "height": height + "px", - "border": "1px dashed #fff", - "background": "transparent" - }); - this.isSelecting = true; - } - } - showTextOptions() { - this.isSelecting = false; - $("#textOptions").show(); - } - putLayer() { - $("#textRectangle").css('border', 'none'); - - if ($('#textRectangle').html() === '') { - return false; - } - - let top = parseInt($("#textRectangle").css('top')); - let left = parseInt($("#textRectangle").css('left')); - html2canvas($('#textRectangle'), { - onrendered: function (canvas) { - let widthImg = canvas.width; - let heightImg = canvas.height; - layers.newLayer(); - layers.setLayerSize(layers.activeId, widthImg, heightImg); - layers.setLayerPosition(layers.activeId, top, left); - webDraft.positionElements(); - - ctx.drawImage(canvas, 0, 0); - - layers.saveState(); - $("#textRectangle") - .css({ - "top": "0px", - "left": "0px", - "border": "1px dashed rgb(255, 255, 255)" - }) - .width(0) - .height(0) - .hide() - .empty(); - } - }); - } -} -class WebDraft { - constructor() { - this.isLoaded = false; - this.title = "WebDraft"; - this.click = { - left: false, //left mouse button - right: false //right mouse button - }; - this.mPosition = {//mouse position on draw - x: 0, - y: 0 - }; - this.draw = { - width: 600, - height: 600, - thisParent: "#drawHandler", - selectorId: "#draw", - eventHandler: "#eventHandler", - bg: "url('pic/transparent.png') repeat" - }; - this.shadow = { - isShadow: false, - blur: 1, - offsetX: 0, - offsetY: 0, - color: "#232324" - }; - this.sensitivityPoints = 1000; - this.size = 10; - this.color = "#000000"; - this.selectedTool = PENCIL; - } - resize() {} - makeid() { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < 15; i++) - text += possible.charAt(Math.floor(Math.random() * possible.length)); - - return text; - } - positionElements() { - var image = { - id: new Array(), - img: new Array() - }; - - var active = layers.activeId; - for (var i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - image.img[i] = ctx.getImageData(0, 0, this.draw.width, this.draw.height); - image.id[i] = layers.list[i].id; - } - } - - $(this.draw.selectorId + "," + this.draw.eventHandler).css({ - "width": this.draw.width, - "height": this.draw.height - }); - $(this.draw.thisParent).css({ - "background": this.draw.bg, - "width": this.draw.width, - "height": this.draw.height - }); - - if (this.draw.width >= $("#content").width()) { - $(this.draw.thisParent).css({"margin-left": "0px"}); - } else { - $(this.draw.thisParent).css({"margin-left": ($("#content").width() - this.draw.width) / 2}); - } - - if (this.draw.height >= $("#content").height()) { - $(this.draw.thisParent).css({"margin-top": "0px"}); - } else { - $(this.draw.thisParent).css({"margin-top": ($("#content").height() - this.draw.height) / 2}); - } - - for (var i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - ctx.putImageData(image.img[i], 0, 0); - layers.saveState(); - } - } - - if (active !== "") - layers.select(active); - - $("title").text(this.title + ' [' + this.draw.width + " x " + this.draw.height + ']'); - $("html, body, #paint").css({"visibility": "visible"}); - } - moveEraseRect(event) { - $("#eraseRect").css({ - "width": this.size, - "height": this.size, - "top": event.pageY - (this.size / 2) + "px", - "left": event.pageX - (this.size / 2) + "px" - }); - } - erase(event) { - this.moveEraseRect(event); - ctx.clearRect(this.mPosition.x - this.size / 2, this.mPosition.y - this.size / 2, this.size, this.size); - - points[layers.activeId] = []; -// do poprawy - } - clear() { - $(this.draw.selectorId).empty(); - $("#listLayers").empty(); - - points = {}; - - this.init(); - } - colorsampler(event) { - var x = this.mPosition.x; - var y = this.mPosition.y; - var p = ctx.getImageData(x, y, 1, 1).data; - var colorCode; - var r = p[0]; - var g = p[1]; - var b = p[2]; - var alpha = p[3]; - var a = Math.floor((100 * alpha) / 255) / 100; - - if (a <= 0) { - colorCode = ((r << 16) | (g << 8) | b).toString(16); - } else { - colorCode = "rgba(" + r + "," + g + "," + b + "," + a + ")"; - } - - if (colorCode === "0") - colorCode = "transparent"; - - $("#textColorSampler").text(colorCode); - $("#colorBoxSampler").css({"background-color": colorCode}); - } - colorsamplerSetcolor() { - if ($("#textColorSampler").text() !== 'null') { - $("#generalColor .color").css({"background": $("#textColorSampler").text()}); - $("#firstColor").val($("#textColorSampler").text()); - this.color = $("#textColorSampler").text(); - } - } - mousePosition(event) { - var touch = undefined; - - if (event.originalEvent.touches) { - touch = event.originalEvent.touches[0]; - - var pos_x = event.pageX || touch.pageX; - var pos_y = event.pageY || touch.pageY; - - - } else { - var pos_x = event.pageX; - var pos_y = event.pageY; - } - - this.mPosition.x = pos_x - parseInt($(this.draw.selectorId).offset().left) - parseInt($(canvas).css('left')); - this.mPosition.y = pos_y - parseInt($(this.draw.selectorId).offset().top) - parseInt($(canvas).css('top')); - - $("#mousePosition").text(this.mPosition.x + " , " + this.mPosition.y); - if (this.mPosition.x < 0 || this.mPosition.x > $(this.draw.selectorId).width() - || this.mPosition.y < 0 || this.mPosition.y > $(this.draw.selectorId).height()) { - $("#mousePosition").empty(); - } - } - _mousedown(event) { - this.mousePosition(event); - if ("buttons" in event) { - if (event.buttons == 1) { - this.click.left = true; - } - } - var button = event.which || event.button; - if (button == 1) { - this.click.left = true; - } - if (!this.click.right && this.click.left) { - if (this.selectedTool !== SELECT) - points[layers.activeId].push({x: this.mPosition.x, y: this.mPosition.y}); - - switch (this.selectedTool) { - case PENCIL : - case ERASER : - draw.drawing(); - break; - case SELECT : - select.initSelect(); - break; - case RECTANGLE : - case CIRCLE : - shapes.startShape(); - break; - case TEXT : - text.initSelect(); - break; - case COLORSAMPLER : - this.colorsamplerSetcolor(); - $("#pencil").click(); - break; - } - } - } - _mouseup(event) { - this.click.left = false; - this.click.right = false; - - ctx.beginPath(); - ctx.stroke(); - - switch (this.selectedTool) { - case SELECT: - select.selectOpt(); - break; - case TEXT : - text.showTextOptions(); - break; - case RECTANGLE : - shapes.drawRect(); - break; - case CIRCLE : - shapes.drawCircle(); - break; - } - - layers.saveState(); - } - _mousemove(event) { - this.mousePosition(event); - - switch (this.selectedTool) { - case ERASER: - this.moveEraseRect(event); - break; - case COLORSAMPLER : - this.colorsampler(); - break; - case SELECT : - if ( - !select.isSelecting && this.mPosition.x <= parseInt($("#selectRectangle").css("left")) + $("#selectRectangle").width() - && this.mPosition.x >= parseInt($("#selectRectangle").css("left")) - && this.mPosition.y <= parseInt($("#selectRectangle").css("top")) + $("#selectRectangle").height() - && this.mPosition.y >= parseInt($("#selectRectangle").css("top")) - ) { - select.hoverSelectRectangle = true; - $("#selectRectangle").css({"z-index": 5}); - } else { - select.hoverSelectRectangle = false; - $("#selectRectangle").css({"z-index": 3}); - } - break; - case TEXT : - if ( - !text.isSelecting && this.mPosition.x <= parseInt($("#textRectangle").css("left")) + $("#textRectangle").width() - && this.mPosition.x >= parseInt($("#textRectangle").css("left")) - && this.mPosition.y <= parseInt($("#textRectangle").css("top")) + $("#textRectangle").height() - && this.mPosition.y >= parseInt($("#textRectangle").css("top")) - ) { - text.hoverSelectRectangle = true; - $("#textRectangle").css({"z-index": 5}); - } else { - text.hoverSelectRectangle = false; - $("#textRectangle").css({"z-index": 3}); - } - break; - } - - if (this.click.left && !this.click.right) { - if (this.selectedTool !== SELECT) - points[layers.activeId].push({x: this.mPosition.x, y: this.mPosition.y}); - - switch (this.selectedTool) { - case PENCIL : - case ERASER : - ctx.lineTo(this.mPosition.x, this.mPosition.y); - ctx.stroke(); - break; - case WEB : - draw.drawWeb(); - break; - case SELECT : - if (!select.hoverSelectRectangle) - select.startSelect(); - break; - case TEXT : - if (!text.hoverSelectRectangle) - text.startSelect(); - break; - case RECTANGLE : - shapes.prepareRect(); - break; - case CIRCLE : - shapes.prepareCircle(); - break; - } - } - } - loadParts() { - $.get('parts/buttons.part.html', function (data) { - $('[data-id=buttons]').html(data); - events.buttons(); - }); - $.get('parts/info.part.html', function (data) { - $('#info').html(data); - events.info(); - }); - $.get('parts/color-picker.part.html', function (data) { - $('[data-id=color-picker]').html(data); - events.color(); - }); - $.get('parts/sliders.part.html', function (data) { - $('[data-id=sliders]').html(data); - events.sliders(); - }); - this.isLoaded = true; - } - init() { - let $this = this; - if (!this.isLoaded) { - this.loadParts(); - } else { - layers.newLayer(); - } - //events on #draw - $(this.draw.eventHandler) - .hover(function () { - if (typeof ctx !== 'undefined') { - ctx.beginPath(); - ctx.stroke(); - } - if (this.selectedTool === ERASER) { - $("#eraseRect").show(); - } - - }) - .bind("contextmenu", function (event) { - if (!DEBUG) { - event.preventDefault(); - } - this.click.right = true; - this.click.left = false; - }) - .on('mousedown', function (e) { - $this._mousedown(e); - }) - .on('mouseup', function (e) { - $this._mouseup(e); - }) - .on('mousemove', function (e) { - $this._mousemove(e); - }) - .mouseleave(function () { - ctx.stroke(); - layers.saveState(); - if (this.selectedTool === ERASER) - $("#eraseRect").hide(); - }) - .dblclick(function () { - switch (this.selectedTool) { - case SELECT: - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px" - }) - .width(0) - .height(0) - .hide(); - break; - case TEXT : - text.putLayer(); - - break; - } - }); - } -} - -const DEBUG = 0; - -const PENCIL = 'pencil'; -const SELECT = 'select'; -const ERASER = 'eraser'; -const WEB = 'web'; -const TEXT = 'text'; -const RECTANGLE = 'rectangle'; -const CIRCLE = 'circle'; -const COLORSAMPLER = 'colorsampler'; - - -let items = [ - { - text: 'Clear', - shortcut: 'ctrl+del', - icon: 'fas fa-ban', - onclick: function () { - webDraft.clear(); - } - }, - { - text: 'Upload image', - shortcut: 'ctrl+o', - icon: 'fas fa-upload', - onclick: function () { - $('#fileUploader').click(); - } - }, - { - text: 'Save image', - shortcut: 'ctrl+s', - icon: 'fas fa-download', - onclick: function () { - file.download(); - } - }, - { - text: 'Color', - icon: 'fas fa-tint', - submenu: [ - { - text: 'Invert', - icon: 'fas fa-adjust', - onclick: function () { - layers.negative(); - } - }, - { - text: 'Colorpicker', - icon: 'fas fa-eye-dropper', - onclick: function () { - $('#colorsampler').click(); - } - } - ] - } -]; - -let context_menu = new Contextmenu(items); -let camera = new Camera(); -let file = new File(); -let shapes = new Shapes(); -let draw = new Draw(); -let text = new Text(); -let resizer = new Resizer(); -let layers = new Layers(); -let select = new Select(); -let webDraft = new WebDraft(); - - -let canvas, - ctx, - randomId, - points = {}; - - -$(window) - .resize(function () { - webDraft.resize(); - webDraft.positionElements(); - }); -$(document) - .ready(function (event) { - if (/mobile/i.test(navigator.userAgent)) { - webDraft.draw.width = window.innerWidth - 10; - webDraft.draw.height = window.innerHeight - 30; - } - - webDraft.init(); - $("#selectRectangle, #textRectangle") - .draggable({snap: false}) - .css({"position": "absolute"}); - - $("#shadowDot").draggable({ - containment: "#shadowSquare", - scroll: false, - drag: function () { - let shadowY = parseInt($(this).css('top')) - (parseInt($(this).parent().height()) / 2); - let shadowX = parseInt($(this).css('left')) - (parseInt($(this).parent().width()) / 2); - webDraft.shadow.offsetX = shadowX; - webDraft.shadow.offsetY = shadowY; - } - }); - $('#shadowSquare').on('mousedown', function (e) { - let x = e.pageX - $(this).offset().left; - let y = e.pageY - $(this).offset().top; - - $("#shadowDot").css({ - top: (y - 5) + 'px', - left: (x - 5) + 'px' - }); - - let shadowY = parseInt($("#shadowDot").css('top')) - (parseInt($("#shadowDot").parent().height()) / 2); - let shadowX = parseInt($("#shadowDot").css('left')) - (parseInt($("#shadowDot").parent().width()) / 2); - webDraft.shadow.offsetX = shadowX; - webDraft.shadow.offsetY = shadowY; - }); - - - }) - .bind("contextmenu", function (e) { - if (!DEBUG) { - e.preventDefault(); - } - }) - .on('mouseup touchend', function (e) { - webDraft._mouseup(e); - }) - .on('mousemove touchmove', function (e) { - webDraft._mousemove(e); - }); - -function hexToRgba(hex, opacity) { - hex = hex.replace('#', ''); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - const a = opacity / 100; - return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; -} \ No newline at end of file diff --git a/docs/manual-qa-checklist.md b/docs/manual-qa-checklist.md new file mode 100644 index 0000000..e779887 --- /dev/null +++ b/docs/manual-qa-checklist.md @@ -0,0 +1,90 @@ +# WebDraft Manual QA Checklist + +Use this before replacing the old entrypoint or after larger editor changes. + +## Automated Gate + +- [ ] Run `npm run check` in `app-v3/`. +- [ ] Confirm the app opens at `http://127.0.0.1:5173/`. +- [ ] Confirm the browser console has no unexpected errors during basic use. + +## Drawing Tools + +- [ ] Pencil draws a continuous stroke and enables Undo. +- [ ] Eraser removes pixels from the active layer. +- [ ] Size slider changes brush width. +- [ ] Color picker changes stroke color. +- [ ] Sampler picks a visible non-transparent color and returns to Pencil. +- [ ] Fill bucket fills a contiguous active-layer region using Fill color, Fill opacity, and Bucket tolerance. +- [ ] Web tool draws the main stroke plus nearby connecting lines. + +## Shapes And Text + +- [ ] Rectangle preview appears while dragging and commits on pointer up. +- [ ] Ellipse preview appears while dragging and commits on pointer up. +- [ ] Fill toggle, fill color, and fill opacity affect shapes. +- [ ] Text tool opens a textarea over the selected bounds. +- [ ] Text commits on blur. +- [ ] Text font, alignment, bold, italic, and size are reflected on commit. + +## Selection And Clipboard + +- [ ] Select tool draws a clipped selection frame. +- [ ] Copy enables Paste. +- [ ] Cut removes the selected pixels and enables Paste. +- [ ] Paste places pixels at the selection bounds. +- [ ] Delete or Backspace clears the active layer. + +## Layers + +- [ ] Add layer creates a new active layer. +- [ ] Delete layer removes the active layer but never removes the last layer. +- [ ] Move up/down changes layer order and visual stacking. +- [ ] Hide/show toggles visibility and export respects hidden layers. +- [ ] Rename keeps a non-empty name and ignores empty names. +- [ ] Layer thumbnails update after drawing and transforms. + +## History + +- [ ] Undo/redo works for drawing strokes. +- [ ] Undo/redo works for shapes and text. +- [ ] Undo/redo works for add/delete/move/rename/hide/show layer. +- [ ] Undo/redo works after image import. +- [ ] Undo/redo works after canvas resize. +- [ ] New edits after Undo clear the Redo stack. +- [ ] History remains bounded to the latest 30 operations. + +## Canvas And Files + +- [ ] Resize canvas changes the visible canvas and keeps existing layer pixels at the top-left. +- [ ] Upload image creates a new fitted layer. +- [ ] Export PNG downloads a composited image. +- [ ] Exported PNG excludes hidden layers. +- [ ] Camera snap either adds a new layer or shows a permission/device error toast. + +## Status And Errors + +- [ ] Successful image import shows a success toast. +- [ ] Successful PNG export shows a success toast. +- [ ] Successful resize shows the new canvas size in a toast. +- [ ] Camera denial or unsupported camera shows an error toast. +- [ ] Toasts do not block drawing or toolbar interaction. + +## Responsive Layout + +- [ ] Desktop sidebar scrolls independently from the canvas workspace. +- [ ] Desktop page has no accidental horizontal body overflow. +- [ ] Mobile toolbar scrolls horizontally. +- [ ] Mobile workspace scrolls the canvas instead of widening the page. +- [ ] Text inside buttons and controls remains readable at mobile width. + +## Keyboard Shortcuts + +- [ ] `S`, `P`, `E`, `C`, `B`, `W`, `R`, `O`, `T` switch tools. +- [ ] `[` and `]` change brush size. +- [ ] Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z undo/redo. +- [ ] Cmd/Ctrl+Y redoes. +- [ ] Cmd/Ctrl+O opens image picker. +- [ ] Cmd/Ctrl+S exports PNG. +- [ ] Cmd/Ctrl+C/X/V copy, cut, paste a selection. +- [ ] `I`, `,`, `.`, `H`, `V` run invert, rotate, and mirror actions. diff --git a/docs/rewrite-checklist.md b/docs/rewrite-checklist.md new file mode 100644 index 0000000..d2c96db --- /dev/null +++ b/docs/rewrite-checklist.md @@ -0,0 +1,44 @@ +# WebDraft Rewrite Checklist + +## Direction + +- Build the new editor in `app-v3/`. +- Keep the old app intact as a reference until the rewrite is useful enough to replace it. +- Use Vanilla TypeScript modules and browser APIs first. +- Avoid jQuery, React, Angular, and server-side rendering. +- Consider Web Components only for repeated UI pieces once their shape stabilizes. + +## Milestones + +- [x] Create an isolated rewrite directory. +- [x] Add a lightweight Vite + TypeScript skeleton. +- [x] Add the first canvas surface, layer manager, pencil, eraser, size control, clear command, and custom color input wrapper. +- [x] Recreate the current tool inventory from the old app. +- [x] Rebuild layers UI without jQuery. +- [x] Add upload image support. +- [x] Add PNG export. +- [x] Add selection, copy, cut, paste. +- [x] Add shape tools. +- [x] Add fill options for shape tools. +- [x] Add text tool. +- [x] Add web drawing tool. +- [x] Add fill bucket tool. +- [x] Add active-layer invert, mirror, and rotate actions. +- [x] Add canvas resize, fill opacity, shadow controls, text options, and camera snap. +- [x] Add undo/redo history. +- [x] Add keyboard shortcuts. +- [x] Add responsive layout polish. +- [x] Add final smoke tests and regression tests. + +## Old Code References + +- `js/modules/WebDraft.js` - pointer flow and current editor state. +- `js/modules/Layers.js` - layer operations and previews. +- `js/modules/File.js` - upload and export behavior. +- `js/modules/Shapes.js` - rectangle and circle behavior. +- `js/modules/Text.js` - text selection and rendering behavior. +- `parts/*.part.html` - current UI inventory. + +## Release Readiness + +- Use `docs/manual-qa-checklist.md` before replacing the old entrypoint or after larger editor changes. diff --git a/docs/wdraft-format.md b/docs/wdraft-format.md new file mode 100644 index 0000000..8386936 --- /dev/null +++ b/docs/wdraft-format.md @@ -0,0 +1,82 @@ +# `.wdraft` File Format (v2) + +## Overview + +`.wdraft` is the native project format for WebDraft. It preserves all layers with their full pixel data, canvas dimensions, layer order, visibility, and the active layer — allowing work to be resumed exactly as it was left. + +- **Extension:** `.wdraft` +- **MIME type:** `application/x-webdraft` +- **Encoding:** binary (little-endian) + +--- + +## Binary layout + +All multi-byte integers are **unsigned, little-endian**. + +### Header + +| Offset | Size | Type | Field | +|--------|------|--------|---------------------------| +| 0 | 4 | bytes | Magic: `0x57 0x44 0x46 0x54` (`"WDFT"`) | +| 4 | 1 | uint8 | Version (currently `2`) | +| 5 | 4 | uint32 | Canvas width (px) | +| 9 | 4 | uint32 | Canvas height (px) | +| 13 | 4 | uint32 | Layer count (internal counter) | +| 17 | 2 | uint16 | Active layer ID length (bytes) | +| 19 | N | UTF-8 | Active layer ID | +| 19+N | 2 | uint16 | Number of layer entries | + +### Per-layer block (repeated) + +| Size | Type | Field | +|------|--------|-------------------------------| +| 2 | uint16 | ID length (bytes) | +| N | UTF-8 | Layer ID | +| 2 | uint16 | Name length (bytes) | +| N | UTF-8 | Layer name | +| 1 | uint8 | Visible (`0` = hidden, `1` = visible) | +| 4 | uint32 | Layer canvas width (px) | +| 4 | uint32 | Layer canvas height (px) | +| 4 | uint32 | PNG data length (bytes) | +| N | bytes | PNG data (raw, no base64) | + +Layers are stored bottom-to-top (index 0 = bottommost layer). + +--- + +## Fields + +- **Layer count** — internal counter used to generate unique layer IDs after import; must be ≥ the highest layer index. +- **Layer canvas width/height** — actual dimensions of the layer's canvas (may differ from canvas width/height if the canvas was resized after the layer was created). +- **PNG data** — lossless PNG encoded directly as raw bytes. No base64 encoding. + +--- + +## File size + +Without base64 overhead, files are ~25–33% smaller than a JSON equivalent. + +| Canvas size | Layers | Estimated file size | +|-------------|--------|---------------------| +| 900 × 620 | 1 | ~200 KB | +| 900 × 620 | 5 | ~1 MB | +| 1920 × 1080 | 5 | ~4–7 MB | + +Mostly-transparent layers compress very well; fully painted layers are larger. + +--- + +## Versioning + +- The version byte is checked on import. Files with a version other than `2` are rejected. +- The magic bytes `WDFT` identify the file format independently of version. +- Version 1 was a JSON/base64 format — those files are not supported by this parser. + +--- + +## Usage in WebDraft + +**Save project** — serialises all layers to a `.wdraft` binary file and downloads it. + +**Open project** — reads a `.wdraft` file with `FileReader.readAsArrayBuffer`, parses the binary, restores canvas size and all layers, and pushes a history entry so the action can be undone. diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index 12b19e5..0000000 Binary files a/favicon.ico and /dev/null differ diff --git a/index.html b/index.html index 7968044..fec3992 100644 --- a/index.html +++ b/index.html @@ -1,80 +1,13 @@ - + - + + + + WebDraft - - - - - - - - - - - - -
- -
- -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
- -
-
-
-
-
-
-
-
-
Del
-
Delete
-
Ctrl + X
-
Cut
-
Ctrl + C
-
Copy
-
Ctrl + V
-
Paste
-
-
-
- -
-
-
-   |   - Created by rtCoder -
-
- - - - - - - - - - - + + +
+ + diff --git a/js/modules/Camera.js b/js/modules/Camera.js deleted file mode 100644 index d842c66..0000000 --- a/js/modules/Camera.js +++ /dev/null @@ -1,171 +0,0 @@ -class Camera { - constructor() { - this.cameraStream = null; - this.filters = { - 'sepia': false, - 'noise': false, - 'greyscale': false, - 'negative': false - }; - let $this = this; - this.filterInterval = null; - $.get('parts/camera.part.html', function (data) { - $('body').append('
'); - - $('#camera').html(data); - - $('#snap').click($this.snap); - $('#saveSnapOnComputer').click($this.saveOnComputer); - $('#applySnap').click($this.applySnap); - $('#cancelSnap').click($this.cancelSnap); - $('#switchFullscreen').click(function (e) { - $('#camera').toggleClass('fullscreen'); - }) - $('#closeCamera').click(function () { - $this.stop(); - }); - $('.filter-checkbox').change(function (e) { - let filterName = $(this).parent().find('.filter').data('id'); - $this.filters[filterName] = $(this).is(':checked'); - }); - }); - } - - init() { - let $this = this; - $('#camera').addClass('opened'); - let video = document.getElementById('video'); - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) { - video.src = window.URL.createObjectURL(stream); - video.play(); - $this.cameraStream = stream; - $('#camera #cameraTitle').text($this.cameraStream.getTracks()[0].label); - $this.setFilter(); - }); - } - } - - stop() { - let $this = this; - let track = $this.cameraStream.getTracks()[0]; - track.stop(); - $this.cameraStream = null; - $('#camera').removeClass('opened'); - } - - snap() { - let snapImage = document.getElementById('snapImage'); - let snapImageContext = snapImage.getContext('2d'); - let video = document.getElementById('video'); - let additionalLayer = document.getElementById('additional'); - let additionalLayerContext = additionalLayer.getContext('2d'); - let videoImageData = additionalLayerContext.getImageData(0, 0, additionalLayer.width, additionalLayer.height); - snapImageContext.putImageData(videoImageData, 0, 0); - $('#camera').addClass('snapped'); - } - - saveOnComputer() { - file.downloadFromCamera('snapImage'); - } - - cancelSnap() { - let snapImage = document.getElementById('snapImage'); - let snapImageContext = snapImage.getContext('2d'); - snapImageContext.clearRect(0, 0, snapImage.width, snapImage.height); - $('#camera').removeClass('snapped'); - if (this.filterInterval) { - clearInterval(this.filterInterval); - } - } - - applySnap() { - let snapImage = document.getElementById('snapImage'); - layers.newLayer(); - layers.setLayerSize(layers.activeId, snapImage.width, snapImage.height); - webDraft.positionElements(); - ctx.drawImage(snapImage, 0, 0); - layers.saveState(); - $('#camera').removeClass('snapped'); - } - - setFilter() { - let additionalLayer = document.getElementById('additional'); - let additionalLayerContext = additionalLayer.getContext('2d'); - let frame = document.getElementById('frame'); - let frameContext = frame.getContext('2d'); - let video = document.getElementById('video'); - let videoImageData = additionalLayerContext.getImageData(0, 0, additionalLayer.width, additionalLayer.height); - if (this.filterInterval) { - clearInterval(this.filterInterval); - } - let $this = this; - setInterval(function (e) { - frameContext.drawImage(video, 0, 0, video.width, video.height); - videoImageData = frameContext.getImageData(0, 0, video.width, video.height); - - for (let i in $this.filters) { - if ($this.filters[i]) { - videoImageData = $this[i + 'Filter'](videoImageData); - } - } - - additionalLayerContext.putImageData(videoImageData, 0, 0); - }, 1) - } - - negativeFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - pixels[i] = 255 - pixels[i]; // red - pixels[i + 1] = 255 - pixels[i + 1]; // green - pixels[i + 2] = 255 - pixels[i + 2]; // blue - } - - return imageData; - } - - greyscaleFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let r = pixels[i]; - let g = pixels[i + 1]; - let b = pixels[i + 2]; - let avg = 0.3 * r + 0.59 * g + 0.11 * b; - - pixels[i] = avg; // red - pixels[i + 1] = avg; // green - pixels[i + 2] = avg; // blue - } - - return imageData; - } - - noiseFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let rand = (0.5 - Math.random()) * 100; - pixels[i] += rand; // red - pixels[i + 1] += rand; // green - pixels[i + 2] += rand; // blue - } - - return imageData; - } - - sepiaFilter(imageData) { - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - let r = pixels[i]; - let g = pixels[i + 1]; - let b = pixels[i + 2]; - let avg = 0.3 * r + 0.59 * g + 0.11 * b; - - pixels[i] = avg + 100; // red - pixels[i + 1] = avg + 50; // green - pixels[i + 2] = avg; // blue - } - - return imageData; - } -} \ No newline at end of file diff --git a/js/modules/Clipboard.js b/js/modules/Clipboard.js deleted file mode 100644 index e69de29..0000000 diff --git a/js/modules/Contextmenu.js b/js/modules/Contextmenu.js deleted file mode 100644 index 4cdc1e2..0000000 --- a/js/modules/Contextmenu.js +++ /dev/null @@ -1,89 +0,0 @@ -class Contextmenu { - constructor(items) { - this.items = items; - let $this = this; - $('body').append(''); - $('#contextmenu').html('
    '); - for (let i in items) { - let li = this.buildItem(this.items[i]); - - $('#contextmenu ul').append(li); - - if (this.items[i].onclick) { - $('#contextmenu ul li').last().click(function (e) { - $this.items[i].onclick(); - $this.hide(e); - }); - } - - if (this.items[i].submenu && this.items[i].submenu.length > 0) { - $('#contextmenu ul li').last().prepend(''); - for (let j in this.items[i].submenu) { - - let li = this.buildItem(this.items[i].submenu[j]); - - $('#contextmenu ul li ul.submenu').append(li); - - if (this.items[i].submenu[j].onclick) { - $('#contextmenu ul li').last().click(function (e) { - $this.items[i].submenu[j].onclick(); - $this.hide(e); - }); - } - } - } - } - - $('body').contextmenu(function (e) { - e.preventDefault(); - $this.show(e); - }).click(function (e) { - if (!$('#contextmenu').is(":hover")) { - $this.hide(e); - } - }); - } - show(e) { - let t = e.pageY; - let l = e.pageX; - $('#contextmenu').removeClass('left-submenu'); - if (t + $('#contextmenu').height() > $(window).height()) { - t = $(window).height() - $('#contextmenu').height(); - } - if (l + $('#contextmenu').width() > $(window).width()) { - l = $(window).width() - $('#contextmenu').width(); - } - if (l + ($('#contextmenu').width() * 2) > $(window).width()) { - $('#contextmenu').addClass('left-submenu'); - } - $('#contextmenu').css({ - top: t + "px", - left: l + "px" - }).show(); - } - hide(e) { - $('#contextmenu').hide(); - } - buildItem(item) { - let arrowLeft = ''; - let arrowRight = ''; - let shortcut = ''; - let text = ''; - let icon = '' + arrowLeft + icon + text + shortcut + arrowRight + ''; - } -} \ No newline at end of file diff --git a/js/modules/Draw.js b/js/modules/Draw.js deleted file mode 100644 index 1a853b7..0000000 --- a/js/modules/Draw.js +++ /dev/null @@ -1,64 +0,0 @@ -class Draw { - drawStyle() { - ctx.lineWidth = webDraft.size; - - if (webDraft.selectedTool === RECTANGLE) { - ctx.lineJoin = ctx.lineCap = 'miter'; - } else { - ctx.lineJoin = ctx.lineCap = 'round'; - } - - if (shapes.fill.isSet === true) { - ctx.fillStyle = hexToRgba(shapes.fill.color, shapes.fill.opacity); - } else { - ctx.fillStyle = "transparent"; - } - - if (webDraft.selectedTool === ERASER) { - ctx.globalCompositeOperation = "destination-out"; - } else { - ctx.globalCompositeOperation = "source-over"; - } - - if (webDraft.shadow.isShadow === true && webDraft.selectedTool !== ERASER) { - ctx.shadowBlur = webDraft.shadow.blur; - ctx.shadowColor = webDraft.shadow.color; - ctx.shadowOffsetX = webDraft.shadow.offsetX; - ctx.shadowOffsetY = webDraft.shadow.offsetY; - } else { - ctx.shadowBlur = 0; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - } - - ctx.strokeStyle = webDraft.color;//line color - } - drawing() { - ctx.beginPath(); - ctx.moveTo(webDraft.mPosition.x, webDraft.mPosition.y); - this.drawStyle(); - ctx.lineTo(webDraft.mPosition.x, webDraft.mPosition.y); - ctx.stroke(); - } - drawWeb() { - ctx.beginPath(); - this.drawStyle(); - ctx.moveTo(points[layers.activeId][points[layers.activeId].length - 2].x, points[layers.activeId][points[layers.activeId].length - 2].y); - ctx.lineTo(points[layers.activeId][points[layers.activeId].length - 1].x, points[layers.activeId][points[layers.activeId].length - 1].y); - ctx.stroke(); - let dx, dy, d; - let len = points[layers.activeId].length; - for (let i = 0; i < len; i++) { - dx = points[layers.activeId][i].x - points[layers.activeId][points[layers.activeId].length - 1].x; - dy = points[layers.activeId][i].y - points[layers.activeId][points[layers.activeId].length - 1].y; - d = dx * dx + dy * dy; - if (d < webDraft.sensitivityPoints) { - ctx.beginPath(); - this.drawStyle(); - ctx.moveTo(points[layers.activeId][points[layers.activeId].length - 1].x + (dx * 0.2), points[layers.activeId][points[layers.activeId].length - 1].y + (dy * 0.2)); - ctx.lineTo(points[layers.activeId][i].x - (dx * 0.2), points[layers.activeId][i].y - (dy * 0.2)); - ctx.stroke(); - } - } - } -} \ No newline at end of file diff --git a/js/modules/File.js b/js/modules/File.js deleted file mode 100644 index 0fbcd65..0000000 --- a/js/modules/File.js +++ /dev/null @@ -1,67 +0,0 @@ -class File { - constructor() { - $('body').append(''); - } - - upload(event) { - let input = event.target; - let widthImg; - let heightImg; - let output = new Image(); - - output.src = URL.createObjectURL(input.files[0]); - - output.onload = function () { - widthImg = this.width; - heightImg = this.height; - - layers.newLayer(); - layers.setLayerSize(layers.activeId, widthImg, heightImg); - webDraft.positionElements(); - - ctx.drawImage(output, 0, 0); - - layers.saveState(); - keys.O = false; - }; - } - download() { - $(webDraft.draw.selectorId).append(''); - - let temp_c = document.getElementById("tmpCanvas"); - let temp_ctx = temp_c.getContext("2d"); - $("#tmpCanvas").width(webDraft.draw.width).height(webDraft.draw.height); - - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string" && layers.list[i].visible === true) { - let imgData = document.getElementById(layers.list[i].id); - let top = parseInt($("#" + layers.list[i].id).css("top")); - let left = parseInt($("#" + layers.list[i].id).css("left")); - temp_ctx.drawImage(imgData, top, left); - } - } - - temp_c.toBlob(function (blob) { - saveAs(blob, "WebDraft-image.png"); - }); - - $("#tmpCanvas").remove(); - } - downloadFromCamera(id = null) { - let imgData = document.getElementById(id); - $(webDraft.draw.selectorId).append(''); - - let temp_c = document.getElementById("tmpCanvas"); - let temp_ctx = temp_c.getContext("2d"); - $("#tmpCanvas").width(imgData.width).height(imgData.height); - - temp_ctx.drawImage(imgData, 0, 0); - - temp_c.toBlob(function (blob) { - saveAs(blob, "WebDraft-camera-photo.png"); - }); - - $("#tmpCanvas").remove(); - - } -} \ No newline at end of file diff --git a/js/modules/Layers.js b/js/modules/Layers.js deleted file mode 100644 index 9d99bd3..0000000 --- a/js/modules/Layers.js +++ /dev/null @@ -1,349 +0,0 @@ -class Layers { - constructor() { - $.get('parts/layers.part.html', function (data) { - $('#layers').html(data); - layers.newLayer(); - }); - - this.activeId = ""; - this.list = []; - } - setLayerPosition(layerId, top, left) { - $("canvas#" + layerId).css({ - "top": top, - "left": left - }); - } - setLayerSize(layerId, width, height) { - let image = { - id: new Array(), - img: new Array() - }; - - let active = layers.activeId; - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - image.img[i] = ctx.getImageData(0, 0, webDraft.draw.width, webDraft.draw.height); - image.id[i] = layers.list[i].id; - } - } - - if (layerId === "") { - webDraft.draw.width = width; - webDraft.draw.height = height; - $("canvas.canvas-draw").attr({ - "width": webDraft.draw.width, - "height": webDraft.draw.height - }); - } else { - $("canvas#" + layerId).attr({ - "width": width, - "height": height - }); - webDraft.draw.width = 0; - webDraft.draw.height = 0; - $("canvas.canvas-draw").each(function () { - let w = parseInt($(this).attr("width")); - let h = parseInt($(this).attr("height")); - if (webDraft.draw.width < w) - webDraft.draw.width = w; - - if (webDraft.draw.height < h) - webDraft.draw.height = h; - }); - } - - for (let i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - ctx.putImageData(image.img[i], 0, 0); - layers.saveState(); - } - } - - if (active !== "") - layers.select(active); - } - saveState() { - let imgSrc = document.getElementById(layers.activeId).toDataURL(); - $(".layerView[data-id=" + layers.activeId + "]").find("img").attr("src", imgSrc).show(); - } - newLayer() { - let j; - if (isNaN(parseInt($(".layerView:last").attr("id")))) { - j = 0; - } else { - j = parseInt($(".layerView:last").attr("id")) + 1; - } - - let countViews = $("#listLayers").children(".layerView").length; - - if (countViews < 15) { - randomId = webDraft.makeid(); - - $(webDraft.draw.selectorId).append(''); - $("#listLayers").append(` -
    -
    - -
    -
    - -
    ` - ); - - let new_layer = { - visible: true, - id: randomId, - top: 0, - left: 0 - }; - layers.list.push(new_layer); - - $(".layerView").click(function () { - if (!$(this).hasClass("hidden")) { - $(".layerView").removeClass("active"); - $(this).addClass("active"); - - let identifier = $(this).attr("data-id"); - - layers.select(identifier); - } - }); - $(".hideLayer").click(function () { - let nr = $(this).parent(".layerView").attr("id"); - - $(this).hide(); - $(this) - .parent(".layerView") - .find(".showLayer") - .css({"display": "block"}); - - layers.hide(nr); - }); - $(".showLayer").click(function () { - let nr = $(this).parent(".layerView").attr("id"); - - $(this).hide(); - $(this) - .parent(".layerView") - .find(".hideLayer") - .css({"display": "block"}); - - layers.show(nr); - }); - $(".layerView[data-id=" + randomId + "]").click(); - points[randomId] = []; - - } - } - delete() { - let identifier = $(".layerView.active").attr("data-id"); - let nr = $(".layerView.active").attr("id"); - - let countViews = $(".layerView").length; - - if (countViews > 1) { - let i = parseInt(nr); - $(".layerView#" + i).remove(); - $("canvas#" + identifier).remove(); - for (i in layers.list) { - if (!$('canvas#' + layers.list[i].id).length) { - layers.list.splice(i, 1); - } - } - - let j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - - webDraft.draw.width = 0; - webDraft.draw.height = 0; - $("canvas.canvas-draw").each(function () { - const w = parseInt($(this).attr("width")); - const h = parseInt($(this).attr("height")); - if (webDraft.draw.width < w) - webDraft.draw.width = w; - - if (webDraft.draw.height < h) - webDraft.draw.height = h; - }); - $(".layerView").first().click(); - webDraft.positionElements(); - } - } - hide(nr) { - let i = parseInt(nr); - - $(".layerView#" + i).addClass("hidden"); - $("canvas#" + layers.list[i].id).addClass("invisible"); - - let j = 0; - $("canvas.canvas-draw").not('#snapImage').each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - // $(".layerView#" + i).next().not(".hidden").click() - } - show(nr) { - let i = parseInt(nr); - - $(".layerView#" + i).removeClass("hidden"); - $("canvas#" + layers.list[i].id).removeClass("invisible"); - - let j = 0; - $("canvas.canvas-draw").not('#snapImage').each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - j = 0; - $(".layerView").each(function () { - $(this).attr({ - "id": j, - "data-id": layers.list[j].id} - ); - j++; - }); - } - select(id) { - if (!$(".layerView[data-id=" + id + "]").hasClass("hidden")) { - canvas = document.getElementById(id); - ctx = canvas.getContext('2d'); - - layers.activeId = id; - } - } - rotate(angle) { - let image = new Image(); - image.src = canvas.toDataURL(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(canvas.width / 2, canvas.height / 2); - ctx.rotate(angle * Math.PI / 180); - ctx.drawImage(image, -image.width / 2, -image.width / 2); - ctx.restore(); - layers.saveState(); - } - mirror(direction) { - if (direction === 'horizontal' || direction === 'vertical') { - let image = new Image(); - image.src = canvas.toDataURL(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(canvas.width / 2, canvas.height / 2); - if (direction === 'vertical') - ctx.scale(-1, 1); - else if (direction === 'horizontal') - ctx.scale(1, -1); - ctx.drawImage(image, -image.width / 2, -image.height / 2); - ctx.restore(); - layers.saveState(); - } - } - negative() { - let destX = 0; - let destY = 0; - - let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - let pixels = imageData.data; - for (let i = 0; i < pixels.length; i += 4) { - pixels[i] = 255 - pixels[i]; // red - pixels[i + 1] = 255 - pixels[i + 1]; // green - pixels[i + 2] = 255 - pixels[i + 2]; // blue - // i+3 is alpha (the fourth element) - } - - // overwrite original image - ctx.putImageData(imageData, 0, 0);//add the function call in the imageObj.onload - layers.saveState(); - } - moveUp() { - let firstID = $(".layerView").first().attr("data-id"); - if (firstID !== layers.activeId) { - let thisNr = $(".layerView[data-id=" + layers.activeId + "]").attr("id"); - let prevNr = parseInt(thisNr) - 1; - let prevId = $(".layerView#" + prevNr).attr("data-id"); - let thisLayer = $(".layerView#" + thisNr); - let prevLayer = $(".layerView#" + prevNr); - - thisLayer.insertBefore(".layerView#" + prevNr); - thisLayer.removeAttr("id").attr("id", prevNr); - prevLayer.removeAttr("id").attr("id", thisNr); - - $("canvas#" + layers.activeId).insertBefore("canvas#" + prevId); - - let j = 0; - $("canvas.canvas-draw").each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - return true; - } - return false; - } - moveDown() { - let lastID = $(".layerView").last().attr("data-id"); - if (lastID !== layers.activeId) { - let thisNr = $(".layerView[data-id=" + layers.activeId + "]").attr("id"); - let nextNr = parseInt(thisNr) + 1; - let nextId = $(".layerView#" + nextNr).attr("data-id"); - let thisLayer = $(".layerView#" + thisNr); - let prevLayer = $(".layerView#" + nextNr); - - thisLayer.insertAfter(".layerView#" + nextNr); - thisLayer.removeAttr("id").attr("id", nextNr); - prevLayer.removeAttr("id").attr("id", thisNr); - - $("canvas#" + layers.activeId).insertAfter("canvas#" + nextId); - let j = 0; - $("canvas.canvas-draw").each(function () { - layers.list[j].id = $(this).attr("id"); - - if ($(this).hasClass("invisible")) - layers.list[j].visible = false; - else - layers.list[j].visible = true; - - j++; - }); - - return true; - } - return false; - } -} \ No newline at end of file diff --git a/js/modules/Resizer.js b/js/modules/Resizer.js deleted file mode 100644 index d2666fa..0000000 --- a/js/modules/Resizer.js +++ /dev/null @@ -1,51 +0,0 @@ -class Resizer { - constructor() { - let $this = this; - $.get('parts/resizer.part.html', function (data) { - $('#resizer').html(data); - - $("#resizer input[type=number]").keyup(function (e) { - $this.onkeyup(e); - }); - }); - } - show() { - $("#resizer").show(); - $("input[type=number]#drawWidth").val(webDraft.draw.width); - $("input[type=number]#drawHeight").val(webDraft.draw.height); - $("#resizeinfo").html(webDraft.draw.width + " " + webDraft.draw.height); - } - cancel() { - $("#resizer").hide(); - $("input[type=number]#drawWidth").val(webDraft.draw.width); - $("input[type=number]#drawHeight").val(webDraft.draw.height); - } - apply() { - $("#resizer").hide(); - let w, h; - if ($("#allLayersResizing").is(":checked")) { - w = $("input[type=number]#drawWidth").val(); - h = $("input[type=number]#drawHeight").val(); - - layers.setLayerSize('', w, h); - } else { - w = $("input[type=number]#drawWidth").val(); - h = $("input[type=number]#drawHeight").val(); - - layers.setLayerSize(layers.activeId, w, h); - } - webDraft.positionElements(); - } - onkeyup(e) { - this.onchange(); - - if (e.keyCode === 13) { - $("#apply").click(); - } - } - onchange() { - let xSize = parseInt($("input[type=number]#drawWidth").val()); - let ySize = parseInt($("input[type=number]#drawHeight").val()); - $("#resizeinfo").html(xSize + " " + ySize); - } -} \ No newline at end of file diff --git a/js/modules/Select.js b/js/modules/Select.js deleted file mode 100644 index f5b35d7..0000000 --- a/js/modules/Select.js +++ /dev/null @@ -1,132 +0,0 @@ -class Select { - constructor() { - $('#drawHandler').append('
    '); - this.hoverSelectRectangle = false; - this.isSelecting = false; - this.startSelectPoints = { - x: 0, - y: 0 - }; - } - initSelect() { - this.startSelectPoints = { - x: webDraft.mPosition.x, - y: webDraft.mPosition.y - }; - } - startSelect() { - let x, y, width, height; - if (this.startSelectPoints.x <= webDraft.mPosition.x) { - x = this.startSelectPoints.x; - width = webDraft.mPosition.x - this.startSelectPoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startSelectPoints.x - webDraft.mPosition.x; - } - if (this.startSelectPoints.y <= webDraft.mPosition.y) { - y = this.startSelectPoints.y; - height = webDraft.mPosition.y - this.startSelectPoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startSelectPoints.y - webDraft.mPosition.y; - } - $("#selectRectangle") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "width": width + "px", - "height": height + "px", - "border": "1px dashed #fff", - "background": "transparent" - }); - this.isSelecting = true; - } - selectOpt() { - this.isSelecting = false; - } - delSelectedPart() { - if ($("#selectRectangle").css("background-image") === "none") {//if selectRectangle is empty then clear part of image hovered by it - xpos = parseInt($("#selectRectangle").css("left")); - ypos = parseInt($("#selectRectangle").css("top")); - ctx.clearRect(xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height()); - } else { //else clear only selectRectangle background - $("#selectRectangle").css({"background": "transparent"}); - } - - layers.saveState(); - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px", - "background": "transparent" - }) - .width(0) - .height(0) - .hide(); - } - copySelectedPart() { - if ($("#selectRectangle").css("background-image") === "none") { - $(webDraft.draw.selectorId).append(''); - - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var testC = document.getElementById("tmpCanvas"); - var testCtx = testC.getContext('2d'); - - testCtx.drawImage(canvas, xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height(), 0, 0, $("#selectRectangle").width(), $("#selectRectangle").height()); - - var bgImg = testC.toDataURL(); - - $("#tmpCanvas").remove(); - $("#selectRectangle").css({"background": "url(" + bgImg + ") -1px -1px no-repeat"}); - } - } - cutSelectedPart() { - $(webDraft.draw.selectorId).append(''); - - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var testC = document.getElementById("tmpCanvas"); - var testCtx = testC.getContext('2d'); - - testCtx.drawImage(canvas, xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height(), 0, 0, $("#selectRectangle").width(), $("#selectRectangle").height()); - - var bgImg = testC.toDataURL(); - - $("#tmpCanvas").remove(); - $("#selectRectangle").css({"background": "url(" + bgImg + ") -1px -1px no-repeat "}); - - ctx.clearRect(xpos, ypos, $("#selectRectangle").width(), $("#selectRectangle").height()); - layers.saveState(); - } - pasteSelectedPart() { - if ($("#selectRectangle").css("background-image") !== "none") { - var xpos = parseInt($("#selectRectangle").css("left")); - var ypos = parseInt($("#selectRectangle").css("top")); - var img = new Image(); - var bg = $("#selectRectangle") - .css("background-image") - .replace('url(', '') - .replace(')', '') - .replace('"', '') - .replace('"', ''); - img.onload = function () { - ctx.drawImage(img, xpos, ypos); //save part of image when loaded - - layers.saveState(); //and then update layer preview - }; - img.src = bg; - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px", - "background": "transparent" - }) - .width(0) - .height(0) - .hide(); - } - } -} -; \ No newline at end of file diff --git a/js/modules/Shapes.js b/js/modules/Shapes.js deleted file mode 100644 index 95552c2..0000000 --- a/js/modules/Shapes.js +++ /dev/null @@ -1,162 +0,0 @@ -class Shapes { - constructor() { - $('#drawHandler').append(` -
    -
    `); - this.startShapePoints = { - x: 0, - y: 0 - }; - this.fill = { - isSet: false, - color: "#ffffff", - opacity: 100 - }; - } - - startShape() { - this.startShapePoints.x = webDraft.mPosition.x; - this.startShapePoints.y = webDraft.mPosition.y; - } - prepareRect() { - let x, y, width, height; - if (this.startShapePoints.x <= webDraft.mPosition.x) { - x = this.startShapePoints.x; - width = webDraft.mPosition.x - this.startShapePoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startShapePoints.x - webDraft.mPosition.x; - } - if (this.startShapePoints.y <= webDraft.mPosition.y) { - y = this.startShapePoints.y; - height = webDraft.mPosition.y - this.startShapePoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startShapePoints.y - webDraft.mPosition.y; - } - $("#prepareRect") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "width": width + "px", - "height": height + "px", - "border": webDraft.size + "px solid " + webDraft.color - }); - if (this.fill.isSet) { - $("#prepareRect").css({"background": hexToRgba(this.fill.color, this.fill.opacity)}); - } else { - $("#prepareRect").css({"background": "transparent"}); - } - if (webDraft.shadow.isShadow) { - $("#prepareRect").css({"box-shadow": webDraft.shadow.offsetX + "px " + webDraft.shadow.offsetY + "px " + webDraft.shadow.blur + "px " + webDraft.shadow.color}); - } else { - $("#prepareRect").css({"box-shadow": "none"}); - } - } - drawRect() { - let x, y, width, height; - if (this.startShapePoints.x >= 0 && this.startShapePoints.y >= 0) { - if (this.startShapePoints.x <= webDraft.mPosition.x) { - x = this.startShapePoints.x; - width = webDraft.mPosition.x - this.startShapePoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startShapePoints.x - webDraft.mPosition.x; - } - if (this.startShapePoints.y <= webDraft.mPosition.y) { - y = this.startShapePoints.y; - height = webDraft.mPosition.y - this.startShapePoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startShapePoints.y - webDraft.mPosition.y; - } - $("#prepareRect").hide(); - ctx.beginPath(); - draw.drawStyle(); - ctx.rect(x, y, width, height); - ctx.fill(); - ctx.stroke(); - } - this.startShapePoints.x = -1; - this.startShapePoints.y = -1; - } - prepareCircle() { - let x, y, width, height, radius; - x = this.startShapePoints.x; - y = this.startShapePoints.y; - - if (x <= webDraft.mPosition.x) { - width = webDraft.mPosition.x - x; - } else { - width = x - webDraft.mPosition.x; - } - - if (y <= webDraft.mPosition.y) { - height = webDraft.mPosition.y - y; - } else { - height = y - webDraft.mPosition.y; - } - - if (width > height) { - radius = width; - } else { - radius = height; - } - $("#prepareCircle") - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) - radius + "px", - "left": x + parseInt($(canvas).css('left')) - radius + "px", - "width": radius * 2 + "px", - "height": radius * 2 + "px", - "border": webDraft.size + "px solid " + webDraft.color, - "border-radius": "100%" - }); - - if (this.fill.isSet) { - $("#prepareCircle").css({"background": hexToRgba(this.fill.color, this.fill.opacity)}); - } else { - $("#prepareCircle").css({"background": "transparent"}); - } - - if (webDraft.shadow.isShadow) { - $("#prepareCircle").css({"box-shadow": webDraft.shadow.offsetX + "px " + webDraft.shadow.offsetY + "px " + webDraft.shadow.blur + "px " + webDraft.shadow.color}); - } else { - $("#prepareCircle").css({"box-shadow": "none"}); - } - } - drawCircle() { - let x, y, width, height, radius; - x = this.startShapePoints.x; - y = this.startShapePoints.y; - - if (x >= 0 && y >= 0) { - - if (x <= webDraft.mPosition.x) { - width = webDraft.mPosition.x - x; - } else { - width = x - webDraft.mPosition.x; - } - if (y <= webDraft.mPosition.y) { - height = webDraft.mPosition.y - y; - } else { - height = y - webDraft.mPosition.y; - } - if (width > height) { - radius = width; - } else { - radius = height; - } - $("#prepareCircle").hide(); - - ctx.beginPath(); - draw.drawStyle(); - ctx.arc(x, y, radius, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.stroke(); - } - this.startShapePoints.x = -1; - this.startShapePoints.y = -1; - } -} \ No newline at end of file diff --git a/js/modules/Text.js b/js/modules/Text.js deleted file mode 100644 index d0e06f5..0000000 --- a/js/modules/Text.js +++ /dev/null @@ -1,135 +0,0 @@ -class Text { - constructor() { - $('#drawHandler').append('
    '); - - $.get('parts/text-options.part.html', function (data) { - $('#textOptions').html(data); - - - $("#selectFontType").change(function () { - $("#textRectangle").css("font-family", $(this).val()); - }); - $("#selectFontSize").change(function () { - $("#textRectangle").css("font-size", $(this).val() + "px"); - }); - $(".textPostionTool").click(function () { - $(".textPostionTool").removeClass("active"); - $(this).addClass("active"); - $("#textRectangle").css("text-align", $(this).attr('id')); - }); - $(".styleTool").click(function () { - $(this).toggleClass("active"); - switch ($(this).attr("id")) { - case 'bold': - if ($(this).hasClass('active')) - $("#textRectangle").css("font-weight", 'bold'); - else - $("#textRectangle").css("font-weight", 'normal'); - break; - case 'italic': - if ($(this).hasClass('active')) - $("#textRectangle").css("font-style", 'italic'); - else - $("#textRectangle").css("font-style", 'normal'); - break; - case 'underline': - case 'line-through': - var textDecoration = ""; - $(".textDecoration.active").each(function () { - textDecoration += $(this).attr("id") + " "; - }); - if ($(".textDecoration.active").length > 0) - $("#textRectangle").css("text-decoration", textDecoration); - else - $("#textRectangle").css("text-decoration", 'none'); - break; - default: - - } - }); - }); - this.hoverSelectRectangle = false; - this.isSelecting = false; - this.startSelectPoints = { - x: 0, - y: 0 - }; - } - initSelect() { - this.startTextPoints = { - x: webDraft.mPosition.x, - y: webDraft.mPosition.y - }; - console.log(this.startTextPoints) - } - startSelect() { - if ($("#textRectangle").empty()) { - let x, y, width, height; - if (this.startTextPoints.x <= webDraft.mPosition.x) { - x = this.startTextPoints.x; - width = webDraft.mPosition.x - this.startTextPoints.x; - } else { - x = webDraft.mPosition.x; - width = this.startTextPoints.x - webDraft.mPosition.x; - } - if (this.startTextPoints.y <= webDraft.mPosition.y) { - y = this.startTextPoints.y; - height = webDraft.mPosition.y - this.startTextPoints.y; - } else { - y = webDraft.mPosition.y; - height = this.startTextPoints.y - webDraft.mPosition.y; - } - $("#textRectangle") - .empty() - .show() - .css({ - "top": y + parseInt($(canvas).css('top')) + "px", - "left": x + parseInt($(canvas).css('left')) + "px", - "min-height": height + "px", - "width": width + "px", - "height": height + "px", - "border": "1px dashed #fff", - "background": "transparent" - }); - this.isSelecting = true; - } - } - showTextOptions() { - this.isSelecting = false; - $("#textOptions").show(); - } - putLayer() { - $("#textRectangle").css('border', 'none'); - - if ($('#textRectangle').html() === '') { - return false; - } - - let top = parseInt($("#textRectangle").css('top')); - let left = parseInt($("#textRectangle").css('left')); - html2canvas($('#textRectangle'), { - onrendered: function (canvas) { - let widthImg = canvas.width; - let heightImg = canvas.height; - layers.newLayer(); - layers.setLayerSize(layers.activeId, widthImg, heightImg); - layers.setLayerPosition(layers.activeId, top, left); - webDraft.positionElements(); - - ctx.drawImage(canvas, 0, 0); - - layers.saveState(); - $("#textRectangle") - .css({ - "top": "0px", - "left": "0px", - "border": "1px dashed rgb(255, 255, 255)" - }) - .width(0) - .height(0) - .hide() - .empty(); - } - }); - } -} \ No newline at end of file diff --git a/js/modules/WebDraft.js b/js/modules/WebDraft.js deleted file mode 100644 index 91945f4..0000000 --- a/js/modules/WebDraft.js +++ /dev/null @@ -1,381 +0,0 @@ -class WebDraft { - constructor() { - this.isLoaded = false; - this.title = "WebDraft"; - this.click = { - left: false, //left mouse button - right: false //right mouse button - }; - this.mPosition = {//mouse position on draw - x: 0, - y: 0 - }; - this.draw = { - width: 600, - height: 600, - thisParent: "#drawHandler", - selectorId: "#draw", - eventHandler: "#eventHandler", - bg: "url('pic/transparent.png') repeat" - }; - this.shadow = { - isShadow: false, - blur: 1, - offsetX: 0, - offsetY: 0, - color: "#232324" - }; - this.sensitivityPoints = 1000; - this.size = 10; - this.color = "#000000"; - this.selectedTool = PENCIL; - } - resize() {} - makeid() { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < 15; i++) - text += possible.charAt(Math.floor(Math.random() * possible.length)); - - return text; - } - positionElements() { - var image = { - id: new Array(), - img: new Array() - }; - - var active = layers.activeId; - for (var i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - image.img[i] = ctx.getImageData(0, 0, this.draw.width, this.draw.height); - image.id[i] = layers.list[i].id; - } - } - - $(this.draw.selectorId + "," + this.draw.eventHandler).css({ - "width": this.draw.width, - "height": this.draw.height - }); - $(this.draw.thisParent).css({ - "background": this.draw.bg, - "width": this.draw.width, - "height": this.draw.height - }); - - if (this.draw.width >= $("#content").width()) { - $(this.draw.thisParent).css({"margin-left": "0px"}); - } else { - $(this.draw.thisParent).css({"margin-left": ($("#content").width() - this.draw.width) / 2}); - } - - if (this.draw.height >= $("#content").height()) { - $(this.draw.thisParent).css({"margin-top": "0px"}); - } else { - $(this.draw.thisParent).css({"margin-top": ($("#content").height() - this.draw.height) / 2}); - } - - for (var i = 0; i < layers.list.length; i++) { - if (typeof layers.list[i].id === "string") { - layers.select(layers.list[i].id); - ctx.putImageData(image.img[i], 0, 0); - layers.saveState(); - } - } - - if (active !== "") - layers.select(active); - - $("title").text(this.title + ' [' + this.draw.width + " x " + this.draw.height + ']'); - $("html, body, #paint").css({"visibility": "visible"}); - } - moveEraseRect(event) { - $("#eraseRect").css({ - "width": this.size, - "height": this.size, - "top": event.pageY - (this.size / 2) + "px", - "left": event.pageX - (this.size / 2) + "px" - }); - } - erase(event) { - this.moveEraseRect(event); - ctx.clearRect(this.mPosition.x - this.size / 2, this.mPosition.y - this.size / 2, this.size, this.size); - - points[layers.activeId] = []; -// do poprawy - } - clear() { - $(this.draw.selectorId).empty(); - $("#listLayers").empty(); - - points = {}; - - this.init(); - } - colorsampler(event) { - var x = this.mPosition.x; - var y = this.mPosition.y; - var p = ctx.getImageData(x, y, 1, 1).data; - var colorCode; - var r = p[0]; - var g = p[1]; - var b = p[2]; - var alpha = p[3]; - var a = Math.floor((100 * alpha) / 255) / 100; - - if (a <= 0) { - colorCode = ((r << 16) | (g << 8) | b).toString(16); - } else { - colorCode = "rgba(" + r + "," + g + "," + b + "," + a + ")"; - } - - if (colorCode === "0") - colorCode = "transparent"; - - $("#textColorSampler").text(colorCode); - $("#colorBoxSampler").css({"background-color": colorCode}); - } - colorsamplerSetcolor() { - if ($("#textColorSampler").text() !== 'null') { - $("#generalColor .color").css({"background": $("#textColorSampler").text()}); - $("#firstColor").val($("#textColorSampler").text()); - this.color = $("#textColorSampler").text(); - } - } - mousePosition(event) { - var touch = undefined; - - if (event.originalEvent.touches) { - touch = event.originalEvent.touches[0]; - - var pos_x = event.pageX || touch.pageX; - var pos_y = event.pageY || touch.pageY; - - - } else { - var pos_x = event.pageX; - var pos_y = event.pageY; - } - - this.mPosition.x = pos_x - parseInt($(this.draw.selectorId).offset().left) - parseInt($(canvas).css('left')); - this.mPosition.y = pos_y - parseInt($(this.draw.selectorId).offset().top) - parseInt($(canvas).css('top')); - - $("#mousePosition").text(this.mPosition.x + " , " + this.mPosition.y); - if (this.mPosition.x < 0 || this.mPosition.x > $(this.draw.selectorId).width() - || this.mPosition.y < 0 || this.mPosition.y > $(this.draw.selectorId).height()) { - $("#mousePosition").empty(); - } - } - _mousedown(event) { - this.mousePosition(event); - if ("buttons" in event) { - if (event.buttons == 1) { - this.click.left = true; - } - } - var button = event.which || event.button; - if (button == 1) { - this.click.left = true; - } - if (!this.click.right && this.click.left) { - if (this.selectedTool !== SELECT) - points[layers.activeId].push({x: this.mPosition.x, y: this.mPosition.y}); - - switch (this.selectedTool) { - case PENCIL : - case ERASER : - draw.drawing(); - break; - case SELECT : - select.initSelect(); - break; - case RECTANGLE : - case CIRCLE : - shapes.startShape(); - break; - case TEXT : - text.initSelect(); - break; - case COLORSAMPLER : - this.colorsamplerSetcolor(); - $("#pencil").click(); - break; - } - } - } - _mouseup(event) { - this.click.left = false; - this.click.right = false; - - ctx.beginPath(); - ctx.stroke(); - - switch (this.selectedTool) { - case SELECT: - select.selectOpt(); - break; - case TEXT : - text.showTextOptions(); - break; - case RECTANGLE : - shapes.drawRect(); - break; - case CIRCLE : - shapes.drawCircle(); - break; - } - - layers.saveState(); - } - _mousemove(event) { - this.mousePosition(event); - - switch (this.selectedTool) { - case ERASER: - this.moveEraseRect(event); - break; - case COLORSAMPLER : - this.colorsampler(); - break; - case SELECT : - if ( - !select.isSelecting && this.mPosition.x <= parseInt($("#selectRectangle").css("left")) + $("#selectRectangle").width() - && this.mPosition.x >= parseInt($("#selectRectangle").css("left")) - && this.mPosition.y <= parseInt($("#selectRectangle").css("top")) + $("#selectRectangle").height() - && this.mPosition.y >= parseInt($("#selectRectangle").css("top")) - ) { - select.hoverSelectRectangle = true; - $("#selectRectangle").css({"z-index": 5}); - } else { - select.hoverSelectRectangle = false; - $("#selectRectangle").css({"z-index": 3}); - } - break; - case TEXT : - if ( - !text.isSelecting && this.mPosition.x <= parseInt($("#textRectangle").css("left")) + $("#textRectangle").width() - && this.mPosition.x >= parseInt($("#textRectangle").css("left")) - && this.mPosition.y <= parseInt($("#textRectangle").css("top")) + $("#textRectangle").height() - && this.mPosition.y >= parseInt($("#textRectangle").css("top")) - ) { - text.hoverSelectRectangle = true; - $("#textRectangle").css({"z-index": 5}); - } else { - text.hoverSelectRectangle = false; - $("#textRectangle").css({"z-index": 3}); - } - break; - } - - if (this.click.left && !this.click.right) { - if (this.selectedTool !== SELECT) - points[layers.activeId].push({x: this.mPosition.x, y: this.mPosition.y}); - - switch (this.selectedTool) { - case PENCIL : - case ERASER : - ctx.lineTo(this.mPosition.x, this.mPosition.y); - ctx.stroke(); - break; - case WEB : - draw.drawWeb(); - break; - case SELECT : - if (!select.hoverSelectRectangle) - select.startSelect(); - break; - case TEXT : - if (!text.hoverSelectRectangle) - text.startSelect(); - break; - case RECTANGLE : - shapes.prepareRect(); - break; - case CIRCLE : - shapes.prepareCircle(); - break; - } - } - } - loadParts() { - $.get('parts/buttons.part.html', function (data) { - $('[data-id=buttons]').html(data); - events.buttons(); - }); - $.get('parts/info.part.html', function (data) { - $('#info').html(data); - events.info(); - }); - $.get('parts/color-picker.part.html', function (data) { - $('[data-id=color-picker]').html(data); - events.color(); - }); - $.get('parts/sliders.part.html', function (data) { - $('[data-id=sliders]').html(data); - events.sliders(); - }); - this.isLoaded = true; - } - init() { - let $this = this; - if (!this.isLoaded) { - this.loadParts(); - } else { - layers.newLayer(); - } - //events on #draw - $(this.draw.eventHandler) - .hover(function () { - if (typeof ctx !== 'undefined') { - ctx.beginPath(); - ctx.stroke(); - } - if (this.selectedTool === ERASER) { - $("#eraseRect").show(); - } - - }) - .bind("contextmenu", function (event) { - if (!DEBUG) { - event.preventDefault(); - } - this.click.right = true; - this.click.left = false; - }) - .on('mousedown', function (e) { - $this._mousedown(e); - }) - .on('mouseup', function (e) { - $this._mouseup(e); - }) - .on('mousemove', function (e) { - $this._mousemove(e); - }) - .mouseleave(function () { - ctx.stroke(); - layers.saveState(); - if (this.selectedTool === ERASER) - $("#eraseRect").hide(); - }) - .dblclick(function () { - switch (this.selectedTool) { - case SELECT: - $("#selectRectangle") - .css({ - "top": "0px", - "left": "0px" - }) - .width(0) - .height(0) - .hide(); - break; - case TEXT : - text.putLayer(); - - break; - } - }); - } -} \ No newline at end of file diff --git a/js/modules/events.module.js b/js/modules/events.module.js deleted file mode 100644 index e19081e..0000000 --- a/js/modules/events.module.js +++ /dev/null @@ -1,134 +0,0 @@ -var events = { - color: function () { - $('#generalColor').colorpicker({ - onSelect: function (color) { - $('#generalColor .color').css({ - 'background': color.hex - }); - webDraft.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - $('#shadowColor').colorpicker({ - onSelect: function (color) { - $('#shadowColor .color').css({ - 'background': color.hex - }); - webDraft.shadow.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - $('#fillColor').colorpicker({ - onSelect: function (color) { - $('#fillColor .color').css({ - 'background': color.hex - }); - shapes.fill.color = color.hex; - } - }, './vendor/rtcoder/colorpicker/html/colorpicker.html'); - - }, - info: function () { - $(".close-info").click(function () { - $("#info").hide(); - }); - }, - buttons: function () { - $("input[type=checkbox]#isShadow").change(function () { - webDraft.shadow.isShadow = $(this).is(":checked");//return true if is :checked or false if not - - if (webDraft.shadow.isShadow) { - $("#shadowColor, #shadow_slider, #shadowSquare").show(); - } else { - $("#shadowColor, #shadow_slider, #shadowSquare").hide(); - } - }); - $("input[type=checkbox]#isFillSet").change(function () { - shapes.fill.isSet = $(this).is(":checked");//return true if is :checked or false if not - - if (shapes.fill.isSet) { - $("#fillColor, #fillOpacity_slider").show(); - } else { - $("#fillColor, #fillOpacity_slider").hide(); - } - }); - - $(".paintTool").click(function () { - $(".paintTool").removeClass("active"); - $(this).addClass("active"); - - var thisId = $(this).attr("id"); - - webDraft.selectedTool = thisId; - - if (thisId === COLORSAMPLER) { - $("#previewColorSampler").show(); - } else { - $("#previewColorSampler").hide(); - } - - if (thisId === WEB) { - $("#sensitivityPoints_slider").show(); - } else { - $("#sensitivityPoints_slider").hide(); - } - - if (thisId === ERASER) { - $("#eraseRect").show(); - $("#draw, #drawHandler, #eventHandler").css({"cursor": "none"}); - } else { - $("#eraseRect").hide(); - $("#draw, #drawHandler, #eventHandler").css({"cursor": "default"}); - } - - if (thisId !== RECTANGLE) { - $("#prepareRect").hide(); - } - - if (thisId !== CIRCLE) { - $("#prepareCircle").hide(); - } - - if (thisId !== SELECT) { - $("#selectRectangle").hide(); - $("#hint, .hintGroup#selecting").hide(); - } else { - $("#hint, .hintGroup#selecting").show(); - } - - if (thisId !== TEXT) { - $("#textRectangle, #textOptions").hide(); - } - - if (thisId === RECTANGLE || thisId === CIRCLE) { - $("#fillShapeInput").show(); - } else { - $("#fillShapeInput").hide(); - $("input#isFillSet").attr('checked', false).change(); - } - }); - }, - sliders: function () { - //changing size - $("input[type=range]#pointSize").on('mousemove touchmove', function () { - webDraft.size = $(this).val(); - $("#pointSizeValue").text("size:" + $(this).val() + "px"); - }); - //changing shadow blur - $("input[type=range]#ShadowBlur").on('mousemove touchmove', function () { - webDraft.shadow.blur = $(this).val(); - $("#ShadowBlurValue").text("blur:" + $(this).val() + "px"); - }); - //changing sensitivity of web points - $("input[type=range]#sensitivityPoints").on('mousemove touchmove', function () { - webDraft.sensitivityPoints = $(this).val(); - $("#sensitivityPointsValue").text("sensitivity:" + Math.floor($(this).val() / 100) + "%"); - }); - //changing fill opacity - $("input[type=range]#fillOpacity").on('mousemove touchmove', function () { - shapes.fill.opacity = $(this).val(); - $("#fillOpacityValue").text("fill opacity:" + Math.floor($(this).val()) + "%"); - }); - } - -}; \ No newline at end of file diff --git a/js/modules/keys.module.js b/js/modules/keys.module.js deleted file mode 100644 index 9571840..0000000 --- a/js/modules/keys.module.js +++ /dev/null @@ -1,171 +0,0 @@ -var keys = { - Enter: false, //press Enter - Esc: false, //press Escape (Esc) - f11: false, //press F11 - f12: false, //press F12 - delete: false, //press delete - C: false, - X: false, - V: false, - O: false, - I: false -}; - -$(document) - .keydown(function (event) { - if (webDraft.selectedTool !== TEXT && $('#resizer').is('hidden')) { - event.preventDefault(); - } - - switch (event.keyCode) { - case 13 : - keys.Enter = true; - break; - case 27 : - keys.Esc = true; - break; - case 46 : - keys.delete = true; - break; - case 67 : - keys.C = true; - break; - case 73 : - keys.I = true; - break; - case 79 : - keys.O = true; - break; - case 83 : - keys.S = true; - break; - case 86 : - keys.V = true; - break; - case 88 : - keys.X = true; - break; - case 122 : - keys.f11 = true; - break; - case 123 : - keys.f12 = true; - break; - } - if (keys.delete) { - if (webDraft.selectedTool === SELECT) { - select.delSelectedPart(); - } - if (event.ctrlKey) { - webDraft.clear(); - } - } - - if (keys.f12 || keys.f11 || event.ctrlKey || keys.delete) { - event.preventDefault(); - } - - if (keys.C) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.copySelectedPart(); - } - } - - if (keys.I) { - if (event.ctrlKey) { - $("#info").toggle(); - } - } - - if (keys.X) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.cutSelectedPart(); - } - } - - if (keys.V) { - if (webDraft.selectedTool === SELECT && event.ctrlKey) { - select.pasteSelectedPart(); - } - } - if (keys.O) { - if (event.ctrlKey) { - $("#fileUploader").click(); - } - } - if (keys.S) { - if (event.ctrlKey) { - file.download(); - } - } - - }) - .keyup(function (event) { - switch (event.keyCode) { - case 13 : - keys.Enter = false; - break; - case 27 : - keys.Esc = false; - break; - case 46 : - keys.delete = false; - break; - case 67 : - keys.C = false; - break; - case 73 : - keys.I = false; - break; - case 79 : - keys.O = false; - break; - case 83 : - keys.S = false; - break; - case 86 : - keys.V = false; - break; - case 88 : - keys.X = false; - break; - case 122 : - keys.f11 = false; - break; - case 123 : - keys.f12 = false; - break; - } - }); - -$(window).bind('mousewheel DOMMouseScroll', function (event) { - if (event.ctrlKey === true) { - event.preventDefault(); - - if (event.originalEvent.wheelDelta / 120 > 0) { - if (webDraft.size < 250) { - webDraft.size += 2; - } else { - webDraft.size = 250; - } - } else { - if (webDraft.size > 1) { - webDraft.size -= 2; - } else { - webDraft.size = 1; - } - } - $("#eraseRect").css({ - "width": webDraft.size, - "height": webDraft.size, - "top": event.pageY - (webDraft.size / 2) + "px", - "left": event.pageX - (webDraft.size / 2) + "px" - }); - $("input#pointSize").val(webDraft.size); - $("#pointSizeValue").text("size:" + webDraft.size + "px"); - } - - if (event.altKey === true) { - // event.preventDefault(); - } -}); \ No newline at end of file diff --git a/js/start.js b/js/start.js deleted file mode 100644 index 17c293d..0000000 --- a/js/start.js +++ /dev/null @@ -1,141 +0,0 @@ -const DEBUG = 0; - -const PENCIL = 'pencil'; -const SELECT = 'select'; -const ERASER = 'eraser'; -const WEB = 'web'; -const TEXT = 'text'; -const RECTANGLE = 'rectangle'; -const CIRCLE = 'circle'; -const COLORSAMPLER = 'colorsampler'; - - -let items = [ - { - text: 'Clear', - shortcut: 'ctrl+del', - icon: 'fas fa-ban', - onclick: function () { - webDraft.clear(); - } - }, - { - text: 'Upload image', - shortcut: 'ctrl+o', - icon: 'fas fa-upload', - onclick: function () { - $('#fileUploader').click(); - } - }, - { - text: 'Save image', - shortcut: 'ctrl+s', - icon: 'fas fa-download', - onclick: function () { - file.download(); - } - }, - { - text: 'Color', - icon: 'fas fa-tint', - submenu: [ - { - text: 'Invert', - icon: 'fas fa-adjust', - onclick: function () { - layers.negative(); - } - }, - { - text: 'Colorpicker', - icon: 'fas fa-eye-dropper', - onclick: function () { - $('#colorsampler').click(); - } - } - ] - } -]; - -let context_menu = new Contextmenu(items); -let camera = new Camera(); -let file = new File(); -let shapes = new Shapes(); -let draw = new Draw(); -let text = new Text(); -let resizer = new Resizer(); -let layers = new Layers(); -let select = new Select(); -let webDraft = new WebDraft(); - - -let canvas, - ctx, - randomId, - points = {}; - - -$(window) - .resize(function () { - webDraft.resize(); - webDraft.positionElements(); - }); -$(document) - .ready(function (event) { - if (/mobile/i.test(navigator.userAgent)) { - webDraft.draw.width = window.innerWidth - 10; - webDraft.draw.height = window.innerHeight - 30; - } - - webDraft.init(); - $("#selectRectangle, #textRectangle") - .draggable({snap: false}) - .css({"position": "absolute"}); - - $("#shadowDot").draggable({ - containment: "#shadowSquare", - scroll: false, - drag: function () { - let shadowY = parseInt($(this).css('top')) - (parseInt($(this).parent().height()) / 2); - let shadowX = parseInt($(this).css('left')) - (parseInt($(this).parent().width()) / 2); - webDraft.shadow.offsetX = shadowX; - webDraft.shadow.offsetY = shadowY; - } - }); - $('#shadowSquare').on('mousedown', function (e) { - let x = e.pageX - $(this).offset().left; - let y = e.pageY - $(this).offset().top; - - $("#shadowDot").css({ - top: (y - 5) + 'px', - left: (x - 5) + 'px' - }); - - let shadowY = parseInt($("#shadowDot").css('top')) - (parseInt($("#shadowDot").parent().height()) / 2); - let shadowX = parseInt($("#shadowDot").css('left')) - (parseInt($("#shadowDot").parent().width()) / 2); - webDraft.shadow.offsetX = shadowX; - webDraft.shadow.offsetY = shadowY; - }); - - - }) - .bind("contextmenu", function (e) { - if (!DEBUG) { - e.preventDefault(); - } - }) - .on('mouseup touchend', function (e) { - webDraft._mouseup(e); - }) - .on('mousemove touchmove', function (e) { - webDraft._mousemove(e); - }); - -function hexToRgba(hex, opacity) { - hex = hex.replace('#', ''); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - const a = opacity / 100; - return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; -} \ No newline at end of file diff --git a/less/camera.less b/less/camera.less deleted file mode 100644 index 372eec9..0000000 --- a/less/camera.less +++ /dev/null @@ -1,181 +0,0 @@ -@import 'variables'; - -#camera { - box-shadow: 0px 0px 5px 2px #6f6f6f; - width: 740px; - max-width: 100vw; - height: 560px; - max-height: 100vh; - background: @dark3; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - position: absolute; - z-index: 99; - display: none; - &.opened { - display: block; - } - &.fullscreen { - width: 100vw; - height: 100vh; - top: 0; - left: 0; - transform: translate(0, 0); - .cameraContent { - .videoContainer { - width: 100vw; - height: ~"calc(100vh - 130px)"; - } - .filters { - width: 100vw; - height: 50px; - line-height: 50px; - label { - span.filter { - margin: 15px 5px; - } - } - } - } - } - #cameraHeader { - width: 100%; - height: 30px; - #cameraTitle { - width: ~"calc(100% - 70px)"; - float: left; - height: 30px; - line-height: 30px; - padding: 0px 5px; - color: @white1; - } - button { - width: 30px; - height: 30px; - border: none; - display: inline-block; - cursor: pointer; - float: right; - i { - cursor: pointer; - } - &#closeCamera { - background: @red1; - color: @white1; - } - &#switchFullscreen { - background: @blue2; - color: @white1; - margin-right: 10px; - } - } - } - .cameraContent { - width: 100%; - float: left; - margin-top: 10px; - .videoContainer { - width: ~"calc(100% - 100px)"; - float: left; - position: relative; - canvas#snapImage, - canvas#frame, - canvas#additional, - video { - width: 100%; - max-width: 100vw; - max-height: ~"calc(100vh - 130px)"; - float: left; - } - canvas#snapImage, - canvas#frame { - display: none; - } - canvas#additional { - display: block; - position: absolute; - top: 0; - left: 0; - } - } - .filters { - width: 100px; - float: left; - label { - input { - display: none; - &:checked + span { - background: @blue1; - color: @white1 - } - } - span.filter { - width: 70px; - height: 20px; - margin: 0px 15px 5px; - cursor: pointer; - background: @white2; - line-height: 20px; - text-align: center; - font-size: 13px; - float: left; - } - } - } - #cameraButtons { - float: left; - width: 100%; - height: 50px; - line-height: 50px; - button { - min-width: 50px; - border: none; - display: none; - cursor: pointer; - * { - cursor: pointer; - } - &#snap { - display: inline-block; - } - &#applySnap { - background: @green2; - color: @white1; - } - &#cancelSnap { - background: @red1; - color: @white1; - } - &#saveSnapOnComputer { - background: @blue1; - color: @white1; - } - } - } - - } - - &.snapped { - - #cameraButtons { - button { - display: inline-block; - &#snap { - display: none; - } - } - } - .cameraContent { - .videoContainer { - canvas#additional, - video#video { - display: none; - } - canvas#snapImage { - display: block; - } - } - } - } -} \ No newline at end of file diff --git a/less/contextmenu.less b/less/contextmenu.less deleted file mode 100644 index 2bc2793..0000000 --- a/less/contextmenu.less +++ /dev/null @@ -1,118 +0,0 @@ -@import "variables"; - -menu#contextmenu{ - display: none; - width: 230px; - height: auto; - position: fixed; - top:50%; - left:50%; - background: #fff; - z-index:10; - ul{ - li{ - cursor: pointer; - float: left; - height: 30px; - line-height: 30px; - position: relative; - width: 100%; - background: #fff; - color: @black1; - *{ - cursor: pointer; - } - .content{ - width: 100%; - float: left; - span{ - height: inherit; - float: left; - &.icon{ - width: 20px; - i{ - float: left; - width: 20px; - height: 30px; - line-height: 30px; - } - } - &.text{ - width: ~"calc(100% - 130px)"; - } - &.shortcut{ - text-align: right; - text-transform: uppercase; - width: 70px; - font-size: 11px; - color: @dark2; - } - &.arrow{ - color: @dark2; - width: 10px; - margin: 0px 5px; - font-size: 11px; - visibility: hidden; - opacity: 0; - } - } - } - .submenu{ - width: 230px; - position: absolute; - display: none; - top: 0; - left: 230px; - &+.content{ - span{ - &.arrow{ - &.left-arrow{ - visibility: hidden; - opacity: 0; - } - &.right-arrow{ - visibility: visible; - opacity: 1; - float: right; - } - } - } - } - } - &.disabled{ - color: @gray1; - } - &:not(.disabled):hover{ - color: @blue1; - background-color: @white6; - .submenu{ - display: block; - color: @black1; - } - } - } - } - &.left-submenu{ - ul{ - li{ - .submenu{ - left: -230px; - &+.content{ - span{ - &.arrow{ - &.left-arrow{ - visibility: visible; - opacity: 1; - } - &.right-arrow{ - visibility: hidden; - opacity: 0; - } - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/less/hints.less b/less/hints.less deleted file mode 100644 index 0561830..0000000 --- a/less/hints.less +++ /dev/null @@ -1,43 +0,0 @@ -//hints -@import "variables"; - - -#hint{ - width: 100vw; - height: 50px; - position: absolute; - bottom: 20px; - left: 0; - background: @dark3; - display: none; - .hintGroup{ - width: 100%; - height: 100%; - float: left; - display: none; - .keyboardButton{ - float: left; - padding: 0 5px; - min-width: 30px; - height: 20px; - background: @white5; - box-sizing: border-box; - border: 1px solid @gray3; - border-radius: 2px; - margin: 15px 5px; - color: @dark3; - text-align: center; - line-height: 20px; - font-size: 12px; - } - .describeHint{ - width: auto; - float: left; - height: 50px; - line-height: 50px; - margin: 0 5px 0 0; - color: @white4; - font-size: 12px; - } - } -} \ No newline at end of file diff --git a/less/info.less b/less/info.less deleted file mode 100644 index 657577c..0000000 --- a/less/info.less +++ /dev/null @@ -1,63 +0,0 @@ -//info -@import "variables"; - -#info{ - width: 350px; - height: 100vh; - z-index: 5; - position: absolute; - display: none; - overflow: auto; - background: @dark3; - border-left: 1px solid @gray2; - color: @white1; - top: 0px; - right: 0px; - @media all and (max-width: 400px){ - width: 100vw; - } - .close-info { - position: absolute; - right: 0; - cursor: pointer; - margin: 5px; - } - .info-section { - width: 100%; - float: left; - margin: 5px 0px; - .info-section-title { - width: 100%; - float: left; - text-align: center; - } - .info-section-line { - width: 100%; - float: left; - .info-section-shortcut { - width: 30%; - float: left; - padding: 2px 5px; - text-align: right; - span { - padding: 0 5px; - min-width: 30px; - height: 20px; - border-radius: 2px; - margin: 15px 5px; - text-align: center; - line-height: 20px; - font-size: 12px; - background: @white5; - border: 1px solid @gray3; - color: @dark3; - } - } - .info-section-shortcut-describe { - width: 70%; - float: left; - padding: 2px 5px; - } - } - } -} \ No newline at end of file diff --git a/less/inputs.less b/less/inputs.less deleted file mode 100644 index 54b23f4..0000000 --- a/less/inputs.less +++ /dev/null @@ -1,149 +0,0 @@ -//inputs -@import "variables"; - -input{ - &[type=checkbox]{ - &+label{ - cursor: pointer; - color: @gray2; - width: 100%; - background: transparent; - text-align: left; - border: none; - border-radius: 0px; - .fa-circle, .fa-check-circle{ - float: left; - text-align: center; - width: 30px; - vertical-align: middle; - margin: 0px 0px 0px 10px; - font-size: 18px; - height: 20px; - line-height: 20px; - } - .fa-check-circle{ - display: none; - } - #l{ - margin-left: 10px; - height: 20px; - line-height: 20px; - } - span{ - width: 100%; - height: 20px; - text-align: left; - &.label{ - width: auto; - } - } - } - &:checked{ - &+label{ - font-weight: bold; - color: @blue1; - .fa-circle{ - display: none; - } - .fa-check-circle{ - display: block; - } - } - } - &:hover{ - &+label{ - color: @white1; - .fa-circle{ - display: none; - } - .fa-check-circle{ - display: block; - } - } - } - } - &[type=range]{ - -webkit-appearance: none; - width: calc(~"100% - 20px"); - margin: 0 10px; - &::-webkit-slider-runnable-track{ - width: 230px; - height: 10px; - background: @white3; - border: none; - border-radius: 0px; - } - &::-webkit-slider-thumb{ - -webkit-appearance: none; - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: @blue1; - } - &::-moz-range-track{ - width: 230px; - height: 10px; - background: @white3; - border: none; - } - &::-moz-range-thumb{ - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: @blue1; - } - &::-ms-track{ - width: 230px; - height: 10px; - background: transparent; - border-color: transparent; - border-width: 0; - color: transparent; - } - &::-ms-thumb{ - border: none; - height: 10px; - width: 10px; - border-radius: 0px; - background: @blue1; - } - &:-moz-focusring{ - outline: none; - } - &::-ms-fill-lower{ - background: @white3; - } - &::-ms-fill-upper{ - background: @white3; - } - &:focus{ - outline: none; - &::-ms-fill-lower{ - background: @gray1; - } - &::-webkit-slider-runnable-track{ - background: @gray3; - } - &::-ms-fill-upper{ - background: @gray3; - } - } - } - &[type=color]{ - visibility: hidden; - width: 0px; - height: 0px; - position: absolute; - } - &#fileUploaderInput{ - display: none; - } - &#isFillSet{ - display: none; - } - &#isShadow{ - display: none; - } -} \ No newline at end of file diff --git a/less/layers.less b/less/layers.less deleted file mode 100644 index 449672c..0000000 --- a/less/layers.less +++ /dev/null @@ -1,110 +0,0 @@ -//layers -@import "variables"; - -#layers { - .title{ - width: 100%; - height: 20px; - line-height: 20px; - float: left; - font-size: 15px; - text-align: center; - background: @white2; - color: @dark2; - } - #layersBtns{ - width: 100%; - background: @dark3; - float: left; - z-index: 4; - color: @gray2; - .layerTool{ - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: @gray2; - cursor: pointer; - &#addLayer{ - color: @green1; - } - &#delLayer{ - color: @red1; - } - &:hover{ - color: @white1; - } - .fas, .fab, .far{ - font-size: 15px; - } - } - } - #listLayers{ - width: 100%; - background: @dark3; - float: left; - z-index: 4; - color: @gray2; - max-height: calc(~"50vh - 170px"); - overflow: auto; - top: 50px; - .layerView{ - position: relative; - width: 100%; - height: 60px; - margin: 10px auto; - background: transparent; - text-align: center; - text-align: center; - cursor: pointer; - &.active { - cursor: default; - background-color: @rgba1; - &:hover{ - cursor: default; - background-color: @rgba1; - } - .imgLayerContainer{ - border-color: @blue2; - } - } - &:hover { - background-color: @rgba2; - } - .imgLayerContainer{ - float: left; - border: 1px solid @gray3; - width: 60px; - height: 60px; - img.imgLayer{ - float: left; - max-width: 58px; - max-height: 58px; - } - } - .hideLayer, .showLayer{ - float: left; - margin: 21px 5px; - width: 18px; - height: 18px; - line-height: 18px !important; - text-align: center; - cursor: pointer; - } - - .showLayer{ - color: @red1; - &:hover{ - color: @green1; - } - } - .hideLayer{ - color: @green1; - &:hover{ - color: @red1; - } - } - } - } -} \ No newline at end of file diff --git a/less/resizer.less b/less/resizer.less deleted file mode 100644 index e55d6cd..0000000 --- a/less/resizer.less +++ /dev/null @@ -1,50 +0,0 @@ -//resizer -@import "variables"; - - -#resizer{ - width: 300px; - height: 210px; - position: absolute; - top: 0; - left: 0; - background: @dark3; - display: none; - padding: 10px; - z-index: 4; - margin-left: calc(~"(100vw - 300px) / 2"); - margin-top: calc(~"(100vh - 210px) / 2"); - input[type=number]{ - width: 250px; - height: 30px; - margin: 10px 15px; - border: none; - cursor: text; - text-align: center; - float: left; - } - #resizeinfo{ - width: 250px; - height: 30px; - line-height: 30px; - text-align: center; - color: @gray3; - float: left; - margin: 0px 20px; - } - .buttonAction{ - float: left; - width: 120px; - height: 30px; - line-height: 30px; - font-size: 15px; - margin: 10px 10px; - text-align: center; - background: @gray3; - cursor: pointer; - &:hover{ - background: @blue1; - color: @gray3; - } - } -} diff --git a/less/shadow-changer.less b/less/shadow-changer.less deleted file mode 100644 index b7c926d..0000000 --- a/less/shadow-changer.less +++ /dev/null @@ -1,41 +0,0 @@ -//shadow-changer -@import "variables"; - - -#shadowSquare{ - width: 200px; - height: 200px; - position: relative; - background: @rgba3; - float: left; - display: none; - #shadowDot{ - cursor: move; - background: @white1; - border-radius: 100%; - width: 10px; - height: 10px; - position: absolute; - top: 95px; - left: 95px; - z-index: 2; - } - &::after, &::before{ - position: absolute; - content: ' '; - background: @gray2; - z-index: 1; - } - &::after{ - width: 200px; - height: 1px; - top: 50%; - left: 0px; - } - &::before{ - width: 1px; - height: 200px; - top: 0%; - left: 50%; - } -} \ No newline at end of file diff --git a/less/sliders.less b/less/sliders.less deleted file mode 100644 index a52fe6e..0000000 --- a/less/sliders.less +++ /dev/null @@ -1,31 +0,0 @@ -//sliders -@import "variables"; - -#sliders{ - width: 100%; - float: left; - .sliderContainer{ - width: 100%; - height: 45px; - float: left; - &#shadow_slider{ - display: none; - } - &#sensitivityPoints_slider{ - display: none; - } - &#fillOpacity_slider{ - display: none; - } - .sliderVal{ - width: 100%; - height: 20px; - margin-top: 5px; - line-height: 20px; - word-wrap: break-word; - font-size: 13px; - float: left; - text-align: center; - } - } -} \ No newline at end of file diff --git a/less/statusbar.less b/less/statusbar.less deleted file mode 100644 index 2a6db74..0000000 --- a/less/statusbar.less +++ /dev/null @@ -1,32 +0,0 @@ -//statusbar -@import "variables"; - -#statusbar{ - width: 100%; - background: @dark3; - height: 20px; - line-height: 20px; - float: left; - position: absolute; - bottom: 0; - font-size: 13px; - color: @gray3; - #mousePosition{ - float: left; - width: 100px; - height: inherit; - line-height: inherit; - text-align: center; - } - #author{ - height: inherit; - line-height: inherit; - float: right; - right: 0; - margin-right: 20px; - a{ - color: @white2; - cursor: pointer; - } - } -} \ No newline at end of file diff --git a/less/style.less b/less/style.less deleted file mode 100644 index 5fac498..0000000 --- a/less/style.less +++ /dev/null @@ -1,92 +0,0 @@ -@import "variables"; -@import "inputs"; -@import "tools"; -@import "text-options"; -@import "layers"; -@import "sliders"; -@import "info"; -@import "resizer"; -@import "hints"; -@import "shadow-changer"; -@import "statusbar"; -@import "camera"; -@import "contextmenu"; - -@import url(https://fonts.googleapis.com/css?family=Open+Sans); -*{ - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: default; - font-family: 'Open Sans', sans-serif; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - margin: 0; - padding: 0; -} -ul{ - list-style: none; - -} -html{ - body{ - top: 0; - left: 0; - width: 100%; - height: 100%; - float: left; - position: fixed; - overflow: hidden; - #content{ - float: left; - background: @bg_color; - overflow: auto; - width: calc(~"100vw - 200px"); - height: calc(~"100vh - 20px"); - #drawHandler{ - border: 1px dotted @blue1; - position: relative; - overflow: hidden; - #draw, #drawHandler, #eventHandler{ - width: 600px; - height: 400px; - overflow: hidden; - position: relative; - /*cursor: crosshair;*/ - } - #eventHandler{ - z-index: 3; - position: absolute; - } - #draw{ - canvas{ - position: absolute; - cursor: crosshair; - border: 1px dotted #0052CC; - &.invisible{ - visibility: hidden; - } - } - } - } - } - #eraseRect, - #prepareRect, - #prepareCircle, - #selectRectangle{ - display: none; - position: absolute; - z-index: 2; - border: 1px solid @black1; - } - #selectRectangle{ - cursor: move; - } - #eraseRect{ - border-radius: 100%; - border-color: @gray3; - } - } -} \ No newline at end of file diff --git a/less/text-options.less b/less/text-options.less deleted file mode 100644 index e1a66f6..0000000 --- a/less/text-options.less +++ /dev/null @@ -1,53 +0,0 @@ -//text-options -@import "variables"; - -#textOptions{ - display: none; - #alignBtns{ - width: 100%; - float: left; - .textPostionTool{ - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: @gray2; - cursor: pointer; - .fas, .fab, .far{ - font-size: 15px; - } - &:hover, &.active{ - color: @white1; - } - } - } - #fontStyleBtns{ - width: 100%; - float: left; - .styleTool{ - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: @gray2; - cursor: pointer; - .fas, .fab, .far{ - font-size: 15px; - } - &:hover, &.active{ - color: @white1; - } - } - } -} - -#textRectangle{ - display: none; - position: absolute; - z-index: 2; - border: 1px solid @black1; - font-size: 8mm; - cursor: move; -} \ No newline at end of file diff --git a/less/tools.less b/less/tools.less deleted file mode 100644 index 69afc57..0000000 --- a/less/tools.less +++ /dev/null @@ -1,150 +0,0 @@ -//tools -@import "variables"; - -#toolsGroup{ - width: 200px; - height: calc(~"100vh - 20px"); - background: @dark3; - float: left; - #previewColorSampler{ - width: 100%; - height: 60px; - background: 0 30px url('/pic/transparent.png'); - z-index: 1; - display: none; - #textColorSampler{ - width: 100%; - float: left; - height: 30px; - background: @dark3; - line-height: 30px; - text-align: center; - font-size: 13px; - color: @white2; - } - #colorBoxSampler{ - width: 100%; - height: 30px; - float: left; - } - } - .tools{ - width: 100%; - background: @dark3; - float: left; - z-index: 4; - color: @gray2; - .title{ - width: 100%; - height: 20px; - line-height: 20px; - float: left; - font-size: 15px; - text-align: center; - background: @white2; - color: @dark2; - .fas, .fab, .far{ - position: relative; - height: inherit; - line-height: inherit; - left: 0; - top: 0; - text-align: center; - } - } - #toolsBtns{ - width: 100%; - float: left; - .primaryTool, - .paintTools{ - width: 50px; - height: 30px; - line-height: 30px; - text-align: center; - float: left; - color: @gray2; - cursor: pointer; - .fas, .fab, .far{ - font-size: 15px; - } - &:hover{ - color: @white1; - } - } - .paintTools{ - &.active{ - cursor: default; - color: @blue1; - background: @dark1; - } - } - .primaryTool{ - &:active{ - cursor: default; - color: @blue1; - background: @dark1; - } - } - } - #radioBtns{ - width: 100%; - float: left; - #shadowInput, #fillShapeInput{ - width: 100%; - line-height: 20px; - float: left; - padding: 5px 0px; - font-size: 12px; - *{ - cursor: pointer; - float: left; - } - } - #fillShapeInput{ - display: none; - } - } - #colorPicker{ - width: 100%; - float: left; - .colorTab{ - width: 100%; - height: 25px; - line-height: 25px; - font-size: 14px; - cursor: pointer; - .color{ - border-radius: 100%; - width: 15px; - height: 15px; - margin: 5px 10px; - float: left; - border: 1px solid @gray3; - } - &#generalColor{ - .color{ - background: #000000; - } - } - &#shadowColor{ - display: none; - .color{ - background: #232324; - } - } - &#fillColor{ - display: none; - .color{ - background: #FFFFFF; - } - } - &:hover { - color: @blue1; - background: @dark1; - } - } - } - - - } -} \ No newline at end of file diff --git a/less/variables.less b/less/variables.less deleted file mode 100644 index 3af0bfd..0000000 --- a/less/variables.less +++ /dev/null @@ -1,31 +0,0 @@ -//variables -@bg_color: #373636; - - -@black1: #000000; - -@white1: #FFFFFF; -@white2: #EEEEEE; -@white3: #DDDDDD; -@white4: #F1F1E4; -@white5: #F1F1F1; -@white6: #dedede; - -@dark1: #1C1B1B; -@dark2: #2E3436; -@dark3: #2C2B2B; - -@gray1: #888888; -@gray2: #A9A9A9; -@gray3: #CCCCCC; - -@blue1: #0052CC; -@blue2: #0021CC; - -@red1: #C50000; -@green1: #16c600; -@green2: #129a01; - -@rgba1: rgba(0,0,0,0.1); -@rgba2: rgba(0,0,0,0.3); -@rgba3: rgba(0,0,0,0.4); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1be093f..952db92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1547 +1,888 @@ { - "name": "webdraft", - "version": "2.3.3", - "lockfileVersion": 1, + "name": "webdraft-v3", + "version": "0.1.0", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "optional": true - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "optional": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "optional": true - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "optional": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", - "requires": { - "continuable-cache": "0.3.1", - "error": "7.0.2", - "raw-body": "1.1.7", - "safe-json-parse": "1.0.1" - } - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "optional": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", - "requires": { - "source-map": "0.5.7" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "optional": true - }, - "coffeescript": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", - "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=", - "dev": true - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "optional": true, - "requires": { - "boom": "2.10.1" + "packages": { + "": { + "name": "webdraft-v3", + "version": "0.1.0", + "devDependencies": { + "typescript": "^5.5.4", + "vite": "8.0.5" } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", "dev": true, - "requires": { - "array-find-index": "1.0.2" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "license": "MIT", "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, + "peer": true, "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true - } + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", "dev": true, - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "license": "MIT", "optional": true, - "requires": { - "prr": "1.0.1" - } - }, - "error": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", - "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", - "requires": { - "string-template": "0.2.1", - "xtend": "4.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "optional": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "requires": { - "websocket-driver": "0.7.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "findup-sync": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", - "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", - "dev": true, - "requires": { - "glob": "5.0.15" - }, + "peer": true, "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "optional": true - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "requires": { - "globule": "1.2.0" - } - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true - } + "tslib": "^2.4.0" } }, - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", - "requires": { - "glob": "7.1.2", - "lodash": "4.17.10", - "minimatch": "3.0.4" - }, + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } + "tslib": "^2.4.0" } }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "grunt": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.2.tgz", - "integrity": "sha1-TmpeaVtwRy/VME9fqeNCNoNqc7w=", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, - "requires": { - "coffeescript": "1.10.0", - "dateformat": "1.0.12", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.3.0", - "glob": "7.0.6", - "grunt-cli": "1.2.0", - "grunt-known-options": "1.1.0", - "grunt-legacy-log": "1.0.2", - "grunt-legacy-util": "1.0.0", - "iconv-lite": "0.4.23", - "js-yaml": "3.5.5", - "minimatch": "3.0.4", - "nopt": "3.0.6", - "path-is-absolute": "1.0.1", - "rimraf": "2.2.8" - }, + "license": "MIT", + "optional": true, "dependencies": { - "grunt-cli": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", - "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", - "dev": true, - "requires": { - "findup-sync": "0.3.0", - "grunt-known-options": "1.1.0", - "nopt": "3.0.6", - "resolve": "1.1.7" - } - } - } - }, - "grunt-contrib-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-1.0.1.tgz", - "integrity": "sha1-YVCYYwhOhx1+ht5IwBUlntl3Rb0=", - "requires": { - "chalk": "1.1.3", - "source-map": "0.5.7" - } - }, - "grunt-contrib-cssmin": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.1.tgz", - "integrity": "sha512-IXNomhQ5ekVZbDbj/ik5YccoD9khU6LT2fDXqO1+/Txjq8cp0tQKjVS8i8EAbHOrSDkL7/UD6A7b+xj98gqh9w==", - "requires": { - "chalk": "1.1.3", - "clean-css": "4.1.11", - "maxmin": "2.1.0" - } - }, - "grunt-contrib-less": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-less/-/grunt-contrib-less-1.4.1.tgz", - "integrity": "sha1-O73sC3XRLOqlXWKUNiXAsIYc328=", - "requires": { - "async": "2.6.1", - "chalk": "1.1.3", - "less": "2.7.3", - "lodash": "4.17.10" + "@tybys/wasm-util": "^0.10.2" }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.10" - } - } - } - }, - "grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "requires": { - "async": "2.6.1", - "gaze": "1.1.3", - "lodash": "4.17.10", - "tiny-lr": "1.1.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.10" - } - } + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", - "dev": true - }, - "grunt-legacy-log": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz", - "integrity": "sha512-WdedTJ/6zCXnI/coaouzqvkI19uwqbcPkdsXiDRKJyB5rOUlOxnCnTVbpeUdEckKVir2uHF3rDBYppj2p6N3+g==", + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, - "requires": { - "colors": "1.1.2", - "grunt-legacy-log-utils": "1.0.0", - "hooker": "0.2.3", - "lodash": "4.17.10" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "chalk": "1.1.3", - "lodash": "4.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", - "dev": true, - "requires": { - "async": "1.5.2", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "4.3.0", - "underscore.string": "3.2.3", - "which": "1.2.14" + "@napi-rs/wasm-runtime": "^1.1.1" }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true - } + "engines": { + "node": ">=14.0.0" } }, - "gzip-size": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", - "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", - "requires": { - "duplexer": "0.1.1" - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true - }, - "http-parser-js": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", - "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=" - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", "dev": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "optional": true + "license": "MIT" }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "requires": { - "number-is-nan": "1.0.1" + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "optional": true - }, - "js-yaml": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", - "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "optional": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { "optional": true } } }, - "less": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", - "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", - "requires": { - "errno": "0.1.7", - "graceful-fs": "4.1.11", - "image-size": "0.5.5", - "mime": "1.6.0", - "mkdirp": "0.5.1", - "promise": "7.3.1", - "request": "2.81.0", - "source-map": "0.5.7" - } - }, - "livereload-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", - "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==" - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "material-design-icons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", - "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" - }, - "maxmin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz", - "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=", - "requires": { - "chalk": "1.1.3", - "figures": "1.7.0", - "gzip-size": "3.0.0", - "pretty-bytes": "3.0.1" + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "optional": true - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "1.33.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", "optional": true, - "requires": { - "minimist": "0.0.8" + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "error-ex": "1.3.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "pinkie-promise": "2.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "optional": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "pretty-bytes": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", - "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MPL-2.0", "optional": true, - "requires": { - "asap": "2.0.6" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "optional": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "optional": true - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", - "requires": { - "bytes": "1.0.0", - "string_decoder": "0.10.31" + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "license": "MPL-2.0", "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", "optional": true, - "requires": { - "hoek": "2.16.3" + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "license": "ISC" }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "dev": true, - "requires": { - "is-utf8": "0.2.1" + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "requires": { - "get-stdin": "4.0.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "requires": { - "body": "5.1.0", - "debug": "3.1.0", - "faye-websocket": "0.10.0", - "livereload-js": "2.3.0", - "object-assign": "4.1.1", - "qs": "6.4.0" - } - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", - "dev": true - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "license": "0BSD", "optional": true }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, + "node_modules/vite": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.5.tgz", + "integrity": "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==", + "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } - }, - "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", - "requires": { - "http-parser-js": "0.4.13", - "websocket-extensions": "0.1.3" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } } diff --git a/package.json b/package.json index 3e478c0..d9a6c9d 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,17 @@ { - "name": "webdraft", - "version": "2.3.3", - "description": "Web application for drawing.", - "main": "js/start.js", + "name": "webdraft-v3", + "version": "0.1.0", + "private": true, + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "dev": "vite --host 127.0.0.1", + "test": "node tests/layer-transforms.test.mjs && node tests/editor-geometry.test.mjs && node tests/history-manager.test.mjs && node tests/flood-fill.test.mjs", + "check": "npm test && npm run build", + "build": "tsc && vite build", + "preview": "vite preview --host 127.0.0.1" }, - "repository": { - "type": "git", - "url": "git+https://github.com/rtcoder/WebDraft.git" - }, - "author": "Dawid Jeż (rtcoder)", - "license": "MIT", - "bugs": { - "url": "https://github.com/rtcoder/WebDraft/issues" - }, - "homepage": "https://github.com/rtcoder/WebDraft#readme", "devDependencies": { - "grunt": "^1.0.2" - }, - "dependencies": { - "grunt-contrib-concat": "^1.0.1", - "grunt-contrib-cssmin": "^2.2.1", - "grunt-contrib-less": "^1.4.1", - "grunt-contrib-watch": "^1.1.0" + "typescript": "^5.5.4", + "vite": "8.0.5" } } diff --git a/parts/buttons.part.html b/parts/buttons.part.html deleted file mode 100644 index 9d25fbd..0000000 --- a/parts/buttons.part.html +++ /dev/null @@ -1,42 +0,0 @@ -
    - - Tools -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    diff --git a/parts/camera.part.html b/parts/camera.part.html deleted file mode 100644 index f35cc39..0000000 --- a/parts/camera.part.html +++ /dev/null @@ -1,38 +0,0 @@ -
    -
    - - -
    -
    -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    -
    \ No newline at end of file diff --git a/parts/color-picker.part.html b/parts/color-picker.part.html deleted file mode 100644 index 027df72..0000000 --- a/parts/color-picker.part.html +++ /dev/null @@ -1,15 +0,0 @@ - -
    -
    -
    - Color -
    -
    -
    - Shadow color -
    -
    -
    - Fill color -
    -
    \ No newline at end of file diff --git a/parts/info.part.html b/parts/info.part.html deleted file mode 100644 index b4d3dfe..0000000 --- a/parts/info.part.html +++ /dev/null @@ -1,61 +0,0 @@ -
    -
    -
    General
    -
    -
    - CTRL + I -
    -
    Show/hide this info
    -
    -
    -
    - CTRL + S -
    -
    Save image
    -
    -
    -
    - CTRL + O -
    -
    Upload image
    -
    -
    -
    - CTRL + DEL -
    -
    Clear image
    -
    -
    -
    - CTRL + Scroll -
    -
    Pencil size
    -
    -
    -
    -
    Selection
    -
    -
    - CTRL + X -
    -
    Cut
    -
    -
    -
    - CTRL + C -
    -
    Copy
    -
    -
    -
    - CTRL + V -
    -
    Paste
    -
    -
    -
    - DEL -
    -
    Delete selection
    -
    -
    \ No newline at end of file diff --git a/parts/layers.part.html b/parts/layers.part.html deleted file mode 100644 index acd02d7..0000000 --- a/parts/layers.part.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - - Layers -
    -
    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/parts/resizer.part.html b/parts/resizer.part.html deleted file mode 100644 index 16bc7dc..0000000 --- a/parts/resizer.part.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -
    -
    Cancel (Esc)
    -
    OK (Enter)
    \ No newline at end of file diff --git a/parts/sliders.part.html b/parts/sliders.part.html deleted file mode 100644 index 45b1bd0..0000000 --- a/parts/sliders.part.html +++ /dev/null @@ -1,18 +0,0 @@ -
    -
    -
    size:10px
    - -
    -
    -
    shadow:1px
    - -
    -
    -
    sensitivity:10%
    - -
    -
    -
    fill opacity:100%
    - -
    -
    \ No newline at end of file diff --git a/parts/text-options.part.html b/parts/text-options.part.html deleted file mode 100644 index 780d52a..0000000 --- a/parts/text-options.part.html +++ /dev/null @@ -1,36 +0,0 @@ -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - - \ No newline at end of file diff --git a/pic/transparent.png b/pic/transparent.png deleted file mode 100644 index 01dc43d..0000000 Binary files a/pic/transparent.png and /dev/null differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..759b9c0 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core/canvas-drawing.ts b/src/core/canvas-drawing.ts new file mode 100644 index 0000000..b6aaece --- /dev/null +++ b/src/core/canvas-drawing.ts @@ -0,0 +1,146 @@ +import type {EditorState, Point, SizeWithPosition} from '../types'; +import {Tool} from '../types'; + +export {drawRichText} from './rich-text-renderer'; + +export function drawShape( + context: CanvasRenderingContext2D, + bounds: SizeWithPosition, + state: EditorState, +): void { + if (bounds.width < 1 || bounds.height < 1) { + return; + } + + applyStroke(context, state); + context.beginPath(); + + if (state.activeTool === Tool.Rectangle) { + context.rect(bounds.x, bounds.y, bounds.width, bounds.height); + } + + if (state.activeTool === Tool.Ellipse) { + context.ellipse( + bounds.x + bounds.width / 2, + bounds.y + bounds.height / 2, + bounds.width / 2, + bounds.height / 2, + 0, + 0, + Math.PI * 2, + ); + } + + if (state.fillEnabled) { + context.fill(); + } + + context.stroke(); +} + +export function drawPoint(context: CanvasRenderingContext2D, point: Point, state: EditorState): void { + applyBrush(context, state); + + context.beginPath(); + context.arc(point.x, point.y, state.size / 2, 0, Math.PI * 2); + context.fill(); +} + +export function drawLine(context: CanvasRenderingContext2D, start: Point, end: Point, state: EditorState): void { + applyBrush(context, state); + + context.beginPath(); + context.moveTo(start.x, start.y); + context.lineTo(end.x, end.y); + context.stroke(); +} + +export function drawWebLine( + context: CanvasRenderingContext2D, + point: Point, + points: Point[], + state: EditorState, +): Point[] { + const previousPoint = points[points.length - 1]; + + if (!previousPoint) { + return [...points, point]; + } + + applyBrush(context, state); + context.beginPath(); + context.moveTo(previousPoint.x, previousPoint.y); + context.lineTo(point.x, point.y); + context.stroke(); + + const sensitivitySquared = state.webSensitivity * state.webSensitivity; + + for (const pastPoint of points) { + const dx = pastPoint.x - point.x; + const dy = pastPoint.y - point.y; + const distanceSquared = dx * dx + dy * dy; + + if (distanceSquared > 0 && distanceSquared < sensitivitySquared) { + context.beginPath(); + context.moveTo(point.x + dx * 0.2, point.y + dy * 0.2); + context.lineTo(pastPoint.x - dx * 0.2, pastPoint.y - dy * 0.2); + context.stroke(); + } + } + + return [...points, point]; +} + +export function applyBrush(context: CanvasRenderingContext2D, state: EditorState): void { + context.lineCap = 'round'; + context.lineJoin = 'round'; + context.lineWidth = state.size; + + if (state.activeTool === Tool.Eraser) { + context.globalCompositeOperation = 'destination-out'; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.strokeStyle = '#000000'; + context.fillStyle = '#000000'; + return; + } + + context.globalCompositeOperation = 'source-over'; + context.strokeStyle = state.color; + context.fillStyle = state.color; + applyShadow(context, state); +} + +export function applyStroke(context: CanvasRenderingContext2D, state: EditorState): void { + context.globalCompositeOperation = 'source-over'; + context.lineCap = 'round'; + context.lineJoin = 'round'; + context.lineWidth = state.size; + context.strokeStyle = state.color; + context.fillStyle = hexToRgba(state.fillColor, state.fillOpacity / 100); + applyShadow(context, state); +} + +export function applyShadow(context: CanvasRenderingContext2D, state: EditorState): void { + if (!state.shadowEnabled) { + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + return; + } + + context.shadowColor = state.shadowColor; + context.shadowBlur = state.shadowBlur; + context.shadowOffsetX = state.shadowOffsetX; + context.shadowOffsetY = state.shadowOffsetY; +} + +function hexToRgba(hex: string, alpha: number): string { + const normalized = hex.replace('#', ''); + const red = Number.parseInt(normalized.slice(0, 2), 16); + const green = Number.parseInt(normalized.slice(2, 4), 16); + const blue = Number.parseInt(normalized.slice(4, 6), 16); + + return `rgb(${red} ${green} ${blue} / ${alpha})`; +} diff --git a/src/core/editor-geometry.ts b/src/core/editor-geometry.ts new file mode 100644 index 0000000..454957c --- /dev/null +++ b/src/core/editor-geometry.ts @@ -0,0 +1,66 @@ +import type {ClippedPasteBounds, NaturalSize, Point, Size, SizeWithPosition} from '../types'; + +export function getBounds(start: Point, end: Point): SizeWithPosition { + const x = Math.min(start.x, end.x); + const y = Math.min(start.y, end.y); + const width = Math.abs(end.x - start.x); + const height = Math.abs(end.y - start.y); + + return {x, y, width, height}; +} + +export function fitNaturalSizeToCanvas(source: NaturalSize, canvasSize: Size): SizeWithPosition { + const ratio = Math.min(canvasSize.width / source.naturalWidth, canvasSize.height / source.naturalHeight, 1); + const width = source.naturalWidth * ratio; + const height = source.naturalHeight * ratio; + + return { + x: (canvasSize.width - width) / 2, + y: (canvasSize.height - height) / 2, + width, + height, + }; +} + +export function normalizeCanvasBounds(bounds: SizeWithPosition, canvasSize: Size): SizeWithPosition | null { + const x = Math.max(0, Math.round(bounds.x)); + const y = Math.max(0, Math.round(bounds.y)); + const right = Math.min(canvasSize.width, Math.round(bounds.x + bounds.width)); + const bottom = Math.min(canvasSize.height, Math.round(bounds.y + bounds.height)); + const width = right - x; + const height = bottom - y; + + if (width < 1 || height < 1) { + return null; + } + + return {x, y, width, height}; +} + +export function normalizeTextBounds(bounds: SizeWithPosition, canvasSize: Size): SizeWithPosition { + const x = Math.max(0, Math.round(bounds.x)); + const y = Math.max(0, Math.round(bounds.y)); + const width = Math.max(160, Math.min(canvasSize.width - x, Math.round(bounds.width))); + const height = Math.max(48, Math.min(canvasSize.height - y, Math.round(bounds.height))); + + return {x, y, width, height}; +} + +export function getClippedPasteBounds(target: Point, imageSize: Size, canvasSize: Size): ClippedPasteBounds | null { + const x = Math.round(target.x); + const y = Math.round(target.y); + const sourceX = Math.max(0, -x); + const sourceY = Math.max(0, -y); + const targetX = Math.max(0, x); + const targetY = Math.max(0, y); + const right = Math.min(canvasSize.width, x + imageSize.width); + const bottom = Math.min(canvasSize.height, y + imageSize.height); + const width = right - targetX; + const height = bottom - targetY; + + if (width < 1 || height < 1) { + return null; + } + + return {targetX, targetY, sourceX, sourceY, width, height}; +} diff --git a/src/core/flood-fill.ts b/src/core/flood-fill.ts new file mode 100644 index 0000000..0b7b10e --- /dev/null +++ b/src/core/flood-fill.ts @@ -0,0 +1,103 @@ +import type {RgbaColor} from '../types'; + +export function floodFillImageData( + imageData: ImageData, + x: number, + y: number, + color: RgbaColor, + tolerance = 0, +): boolean { + const startX = Math.floor(x); + const startY = Math.floor(y); + + if (startX < 0 || startY < 0 || startX >= imageData.width || startY >= imageData.height) { + return false; + } + + const target = getPixel(imageData, startX, startY); + + if (colorsMatch(target, color, 0)) { + return false; + } + + const stack: Array<[number, number]> = [[startX, startY]]; + const visited = new Uint8Array(imageData.width * imageData.height); + + while (stack.length > 0) { + const [currentX, currentY] = stack.pop() as [number, number]; + + if (currentX < 0 || currentY < 0 || currentX >= imageData.width || currentY >= imageData.height) { + continue; + } + + const visitIndex = currentY * imageData.width + currentX; + + if (visited[visitIndex]) { + continue; + } + + visited[visitIndex] = 1; + + if (!colorsMatch(getPixel(imageData, currentX, currentY), target, tolerance)) { + continue; + } + + setPixel(imageData, currentX, currentY, color); + stack.push( + [currentX + 1, currentY], + [currentX - 1, currentY], + [currentX, currentY + 1], + [currentX, currentY - 1], + ); + } + + return true; +} + +export function hexToRgbaColor(hex: string, opacity: number): RgbaColor { + const normalized = hex.replace('#', ''); + + return { + red: Number.parseInt(normalized.slice(0, 2), 16), + green: Number.parseInt(normalized.slice(2, 4), 16), + blue: Number.parseInt(normalized.slice(4, 6), 16), + alpha: Math.round(Math.min(Math.max(opacity, 0), 1) * 255), + }; +} + +function getPixel(imageData: ImageData, x: number, y: number): RgbaColor { + const index = getPixelIndex(imageData.width, x, y); + const {data} = imageData; + + return { + red: data[index], + green: data[index + 1], + blue: data[index + 2], + alpha: data[index + 3], + }; +} + +function setPixel(imageData: ImageData, x: number, y: number, color: RgbaColor): void { + const index = getPixelIndex(imageData.width, x, y); + const {data} = imageData; + + data[index] = color.red; + data[index + 1] = color.green; + data[index + 2] = color.blue; + data[index + 3] = color.alpha; +} + +function colorsMatch(first: RgbaColor, second: RgbaColor, tolerance: number): boolean { + const threshold = Math.min(Math.max(tolerance, 0), 255); + + return ( + Math.abs(first.red - second.red) <= threshold && + Math.abs(first.green - second.green) <= threshold && + Math.abs(first.blue - second.blue) <= threshold && + Math.abs(first.alpha - second.alpha) <= threshold + ); +} + +function getPixelIndex(width: number, x: number, y: number): number { + return (y * width + x) * 4; +} diff --git a/src/core/history-manager.ts b/src/core/history-manager.ts new file mode 100644 index 0000000..4f6d00f --- /dev/null +++ b/src/core/history-manager.ts @@ -0,0 +1,58 @@ +import {HistoryEntry} from '../types'; + +export class HistoryManager { + private readonly undoStack: HistoryEntry[] = []; + private readonly redoStack: HistoryEntry[] = []; + + constructor(private readonly limit = 30) {} + + get canUndo(): boolean { + return this.undoStack.length > 0; + } + + get canRedo(): boolean { + return this.redoStack.length > 0; + } + + push(before: TSnapshot, after: TSnapshot): void { + this.undoStack.push({before, after}); + this.trimUndoStack(); + this.redoStack.length = 0; + } + + undo(): TSnapshot | null { + const entry = this.undoStack.pop(); + + if (!entry) { + return null; + } + + this.redoStack.push(entry); + return entry.before; + } + + redo(): TSnapshot | null { + const entry = this.redoStack.pop(); + + if (!entry) { + return null; + } + + this.undoStack.push(entry); + this.trimUndoStack(); + return entry.after; + } + + private trimUndoStack(): void { + if (this.limit < 1) { + this.undoStack.length = 0; + return; + } + + const excessEntries = this.undoStack.length - this.limit; + + if (excessEntries > 0) { + this.undoStack.splice(0, excessEntries); + } + } +} diff --git a/src/core/i18n.ts b/src/core/i18n.ts new file mode 100644 index 0000000..1ae71fe --- /dev/null +++ b/src/core/i18n.ts @@ -0,0 +1,22 @@ +import { en } from '../i18n/en'; +import { pl } from '../i18n/pl'; +import type {Lang, Translations} from '../types'; + +const LANG_KEY = 'webdraft-lang'; + +const translations: Record = { pl, en }; + +function detectLang(): Lang { + const stored = localStorage.getItem(LANG_KEY); + if (stored === 'pl' || stored === 'en') return stored; + return (navigator.languages?.[0] ?? navigator.language ?? '').toLowerCase().startsWith('pl') + ? 'pl' + : 'en'; +} + +export function setLang(next: Lang): void { + localStorage.setItem(LANG_KEY, next); +} + +export const lang: Lang = detectLang(); +export const t: Translations = translations[lang]; diff --git a/src/core/layer-manager.ts b/src/core/layer-manager.ts new file mode 100644 index 0000000..037d63f --- /dev/null +++ b/src/core/layer-manager.ts @@ -0,0 +1,307 @@ +import type {Layer, LayerDocumentSnapshot, LayerSummary, SizeWithPosition} from '../types'; +import {t} from './i18n.ts'; + + +export class LayerManager { + private readonly root: HTMLElement; + private readonly layers: Layer[] = []; + private activeLayerId = ''; + private layerCount = 0; + + constructor(root: HTMLElement) { + this.root = root; + } + + get activeLayer(): Layer { + const layer = this.layers.find((item) => item.id === this.activeLayerId); + + if (!layer) { + throw new Error('No active drawing layer.'); + } + + return layer; + } + + get count(): number { + return this.layers.length; + } + + get summaries(): LayerSummary[] { + return this.layers + .map((layer) => ({ + id: layer.id, + name: layer.name, + active: layer.id === this.activeLayerId, + preview: layer.canvas.toDataURL('image/png'), + visible: layer.visible, + isTextLayer: !!layer.textData, + })) + .reverse(); + } + + get visibleLayers(): Layer[] { + return this.layers.filter((layer) => layer.visible); + } + + get allLayers(): Layer[] { + return [...this.layers]; + } + + createLayer(width: number, height: number): Layer { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Canvas 2D context is unavailable.'); + } + + canvas.width = width; + canvas.height = height; + const id = crypto.randomUUID(); + + canvas.className = 'drawing-layer'; + canvas.dataset.layerId = id; + + const name = t.layers.layerName(`${this.layerCount + 1}`); + + const layer: Layer = { + id, + name, + canvas, + context, + visible: true, + x: 0, + y: 0, + }; + + this.layerCount += 1; + this.layers.push(layer); + this.activeLayerId = layer.id; + this.root.append(canvas); + this.syncLayerStyle(layer); + this.updateActiveLayerClass(); + + return layer; + } + + clearActiveLayer(): void { + const layer = this.activeLayer; + layer.context.clearRect(0, 0, layer.canvas.width, layer.canvas.height); + } + + resizeLayers(width: number, height: number): void { + for (const layer of this.layers) { + const source = document.createElement('canvas'); + const sourceContext = source.getContext('2d'); + + if (!sourceContext) { + throw new Error('Canvas 2D context is unavailable.'); + } + + source.width = layer.canvas.width; + source.height = layer.canvas.height; + sourceContext.drawImage(layer.canvas, 0, 0); + layer.canvas.width = width; + layer.canvas.height = height; + layer.context.clearRect(0, 0, width, height); + layer.context.drawImage(source, 0, 0); + layer.x = 0; + layer.y = 0; + this.syncLayerStyle(layer); + } + } + + captureDocument(): LayerDocumentSnapshot { + return { + activeLayerId: this.activeLayerId, + layerCount: this.layerCount, + layers: this.layers.map((layer) => ({ + layerId: layer.id, + name: layer.name, + visible: layer.visible, + x: layer.x, + y: layer.y, + textData: layer.textData, + imageData: layer.context.getImageData(0, 0, layer.canvas.width, layer.canvas.height), + })), + }; + } + + restoreDocument(snapshot: LayerDocumentSnapshot): void { + for (const layer of this.layers) { + layer.canvas.remove(); + } + + this.layers.length = 0; + this.layerCount = snapshot.layerCount; + + for (const layerSnapshot of snapshot.layers) { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Canvas 2D context is unavailable.'); + } + + canvas.width = layerSnapshot.imageData.width; + canvas.height = layerSnapshot.imageData.height; + canvas.className = 'drawing-layer'; + canvas.dataset.layerId = layerSnapshot.layerId; + canvas.hidden = !layerSnapshot.visible; + context.putImageData(layerSnapshot.imageData, 0, 0); + + const layer: Layer = { + id: layerSnapshot.layerId, + name: layerSnapshot.name, + canvas, + context, + visible: layerSnapshot.visible, + x: layerSnapshot.x ?? 0, + y: layerSnapshot.y ?? 0, + textData: layerSnapshot.textData, + }; + this.syncLayerStyle(layer); + this.layers.push(layer); + this.root.append(canvas); + } + + this.activeLayerId = snapshot.layers.some((layer) => layer.layerId === snapshot.activeLayerId) + ? snapshot.activeLayerId + : this.layers[0]?.id ?? ''; + this.updateActiveLayerClass(); + } + + drawImageOnNewLayer( + image: CanvasImageSource, + width: number, + height: number, + target: SizeWithPosition, + ): Layer { + const layer = this.createLayer(width, height); + layer.context.drawImage(image, target.x, target.y, target.width, target.height); + + return layer; + } + + selectLayer(id: string): void { + this.assertLayer(id); + this.activeLayerId = id; + this.updateActiveLayerClass(); + } + + renameLayer(id: string, name: string): boolean { + const layer = this.assertLayer(id); + const nextName = name.trim(); + + if (!nextName || nextName === layer.name) { + return false; + } + + layer.name = nextName; + return true; + } + + deleteActiveLayer(): Layer | null { + if (this.layers.length <= 1) { + return null; + } + + const index = this.getActiveIndex(); + const [removedLayer] = this.layers.splice(index, 1); + removedLayer.canvas.remove(); + + const nextLayer = this.layers[index] ?? this.layers[index - 1]; + this.activeLayerId = nextLayer.id; + this.updateActiveLayerClass(); + + return removedLayer; + } + + toggleVisibility(id: string): void { + const layer = this.assertLayer(id); + layer.visible = !layer.visible; + layer.canvas.hidden = !layer.visible; + + if (!layer.visible && layer.id === this.activeLayerId) { + const nextVisibleLayer = [...this.layers].reverse().find((item) => item.visible); + if (nextVisibleLayer) { + this.activeLayerId = nextVisibleLayer.id; + this.updateActiveLayerClass(); + } + } + } + + moveActiveLayerUp(): boolean { + const index = this.getActiveIndex(); + + if (index === this.layers.length - 1) { + return false; + } + + this.swapLayers(index, index + 1); + return true; + } + + moveActiveLayerDown(): boolean { + const index = this.getActiveIndex(); + + if (index === 0) { + return false; + } + + this.swapLayers(index, index - 1); + return true; + } + + private getActiveIndex(): number { + const index = this.layers.findIndex((layer) => layer.id === this.activeLayerId); + + if (index < 0) { + throw new Error('No active drawing layer.'); + } + + return index; + } + + private assertLayer(id: string): Layer { + const layer = this.layers.find((item) => item.id === id); + + if (!layer) { + throw new Error(`Unknown layer: ${id}`); + } + + return layer; + } + + syncActiveLayerStyle(): void { + this.syncLayerStyle(this.activeLayer); + } + + private syncLayerStyle(layer: Layer): void { + layer.canvas.style.left = `${layer.x}px`; + layer.canvas.style.top = `${layer.y}px`; + layer.canvas.style.width = `${layer.canvas.width}px`; + layer.canvas.style.height = `${layer.canvas.height}px`; + } + + private updateActiveLayerClass(): void { + for (const layer of this.layers) { + layer.canvas.classList.toggle('drawing-layer--active', layer.id === this.activeLayerId); + } + } + + private swapLayers(firstIndex: number, secondIndex: number): void { + const firstLayer = this.layers[firstIndex]; + const secondLayer = this.layers[secondIndex]; + + this.layers[firstIndex] = secondLayer; + this.layers[secondIndex] = firstLayer; + this.renderLayerOrder(); + } + + private renderLayerOrder(): void { + for (const layer of this.layers) { + this.root.append(layer.canvas); + } + } +} diff --git a/src/core/layer-transforms.ts b/src/core/layer-transforms.ts new file mode 100644 index 0000000..32ca1c1 --- /dev/null +++ b/src/core/layer-transforms.ts @@ -0,0 +1,92 @@ +import type {MirrorAxis, PixelBuffer, RotationDirection} from '../types'; + +export function invertPixelBuffer(buffer: PixelBuffer): PixelBuffer { + const output = clonePixelBuffer(buffer); + + for (let index = 0; index < output.data.length; index += 4) { + output.data[index] = 255 - output.data[index]; + output.data[index + 1] = 255 - output.data[index + 1]; + output.data[index + 2] = 255 - output.data[index + 2]; + } + + return output; +} + +export function rotatePixelBuffer(buffer: PixelBuffer, direction: RotationDirection): PixelBuffer { + const output: PixelBuffer = { + width: buffer.height, + height: buffer.width, + data: new Uint8ClampedArray(buffer.width * buffer.height * 4), + }; + + for (let y = 0; y < buffer.height; y += 1) { + for (let x = 0; x < buffer.width; x += 1) { + const target = + direction === 'right' + ? {x: buffer.height - 1 - y, y: x} + : {x: y, y: buffer.width - 1 - x}; + + copyPixel(buffer, output, x, y, target.x, target.y); + } + } + + return output; +} + +export function mirrorPixelBuffer(buffer: PixelBuffer, axis: MirrorAxis): PixelBuffer { + const output = createEmptyPixelBuffer(buffer); + + for (let y = 0; y < buffer.height; y += 1) { + for (let x = 0; x < buffer.width; x += 1) { + const target = { + x: axis === 'horizontal' ? buffer.width - 1 - x : x, + y: axis === 'vertical' ? buffer.height - 1 - y : y, + }; + + copyPixel(buffer, output, x, y, target.x, target.y); + } + } + + return output; +} + +function clonePixelBuffer(buffer: PixelBuffer): PixelBuffer { + return { + width: buffer.width, + height: buffer.height, + data: new Uint8ClampedArray(buffer.data), + }; +} + +function createEmptyPixelBuffer(buffer: PixelBuffer): PixelBuffer { + return { + width: buffer.width, + height: buffer.height, + data: new Uint8ClampedArray(buffer.data.length), + }; +} + +function copyPixel( + source: PixelBuffer, + target: PixelBuffer, + sourceX: number, + sourceY: number, + targetX: number, + targetY: number, +): void { + if (targetX < 0 || targetY < 0 || targetX >= target.width || targetY >= target.height) { + return; + } + + const sourceIndex = getPixelIndex(source.width, sourceX, sourceY); + const targetIndex = getPixelIndex(target.width, targetX, targetY); + + target.data[targetIndex] = source.data[sourceIndex]; + target.data[targetIndex + 1] = source.data[sourceIndex + 1]; + target.data[targetIndex + 2] = source.data[sourceIndex + 2]; + target.data[targetIndex + 3] = source.data[sourceIndex + 3]; +} + +function getPixelIndex(width: number, x: number, y: number): number { + return (y * width + x) * 4; +} diff --git a/src/core/platform.ts b/src/core/platform.ts new file mode 100644 index 0000000..06850e2 --- /dev/null +++ b/src/core/platform.ts @@ -0,0 +1,4 @@ +export const isMac = + navigator.platform.startsWith('Mac') || navigator.userAgent.includes('Macintosh'); + +export const modKey = isMac ? '⌘' : 'Ctrl'; diff --git a/src/core/project-file.ts b/src/core/project-file.ts new file mode 100644 index 0000000..fa1763b --- /dev/null +++ b/src/core/project-file.ts @@ -0,0 +1,230 @@ +import type {ParsedWdraftFile, TextLayerData, WdraftFileMeta, WdraftLayerMeta} from '../types'; + +export type {WdraftLayerMeta, WdraftFileMeta, ParsedWdraftFile}; + +export const WDRAFT_VERSION = 3; +export const WDRAFT_MAGIC = [0x57, 0x44, 0x46, 0x54] as const; // "WDFT" +export const WDRAFT_MIME_TYPE = 'application/x-webdraft'; +export const WDRAFT_EXTENSION = '.wdraft'; + +const enc = new TextEncoder(); +const dec = new TextDecoder(); + +export function serializeWdraftBinary(meta: WdraftFileMeta, pngBuffers: Uint8Array[]): Blob { + const activeLayerIdBytes = enc.encode(meta.activeLayerId); + const layerStrings = meta.layers.map((l) => ({ + id: enc.encode(l.id), + name: enc.encode(l.name), + textDataJson: l.textData ? enc.encode(JSON.stringify(l.textData)) : null, + })); + + let size = + 4 + // magic + 1 + // version + 4 + // canvasWidth + 4 + // canvasHeight + 4 + // layerCount + 2 + activeLayerIdBytes.length + // activeLayerId + 2; // layer entries count + + for (let i = 0; i < meta.layers.length; i++) { + const td = layerStrings[i].textDataJson; + size += + 2 + layerStrings[i].id.length + + 2 + layerStrings[i].name.length + + 1 + // visible + 4 + // width + 4 + // height + 4 + pngBuffers[i].length + // png data length + data + 2 + (td ? td.length : 0); // textData length + data (0 = none) + } + + const buffer = new ArrayBuffer(size); + const view = new DataView(buffer); + const bytes = new Uint8Array(buffer); + + let offset = 0; + + for (const byte of WDRAFT_MAGIC) bytes[offset++] = byte; + bytes[offset++] = WDRAFT_VERSION; + + view.setUint32(offset, meta.canvasWidth, true); + offset += 4; + view.setUint32(offset, meta.canvasHeight, true); + offset += 4; + view.setUint32(offset, meta.layerCount, true); + offset += 4; + + view.setUint16(offset, activeLayerIdBytes.length, true); + offset += 2; + bytes.set(activeLayerIdBytes, offset); + offset += activeLayerIdBytes.length; + + view.setUint16(offset, meta.layers.length, true); + offset += 2; + + for (let i = 0; i < meta.layers.length; i++) { + const layer = meta.layers[i]; + const idBytes = layerStrings[i].id; + const nameBytes = layerStrings[i].name; + const td = layerStrings[i].textDataJson; + const png = pngBuffers[i]; + + view.setUint16(offset, idBytes.length, true); + offset += 2; + bytes.set(idBytes, offset); + offset += idBytes.length; + + view.setUint16(offset, nameBytes.length, true); + offset += 2; + bytes.set(nameBytes, offset); + offset += nameBytes.length; + + bytes[offset++] = layer.visible ? 1 : 0; + + view.setUint32(offset, layer.width, true); + offset += 4; + view.setUint32(offset, layer.height, true); + offset += 4; + + view.setUint32(offset, png.length, true); + offset += 4; + bytes.set(png, offset); + offset += png.length; + + // v3: textData (2-byte length, 0 = no text data) + const tdLen = td ? td.length : 0; + view.setUint16(offset, tdLen, true); + offset += 2; + if (td && tdLen > 0) { + bytes.set(td, offset); + offset += tdLen; + } + } + + return new Blob([buffer], {type: WDRAFT_MIME_TYPE}); +} + +function migrateTextData(raw: unknown): TextLayerData | undefined { + if (!raw || typeof raw !== 'object') return undefined; + const obj = raw as Record; + + if (typeof obj.html === 'string' && obj.bounds) { + return { + html: obj.html, + bounds: obj.bounds as TextLayerData['bounds'], + defaultFontSize: typeof obj.defaultFontSize === 'number' ? obj.defaultFontSize : 16, + defaultFontFamily: typeof obj.defaultFontFamily === 'string' ? obj.defaultFontFamily : 'sans-serif', + defaultColor: typeof obj.defaultColor === 'string' ? obj.defaultColor : '#000000', + defaultAlign: (typeof obj.defaultAlign === 'string' ? obj.defaultAlign : 'left') as CanvasTextAlign, + }; + } + + // Old format: { text: string, bounds } + if (typeof obj.text === 'string' && obj.bounds) { + const html = obj.text + .split('\n') + .map((l) => l.replace(/&/g, '&').replace(//g, '>')) + .join('
    '); + return { + html, + bounds: obj.bounds as TextLayerData['bounds'], + defaultFontSize: 16, + defaultFontFamily: 'sans-serif', + defaultColor: '#000000', + defaultAlign: 'left', + }; + } + + return undefined; +} + +export function parseWdraftBinary(buffer: ArrayBuffer): ParsedWdraftFile { + const view = new DataView(buffer); + const bytes = new Uint8Array(buffer); + + if (buffer.byteLength < 5) { + throw new Error('Invalid .wdraft file: too short.'); + } + + for (let i = 0; i < WDRAFT_MAGIC.length; i++) { + if (bytes[i] !== WDRAFT_MAGIC[i]) { + throw new Error('Invalid .wdraft file: wrong magic bytes.'); + } + } + + let offset = 4; + + const version = bytes[offset++]; + if (version !== 2 && version !== WDRAFT_VERSION) { + throw new Error(`Unsupported .wdraft version: ${version}. Expected ${WDRAFT_VERSION}.`); + } + + const canvasWidth = view.getUint32(offset, true); + offset += 4; + const canvasHeight = view.getUint32(offset, true); + offset += 4; + const layerCount = view.getUint32(offset, true); + offset += 4; + + const activeLayerIdLen = view.getUint16(offset, true); + offset += 2; + const activeLayerId = dec.decode(bytes.subarray(offset, offset + activeLayerIdLen)); + offset += activeLayerIdLen; + + const layerEntries = view.getUint16(offset, true); + offset += 2; + + if (layerEntries === 0) { + throw new Error('Invalid .wdraft file: no layers.'); + } + + const layers: WdraftLayerMeta[] = []; + const pngBuffers: Uint8Array[] = []; + + for (let i = 0; i < layerEntries; i++) { + const idLen = view.getUint16(offset, true); + offset += 2; + const id = dec.decode(bytes.subarray(offset, offset + idLen)); + offset += idLen; + + const nameLen = view.getUint16(offset, true); + offset += 2; + const name = dec.decode(bytes.subarray(offset, offset + nameLen)); + offset += nameLen; + + const visible = bytes[offset++] !== 0; + + const width = view.getUint32(offset, true); + offset += 4; + const height = view.getUint32(offset, true); + offset += 4; + + const pngLen = view.getUint32(offset, true); + offset += 4; + const png = bytes.slice(offset, offset + pngLen); + offset += pngLen; + + let textData: TextLayerData | undefined; + if (version >= 3) { + const tdLen = view.getUint16(offset, true); + offset += 2; + if (tdLen > 0) { + try { + textData = migrateTextData(JSON.parse(dec.decode(bytes.subarray(offset, offset + tdLen)))); + } catch { + // malformed textData — ignore + } + offset += tdLen; + } + } + + layers.push({id, name, visible, width, height, textData}); + pngBuffers.push(png); + } + + return { + meta: {version, canvasWidth, canvasHeight, activeLayerId, layerCount, layers}, + pngBuffers, + }; +} diff --git a/src/core/rich-text-renderer.ts b/src/core/rich-text-renderer.ts new file mode 100644 index 0000000..86a6101 --- /dev/null +++ b/src/core/rich-text-renderer.ts @@ -0,0 +1,204 @@ +import type {SizeWithPosition, TextLayerData, StyledRun} from '../types'; + +export function drawRichText( + context: CanvasRenderingContext2D, + textData: TextLayerData, + drawBounds: SizeWithPosition, +): void { + const htmlLines = parseHtmlToLines(textData.html, textData); + + context.save(); + context.globalCompositeOperation = 'source-over'; + context.textBaseline = 'top'; + + let y = drawBounds.y; + + for (const htmlLine of htmlLines) { + const displayLines = wrapRuns(context, htmlLine, drawBounds.width); + for (const displayLine of displayLines) { + if (y >= drawBounds.y + drawBounds.height) break; + const lineH = maxFontSize(displayLine, textData.defaultFontSize) * 1.4; + if (displayLine.length > 0) drawRichLine(context, displayLine, drawBounds, y, textData.defaultAlign); + y += lineH; + } + } + + context.restore(); +} + +function wrapRuns(ctx: CanvasRenderingContext2D, runs: StyledRun[], maxWidth: number): StyledRun[][] { + if (runs.length === 0) return [[]]; + + const displayLines: StyledRun[][] = [[]]; + let lineWidth = 0; + + for (const run of runs) { + const words = run.text.split(/(\s+)/); + for (const word of words) { + if (word === '') continue; + const isSpace = /^\s+$/.test(word); + ctx.font = runToFont(run); + const w = ctx.measureText(word).width; + + if (!isSpace && lineWidth > 0 && lineWidth + w > maxWidth) { + displayLines.push([]); + lineWidth = 0; + } + if (isSpace && lineWidth === 0) continue; + + const currentLine = displayLines[displayLines.length - 1]; + const last = currentLine[currentLine.length - 1]; + if (last && sameStyle(last, run)) { + last.text += word; + } else { + currentLine.push({...run, text: word}); + } + lineWidth += w; + } + } + + return displayLines; +} + +function sameStyle(a: StyledRun, b: StyledRun): boolean { + return ( + a.bold === b.bold && + a.italic === b.italic && + a.underline === b.underline && + a.color === b.color && + a.fontSize === b.fontSize && + a.fontFamily === b.fontFamily + ); +} + +function maxFontSize(runs: StyledRun[], fallback: number): number { + return runs.length === 0 ? fallback : Math.max(fallback, ...runs.map((r) => r.fontSize)); +} + +function drawRichLine( + ctx: CanvasRenderingContext2D, + runs: StyledRun[], + bounds: SizeWithPosition, + y: number, + align: CanvasTextAlign, +): void { + let totalWidth = 0; + const widths: number[] = []; + for (const run of runs) { + ctx.font = runToFont(run); + const w = ctx.measureText(run.text).width; + widths.push(w); + totalWidth += w; + } + + let x: number; + if (align === 'center') { + x = bounds.x + (bounds.width - totalWidth) / 2; + } else if (align === 'right' || align === 'end') { + x = bounds.x + bounds.width - totalWidth; + } else { + x = bounds.x; + } + + for (let i = 0; i < runs.length; i++) { + const run = runs[i]; + ctx.font = runToFont(run); + ctx.fillStyle = run.color; + ctx.fillText(run.text, x, y); + if (run.underline) { + ctx.save(); + ctx.strokeStyle = run.color; + ctx.lineWidth = Math.max(1, run.fontSize / 16); + ctx.beginPath(); + ctx.moveTo(x, y + run.fontSize + 2); + ctx.lineTo(x + widths[i], y + run.fontSize + 2); + ctx.stroke(); + ctx.restore(); + } + x += widths[i]; + } +} + +function runToFont(run: StyledRun): string { + return `${run.italic ? 'italic' : 'normal'} ${run.bold ? '700' : '400'} ${run.fontSize}px ${run.fontFamily}`; +} + +function parseHtmlToLines( + html: string, + defaults: {defaultFontSize: number; defaultFontFamily: string; defaultColor: string}, +): StyledRun[][] { + const container = document.createElement('div'); + container.innerHTML = html; + + const allLines: StyledRun[][] = [[]]; + + const currentLine = (): StyledRun[] => allLines[allLines.length - 1]; + const flush = (): void => { + allLines.push([]); + }; + + type RunStyle = { + bold: boolean; + italic: boolean; + underline: boolean; + color: string; + fontSize: number; + fontFamily: string; + }; + + function walk(node: Node, style: RunStyle): void { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent ?? ''; + if (text) currentLine().push({text, ...style}); + return; + } + if (node.nodeType !== Node.ELEMENT_NODE) return; + + const el = node as HTMLElement; + const tag = el.tagName.toLowerCase(); + + if (tag === 'br') { + flush(); + return; + } + + const isBlock = tag === 'div' || tag === 'p'; + if (isBlock && currentLine().length > 0) flush(); + + const next: RunStyle = {...style}; + if (tag === 'b' || tag === 'strong') next.bold = true; + if (tag === 'i' || tag === 'em') next.italic = true; + if (tag === 'u') next.underline = true; + + const s = el.style; + if (s) { + if (['bold', '600', '700', '800', '900'].includes(s.fontWeight)) next.bold = true; + if (s.fontStyle === 'italic') next.italic = true; + const td = s.textDecorationLine || s.textDecoration; + if (td?.includes('underline')) next.underline = true; + if (s.color) next.color = s.color; + const px = parseFloat(s.fontSize); + if (px > 0) next.fontSize = px; + if (s.fontFamily) next.fontFamily = s.fontFamily.replace(/['"]/g, '').trim(); + } + + for (const child of Array.from(el.childNodes)) walk(child, next); + + if (isBlock) flush(); + } + + const base: RunStyle = { + bold: false, + italic: false, + underline: false, + color: defaults.defaultColor, + fontSize: defaults.defaultFontSize, + fontFamily: defaults.defaultFontFamily, + }; + + for (const child of Array.from(container.childNodes)) walk(child, base); + + while (allLines.length > 1 && allLines[allLines.length - 1].length === 0) allLines.pop(); + + return allLines; +} diff --git a/src/core/selection-controller.ts b/src/core/selection-controller.ts new file mode 100644 index 0000000..0821428 --- /dev/null +++ b/src/core/selection-controller.ts @@ -0,0 +1,127 @@ +import type {EditorServices, Point, SizeWithPosition, ClipboardSnapshot} from '../types'; +import {getBounds, getClippedPasteBounds, normalizeCanvasBounds} from './editor-geometry'; + +export class SelectionController { + private startPoint: Point | null = null; + private _bounds: SizeWithPosition | null = null; + private clipboard: ClipboardSnapshot | null = null; + + constructor(private readonly svc: EditorServices) { + } + + get hasBounds(): boolean { + return this._bounds !== null; + } + + get bounds(): SizeWithPosition | null { + return this._bounds; + } + + get canPaste(): boolean { + return this.clipboard !== null; + } + + onPointerDown(point: Point): void { + this.startPoint = point; + this._bounds = null; + this.svc.clearPreview(); + } + + onPointerMove(point: Point): void { + if (!this.startPoint) return; + this._bounds = getBounds(this.startPoint, point); + this.renderFrame(); + } + + onPointerUp(point: Point): void { + if (!this.startPoint) return; + this._bounds = getBounds(this.startPoint, point); + if (!normalizeCanvasBounds(this._bounds, this.canvasSize)) { + this.clear(); + return; + } + this.renderFrame(); + this.svc.dispatchChange(); + } + + copy(): void { + if (!this._bounds) return; + const bounds = normalizeCanvasBounds(this._bounds, this.canvasSize); + if (!bounds) return; + const {context} = this.svc.layerManager.activeLayer; + this.clipboard = { + bounds, + imageData: context.getImageData(bounds.x, bounds.y, bounds.width, bounds.height), + }; + this.svc.dispatchChange(); + } + + cut(): void { + if (!this._bounds) return; + const bounds = normalizeCanvasBounds(this._bounds, this.canvasSize); + if (!bounds) return; + const before = this.svc.captureSnapshot(); + const {context} = this.svc.layerManager.activeLayer; + this.clipboard = { + bounds, + imageData: context.getImageData(bounds.x, bounds.y, bounds.width, bounds.height), + }; + context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height); + this.svc.pushHistory(before, this.svc.captureSnapshot()); + this.svc.dispatchChange(); + } + + paste(): void { + if (!this.clipboard) return; + const before = this.svc.captureSnapshot(); + const {context} = this.svc.layerManager.activeLayer; + const target = this._bounds ?? this.clipboard.bounds; + const pasteBounds = getClippedPasteBounds(target, this.clipboard.bounds, this.canvasSize); + if (!pasteBounds) return; + context.putImageData( + this.clipboard.imageData, + pasteBounds.targetX - pasteBounds.sourceX, + pasteBounds.targetY - pasteBounds.sourceY, + pasteBounds.sourceX, + pasteBounds.sourceY, + pasteBounds.width, + pasteBounds.height, + ); + this._bounds = { + x: pasteBounds.targetX, + y: pasteBounds.targetY, + width: this.clipboard.bounds.width, + height: this.clipboard.bounds.height, + }; + this.renderFrame(); + this.svc.pushHistory(before, this.svc.captureSnapshot()); + this.svc.dispatchChange(); + } + + clear(): void { + this.startPoint = null; + this._bounds = null; + this.svc.clearPreview(); + } + + private get canvasSize(): { width: number; height: number } { + return {width: this.svc.state.canvasWidth, height: this.svc.state.canvasHeight}; + } + + private renderFrame(): void { + if (!this._bounds) return; + const bounds = normalizeCanvasBounds(this._bounds, this.canvasSize); + if (!bounds) { + this.svc.clearPreview(); + return; + } + this.svc.clearPreview(); + const ctx = this.svc.previewContext; + ctx.save(); + ctx.setLineDash([6, 4]); + ctx.lineWidth = 1; + ctx.strokeStyle = '#1b6cff'; + ctx.strokeRect(bounds.x + 0.5, bounds.y + 0.5, bounds.width, bounds.height); + ctx.restore(); + } +} diff --git a/src/core/text-edit-controller.ts b/src/core/text-edit-controller.ts new file mode 100644 index 0000000..9a1d682 --- /dev/null +++ b/src/core/text-edit-controller.ts @@ -0,0 +1,279 @@ +import type {EditorServices, Layer, Point, SizeWithPosition, TextToolbarDefaults} from '../types'; +import {createTextToolbar} from '../ui/text-toolbar'; +import {getBounds, normalizeTextBounds} from './editor-geometry'; +import {drawRichText} from './rich-text-renderer'; + +export class TextEditController { + private startPoint: Point | null = null; + private _bounds: SizeWithPosition | null = null; + private inputEl: HTMLElement | null = null; + private toolbarEl: HTMLElement | null = null; + private toolbarCleanup: (() => void) | null = null; + private inputDefaults: TextToolbarDefaults | null = null; + private skipNextPointerDown = false; + private pendingLayerEdit: Layer | null = null; + private editingLayer: Layer | null = null; + private editSnapshot: ImageData | null = null; + + constructor(private readonly svc: EditorServices) { + } + + get isActive(): boolean { + return this.inputEl !== null; + } + + /** + * Returns true if the pointer down was fully handled (caller should not start a draw stroke). + */ + onPointerDown(point: Point): boolean { + if (this.inputEl) { + this.commit(); + return true; + } + + const textLayerAtPoint = this.findTextLayerAt(point); + + if (this.skipNextPointerDown) { + this.skipNextPointerDown = false; + if (!textLayerAtPoint) return true; + } + + this.commit(); + + if (textLayerAtPoint) { + this.pendingLayerEdit = textLayerAtPoint; + return true; + } + + this.startPoint = point; + this._bounds = null; + this.svc.clearPreview(); + return false; + } + + onPointerMove(point: Point): void { + if (!this.startPoint) return; + this._bounds = getBounds(this.startPoint, point); + this.renderFrame(); + } + + onPointerUp(point: Point): void { + if (this.pendingLayerEdit) { + this.enterEditMode(this.pendingLayerEdit); + this.pendingLayerEdit = null; + return; + } + if (!this.startPoint) return; + const bounds = normalizeTextBounds( + getBounds(this.startPoint, point), + this.canvasSize, + ); + this._bounds = bounds; + this.svc.clearPreview(); + this.createInput(bounds, { + fontSize: 16, + fontFamily: 'sans-serif', + color: this.svc.state.color, + align: 'left', + }); + this.startPoint = null; + } + + commit(options: { skipNextPointerDown?: boolean } = {}): void { + if (!this.inputEl || !this._bounds) return; + + const html = this.inputEl.innerHTML; + const bounds = this._bounds; + const editingLayer = this.editingLayer; + const snapshot = this.editSnapshot; + const defaults = this.inputDefaults ?? { + fontSize: 16, + fontFamily: 'sans-serif', + color: '#000000', + align: 'left' as CanvasTextAlign, + }; + + this.removeInput(); + this.svc.clearPreview(); + this.skipNextPointerDown = options.skipNextPointerDown ?? false; + this._bounds = null; + this.editingLayer = null; + this.editSnapshot = null; + + const plainText = htmlToPlainText(html); + if (!plainText.trim()) { + if (editingLayer && snapshot) editingLayer.context.putImageData(snapshot, 0, 0); + return; + } + + const textData = { + html, + bounds, + defaultFontSize: defaults.fontSize, + defaultFontFamily: defaults.fontFamily, + defaultColor: defaults.color, + defaultAlign: defaults.align, + }; + + const before = this.svc.captureSnapshot(); + + if (editingLayer) { + const {context, x: lx, y: ly} = editingLayer; + drawRichText(context, textData, { + x: bounds.x - lx, + y: bounds.y - ly, + width: bounds.width, + height: bounds.height, + }); + editingLayer.textData = textData; + editingLayer.name = `T: ${plainText.slice(0, 18)}`; + } else { + const newLayer = this.svc.layerManager.createLayer( + this.svc.state.canvasWidth, + this.svc.state.canvasHeight, + ); + drawRichText(newLayer.context, textData, bounds); + newLayer.textData = textData; + newLayer.name = `T: ${plainText.slice(0, 18)}`; + } + + this.svc.pushHistory(before, this.svc.captureSnapshot()); + this.svc.dispatchChange(); + } + + private enterEditMode(layer: Layer): void { + if (!layer.textData) return; + if (layer.id !== this.svc.layerManager.activeLayer.id) { + this.svc.layerManager.selectLayer(layer.id); + this.svc.dispatchChange(); + } + this.editSnapshot = layer.context.getImageData(0, 0, layer.canvas.width, layer.canvas.height); + layer.context.clearRect(0, 0, layer.canvas.width, layer.canvas.height); + this._bounds = layer.textData.bounds; + this.editingLayer = layer; + this.createInput(layer.textData.bounds, { + fontSize: layer.textData.defaultFontSize, + fontFamily: layer.textData.defaultFontFamily, + color: layer.textData.defaultColor, + align: layer.textData.defaultAlign, + }); + if (this.inputEl) { + this.inputEl.innerHTML = layer.textData.html; + } + } + + private findTextLayerAt(point: Point): Layer | null { + const layers = this.svc.layerManager.allLayers; + for (let i = layers.length - 1; i >= 0; i--) { + const layer = layers[i]; + if (!layer.visible) continue; + if (layer.textData) { + const b = layer.textData.bounds; + if ( + point.x >= b.x && + point.x < b.x + b.width && + point.y >= b.y && + point.y < b.y + b.height + ) { + return layer; + } + } + const lx = Math.floor(point.x - layer.x); + const ly = Math.floor(point.y - layer.y); + if (lx >= 0 && ly >= 0 && lx < layer.canvas.width && ly < layer.canvas.height) { + if (layer.context.getImageData(lx, ly, 1, 1).data[3] > 10) { + return null; + } + } + } + return null; + } + + private createInput(bounds: SizeWithPosition, defaults: TextToolbarDefaults): void { + this.removeInput(); + + const input = document.createElement('div'); + input.className = 'text-input-layer'; + input.contentEditable = 'true'; + input.style.left = `${bounds.x}px`; + input.style.top = `${bounds.y}px`; + input.style.width = `${bounds.width}px`; + input.style.height = `${bounds.height}px`; + input.style.color = defaults.color; + input.style.fontSize = `${defaults.fontSize}px`; + input.style.fontFamily = defaults.fontFamily; + input.style.textAlign = defaults.align; + + input.addEventListener('keydown', (event) => { + if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { + event.preventDefault(); + this.commit(); + } + if (event.key === 'Escape') { + event.preventDefault(); + const cancelLayer = this.editingLayer; + const cancelSnapshot = this.editSnapshot; + this.editingLayer = null; + this.editSnapshot = null; + this._bounds = null; + if (cancelLayer && cancelSnapshot) { + cancelLayer.context.putImageData(cancelSnapshot, 0, 0); + } + this.removeInput(); + this.svc.clearPreview(); + } + }); + + input.addEventListener('blur', (event) => { + const related = (event as FocusEvent).relatedTarget as Node | null; + if (this.toolbarEl?.contains(related)) return; + this.commit({skipNextPointerDown: true}); + }); + + this.inputEl = input; + this.inputDefaults = defaults; + + const {element: toolbar, cleanup} = createTextToolbar(input, bounds, defaults, (align) => { + if (this.inputDefaults) this.inputDefaults.align = align; + }); + this.toolbarEl = toolbar; + this.toolbarCleanup = cleanup; + + this.svc.root.append(input); + this.svc.root.append(toolbar); + input.focus(); + } + + private removeInput(): void { + this.toolbarCleanup?.(); + this.toolbarCleanup = null; + this.toolbarEl?.remove(); + this.toolbarEl = null; + this.inputEl?.remove(); + this.inputEl = null; + this.inputDefaults = null; + } + + private renderFrame(): void { + if (!this._bounds) return; + const bounds = normalizeTextBounds(this._bounds, this.canvasSize); + this.svc.clearPreview(); + const ctx = this.svc.previewContext; + ctx.save(); + ctx.setLineDash([4, 4]); + ctx.lineWidth = 1; + ctx.strokeStyle = '#6b9dff'; + ctx.strokeRect(bounds.x + 0.5, bounds.y + 0.5, bounds.width, bounds.height); + ctx.restore(); + } + + private get canvasSize(): { width: number; height: number } { + return {width: this.svc.state.canvasWidth, height: this.svc.state.canvasHeight}; + } +} + +function htmlToPlainText(html: string): string { + const tmp = document.createElement('div'); + tmp.innerHTML = html; + return tmp.textContent ?? ''; +} diff --git a/src/core/webdraft-editor.ts b/src/core/webdraft-editor.ts new file mode 100644 index 0000000..83046cc --- /dev/null +++ b/src/core/webdraft-editor.ts @@ -0,0 +1,864 @@ +import type { + EditorOptions, + EditorServices, + EditorState, + HistorySnapshot, + LayerSummary, + ParsedWdraftFile, + Point, + WdraftFileMeta, +} from '../types'; +import {Tool} from '../types'; +import {drawLine, drawPoint, drawShape, drawWebLine} from './canvas-drawing'; +import {fitNaturalSizeToCanvas, getBounds} from './editor-geometry'; +import {floodFillImageData, hexToRgbaColor} from './flood-fill'; +import {HistoryManager} from './history-manager'; +import {LayerManager} from './layer-manager'; +import {invertPixelBuffer, mirrorPixelBuffer, rotatePixelBuffer} from './layer-transforms'; +import {serializeWdraftBinary, WDRAFT_VERSION} from './project-file'; +import {SelectionController} from './selection-controller'; +import {TextEditController} from './text-edit-controller'; + +const ZOOM_STEPS = [0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4, 6, 8]; + +export class WebDraftEditor extends EventTarget { + private readonly root: HTMLElement; + private readonly layerManager: LayerManager; + private readonly previewCanvas: HTMLCanvasElement; + private readonly previewContext: CanvasRenderingContext2D; + private readonly eventLayer: HTMLDivElement; + private readonly selection: SelectionController; + private readonly text: TextEditController; + private isDrawing = false; + private lastPoint: Point | null = null; + private shapeStartPoint: Point | null = null; + private webPoints: Point[] = []; + private pendingHistorySnapshot: HistorySnapshot | null = null; + private readonly history = new HistoryManager(30); + + readonly state: EditorState; + + constructor(root: HTMLElement, options: EditorOptions) { + super(); + + this.root = root; + this.layerManager = new LayerManager(root); + this.previewCanvas = document.createElement('canvas'); + this.previewCanvas.className = 'shape-preview-layer'; + this.eventLayer = document.createElement('div'); + this.eventLayer.className = 'event-layer'; + + const previewContext = this.previewCanvas.getContext('2d'); + + if (!previewContext) { + throw new Error('Canvas 2D context is unavailable.'); + } + + this.previewContext = previewContext; + + this.state = { + activeTool: Tool.Pencil, + zoom: 1, + color: options.color, + fillColor: '#ffffff', + fillEnabled: false, + fillOpacity: 100, + fillTolerance: 0, + size: options.size, + canvasWidth: options.width, + canvasHeight: options.height, + webSensitivity: 100, + shadowEnabled: false, + shadowColor: '#000000', + shadowBlur: 8, + shadowOffsetX: 8, + shadowOffsetY: 8, + }; + + const services: EditorServices = { + state: this.state, + layerManager: this.layerManager, + previewContext: this.previewContext, + previewCanvas: this.previewCanvas, + root: this.root, + captureSnapshot: () => this.captureHistorySnapshot(), + pushHistory: (b, a) => this.pushHistory(b, a), + clearPreview: () => this.clearPreview(), + dispatchChange: () => this.dispatchChange(), + }; + + this.selection = new SelectionController(services); + this.text = new TextEditController(services); + } + + mount(): void { + this.applyCanvasSize(); + this.layerManager.createLayer(this.state.canvasWidth, this.state.canvasHeight); + this.root.append(this.previewCanvas); + this.root.append(this.eventLayer); + this.bindPointerEvents(); + this.dispatchChange(); + } + + get layers(): LayerSummary[] { + return this.layerManager.summaries; + } + + get canUndo(): boolean { + return this.history.canUndo; + } + + get canRedo(): boolean { + return this.history.canRedo; + } + + get hasSelection(): boolean { + return this.selection.hasBounds; + } + + get canPaste(): boolean { + return this.selection.canPaste; + } + + get canvasWidth(): number { + return this.state.canvasWidth; + } + + get canvasHeight(): number { + return this.state.canvasHeight; + } + + get activeLayerWidth(): number { + return this.layerManager.activeLayer.canvas.width; + } + + get activeLayerHeight(): number { + return this.layerManager.activeLayer.canvas.height; + } + + setTool(tool: Tool): void { + this.text.commit(); + this.state.activeTool = tool; + if (tool !== Tool.Select) { + this.selection.clear(); + } + this.dispatchChange(); + } + + setColor(color: string): void { + this.state.color = color; + this.dispatchChange(); + } + + setFillColor(color: string): void { + this.state.fillColor = color; + this.dispatchChange(); + } + + setFillEnabled(enabled: boolean): void { + this.state.fillEnabled = enabled; + this.dispatchChange(); + } + + setFillOpacity(opacity: number): void { + this.state.fillOpacity = Math.min(Math.max(opacity, 0), 100); + this.dispatchChange(); + } + + setFillTolerance(tolerance: number): void { + this.state.fillTolerance = Math.min(Math.max(tolerance, 0), 255); + this.dispatchChange(); + } + + setSize(size: number): void { + this.state.size = Math.min(Math.max(size, 1), 120); + this.dispatchChange(); + } + + setWebSensitivity(sensitivity: number): void { + this.state.webSensitivity = Math.min(Math.max(sensitivity, 20), 260); + this.dispatchChange(); + } + + setShadowEnabled(enabled: boolean): void { + this.state.shadowEnabled = enabled; + this.dispatchChange(); + } + + setShadowColor(color: string): void { + this.state.shadowColor = color; + this.dispatchChange(); + } + + setShadowBlur(blur: number): void { + this.state.shadowBlur = Math.min(Math.max(blur, 0), 80); + this.dispatchChange(); + } + + setShadowOffsetX(offset: number): void { + this.state.shadowOffsetX = Math.min(Math.max(offset, -120), 120); + this.dispatchChange(); + } + + setShadowOffsetY(offset: number): void { + this.state.shadowOffsetY = Math.min(Math.max(offset, -120), 120); + this.dispatchChange(); + } + + setZoom(zoom: number): void { + this.state.zoom = Math.min(Math.max(zoom, 0.1), 8); + this.applyZoom(); + this.dispatchChange(); + } + + zoomIn(): void { + const next = ZOOM_STEPS.find((z) => z > this.state.zoom + 0.001); + if (next !== undefined) this.setZoom(next); + } + + zoomOut(): void { + const prev = [...ZOOM_STEPS].reverse().find((z) => z < this.state.zoom - 0.001); + if (prev !== undefined) this.setZoom(prev); + } + + resizeCanvas(width: number, height: number, resizeLayersToo = true): void { + this.text.commit(); + this.selection.clear(); + + const nextWidth = Math.min(Math.max(Math.round(width), 64), 4096); + const nextHeight = Math.min(Math.max(Math.round(height), 64), 4096); + + if (nextWidth === this.state.canvasWidth && nextHeight === this.state.canvasHeight) { + return; + } + + const before = this.captureHistorySnapshot(); + this.state.canvasWidth = nextWidth; + this.state.canvasHeight = nextHeight; + if (resizeLayersToo) this.layerManager.resizeLayers(nextWidth, nextHeight); + this.applyCanvasSize(); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + resizeActiveLayer(width: number, height: number): void { + this.text.commit(); + this.selection.clear(); + + const nextWidth = Math.min(Math.max(Math.round(width), 1), 8192); + const nextHeight = Math.min(Math.max(Math.round(height), 1), 8192); + + const before = this.captureHistorySnapshot(); + const layer = this.layerManager.activeLayer; + const source = document.createElement('canvas'); + const sourceCtx = source.getContext('2d'); + + if (!sourceCtx) throw new Error('Canvas 2D context is unavailable.'); + + source.width = layer.canvas.width; + source.height = layer.canvas.height; + sourceCtx.drawImage(layer.canvas, 0, 0); + layer.canvas.width = nextWidth; + layer.canvas.height = nextHeight; + layer.context.clearRect(0, 0, nextWidth, nextHeight); + layer.context.drawImage(source, 0, 0); + this.layerManager.syncActiveLayerStyle(); + this.pushHistory(before, this.captureHistorySnapshot()); + this.dispatchChange(); + } + + clear(): void { + const before = this.captureHistorySnapshot(); + this.layerManager.clearActiveLayer(); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + undo(): void { + const snapshot = this.history.undo(); + if (!snapshot) return; + this.restoreHistorySnapshot(snapshot); + this.dispatchChange(); + } + + redo(): void { + const snapshot = this.history.redo(); + if (!snapshot) return; + this.restoreHistorySnapshot(snapshot); + this.dispatchChange(); + } + + async importImage(file: File): Promise { + const before = this.captureHistorySnapshot(); + const image = await this.loadImage(file); + const target = fitNaturalSizeToCanvas(image, this.canvasSize); + this.layerManager.drawImageOnNewLayer(image, this.state.canvasWidth, this.state.canvasHeight, target); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + async importCameraFrame(): Promise { + const mediaDevices = navigator.mediaDevices; + + if (!mediaDevices?.getUserMedia) { + throw new Error('Camera is unavailable in this browser.'); + } + + const stream = await mediaDevices.getUserMedia({video: true}); + const video = document.createElement('video'); + + try { + video.muted = true; + video.playsInline = true; + video.srcObject = stream; + await video.play(); + + await new Promise((resolve) => { + if (video.videoWidth > 0 && video.videoHeight > 0) { + resolve(); + return; + } + video.addEventListener('loadedmetadata', () => resolve(), {once: true}); + }); + + const before = this.captureHistorySnapshot(); + const target = fitNaturalSizeToCanvas( + {naturalWidth: video.videoWidth, naturalHeight: video.videoHeight}, + this.canvasSize, + ); + this.layerManager.drawImageOnNewLayer(video, this.state.canvasWidth, this.state.canvasHeight, target); + this.pushHistory(before, this.captureHistorySnapshot()); + } finally { + stream.getTracks().forEach((track) => track.stop()); + } + } + + importCanvasAsLayer(source: HTMLCanvasElement): void { + const before = this.captureHistorySnapshot(); + const target = fitNaturalSizeToCanvas( + {naturalWidth: source.width, naturalHeight: source.height}, + this.canvasSize, + ); + this.layerManager.drawImageOnNewLayer(source, this.state.canvasWidth, this.state.canvasHeight, target); + this.pushHistory(before, this.captureHistorySnapshot()); + this.dispatchChange(); + } + + async exportProject(): Promise { + const snapshot = this.layerManager.captureDocument(); + const pngBuffers = await Promise.all( + snapshot.layers.map(async (layer) => { + const canvas = document.createElement('canvas'); + canvas.width = layer.imageData.width; + canvas.height = layer.imageData.height; + canvas.getContext('2d')!.putImageData(layer.imageData, 0, 0); + const blob = await new Promise((resolve, reject) => + canvas.toBlob( + (b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), + 'image/png', + ), + ); + return new Uint8Array(await blob.arrayBuffer()); + }), + ); + const meta: WdraftFileMeta = { + version: WDRAFT_VERSION, + canvasWidth: this.state.canvasWidth, + canvasHeight: this.state.canvasHeight, + activeLayerId: snapshot.activeLayerId, + layerCount: snapshot.layerCount, + layers: snapshot.layers.map((l) => ({ + id: l.layerId, + name: l.name, + visible: l.visible, + width: l.imageData.width, + height: l.imageData.height, + textData: l.textData, + })), + }; + return serializeWdraftBinary(meta, pngBuffers); + } + + async importProject(parsed: ParsedWdraftFile): Promise { + const {meta, pngBuffers} = parsed; + const before = this.captureHistorySnapshot(); + const imageDataList = await Promise.all( + meta.layers.map((l, i) => loadPngBytesAsImageData(pngBuffers[i], l.width, l.height)), + ); + const newDocument = { + activeLayerId: meta.activeLayerId, + layerCount: meta.layerCount, + layers: meta.layers.map((l, i) => ({ + layerId: l.id, + name: l.name, + visible: l.visible, + x: 0, + y: 0, + textData: l.textData, + imageData: imageDataList[i], + })), + }; + this.state.canvasWidth = meta.canvasWidth; + this.state.canvasHeight = meta.canvasHeight; + this.layerManager.restoreDocument(newDocument); + this.applyCanvasSize(); + this.selection.clear(); + this.pushHistory(before, this.captureHistorySnapshot()); + this.dispatchChange(); + } + + async exportPng(): Promise { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Canvas 2D context is unavailable.'); + } + + canvas.width = this.state.canvasWidth; + canvas.height = this.state.canvasHeight; + + for (const layer of this.layerManager.visibleLayers) { + context.drawImage(layer.canvas, layer.x, layer.y); + } + + const blob = await new Promise((resolve) => { + canvas.toBlob(resolve, 'image/png'); + }); + + if (!blob) { + throw new Error('Unable to export PNG.'); + } + + return blob; + } + + addLayer(): void { + const before = this.captureHistorySnapshot(); + this.layerManager.createLayer(this.state.canvasWidth, this.state.canvasHeight); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + deleteActiveLayer(): void { + const before = this.captureHistorySnapshot(); + if (this.layerManager.deleteActiveLayer()) { + this.pushHistory(before, this.captureHistorySnapshot()); + } + } + + selectLayer(id: string): void { + this.layerManager.selectLayer(id); + this.dispatchChange(); + } + + renameLayer(id: string, name: string): void { + const before = this.captureHistorySnapshot(); + if (this.layerManager.renameLayer(id, name)) { + this.pushHistory(before, this.captureHistorySnapshot()); + } + } + + toggleLayerVisibility(id: string): void { + const before = this.captureHistorySnapshot(); + this.layerManager.toggleVisibility(id); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + moveActiveLayerUp(): void { + const before = this.captureHistorySnapshot(); + if (this.layerManager.moveActiveLayerUp()) { + this.pushHistory(before, this.captureHistorySnapshot()); + } + } + + moveActiveLayerDown(): void { + const before = this.captureHistorySnapshot(); + if (this.layerManager.moveActiveLayerDown()) { + this.pushHistory(before, this.captureHistorySnapshot()); + } + } + + copySelection(): void { + this.selection.copy(); + } + + cutSelection(): void { + this.selection.cut(); + } + + pasteSelection(): void { + this.selection.paste(); + } + + invertActiveLayer(): void { + this.text.commit(); + this.selection.clear(); + + const before = this.captureHistorySnapshot(); + const {canvas, context} = this.layerManager.activeLayer; + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const transformed = invertPixelBuffer(imageData); + + context.putImageData(createImageData(transformed), 0, 0); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + rotateActiveLayer(direction: 'left' | 'right'): void { + this.text.commit(); + this.selection.clear(); + + const before = this.captureHistorySnapshot(); + const layer = this.layerManager.activeLayer; + const {canvas, context} = layer; + const oldW = canvas.width; + const oldH = canvas.height; + const transformed = rotatePixelBuffer(context.getImageData(0, 0, oldW, oldH), direction); + + canvas.width = transformed.width; + canvas.height = transformed.height; + context.putImageData(createImageData(transformed), 0, 0); + layer.x = Math.round(layer.x + (oldW - oldH) / 2); + layer.y = Math.round(layer.y + (oldH - oldW) / 2); + this.layerManager.syncActiveLayerStyle(); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + mirrorActiveLayer(axis: 'horizontal' | 'vertical'): void { + this.text.commit(); + this.selection.clear(); + + const before = this.captureHistorySnapshot(); + const {canvas, context} = this.layerManager.activeLayer; + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const transformed = mirrorPixelBuffer(imageData, axis); + + context.putImageData(createImageData(transformed), 0, 0); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + private bindPointerEvents(): void { + this.eventLayer.addEventListener('pointerdown', (event) => { + this.eventLayer.setPointerCapture(event.pointerId); + this.isDrawing = true; + this.lastPoint = this.getPoint(event); + + if (this.state.activeTool === Tool.Select) { + this.selection.onPointerDown(this.lastPoint); + return; + } + + if (this.state.activeTool === Tool.Text) { + const consumed = this.text.onPointerDown(this.lastPoint); + if (consumed) { + this.isDrawing = false; + this.lastPoint = null; + } + return; + } + + if (this.state.activeTool === Tool.Sampler) { + this.sampleColor(this.lastPoint); + this.setTool(Tool.Pencil); + this.isDrawing = false; + this.lastPoint = null; + return; + } + + if (this.state.activeTool === Tool.FillBucket) { + this.fillActiveLayer(this.toLayerPoint(this.lastPoint)); + this.isDrawing = false; + this.lastPoint = null; + return; + } + + this.pendingHistorySnapshot = this.captureHistorySnapshot(); + + if (this.isShapeTool()) { + this.shapeStartPoint = this.lastPoint; + return; + } + + if (this.state.activeTool === Tool.Web) { + const lp = this.toLayerPoint(this.lastPoint); + this.webPoints = [lp]; + drawPoint(this.layerManager.activeLayer.context, lp, this.state); + return; + } + + drawPoint(this.layerManager.activeLayer.context, this.toLayerPoint(this.lastPoint), this.state); + }); + + this.eventLayer.addEventListener('pointermove', (event) => { + if (!this.isDrawing || !this.lastPoint) return; + + const nextPoint = this.getPoint(event); + + if (this.state.activeTool === Tool.Select) { + this.selection.onPointerMove(nextPoint); + return; + } + + if (this.state.activeTool === Tool.Text) { + this.text.onPointerMove(nextPoint); + return; + } + + if (this.isShapeTool()) { + this.renderShapePreview(nextPoint); + return; + } + + if (this.state.activeTool === Tool.Web) { + this.webPoints = drawWebLine( + this.layerManager.activeLayer.context, + this.toLayerPoint(nextPoint), + this.webPoints, + this.state, + ); + this.lastPoint = nextPoint; + return; + } + + drawLine( + this.layerManager.activeLayer.context, + this.toLayerPoint(this.lastPoint), + this.toLayerPoint(nextPoint), + this.state, + ); + this.lastPoint = nextPoint; + }); + + this.eventLayer.addEventListener('pointerup', (event) => { + const point = this.getPoint(event); + + if (this.state.activeTool === Tool.Select) { + this.selection.onPointerUp(point); + } + + if (this.state.activeTool === Tool.Text) { + this.text.onPointerUp(point); + } + + if (this.isShapeTool()) { + this.commitShape(point); + } + + if (this.state.activeTool !== Tool.Select && this.state.activeTool !== Tool.Text) { + this.commitPendingHistory(); + } + + this.eventLayer.releasePointerCapture(event.pointerId); + this.isDrawing = false; + this.lastPoint = null; + this.shapeStartPoint = null; + this.webPoints = []; + }); + + this.eventLayer.addEventListener('pointercancel', () => { + this.isDrawing = false; + this.lastPoint = null; + this.shapeStartPoint = null; + this.webPoints = []; + this.pendingHistorySnapshot = null; + this.clearPreview(); + }); + + this.eventLayer.addEventListener( + 'wheel', + (event) => { + if (event.ctrlKey || event.metaKey) { + event.preventDefault(); + if (event.deltaY < 0) this.zoomIn(); + else this.zoomOut(); + } + }, + {passive: false}, + ); + } + + private toLayerPoint(p: Point): Point { + const {x, y} = this.layerManager.activeLayer; + return {x: p.x - x, y: p.y - y}; + } + + private getPoint(event: PointerEvent): Point { + const rect = this.eventLayer.getBoundingClientRect(); + const zoom = this.state.zoom; + return { + x: (event.clientX - rect.left) / zoom, + y: (event.clientY - rect.top) / zoom, + }; + } + + private async loadImage(file: File): Promise { + const url = URL.createObjectURL(file); + const image = new Image(); + + try { + await new Promise((resolve, reject) => { + image.addEventListener('load', () => resolve(), {once: true}); + image.addEventListener('error', () => reject(new Error('Unable to load image.')), {once: true}); + image.src = url; + }); + return image; + } finally { + URL.revokeObjectURL(url); + } + } + + private applyCanvasSize(): void { + this.root.style.setProperty('--canvas-width', `${this.state.canvasWidth}px`); + this.root.style.setProperty('--canvas-height', `${this.state.canvasHeight}px`); + this.previewCanvas.width = this.state.canvasWidth; + this.previewCanvas.height = this.state.canvasHeight; + this.applyZoom(); + } + + private applyZoom(): void { + const zoom = this.state.zoom; + const w = this.state.canvasWidth; + const h = this.state.canvasHeight; + this.root.style.transform = zoom !== 1 ? `scale(${zoom})` : ''; + this.root.style.transformOrigin = 'top left'; + this.root.style.marginRight = zoom > 1 ? `${Math.round(w * (zoom - 1))}px` : ''; + this.root.style.marginBottom = zoom > 1 ? `${Math.round(h * (zoom - 1))}px` : ''; + } + + private get canvasSize(): { width: number; height: number } { + return {width: this.state.canvasWidth, height: this.state.canvasHeight}; + } + + private isShapeTool(): boolean { + return this.state.activeTool === Tool.Rectangle || this.state.activeTool === Tool.Ellipse; + } + + private sampleColor(point: Point): void { + const x = Math.floor(point.x); + const y = Math.floor(point.y); + + if (x < 0 || y < 0 || x >= this.state.canvasWidth || y >= this.state.canvasHeight) { + return; + } + + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (!context) throw new Error('Canvas 2D context is unavailable.'); + + canvas.width = this.state.canvasWidth; + canvas.height = this.state.canvasHeight; + + for (const layer of this.layerManager.visibleLayers) { + context.drawImage(layer.canvas, layer.x, layer.y); + } + + const [red, green, blue, alpha] = context.getImageData(x, y, 1, 1).data; + if (alpha === 0) return; + this.setColor(rgbToHex(red, green, blue)); + } + + private fillActiveLayer(point: Point): void { + const before = this.captureHistorySnapshot(); + const {canvas, context} = this.layerManager.activeLayer; + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const changed = floodFillImageData( + imageData, + point.x, + point.y, + hexToRgbaColor(this.state.fillColor, this.state.fillOpacity / 100), + this.state.fillTolerance, + ); + if (!changed) return; + context.putImageData(imageData, 0, 0); + this.pushHistory(before, this.captureHistorySnapshot()); + } + + private renderShapePreview(point: Point): void { + if (!this.shapeStartPoint) return; + this.clearPreview(); + drawShape(this.previewContext, getBounds(this.shapeStartPoint, point), this.state); + } + + private commitShape(point: Point): void { + if (!this.shapeStartPoint) return; + const {context} = this.layerManager.activeLayer; + drawShape( + context, + getBounds(this.toLayerPoint(this.shapeStartPoint), this.toLayerPoint(point)), + this.state, + ); + this.clearPreview(); + } + + private clearPreview(): void { + this.previewContext.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height); + } + + private commitPendingHistory(): void { + if (!this.pendingHistorySnapshot) return; + this.pushHistory(this.pendingHistorySnapshot, this.captureHistorySnapshot()); + this.pendingHistorySnapshot = null; + } + + private captureHistorySnapshot(): HistorySnapshot { + return { + canvasWidth: this.state.canvasWidth, + canvasHeight: this.state.canvasHeight, + document: this.layerManager.captureDocument(), + }; + } + + private restoreHistorySnapshot(snapshot: HistorySnapshot): void { + this.state.canvasWidth = snapshot.canvasWidth; + this.state.canvasHeight = snapshot.canvasHeight; + this.layerManager.restoreDocument(snapshot.document); + this.applyCanvasSize(); + this.selection.clear(); + } + + private pushHistory(before: HistorySnapshot, after: HistorySnapshot): void { + this.history.push(before, after); + this.dispatchChange(); + } + + private dispatchChange(): void { + this.dispatchEvent(new CustomEvent('change', {detail: this.state})); + } +} + +function rgbToHex(red: number, green: number, blue: number): string { + return `#${toHex(red)}${toHex(green)}${toHex(blue)}`; +} + +function toHex(value: number): string { + return value.toString(16).padStart(2, '0'); +} + +function createImageData(buffer: { data: Uint8ClampedArray; width: number; height: number }): ImageData { + const data = new Uint8ClampedArray(buffer.data.length); + data.set(buffer.data); + return new ImageData(data, buffer.width, buffer.height); +} + +function loadPngBytesAsImageData(bytes: Uint8Array, width: number, height: number): Promise { + return new Promise((resolve, reject) => { + const url = URL.createObjectURL( + new Blob( + [(bytes.buffer as ArrayBuffer).slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)], + {type: 'image/png'}, + ), + ); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(url); + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d')!; + ctx.drawImage(img, 0, 0); + resolve(ctx.getImageData(0, 0, width, height)); + }; + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Failed to load layer image.')); + }; + img.src = url; + }); +} diff --git a/src/i18n/en/camera.ts b/src/i18n/en/camera.ts new file mode 100644 index 0000000..d792010 --- /dev/null +++ b/src/i18n/en/camera.ts @@ -0,0 +1,19 @@ +import type {CameraTranslations} from '../../types/i18n'; + +export const cameraTranslations: CameraTranslations = { + title: 'Camera', + titleWithLabel: (label) => `Camera \u2014 ${label}`, + unavailable: 'Camera is unavailable in this browser.', + sepia: 'Sepia', + noise: 'Noise', + greyscale: 'Greyscale', + negative: 'Negative', + snap: 'Snap', + snapToCanvas: 'Snap to canvas', + applyToCanvas: 'Apply to canvas', + saveToFile: 'Save to file', + backToLive: 'Back to live', + frameAdded: 'Camera frame added.', + cameraError: 'Camera error.', + snapFilename: 'camera-snap.png', +}; diff --git a/src/i18n/en/common.ts b/src/i18n/en/common.ts new file mode 100644 index 0000000..9eb95d5 --- /dev/null +++ b/src/i18n/en/common.ts @@ -0,0 +1,6 @@ +import type {CommonTranslations} from '../../types/i18n'; + +export const commonTranslations: CommonTranslations = { + somethingWentWrong: 'Something went wrong.', + appTitle: 'WebDraft', +}; diff --git a/src/i18n/en/edit.ts b/src/i18n/en/edit.ts new file mode 100644 index 0000000..482f522 --- /dev/null +++ b/src/i18n/en/edit.ts @@ -0,0 +1,10 @@ +import type {EditTranslations} from '../../types/i18n'; + +export const editTranslations: EditTranslations = { + undo: 'Undo', + redo: 'Redo', + cut: 'Cut', + copy: 'Copy', + paste: 'Paste', + clearLayer: 'Clear Layer', +}; diff --git a/src/i18n/en/file.ts b/src/i18n/en/file.ts new file mode 100644 index 0000000..af1e690 --- /dev/null +++ b/src/i18n/en/file.ts @@ -0,0 +1,16 @@ +import type {FileTranslations} from '../../types/i18n'; + +export const fileTranslations: FileTranslations = { + new: 'New Project', + open: 'Open\u2026', + save: 'Save Project', + importImage: 'Import Image\u2026', + exportPng: 'Export PNG', + newConfirm: 'New project? Unsaved changes will be lost.', + savedOk: 'Project saved.', + openedOk: 'Project opened.', + importedOk: 'Image imported.', + exportedOk: 'PNG exported.', + projectFilename: 'project.wdraft', + imageFilename: 'webdraft-image.png', +}; diff --git a/src/i18n/en/help.ts b/src/i18n/en/help.ts new file mode 100644 index 0000000..29b2371 --- /dev/null +++ b/src/i18n/en/help.ts @@ -0,0 +1,14 @@ +import type {HelpTranslations} from '../../types/i18n'; + +export const helpTranslations: HelpTranslations = { + keyboardShortcuts: 'Keyboard Shortcuts', + close: 'Close', + sectionTools: 'Tools', + sectionView: 'View', + sectionCanvas: 'Canvas', + sizeDecrease: 'Decrease brush size', + sizeIncrease: 'Increase brush size', + zoomIn: 'Zoom in', + zoomOut: 'Zoom out', + zoomReset: 'Reset zoom', +}; diff --git a/src/i18n/en/image.ts b/src/i18n/en/image.ts new file mode 100644 index 0000000..a75e568 --- /dev/null +++ b/src/i18n/en/image.ts @@ -0,0 +1,14 @@ +import type {ImageTranslations} from '../../types/i18n'; + +export const imageTranslations: ImageTranslations = { + resizeCanvas: 'Resize Canvas\u2026', + invertColors: 'Invert Colors', + mirrorH: 'Mirror Horizontal', + mirrorV: 'Mirror Vertical', + rotateLeft: 'Rotate Left', + rotateRight: 'Rotate Right', + widthPrompt: 'Width (px):', + heightPrompt: 'Height (px):', + resizedOk: (w, h) => `Canvas: ${w}\u00d7${h}`, + applyToAllLayers: 'Apply to all layers', +}; diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts new file mode 100644 index 0000000..f9c7c23 --- /dev/null +++ b/src/i18n/en/index.ts @@ -0,0 +1,28 @@ +import type {Translations} from '../../types/i18n'; +import {cameraTranslations} from './camera.ts'; +import {commonTranslations} from './common.ts'; +import {editTranslations} from './edit.ts'; +import {fileTranslations} from './file.ts'; +import {helpTranslations} from './help.ts'; +import {imageTranslations} from './image.ts'; +import {layerTranslations} from './layer.ts'; +import {layersTranslations} from './layers.ts'; +import {menuTranslations} from './menu.ts'; +import {pickerTranslations} from './picker.ts'; +import {saveDialogTranslations} from './saveDialog.ts'; +import {toolbarTranslations} from './toolbar.ts'; + +export const en: Translations = { + menu: menuTranslations, + file: fileTranslations, + edit: editTranslations, + image: imageTranslations, + layer: layerTranslations, + toolbar: toolbarTranslations, + layers: layersTranslations, + camera: cameraTranslations, + picker: pickerTranslations, + common: commonTranslations, + saveDialog: saveDialogTranslations, + help: helpTranslations, +}; diff --git a/src/i18n/en/layer.ts b/src/i18n/en/layer.ts new file mode 100644 index 0000000..14e5ade --- /dev/null +++ b/src/i18n/en/layer.ts @@ -0,0 +1,12 @@ +import type {LayerTranslations} from '../../types/i18n'; + +export const layerTranslations: LayerTranslations = { + new: 'New Layer', + delete: 'Delete Layer', + moveUp: 'Move Layer Up', + moveDown: 'Move Layer Down', + addedOk: 'Layer added.', + deletedOk: 'Layer deleted.', + resize: 'Resize Layer\u2026', + layerResizedOk: (w, h) => `Layer: ${w}\u00d7${h}`, +}; diff --git a/src/i18n/en/layers.ts b/src/i18n/en/layers.ts new file mode 100644 index 0000000..20024c2 --- /dev/null +++ b/src/i18n/en/layers.ts @@ -0,0 +1,15 @@ +import type {LayersTranslations} from '../../types/i18n'; + +export const layersTranslations: LayersTranslations = { + title: 'Layers', + addLayer: 'Add layer', + deleteLayer: 'Delete layer', + layerName: (suffix) => `Layer ${suffix}`, + moveLayerUp: 'Move layer up', + moveLayerDown: 'Move layer down', + selectLayer: (name) => `Select ${name}`, + renameLayer: 'Rename layer', + hideLayer: 'Hide layer', + showLayer: 'Show layer', + textLayer: 'Text layer', +}; diff --git a/src/i18n/en/menu.ts b/src/i18n/en/menu.ts new file mode 100644 index 0000000..44e78fc --- /dev/null +++ b/src/i18n/en/menu.ts @@ -0,0 +1,9 @@ +import type {MenuTranslations} from '../../types/i18n'; + +export const menuTranslations: MenuTranslations = { + file: 'File', + edit: 'Edit', + image: 'Image', + layer: 'Layer', + help: 'Help', +}; diff --git a/src/i18n/en/picker.ts b/src/i18n/en/picker.ts new file mode 100644 index 0000000..8829f7b --- /dev/null +++ b/src/i18n/en/picker.ts @@ -0,0 +1,10 @@ +import type {PickerTranslations} from '../../types/i18n'; + +export const pickerTranslations: PickerTranslations = { + addColor: 'Add current color', + restorePrevious: 'Click to restore previous color', + newColor: 'New:', + previousColor: 'Previous:', + range100: '0..100', + range255: '0..255', +}; diff --git a/src/i18n/en/saveDialog.ts b/src/i18n/en/saveDialog.ts new file mode 100644 index 0000000..8589023 --- /dev/null +++ b/src/i18n/en/saveDialog.ts @@ -0,0 +1,9 @@ +import type {SaveDialogTranslations} from '../../types/i18n'; + +export const saveDialogTranslations: SaveDialogTranslations = { + title: 'Save', + exportPng: 'Export PNG', + saveProject: 'Save project', + rememberChoice: 'Remember decision', + cancel: 'Cancel', +}; diff --git a/src/i18n/en/toolbar.ts b/src/i18n/en/toolbar.ts new file mode 100644 index 0000000..fabc461 --- /dev/null +++ b/src/i18n/en/toolbar.ts @@ -0,0 +1,52 @@ +import type {ToolbarTranslations} from '../../types/i18n'; +import {modKey} from '../../core/platform'; + +export const toolbarTranslations: ToolbarTranslations = { + toolSelect: 'Select \u2014 S', + toolPencil: 'Pencil \u2014 P', + toolEraser: 'Eraser \u2014 E', + toolSampler: 'Sampler \u2014 C', + toolFillBucket: 'Fill bucket \u2014 B', + toolWeb: 'Web \u2014 W', + toolRectangle: 'Rectangle \u2014 R', + toolEllipse: 'Ellipse \u2014 O', + toolText: 'Text \u2014 T', + invertColors: 'Invert colors \u2014 I', + rotateLeft: 'Rotate left \u2014 ,', + rotateRight: 'Rotate right \u2014 .', + mirrorH: 'Mirror horizontally \u2014 H', + mirrorV: 'Mirror vertically \u2014 V', + size: 'Size', + webSensitivity: 'Web', + color: 'Color', + fillShapes: 'Fill shapes', + fillColor: 'Fill color', + fillOpacity: 'Fill opacity', + bucketTolerance: 'Bucket tolerance', + shadow: 'Shadow', + shadowColor: 'Shadow color', + shadowBlur: 'Shadow blur', + shadowX: 'Shadow X', + shadowY: 'Shadow Y', + textFont: 'Text font', + textAlignment: 'Text alignment', + bold: 'Bold', + italic: 'Italic', + clear: 'Clear', + undo: `Undo \u2014 ${modKey}+Z`, + redo: `Redo \u2014 ${modKey}+Shift+Z`, + copy: `Copy \u2014 ${modKey}+C`, + cut: `Cut \u2014 ${modKey}+X`, + paste: `Paste \u2014 ${modKey}+V`, + width: 'Width', + height: 'Height', + resizeCanvas: 'Resize canvas', + uploadImage: 'Upload image', + camera: 'Camera', + exportPng: 'Export PNG', + saveProject: 'Save project', + openProject: 'Open project', + pngExportedOk: 'PNG exported.', + imageImportedOk: 'Image imported.', + canvasResizedOk: (w, h) => `Canvas resized to ${w} \u00d7 ${h}.`, +}; diff --git a/src/i18n/pl/camera.ts b/src/i18n/pl/camera.ts new file mode 100644 index 0000000..d534625 --- /dev/null +++ b/src/i18n/pl/camera.ts @@ -0,0 +1,19 @@ +import type {CameraTranslations} from '../../types/i18n'; + +export const cameraTranslations: CameraTranslations = { + title: 'Kamera', + titleWithLabel: (label) => `Kamera \u2014 ${label}`, + unavailable: 'Kamera jest niedostępna w tej przeglądarce.', + sepia: 'Sepia', + noise: 'Szum', + greyscale: 'Skala szarości', + negative: 'Negatyw', + snap: 'Zdjęcie', + snapToCanvas: 'Zdjęcie na płótno', + applyToCanvas: 'Zastosuj na płótno', + saveToFile: 'Zapisz do pliku', + backToLive: 'Wróć do podglądu', + frameAdded: 'Kadr z kamery dodany.', + cameraError: 'Błąd kamery.', + snapFilename: 'camera-snap.png', +}; diff --git a/src/i18n/pl/common.ts b/src/i18n/pl/common.ts new file mode 100644 index 0000000..c808a2f --- /dev/null +++ b/src/i18n/pl/common.ts @@ -0,0 +1,6 @@ +import type {CommonTranslations} from '../../types/i18n'; + +export const commonTranslations: CommonTranslations = { + somethingWentWrong: 'Coś poszło nie tak.', + appTitle: 'WebDraft', +}; diff --git a/src/i18n/pl/edit.ts b/src/i18n/pl/edit.ts new file mode 100644 index 0000000..a37d64c --- /dev/null +++ b/src/i18n/pl/edit.ts @@ -0,0 +1,10 @@ +import type {EditTranslations} from '../../types/i18n'; + +export const editTranslations: EditTranslations = { + undo: 'Cofnij', + redo: 'Ponów', + cut: 'Wytnij', + copy: 'Kopiuj', + paste: 'Wklej', + clearLayer: 'Wyczyść warstwę', +}; diff --git a/src/i18n/pl/file.ts b/src/i18n/pl/file.ts new file mode 100644 index 0000000..946c7ee --- /dev/null +++ b/src/i18n/pl/file.ts @@ -0,0 +1,16 @@ +import type {FileTranslations} from '../../types/i18n'; + +export const fileTranslations: FileTranslations = { + new: 'Nowy projekt', + open: 'Otwórz\u2026', + save: 'Zapisz projekt', + importImage: 'Importuj obraz\u2026', + exportPng: 'Eksportuj PNG', + newConfirm: 'Nowy projekt? Niezapisane zmiany zostaną utracone.', + savedOk: 'Projekt zapisany.', + openedOk: 'Projekt otwarty.', + importedOk: 'Obraz zaimportowany.', + exportedOk: 'PNG wyeksportowany.', + projectFilename: 'projekt.wdraft', + imageFilename: 'webdraft-obraz.png', +}; diff --git a/src/i18n/pl/help.ts b/src/i18n/pl/help.ts new file mode 100644 index 0000000..733528d --- /dev/null +++ b/src/i18n/pl/help.ts @@ -0,0 +1,14 @@ +import type {HelpTranslations} from '../../types/i18n'; + +export const helpTranslations: HelpTranslations = { + keyboardShortcuts: 'Skróty klawiszowe', + close: 'Zamknij', + sectionTools: 'Narzędzia', + sectionView: 'Widok', + sectionCanvas: 'Płótno', + sizeDecrease: 'Zmniejsz rozmiar pędzla', + sizeIncrease: 'Zwiększ rozmiar pędzla', + zoomIn: 'Powiększ', + zoomOut: 'Pomniejsz', + zoomReset: 'Resetuj zoom', +}; diff --git a/src/i18n/pl/image.ts b/src/i18n/pl/image.ts new file mode 100644 index 0000000..6278d10 --- /dev/null +++ b/src/i18n/pl/image.ts @@ -0,0 +1,14 @@ +import type {ImageTranslations} from '../../types/i18n'; + +export const imageTranslations: ImageTranslations = { + resizeCanvas: 'Zmień rozmiar płótna\u2026', + invertColors: 'Odwróć kolory', + mirrorH: 'Lustro poziome', + mirrorV: 'Lustro pionowe', + rotateLeft: 'Obróć w lewo', + rotateRight: 'Obróć w prawo', + widthPrompt: 'Szerokość (px):', + heightPrompt: 'Wysokość (px):', + resizedOk: (w, h) => `Płótno: ${w}\u00d7${h}`, + applyToAllLayers: 'Zastosuj do wszystkich warstw', +}; diff --git a/src/i18n/pl/index.ts b/src/i18n/pl/index.ts new file mode 100644 index 0000000..83b60cd --- /dev/null +++ b/src/i18n/pl/index.ts @@ -0,0 +1,28 @@ +import type {Translations} from '../../types/i18n'; +import {cameraTranslations} from './camera.ts'; +import {commonTranslations} from './common.ts'; +import {editTranslations} from './edit.ts'; +import {fileTranslations} from './file.ts'; +import {helpTranslations} from './help.ts'; +import {imageTranslations} from './image.ts'; +import {layerTranslations} from './layer.ts'; +import {layersTranslations} from './layers.ts'; +import {menuTranslations} from './menu.ts'; +import {pickerTranslations} from './picker.ts'; +import {saveDialogTranslations} from './saveDialog.ts'; +import {toolbarTranslations} from './toolbar.ts'; + +export const pl: Translations = { + menu: menuTranslations, + file: fileTranslations, + edit: editTranslations, + image: imageTranslations, + layer: layerTranslations, + toolbar: toolbarTranslations, + layers: layersTranslations, + camera: cameraTranslations, + picker: pickerTranslations, + common: commonTranslations, + saveDialog: saveDialogTranslations, + help: helpTranslations, +}; diff --git a/src/i18n/pl/layer.ts b/src/i18n/pl/layer.ts new file mode 100644 index 0000000..0a5e46b --- /dev/null +++ b/src/i18n/pl/layer.ts @@ -0,0 +1,12 @@ +import type {LayerTranslations} from '../../types/i18n'; + +export const layerTranslations: LayerTranslations = { + new: 'Nowa warstwa', + delete: 'Usuń warstwę', + moveUp: 'Przesuń warstwę w górę', + moveDown: 'Przesuń warstwę w dół', + addedOk: 'Warstwa dodana.', + deletedOk: 'Warstwa usunięta.', + resize: 'Zmień rozmiar warstwy\u2026', + layerResizedOk: (w, h) => `Warstwa: ${w}\u00d7${h}`, +}; diff --git a/src/i18n/pl/layers.ts b/src/i18n/pl/layers.ts new file mode 100644 index 0000000..8d589bf --- /dev/null +++ b/src/i18n/pl/layers.ts @@ -0,0 +1,15 @@ +import type {LayersTranslations} from '../../types/i18n'; + +export const layersTranslations: LayersTranslations = { + title: 'Warstwy', + addLayer: 'Dodaj warstwę', + deleteLayer: 'Usuń warstwę', + layerName: (suffix) => `Warstwa ${suffix}`, + moveLayerUp: 'Przesuń warstwę w górę', + moveLayerDown: 'Przesuń warstwę w dół', + selectLayer: (name) => `Zaznacz ${name}`, + renameLayer: 'Zmień nazwę warstwy', + hideLayer: 'Ukryj warstwę', + showLayer: 'Pokaż warstwę', + textLayer: 'Warstwa tekstowa', +}; diff --git a/src/i18n/pl/menu.ts b/src/i18n/pl/menu.ts new file mode 100644 index 0000000..cbdacff --- /dev/null +++ b/src/i18n/pl/menu.ts @@ -0,0 +1,9 @@ +import type {MenuTranslations} from '../../types/i18n'; + +export const menuTranslations: MenuTranslations = { + file: 'Plik', + edit: 'Edycja', + image: 'Obraz', + layer: 'Warstwa', + help: 'Pomoc', +}; diff --git a/src/i18n/pl/picker.ts b/src/i18n/pl/picker.ts new file mode 100644 index 0000000..04f8aef --- /dev/null +++ b/src/i18n/pl/picker.ts @@ -0,0 +1,10 @@ +import type {PickerTranslations} from '../../types/i18n'; + +export const pickerTranslations: PickerTranslations = { + addColor: 'Dodaj bieżący kolor', + restorePrevious: 'Kliknij, aby przywrócić poprzedni kolor', + newColor: 'Nowy:', + previousColor: 'Poprzedni:', + range100: '0..100', + range255: '0..255', +}; diff --git a/src/i18n/pl/saveDialog.ts b/src/i18n/pl/saveDialog.ts new file mode 100644 index 0000000..37ccf3b --- /dev/null +++ b/src/i18n/pl/saveDialog.ts @@ -0,0 +1,9 @@ +import type {SaveDialogTranslations} from '../../types/i18n'; + +export const saveDialogTranslations: SaveDialogTranslations = { + title: 'Zapisz', + exportPng: 'Eksportuj PNG', + saveProject: 'Zapisz projekt', + rememberChoice: 'Zapamiętaj decyzję', + cancel: 'Anuluj', +}; diff --git a/src/i18n/pl/toolbar.ts b/src/i18n/pl/toolbar.ts new file mode 100644 index 0000000..e5124e6 --- /dev/null +++ b/src/i18n/pl/toolbar.ts @@ -0,0 +1,52 @@ +import type {ToolbarTranslations} from '../../types/i18n'; +import {modKey} from '../../core/platform'; + +export const toolbarTranslations: ToolbarTranslations = { + toolSelect: 'Zaznaczanie \u2014 S', + toolPencil: 'Ołówek \u2014 P', + toolEraser: 'Gumka \u2014 E', + toolSampler: 'Próbnik \u2014 C', + toolFillBucket: 'Wiadro z farbą \u2014 B', + toolWeb: 'Sieć \u2014 W', + toolRectangle: 'Prostokąt \u2014 R', + toolEllipse: 'Elipsa \u2014 O', + toolText: 'Tekst \u2014 T', + invertColors: 'Odwróć kolory \u2014 I', + rotateLeft: 'Obróć w lewo \u2014 ,', + rotateRight: 'Obróć w prawo \u2014 .', + mirrorH: 'Lustro poziome \u2014 H', + mirrorV: 'Lustro pionowe \u2014 V', + size: 'Rozmiar', + webSensitivity: 'Sieć', + color: 'Kolor', + fillShapes: 'Wypełnij kształty', + fillColor: 'Kolor wypełnienia', + fillOpacity: 'Krycie wypełnienia', + bucketTolerance: 'Tolerancja', + shadow: 'Cień', + shadowColor: 'Kolor cienia', + shadowBlur: 'Rozmycie cienia', + shadowX: 'Cień X', + shadowY: 'Cień Y', + textFont: 'Czcionka', + textAlignment: 'Wyrównanie', + bold: 'Pogrubienie', + italic: 'Kursywa', + clear: 'Wyczyść', + undo: `Cofnij \u2014 ${modKey}+Z`, + redo: `Ponów \u2014 ${modKey}+Shift+Z`, + copy: `Kopiuj \u2014 ${modKey}+C`, + cut: `Wytnij \u2014 ${modKey}+X`, + paste: `Wklej \u2014 ${modKey}+V`, + width: 'Szerokość', + height: 'Wysokość', + resizeCanvas: 'Zmień rozmiar płótna', + uploadImage: 'Wgraj obraz', + camera: 'Kamera', + exportPng: 'Eksportuj PNG', + saveProject: 'Zapisz projekt', + openProject: 'Otwórz projekt', + pngExportedOk: 'PNG wyeksportowany.', + imageImportedOk: 'Obraz zaimportowany.', + canvasResizedOk: (w, h) => `Płótno zmienione: ${w}\u00d7${h}`, +}; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..b0ee3b0 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,51 @@ +import { WebDraftEditor } from './core/webdraft-editor'; +import { createMenuBar } from './ui/menu-bar'; +import { createRightPanel } from './ui/right-panel'; +import { createStatusToasts } from './ui/status-toasts'; +import { createToolbar } from './ui/toolbar'; +import { restoreStateAfterReload } from './ui/lang-state'; +import './styles.css'; + +const app = document.querySelector('#app'); + +if (!app) { + throw new Error('Missing #app root element.'); +} + +const appWrap = document.createElement('div'); +appWrap.className = 'app-wrap'; + +const shell = document.createElement('main'); +shell.className = 'app-shell'; + +const sidebar = document.createElement('aside'); +sidebar.className = 'sidebar'; + +const workspace = document.createElement('section'); +workspace.className = 'workspace'; + +const surface = document.createElement('div'); +surface.className = 'canvas-surface'; + +const rightPanel = document.createElement('aside'); +rightPanel.className = 'right-panel'; + +const statusToasts = createStatusToasts(); + +workspace.append(surface); +shell.append(sidebar, workspace, rightPanel); +appWrap.append(shell); +app.append(appWrap, statusToasts); + +const editor = new WebDraftEditor(surface, { + width: 900, + height: 620, + color: '#111111', + size: 10 +}); + +appWrap.prepend(createMenuBar(editor, statusToasts)); +sidebar.append(createToolbar(editor, statusToasts)); +rightPanel.append(createRightPanel(editor, statusToasts)); +editor.mount(); +void restoreStateAfterReload(editor); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..daa1173 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,1625 @@ +:root { + color: #f4f7fb; + background: #202327; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { + box-sizing: border-box; +} + +[hidden] { + display: none !important; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + overflow: hidden; + background: #202327; +} + +button, +input, +select { + font: inherit; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + +.app-wrap { + display: flex; + flex-direction: column; + width: 100%; + height: 100vh; + overflow: hidden; +} + +.app-shell { + display: grid; + grid-template-columns: 240px minmax(0, 1fr) 240px; + width: 100%; + min-width: 0; + flex: 1; + overflow: hidden; +} + +/* ── Menu bar ─────────────────────────────────────────── */ + +.menu-bar { + display: flex; + align-items: stretch; + height: 28px; + flex-shrink: 0; + background: #17191d; + border-bottom: 1px solid #343941; + padding: 0 4px; + user-select: none; + z-index: 100; +} + +.menu-lang-selector { + display: flex; + align-items: stretch; +} + +.menu-lang-btn { + font-size: 12px; + letter-spacing: 0.02em; +} + +.menu-lang-dropdown { + min-width: 100px; +} + +.menu-dropdown__item--active button { + color: #fff; + background: #2e6fb7; +} + +/* ── Zoom widget ─────────────────────────────────────────── */ + +.menu-zoom { + display: flex; + align-items: center; + gap: 1px; + margin-left: auto; + padding: 0 6px; +} + +.menu-zoom-btn, +.menu-zoom-label { + display: flex; + align-items: center; + justify-content: center; + height: 20px; + padding: 0 6px; + border: 1px solid transparent; + border-radius: 3px; + background: none; + color: #c8cdd4; + font-size: 12px; + cursor: pointer; + line-height: 1; + white-space: nowrap; +} + +.menu-zoom-btn { + min-width: 20px; + font-size: 15px; +} + +.menu-zoom-btn:hover, +.menu-zoom-label:hover { + background: #2c3038; + border-color: #3a4049; + color: #f4f7fb; +} + +.menu-zoom-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.menu-zoom-btn:disabled:hover { + background: none; + border-color: transparent; +} + +.menu-btn { + display: flex; + align-items: center; + padding: 0 10px; + height: 100%; + background: none; + border: none; + color: #c8cdd4; + font-size: 13px; + cursor: default; + border-radius: 3px; + white-space: nowrap; +} + +.menu-btn:hover, +.menu-btn--active { + background: #2c3038; + color: #f4f7fb; +} + +.menu-dropdown { + position: fixed; + z-index: 9999; + min-width: 210px; + margin: 0; + padding: 4px 0; + list-style: none; + background: #1e2126; + border: 1px solid #343941; + border-radius: 5px; + box-shadow: 0 8px 24px rgba(0,0,0,.45); +} + +.menu-dropdown__item button { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 5px 14px; + background: none; + border: none; + color: #c8cdd4; + font-size: 13px; + text-align: left; + cursor: default; + gap: 24px; +} + +.menu-dropdown__item button:hover { + background: #2e6fb7; + color: #fff; +} + +.menu-dropdown__item--disabled button { + opacity: .38; + pointer-events: none; +} + +.menu-dropdown__shortcut { + margin-left: auto; + font-size: 11px; + opacity: .55; + white-space: nowrap; +} + +.menu-dropdown__sep { + height: 1px; + margin: 4px 0; + background: #343941; +} + +/* ── Sidebar height adjustment ──────────────────────────── */ + +.sidebar { + min-width: 0; + height: 100%; + max-height: 100%; + overflow: auto; + border-right: 1px solid #343941; + background: #17191d; +} + +.right-panel { + min-width: 0; + height: 100%; + max-height: 100%; + overflow: auto; + border-left: 1px solid #343941; + background: #17191d; +} + +.workspace { + display: grid; + place-items: center; + width: 100%; + min-width: 0; + height: 100%; + overflow: auto; + padding: 32px; +} + +.canvas-surface { + position: relative; + flex-shrink: 0; + width: var(--canvas-width); + height: var(--canvas-height); + background-color: #f9fafb; + background-image: + linear-gradient(45deg, #d6dae0 25%, transparent 25%), + linear-gradient(-45deg, #d6dae0 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #d6dae0 75%), + linear-gradient(-45deg, transparent 75%, #d6dae0 75%); + background-position: 0 0, 0 10px, 10px -10px, -10px 0; + background-size: 20px 20px; + border: 1px solid #4b89ff; + box-shadow: 0 24px 80px rgb(0 0 0 / 30%); + overflow: visible; +} + +.shape-preview-layer, +.event-layer { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.drawing-layer { + position: absolute; + left: 0; + top: 0; + z-index: 1; + pointer-events: none; +} + +.drawing-layer--active { + outline: 1px dashed rgba(75, 137, 255, 0.7); +} + +.shape-preview-layer { + z-index: 5; + pointer-events: none; +} + +.event-layer { + z-index: 10; + touch-action: none; + cursor: crosshair; +} + +.text-input-layer { + position: absolute; + z-index: 20; + min-width: 120px; + min-height: 40px; + padding: 6px; + border: 1px solid #5b9cff; + border-radius: 4px; + outline: 2px solid rgb(91 156 255 / 20%); + background: rgb(255 255 255 / 82%); + caret-color: #111111; + line-height: 1.4; + word-break: break-word; + overflow-wrap: break-word; + white-space: pre-wrap; + overflow: hidden; +} + +.text-input-layer:focus { + outline: 2px solid rgb(91 156 255 / 40%); +} + +.text-toolbar { + position: absolute; + z-index: 30; + display: flex; + align-items: center; + gap: 2px; + padding: 4px 6px; + background: #1e2329; + border: 1px solid #3a4049; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.55); + user-select: none; + height: 36px; +} + +.text-toolbar-group { + display: flex; + align-items: center; + gap: 2px; +} + +.text-toolbar-sep { + width: 1px; + height: 20px; + background: #3a4049; + margin: 0 4px; + flex-shrink: 0; +} + +.text-toolbar-btn { + display: flex; + align-items: center; + justify-content: center; + min-width: 26px; + height: 26px; + padding: 0 5px; + border: 1px solid transparent; + border-radius: 4px; + background: none; + color: #c9d1dc; + font-size: 13px; + cursor: pointer; + line-height: 1; +} + +.text-toolbar-btn:hover { + background: #2d3340; + border-color: #3a4049; +} + +.text-toolbar-btn.is-active { + background: #1e3a5c; + border-color: #4b89ff; + color: #fff; +} + +.text-toolbar-select { + height: 26px; + padding: 0 4px; + border: 1px solid #3a4049; + border-radius: 4px; + background: #252b33; + color: #c9d1dc; + font-size: 12px; + cursor: pointer; +} + +.text-toolbar-size-select { + width: 56px; +} + +.text-toolbar-color { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border: 1px solid #3a4049; + border-radius: 4px; + background: #252b33; + cursor: pointer; + overflow: hidden; + padding: 2px; +} + +.text-toolbar-color input[type="color"] { + width: 20px; + height: 20px; + padding: 0; + border: none; + border-radius: 2px; + cursor: pointer; + background: none; +} + +.toolbar { + display: grid; + align-content: start; + gap: 12px; + padding: 14px; +} + +.toolbar h1 { + margin: 0; + padding: 0 4px 4px; + font-size: 18px; + font-weight: 700; +} + +.toolbar__section { + display: grid; + gap: 12px; + padding: 12px; + border: 1px solid #2f343d; + border-radius: 6px; + background: #1b1e24; +} + +.toolbar__stack { + display: grid; + gap: 8px; +} + +.toolbar__group { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; +} + +.tool-button, +.command-button { + min-height: 40px; + border: 1px solid #343941; + border-radius: 6px; + color: #f4f7fb; + background: #242832; + cursor: pointer; +} + +.tool-button { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 44px; +} + +.tool-button svg, +.command-button svg { + pointer-events: none; + flex-shrink: 0; +} + +.tool-button:hover, +.command-button:hover { + border-color: #5b9cff; +} + +.tool-button:disabled, +.command-button:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.tool-button:disabled:hover, +.command-button:disabled:hover { + border-color: #343941; +} + +.tool-button.is-active { + border-color: #5b9cff; + background: #173d76; +} + +.color-picker, +.checkbox-control, +.range-control { + display: grid; + gap: 8px; + color: #c9d1dc; + font-size: 13px; +} + +.checkbox-control { + grid-template-columns: auto 1fr; + align-items: center; + cursor: pointer; +} + +.checkbox-control input { + width: 16px; + height: 16px; + accent-color: #5b9cff; +} + +.color-picker { + grid-template-columns: 24px 1fr; + align-items: center; + cursor: pointer; + user-select: none; +} + +.color-picker__swatch { + width: 24px; + height: 24px; + border: 1px solid #59606d; + border-radius: 50%; + flex-shrink: 0; +} + +/* ---- Advanced color picker popup ---- */ + +.cp-backdrop { + position: fixed; + inset: 0; + z-index: 999; + background: rgba(0, 0, 0, 0.45); +} + +.cp-popup { + position: fixed; + z-index: 1000; + background: #1e2329; + border: 1px solid #3a4049; + border-radius: 6px; + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); + box-sizing: border-box; + user-select: none; +} + +/* Drag handle */ +.cp-drag-handle { + display: flex; + justify-content: center; + align-items: center; + height: 16px; + cursor: grab; + margin: -4px -4px 0; + border-radius: 5px 5px 0 0; + color: #59606d; + transition: color 0.15s; +} + +.cp-drag-handle:hover { + color: #c9d1dc; +} + +.cp-drag-handle:active { + cursor: grabbing; +} + +.cp-drag-grip { + font-size: 16px; + line-height: 1; + letter-spacing: 2px; + pointer-events: none; +} + +/* Mobile: centered modal */ +.cp-popup--mobile { + left: 50% !important; + top: 50% !important; + transform: translate(-50%, -50%); + max-width: min(520px, 96vw); + max-height: 90vh; + overflow-y: auto; +} + +.cp-popup--mobile .cp-drag-handle { + display: none; +} + +@media (max-width: 560px) { + .cp-popup--mobile .cp-middle { + flex-direction: column; + } + + .cp-popup--mobile .cp-right { + width: 100%; + } + + .cp-popup--mobile .cp-canvas { + width: min(220px, 70vw); + height: min(220px, 70vw); + } + + .cp-popup--mobile .cp-hue-bar { + height: min(220px, 70vw); + } + + .cp-popup--mobile .cp-wheel { + width: min(250px, 80vw); + height: min(250px, 80vw); + } +} + +/* Top bar */ +.cp-topbar { + display: flex; + align-items: center; + gap: 6px; +} + +.cp-btn-group { + display: flex; + gap: 2px; +} + +.cp-toggle-btn { + background: #252b33; + border: 1px solid #3a4049; + border-radius: 3px; + color: #7a8394; + font-size: 11px; + padding: 2px 7px; + cursor: pointer; +} + +.cp-toggle-btn.is-active { + background: #2d3a4a; + border-color: #5b9cff; + color: #c9d1dc; +} + +/* Middle: left + right */ +.cp-middle { + display: flex; + gap: 10px; +} + +/* Left: tabs */ +.cp-left { + display: flex; + flex-direction: column; + gap: 6px; +} + +.cp-tabs { + display: flex; + gap: 2px; +} + +.cp-tab-btn { + width: 30px; + height: 26px; + background: #252b33; + border: 1px solid #3a4049; + border-radius: 3px; + color: #7a8394; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 4px; +} + +.cp-tab-btn svg { + width: 16px; + height: 16px; + pointer-events: none; +} + +.cp-tab-btn.is-active { + background: #2d3a4a; + border-color: #5b9cff; + color: #c9d1dc; +} + +.cp-tab-content { + flex: 1; +} + +/* Canvas-based pickers */ +.cp-hsv-wrap { + display: flex; + gap: 6px; + align-items: flex-start; +} + +.cp-canvas { + display: block; + width: 220px; + height: 220px; + flex-shrink: 0; + border-radius: 2px; + cursor: crosshair; + touch-action: none; +} + +.cp-hue-bar { + display: block; + width: 14px; + height: 220px; + flex-shrink: 0; + border-radius: 2px; + cursor: ns-resize; + touch-action: none; +} + +.cp-wheel { + display: block; + width: 250px; + height: 250px; + border-radius: 50%; + cursor: crosshair; + touch-action: none; +} + +/* CMYK tab */ +.cp-cmyk-wrap { + display: flex; + flex-direction: column; + gap: 10px; + padding-top: 4px; + width: 240px; +} + +.cp-cmyk-row { + display: flex; + align-items: center; + gap: 6px; +} + +.cp-cmyk-label { + color: #7a8394; + font-size: 12px; + font-weight: 600; + width: 14px; + text-align: center; + flex-shrink: 0; +} + +.cp-cmyk-track { + flex: 1; + min-width: 0; + accent-color: #5b9cff; + cursor: pointer; +} + +.cp-cmyk-val { + width: 38px !important; + flex: none !important; + text-align: center; +} + +/* Right panel */ +.cp-right { + display: flex; + flex-direction: column; + gap: 5px; + width: 200px; + flex-shrink: 0; +} + +.cp-ch-row { + display: flex; + align-items: center; + gap: 5px; +} + +.cp-ch-label { + color: #7a8394; + font-size: 11px; + font-weight: 600; + width: 12px; + text-align: right; + flex-shrink: 0; +} + +.cp-ch-slider { + flex: 1; + min-width: 0; + height: 14px; + -webkit-appearance: none; + appearance: none; + border-radius: 3px; + background: linear-gradient(to right, var(--grad-from, #000), var(--grad-to, #fff)); + outline: none; + cursor: pointer; +} + +.cp-ch-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 10px; + height: 14px; + background: #fff; + border: 1px solid rgba(0,0,0,0.5); + border-radius: 2px; + cursor: pointer; +} + +.cp-ch-slider::-moz-range-thumb { + width: 10px; + height: 14px; + background: #fff; + border: 1px solid rgba(0,0,0,0.5); + border-radius: 2px; + cursor: pointer; +} + +.cp-ch-val { + color: #c9d1dc; + font-size: 11px; + width: 28px; + text-align: right; + flex-shrink: 0; +} + +.cp-icon-btn { + width: 18px; + height: 18px; + background: #252b33; + border: 1px solid #3a4049; + border-radius: 3px; + color: #c9d1dc; + font-size: 13px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; +} + +.cp-hex-row { + display: flex; + align-items: center; + gap: 5px; + margin-top: 2px; +} + +.cp-field-input { + flex: 1; + min-width: 0; + background: #12161b; + border: 1px solid #3a4049; + border-radius: 3px; + color: #c9d1dc; + font-size: 12px; + padding: 3px 5px; + outline: none; + box-sizing: border-box; +} + +.cp-field-input:focus { + border-color: #5b9cff; +} + +.cp-hex-input { + font-family: monospace; +} + +input[type="number"].cp-field-input { + -moz-appearance: textfield; +} + +input[type="number"].cp-field-input::-webkit-outer-spin-button, +input[type="number"].cp-field-input::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +/* Palette */ +.cp-palette { + display: flex; + align-items: flex-start; + gap: 4px; + flex-wrap: nowrap; + margin-top: 2px; +} + +.cp-palette-add { + width: 18px; + height: 18px; + background: #252b33; + border: 1px solid #3a4049; + border-radius: 3px; + color: #c9d1dc; + font-size: 14px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; +} + +.cp-palette-grid { + display: flex; + flex-wrap: wrap; + gap: 3px; + flex: 1; +} + +.cp-palette-swatch { + width: 16px; + height: 16px; + border: 1px solid #59606d; + border-radius: 2px; + cursor: pointer; + padding: 0; + flex-shrink: 0; +} + +.cp-palette-swatch:hover { + border-color: #c9d1dc; +} + +/* Bottom bar: current / previous */ +.cp-bottom-bar { + display: flex; + flex-direction: column; + gap: 3px; + border-top: 1px solid #2d3340; + padding-top: 6px; +} + +.cp-color-row { + display: flex; + align-items: center; + gap: 8px; +} + +.cp-color-row-label { + color: #7a8394; + font-size: 11px; + width: 56px; + flex-shrink: 0; +} + +.cp-color-strip { + flex: 1; + height: 18px; + border: 1px solid #3a4049; + border-radius: 3px; +} + +.cp-color-strip--old { + cursor: pointer; +} + +.cp-color-strip--old:hover { + border-color: #c9d1dc; +} + +.range-control { + grid-template-columns: 1fr auto; +} + +.range-control input { + grid-column: 1 / -1; + width: 100%; +} + +.command-button { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + width: 100%; + padding: 0 12px; +} + +.command-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} + +.command-grid .command-button { + min-width: 0; + padding: 0 8px; + overflow-wrap: anywhere; +} + +.field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.number-control, +.select-control { + min-width: 0; + min-height: 40px; + width: 100%; + border: 1px solid #343941; + border-radius: 6px; + color: #f4f7fb; + background: #242832; +} + +.number-control { + padding: 0 10px; +} + +.select-control { + padding: 0 8px; +} + +.field-grid .command-button { + grid-column: 1 / -1; +} + +.layers-panel { + display: grid; + gap: 10px; + min-width: 0; +} + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.panel-header h2 { + margin: 0; + color: #f4f7fb; + font-size: 13px; + font-weight: 700; +} + +.panel-actions { + display: flex; + gap: 6px; +} + +.icon-button { + display: grid; + place-items: center; + width: 28px; + height: 28px; + border: 1px solid #343941; + border-radius: 6px; + color: #f4f7fb; + background: #242832; + cursor: pointer; +} + +.icon-button:hover { + border-color: #5b9cff; +} + +.layer-list { + display: grid; + gap: 6px; +} + +.layer-item { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) 34px; + align-items: center; + min-height: 46px; + border: 1px solid #343941; + border-radius: 6px; + overflow: hidden; + background: #20242c; +} + +.layer-item.is-active { + border-color: #5b9cff; + background: #17263c; +} + +.layer-select, +.layer-preview, +.layer-name-input, +.layer-visibility { + min-height: 46px; + border: 0; + color: #dce5f2; + background: transparent; + cursor: pointer; +} + +.layer-preview { + position: relative; + display: grid; + place-items: center; + padding: 5px; + border-right: 1px solid #343941; +} + +.layer-preview img { + width: 32px; + height: 32px; + object-fit: contain; + background-image: + linear-gradient(45deg, #d6dae0 25%, transparent 25%), + linear-gradient(-45deg, #d6dae0 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #d6dae0 75%), + linear-gradient(-45deg, transparent 75%, #d6dae0 75%); + background-position: 0 0, 0 4px, 4px -4px, -4px 0; + background-size: 8px 8px; +} + +.layer-text-badge { + position: absolute; + bottom: 3px; + right: 3px; + font-size: 9px; + font-weight: bold; + line-height: 1; + padding: 1px 3px; + border-radius: 2px; + background: #5b9cff; + color: #fff; + pointer-events: none; +} + +.layer-name-input { + min-width: 0; + padding: 0 10px; + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + cursor: text; +} + +.layer-name-input:focus { + outline: 1px solid #5b9cff; + outline-offset: -1px; +} + +.layer-visibility { + border-left: 1px solid #343941; +} + +.status-toasts { + position: fixed; + right: 18px; + bottom: 18px; + z-index: 50; + display: grid; + justify-items: end; + width: min(360px, calc(100vw - 36px)); + pointer-events: none; +} + +.status-toast { + width: fit-content; + max-width: 100%; + padding: 12px 14px; + border: 1px solid #3d4654; + border-radius: 6px; + color: #f4f7fb; + background: #1b1e24; + box-shadow: 0 16px 42px rgb(0 0 0 / 35%); + font-size: 13px; + line-height: 1.35; +} + +.status-toast--success { + border-color: #2f8f5b; + background: #173425; +} + +.status-toast--error { + border-color: #b64d5a; + background: #3a1d24; +} + +@media (max-width: 760px) { + .app-shell { + grid-template-columns: 1fr; + grid-template-rows: auto minmax(0, 1fr) auto; + min-height: 100svh; + } + + .sidebar { + width: 100%; + min-height: 0; + max-height: none; + overflow: hidden; + border-right: 0; + border-bottom: 1px solid #343941; + } + + .right-panel { + min-height: 0; + max-height: none; + border-left: 0; + border-top: 1px solid #343941; + } + + .toolbar { + display: flex; + align-items: center; + width: 100%; + min-width: 0; + gap: 10px; + overflow-x: auto; + overflow-y: hidden; + padding: 10px; + scroll-snap-type: x proximity; + } + + .toolbar h1 { + flex: 0 0 auto; + padding: 0 8px 0 0; + } + + .toolbar__section { + flex: 0 0 min(280px, 82vw); + scroll-snap-align: start; + } + + .toolbar__group { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .workspace { + padding: 16px; + } + + .layers-panel { + min-width: 0; + } + + .status-toasts { + right: 10px; + bottom: 10px; + width: calc(100vw - 20px); + justify-items: stretch; + } + + .status-toast { + width: 100%; + } +} + +@media (max-width: 480px) { + .toolbar__section { + flex-basis: min(252px, 86vw); + } +} + +.camera-panel-backdrop { + position: fixed; + inset: 0; + z-index: 59; + background: rgb(0 0 0 / 50%); +} + +.camera-panel { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 60; + display: grid; + gap: 12px; + padding: 16px; + width: min(480px, 96vw); + background: #17191d; + border: 1px solid #343941; + border-radius: 8px; + box-shadow: 0 24px 80px rgb(0 0 0 / 60%); +} + +.camera-panel__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.camera-panel__title { + font-weight: 600; +} + +.camera-panel__close { + padding: 0 10px; + min-height: 32px; +} + +.camera-panel__canvas { + width: 100%; + aspect-ratio: 4 / 3; + background: #000; + border-radius: 4px; + display: block; +} + +.camera-panel__filters { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.camera-panel__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +/* ── Save dialog ────────────────────────────────────────── */ + +.save-dialog-backdrop { + position: fixed; + inset: 0; + z-index: 1000; + background: rgb(0 0 0 / 55%); +} + +.save-dialog { + position: fixed; + z-index: 1001; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 300px; + background: #1e2126; + border: 1px solid #343941; + border-radius: 8px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; + box-shadow: 0 16px 48px rgb(0 0 0 / 55%); +} + +.save-dialog__title { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #f4f7fb; +} + +.save-dialog__buttons { + display: flex; + flex-direction: column; + gap: 8px; +} + +.save-dialog__btn { + padding: 9px 14px; + border: 1px solid #3a4049; + border-radius: 5px; + background: #262b32; + color: #c8cdd4; + font-size: 13px; + cursor: pointer; + text-align: left; + transition: background 0.1s, color 0.1s; +} + +.save-dialog__btn:hover { + background: #2e3640; + color: #f4f7fb; +} + +.save-dialog__btn--primary { + background: #2e6fb7; + border-color: #2e6fb7; + color: #fff; +} + +.save-dialog__btn--primary:hover { + background: #3880cc; + border-color: #3880cc; +} + +.save-dialog__footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.save-dialog__remember { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: #8a9099; + cursor: pointer; + user-select: none; +} + +.save-dialog__remember input { + accent-color: #2e6fb7; + cursor: pointer; +} + +.save-dialog__cancel { + background: none; + border: none; + color: #8a9099; + font-size: 12px; + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; +} + +.save-dialog__cancel:hover { + color: #c8cdd4; + background: #2c3038; +} + +.resize-dlg-backdrop { + position: fixed; + inset: 0; + z-index: 1000; + background: rgb(0 0 0 / 55%); +} + +.resize-dlg { + position: fixed; + z-index: 1001; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 280px; + background: #1e2126; + border: 1px solid #343941; + border-radius: 8px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 14px; + box-shadow: 0 16px 48px rgb(0 0 0 / 55%); +} + +.resize-dlg__title { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #f4f7fb; +} + +.resize-dlg__field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.resize-dlg__label { + font-size: 12px; + color: #8a9099; + display: flex; + flex-direction: column; + gap: 4px; +} + +.resize-dlg__input { + padding: 6px 8px; + background: #262b32; + border: 1px solid #3a4049; + border-radius: 5px; + color: #f4f7fb; + font-size: 13px; + width: 100%; + box-sizing: border-box; +} + +.resize-dlg__input:focus { + outline: none; + border-color: #4b89ff; +} + +.resize-dlg__checkbox-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #8a9099; + cursor: pointer; +} + +.resize-dlg__actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 2px; +} + +.resize-dlg__btn { + padding: 6px 16px; + border-radius: 5px; + font-size: 13px; + cursor: pointer; + border: 1px solid transparent; +} + +.resize-dlg__btn--primary { + background: #2e6fb7; + border-color: #2e6fb7; + color: #fff; +} + +.resize-dlg__btn--primary:hover { + background: #3a7fc7; +} + +.resize-dlg__btn--secondary { + background: transparent; + border-color: #3a4049; + color: #8a9099; +} + +.resize-dlg__btn--secondary:hover { + color: #c8cdd4; + background: #2c3038; +} + +/* ── Keyboard shortcuts dialog ─────────────────────────────────────────── */ + +.shortcuts-dialog-backdrop { + position: fixed; + inset: 0; + z-index: 1000; + background: rgb(0 0 0 / 55%); +} + +.shortcuts-dialog { + position: fixed; + z-index: 1001; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 560px; + max-height: 80vh; + overflow-y: auto; + background: #1e2126; + border: 1px solid #343941; + border-radius: 8px; + padding: 20px 24px; + display: flex; + flex-direction: column; + gap: 20px; + box-shadow: 0 16px 48px rgb(0 0 0 / 55%); +} + +.shortcuts-dialog__header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.shortcuts-dialog__title { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #f4f7fb; +} + +.shortcuts-dialog__close { + background: none; + border: none; + color: #8a9099; + font-size: 20px; + line-height: 1; + cursor: pointer; + padding: 2px 7px; + border-radius: 4px; +} + +.shortcuts-dialog__close:hover { + color: #f4f7fb; + background: #2c3038; +} + +.shortcuts-dialog__body { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px 24px; +} + +.shortcuts-section__title { + margin: 0 0 8px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.07em; + color: #5a6370; +} + +.shortcuts-section__rows { + display: flex; + flex-direction: column; + gap: 5px; +} + +.shortcuts-row { + display: flex; + align-items: center; + gap: 10px; +} + +.shortcuts-row__key { + font-family: ui-monospace, monospace; + font-size: 11px; + background: #262b32; + border: 1px solid #3a4049; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 7px; + color: #c8cdd4; + white-space: nowrap; + min-width: 82px; + text-align: center; +} + +.shortcuts-row__desc { + font-size: 12px; + color: #8a9099; +} diff --git a/src/types/camera.ts b/src/types/camera.ts new file mode 100644 index 0000000..2ab4143 --- /dev/null +++ b/src/types/camera.ts @@ -0,0 +1,6 @@ +export type CameraFilters = { + sepia: boolean; + noise: boolean; + greyscale: boolean; + negative: boolean; +}; diff --git a/src/types/clipboard.ts b/src/types/clipboard.ts new file mode 100644 index 0000000..afdf36e --- /dev/null +++ b/src/types/clipboard.ts @@ -0,0 +1,6 @@ +import {SizeWithPosition} from './core.ts'; + +export type ClipboardSnapshot = { + bounds: SizeWithPosition; + imageData: ImageData; +}; diff --git a/src/types/colors.ts b/src/types/colors.ts new file mode 100644 index 0000000..5fc736f --- /dev/null +++ b/src/types/colors.ts @@ -0,0 +1,6 @@ +export type RgbaColor = { + red: number; + green: number; + blue: number; + alpha: number; +}; diff --git a/src/types/core.ts b/src/types/core.ts new file mode 100644 index 0000000..4a69256 --- /dev/null +++ b/src/types/core.ts @@ -0,0 +1,58 @@ +export enum Tool { + Select = 'select', + Pencil = 'pencil', + Eraser = 'eraser', + Sampler = 'sampler', + FillBucket = 'fill-bucket', + Web = 'web', + Rectangle = 'rectangle', + Ellipse = 'ellipse', + Text = 'text', +} + +export type Point = { + x: number; + y: number; +}; + +export type Size = { + width: number; + height: number; +}; + +export type SizeWithPosition = Size & Point; + +export type TextLayerData = { + html: string; + bounds: SizeWithPosition; + defaultFontSize: number; + defaultFontFamily: string; + defaultColor: string; + defaultAlign: CanvasTextAlign; +}; + +export type EditorOptions = { + width: number; + height: number; + color: string; + size: number; +}; + +export type EditorState = { + activeTool: Tool; + zoom: number; + color: string; + fillColor: string; + fillEnabled: boolean; + fillOpacity: number; + fillTolerance: number; + size: number; + canvasWidth: number; + canvasHeight: number; + webSensitivity: number; + shadowEnabled: boolean; + shadowColor: string; + shadowBlur: number; + shadowOffsetX: number; + shadowOffsetY: number; +}; diff --git a/src/types/geometry.ts b/src/types/geometry.ts new file mode 100644 index 0000000..d59cc5c --- /dev/null +++ b/src/types/geometry.ts @@ -0,0 +1,13 @@ +export type NaturalSize = { + naturalWidth: number; + naturalHeight: number; +}; + +export type ClippedPasteBounds = { + targetX: number; + targetY: number; + sourceX: number; + sourceY: number; + width: number; + height: number; +}; diff --git a/src/types/history.ts b/src/types/history.ts new file mode 100644 index 0000000..d84891f --- /dev/null +++ b/src/types/history.ts @@ -0,0 +1,26 @@ +import type {LayerManager} from '../core/layer-manager'; +import type {EditorState} from './core'; +import type {LayerDocumentSnapshot} from './layers'; + +export type HistoryEntry = { + before: TSnapshot; + after: TSnapshot; +}; + +export type HistorySnapshot = { + canvasWidth: number; + canvasHeight: number; + document: LayerDocumentSnapshot; +}; + +export type EditorServices = { + readonly state: EditorState; + readonly layerManager: LayerManager; + readonly previewContext: CanvasRenderingContext2D; + readonly previewCanvas: HTMLCanvasElement; + readonly root: HTMLElement; + captureSnapshot(): HistorySnapshot; + pushHistory(before: HistorySnapshot, after: HistorySnapshot): void; + clearPreview(): void; + dispatchChange(): void; +}; diff --git a/src/types/i18n.ts b/src/types/i18n.ts new file mode 100644 index 0000000..c8751f9 --- /dev/null +++ b/src/types/i18n.ts @@ -0,0 +1,189 @@ +export type Lang = 'pl' | 'en'; + +export type MenuTranslations = { + file: string; + edit: string; + image: string; + layer: string; + help: string; +}; + +export type FileTranslations = { + new: string; + open: string; + save: string; + importImage: string; + exportPng: string; + newConfirm: string; + savedOk: string; + openedOk: string; + importedOk: string; + exportedOk: string; + projectFilename: string; + imageFilename: string; +}; + +export type EditTranslations = { + undo: string; + redo: string; + cut: string; + copy: string; + paste: string; + clearLayer: string; +}; + +export type ImageTranslations = { + resizeCanvas: string; + invertColors: string; + mirrorH: string; + mirrorV: string; + rotateLeft: string; + rotateRight: string; + widthPrompt: string; + heightPrompt: string; + resizedOk: (w: number, h: number) => string; + applyToAllLayers: string; +}; + +export type LayerTranslations = { + new: string; + delete: string; + moveUp: string; + moveDown: string; + addedOk: string; + deletedOk: string; + resize: string; + layerResizedOk: (w: number, h: number) => string; +}; + +export type ToolbarTranslations = { + toolSelect: string; + toolPencil: string; + toolEraser: string; + toolSampler: string; + toolFillBucket: string; + toolWeb: string; + toolRectangle: string; + toolEllipse: string; + toolText: string; + invertColors: string; + rotateLeft: string; + rotateRight: string; + mirrorH: string; + mirrorV: string; + size: string; + webSensitivity: string; + color: string; + fillShapes: string; + fillColor: string; + fillOpacity: string; + bucketTolerance: string; + shadow: string; + shadowColor: string; + shadowBlur: string; + shadowX: string; + shadowY: string; + textFont: string; + textAlignment: string; + bold: string; + italic: string; + clear: string; + undo: string; + redo: string; + copy: string; + cut: string; + paste: string; + width: string; + height: string; + resizeCanvas: string; + uploadImage: string; + camera: string; + exportPng: string; + saveProject: string; + openProject: string; + pngExportedOk: string; + imageImportedOk: string; + canvasResizedOk: (w: number, h: number) => string; +}; + +export type LayersTranslations = { + title: string; + addLayer: string; + deleteLayer: string; + layerName: (suffix: string) => string; + moveLayerUp: string; + moveLayerDown: string; + selectLayer: (name: string) => string; + renameLayer: string; + hideLayer: string; + showLayer: string; + textLayer: string; +}; + +export type CameraTranslations = { + title: string; + titleWithLabel: (label: string) => string; + unavailable: string; + sepia: string; + noise: string; + greyscale: string; + negative: string; + snap: string; + snapToCanvas: string; + applyToCanvas: string; + saveToFile: string; + backToLive: string; + frameAdded: string; + cameraError: string; + snapFilename: string; +}; + +export type PickerTranslations = { + addColor: string; + restorePrevious: string; + newColor: string; + previousColor: string; + range100: string; + range255: string; +}; + +export type CommonTranslations = { + somethingWentWrong: string; + appTitle: string; +}; + +export type SaveDialogTranslations = { + title: string; + exportPng: string; + saveProject: string; + rememberChoice: string; + cancel: string; +}; + +export type HelpTranslations = { + keyboardShortcuts: string; + close: string; + sectionTools: string; + sectionView: string; + sectionCanvas: string; + sizeDecrease: string; + sizeIncrease: string; + zoomIn: string; + zoomOut: string; + zoomReset: string; +}; + +export type Translations = { + menu: MenuTranslations; + file: FileTranslations; + edit: EditTranslations; + image: ImageTranslations; + layer: LayerTranslations; + toolbar: ToolbarTranslations; + layers: LayersTranslations; + camera: CameraTranslations; + picker: PickerTranslations; + common: CommonTranslations; + saveDialog: SaveDialogTranslations; + help: HelpTranslations; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..b521050 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,13 @@ +export * from './core'; +export * from './layers'; +export * from './project'; +export * from './history'; +export * from './geometry'; +export * from './transforms'; +export * from './colors'; +export * from './ui'; +export * from './i18n'; +export * from './text'; +export * from './clipboard'; +export * from './camera'; +export * from './shortcuts'; diff --git a/src/types/layers.ts b/src/types/layers.ts new file mode 100644 index 0000000..f37cdae --- /dev/null +++ b/src/types/layers.ts @@ -0,0 +1,40 @@ +import type {TextLayerData} from './core'; + +export type Layer = { + id: string; + name: string; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + visible: boolean; + x: number; + y: number; + textData?: TextLayerData; +}; + +export type LayerSummary = { + id: string; + name: string; + active: boolean; + preview: string; + visible: boolean; + isTextLayer: boolean; +}; + +type LayerSnapshot = { + layerId: string; + imageData: ImageData; +}; + +type LayerSnapshotWithMetadata = LayerSnapshot & { + name: string; + visible: boolean; + x: number; + y: number; + textData?: TextLayerData; +}; + +export type LayerDocumentSnapshot = { + activeLayerId: string; + layerCount: number; + layers: LayerSnapshotWithMetadata[]; +}; diff --git a/src/types/project.ts b/src/types/project.ts new file mode 100644 index 0000000..23951d5 --- /dev/null +++ b/src/types/project.ts @@ -0,0 +1,24 @@ +import type {TextLayerData} from './core'; + +export type WdraftLayerMeta = { + id: string; + name: string; + visible: boolean; + width: number; + height: number; + textData?: TextLayerData; +}; + +export type WdraftFileMeta = { + version: number; + canvasWidth: number; + canvasHeight: number; + activeLayerId: string; + layerCount: number; + layers: WdraftLayerMeta[]; +}; + +export type ParsedWdraftFile = { + meta: WdraftFileMeta; + pngBuffers: Uint8Array[]; +}; diff --git a/src/types/shortcuts.ts b/src/types/shortcuts.ts new file mode 100644 index 0000000..deac9d8 --- /dev/null +++ b/src/types/shortcuts.ts @@ -0,0 +1,6 @@ +export type ShortcutOptions = { + openImagePicker: () => void; + exportImage: () => Promise; + onSave: () => void; + openShortcutsDialog: () => void; +}; diff --git a/src/types/text.ts b/src/types/text.ts new file mode 100644 index 0000000..0302f0b --- /dev/null +++ b/src/types/text.ts @@ -0,0 +1,9 @@ +export type StyledRun = { + text: string; + bold: boolean; + italic: boolean; + underline: boolean; + color: string; + fontSize: number; + fontFamily: string; +}; diff --git a/src/types/toolbar.ts b/src/types/toolbar.ts new file mode 100644 index 0000000..ab4416f --- /dev/null +++ b/src/types/toolbar.ts @@ -0,0 +1,12 @@ +import {Tool} from './core.ts'; + +export type ToolbarSection = { + element: HTMLElement; + sync: () => void; +}; + +export type ToolConfig = { + id: Tool; + label: string; + icon: string; +}; diff --git a/src/types/transforms.ts b/src/types/transforms.ts new file mode 100644 index 0000000..0fcc05e --- /dev/null +++ b/src/types/transforms.ts @@ -0,0 +1,8 @@ +export type PixelBuffer = { + width: number; + height: number; + data: Uint8ClampedArray; +}; + +export type RotationDirection = 'left' | 'right'; +export type MirrorAxis = 'horizontal' | 'vertical'; diff --git a/src/types/ui.ts b/src/types/ui.ts new file mode 100644 index 0000000..b87a01e --- /dev/null +++ b/src/types/ui.ts @@ -0,0 +1,49 @@ +export type StatusTone = 'info' | 'success' | 'error'; + +export type StatusReporter = { + show: (message: string, tone?: StatusTone) => void; +}; + +export type ColorPickerOptions = { + label: string; + value: string; + onChange: (color: string) => void; +}; + +export type ColorPickerControl = HTMLElement & { + setValue: (color: string) => void; +}; + +export type TextToolbarDefaults = { + fontSize: number; + fontFamily: string; + color: string; + align: CanvasTextAlign; +}; + +export type SaveAction = 'png' | 'project'; + +export type CheckboxControl = { + element: HTMLLabelElement; + setChecked: (checked: boolean) => void; +}; + +export type RangeControl = { + element: HTMLLabelElement; + setValue: (value: number) => void; +}; + +export type MenuItem = + | { type: 'action'; label: string; action: () => void; shortcut?: string; disabled?: () => boolean } + | { type: 'separator' }; + +export type Menu = { label: string; items: MenuItem[] }; + +export type ResizeDialogOptions = { + title: string; + width: number; + height: number; + checkboxLabel?: string; + checkboxChecked?: boolean; + onConfirm: (w: number, h: number, all: boolean) => void; +}; diff --git a/src/ui/camera-panel.ts b/src/ui/camera-panel.ts new file mode 100644 index 0000000..aa99070 --- /dev/null +++ b/src/ui/camera-panel.ts @@ -0,0 +1,274 @@ +import {t} from '../core/i18n'; +import type {WebDraftEditor} from '../core/webdraft-editor.ts'; +import type {CameraFilters, StatusReporter} from '../types'; + +function applyFilters(data: Uint8ClampedArray, filters: CameraFilters): void { + for (let i = 0; i < data.length; i += 4) { + let r = data[i]; + let g = data[i + 1]; + let b = data[i + 2]; + + if (filters.greyscale || filters.sepia) { + const avg = 0.3 * r + 0.59 * g + 0.11 * b; + r = filters.sepia ? avg + 100 : avg; + g = filters.sepia ? avg + 50 : avg; + b = filters.sepia ? avg : avg; + } + + if (filters.negative) { + r = 255 - r; + g = 255 - g; + b = 255 - b; + } + + if (filters.noise) { + const rand = (Math.random() - 0.5) * 100; + r = Math.max(0, Math.min(255, r + rand)); + g = Math.max(0, Math.min(255, g + rand)); + b = Math.max(0, Math.min(255, b + rand)); + } + + data[i] = r; + data[i + 1] = g; + data[i + 2] = b; + } +} + +function createFilterCheckbox(label: string, onChange: (checked: boolean) => void): HTMLLabelElement { + const el = document.createElement('label'); + el.className = 'checkbox-control'; + + const input = document.createElement('input'); + input.type = 'checkbox'; + input.addEventListener('change', () => onChange(input.checked)); + + const text = document.createElement('span'); + text.textContent = label; + el.append(input, text); + + return el; +} + +export function openCameraPanel(editor: WebDraftEditor, status: StatusReporter): void { + const mediaDevices = navigator.mediaDevices; + + if (!mediaDevices?.getUserMedia) { + status.show(t.camera.unavailable, 'error'); + return; + } + + const filters: CameraFilters = {sepia: false, noise: false, greyscale: false, negative: false}; + let stream: MediaStream | null = null; + let rafId = 0; + let isSnapped = false; + let snapshotCanvas: HTMLCanvasElement | null = null; + + const video = document.createElement('video'); + video.muted = true; + video.playsInline = true; + + const backdrop = document.createElement('div'); + backdrop.className = 'camera-panel-backdrop'; + + const panel = document.createElement('div'); + panel.className = 'camera-panel'; + + // Header + const header = document.createElement('div'); + header.className = 'camera-panel__header'; + + const title = document.createElement('span'); + title.className = 'camera-panel__title'; + title.textContent = t.camera.title; + + const closeBtn = document.createElement('button'); + closeBtn.type = 'button'; + closeBtn.className = 'camera-panel__close command-button'; + closeBtn.textContent = '✕'; + closeBtn.addEventListener('click', closePanel); + + header.append(title, closeBtn); + + // Preview canvas + const previewCanvas = document.createElement('canvas'); + previewCanvas.className = 'camera-panel__canvas'; + + // Filters + const filtersContainer = document.createElement('div'); + filtersContainer.className = 'camera-panel__filters'; + + const filterDefs: Array<{ label: string; key: keyof CameraFilters }> = [ + {label: t.camera.sepia, key: 'sepia'}, + {label: t.camera.noise, key: 'noise'}, + {label: t.camera.greyscale, key: 'greyscale'}, + {label: t.camera.negative, key: 'negative'}, + ]; + + for (const {label, key} of filterDefs) { + filtersContainer.append(createFilterCheckbox(label, (checked) => { + filters[key] = checked; + })); + } + + // Actions + const actionsContainer = document.createElement('div'); + actionsContainer.className = 'camera-panel__actions'; + + const snapBtn = document.createElement('button'); + snapBtn.type = 'button'; + snapBtn.className = 'command-button'; + snapBtn.textContent = t.camera.snap; + snapBtn.addEventListener('click', doSnap); + + const snapToCanvasBtn = document.createElement('button'); + snapToCanvasBtn.type = 'button'; + snapToCanvasBtn.className = 'command-button'; + snapToCanvasBtn.textContent = t.camera.snapToCanvas; + snapToCanvasBtn.addEventListener('click', doSnapToCanvas); + + const applyBtn = document.createElement('button'); + applyBtn.type = 'button'; + applyBtn.className = 'command-button'; + applyBtn.textContent = t.camera.applyToCanvas; + applyBtn.addEventListener('click', doApply); + + const saveBtn = document.createElement('button'); + saveBtn.type = 'button'; + saveBtn.className = 'command-button'; + saveBtn.textContent = t.camera.saveToFile; + saveBtn.addEventListener('click', doSave); + + const backBtn = document.createElement('button'); + backBtn.type = 'button'; + backBtn.className = 'command-button'; + backBtn.textContent = t.camera.backToLive; + backBtn.addEventListener('click', resumeLive); + + showLiveActions(); + + panel.append(header, previewCanvas, filtersContainer, actionsContainer); + document.body.append(backdrop, panel); + + // Keyboard: Escape closes panel + function onKeyDown(e: KeyboardEvent): void { + if (e.key === 'Escape') { + e.preventDefault(); + closePanel(); + } + } + + document.addEventListener('keydown', onKeyDown); + + // Start camera + void startCamera(); + + async function startCamera(): Promise { + try { + stream = await mediaDevices.getUserMedia({video: true}); + video.srcObject = stream; + + await new Promise((resolve) => { + video.addEventListener('loadedmetadata', () => resolve(), {once: true}); + void video.play(); + }); + + const track = stream.getVideoTracks()[0]; + if (track?.label) { + title.textContent = t.camera.titleWithLabel(track.label); + } + + previewCanvas.width = video.videoWidth; + previewCanvas.height = video.videoHeight; + + startLiveLoop(); + } catch (error) { + closePanel(); + status.show(error instanceof Error ? error.message : t.camera.cameraError, 'error'); + } + } + + function startLiveLoop(): void { + const ctx = previewCanvas.getContext('2d'); + if (!ctx) return; + + function frame(): void { + if (isSnapped) return; + ctx!.drawImage(video, 0, 0); + const hasActiveFilter = filters.sepia || filters.noise || filters.greyscale || filters.negative; + if (hasActiveFilter) { + const imageData = ctx!.getImageData(0, 0, previewCanvas.width, previewCanvas.height); + applyFilters(imageData.data, filters); + ctx!.putImageData(imageData, 0, 0); + } + rafId = requestAnimationFrame(frame); + } + + rafId = requestAnimationFrame(frame); + } + + function doSnap(): void { + cancelAnimationFrame(rafId); + isSnapped = true; + + snapshotCanvas = document.createElement('canvas'); + snapshotCanvas.width = previewCanvas.width; + snapshotCanvas.height = previewCanvas.height; + const snapCtx = snapshotCanvas.getContext('2d'); + snapCtx?.drawImage(previewCanvas, 0, 0); + + showSnappedActions(); + } + + function resumeLive(): void { + isSnapped = false; + snapshotCanvas = null; + showLiveActions(); + startLiveLoop(); + } + + function doApply(): void { + if (!snapshotCanvas) return; + editor.importCanvasAsLayer(snapshotCanvas); + status.show(t.camera.frameAdded, 'success'); + closePanel(); + } + + function doSave(): void { + if (!snapshotCanvas) return; + snapshotCanvas.toBlob((blob) => { + if (!blob) return; + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = t.camera.snapFilename; + a.click(); + URL.revokeObjectURL(url); + }); + } + + function doSnapToCanvas(): void { + const tmp = document.createElement('canvas'); + tmp.width = previewCanvas.width; + tmp.height = previewCanvas.height; + tmp.getContext('2d')?.drawImage(previewCanvas, 0, 0); + editor.importCanvasAsLayer(tmp); + status.show(t.camera.frameAdded, 'success'); + } + + function showLiveActions(): void { + actionsContainer.replaceChildren(snapBtn, snapToCanvasBtn); + } + + function showSnappedActions(): void { + actionsContainer.replaceChildren(applyBtn, saveBtn, backBtn); + } + + function closePanel(): void { + cancelAnimationFrame(rafId); + document.removeEventListener('keydown', onKeyDown); + stream?.getTracks().forEach((t) => t.stop()); + video.srcObject = null; + panel.remove(); + backdrop.remove(); + } +} diff --git a/src/ui/color-picker.ts b/src/ui/color-picker.ts new file mode 100644 index 0000000..020c99d --- /dev/null +++ b/src/ui/color-picker.ts @@ -0,0 +1,1013 @@ +import {t} from '../core/i18n'; +import type {ColorPickerControl, ColorPickerOptions} from '../types'; + +// Canvas sizes +const SB = 220; // SB square / HS rect +const HUE_BAR = 14; +const WHEEL = 250; +const WHEEL_OUTER = 118; +const WHEEL_INNER = 88; + +const PALETTE_KEY = 'webdraft-color-palette'; + +export function createColorPicker(options: ColorPickerOptions): ColorPickerControl { + let hsv = hexToHsv(options.value); + let currentHex = normalizeHex(options.value); + let oldColor = currentHex; + let activeTab = 0; + let rangeMax = 255; // 0..100 or 0..255 + let popup: HTMLElement | null = null; + let backdrop: HTMLElement | null = null; + + // --- Swatch row --- + const wrapper = document.createElement('div'); + wrapper.className = 'color-picker'; + + const swatch = document.createElement('span'); + swatch.className = 'color-picker__swatch'; + swatch.style.backgroundColor = currentHex; + + const labelEl = document.createElement('span'); + labelEl.textContent = options.label; + + wrapper.append(swatch, labelEl); + wrapper.addEventListener('pointerdown', (e) => { + e.stopPropagation(); + if (popup) { closePopup(); } else { openPopup(); } + }); + + // ------------------------------------------------------------------ open/close + + function isTouchDevice(): boolean { + return window.matchMedia('(pointer: coarse)').matches; + } + + function openPopup() { + oldColor = currentHex; + + popup = document.createElement('div'); + popup.className = 'cp-popup'; + + const handle = document.createElement('div'); + handle.className = 'cp-drag-handle'; + handle.innerHTML = ``; + + const top = buildTopBar(); + const middle = document.createElement('div'); + middle.className = 'cp-middle'; + + const left = document.createElement('div'); + left.className = 'cp-left'; + left.append(buildTabIcons(), buildTabContent()); + + const right = buildRightPanel(); + middle.append(left, right); + + const bottom = buildBottom(); + + popup.append(handle, top, middle, bottom); + + if (isTouchDevice()) { + backdrop = document.createElement('div'); + backdrop.className = 'cp-backdrop'; + backdrop.addEventListener('pointerdown', () => closePopup()); + document.body.append(backdrop); + popup.classList.add('cp-popup--mobile'); + } else { + makeDraggable(handle, popup); + } + + document.body.append(popup); + positionPopup(); + switchTab(activeTab, left.querySelector('.cp-tab-content')!); + + document.addEventListener('pointerdown', onOutsideClick); + document.addEventListener('keydown', onEscape); + } + + function closePopup() { + popup?.remove(); + backdrop?.remove(); + popup = null; + backdrop = null; + document.removeEventListener('pointerdown', onOutsideClick); + document.removeEventListener('keydown', onEscape); + } + + function onOutsideClick(e: PointerEvent) { + if (popup && !popup.contains(e.target as Node) && !wrapper.contains(e.target as Node) && e.target !== backdrop) { + closePopup(); + } + } + + function onEscape(e: KeyboardEvent) { + if (e.key === 'Escape') closePopup(); + } + + function positionPopup() { + if (!popup) return; + if (isTouchDevice()) return; // mobile is centered via CSS + const rect = wrapper.getBoundingClientRect(); + const pw = popup.offsetWidth || 510; + const ph = popup.offsetHeight || 340; + let left = rect.right + 8; + let top = rect.top; + if (left + pw > window.innerWidth) left = rect.left - pw - 8; + if (left < 8) left = 8; + if (top + ph > window.innerHeight) top = window.innerHeight - ph - 8; + if (top < 8) top = 8; + popup.style.left = `${left}px`; + popup.style.top = `${top}px`; + } + + function makeDraggable(handle: HTMLElement, target: HTMLElement) { + let dragging = false; + let startX = 0, startY = 0, startLeft = 0, startTop = 0; + + handle.addEventListener('pointerdown', (e) => { + if (e.button !== 0) return; + dragging = true; + handle.setPointerCapture(e.pointerId); + startX = e.clientX; + startY = e.clientY; + startLeft = parseInt(target.style.left, 10) || 0; + startTop = parseInt(target.style.top, 10) || 0; + e.stopPropagation(); + }); + + handle.addEventListener('pointermove', (e) => { + if (!dragging) return; + const newLeft = Math.max(0, Math.min(window.innerWidth - target.offsetWidth, startLeft + e.clientX - startX)); + const newTop = Math.max(0, Math.min(window.innerHeight - target.offsetHeight, startTop + e.clientY - startY)); + target.style.left = `${newLeft}px`; + target.style.top = `${newTop}px`; + }); + + handle.addEventListener('pointerup', () => { dragging = false; }); + } + + // ------------------------------------------------------------------ top bar + + function buildTopBar(): HTMLElement { + const bar = document.createElement('div'); + bar.className = 'cp-topbar'; + + const rangeGroup = document.createElement('div'); + rangeGroup.className = 'cp-btn-group'; + const btn100 = makeToggleBtn(t.picker.range100, rangeMax === 100, () => { rangeMax = 100; syncRangeButtons(); syncRightPanel(); }); + const btn255 = makeToggleBtn(t.picker.range255, rangeMax === 255, () => { rangeMax = 255; syncRangeButtons(); syncRightPanel(); }); + + rangeGroup.append(btn100, btn255); + bar.append(rangeGroup); + + function syncRangeButtons() { + btn100.classList.toggle('is-active', rangeMax === 100); + btn255.classList.toggle('is-active', rangeMax === 255); + } + + return bar; + } + + // ------------------------------------------------------------------ tab icons + + const TAB_ICONS = [ + // HSV square + ` + + + `, + // Color wheel + ` + + + `, + // HS rectangle + ` + + + `, + // CMYK sliders + ` + + + + `, + ]; + + function buildTabIcons(): HTMLElement { + const bar = document.createElement('div'); + bar.className = 'cp-tabs'; + TAB_ICONS.forEach((svg, i) => { + const btn = document.createElement('button'); + btn.className = 'cp-tab-btn' + (i === activeTab ? ' is-active' : ''); + btn.innerHTML = svg; + btn.addEventListener('pointerdown', (e) => { e.stopPropagation(); }); + btn.addEventListener('click', () => { + activeTab = i; + bar.querySelectorAll('.cp-tab-btn').forEach((b, j) => b.classList.toggle('is-active', j === i)); + const content = popup!.querySelector('.cp-tab-content') as HTMLElement; + switchTab(i, content); + }); + bar.append(btn); + }); + return bar; + } + + function buildTabContent(): HTMLElement { + const content = document.createElement('div'); + content.className = 'cp-tab-content'; + return content; + } + + function switchTab(tab: number, content: HTMLElement) { + content.innerHTML = ''; + if (tab === 0) content.append(buildHsvTab()); + else if (tab === 1) content.append(buildWheelTab()); + else if (tab === 2) content.append(buildHsRectTab()); + else content.append(buildCmykTab()); + } + + // ------------------------------------------------------------------ Tab 1: HSV square + bar + + function buildHsvTab(): HTMLElement { + const wrap = document.createElement('div'); + wrap.className = 'cp-hsv-wrap'; + + const sbCanvas = document.createElement('canvas'); + sbCanvas.className = 'cp-canvas'; + sbCanvas.width = SB; + sbCanvas.height = SB; + + const hueCanvas = document.createElement('canvas'); + hueCanvas.className = 'cp-hue-bar'; + hueCanvas.width = HUE_BAR; + hueCanvas.height = SB; + + wrap.append(sbCanvas, hueCanvas); + + drawSb(sbCanvas); + drawHueBar(hueCanvas); + bindSbDrag(sbCanvas); + bindHueBarDrag(hueCanvas, sbCanvas); + + return wrap; + } + + function drawSb(canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d')!; + const W = canvas.width, H = canvas.height; + ctx.fillStyle = `hsl(${hsv.h}, 100%, 50%)`; + ctx.fillRect(0, 0, W, H); + const wg = ctx.createLinearGradient(0, 0, W, 0); + wg.addColorStop(0, 'rgba(255,255,255,1)'); + wg.addColorStop(1, 'rgba(255,255,255,0)'); + ctx.fillStyle = wg; + ctx.fillRect(0, 0, W, H); + const bg = ctx.createLinearGradient(0, 0, 0, H); + bg.addColorStop(0, 'rgba(0,0,0,0)'); + bg.addColorStop(1, 'rgba(0,0,0,1)'); + ctx.fillStyle = bg; + ctx.fillRect(0, 0, W, H); + const cx = hsv.s * W, cy = (1 - hsv.v) * H; + ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(0,0,0,0.6)'; ctx.lineWidth = 2; ctx.stroke(); + ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); + } + + function drawHueBar(canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d')!; + const W = canvas.width, H = canvas.height; + const g = ctx.createLinearGradient(0, 0, 0, H); + ['#f00','#ff0','#0f0','#0ff','#00f','#f0f','#f00'].forEach((c, i) => g.addColorStop(i / 6, c)); + ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); + const cy = (hsv.h / 360) * H; + ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, cy - 2, W, 4); + ctx.fillStyle = '#fff'; ctx.fillRect(0, cy - 1, W, 2); + } + + function bindSbDrag(canvas: HTMLCanvasElement) { + let down = false; + canvas.addEventListener('pointerdown', (e) => { down = true; canvas.setPointerCapture(e.pointerId); pick(e); }); + canvas.addEventListener('pointermove', (e) => { if (down) pick(e); }); + canvas.addEventListener('pointerup', () => { down = false; }); + function pick(e: PointerEvent) { + const r = canvas.getBoundingClientRect(); + const s = clamp01((e.clientX - r.left) / SB); + const v = clamp01(1 - (e.clientY - r.top) / SB); + hsv = {h: hsv.h, s, v}; + applyHsv(); + drawSb(canvas); + } + } + + function bindHueBarDrag(hueCanvas: HTMLCanvasElement, sbCanvas: HTMLCanvasElement) { + let down = false; + hueCanvas.addEventListener('pointerdown', (e) => { down = true; hueCanvas.setPointerCapture(e.pointerId); pick(e); }); + hueCanvas.addEventListener('pointermove', (e) => { if (down) pick(e); }); + hueCanvas.addEventListener('pointerup', () => { down = false; }); + function pick(e: PointerEvent) { + const r = hueCanvas.getBoundingClientRect(); + hsv = {h: clamp(((e.clientY - r.top) / SB) * 360, 0, 360), s: hsv.s, v: hsv.v}; + applyHsv(); + drawSb(sbCanvas); + drawHueBar(hueCanvas); + } + } + + // ------------------------------------------------------------------ Tab 2: Color wheel + + function buildWheelTab(): HTMLElement { + const canvas = document.createElement('canvas'); + canvas.className = 'cp-wheel'; + canvas.width = WHEEL; + canvas.height = WHEEL; + drawWheel(canvas); + bindWheelDrag(canvas); + return canvas; + } + + function drawWheel(canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d')!; + const cx = WHEEL / 2, cy = WHEEL / 2; + ctx.clearRect(0, 0, WHEEL, WHEEL); + + // 1. Triangle first (via offscreen canvas so transparent pixels don't overwrite) + const tri = triangleVertices(cx, cy, hsv.h); + drawTriangle(ctx, tri); + + // 2. Hue ring on top, clipped to annulus (no destination-out needed) + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, WHEEL_OUTER, 0, Math.PI * 2, false); + ctx.arc(cx, cy, WHEEL_INNER, 0, Math.PI * 2, true); + ctx.clip('evenodd'); + for (let a = 0; a < 360; a++) { + const s1 = (a - 0.5) * Math.PI / 180; + const s2 = (a + 1.5) * Math.PI / 180; + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.arc(cx, cy, WHEEL_OUTER, s1, s2); + ctx.closePath(); + ctx.fillStyle = `hsl(${a}, 100%, 50%)`; + ctx.fill(); + } + ctx.restore(); + + // Ring cursor + const ra = (hsv.h - 90) * Math.PI / 180; + const rm = (WHEEL_OUTER + WHEEL_INNER) / 2; + const rx = cx + rm * Math.cos(ra), ry = cy + rm * Math.sin(ra); + ctx.beginPath(); ctx.arc(rx, ry, 6, 0, Math.PI * 2); + ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); + ctx.beginPath(); ctx.arc(rx, ry, 6, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(0,0,0,0.4)'; ctx.lineWidth = 1; ctx.stroke(); + + // Triangle cursor + const {vHue, vBlack, vWhite} = tri; + const px = hsv.v * (hsv.s * vHue.x + (1 - hsv.s) * vWhite.x) + (1 - hsv.v) * vBlack.x; + const py = hsv.v * (hsv.s * vHue.y + (1 - hsv.s) * vWhite.y) + (1 - hsv.v) * vBlack.y; + ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(0,0,0,0.6)'; ctx.lineWidth = 2; ctx.stroke(); + ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI * 2); + ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); + } + + type Vec2 = {x: number; y: number}; + type TriVerts = {vHue: Vec2; vBlack: Vec2; vWhite: Vec2}; + + function triangleVertices(cx: number, cy: number, hue: number): TriVerts { + const r = WHEEL_INNER - 4; + const angle = (hue - 90) * Math.PI / 180; + const vHue = {x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle)}; + const vBlack= {x: cx + r * Math.cos(angle + (2 * Math.PI / 3)), y: cy + r * Math.sin(angle + (2 * Math.PI / 3))}; + const vWhite= {x: cx + r * Math.cos(angle - (2 * Math.PI / 3)), y: cy + r * Math.sin(angle - (2 * Math.PI / 3))}; + return {vHue, vBlack, vWhite}; + } + + function drawTriangle(ctx: CanvasRenderingContext2D, {vHue, vBlack, vWhite}: TriVerts) { + const [r, g, b] = hsvToRgb(hsv.h, 1, 1); + const bounds = { + minX: Math.floor(Math.min(vHue.x, vBlack.x, vWhite.x)), + minY: Math.floor(Math.min(vHue.y, vBlack.y, vWhite.y)), + maxX: Math.ceil(Math.max(vHue.x, vBlack.x, vWhite.x)), + maxY: Math.ceil(Math.max(vHue.y, vBlack.y, vWhite.y)), + }; + const W = bounds.maxX - bounds.minX + 1; + const H = bounds.maxY - bounds.minY + 1; + + // Render into offscreen canvas so transparent pixels don't overwrite the main canvas + const offscreen = document.createElement('canvas'); + offscreen.width = W; + offscreen.height = H; + const off = offscreen.getContext('2d')!; + const img = off.createImageData(W, H); + + for (let py = 0; py < H; py++) { + for (let px = 0; px < W; px++) { + const cx2 = bounds.minX + px, cy2 = bounds.minY + py; + const {w0, w1, w2} = bary({x: cx2, y: cy2}, vHue, vBlack, vWhite); + if (w0 < -0.01 || w1 < -0.01 || w2 < -0.01) continue; + const wh = clamp01(w0), wb = clamp01(w1), ww = clamp01(w2); + void wb; + const idx = (py * W + px) * 4; + img.data[idx] = Math.round(wh * r + ww * 255); + img.data[idx+1] = Math.round(wh * g + ww * 255); + img.data[idx+2] = Math.round(wh * b + ww * 255); + img.data[idx+3] = 255; + } + } + off.putImageData(img, 0, 0); + ctx.drawImage(offscreen, bounds.minX, bounds.minY); + } + + function bary(p: Vec2, a: Vec2, b: Vec2, c: Vec2) { + const denom = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y); + const w0 = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / denom; + const w1 = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / denom; + return {w0, w1, w2: 1 - w0 - w1}; + } + + function bindWheelDrag(canvas: HTMLCanvasElement) { + let down = false; + let mode: 'ring' | 'tri' | null = null; + const cx = WHEEL / 2, cy = WHEEL / 2; + + canvas.addEventListener('pointerdown', (e) => { + down = true; canvas.setPointerCapture(e.pointerId); + const r = canvas.getBoundingClientRect(); + const px = e.clientX - r.left - cx, py = e.clientY - r.top - cy; + const dist = Math.sqrt(px * px + py * py); + mode = dist >= WHEEL_INNER ? 'ring' : 'tri'; + pick(e); + }); + canvas.addEventListener('pointermove', (e) => { if (down) pick(e); }); + canvas.addEventListener('pointerup', () => { down = false; mode = null; }); + + function pick(e: PointerEvent) { + const r = canvas.getBoundingClientRect(); + const px = e.clientX - r.left, py = e.clientY - r.top; + if (mode === 'ring') { + const angle = Math.atan2(py - cy, px - cx) * 180 / Math.PI + 90; + hsv = {h: (angle + 360) % 360, s: hsv.s, v: hsv.v}; + } else if (mode === 'tri') { + const tri = triangleVertices(cx, cy, hsv.h); + const {w0, w1, w2} = bary({x: px, y: py}, tri.vHue, tri.vBlack, tri.vWhite); + const wh = clamp01(w0), wb = clamp01(w1), ww = clamp01(w2); + const sum = wh + wb + ww; + const nhue = wh / sum, nblack = wb / sum, nwhite = ww / sum; + const v = clamp01(nhue + nwhite); + const s = v > 0 ? clamp01(nhue / v) : 0; + hsv = {h: hsv.h, s, v}; + } + applyHsv(); + drawWheel(canvas); + } + } + + // ------------------------------------------------------------------ Tab 3: HS rect + V bar + + function buildHsRectTab(): HTMLElement { + const wrap = document.createElement('div'); + wrap.className = 'cp-hsv-wrap'; + + const hsCanvas = document.createElement('canvas'); + hsCanvas.className = 'cp-canvas'; + hsCanvas.width = SB; + hsCanvas.height = SB; + + const vCanvas = document.createElement('canvas'); + vCanvas.className = 'cp-hue-bar'; + vCanvas.width = HUE_BAR; + vCanvas.height = SB; + + wrap.append(hsCanvas, vCanvas); + drawHsRect(hsCanvas); + drawVBar(vCanvas); + bindHsRectDrag(hsCanvas); + bindVBarDrag(vCanvas, hsCanvas); + + return wrap; + } + + function drawHsRect(canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d')!; + const W = canvas.width, H = canvas.height; + const img = ctx.createImageData(W, H); + + for (let py = 0; py < H; py++) { + for (let px = 0; px < W; px++) { + const h = (px / W) * 360; + const s = 1 - py / H; + const [r, g, b] = hsvToRgb(h, s, hsv.v); + const idx = (py * W + px) * 4; + img.data[idx] = r; img.data[idx+1] = g; img.data[idx+2] = b; img.data[idx+3] = 255; + } + } + ctx.putImageData(img, 0, 0); + + const cx = (hsv.h / 360) * W, cy = (1 - hsv.s) * H; + ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(0,0,0,0.6)'; ctx.lineWidth = 2; ctx.stroke(); + ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); + } + + function drawVBar(canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d')!; + const W = canvas.width, H = canvas.height; + const g = ctx.createLinearGradient(0, 0, 0, H); + g.addColorStop(0, '#fff'); g.addColorStop(1, '#000'); + ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); + const cy = (1 - hsv.v) * H; + ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, cy - 2, W, 4); + ctx.fillStyle = '#fff'; ctx.fillRect(0, cy - 1, W, 2); + } + + function bindHsRectDrag(canvas: HTMLCanvasElement) { + let down = false; + canvas.addEventListener('pointerdown', (e) => { down = true; canvas.setPointerCapture(e.pointerId); pick(e); }); + canvas.addEventListener('pointermove', (e) => { if (down) pick(e); }); + canvas.addEventListener('pointerup', () => { down = false; }); + function pick(e: PointerEvent) { + const r = canvas.getBoundingClientRect(); + const h = clamp(((e.clientX - r.left) / SB) * 360, 0, 360); + const s = clamp01(1 - (e.clientY - r.top) / SB); + hsv = {h, s, v: hsv.v}; + applyHsv(); + drawHsRect(canvas); + } + } + + function bindVBarDrag(vCanvas: HTMLCanvasElement, hsCanvas: HTMLCanvasElement) { + let down = false; + vCanvas.addEventListener('pointerdown', (e) => { down = true; vCanvas.setPointerCapture(e.pointerId); pick(e); }); + vCanvas.addEventListener('pointermove', (e) => { if (down) pick(e); }); + vCanvas.addEventListener('pointerup', () => { down = false; }); + function pick(e: PointerEvent) { + const r = vCanvas.getBoundingClientRect(); + hsv = {h: hsv.h, s: hsv.s, v: clamp01(1 - (e.clientY - r.top) / SB)}; + applyHsv(); + drawHsRect(hsCanvas); + drawVBar(vCanvas); + } + } + + // ------------------------------------------------------------------ Tab 4: CMYK sliders + + function buildCmykTab(): HTMLElement { + const wrap = document.createElement('div'); + wrap.className = 'cp-cmyk-wrap'; + + const [r, g, b] = hsvToRgb(hsv.h, hsv.s, hsv.v); + let {c, m, y, k} = rgbToCmyk(r, g, b); + + const sliders: HTMLElement[] = []; + + function rebuild() { + const [r2, g2, b2] = cmykToRgb(c, m, y, k); + const [h, s, v] = rgbToHsv(r2, g2, b2); + hsv = {h, s, v}; + applyHsv(); + // refresh slider backgrounds + updateCmykBgs(); + } + + function updateCmykBgs() { + const [r2, g2, b2] = hsvToRgb(hsv.h, hsv.s, hsv.v); + const cmyk2 = rgbToCmyk(r2, g2, b2); + c = cmyk2.c; m = cmyk2.m; y = cmyk2.y; k = cmyk2.k; + + const entries: [string, number, (v: number) => void][] = [ + ['C', c, (v) => { c = v; rebuild(); }], + ['M', m, (v) => { m = v; rebuild(); }], + ['Y', y, (v) => { y = v; rebuild(); }], + ['K', k, (v) => { k = v; rebuild(); }], + ]; + sliders.forEach((row, i) => { + const [, val,] = entries[i]; + const inp = row.querySelector('input[type=range]') as HTMLInputElement; + const num = row.querySelector('.cp-cmyk-val') as HTMLInputElement; + if (inp) inp.value = String(Math.round(val)); + if (num) num.value = String(Math.round(val)); + }); + } + + const entries: [string, number, (v: number) => void][] = [ + ['C', c, (v) => { c = v; rebuild(); }], + ['M', m, (v) => { m = v; rebuild(); }], + ['Y', y, (v) => { y = v; rebuild(); }], + ['K', k, (v) => { k = v; rebuild(); }], + ]; + + for (const [label, val, onChange] of entries) { + const row = document.createElement('div'); + row.className = 'cp-cmyk-row'; + + const lbl = document.createElement('span'); + lbl.className = 'cp-cmyk-label'; + lbl.textContent = label; + + const track = document.createElement('input'); + track.type = 'range'; + track.min = '0'; track.max = '100'; track.step = '1'; + track.value = String(Math.round(val)); + track.className = 'cp-cmyk-track'; + + const num = document.createElement('input'); + num.type = 'number'; + num.min = '0'; num.max = '100'; + num.value = String(Math.round(val)); + num.className = 'cp-cmyk-val cp-field-input'; + + const minus = makeIconBtn('−', () => { track.value = String(Math.max(0, +track.value - 1)); onChange(+track.value); num.value = track.value; }); + const plus = makeIconBtn('+', () => { track.value = String(Math.min(100, +track.value + 1)); onChange(+track.value); num.value = track.value; }); + + track.addEventListener('input', () => { onChange(+track.value); num.value = track.value; }); + num.addEventListener('input', () => { const v = clamp(+num.value, 0, 100); onChange(v); track.value = String(v); }); + num.addEventListener('pointerdown', (e) => e.stopPropagation()); + + row.append(lbl, track, num, minus, plus); + sliders.push(row); + wrap.append(row); + } + + return wrap; + } + + // ------------------------------------------------------------------ Right panel + + let rgbSliderEls: {label: string; input: HTMLInputElement; valEl: HTMLElement}[] = []; + let hexInputEl: HTMLInputElement | null = null; + let swatchNewEl: HTMLElement | null = null; + let swatchOldEl: HTMLElement | null = null; + let currentBarEl: HTMLElement | null = null; + + function buildRightPanel(): HTMLElement { + const panel = document.createElement('div'); + panel.className = 'cp-right'; + + // RGB sliders + const channels = ['R', 'G', 'B']; + rgbSliderEls = channels.map((ch) => { + const row = document.createElement('div'); + row.className = 'cp-ch-row'; + const lbl = document.createElement('span'); + lbl.className = 'cp-ch-label'; + lbl.textContent = ch; + const inp = document.createElement('input'); + inp.type = 'range'; + inp.min = '0'; inp.max = '255'; inp.step = '1'; + inp.className = 'cp-ch-slider'; + const val = document.createElement('span'); + val.className = 'cp-ch-val'; + const minus = makeIconBtn('−', () => { inp.value = String(Math.max(0, +inp.value - 1)); onRgbSlider(); }); + const plus = makeIconBtn('+', () => { inp.value = String(Math.min(255, +inp.value + 1)); onRgbSlider(); }); + inp.addEventListener('input', onRgbSlider); + row.append(lbl, inp, val, minus, plus); + panel.append(row); + return {label: ch, input: inp, valEl: val}; + }); + + function onRgbSlider() { + const r = +rgbSliderEls[0].input.value; + const g = +rgbSliderEls[1].input.value; + const b = +rgbSliderEls[2].input.value; + const [h, s, v] = rgbToHsv(r, g, b); + hsv = {h, s, v}; + applyHsvSilentSliders(); + } + + // Hex row + const hexRow = document.createElement('div'); + hexRow.className = 'cp-hex-row'; + const hexLbl = document.createElement('span'); + hexLbl.className = 'cp-ch-label'; + hexLbl.textContent = '#'; + hexInputEl = document.createElement('input'); + hexInputEl.type = 'text'; + hexInputEl.className = 'cp-hex-input cp-field-input'; + hexInputEl.maxLength = 7; + hexInputEl.value = currentHex; + hexInputEl.spellcheck = false; + hexInputEl.addEventListener('input', () => { + const parsed = hexToRgb(hexInputEl!.value); + if (parsed) { + const [h, s, v] = rgbToHsv(...parsed); + hsv = {h, s, v}; + applyHsvSilentHex(); + } + }); + hexInputEl.addEventListener('pointerdown', (e) => e.stopPropagation()); + hexInputEl.addEventListener('keydown', (e) => { if (e.key === 'Escape') closePopup(); }); + hexRow.append(hexLbl, hexInputEl); + panel.append(hexRow); + + // Swatches palette + panel.append(buildPalette()); + + syncRightPanel(); + return panel; + } + + function syncRightPanel() { + if (!popup) return; + const [r, g, b] = hsvToRgb(hsv.h, hsv.s, hsv.v); + rgbSliderEls.forEach(({input, valEl}, i) => { + const val = [r, g, b][i]; + const displayed = rangeMax === 100 ? Math.round((val / 255) * 100) : val; + input.value = String(val); + valEl.textContent = String(displayed); + // Gradient background + const others = [r, g, b].slice(); + const lo = others.map((v2, j) => j === i ? 0 : v2); + const hi = others.map((v2, j) => j === i ? 255 : v2); + input.style.setProperty('--grad-from', `rgb(${lo.join(',')})`); + input.style.setProperty('--grad-to', `rgb(${hi.join(',')})`); + }); + if (hexInputEl) hexInputEl.value = currentHex; + if (swatchNewEl) swatchNewEl.style.backgroundColor = currentHex; + if (currentBarEl) currentBarEl.style.backgroundColor = currentHex; + updateActiveTabCanvas(); + } + + function updateActiveTabCanvas() { + if (!popup) return; + const content = popup.querySelector('.cp-tab-content') as HTMLElement | null; + if (!content) return; + if (activeTab === 0) { + const sb = content.querySelector('.cp-canvas') as HTMLCanvasElement | null; + const hb = content.querySelector('.cp-hue-bar') as HTMLCanvasElement | null; + if (sb) drawSb(sb); + if (hb) drawHueBar(hb); + } else if (activeTab === 1) { + const wh = content.querySelector('.cp-wheel') as HTMLCanvasElement | null; + if (wh) drawWheel(wh); + } else if (activeTab === 2) { + const hs = content.querySelector('.cp-canvas') as HTMLCanvasElement | null; + const vb = content.querySelector('.cp-hue-bar') as HTMLCanvasElement | null; + if (hs) drawHsRect(hs); + if (vb) drawVBar(vb); + } + } + + function buildPalette(): HTMLElement { + const wrap = document.createElement('div'); + wrap.className = 'cp-palette'; + + const addBtn = document.createElement('button'); + addBtn.className = 'cp-palette-add'; + addBtn.textContent = '+'; + addBtn.title = t.picker.addColor; + addBtn.addEventListener('click', () => { + const palette = loadPalette(); + if (!palette.includes(currentHex)) { + palette.unshift(currentHex); + if (palette.length > 24) palette.pop(); + savePalette(palette); + } + renderSwatches(); + }); + addBtn.addEventListener('pointerdown', (e) => e.stopPropagation()); + wrap.append(addBtn); + + const grid = document.createElement('div'); + grid.className = 'cp-palette-grid'; + wrap.append(grid); + + function renderSwatches() { + grid.innerHTML = ''; + loadPalette().forEach((hex) => { + const sw = document.createElement('button'); + sw.className = 'cp-palette-swatch'; + sw.style.backgroundColor = hex; + sw.title = hex; + sw.addEventListener('pointerdown', (e) => e.stopPropagation()); + sw.addEventListener('click', () => { + const parsed = hexToRgb(hex); + if (!parsed) return; + const [h, s, v] = rgbToHsv(...parsed); + hsv = {h, s, v}; + currentHex = hex; + applyColor(); + }); + grid.append(sw); + }); + } + renderSwatches(); + return wrap; + } + + // ------------------------------------------------------------------ Bottom bar + + function buildBottom(): HTMLElement { + const bar = document.createElement('div'); + bar.className = 'cp-bottom-bar'; + + const make = (label: string, color: string, isOld: boolean) => { + const row = document.createElement('div'); + row.className = 'cp-color-row'; + const lbl = document.createElement('span'); + lbl.className = 'cp-color-row-label'; + lbl.textContent = label; + const strip = document.createElement('div'); + strip.className = 'cp-color-strip' + (isOld ? ' cp-color-strip--old' : ''); + strip.style.backgroundColor = color; + if (isOld) { + strip.title = t.picker.restorePrevious; + strip.addEventListener('pointerdown', (e) => e.stopPropagation()); + strip.addEventListener('click', () => { + const parsed = hexToRgb(oldColor); + if (!parsed) return; + const [h, s, v] = rgbToHsv(...parsed); + hsv = {h, s, v}; + currentHex = oldColor; + applyColor(); + }); + } else { + currentBarEl = strip; + } + row.append(lbl, strip); + return row; + }; + + bar.append(make(t.picker.newColor, currentHex, false)); + bar.append(make(t.picker.previousColor, oldColor, true)); + return bar; + } + + // ------------------------------------------------------------------ Apply helpers + + function applyHsv() { + const [r, g, b] = hsvToRgb(hsv.h, hsv.s, hsv.v); + currentHex = rgbToHex(r, g, b); + swatch.style.backgroundColor = currentHex; + options.onChange(currentHex); + syncRightPanel(); + } + + function applyHsvSilentSliders() { + const [r, g, b] = hsvToRgb(hsv.h, hsv.s, hsv.v); + currentHex = rgbToHex(r, g, b); + swatch.style.backgroundColor = currentHex; + options.onChange(currentHex); + if (hexInputEl) hexInputEl.value = currentHex; + if (swatchNewEl) swatchNewEl.style.backgroundColor = currentHex; + if (currentBarEl) currentBarEl.style.backgroundColor = currentHex; + // update slider gradients only + const vals = [r, g, b]; + rgbSliderEls.forEach(({valEl}, i) => { + const displayed = rangeMax === 100 ? Math.round((vals[i] / 255) * 100) : vals[i]; + valEl.textContent = String(displayed); + const lo = vals.map((v2, j) => j === i ? 0 : v2); + const hi = vals.map((v2, j) => j === i ? 255 : v2); + rgbSliderEls[i].input.style.setProperty('--grad-from', `rgb(${lo.join(',')})`); + rgbSliderEls[i].input.style.setProperty('--grad-to', `rgb(${hi.join(',')})`); + }); + updateActiveTabCanvas(); + } + + function applyHsvSilentHex() { + const [r, g, b] = hsvToRgb(hsv.h, hsv.s, hsv.v); + currentHex = rgbToHex(r, g, b); + swatch.style.backgroundColor = currentHex; + options.onChange(currentHex); + // update sliders + canvas but not hex input + const vals = [r, g, b]; + rgbSliderEls.forEach(({input, valEl}, i) => { + input.value = String(vals[i]); + const displayed = rangeMax === 100 ? Math.round((vals[i] / 255) * 100) : vals[i]; + valEl.textContent = String(displayed); + const lo = vals.map((v2, j) => j === i ? 0 : v2); + const hi = vals.map((v2, j) => j === i ? 255 : v2); + input.style.setProperty('--grad-from', `rgb(${lo.join(',')})`); + input.style.setProperty('--grad-to', `rgb(${hi.join(',')})`); + }); + if (swatchNewEl) swatchNewEl.style.backgroundColor = currentHex; + if (currentBarEl) currentBarEl.style.backgroundColor = currentHex; + updateActiveTabCanvas(); + } + + function applyColor() { + swatch.style.backgroundColor = currentHex; + options.onChange(currentHex); + syncRightPanel(); + } + + // ------------------------------------------------------------------ Misc helpers + + function makeToggleBtn(label: string, active: boolean, onClick: () => void): HTMLButtonElement { + const btn = document.createElement('button'); + btn.className = 'cp-toggle-btn' + (active ? ' is-active' : ''); + btn.textContent = label; + btn.addEventListener('pointerdown', (e) => e.stopPropagation()); + btn.addEventListener('click', onClick); + return btn; + } + + function makeIconBtn(label: string, onClick: () => void): HTMLButtonElement { + const btn = document.createElement('button'); + btn.className = 'cp-icon-btn'; + btn.textContent = label; + btn.addEventListener('pointerdown', (e) => e.stopPropagation()); + btn.addEventListener('click', onClick); + return btn; + } + + return Object.assign(wrapper, { + setValue(color: string) { + const parsed = hexToRgb(color); + if (!parsed) return; + const [h, s, v] = rgbToHsv(...parsed); + hsv = {h, s, v}; + currentHex = rgbToHex(...parsed); + swatch.style.backgroundColor = currentHex; + if (popup) syncRightPanel(); + }, + }); +} + +// -------------------------------------------------------------------- Palette storage + +function loadPalette(): string[] { + try { return JSON.parse(localStorage.getItem(PALETTE_KEY) ?? '[]'); } catch { return []; } +} + +function savePalette(palette: string[]) { + localStorage.setItem(PALETTE_KEY, JSON.stringify(palette)); +} + +// -------------------------------------------------------------------- Color math + +type Hsv = {h: number; s: number; v: number}; + +function hexToHsv(hex: string): Hsv { + const rgb = hexToRgb(hex); + if (!rgb) return {h: 0, s: 0, v: 1}; + const [h, s, v] = rgbToHsv(...rgb); + return {h, s, v}; +} + +function normalizeHex(hex: string): string { + const rgb = hexToRgb(hex); + return rgb ? rgbToHex(...rgb) : '#000000'; +} + +function hexToRgb(hex: string): [number, number, number] | null { + const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex.trim()); + if (!m) return null; + return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)]; +} + +function rgbToHex(r: number, g: number, b: number): string { + return '#' + [r, g, b].map((n) => Math.round(n).toString(16).padStart(2, '0')).join(''); +} + +function rgbToHsv(r: number, g: number, b: number): [number, number, number] { + const rn = r / 255, gn = g / 255, bn = b / 255; + const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn), delta = max - min; + let h = 0; + if (delta > 0) { + if (max === rn) h = ((gn - bn) / delta) % 6; + else if (max === gn) h = (bn - rn) / delta + 2; + else h = (rn - gn) / delta + 4; + h = (h * 60 + 360) % 360; + } + return [h, max === 0 ? 0 : delta / max, max]; +} + +function hsvToRgb(h: number, s: number, v: number): [number, number, number] { + const c = v * s, x = c * (1 - Math.abs(((h / 60) % 2) - 1)), m = v - c; + let r = 0, g = 0, b = 0; + if (h < 60) { r = c; g = x; } + else if (h < 120) { r = x; g = c; } + else if (h < 180) { g = c; b = x; } + else if (h < 240) { g = x; b = c; } + else if (h < 300) { r = x; b = c; } + else { r = c; b = x; } + return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)]; +} + +function rgbToCmyk(r: number, g: number, b: number): {c: number; m: number; y: number; k: number} { + const rn = r / 255, gn = g / 255, bn = b / 255; + const k = 1 - Math.max(rn, gn, bn); + if (k === 1) return {c: 0, m: 0, y: 0, k: 100}; + return { + c: Math.round(((1 - rn - k) / (1 - k)) * 100), + m: Math.round(((1 - gn - k) / (1 - k)) * 100), + y: Math.round(((1 - bn - k) / (1 - k)) * 100), + k: Math.round(k * 100), + }; +} + +function cmykToRgb(c: number, m: number, y: number, k: number): [number, number, number] { + const cn = c / 100, mn = m / 100, yn = y / 100, kn = k / 100; + return [ + Math.round(255 * (1 - cn) * (1 - kn)), + Math.round(255 * (1 - mn) * (1 - kn)), + Math.round(255 * (1 - yn) * (1 - kn)), + ]; +} + +function clamp(v: number, lo: number, hi: number) { return Math.max(lo, Math.min(hi, v)); } +function clamp01(v: number) { return clamp(v, 0, 1); } diff --git a/src/ui/keyboard-shortcuts.ts b/src/ui/keyboard-shortcuts.ts new file mode 100644 index 0000000..a1bfd38 --- /dev/null +++ b/src/ui/keyboard-shortcuts.ts @@ -0,0 +1,177 @@ +import {Tool} from '../types'; +import type {ShortcutOptions} from '../types'; +import type {WebDraftEditor} from '../core/webdraft-editor'; + +export function bindKeyboardShortcuts(editor: WebDraftEditor, options: ShortcutOptions): void { + window.addEventListener('keydown', (event) => { + if (isTypingTarget(event.target)) { + return; + } + + const key = event.key.toLowerCase(); + const isCommand = event.metaKey || event.ctrlKey; + + if (isCommand && key === 'z') { + event.preventDefault(); + if (event.shiftKey) { + editor.redo(); + } else { + editor.undo(); + } + return; + } + + if (isCommand && key === 'y') { + event.preventDefault(); + editor.redo(); + return; + } + + if (isCommand && key === 'o') { + event.preventDefault(); + options.openImagePicker(); + return; + } + + if (isCommand && key === 's') { + event.preventDefault(); + options.onSave(); + return; + } + + if (isCommand && key === 'h') { + event.preventDefault(); + options.openShortcutsDialog(); + return; + } + + if (isCommand && (key === '=' || key === '+')) { + event.preventDefault(); + editor.zoomIn(); + return; + } + + if (isCommand && key === '-') { + event.preventDefault(); + editor.zoomOut(); + return; + } + + if (isCommand && key === '0') { + event.preventDefault(); + editor.setZoom(1); + return; + } + + if (isCommand && key === 'c') { + event.preventDefault(); + editor.copySelection(); + return; + } + + if (isCommand && key === 'x') { + event.preventDefault(); + editor.cutSelection(); + return; + } + + if (isCommand && key === 'v') { + event.preventDefault(); + editor.pasteSelection(); + return; + } + + if (event.key === 'Delete' || event.key === 'Backspace') { + event.preventDefault(); + editor.clear(); + return; + } + + if (key === '[') { + event.preventDefault(); + editor.setSize(editor.state.size - 1); + return; + } + + if (key === ']') { + event.preventDefault(); + editor.setSize(editor.state.size + 1); + return; + } + + if (key === 'i') { + event.preventDefault(); + editor.invertActiveLayer(); + return; + } + + if (key === ',') { + event.preventDefault(); + editor.rotateActiveLayer('left'); + return; + } + + if (key === '.') { + event.preventDefault(); + editor.rotateActiveLayer('right'); + return; + } + + if (key === 'h') { + event.preventDefault(); + editor.mirrorActiveLayer('horizontal'); + return; + } + + if (key === 'v') { + event.preventDefault(); + editor.mirrorActiveLayer('vertical'); + return; + } + + const tool = getToolForKey(key); + + if (tool) { + event.preventDefault(); + editor.setTool(tool); + } + }); +} + +function getToolForKey(key: string): Tool | null { + switch (key) { + case 's': + return Tool.Select; + case 'p': + return Tool.Pencil; + case 'e': + return Tool.Eraser; + case 'c': + return Tool.Sampler; + case 'b': + return Tool.FillBucket; + case 'w': + return Tool.Web; + case 'r': + return Tool.Rectangle; + case 'o': + return Tool.Ellipse; + case 't': + return Tool.Text; + default: + return null; + } +} + +function isTypingTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) { + return false; + } + + return ( + target.isContentEditable || + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + target instanceof HTMLSelectElement + ); +} diff --git a/src/ui/lang-state.ts b/src/ui/lang-state.ts new file mode 100644 index 0000000..5ddb292 --- /dev/null +++ b/src/ui/lang-state.ts @@ -0,0 +1,29 @@ +import { parseWdraftBinary } from '../core/project-file'; +import type { WebDraftEditor } from '../core/webdraft-editor'; + +const RESTORE_KEY = 'webdraft-lang-restore'; + +async function blobToBase64(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve((reader.result as string).split(',')[1]); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); +} + +export async function saveStateForReload(editor: WebDraftEditor): Promise { + const blob = await editor.exportProject(); + const base64 = await blobToBase64(blob); + sessionStorage.setItem(RESTORE_KEY, base64); +} + +export async function restoreStateAfterReload(editor: WebDraftEditor): Promise { + const base64 = sessionStorage.getItem(RESTORE_KEY); + if (!base64) return; + sessionStorage.removeItem(RESTORE_KEY); + + const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const parsed = parseWdraftBinary(bytes.buffer as ArrayBuffer); + await editor.importProject(parsed); +} diff --git a/src/ui/layers-panel.ts b/src/ui/layers-panel.ts new file mode 100644 index 0000000..187d2ee --- /dev/null +++ b/src/ui/layers-panel.ts @@ -0,0 +1,101 @@ +import { t } from '../core/i18n'; +import type { WebDraftEditor } from '../core/webdraft-editor'; + +export function createLayersPanel(editor: WebDraftEditor): HTMLElement { + const panel = document.createElement('section'); + panel.className = 'layers-panel'; + + const header = document.createElement('div'); + header.className = 'panel-header'; + + const title = document.createElement('h2'); + title.textContent = t.layers.title; + + const actions = document.createElement('div'); + actions.className = 'panel-actions'; + + const addButton = createIconButton(t.layers.addLayer, '+', () => editor.addLayer()); + const deleteButton = createIconButton(t.layers.deleteLayer, '-', () => editor.deleteActiveLayer()); + const upButton = createIconButton(t.layers.moveLayerUp, '↑', () => editor.moveActiveLayerUp()); + const downButton = createIconButton(t.layers.moveLayerDown, '↓', () => editor.moveActiveLayerDown()); + + actions.append(addButton, deleteButton, upButton, downButton); + header.append(title, actions); + + const list = document.createElement('div'); + list.className = 'layer-list'; + + const render = () => { + list.replaceChildren( + ...editor.layers.map((layer) => { + const item = document.createElement('div'); + item.className = 'layer-item'; + item.classList.toggle('is-active', layer.active); + + const previewButton = document.createElement('button'); + previewButton.type = 'button'; + previewButton.className = 'layer-preview'; + previewButton.title = t.layers.selectLayer(layer.name); + previewButton.addEventListener('click', () => editor.selectLayer(layer.id)); + + const previewImage = document.createElement('img'); + previewImage.alt = ''; + previewImage.src = layer.preview; + previewButton.append(previewImage); + + const renameInput = document.createElement('input'); + renameInput.className = 'layer-name-input'; + renameInput.value = layer.name; + renameInput.title = t.layers.renameLayer; + renameInput.addEventListener('focus', () => { + renameInput.select(); + }); + renameInput.addEventListener('blur', () => { + editor.renameLayer(layer.id, renameInput.value); + }); + renameInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + renameInput.blur(); + } + }); + + const visibilityButton = document.createElement('button'); + visibilityButton.type = 'button'; + visibilityButton.className = 'layer-visibility'; + visibilityButton.title = layer.visible ? t.layers.hideLayer : t.layers.showLayer; + visibilityButton.textContent = layer.visible ? '●' : '○'; + visibilityButton.addEventListener('click', () => editor.toggleLayerVisibility(layer.id)); + + if (layer.isTextLayer) { + const badge = document.createElement('span'); + badge.className = 'layer-text-badge'; + badge.textContent = 'T'; + badge.title = t.layers.textLayer; + previewButton.append(badge); + } + + item.append(previewButton, renameInput, visibilityButton); + + return item; + }) + ); + }; + + editor.addEventListener('change', render); + render(); + + panel.append(header, list); + + return panel; +} + +function createIconButton(label: string, icon: string, onClick: () => void): HTMLButtonElement { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'icon-button'; + button.title = label; + button.textContent = icon; + button.addEventListener('click', onClick); + + return button; +} diff --git a/src/ui/menu-bar.ts b/src/ui/menu-bar.ts new file mode 100644 index 0000000..f2a3963 --- /dev/null +++ b/src/ui/menu-bar.ts @@ -0,0 +1,486 @@ +import { parseWdraftBinary } from '../core/project-file'; +import { lang, setLang, t } from '../core/i18n'; +import { modKey } from '../core/platform'; +import { openShortcutsDialog } from './shortcuts-dialog'; +import type { WebDraftEditor } from '../core/webdraft-editor'; +import {Lang, Menu, MenuItem} from '../types'; +import type { StatusReporter } from '../types'; +import { getErrorMessage } from './status-toasts'; +import { saveStateForReload } from './lang-state'; +import { openResizeDialog } from './resize-dialog'; + +let openMenu: HTMLElement | null = null; +let openButton: HTMLButtonElement | null = null; + +function closeOpenMenu(): void { + openMenu?.remove(); + openMenu = null; + openButton?.classList.remove('menu-btn--active'); + openButton = null; +} + +function buildDropdown(items: MenuItem[], anchorEl: HTMLElement): HTMLElement { + const dropdown = document.createElement('ul'); + dropdown.className = 'menu-dropdown'; + dropdown.setAttribute('role', 'menu'); + + for (const item of items) { + const li = document.createElement('li'); + li.setAttribute('role', 'none'); + + if (item.type === 'separator') { + li.className = 'menu-dropdown__sep'; + li.setAttribute('role', 'separator'); + } else { + li.className = 'menu-dropdown__item'; + if (item.disabled?.()) li.classList.add('menu-dropdown__item--disabled'); + + const btn = document.createElement('button'); + btn.setAttribute('role', 'menuitem'); + btn.tabIndex = -1; + btn.textContent = item.label; + + if (item.shortcut) { + const kbd = document.createElement('span'); + kbd.className = 'menu-dropdown__shortcut'; + kbd.textContent = item.shortcut; + btn.append(kbd); + } + + btn.addEventListener('click', () => { + if (!item.disabled?.()) { + closeOpenMenu(); + item.action(); + } + }); + li.append(btn); + } + + dropdown.append(li); + } + + const rect = anchorEl.getBoundingClientRect(); + dropdown.style.left = `${rect.left}px`; + dropdown.style.top = `${rect.bottom}px`; + + return dropdown; +} + +function createMenuButton(menu: Menu): HTMLButtonElement { + const btn = document.createElement('button'); + btn.className = 'menu-btn'; + btn.textContent = menu.label; + btn.setAttribute('aria-haspopup', 'true'); + btn.setAttribute('aria-expanded', 'false'); + + btn.addEventListener('click', (e) => { + e.stopPropagation(); + if (openMenu && openButton === btn) { + closeOpenMenu(); + return; + } + closeOpenMenu(); + + const dropdown = buildDropdown(menu.items, btn); + document.body.append(dropdown); + openMenu = dropdown; + openButton = btn; + btn.classList.add('menu-btn--active'); + btn.setAttribute('aria-expanded', 'true'); + }); + + btn.addEventListener('mouseenter', () => { + if (openMenu && openButton !== btn) { + closeOpenMenu(); + const dropdown = buildDropdown(menu.items, btn); + document.body.append(dropdown); + openMenu = dropdown; + openButton = btn; + btn.classList.add('menu-btn--active'); + btn.setAttribute('aria-expanded', 'true'); + } + }); + + return btn; +} + +const LANG_LABELS: Record = { + pl: '🇵🇱 PL', + en: '🇬🇧 EN', +}; + +const ALL_LANGS: Lang[] = ['pl', 'en']; + +function createLangSelector(editor: WebDraftEditor): HTMLElement { + const wrapper = document.createElement('div'); + wrapper.className = 'menu-lang-selector'; + + const btn = document.createElement('button'); + btn.className = 'menu-btn menu-lang-btn'; + btn.textContent = LANG_LABELS[lang]; + btn.setAttribute('aria-haspopup', 'true'); + btn.setAttribute('aria-expanded', 'false'); + + let dropdown: HTMLElement | null = null; + + function closeLangMenu(): void { + dropdown?.remove(); + dropdown = null; + btn.classList.remove('menu-btn--active'); + btn.setAttribute('aria-expanded', 'false'); + } + + btn.addEventListener('click', (e) => { + e.stopPropagation(); + if (dropdown) { closeLangMenu(); return; } + + closeOpenMenu(); + + dropdown = document.createElement('ul'); + dropdown.className = 'menu-dropdown menu-lang-dropdown'; + dropdown.setAttribute('role', 'menu'); + + for (const l of ALL_LANGS) { + const li = document.createElement('li'); + li.setAttribute('role', 'none'); + li.className = 'menu-dropdown__item' + (l === lang ? ' menu-dropdown__item--active' : ''); + + const item = document.createElement('button'); + item.setAttribute('role', 'menuitem'); + item.tabIndex = -1; + item.textContent = LANG_LABELS[l]; + item.addEventListener('click', () => { + closeLangMenu(); + if (l !== lang) { + void saveStateForReload(editor).then(() => { + setLang(l); + location.reload(); + }); + } + }); + + li.append(item); + dropdown.append(li); + } + + const rect = btn.getBoundingClientRect(); + dropdown.style.left = `${rect.right}px`; + dropdown.style.top = `${rect.bottom}px`; + dropdown.style.transform = 'translateX(-100%)'; + + document.body.append(dropdown); + btn.classList.add('menu-btn--active'); + btn.setAttribute('aria-expanded', 'true'); + + document.addEventListener('click', closeLangMenu, { once: true }); + }); + + wrapper.append(btn); + return wrapper; +} + +export function createMenuBar(editor: WebDraftEditor, status: StatusReporter): HTMLElement { + const bar = document.createElement('nav'); + bar.className = 'menu-bar'; + bar.setAttribute('aria-label', t.menu.file); + + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*,.wdraft'; + fileInput.className = 'visually-hidden'; + fileInput.addEventListener('change', () => { + const file = fileInput.files?.[0]; + if (!file) return; + void (async () => { + try { + if (file.name.endsWith('.wdraft')) { + const buffer = await file.arrayBuffer(); + const parsed = parseWdraftBinary(buffer); + await editor.importProject(parsed); + status.show(t.file.openedOk, 'success'); + } else { + await editor.importImage(file); + status.show(t.file.importedOk, 'success'); + } + } catch (err) { + status.show(getErrorMessage(err), 'error'); + } finally { + fileInput.value = ''; + } + })(); + }); + + bar.append(fileInput); + + const menus: Menu[] = [ + { + label: t.menu.file, + items: [ + { + type: 'action', + label: t.file.new, + shortcut: `${modKey}+N`, + action: () => { + if (confirm(t.file.newConfirm)) { + editor.clear(); + status.show(t.file.new, 'success'); + } + }, + }, + { + type: 'action', + label: t.file.open, + shortcut: `${modKey}+O`, + action: () => fileInput.click(), + }, + { type: 'separator' }, + { + type: 'action', + label: t.file.save, + action: () => { + void (async () => { + try { + const blob = await editor.exportProject(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = t.file.projectFilename; + a.click(); + URL.revokeObjectURL(url); + status.show(t.file.savedOk, 'success'); + } catch (err) { + status.show(getErrorMessage(err), 'error'); + } + })(); + }, + }, + { type: 'separator' }, + { + type: 'action', + label: t.file.exportPng, + action: () => { + void (async () => { + try { + const blob = await editor.exportPng(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = t.file.imageFilename; + a.click(); + URL.revokeObjectURL(url); + status.show(t.file.exportedOk, 'success'); + } catch (err) { + status.show(getErrorMessage(err), 'error'); + } + })(); + }, + }, + ], + }, + { + label: t.menu.edit, + items: [ + { + type: 'action', + label: t.edit.undo, + shortcut: `${modKey}+Z`, + disabled: () => !editor.canUndo, + action: () => editor.undo(), + }, + { + type: 'action', + label: t.edit.redo, + shortcut: `${modKey}+Y`, + disabled: () => !editor.canRedo, + action: () => editor.redo(), + }, + { type: 'separator' }, + { + type: 'action', + label: t.edit.cut, + shortcut: `${modKey}+X`, + action: () => editor.cutSelection(), + }, + { + type: 'action', + label: t.edit.copy, + shortcut: `${modKey}+C`, + action: () => editor.copySelection(), + }, + { + type: 'action', + label: t.edit.paste, + shortcut: `${modKey}+V`, + action: () => editor.pasteSelection(), + }, + { type: 'separator' }, + { + type: 'action', + label: t.edit.clearLayer, + action: () => editor.clear(), + }, + ], + }, + { + label: t.menu.image, + items: [ + { + type: 'action', + label: t.image.resizeCanvas, + action: () => { + openResizeDialog({ + title: t.image.resizeCanvas, + width: editor.canvasWidth, + height: editor.canvasHeight, + checkboxLabel: t.image.applyToAllLayers, + checkboxChecked: true, + onConfirm: (w, h, all) => { + editor.resizeCanvas(w, h, all); + status.show(t.image.resizedOk(w, h), 'success'); + }, + }); + }, + }, + { type: 'separator' }, + { + type: 'action', + label: t.image.invertColors, + action: () => editor.invertActiveLayer(), + }, + { + type: 'action', + label: t.image.mirrorH, + action: () => editor.mirrorActiveLayer('horizontal'), + }, + { + type: 'action', + label: t.image.mirrorV, + action: () => editor.mirrorActiveLayer('vertical'), + }, + { type: 'separator' }, + { + type: 'action', + label: t.image.rotateLeft, + action: () => editor.rotateActiveLayer('left'), + }, + { + type: 'action', + label: t.image.rotateRight, + action: () => editor.rotateActiveLayer('right'), + }, + ], + }, + { + label: t.menu.layer, + items: [ + { + type: 'action', + label: t.layer.new, + action: () => { + editor.addLayer(); + status.show(t.layer.addedOk, 'success'); + }, + }, + { + type: 'action', + label: t.layer.delete, + disabled: () => editor.layers.length <= 1, + action: () => { + editor.deleteActiveLayer(); + status.show(t.layer.deletedOk, 'success'); + }, + }, + { type: 'separator' }, + { + type: 'action', + label: t.layer.moveUp, + action: () => editor.moveActiveLayerUp(), + }, + { + type: 'action', + label: t.layer.moveDown, + action: () => editor.moveActiveLayerDown(), + }, + { type: 'separator' }, + { + type: 'action', + label: t.layer.resize, + action: () => { + openResizeDialog({ + title: t.layer.resize, + width: editor.activeLayerWidth, + height: editor.activeLayerHeight, + onConfirm: (w, h) => { + editor.resizeActiveLayer(w, h); + status.show(t.layer.layerResizedOk(w, h), 'success'); + }, + }); + }, + }, + ], + }, + { + label: t.menu.help, + items: [ + { + type: 'action', + label: t.help.keyboardShortcuts, + shortcut: `${modKey}+H`, + action: openShortcutsDialog, + }, + ], + }, + ]; + + for (const menu of menus) { + bar.append(createMenuButton(menu)); + } + + bar.append(createZoomWidget(editor), createLangSelector(editor)); + + document.addEventListener('click', closeOpenMenu); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') closeOpenMenu(); + }); + + return bar; +} + +function createZoomWidget(editor: WebDraftEditor): HTMLElement { + const wrap = document.createElement('div'); + wrap.className = 'menu-zoom'; + + const btnOut = document.createElement('button'); + btnOut.type = 'button'; + btnOut.className = 'menu-zoom-btn'; + btnOut.textContent = '−'; + btnOut.title = `Zoom out (${modKey}+−)`; + btnOut.addEventListener('click', () => editor.zoomOut()); + + const label = document.createElement('button'); + label.type = 'button'; + label.className = 'menu-zoom-label'; + label.title = `Reset zoom (${modKey}+0)`; + label.addEventListener('click', () => editor.setZoom(1)); + + const btnIn = document.createElement('button'); + btnIn.type = 'button'; + btnIn.className = 'menu-zoom-btn'; + btnIn.textContent = '+'; + btnIn.title = `Zoom in (${modKey}+=)`; + btnIn.addEventListener('click', () => editor.zoomIn()); + + wrap.append(btnOut, label, btnIn); + + const sync = (): void => { + const pct = Math.round(editor.state.zoom * 100); + label.textContent = `${pct}%`; + btnOut.disabled = editor.state.zoom <= 0.1; + btnIn.disabled = editor.state.zoom >= 8; + }; + + editor.addEventListener('change', sync); + sync(); + + return wrap; +} diff --git a/src/ui/resize-dialog.ts b/src/ui/resize-dialog.ts new file mode 100644 index 0000000..773a8e2 --- /dev/null +++ b/src/ui/resize-dialog.ts @@ -0,0 +1,99 @@ +import { t } from '../core/i18n'; +import {ResizeDialogOptions} from '../types'; + +export function openResizeDialog(opts: ResizeDialogOptions): void { + const backdrop = document.createElement('div'); + backdrop.className = 'resize-dlg-backdrop'; + + const dialog = document.createElement('div'); + dialog.className = 'resize-dlg'; + dialog.setAttribute('role', 'dialog'); + dialog.setAttribute('aria-modal', 'true'); + + const titleEl = document.createElement('h2'); + titleEl.className = 'resize-dlg__title'; + titleEl.textContent = opts.title; + + function makeField(labelText: string, defaultValue: number): { row: HTMLElement; input: HTMLInputElement } { + const row = document.createElement('div'); + row.className = 'resize-dlg__field'; + const lbl = document.createElement('label'); + lbl.className = 'resize-dlg__label'; + lbl.textContent = labelText; + const input = document.createElement('input'); + input.type = 'number'; + input.className = 'resize-dlg__input'; + input.min = '1'; + input.max = '8192'; + input.value = String(defaultValue); + lbl.append(input); + row.append(lbl); + return { row, input }; + } + + const { row: wRow, input: wInput } = makeField(t.image.widthPrompt, opts.width); + const { row: hRow, input: hInput } = makeField(t.image.heightPrompt, opts.height); + + let checkboxEl: HTMLInputElement | null = null; + + const actions = document.createElement('div'); + actions.className = 'resize-dlg__actions'; + + const cancelBtn = document.createElement('button'); + cancelBtn.className = 'resize-dlg__btn resize-dlg__btn--secondary'; + cancelBtn.textContent = t.saveDialog.cancel; + cancelBtn.type = 'button'; + + const confirmBtn = document.createElement('button'); + confirmBtn.className = 'resize-dlg__btn resize-dlg__btn--primary'; + confirmBtn.textContent = 'OK'; + confirmBtn.type = 'button'; + + actions.append(cancelBtn, confirmBtn); + + dialog.append(titleEl, wRow, hRow); + + if (opts.checkboxLabel !== undefined) { + const checkboxRow = document.createElement('label'); + checkboxRow.className = 'resize-dlg__checkbox-row'; + checkboxEl = document.createElement('input'); + checkboxEl.type = 'checkbox'; + checkboxEl.checked = opts.checkboxChecked ?? true; + const lbl = document.createElement('span'); + lbl.textContent = opts.checkboxLabel; + checkboxRow.append(checkboxEl, lbl); + dialog.append(checkboxRow); + } + + dialog.append(actions); + backdrop.append(dialog); + document.body.append(backdrop); + + wInput.focus(); + wInput.select(); + + function close(): void { + backdrop.remove(); + } + + function confirm(): void { + const w = parseInt(wInput.value, 10); + const h = parseInt(hInput.value, 10); + if (w > 0 && h > 0) { + close(); + opts.onConfirm(w, h, checkboxEl?.checked ?? false); + } + } + + cancelBtn.addEventListener('click', close); + confirmBtn.addEventListener('click', confirm); + + backdrop.addEventListener('click', (e) => { + if (e.target === backdrop) close(); + }); + + dialog.addEventListener('keydown', (e) => { + if (e.key === 'Escape') close(); + if (e.key === 'Enter') confirm(); + }); +} diff --git a/src/ui/right-panel.ts b/src/ui/right-panel.ts new file mode 100644 index 0000000..aa2ceae --- /dev/null +++ b/src/ui/right-panel.ts @@ -0,0 +1,25 @@ +import type {WebDraftEditor} from '../core/webdraft-editor'; +import {StatusReporter} from '../types'; +import {createLayersPanel} from './layers-panel'; +import {createEditSection, syncToolbarSections} from './toolbar-sections'; +import {createToolbarSection} from './toolbar-controls'; + +export function createRightPanel(editor: WebDraftEditor, status: StatusReporter): HTMLElement { + const panel = document.createElement('div'); + panel.className = 'toolbar'; + + const sections = [ + createEditSection(editor), + { + element: createToolbarSection(createLayersPanel(editor)), + sync: () => {}, + }, + ]; + + editor.addEventListener('change', () => syncToolbarSections(sections)); + syncToolbarSections(sections); + + panel.append(...sections.map((s) => s.element)); + + return panel; +} diff --git a/src/ui/save-dialog.ts b/src/ui/save-dialog.ts new file mode 100644 index 0000000..c68172e --- /dev/null +++ b/src/ui/save-dialog.ts @@ -0,0 +1,104 @@ +import {t} from '../core/i18n'; +import type {SaveAction} from '../types'; + +const STORAGE_KEY = 'webdraft-save-action'; + +function getStoredAction(): SaveAction | null { + const v = localStorage.getItem(STORAGE_KEY); + return v === 'png' || v === 'project' ? v : null; +} + +function storeAction(action: SaveAction): void { + localStorage.setItem(STORAGE_KEY, action); +} + +function clearStoredAction(): void { + localStorage.removeItem(STORAGE_KEY); +} + +export function openSaveDialog(onAction: (action: SaveAction) => void): void { + const stored = getStoredAction(); + if (stored) { + onAction(stored); + return; + } + + const backdrop = document.createElement('div'); + backdrop.className = 'save-dialog-backdrop'; + + const dialog = document.createElement('div'); + dialog.className = 'save-dialog'; + dialog.setAttribute('role', 'dialog'); + dialog.setAttribute('aria-modal', 'true'); + dialog.setAttribute('aria-label', t.saveDialog.title); + + const title = document.createElement('h2'); + title.className = 'save-dialog__title'; + title.textContent = t.saveDialog.title; + + const buttons = document.createElement('div'); + buttons.className = 'save-dialog__buttons'; + + const pngBtn = document.createElement('button'); + pngBtn.className = 'save-dialog__btn'; + pngBtn.textContent = t.saveDialog.exportPng; + + const projectBtn = document.createElement('button'); + projectBtn.className = 'save-dialog__btn save-dialog__btn--primary'; + projectBtn.textContent = t.saveDialog.saveProject; + + buttons.append(pngBtn, projectBtn); + + const footer = document.createElement('div'); + footer.className = 'save-dialog__footer'; + + const rememberLabel = document.createElement('label'); + rememberLabel.className = 'save-dialog__remember'; + + const rememberCheckbox = document.createElement('input'); + rememberCheckbox.type = 'checkbox'; + + const rememberText = document.createElement('span'); + rememberText.textContent = t.saveDialog.rememberChoice; + + rememberLabel.append(rememberCheckbox, rememberText); + + const cancelBtn = document.createElement('button'); + cancelBtn.className = 'save-dialog__cancel'; + cancelBtn.textContent = t.saveDialog.cancel; + + footer.append(rememberLabel, cancelBtn); + dialog.append(title, buttons, footer); + document.body.append(backdrop, dialog); + + function close(): void { + backdrop.remove(); + dialog.remove(); + document.removeEventListener('keydown', onKey); + } + + function choose(action: SaveAction): void { + if (rememberCheckbox.checked) { + storeAction(action); + } + close(); + onAction(action); + } + + function onKey(e: KeyboardEvent): void { + if (e.key === 'Escape') { + e.preventDefault(); + close(); + } + } + + pngBtn.addEventListener('click', () => choose('png')); + projectBtn.addEventListener('click', () => choose('project')); + cancelBtn.addEventListener('click', close); + backdrop.addEventListener('click', close); + document.addEventListener('keydown', onKey); + + projectBtn.focus(); +} + +export { clearStoredAction }; diff --git a/src/ui/shortcuts-dialog.ts b/src/ui/shortcuts-dialog.ts new file mode 100644 index 0000000..5417fc9 --- /dev/null +++ b/src/ui/shortcuts-dialog.ts @@ -0,0 +1,147 @@ +import {t} from '../core/i18n'; +import {modKey} from '../core/platform'; + +type ShortcutRow = [string, string]; +type Section = {title: string; rows: ShortcutRow[]}; + +function toolLabel(s: string): string { + return s.split(' \u2014 ')[0].trim(); +} + +export function openShortcutsDialog(): void { + if (document.querySelector('.shortcuts-dialog')) return; + + const backdrop = document.createElement('div'); + backdrop.className = 'shortcuts-dialog-backdrop'; + + const dialog = document.createElement('div'); + dialog.className = 'shortcuts-dialog'; + dialog.setAttribute('role', 'dialog'); + dialog.setAttribute('aria-modal', 'true'); + dialog.setAttribute('aria-label', t.help.keyboardShortcuts); + + const header = document.createElement('div'); + header.className = 'shortcuts-dialog__header'; + + const title = document.createElement('h2'); + title.className = 'shortcuts-dialog__title'; + title.textContent = t.help.keyboardShortcuts; + + const closeBtn = document.createElement('button'); + closeBtn.type = 'button'; + closeBtn.className = 'shortcuts-dialog__close'; + closeBtn.textContent = '\u00d7'; + closeBtn.title = t.help.close; + header.append(title, closeBtn); + + const sections: Section[] = [ + { + title: t.menu.file, + rows: [ + [`${modKey}+N`, t.file.new], + [`${modKey}+O`, t.file.open], + [`${modKey}+S`, t.file.save], + ], + }, + { + title: t.menu.edit, + rows: [ + [`${modKey}+Z`, t.edit.undo], + [`${modKey}+Shift+Z`, t.edit.redo], + [`${modKey}+C`, t.edit.copy], + [`${modKey}+X`, t.edit.cut], + [`${modKey}+V`, t.edit.paste], + ['Delete', t.edit.clearLayer], + ], + }, + { + title: t.help.sectionView, + rows: [ + [`${modKey}+=`, t.help.zoomIn], + [`${modKey}+\u2212`, t.help.zoomOut], + [`${modKey}+0`, t.help.zoomReset], + ], + }, + { + title: t.help.sectionTools, + rows: [ + ['S', toolLabel(t.toolbar.toolSelect)], + ['P', toolLabel(t.toolbar.toolPencil)], + ['E', toolLabel(t.toolbar.toolEraser)], + ['C', toolLabel(t.toolbar.toolSampler)], + ['B', toolLabel(t.toolbar.toolFillBucket)], + ['W', toolLabel(t.toolbar.toolWeb)], + ['R', toolLabel(t.toolbar.toolRectangle)], + ['O', toolLabel(t.toolbar.toolEllipse)], + ['T', toolLabel(t.toolbar.toolText)], + ], + }, + { + title: t.help.sectionCanvas, + rows: [ + ['I', toolLabel(t.toolbar.invertColors)], + ['H', toolLabel(t.toolbar.mirrorH)], + ['V', toolLabel(t.toolbar.mirrorV)], + [',', toolLabel(t.toolbar.rotateLeft)], + ['.', toolLabel(t.toolbar.rotateRight)], + ['[', t.help.sizeDecrease], + [']', t.help.sizeIncrease], + ], + }, + ]; + + const body = document.createElement('div'); + body.className = 'shortcuts-dialog__body'; + + for (const section of sections) { + const col = document.createElement('div'); + col.className = 'shortcuts-section'; + + const sectionTitle = document.createElement('h3'); + sectionTitle.className = 'shortcuts-section__title'; + sectionTitle.textContent = section.title; + + const rows = document.createElement('div'); + rows.className = 'shortcuts-section__rows'; + + for (const [key, desc] of section.rows) { + const row = document.createElement('div'); + row.className = 'shortcuts-row'; + + const kbd = document.createElement('kbd'); + kbd.className = 'shortcuts-row__key'; + kbd.textContent = key; + + const descEl = document.createElement('span'); + descEl.className = 'shortcuts-row__desc'; + descEl.textContent = desc; + + row.append(kbd, descEl); + rows.append(row); + } + + col.append(sectionTitle, rows); + body.append(col); + } + + dialog.append(header, body); + document.body.append(backdrop, dialog); + + function close(): void { + backdrop.remove(); + dialog.remove(); + document.removeEventListener('keydown', onKey); + } + + function onKey(e: KeyboardEvent): void { + if (e.key === 'Escape') { + e.preventDefault(); + close(); + } + } + + closeBtn.addEventListener('click', close); + backdrop.addEventListener('click', close); + document.addEventListener('keydown', onKey); + closeBtn.focus(); +} diff --git a/src/ui/status-toasts.ts b/src/ui/status-toasts.ts new file mode 100644 index 0000000..49e8312 --- /dev/null +++ b/src/ui/status-toasts.ts @@ -0,0 +1,29 @@ +import {t} from '../core/i18n'; +import type {StatusReporter, StatusTone} from '../types'; + +export function createStatusToasts(): HTMLElement & StatusReporter { + const host = Object.assign(document.createElement('div'), { + show: (_message: string, _tone: StatusTone = 'info') => {}, + }); + host.className = 'status-toasts'; + host.setAttribute('aria-live', 'polite'); + host.show = (message, tone = 'info') => { + const toast = document.createElement('div'); + toast.className = `status-toast status-toast--${tone}`; + toast.textContent = message; + + host.replaceChildren(toast); + window.setTimeout(() => { + if (toast.parentElement === host) { + toast.remove(); + } + }, tone === 'error' ? 6000 : 3200); + }; + + return host; +} + +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) return error.message; + return t.common.somethingWentWrong; +} diff --git a/src/ui/text-toolbar.ts b/src/ui/text-toolbar.ts new file mode 100644 index 0000000..6e394ab --- /dev/null +++ b/src/ui/text-toolbar.ts @@ -0,0 +1,201 @@ +import type {SizeWithPosition, TextToolbarDefaults} from '../types'; + +const FONT_FAMILIES = ['sans-serif', 'serif', 'monospace', 'cursive', 'fantasy']; +const FONT_SIZES = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 64, 72, 96, 128]; + +const ALIGN_ICONS: Record = { + left: '', + center: '', + right: '', +}; + +export function createTextToolbar( + contentEl: HTMLElement, + bounds: SizeWithPosition, + defaults: TextToolbarDefaults, + onAlignChange: (align: CanvasTextAlign) => void, +): { element: HTMLElement; cleanup: () => void } { + const toolbar = document.createElement('div'); + toolbar.className = 'text-toolbar'; + + const top = bounds.y >= 44 ? bounds.y - 44 : bounds.y + bounds.height + 8; + toolbar.style.top = `${top}px`; + toolbar.style.left = `${Math.max(0, bounds.x)}px`; + + // --- helpers --- + + const mkBtn = (title: string, html: string, extraClass = ''): HTMLButtonElement => { + const b = document.createElement('button'); + b.type = 'button'; + b.title = title; + b.className = `text-toolbar-btn${extraClass ? ' ' + extraClass : ''}`; + b.innerHTML = html; + b.addEventListener('mousedown', (e) => e.preventDefault()); + return b; + }; + + const sep = (): HTMLElement => { + const d = document.createElement('div'); + d.className = 'text-toolbar-sep'; + return d; + }; + + let savedRange: Range | null = null; + + const saveRange = (): void => { + const sel = window.getSelection(); + if (sel && sel.rangeCount > 0) { + const r = sel.getRangeAt(0); + if (contentEl.contains(r.commonAncestorContainer)) savedRange = r.cloneRange(); + } + }; + + const restoreAndApply = (fn: () => void): void => { + contentEl.focus(); + if (savedRange) { + const sel = window.getSelection(); + if (sel) { + sel.removeAllRanges(); + sel.addRange(savedRange); + } + savedRange = null; + } + fn(); + }; + + // --- Alignment --- + + const alignBtns: Record = { + left: mkBtn('Align left', ALIGN_ICONS['left'], 'text-toolbar-align'), + center: mkBtn('Align center', ALIGN_ICONS['center'], 'text-toolbar-align'), + right: mkBtn('Align right', ALIGN_ICONS['right'], 'text-toolbar-align'), + }; + + const setAlign = (align: CanvasTextAlign): void => { + Object.values(alignBtns).forEach((b) => b.classList.remove('is-active')); + const key = align === 'start' ? 'left' : align === 'end' ? 'right' : align; + alignBtns[key]?.classList.add('is-active'); + defaults.align = align; + contentEl.style.textAlign = align; + onAlignChange(align); + }; + + Object.entries(alignBtns).forEach(([align, btn]) => { + btn.addEventListener('click', () => setAlign(align as CanvasTextAlign)); + }); + setAlign(defaults.align); + + const alignGroup = document.createElement('div'); + alignGroup.className = 'text-toolbar-group'; + alignGroup.append(...Object.values(alignBtns)); + + // --- Bold / Italic / Underline --- + + const btnBold = mkBtn('Bold', 'B', 'text-toolbar-format'); + const btnItalic = mkBtn('Italic', 'I', 'text-toolbar-format'); + const btnUnderline = mkBtn('Underline', 'U', 'text-toolbar-format'); + + btnBold.addEventListener('click', () => document.execCommand('bold')); + btnItalic.addEventListener('click', () => document.execCommand('italic')); + btnUnderline.addEventListener('click', () => document.execCommand('underline')); + + const formatGroup = document.createElement('div'); + formatGroup.className = 'text-toolbar-group'; + formatGroup.append(btnBold, btnItalic, btnUnderline); + + // --- Font family --- + + const fontFamilySelect = document.createElement('select'); + fontFamilySelect.className = 'text-toolbar-select'; + fontFamilySelect.title = 'Font family'; + FONT_FAMILIES.forEach((f) => { + const o = document.createElement('option'); + o.value = f; + o.textContent = f; + if (f === defaults.fontFamily) o.selected = true; + fontFamilySelect.append(o); + }); + fontFamilySelect.addEventListener('mousedown', saveRange); + fontFamilySelect.addEventListener('change', () => { + restoreAndApply(() => applyInlineStyle(contentEl, 'font-family', fontFamilySelect.value)); + }); + + // --- Font size --- + + const fontSizeSelect = document.createElement('select'); + fontSizeSelect.className = 'text-toolbar-select text-toolbar-size-select'; + fontSizeSelect.title = 'Font size'; + FONT_SIZES.forEach((s) => { + const o = document.createElement('option'); + o.value = `${s}`; + o.textContent = `${s}`; + if (s === defaults.fontSize) o.selected = true; + fontSizeSelect.append(o); + }); + fontSizeSelect.addEventListener('mousedown', saveRange); + fontSizeSelect.addEventListener('change', () => { + const px = parseInt(fontSizeSelect.value); + if (px > 0) { + defaults.fontSize = px; + restoreAndApply(() => applyInlineStyle(contentEl, 'font-size', `${px}px`)); + } + }); + + const fontGroup = document.createElement('div'); + fontGroup.className = 'text-toolbar-group'; + fontGroup.append(fontFamilySelect, fontSizeSelect); + + // --- Color --- + + const colorLabel = document.createElement('label'); + colorLabel.className = 'text-toolbar-color'; + colorLabel.title = 'Text color'; + const colorInput = document.createElement('input'); + colorInput.type = 'color'; + colorInput.value = defaults.color; + colorLabel.append(colorInput); + + colorInput.addEventListener('mousedown', saveRange); + colorInput.addEventListener('change', () => { + restoreAndApply(() => applyInlineStyle(contentEl, 'color', colorInput.value)); + }); + + toolbar.append(alignGroup, sep(), formatGroup, sep(), fontGroup, sep(), colorLabel); + + // --- Selection state update --- + + const onSelectionChange = (): void => { + if (!contentEl.contains(window.getSelection()?.anchorNode ?? null)) return; + btnBold.classList.toggle('is-active', document.queryCommandState('bold')); + btnItalic.classList.toggle('is-active', document.queryCommandState('italic')); + btnUnderline.classList.toggle('is-active', document.queryCommandState('underline')); + }; + document.addEventListener('selectionchange', onSelectionChange); + + return { + element: toolbar, + cleanup: () => document.removeEventListener('selectionchange', onSelectionChange), + }; +} + +function applyInlineStyle(contentEl: HTMLElement, prop: string, value: string): void { + const sel = window.getSelection(); + if (!sel || sel.rangeCount === 0) return; + const range = sel.getRangeAt(0); + if (!contentEl.contains(range.commonAncestorContainer)) return; + if (sel.isCollapsed) return; + + try { + const fragment = range.extractContents(); + const span = document.createElement('span'); + span.style.setProperty(prop, value); + span.appendChild(fragment); + range.insertNode(span); + const newRange = document.createRange(); + newRange.selectNodeContents(span); + sel.removeAllRanges(); + sel.addRange(newRange); + } catch { + // Cross-block selections may fail — ignore + } +} diff --git a/src/ui/toolbar-controls.ts b/src/ui/toolbar-controls.ts new file mode 100644 index 0000000..6bc1f29 --- /dev/null +++ b/src/ui/toolbar-controls.ts @@ -0,0 +1,123 @@ +import type {CheckboxControl, RangeControl} from '../types'; + +export function createCommandButton(label: string, onClick: () => void): HTMLButtonElement { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'command-button'; + button.textContent = label; + button.addEventListener('click', onClick); + + return button; +} + +export function createNumberInput(label: string, value: number): HTMLInputElement { + const input = document.createElement('input'); + input.type = 'number'; + input.className = 'number-control'; + input.min = '64'; + input.max = '4096'; + input.step = '1'; + input.value = String(value); + input.placeholder = label; + input.title = label; + + return input; +} + +export function createCheckboxControl( + label: string, + checked: boolean, + onChange: (checked: boolean) => void, +): CheckboxControl { + const element = document.createElement('label'); + element.className = 'checkbox-control'; + + const input = document.createElement('input'); + input.type = 'checkbox'; + input.checked = checked; + input.addEventListener('change', () => onChange(input.checked)); + + const text = document.createElement('span'); + text.textContent = label; + element.append(input, text); + + return { + element, + setChecked: (nextChecked: boolean) => { + input.checked = nextChecked; + }, + }; +} + +export function createRangeControl(options: { + label: string; + min: number; + max: number; + value: number; + onChange: (value: number) => void; +}): RangeControl { + const element = document.createElement('label'); + element.className = 'range-control'; + element.textContent = options.label; + + const value = document.createElement('span'); + value.textContent = String(options.value); + + const input = document.createElement('input'); + input.type = 'range'; + input.min = String(options.min); + input.max = String(options.max); + input.value = String(options.value); + input.addEventListener('input', () => { + const nextValue = Number(input.value); + value.textContent = String(nextValue); + options.onChange(nextValue); + }); + + element.append(input, value); + + return { + element, + setValue: (nextValue: number) => { + input.value = String(nextValue); + value.textContent = String(nextValue); + }, + }; +} + +export function createSelectControl( + title: string, + options: readonly T[], + onChange: (value: T) => void, +): HTMLSelectElement { + const select = document.createElement('select'); + select.className = 'select-control'; + select.title = title; + + for (const option of options) { + const item = document.createElement('option'); + item.value = option; + item.textContent = option; + select.append(item); + } + + select.addEventListener('change', () => onChange(select.value as T)); + + return select; +} + +export function createGrid(className: string, ...children: HTMLElement[]): HTMLDivElement { + const grid = document.createElement('div'); + grid.className = className; + grid.append(...children); + + return grid; +} + +export function createToolbarSection(...children: HTMLElement[]): HTMLElement { + const section = document.createElement('section'); + section.className = 'toolbar__section'; + section.append(...children); + + return section; +} diff --git a/src/ui/toolbar-sections.ts b/src/ui/toolbar-sections.ts new file mode 100644 index 0000000..2cb2927 --- /dev/null +++ b/src/ui/toolbar-sections.ts @@ -0,0 +1,361 @@ +import {t} from '../core/i18n'; +import type {WebDraftEditor} from '../core/webdraft-editor'; +import {StatusReporter, Tool} from '../types'; +import {ToolbarSection, ToolConfig} from '../types/toolbar.ts'; +import {openCameraPanel} from './camera-panel'; +import {createColorPicker} from './color-picker'; +import { + createCheckboxControl, + createCommandButton, + createGrid, + createNumberInput, + createRangeControl, + createToolbarSection, +} from './toolbar-controls'; + +const tools: ToolConfig[] = [ + { + id: Tool.Select, + label: t.toolbar.toolSelect, + icon: '', + }, + { + id: Tool.Pencil, + label: t.toolbar.toolPencil, + icon: '', + }, + { + id: Tool.Eraser, + label: t.toolbar.toolEraser, + icon: '', + }, + { + id: Tool.Sampler, + label: t.toolbar.toolSampler, + icon: '', + }, + { + id: Tool.FillBucket, + label: t.toolbar.toolFillBucket, + icon: '', + }, + { + id: Tool.Web, + label: t.toolbar.toolWeb, + icon: '', + }, + { + id: Tool.Rectangle, + label: t.toolbar.toolRectangle, + icon: '', + }, + { + id: Tool.Ellipse, + label: t.toolbar.toolEllipse, + icon: '', + }, + { + id: Tool.Text, + label: t.toolbar.toolText, + icon: '', + }, +]; + +function createSvgIcon(content: string): SVGSVGElement { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('viewBox', '0 0 24 24'); + svg.setAttribute('width', '18'); + svg.setAttribute('height', '18'); + svg.setAttribute('fill', 'none'); + svg.setAttribute('stroke', 'currentColor'); + svg.setAttribute('stroke-width', '2'); + svg.setAttribute('stroke-linecap', 'round'); + svg.setAttribute('stroke-linejoin', 'round'); + svg.setAttribute('aria-hidden', 'true'); + svg.innerHTML = content; + return svg; +} + +function createEditButton(title: string, svgContent: string, onClick: () => void): HTMLButtonElement { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'command-button'; + button.title = title; + button.append(createSvgIcon(svgContent)); + button.addEventListener('click', onClick); + return button; +} + +export function createToolSection(editor: WebDraftEditor): ToolbarSection { + const toolGroup = document.createElement('div'); + toolGroup.className = 'toolbar__group'; + + const toolButtons = new Map(); + + for (const tool of tools) { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'tool-button'; + button.dataset.tool = tool.id; + button.title = tool.label; + button.append(createSvgIcon(tool.icon)); + button.addEventListener('click', () => { + editor.setTool(tool.id); + }); + + toolButtons.set(tool.id, button); + toolGroup.append(button); + } + + const transformGroup = createGrid( + 'toolbar__group', + createEditButton( + t.toolbar.invertColors, + '', + () => editor.invertActiveLayer(), + ), + createEditButton( + t.toolbar.rotateLeft, + '', + () => editor.rotateActiveLayer('left'), + ), + createEditButton( + t.toolbar.rotateRight, + '', + () => editor.rotateActiveLayer('right'), + ), + createEditButton( + t.toolbar.mirrorH, + '', + () => editor.mirrorActiveLayer('horizontal'), + ), + createEditButton( + t.toolbar.mirrorV, + '', + () => editor.mirrorActiveLayer('vertical'), + ), + ); + + return { + element: createToolbarSection(toolGroup, transformGroup), + sync: () => { + for (const [tool, button] of toolButtons) { + button.classList.toggle('is-active', tool === editor.state.activeTool); + } + }, + }; +} + +export function createStyleSection(editor: WebDraftEditor): ToolbarSection { + const sizeControl = createRangeControl({ + label: t.toolbar.size, + min: 1, + max: 120, + value: editor.state.size, + onChange: (value) => editor.setSize(value), + }); + + const webSensitivityControl = createRangeControl({ + label: t.toolbar.webSensitivity, + min: 20, + max: 260, + value: editor.state.webSensitivity, + onChange: (value) => editor.setWebSensitivity(value), + }); + + const colorPicker = createColorPicker({ + label: t.toolbar.color, + value: editor.state.color, + onChange: (color) => editor.setColor(color), + }); + + const fillToggle = createCheckboxControl(t.toolbar.fillShapes, editor.state.fillEnabled, (enabled) => { + editor.setFillEnabled(enabled); + }); + + const fillColorPicker = createColorPicker({ + label: t.toolbar.fillColor, + value: editor.state.fillColor, + onChange: (color) => editor.setFillColor(color), + }); + + const fillOpacityControl = createRangeControl({ + label: t.toolbar.fillOpacity, + min: 0, + max: 100, + value: editor.state.fillOpacity, + onChange: (value) => editor.setFillOpacity(value), + }); + + const fillToleranceControl = createRangeControl({ + label: t.toolbar.bucketTolerance, + min: 0, + max: 255, + value: editor.state.fillTolerance, + onChange: (value) => editor.setFillTolerance(value), + }); + + const syncVisibility = () => { + const tool = editor.state.activeTool; + const isShape = tool === Tool.Rectangle || tool === Tool.Ellipse; + const isBucket = tool === Tool.FillBucket; + const isWeb = tool === Tool.Web; + const hasBrushSize = tool === Tool.Pencil || tool === Tool.Eraser; + fillToggle.element.hidden = !isShape; + fillColorPicker.hidden = !isShape; + fillOpacityControl.element.hidden = !isShape; + fillToleranceControl.element.hidden = !isBucket; + webSensitivityControl.element.hidden = !isWeb; + sizeControl.element.hidden = !hasBrushSize && !isWeb && !isShape; + }; + + syncVisibility(); + + return { + element: createToolbarSection( + colorPicker, + fillToggle.element, + fillColorPicker, + fillOpacityControl.element, + fillToleranceControl.element, + sizeControl.element, + webSensitivityControl.element, + ), + sync: () => { + syncVisibility(); + colorPicker.setValue(editor.state.color); + fillToggle.setChecked(editor.state.fillEnabled); + fillColorPicker.setValue(editor.state.fillColor); + fillOpacityControl.setValue(editor.state.fillOpacity); + fillToleranceControl.setValue(editor.state.fillTolerance); + sizeControl.setValue(editor.state.size); + webSensitivityControl.setValue(editor.state.webSensitivity); + }, + }; +} + +export function createShadowSection(editor: WebDraftEditor): ToolbarSection { + const shadowToggle = createCheckboxControl(t.toolbar.shadow, editor.state.shadowEnabled, (enabled) => { + editor.setShadowEnabled(enabled); + }); + + const shadowColorPicker = createColorPicker({ + label: t.toolbar.shadowColor, + value: editor.state.shadowColor, + onChange: (color) => editor.setShadowColor(color), + }); + + const shadowBlurControl = createRangeControl({ + label: t.toolbar.shadowBlur, + min: 0, + max: 80, + value: editor.state.shadowBlur, + onChange: (value) => editor.setShadowBlur(value), + }); + + const shadowXControl = createRangeControl({ + label: t.toolbar.shadowX, + min: -120, + max: 120, + value: editor.state.shadowOffsetX, + onChange: (value) => editor.setShadowOffsetX(value), + }); + + const shadowYControl = createRangeControl({ + label: t.toolbar.shadowY, + min: -120, + max: 120, + value: editor.state.shadowOffsetY, + onChange: (value) => editor.setShadowOffsetY(value), + }); + + const syncShadowDetails = () => { + const visible = editor.state.shadowEnabled; + shadowColorPicker.hidden = !visible; + shadowBlurControl.element.hidden = !visible; + shadowXControl.element.hidden = !visible; + shadowYControl.element.hidden = !visible; + }; + + syncShadowDetails(); + + return { + element: createToolbarSection( + shadowToggle.element, + shadowColorPicker, + shadowBlurControl.element, + shadowXControl.element, + shadowYControl.element, + ), + sync: () => { + syncShadowDetails(); + shadowToggle.setChecked(editor.state.shadowEnabled); + shadowColorPicker.setValue(editor.state.shadowColor); + shadowBlurControl.setValue(editor.state.shadowBlur); + shadowXControl.setValue(editor.state.shadowOffsetX); + shadowYControl.setValue(editor.state.shadowOffsetY); + }, + }; +} + +export function createEditSection(editor: WebDraftEditor): ToolbarSection { + const clearButton = createEditButton( + t.toolbar.clear, + '', + () => editor.clear(), + ); + const undoButton = createEditButton( + t.toolbar.undo, + '', + () => editor.undo(), + ); + const redoButton = createEditButton( + t.toolbar.redo, + '', + () => editor.redo(), + ); + const copyButton = createEditButton( + t.toolbar.copy, + '', + () => editor.copySelection(), + ); + const cutButton = createEditButton( + t.toolbar.cut, + '', + () => editor.cutSelection(), + ); + const pasteButton = createEditButton( + t.toolbar.paste, + '', + () => editor.pasteSelection(), + ); + return { + element: createToolbarSection( + createGrid('command-grid', clearButton, undoButton, redoButton, copyButton, cutButton, pasteButton), + ), + sync: () => { + undoButton.disabled = !editor.canUndo; + redoButton.disabled = !editor.canRedo; + copyButton.disabled = !editor.hasSelection; + cutButton.disabled = !editor.hasSelection; + pasteButton.disabled = !editor.canPaste; + }, + }; +} + +export function createFileSection(editor: WebDraftEditor, status: StatusReporter): ToolbarSection { + const cameraButton = createCommandButton(t.toolbar.camera, () => { + openCameraPanel(editor, status); + }); + + return { + element: createToolbarSection(cameraButton), + sync: () => {}, + }; +} + +export function syncToolbarSections(sections: ToolbarSection[]): void { + for (const section of sections) { + section.sync(); + } +} diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts new file mode 100644 index 0000000..9553f7f --- /dev/null +++ b/src/ui/toolbar.ts @@ -0,0 +1,109 @@ +import {parseWdraftBinary} from '../core/project-file'; +import {t} from '../core/i18n'; +import type {WebDraftEditor} from '../core/webdraft-editor'; +import {StatusReporter} from '../types'; +import {bindKeyboardShortcuts} from './keyboard-shortcuts'; +import {openSaveDialog} from './save-dialog'; +import {openShortcutsDialog} from './shortcuts-dialog'; +import {getErrorMessage} from './status-toasts'; +import { + createFileSection, + createShadowSection, + createStyleSection, + createToolSection, + syncToolbarSections, +} from './toolbar-sections'; + +export function createToolbar(editor: WebDraftEditor, status: StatusReporter): HTMLElement { + const toolbar = document.createElement('div'); + toolbar.className = 'toolbar'; + + const title = document.createElement('h1'); + title.textContent = 'WebDraft'; + + const fileInput = createFileInput(editor, status); + const exportImage = async () => { + try { + const blob = await editor.exportPng(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = url; + link.download = t.file.imageFilename; + link.click(); + URL.revokeObjectURL(url); + status.show(t.toolbar.pngExportedOk, 'success'); + } catch (error) { + status.show(getErrorMessage(error), 'error'); + } + }; + + const sections = [ + createToolSection(editor), + createStyleSection(editor), + createShadowSection(editor), + createFileSection(editor, status), + ]; + + editor.addEventListener('change', () => syncToolbarSections(sections)); + syncToolbarSections(sections); + + toolbar.append(title, fileInput, ...sections.map((section) => section.element)); + + const saveProject = async () => { + try { + const blob = await editor.exportProject(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = t.file.projectFilename; + link.click(); + URL.revokeObjectURL(url); + status.show(t.file.savedOk, 'success'); + } catch (error) { + status.show(getErrorMessage(error), 'error'); + } + }; + + bindKeyboardShortcuts(editor, { + openImagePicker: () => fileInput.click(), + exportImage, + onSave: () => openSaveDialog((action) => { + if (action === 'png') void exportImage(); + else void saveProject(); + }), + openShortcutsDialog, + }); + + return toolbar; +} + +function createFileInput(editor: WebDraftEditor, status: StatusReporter): HTMLInputElement { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*,.wdraft'; + fileInput.className = 'visually-hidden'; + fileInput.addEventListener('change', () => { + const file = fileInput.files?.[0]; + if (!file) return; + void (async () => { + try { + if (file.name.endsWith('.wdraft')) { + const buffer = await file.arrayBuffer(); + const parsed = parseWdraftBinary(buffer); + await editor.importProject(parsed); + status.show(t.file.openedOk, 'success'); + } else { + await editor.importImage(file); + status.show(t.toolbar.imageImportedOk, 'success'); + } + } catch (error) { + status.show(getErrorMessage(error), 'error'); + } finally { + fileInput.value = ''; + } + })(); + }); + + return fileInput; +} diff --git a/tests/editor-geometry.test.mjs b/tests/editor-geometry.test.mjs new file mode 100644 index 0000000..eda0354 --- /dev/null +++ b/tests/editor-geometry.test.mjs @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import {importTypescriptModule, test} from './test-support.mjs'; + +const geometryModule = await importTypescriptModule('../src/core/editor-geometry.ts'); +const {fitNaturalSizeToCanvas, getBounds, getClippedPasteBounds, normalizeCanvasBounds, normalizeTextBounds} = + geometryModule; + +test('getBounds normalizes drag direction', () => { + assert.deepEqual(getBounds({x: 80, y: 90}, {x: 20, y: 10}), { + x: 20, + y: 10, + width: 60, + height: 80, + }); +}); + +test('normalizeCanvasBounds clips selection to the canvas', () => { + assert.deepEqual(normalizeCanvasBounds({x: -10, y: 5, width: 50, height: 110}, {width: 100, height: 80}), { + x: 0, + y: 5, + width: 40, + height: 75, + }); + + assert.equal(normalizeCanvasBounds({x: 120, y: 10, width: 20, height: 20}, {width: 100, height: 80}), null); +}); + +test('normalizeTextBounds keeps a useful minimum box inside the canvas', () => { + assert.deepEqual(normalizeTextBounds({x: 10, y: 20, width: 8, height: 8}, {width: 300, height: 200}), { + x: 10, + y: 20, + width: 160, + height: 48, + }); +}); + +test('fitNaturalSizeToCanvas scales large images and centers them', () => { + assert.deepEqual(fitNaturalSizeToCanvas({naturalWidth: 200, naturalHeight: 100}, {width: 100, height: 100}), { + x: 0, + y: 25, + width: 100, + height: 50, + }); +}); + +test('getClippedPasteBounds clips pasted pixels to the canvas', () => { + assert.deepEqual(getClippedPasteBounds({x: -4, y: 8}, {width: 10, height: 6}, {width: 20, height: 20}), { + targetX: 0, + targetY: 8, + sourceX: 4, + sourceY: 0, + width: 6, + height: 6, + }); + + assert.deepEqual(getClippedPasteBounds({x: 15, y: 18}, {width: 10, height: 6}, {width: 20, height: 20}), { + targetX: 15, + targetY: 18, + sourceX: 0, + sourceY: 0, + width: 5, + height: 2, + }); + + assert.equal(getClippedPasteBounds({x: 25, y: 5}, {width: 10, height: 6}, {width: 20, height: 20}), null); +}); + +console.log('Editor geometry tests passed.'); diff --git a/tests/flood-fill.test.mjs b/tests/flood-fill.test.mjs new file mode 100644 index 0000000..57c8ba8 --- /dev/null +++ b/tests/flood-fill.test.mjs @@ -0,0 +1,128 @@ +import assert from 'node:assert/strict'; +import {importTypescriptModule, test} from './test-support.mjs'; + +const floodFillModule = await importTypescriptModule('../src/core/flood-fill.ts'); +const {floodFillImageData, hexToRgbaColor} = floodFillModule; + +test('floodFillImageData fills only a contiguous matching region', () => { + const imageData = image(3, 3, [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [10, 10, 10, 255], + [0, 0, 0, 0], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + ]); + + const changed = floodFillImageData(imageData, 0, 0, {red: 200, green: 20, blue: 30, alpha: 255}); + + assert.equal(changed, true); + assert.deepEqual(pixels(imageData), [ + [200, 20, 30, 255], + [200, 20, 30, 255], + [10, 10, 10, 255], + [200, 20, 30, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + ]); +}); + +test('floodFillImageData returns false when replacement matches target', () => { + const imageData = image(1, 1, [[4, 5, 6, 255]]); + + assert.equal(floodFillImageData(imageData, 0, 0, {red: 4, green: 5, blue: 6, alpha: 255}), false); + assert.deepEqual(pixels(imageData), [[4, 5, 6, 255]]); +}); + +test('floodFillImageData ignores points outside image bounds', () => { + const imageData = image(1, 1, [[4, 5, 6, 255]]); + + assert.equal(floodFillImageData(imageData, 2, 0, {red: 9, green: 9, blue: 9, alpha: 255}), false); + assert.deepEqual(pixels(imageData), [[4, 5, 6, 255]]); +}); + +test('floodFillImageData fills similar colors when tolerance allows it', () => { + const imageData = image(3, 1, [ + [10, 10, 10, 255], + [12, 9, 10, 255], + [30, 30, 30, 255], + ]); + + const changed = floodFillImageData(imageData, 0, 0, {red: 200, green: 0, blue: 0, alpha: 255}, 3); + + assert.equal(changed, true); + assert.deepEqual(pixels(imageData), [ + [200, 0, 0, 255], + [200, 0, 0, 255], + [30, 30, 30, 255], + ]); +}); + +test('floodFillImageData keeps similar colors outside a zero tolerance fill', () => { + const imageData = image(2, 1, [ + [10, 10, 10, 255], + [11, 10, 10, 255], + ]); + + floodFillImageData(imageData, 0, 0, {red: 200, green: 0, blue: 0, alpha: 255}); + + assert.deepEqual(pixels(imageData), [ + [200, 0, 0, 255], + [11, 10, 10, 255], + ]); +}); + +test('floodFillImageData terminates when replacement color is inside tolerance', () => { + const imageData = image(2, 2, [ + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + [10, 10, 10, 255], + ]); + + const changed = floodFillImageData(imageData, 0, 0, {red: 12, green: 10, blue: 10, alpha: 255}, 3); + + assert.equal(changed, true); + assert.deepEqual(pixels(imageData), [ + [12, 10, 10, 255], + [12, 10, 10, 255], + [12, 10, 10, 255], + [12, 10, 10, 255], + ]); +}); + +test('hexToRgbaColor clamps opacity into alpha channel', () => { + assert.deepEqual(hexToRgbaColor('#336699', 0.5), {red: 51, green: 102, blue: 153, alpha: 128}); + assert.deepEqual(hexToRgbaColor('#336699', 2), {red: 51, green: 102, blue: 153, alpha: 255}); +}); + +console.log('Flood fill tests passed.'); + +function image(width, height, sourcePixels) { + return { + width, + height, + data: new Uint8ClampedArray(sourcePixels.flat()), + }; +} + +function pixels(imageData) { + const result = []; + + for (let index = 0; index < imageData.data.length; index += 4) { + result.push([ + imageData.data[index], + imageData.data[index + 1], + imageData.data[index + 2], + imageData.data[index + 3], + ]); + } + + return result; +} diff --git a/tests/history-manager.test.mjs b/tests/history-manager.test.mjs new file mode 100644 index 0000000..89e3f3c --- /dev/null +++ b/tests/history-manager.test.mjs @@ -0,0 +1,55 @@ +import assert from 'node:assert/strict'; +import {importTypescriptModule, test} from './test-support.mjs'; + +const historyModule = await importTypescriptModule('../src/core/history-manager.ts'); +const {HistoryManager} = historyModule; + +test('HistoryManager returns previous and next snapshots', () => { + const history = new HistoryManager(); + + history.push('a', 'b'); + + assert.equal(history.canUndo, true); + assert.equal(history.canRedo, false); + assert.equal(history.undo(), 'a'); + assert.equal(history.canUndo, false); + assert.equal(history.canRedo, true); + assert.equal(history.redo(), 'b'); + assert.equal(history.canUndo, true); + assert.equal(history.canRedo, false); +}); + +test('HistoryManager clears redo stack after a new branch', () => { + const history = new HistoryManager(); + + history.push('a', 'b'); + assert.equal(history.undo(), 'a'); + history.push('a', 'c'); + + assert.equal(history.canRedo, false); + assert.equal(history.redo(), null); + assert.equal(history.undo(), 'a'); +}); + +test('HistoryManager trims the oldest undo entries when the limit is exceeded', () => { + const history = new HistoryManager(2); + + history.push('a', 'b'); + history.push('b', 'c'); + history.push('c', 'd'); + + assert.equal(history.undo(), 'c'); + assert.equal(history.undo(), 'b'); + assert.equal(history.undo(), null); +}); + +test('HistoryManager can disable history with a zero limit', () => { + const history = new HistoryManager(0); + + history.push('a', 'b'); + + assert.equal(history.canUndo, false); + assert.equal(history.undo(), null); +}); + +console.log('History manager tests passed.'); diff --git a/tests/layer-transforms.test.mjs b/tests/layer-transforms.test.mjs new file mode 100644 index 0000000..2671e8c --- /dev/null +++ b/tests/layer-transforms.test.mjs @@ -0,0 +1,107 @@ +import assert from 'node:assert/strict'; +import {importTypescriptModule, test} from './test-support.mjs'; + +const transformModule = await importTypescriptModule('../src/core/layer-transforms.ts'); +const {invertPixelBuffer, mirrorPixelBuffer, rotatePixelBuffer} = transformModule; + +test('invertPixelBuffer inverts RGB channels and keeps alpha', () => { + const source = pixelBuffer(1, 2, [ + [10, 20, 30, 40], + [255, 128, 0, 255], + ]); + + const result = invertPixelBuffer(source); + + assertPixels(result, [ + [245, 235, 225, 40], + [0, 127, 255, 255], + ]); + assertPixels(source, [ + [10, 20, 30, 40], + [255, 128, 0, 255], + ]); +}); + +test('mirrorPixelBuffer mirrors horizontally', () => { + const source = pixelBuffer(2, 2, [ + [1, 0, 0, 255], + [2, 0, 0, 255], + [3, 0, 0, 255], + [4, 0, 0, 255], + ]); + + assertPixels(mirrorPixelBuffer(source, 'horizontal'), [ + [2, 0, 0, 255], + [1, 0, 0, 255], + [4, 0, 0, 255], + [3, 0, 0, 255], + ]); +}); + +test('mirrorPixelBuffer mirrors vertically', () => { + const source = pixelBuffer(2, 2, [ + [1, 0, 0, 255], + [2, 0, 0, 255], + [3, 0, 0, 255], + [4, 0, 0, 255], + ]); + + assertPixels(mirrorPixelBuffer(source, 'vertical'), [ + [3, 0, 0, 255], + [4, 0, 0, 255], + [1, 0, 0, 255], + [2, 0, 0, 255], + ]); +}); + +test('rotatePixelBuffer rotates square buffers right and left', () => { + const source = pixelBuffer(3, 3, [ + [1, 0, 0, 255], + [2, 0, 0, 255], + [3, 0, 0, 255], + [4, 0, 0, 255], + [5, 0, 0, 255], + [6, 0, 0, 255], + [7, 0, 0, 255], + [8, 0, 0, 255], + [9, 0, 0, 255], + ]); + + assertPixels(rotatePixelBuffer(source, 'right'), [ + [7, 0, 0, 255], + [4, 0, 0, 255], + [1, 0, 0, 255], + [8, 0, 0, 255], + [5, 0, 0, 255], + [2, 0, 0, 255], + [9, 0, 0, 255], + [6, 0, 0, 255], + [3, 0, 0, 255], + ]); + + assertPixels(rotatePixelBuffer(source, 'left'), [ + [3, 0, 0, 255], + [6, 0, 0, 255], + [9, 0, 0, 255], + [2, 0, 0, 255], + [5, 0, 0, 255], + [8, 0, 0, 255], + [1, 0, 0, 255], + [4, 0, 0, 255], + [7, 0, 0, 255], + ]); +}); + +console.log('Layer transform tests passed.'); + +function pixelBuffer(width, height, pixels) { + return { + width, + height, + data: new Uint8ClampedArray(pixels.flat()), + }; +} + +function assertPixels(buffer, expectedPixels) { + assert.deepEqual([...buffer.data], expectedPixels.flat()); +} diff --git a/tests/test-support.mjs b/tests/test-support.mjs new file mode 100644 index 0000000..9f8eaca --- /dev/null +++ b/tests/test-support.mjs @@ -0,0 +1,28 @@ +import {Buffer} from 'node:buffer'; +import {readFile} from 'node:fs/promises'; +import ts from 'typescript'; + +export async function importTypescriptModule(path) { + const url = new URL(path, import.meta.url); + const source = await readFile(url, 'utf8'); + const {outputText} = ts.transpileModule(source, { + compilerOptions: { + module: ts.ModuleKind.ES2022, + target: ts.ScriptTarget.ES2022, + strict: true, + }, + fileName: url.pathname, + }); + const encoded = Buffer.from(outputText).toString('base64'); + + return import(`data:text/javascript;base64,${encoded}`); +} + +export function test(name, run) { + try { + run(); + } catch (error) { + error.message = `${name}: ${error.message}`; + throw error; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..48d633f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true + }, + "include": ["src"] +} diff --git a/vendor/canvas-to-blob/canvas-toBlob.js b/vendor/canvas-to-blob/canvas-toBlob.js deleted file mode 100644 index 6d895a7..0000000 --- a/vendor/canvas-to-blob/canvas-toBlob.js +++ /dev/null @@ -1,124 +0,0 @@ -/* canvas-toBlob.js - * A canvas.toBlob() implementation. - * 2013-12-27 - * - * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr - * License: MIT - * See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md - */ - -/*global self */ -/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, - plusplus: true */ - -/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */ - -(function(view) { -"use strict"; -var - Uint8Array = view.Uint8Array - , HTMLCanvasElement = view.HTMLCanvasElement - , canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype - , is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i - , to_data_url = "toDataURL" - , base64_ranks - , decode_base64 = function(base64) { - var - len = base64.length - , buffer = new Uint8Array(len / 4 * 3 | 0) - , i = 0 - , outptr = 0 - , last = [0, 0] - , state = 0 - , save = 0 - , rank - , code - , undef - ; - while (len--) { - code = base64.charCodeAt(i++); - rank = base64_ranks[code-43]; - if (rank !== 255 && rank !== undef) { - last[1] = last[0]; - last[0] = code; - save = (save << 6) | rank; - state++; - if (state === 4) { - buffer[outptr++] = save >>> 16; - if (last[1] !== 61 /* padding character */) { - buffer[outptr++] = save >>> 8; - } - if (last[0] !== 61 /* padding character */) { - buffer[outptr++] = save; - } - state = 0; - } - } - } - // 2/3 chance there's going to be some null bytes at the end, but that - // doesn't really matter with most image formats. - // If it somehow matters for you, truncate the buffer up outptr. - return buffer; - } -; -if (Uint8Array) { - base64_ranks = new Uint8Array([ - 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1 - , -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 - , -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 - , 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - ]); -} -if (HTMLCanvasElement && !canvas_proto.toBlob) { - canvas_proto.toBlob = function(callback, type /*, ...args*/) { - if (!type) { - type = "image/png"; - } if (this.mozGetAsFile) { - callback(this.mozGetAsFile("canvas", type)); - return; - } if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) { - callback(this.msToBlob()); - return; - } - - var - args = Array.prototype.slice.call(arguments, 1) - , dataURI = this[to_data_url].apply(this, args) - , header_end = dataURI.indexOf(",") - , data = dataURI.substring(header_end + 1) - , is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)) - , blob - ; - if (Blob.fake) { - // no reason to decode a data: URI that's just going to become a data URI again - blob = new Blob - if (is_base64) { - blob.encoding = "base64"; - } else { - blob.encoding = "URI"; - } - blob.data = data; - blob.size = data.length; - } else if (Uint8Array) { - if (is_base64) { - blob = new Blob([decode_base64(data)], {type: type}); - } else { - blob = new Blob([decodeURIComponent(data)], {type: type}); - } - } - callback(blob); - }; - - if (canvas_proto.toDataURLHD) { - canvas_proto.toBlobHD = function() { - to_data_url = "toDataURLHD"; - var blob = this.toBlob(); - to_data_url = "toDataURL"; - return blob; - } - } else { - canvas_proto.toBlobHD = canvas_proto.toBlob; - } -} -}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); diff --git a/vendor/file-saver/FileSaver.min.js b/vendor/file-saver/FileSaver.min.js deleted file mode 100644 index eb062f2..0000000 --- a/vendor/file-saver/FileSaver.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ -var saveAs=saveAs||function(view){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var doc=view.document,get_URL=function(){return view.URL||view.webkitURL||view},save_link=doc.createElementNS("http://www.w3.org/1999/xhtml","a"),can_use_save_link="download"in save_link,click=function(node){var event=new MouseEvent("click");node.dispatchEvent(event)},is_safari=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),webkit_req_fs=view.webkitRequestFileSystem,req_fs=view.requestFileSystem||webkit_req_fs||view.mozRequestFileSystem,throw_outside=function(ex){(view.setImmediate||view.setTimeout)(function(){throw ex},0)},force_saveable_type="application/octet-stream",fs_min_size=0,arbitrary_revoke_timeout=500,revoke=function(file){var revoker=function(){if(typeof file==="string"){get_URL().revokeObjectURL(file)}else{file.remove()}};if(view.chrome){revoker()}else{setTimeout(revoker,arbitrary_revoke_timeout)}},dispatch=function(filesaver,event_types,event){event_types=[].concat(event_types);var i=event_types.length;while(i--){var listener=filesaver["on"+event_types[i]];if(typeof listener==="function"){try{listener.call(filesaver,event||filesaver)}catch(ex){throw_outside(ex)}}}},auto_bom=function(blob){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)){return new Blob(["\ufeff",blob],{type:blob.type})}return blob},FileSaver=function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}var filesaver=this,type=blob.type,blob_changed=false,object_url,target_view,dispatch_all=function(){dispatch(filesaver,"writestart progress write writeend".split(" "))},fs_error=function(){if(target_view&&is_safari&&typeof FileReader!=="undefined"){var reader=new FileReader;reader.onloadend=function(){var base64Data=reader.result;target_view.location.href="data:attachment/file"+base64Data.slice(base64Data.search(/[,;]/));filesaver.readyState=filesaver.DONE;dispatch_all()};reader.readAsDataURL(blob);filesaver.readyState=filesaver.INIT;return}if(blob_changed||!object_url){object_url=get_URL().createObjectURL(blob)}if(target_view){target_view.location.href=object_url}else{var new_tab=view.open(object_url,"_blank");if(new_tab==undefined&&is_safari){view.location.href=object_url}}filesaver.readyState=filesaver.DONE;dispatch_all();revoke(object_url)},abortable=function(func){return function(){if(filesaver.readyState!==filesaver.DONE){return func.apply(this,arguments)}}},create_if_not_found={create:true,exclusive:false},slice;filesaver.readyState=filesaver.INIT;if(!name){name="download"}if(can_use_save_link){object_url=get_URL().createObjectURL(blob);setTimeout(function(){save_link.href=object_url;save_link.download=name;click(save_link);dispatch_all();revoke(object_url);filesaver.readyState=filesaver.DONE});return}if(view.chrome&&type&&type!==force_saveable_type){slice=blob.slice||blob.webkitSlice;blob=slice.call(blob,0,blob.size,force_saveable_type);blob_changed=true}if(webkit_req_fs&&name!=="download"){name+=".download"}if(type===force_saveable_type||webkit_req_fs){target_view=view}if(!req_fs){fs_error();return}fs_min_size+=blob.size;req_fs(view.TEMPORARY,fs_min_size,abortable(function(fs){fs.root.getDirectory("saved",create_if_not_found,abortable(function(dir){var save=function(){dir.getFile(name,create_if_not_found,abortable(function(file){file.createWriter(abortable(function(writer){writer.onwriteend=function(event){target_view.location.href=file.toURL();filesaver.readyState=filesaver.DONE;dispatch(filesaver,"writeend",event);revoke(file)};writer.onerror=function(){var error=writer.error;if(error.code!==error.ABORT_ERR){fs_error()}};"writestart progress write abort".split(" ").forEach(function(event){writer["on"+event]=filesaver["on"+event]});writer.write(blob);filesaver.abort=function(){writer.abort();filesaver.readyState=filesaver.DONE};filesaver.readyState=filesaver.WRITING}),fs_error)}),fs_error)};dir.getFile(name,{create:false},abortable(function(file){file.remove();save()}),abortable(function(ex){if(ex.code===ex.NOT_FOUND_ERR){save()}else{fs_error()}}))}),fs_error)}),fs_error)},FS_proto=FileSaver.prototype,saveAs=function(blob,name,no_auto_bom){return new FileSaver(blob,name,no_auto_bom)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}return navigator.msSaveOrOpenBlob(blob,name||"download")}}FS_proto.abort=function(){var filesaver=this;filesaver.readyState=filesaver.DONE;dispatch(filesaver,"abort")};FS_proto.readyState=FS_proto.INIT=0;FS_proto.WRITING=1;FS_proto.DONE=2;FS_proto.error=FS_proto.onwritestart=FS_proto.onprogress=FS_proto.onwrite=FS_proto.onabort=FS_proto.onerror=FS_proto.onwriteend=null;return saveAs}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})} diff --git a/vendor/fontawesome/css/fa-brands.css b/vendor/fontawesome/css/fa-brands.css deleted file mode 100644 index 3e94b08..0000000 --- a/vendor/fontawesome/css/fa-brands.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face { - font-family: 'Font Awesome 5 Brands'; - font-style: normal; - font-weight: normal; - src: url("../webfonts/fa-brands-400.eot"); - src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } - -.fab { - font-family: 'Font Awesome 5 Brands'; } diff --git a/vendor/fontawesome/css/fa-brands.min.css b/vendor/fontawesome/css/fa-brands.min.css deleted file mode 100644 index 15046ce..0000000 --- a/vendor/fontawesome/css/fa-brands.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands} \ No newline at end of file diff --git a/vendor/fontawesome/css/fa-regular.css b/vendor/fontawesome/css/fa-regular.css deleted file mode 100644 index a78295a..0000000 --- a/vendor/fontawesome/css/fa-regular.css +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 400; - src: url("../webfonts/fa-regular-400.eot"); - src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); } - -.far { - font-family: 'Font Awesome 5 Free'; - font-weight: 400; } diff --git a/vendor/fontawesome/css/fa-regular.min.css b/vendor/fontawesome/css/fa-regular.min.css deleted file mode 100644 index ccdec70..0000000 --- a/vendor/fontawesome/css/fa-regular.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400} \ No newline at end of file diff --git a/vendor/fontawesome/css/fa-solid.css b/vendor/fontawesome/css/fa-solid.css deleted file mode 100644 index 81f51cc..0000000 --- a/vendor/fontawesome/css/fa-solid.css +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 900; - src: url("../webfonts/fa-solid-900.eot"); - src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); } - -.fa, -.fas { - font-family: 'Font Awesome 5 Free'; - font-weight: 900; } diff --git a/vendor/fontawesome/css/fa-solid.min.css b/vendor/fontawesome/css/fa-solid.min.css deleted file mode 100644 index b718a31..0000000 --- a/vendor/fontawesome/css/fa-solid.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900} \ No newline at end of file diff --git a/vendor/fontawesome/css/fontawesome-all.css b/vendor/fontawesome/css/fontawesome-all.css deleted file mode 100644 index e32cbcd..0000000 --- a/vendor/fontawesome/css/fontawesome-all.css +++ /dev/null @@ -1,2726 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa, -.fas, -.far, -.fal, -.fab { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; } - -.fa-lg { - font-size: 1.33333em; - line-height: 0.75em; - vertical-align: -.0667em; } - -.fa-xs { - font-size: .75em; } - -.fa-sm { - font-size: .875em; } - -.fa-1x { - font-size: 1em; } - -.fa-2x { - font-size: 2em; } - -.fa-3x { - font-size: 3em; } - -.fa-4x { - font-size: 4em; } - -.fa-5x { - font-size: 5em; } - -.fa-6x { - font-size: 6em; } - -.fa-7x { - font-size: 7em; } - -.fa-8x { - font-size: 8em; } - -.fa-9x { - font-size: 9em; } - -.fa-10x { - font-size: 10em; } - -.fa-fw { - text-align: center; - width: 1.25em; } - -.fa-ul { - list-style-type: none; - margin-left: 2.5em; - padding-left: 0; } - .fa-ul > li { - position: relative; } - -.fa-li { - left: -2em; - position: absolute; - text-align: center; - width: 2em; - line-height: inherit; } - -.fa-border { - border: solid 0.08em #eee; - border-radius: .1em; - padding: .2em .25em .15em; } - -.fa-pull-left { - float: left; } - -.fa-pull-right { - float: right; } - -.fa.fa-pull-left, -.fas.fa-pull-left, -.far.fa-pull-left, -.fal.fa-pull-left, -.fab.fa-pull-left { - margin-right: .3em; } - -.fa.fa-pull-right, -.fas.fa-pull-right, -.far.fa-pull-right, -.fal.fa-pull-right, -.fab.fa-pull-right { - margin-left: .3em; } - -.fa-spin { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; } - -.fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); } - -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -.fa-rotate-90 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); } - -.fa-rotate-180 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; - -webkit-transform: rotate(180deg); - transform: rotate(180deg); } - -.fa-rotate-270 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; - -webkit-transform: rotate(270deg); - transform: rotate(270deg); } - -.fa-flip-horizontal { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; - -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } - -.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(1, -1); - transform: scale(1, -1); } - -.fa-flip-horizontal.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } - -:root .fa-rotate-90, -:root .fa-rotate-180, -:root .fa-rotate-270, -:root .fa-flip-horizontal, -:root .fa-flip-vertical { - -webkit-filter: none; - filter: none; } - -.fa-stack { - display: inline-block; - height: 2em; - line-height: 2em; - position: relative; - vertical-align: middle; - width: 2em; } - -.fa-stack-1x, -.fa-stack-2x { - left: 0; - position: absolute; - text-align: center; - width: 100%; } - -.fa-stack-1x { - line-height: inherit; } - -.fa-stack-2x { - font-size: 2em; } - -.fa-inverse { - color: #fff; } - -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen -readers do not read off random characters that represent icons */ -.fa-500px:before { - content: "\f26e"; } - -.fa-accessible-icon:before { - content: "\f368"; } - -.fa-accusoft:before { - content: "\f369"; } - -.fa-address-book:before { - content: "\f2b9"; } - -.fa-address-card:before { - content: "\f2bb"; } - -.fa-adjust:before { - content: "\f042"; } - -.fa-adn:before { - content: "\f170"; } - -.fa-adversal:before { - content: "\f36a"; } - -.fa-affiliatetheme:before { - content: "\f36b"; } - -.fa-algolia:before { - content: "\f36c"; } - -.fa-align-center:before { - content: "\f037"; } - -.fa-align-justify:before { - content: "\f039"; } - -.fa-align-left:before { - content: "\f036"; } - -.fa-align-right:before { - content: "\f038"; } - -.fa-amazon:before { - content: "\f270"; } - -.fa-amazon-pay:before { - content: "\f42c"; } - -.fa-ambulance:before { - content: "\f0f9"; } - -.fa-american-sign-language-interpreting:before { - content: "\f2a3"; } - -.fa-amilia:before { - content: "\f36d"; } - -.fa-anchor:before { - content: "\f13d"; } - -.fa-android:before { - content: "\f17b"; } - -.fa-angellist:before { - content: "\f209"; } - -.fa-angle-double-down:before { - content: "\f103"; } - -.fa-angle-double-left:before { - content: "\f100"; } - -.fa-angle-double-right:before { - content: "\f101"; } - -.fa-angle-double-up:before { - content: "\f102"; } - -.fa-angle-down:before { - content: "\f107"; } - -.fa-angle-left:before { - content: "\f104"; } - -.fa-angle-right:before { - content: "\f105"; } - -.fa-angle-up:before { - content: "\f106"; } - -.fa-angrycreative:before { - content: "\f36e"; } - -.fa-angular:before { - content: "\f420"; } - -.fa-app-store:before { - content: "\f36f"; } - -.fa-app-store-ios:before { - content: "\f370"; } - -.fa-apper:before { - content: "\f371"; } - -.fa-apple:before { - content: "\f179"; } - -.fa-apple-pay:before { - content: "\f415"; } - -.fa-archive:before { - content: "\f187"; } - -.fa-arrow-alt-circle-down:before { - content: "\f358"; } - -.fa-arrow-alt-circle-left:before { - content: "\f359"; } - -.fa-arrow-alt-circle-right:before { - content: "\f35a"; } - -.fa-arrow-alt-circle-up:before { - content: "\f35b"; } - -.fa-arrow-circle-down:before { - content: "\f0ab"; } - -.fa-arrow-circle-left:before { - content: "\f0a8"; } - -.fa-arrow-circle-right:before { - content: "\f0a9"; } - -.fa-arrow-circle-up:before { - content: "\f0aa"; } - -.fa-arrow-down:before { - content: "\f063"; } - -.fa-arrow-left:before { - content: "\f060"; } - -.fa-arrow-right:before { - content: "\f061"; } - -.fa-arrow-up:before { - content: "\f062"; } - -.fa-arrows-alt:before { - content: "\f0b2"; } - -.fa-arrows-alt-h:before { - content: "\f337"; } - -.fa-arrows-alt-v:before { - content: "\f338"; } - -.fa-assistive-listening-systems:before { - content: "\f2a2"; } - -.fa-asterisk:before { - content: "\f069"; } - -.fa-asymmetrik:before { - content: "\f372"; } - -.fa-at:before { - content: "\f1fa"; } - -.fa-audible:before { - content: "\f373"; } - -.fa-audio-description:before { - content: "\f29e"; } - -.fa-autoprefixer:before { - content: "\f41c"; } - -.fa-avianex:before { - content: "\f374"; } - -.fa-aviato:before { - content: "\f421"; } - -.fa-aws:before { - content: "\f375"; } - -.fa-backward:before { - content: "\f04a"; } - -.fa-balance-scale:before { - content: "\f24e"; } - -.fa-ban:before { - content: "\f05e"; } - -.fa-band-aid:before { - content: "\f462"; } - -.fa-bandcamp:before { - content: "\f2d5"; } - -.fa-barcode:before { - content: "\f02a"; } - -.fa-bars:before { - content: "\f0c9"; } - -.fa-baseball-ball:before { - content: "\f433"; } - -.fa-basketball-ball:before { - content: "\f434"; } - -.fa-bath:before { - content: "\f2cd"; } - -.fa-battery-empty:before { - content: "\f244"; } - -.fa-battery-full:before { - content: "\f240"; } - -.fa-battery-half:before { - content: "\f242"; } - -.fa-battery-quarter:before { - content: "\f243"; } - -.fa-battery-three-quarters:before { - content: "\f241"; } - -.fa-bed:before { - content: "\f236"; } - -.fa-beer:before { - content: "\f0fc"; } - -.fa-behance:before { - content: "\f1b4"; } - -.fa-behance-square:before { - content: "\f1b5"; } - -.fa-bell:before { - content: "\f0f3"; } - -.fa-bell-slash:before { - content: "\f1f6"; } - -.fa-bicycle:before { - content: "\f206"; } - -.fa-bimobject:before { - content: "\f378"; } - -.fa-binoculars:before { - content: "\f1e5"; } - -.fa-birthday-cake:before { - content: "\f1fd"; } - -.fa-bitbucket:before { - content: "\f171"; } - -.fa-bitcoin:before { - content: "\f379"; } - -.fa-bity:before { - content: "\f37a"; } - -.fa-black-tie:before { - content: "\f27e"; } - -.fa-blackberry:before { - content: "\f37b"; } - -.fa-blind:before { - content: "\f29d"; } - -.fa-blogger:before { - content: "\f37c"; } - -.fa-blogger-b:before { - content: "\f37d"; } - -.fa-bluetooth:before { - content: "\f293"; } - -.fa-bluetooth-b:before { - content: "\f294"; } - -.fa-bold:before { - content: "\f032"; } - -.fa-bolt:before { - content: "\f0e7"; } - -.fa-bomb:before { - content: "\f1e2"; } - -.fa-book:before { - content: "\f02d"; } - -.fa-bookmark:before { - content: "\f02e"; } - -.fa-bowling-ball:before { - content: "\f436"; } - -.fa-box:before { - content: "\f466"; } - -.fa-boxes:before { - content: "\f468"; } - -.fa-braille:before { - content: "\f2a1"; } - -.fa-briefcase:before { - content: "\f0b1"; } - -.fa-btc:before { - content: "\f15a"; } - -.fa-bug:before { - content: "\f188"; } - -.fa-building:before { - content: "\f1ad"; } - -.fa-bullhorn:before { - content: "\f0a1"; } - -.fa-bullseye:before { - content: "\f140"; } - -.fa-buromobelexperte:before { - content: "\f37f"; } - -.fa-bus:before { - content: "\f207"; } - -.fa-buysellads:before { - content: "\f20d"; } - -.fa-calculator:before { - content: "\f1ec"; } - -.fa-calendar:before { - content: "\f133"; } - -.fa-calendar-alt:before { - content: "\f073"; } - -.fa-calendar-check:before { - content: "\f274"; } - -.fa-calendar-minus:before { - content: "\f272"; } - -.fa-calendar-plus:before { - content: "\f271"; } - -.fa-calendar-times:before { - content: "\f273"; } - -.fa-camera:before { - content: "\f030"; } - -.fa-camera-retro:before { - content: "\f083"; } - -.fa-car:before { - content: "\f1b9"; } - -.fa-caret-down:before { - content: "\f0d7"; } - -.fa-caret-left:before { - content: "\f0d9"; } - -.fa-caret-right:before { - content: "\f0da"; } - -.fa-caret-square-down:before { - content: "\f150"; } - -.fa-caret-square-left:before { - content: "\f191"; } - -.fa-caret-square-right:before { - content: "\f152"; } - -.fa-caret-square-up:before { - content: "\f151"; } - -.fa-caret-up:before { - content: "\f0d8"; } - -.fa-cart-arrow-down:before { - content: "\f218"; } - -.fa-cart-plus:before { - content: "\f217"; } - -.fa-cc-amazon-pay:before { - content: "\f42d"; } - -.fa-cc-amex:before { - content: "\f1f3"; } - -.fa-cc-apple-pay:before { - content: "\f416"; } - -.fa-cc-diners-club:before { - content: "\f24c"; } - -.fa-cc-discover:before { - content: "\f1f2"; } - -.fa-cc-jcb:before { - content: "\f24b"; } - -.fa-cc-mastercard:before { - content: "\f1f1"; } - -.fa-cc-paypal:before { - content: "\f1f4"; } - -.fa-cc-stripe:before { - content: "\f1f5"; } - -.fa-cc-visa:before { - content: "\f1f0"; } - -.fa-centercode:before { - content: "\f380"; } - -.fa-certificate:before { - content: "\f0a3"; } - -.fa-chart-area:before { - content: "\f1fe"; } - -.fa-chart-bar:before { - content: "\f080"; } - -.fa-chart-line:before { - content: "\f201"; } - -.fa-chart-pie:before { - content: "\f200"; } - -.fa-check:before { - content: "\f00c"; } - -.fa-check-circle:before { - content: "\f058"; } - -.fa-check-square:before { - content: "\f14a"; } - -.fa-chess:before { - content: "\f439"; } - -.fa-chess-bishop:before { - content: "\f43a"; } - -.fa-chess-board:before { - content: "\f43c"; } - -.fa-chess-king:before { - content: "\f43f"; } - -.fa-chess-knight:before { - content: "\f441"; } - -.fa-chess-pawn:before { - content: "\f443"; } - -.fa-chess-queen:before { - content: "\f445"; } - -.fa-chess-rook:before { - content: "\f447"; } - -.fa-chevron-circle-down:before { - content: "\f13a"; } - -.fa-chevron-circle-left:before { - content: "\f137"; } - -.fa-chevron-circle-right:before { - content: "\f138"; } - -.fa-chevron-circle-up:before { - content: "\f139"; } - -.fa-chevron-down:before { - content: "\f078"; } - -.fa-chevron-left:before { - content: "\f053"; } - -.fa-chevron-right:before { - content: "\f054"; } - -.fa-chevron-up:before { - content: "\f077"; } - -.fa-child:before { - content: "\f1ae"; } - -.fa-chrome:before { - content: "\f268"; } - -.fa-circle:before { - content: "\f111"; } - -.fa-circle-notch:before { - content: "\f1ce"; } - -.fa-clipboard:before { - content: "\f328"; } - -.fa-clipboard-check:before { - content: "\f46c"; } - -.fa-clipboard-list:before { - content: "\f46d"; } - -.fa-clock:before { - content: "\f017"; } - -.fa-clone:before { - content: "\f24d"; } - -.fa-closed-captioning:before { - content: "\f20a"; } - -.fa-cloud:before { - content: "\f0c2"; } - -.fa-cloud-download-alt:before { - content: "\f381"; } - -.fa-cloud-upload-alt:before { - content: "\f382"; } - -.fa-cloudscale:before { - content: "\f383"; } - -.fa-cloudsmith:before { - content: "\f384"; } - -.fa-cloudversify:before { - content: "\f385"; } - -.fa-code:before { - content: "\f121"; } - -.fa-code-branch:before { - content: "\f126"; } - -.fa-codepen:before { - content: "\f1cb"; } - -.fa-codiepie:before { - content: "\f284"; } - -.fa-coffee:before { - content: "\f0f4"; } - -.fa-cog:before { - content: "\f013"; } - -.fa-cogs:before { - content: "\f085"; } - -.fa-columns:before { - content: "\f0db"; } - -.fa-comment:before { - content: "\f075"; } - -.fa-comment-alt:before { - content: "\f27a"; } - -.fa-comments:before { - content: "\f086"; } - -.fa-compass:before { - content: "\f14e"; } - -.fa-compress:before { - content: "\f066"; } - -.fa-connectdevelop:before { - content: "\f20e"; } - -.fa-contao:before { - content: "\f26d"; } - -.fa-copy:before { - content: "\f0c5"; } - -.fa-copyright:before { - content: "\f1f9"; } - -.fa-cpanel:before { - content: "\f388"; } - -.fa-creative-commons:before { - content: "\f25e"; } - -.fa-credit-card:before { - content: "\f09d"; } - -.fa-crop:before { - content: "\f125"; } - -.fa-crosshairs:before { - content: "\f05b"; } - -.fa-css3:before { - content: "\f13c"; } - -.fa-css3-alt:before { - content: "\f38b"; } - -.fa-cube:before { - content: "\f1b2"; } - -.fa-cubes:before { - content: "\f1b3"; } - -.fa-cut:before { - content: "\f0c4"; } - -.fa-cuttlefish:before { - content: "\f38c"; } - -.fa-d-and-d:before { - content: "\f38d"; } - -.fa-dashcube:before { - content: "\f210"; } - -.fa-database:before { - content: "\f1c0"; } - -.fa-deaf:before { - content: "\f2a4"; } - -.fa-delicious:before { - content: "\f1a5"; } - -.fa-deploydog:before { - content: "\f38e"; } - -.fa-deskpro:before { - content: "\f38f"; } - -.fa-desktop:before { - content: "\f108"; } - -.fa-deviantart:before { - content: "\f1bd"; } - -.fa-digg:before { - content: "\f1a6"; } - -.fa-digital-ocean:before { - content: "\f391"; } - -.fa-discord:before { - content: "\f392"; } - -.fa-discourse:before { - content: "\f393"; } - -.fa-dna:before { - content: "\f471"; } - -.fa-dochub:before { - content: "\f394"; } - -.fa-docker:before { - content: "\f395"; } - -.fa-dollar-sign:before { - content: "\f155"; } - -.fa-dolly:before { - content: "\f472"; } - -.fa-dolly-flatbed:before { - content: "\f474"; } - -.fa-dot-circle:before { - content: "\f192"; } - -.fa-download:before { - content: "\f019"; } - -.fa-draft2digital:before { - content: "\f396"; } - -.fa-dribbble:before { - content: "\f17d"; } - -.fa-dribbble-square:before { - content: "\f397"; } - -.fa-dropbox:before { - content: "\f16b"; } - -.fa-drupal:before { - content: "\f1a9"; } - -.fa-dyalog:before { - content: "\f399"; } - -.fa-earlybirds:before { - content: "\f39a"; } - -.fa-edge:before { - content: "\f282"; } - -.fa-edit:before { - content: "\f044"; } - -.fa-eject:before { - content: "\f052"; } - -.fa-elementor:before { - content: "\f430"; } - -.fa-ellipsis-h:before { - content: "\f141"; } - -.fa-ellipsis-v:before { - content: "\f142"; } - -.fa-ember:before { - content: "\f423"; } - -.fa-empire:before { - content: "\f1d1"; } - -.fa-envelope:before { - content: "\f0e0"; } - -.fa-envelope-open:before { - content: "\f2b6"; } - -.fa-envelope-square:before { - content: "\f199"; } - -.fa-envira:before { - content: "\f299"; } - -.fa-eraser:before { - content: "\f12d"; } - -.fa-erlang:before { - content: "\f39d"; } - -.fa-ethereum:before { - content: "\f42e"; } - -.fa-etsy:before { - content: "\f2d7"; } - -.fa-euro-sign:before { - content: "\f153"; } - -.fa-exchange-alt:before { - content: "\f362"; } - -.fa-exclamation:before { - content: "\f12a"; } - -.fa-exclamation-circle:before { - content: "\f06a"; } - -.fa-exclamation-triangle:before { - content: "\f071"; } - -.fa-expand:before { - content: "\f065"; } - -.fa-expand-arrows-alt:before { - content: "\f31e"; } - -.fa-expeditedssl:before { - content: "\f23e"; } - -.fa-external-link-alt:before { - content: "\f35d"; } - -.fa-external-link-square-alt:before { - content: "\f360"; } - -.fa-eye:before { - content: "\f06e"; } - -.fa-eye-dropper:before { - content: "\f1fb"; } - -.fa-eye-slash:before { - content: "\f070"; } - -.fa-facebook:before { - content: "\f09a"; } - -.fa-facebook-f:before { - content: "\f39e"; } - -.fa-facebook-messenger:before { - content: "\f39f"; } - -.fa-facebook-square:before { - content: "\f082"; } - -.fa-fast-backward:before { - content: "\f049"; } - -.fa-fast-forward:before { - content: "\f050"; } - -.fa-fax:before { - content: "\f1ac"; } - -.fa-female:before { - content: "\f182"; } - -.fa-fighter-jet:before { - content: "\f0fb"; } - -.fa-file:before { - content: "\f15b"; } - -.fa-file-alt:before { - content: "\f15c"; } - -.fa-file-archive:before { - content: "\f1c6"; } - -.fa-file-audio:before { - content: "\f1c7"; } - -.fa-file-code:before { - content: "\f1c9"; } - -.fa-file-excel:before { - content: "\f1c3"; } - -.fa-file-image:before { - content: "\f1c5"; } - -.fa-file-pdf:before { - content: "\f1c1"; } - -.fa-file-powerpoint:before { - content: "\f1c4"; } - -.fa-file-video:before { - content: "\f1c8"; } - -.fa-file-word:before { - content: "\f1c2"; } - -.fa-film:before { - content: "\f008"; } - -.fa-filter:before { - content: "\f0b0"; } - -.fa-fire:before { - content: "\f06d"; } - -.fa-fire-extinguisher:before { - content: "\f134"; } - -.fa-firefox:before { - content: "\f269"; } - -.fa-first-aid:before { - content: "\f479"; } - -.fa-first-order:before { - content: "\f2b0"; } - -.fa-firstdraft:before { - content: "\f3a1"; } - -.fa-flag:before { - content: "\f024"; } - -.fa-flag-checkered:before { - content: "\f11e"; } - -.fa-flask:before { - content: "\f0c3"; } - -.fa-flickr:before { - content: "\f16e"; } - -.fa-flipboard:before { - content: "\f44d"; } - -.fa-fly:before { - content: "\f417"; } - -.fa-folder:before { - content: "\f07b"; } - -.fa-folder-open:before { - content: "\f07c"; } - -.fa-font:before { - content: "\f031"; } - -.fa-font-awesome:before { - content: "\f2b4"; } - -.fa-font-awesome-alt:before { - content: "\f35c"; } - -.fa-font-awesome-flag:before { - content: "\f425"; } - -.fa-fonticons:before { - content: "\f280"; } - -.fa-fonticons-fi:before { - content: "\f3a2"; } - -.fa-football-ball:before { - content: "\f44e"; } - -.fa-fort-awesome:before { - content: "\f286"; } - -.fa-fort-awesome-alt:before { - content: "\f3a3"; } - -.fa-forumbee:before { - content: "\f211"; } - -.fa-forward:before { - content: "\f04e"; } - -.fa-foursquare:before { - content: "\f180"; } - -.fa-free-code-camp:before { - content: "\f2c5"; } - -.fa-freebsd:before { - content: "\f3a4"; } - -.fa-frown:before { - content: "\f119"; } - -.fa-futbol:before { - content: "\f1e3"; } - -.fa-gamepad:before { - content: "\f11b"; } - -.fa-gavel:before { - content: "\f0e3"; } - -.fa-gem:before { - content: "\f3a5"; } - -.fa-genderless:before { - content: "\f22d"; } - -.fa-get-pocket:before { - content: "\f265"; } - -.fa-gg:before { - content: "\f260"; } - -.fa-gg-circle:before { - content: "\f261"; } - -.fa-gift:before { - content: "\f06b"; } - -.fa-git:before { - content: "\f1d3"; } - -.fa-git-square:before { - content: "\f1d2"; } - -.fa-github:before { - content: "\f09b"; } - -.fa-github-alt:before { - content: "\f113"; } - -.fa-github-square:before { - content: "\f092"; } - -.fa-gitkraken:before { - content: "\f3a6"; } - -.fa-gitlab:before { - content: "\f296"; } - -.fa-gitter:before { - content: "\f426"; } - -.fa-glass-martini:before { - content: "\f000"; } - -.fa-glide:before { - content: "\f2a5"; } - -.fa-glide-g:before { - content: "\f2a6"; } - -.fa-globe:before { - content: "\f0ac"; } - -.fa-gofore:before { - content: "\f3a7"; } - -.fa-golf-ball:before { - content: "\f450"; } - -.fa-goodreads:before { - content: "\f3a8"; } - -.fa-goodreads-g:before { - content: "\f3a9"; } - -.fa-google:before { - content: "\f1a0"; } - -.fa-google-drive:before { - content: "\f3aa"; } - -.fa-google-play:before { - content: "\f3ab"; } - -.fa-google-plus:before { - content: "\f2b3"; } - -.fa-google-plus-g:before { - content: "\f0d5"; } - -.fa-google-plus-square:before { - content: "\f0d4"; } - -.fa-google-wallet:before { - content: "\f1ee"; } - -.fa-graduation-cap:before { - content: "\f19d"; } - -.fa-gratipay:before { - content: "\f184"; } - -.fa-grav:before { - content: "\f2d6"; } - -.fa-gripfire:before { - content: "\f3ac"; } - -.fa-grunt:before { - content: "\f3ad"; } - -.fa-gulp:before { - content: "\f3ae"; } - -.fa-h-square:before { - content: "\f0fd"; } - -.fa-hacker-news:before { - content: "\f1d4"; } - -.fa-hacker-news-square:before { - content: "\f3af"; } - -.fa-hand-lizard:before { - content: "\f258"; } - -.fa-hand-paper:before { - content: "\f256"; } - -.fa-hand-peace:before { - content: "\f25b"; } - -.fa-hand-point-down:before { - content: "\f0a7"; } - -.fa-hand-point-left:before { - content: "\f0a5"; } - -.fa-hand-point-right:before { - content: "\f0a4"; } - -.fa-hand-point-up:before { - content: "\f0a6"; } - -.fa-hand-pointer:before { - content: "\f25a"; } - -.fa-hand-rock:before { - content: "\f255"; } - -.fa-hand-scissors:before { - content: "\f257"; } - -.fa-hand-spock:before { - content: "\f259"; } - -.fa-handshake:before { - content: "\f2b5"; } - -.fa-hashtag:before { - content: "\f292"; } - -.fa-hdd:before { - content: "\f0a0"; } - -.fa-heading:before { - content: "\f1dc"; } - -.fa-headphones:before { - content: "\f025"; } - -.fa-heart:before { - content: "\f004"; } - -.fa-heartbeat:before { - content: "\f21e"; } - -.fa-hips:before { - content: "\f452"; } - -.fa-hire-a-helper:before { - content: "\f3b0"; } - -.fa-history:before { - content: "\f1da"; } - -.fa-hockey-puck:before { - content: "\f453"; } - -.fa-home:before { - content: "\f015"; } - -.fa-hooli:before { - content: "\f427"; } - -.fa-hospital:before { - content: "\f0f8"; } - -.fa-hospital-symbol:before { - content: "\f47e"; } - -.fa-hotjar:before { - content: "\f3b1"; } - -.fa-hourglass:before { - content: "\f254"; } - -.fa-hourglass-end:before { - content: "\f253"; } - -.fa-hourglass-half:before { - content: "\f252"; } - -.fa-hourglass-start:before { - content: "\f251"; } - -.fa-houzz:before { - content: "\f27c"; } - -.fa-html5:before { - content: "\f13b"; } - -.fa-hubspot:before { - content: "\f3b2"; } - -.fa-i-cursor:before { - content: "\f246"; } - -.fa-id-badge:before { - content: "\f2c1"; } - -.fa-id-card:before { - content: "\f2c2"; } - -.fa-image:before { - content: "\f03e"; } - -.fa-images:before { - content: "\f302"; } - -.fa-imdb:before { - content: "\f2d8"; } - -.fa-inbox:before { - content: "\f01c"; } - -.fa-indent:before { - content: "\f03c"; } - -.fa-industry:before { - content: "\f275"; } - -.fa-info:before { - content: "\f129"; } - -.fa-info-circle:before { - content: "\f05a"; } - -.fa-instagram:before { - content: "\f16d"; } - -.fa-internet-explorer:before { - content: "\f26b"; } - -.fa-ioxhost:before { - content: "\f208"; } - -.fa-italic:before { - content: "\f033"; } - -.fa-itunes:before { - content: "\f3b4"; } - -.fa-itunes-note:before { - content: "\f3b5"; } - -.fa-jenkins:before { - content: "\f3b6"; } - -.fa-joget:before { - content: "\f3b7"; } - -.fa-joomla:before { - content: "\f1aa"; } - -.fa-js:before { - content: "\f3b8"; } - -.fa-js-square:before { - content: "\f3b9"; } - -.fa-jsfiddle:before { - content: "\f1cc"; } - -.fa-key:before { - content: "\f084"; } - -.fa-keyboard:before { - content: "\f11c"; } - -.fa-keycdn:before { - content: "\f3ba"; } - -.fa-kickstarter:before { - content: "\f3bb"; } - -.fa-kickstarter-k:before { - content: "\f3bc"; } - -.fa-korvue:before { - content: "\f42f"; } - -.fa-language:before { - content: "\f1ab"; } - -.fa-laptop:before { - content: "\f109"; } - -.fa-laravel:before { - content: "\f3bd"; } - -.fa-lastfm:before { - content: "\f202"; } - -.fa-lastfm-square:before { - content: "\f203"; } - -.fa-leaf:before { - content: "\f06c"; } - -.fa-leanpub:before { - content: "\f212"; } - -.fa-lemon:before { - content: "\f094"; } - -.fa-less:before { - content: "\f41d"; } - -.fa-level-down-alt:before { - content: "\f3be"; } - -.fa-level-up-alt:before { - content: "\f3bf"; } - -.fa-life-ring:before { - content: "\f1cd"; } - -.fa-lightbulb:before { - content: "\f0eb"; } - -.fa-line:before { - content: "\f3c0"; } - -.fa-link:before { - content: "\f0c1"; } - -.fa-linkedin:before { - content: "\f08c"; } - -.fa-linkedin-in:before { - content: "\f0e1"; } - -.fa-linode:before { - content: "\f2b8"; } - -.fa-linux:before { - content: "\f17c"; } - -.fa-lira-sign:before { - content: "\f195"; } - -.fa-list:before { - content: "\f03a"; } - -.fa-list-alt:before { - content: "\f022"; } - -.fa-list-ol:before { - content: "\f0cb"; } - -.fa-list-ul:before { - content: "\f0ca"; } - -.fa-location-arrow:before { - content: "\f124"; } - -.fa-lock:before { - content: "\f023"; } - -.fa-lock-open:before { - content: "\f3c1"; } - -.fa-long-arrow-alt-down:before { - content: "\f309"; } - -.fa-long-arrow-alt-left:before { - content: "\f30a"; } - -.fa-long-arrow-alt-right:before { - content: "\f30b"; } - -.fa-long-arrow-alt-up:before { - content: "\f30c"; } - -.fa-low-vision:before { - content: "\f2a8"; } - -.fa-lyft:before { - content: "\f3c3"; } - -.fa-magento:before { - content: "\f3c4"; } - -.fa-magic:before { - content: "\f0d0"; } - -.fa-magnet:before { - content: "\f076"; } - -.fa-male:before { - content: "\f183"; } - -.fa-map:before { - content: "\f279"; } - -.fa-map-marker:before { - content: "\f041"; } - -.fa-map-marker-alt:before { - content: "\f3c5"; } - -.fa-map-pin:before { - content: "\f276"; } - -.fa-map-signs:before { - content: "\f277"; } - -.fa-mars:before { - content: "\f222"; } - -.fa-mars-double:before { - content: "\f227"; } - -.fa-mars-stroke:before { - content: "\f229"; } - -.fa-mars-stroke-h:before { - content: "\f22b"; } - -.fa-mars-stroke-v:before { - content: "\f22a"; } - -.fa-maxcdn:before { - content: "\f136"; } - -.fa-medapps:before { - content: "\f3c6"; } - -.fa-medium:before { - content: "\f23a"; } - -.fa-medium-m:before { - content: "\f3c7"; } - -.fa-medkit:before { - content: "\f0fa"; } - -.fa-medrt:before { - content: "\f3c8"; } - -.fa-meetup:before { - content: "\f2e0"; } - -.fa-meh:before { - content: "\f11a"; } - -.fa-mercury:before { - content: "\f223"; } - -.fa-microchip:before { - content: "\f2db"; } - -.fa-microphone:before { - content: "\f130"; } - -.fa-microphone-slash:before { - content: "\f131"; } - -.fa-microsoft:before { - content: "\f3ca"; } - -.fa-minus:before { - content: "\f068"; } - -.fa-minus-circle:before { - content: "\f056"; } - -.fa-minus-square:before { - content: "\f146"; } - -.fa-mix:before { - content: "\f3cb"; } - -.fa-mixcloud:before { - content: "\f289"; } - -.fa-mizuni:before { - content: "\f3cc"; } - -.fa-mobile:before { - content: "\f10b"; } - -.fa-mobile-alt:before { - content: "\f3cd"; } - -.fa-modx:before { - content: "\f285"; } - -.fa-monero:before { - content: "\f3d0"; } - -.fa-money-bill-alt:before { - content: "\f3d1"; } - -.fa-moon:before { - content: "\f186"; } - -.fa-motorcycle:before { - content: "\f21c"; } - -.fa-mouse-pointer:before { - content: "\f245"; } - -.fa-music:before { - content: "\f001"; } - -.fa-napster:before { - content: "\f3d2"; } - -.fa-neuter:before { - content: "\f22c"; } - -.fa-newspaper:before { - content: "\f1ea"; } - -.fa-nintendo-switch:before { - content: "\f418"; } - -.fa-node:before { - content: "\f419"; } - -.fa-node-js:before { - content: "\f3d3"; } - -.fa-npm:before { - content: "\f3d4"; } - -.fa-ns8:before { - content: "\f3d5"; } - -.fa-nutritionix:before { - content: "\f3d6"; } - -.fa-object-group:before { - content: "\f247"; } - -.fa-object-ungroup:before { - content: "\f248"; } - -.fa-odnoklassniki:before { - content: "\f263"; } - -.fa-odnoklassniki-square:before { - content: "\f264"; } - -.fa-opencart:before { - content: "\f23d"; } - -.fa-openid:before { - content: "\f19b"; } - -.fa-opera:before { - content: "\f26a"; } - -.fa-optin-monster:before { - content: "\f23c"; } - -.fa-osi:before { - content: "\f41a"; } - -.fa-outdent:before { - content: "\f03b"; } - -.fa-page4:before { - content: "\f3d7"; } - -.fa-pagelines:before { - content: "\f18c"; } - -.fa-paint-brush:before { - content: "\f1fc"; } - -.fa-palfed:before { - content: "\f3d8"; } - -.fa-pallet:before { - content: "\f482"; } - -.fa-paper-plane:before { - content: "\f1d8"; } - -.fa-paperclip:before { - content: "\f0c6"; } - -.fa-paragraph:before { - content: "\f1dd"; } - -.fa-paste:before { - content: "\f0ea"; } - -.fa-patreon:before { - content: "\f3d9"; } - -.fa-pause:before { - content: "\f04c"; } - -.fa-pause-circle:before { - content: "\f28b"; } - -.fa-paw:before { - content: "\f1b0"; } - -.fa-paypal:before { - content: "\f1ed"; } - -.fa-pen-square:before { - content: "\f14b"; } - -.fa-pencil-alt:before { - content: "\f303"; } - -.fa-percent:before { - content: "\f295"; } - -.fa-periscope:before { - content: "\f3da"; } - -.fa-phabricator:before { - content: "\f3db"; } - -.fa-phoenix-framework:before { - content: "\f3dc"; } - -.fa-phone:before { - content: "\f095"; } - -.fa-phone-square:before { - content: "\f098"; } - -.fa-phone-volume:before { - content: "\f2a0"; } - -.fa-php:before { - content: "\f457"; } - -.fa-pied-piper:before { - content: "\f2ae"; } - -.fa-pied-piper-alt:before { - content: "\f1a8"; } - -.fa-pied-piper-pp:before { - content: "\f1a7"; } - -.fa-pills:before { - content: "\f484"; } - -.fa-pinterest:before { - content: "\f0d2"; } - -.fa-pinterest-p:before { - content: "\f231"; } - -.fa-pinterest-square:before { - content: "\f0d3"; } - -.fa-plane:before { - content: "\f072"; } - -.fa-play:before { - content: "\f04b"; } - -.fa-play-circle:before { - content: "\f144"; } - -.fa-playstation:before { - content: "\f3df"; } - -.fa-plug:before { - content: "\f1e6"; } - -.fa-plus:before { - content: "\f067"; } - -.fa-plus-circle:before { - content: "\f055"; } - -.fa-plus-square:before { - content: "\f0fe"; } - -.fa-podcast:before { - content: "\f2ce"; } - -.fa-pound-sign:before { - content: "\f154"; } - -.fa-power-off:before { - content: "\f011"; } - -.fa-print:before { - content: "\f02f"; } - -.fa-product-hunt:before { - content: "\f288"; } - -.fa-pushed:before { - content: "\f3e1"; } - -.fa-puzzle-piece:before { - content: "\f12e"; } - -.fa-python:before { - content: "\f3e2"; } - -.fa-qq:before { - content: "\f1d6"; } - -.fa-qrcode:before { - content: "\f029"; } - -.fa-question:before { - content: "\f128"; } - -.fa-question-circle:before { - content: "\f059"; } - -.fa-quidditch:before { - content: "\f458"; } - -.fa-quinscape:before { - content: "\f459"; } - -.fa-quora:before { - content: "\f2c4"; } - -.fa-quote-left:before { - content: "\f10d"; } - -.fa-quote-right:before { - content: "\f10e"; } - -.fa-random:before { - content: "\f074"; } - -.fa-ravelry:before { - content: "\f2d9"; } - -.fa-react:before { - content: "\f41b"; } - -.fa-rebel:before { - content: "\f1d0"; } - -.fa-recycle:before { - content: "\f1b8"; } - -.fa-red-river:before { - content: "\f3e3"; } - -.fa-reddit:before { - content: "\f1a1"; } - -.fa-reddit-alien:before { - content: "\f281"; } - -.fa-reddit-square:before { - content: "\f1a2"; } - -.fa-redo:before { - content: "\f01e"; } - -.fa-redo-alt:before { - content: "\f2f9"; } - -.fa-registered:before { - content: "\f25d"; } - -.fa-rendact:before { - content: "\f3e4"; } - -.fa-renren:before { - content: "\f18b"; } - -.fa-reply:before { - content: "\f3e5"; } - -.fa-reply-all:before { - content: "\f122"; } - -.fa-replyd:before { - content: "\f3e6"; } - -.fa-resolving:before { - content: "\f3e7"; } - -.fa-retweet:before { - content: "\f079"; } - -.fa-road:before { - content: "\f018"; } - -.fa-rocket:before { - content: "\f135"; } - -.fa-rocketchat:before { - content: "\f3e8"; } - -.fa-rockrms:before { - content: "\f3e9"; } - -.fa-rss:before { - content: "\f09e"; } - -.fa-rss-square:before { - content: "\f143"; } - -.fa-ruble-sign:before { - content: "\f158"; } - -.fa-rupee-sign:before { - content: "\f156"; } - -.fa-safari:before { - content: "\f267"; } - -.fa-sass:before { - content: "\f41e"; } - -.fa-save:before { - content: "\f0c7"; } - -.fa-schlix:before { - content: "\f3ea"; } - -.fa-scribd:before { - content: "\f28a"; } - -.fa-search:before { - content: "\f002"; } - -.fa-search-minus:before { - content: "\f010"; } - -.fa-search-plus:before { - content: "\f00e"; } - -.fa-searchengin:before { - content: "\f3eb"; } - -.fa-sellcast:before { - content: "\f2da"; } - -.fa-sellsy:before { - content: "\f213"; } - -.fa-server:before { - content: "\f233"; } - -.fa-servicestack:before { - content: "\f3ec"; } - -.fa-share:before { - content: "\f064"; } - -.fa-share-alt:before { - content: "\f1e0"; } - -.fa-share-alt-square:before { - content: "\f1e1"; } - -.fa-share-square:before { - content: "\f14d"; } - -.fa-shekel-sign:before { - content: "\f20b"; } - -.fa-shield-alt:before { - content: "\f3ed"; } - -.fa-ship:before { - content: "\f21a"; } - -.fa-shipping-fast:before { - content: "\f48b"; } - -.fa-shirtsinbulk:before { - content: "\f214"; } - -.fa-shopping-bag:before { - content: "\f290"; } - -.fa-shopping-basket:before { - content: "\f291"; } - -.fa-shopping-cart:before { - content: "\f07a"; } - -.fa-shower:before { - content: "\f2cc"; } - -.fa-sign-in-alt:before { - content: "\f2f6"; } - -.fa-sign-language:before { - content: "\f2a7"; } - -.fa-sign-out-alt:before { - content: "\f2f5"; } - -.fa-signal:before { - content: "\f012"; } - -.fa-simplybuilt:before { - content: "\f215"; } - -.fa-sistrix:before { - content: "\f3ee"; } - -.fa-sitemap:before { - content: "\f0e8"; } - -.fa-skyatlas:before { - content: "\f216"; } - -.fa-skype:before { - content: "\f17e"; } - -.fa-slack:before { - content: "\f198"; } - -.fa-slack-hash:before { - content: "\f3ef"; } - -.fa-sliders-h:before { - content: "\f1de"; } - -.fa-slideshare:before { - content: "\f1e7"; } - -.fa-smile:before { - content: "\f118"; } - -.fa-snapchat:before { - content: "\f2ab"; } - -.fa-snapchat-ghost:before { - content: "\f2ac"; } - -.fa-snapchat-square:before { - content: "\f2ad"; } - -.fa-snowflake:before { - content: "\f2dc"; } - -.fa-sort:before { - content: "\f0dc"; } - -.fa-sort-alpha-down:before { - content: "\f15d"; } - -.fa-sort-alpha-up:before { - content: "\f15e"; } - -.fa-sort-amount-down:before { - content: "\f160"; } - -.fa-sort-amount-up:before { - content: "\f161"; } - -.fa-sort-down:before { - content: "\f0dd"; } - -.fa-sort-numeric-down:before { - content: "\f162"; } - -.fa-sort-numeric-up:before { - content: "\f163"; } - -.fa-sort-up:before { - content: "\f0de"; } - -.fa-soundcloud:before { - content: "\f1be"; } - -.fa-space-shuttle:before { - content: "\f197"; } - -.fa-speakap:before { - content: "\f3f3"; } - -.fa-spinner:before { - content: "\f110"; } - -.fa-spotify:before { - content: "\f1bc"; } - -.fa-square:before { - content: "\f0c8"; } - -.fa-square-full:before { - content: "\f45c"; } - -.fa-stack-exchange:before { - content: "\f18d"; } - -.fa-stack-overflow:before { - content: "\f16c"; } - -.fa-star:before { - content: "\f005"; } - -.fa-star-half:before { - content: "\f089"; } - -.fa-staylinked:before { - content: "\f3f5"; } - -.fa-steam:before { - content: "\f1b6"; } - -.fa-steam-square:before { - content: "\f1b7"; } - -.fa-steam-symbol:before { - content: "\f3f6"; } - -.fa-step-backward:before { - content: "\f048"; } - -.fa-step-forward:before { - content: "\f051"; } - -.fa-stethoscope:before { - content: "\f0f1"; } - -.fa-sticker-mule:before { - content: "\f3f7"; } - -.fa-sticky-note:before { - content: "\f249"; } - -.fa-stop:before { - content: "\f04d"; } - -.fa-stop-circle:before { - content: "\f28d"; } - -.fa-stopwatch:before { - content: "\f2f2"; } - -.fa-strava:before { - content: "\f428"; } - -.fa-street-view:before { - content: "\f21d"; } - -.fa-strikethrough:before { - content: "\f0cc"; } - -.fa-stripe:before { - content: "\f429"; } - -.fa-stripe-s:before { - content: "\f42a"; } - -.fa-studiovinari:before { - content: "\f3f8"; } - -.fa-stumbleupon:before { - content: "\f1a4"; } - -.fa-stumbleupon-circle:before { - content: "\f1a3"; } - -.fa-subscript:before { - content: "\f12c"; } - -.fa-subway:before { - content: "\f239"; } - -.fa-suitcase:before { - content: "\f0f2"; } - -.fa-sun:before { - content: "\f185"; } - -.fa-superpowers:before { - content: "\f2dd"; } - -.fa-superscript:before { - content: "\f12b"; } - -.fa-supple:before { - content: "\f3f9"; } - -.fa-sync:before { - content: "\f021"; } - -.fa-sync-alt:before { - content: "\f2f1"; } - -.fa-syringe:before { - content: "\f48e"; } - -.fa-table:before { - content: "\f0ce"; } - -.fa-table-tennis:before { - content: "\f45d"; } - -.fa-tablet:before { - content: "\f10a"; } - -.fa-tablet-alt:before { - content: "\f3fa"; } - -.fa-tachometer-alt:before { - content: "\f3fd"; } - -.fa-tag:before { - content: "\f02b"; } - -.fa-tags:before { - content: "\f02c"; } - -.fa-tasks:before { - content: "\f0ae"; } - -.fa-taxi:before { - content: "\f1ba"; } - -.fa-telegram:before { - content: "\f2c6"; } - -.fa-telegram-plane:before { - content: "\f3fe"; } - -.fa-tencent-weibo:before { - content: "\f1d5"; } - -.fa-terminal:before { - content: "\f120"; } - -.fa-text-height:before { - content: "\f034"; } - -.fa-text-width:before { - content: "\f035"; } - -.fa-th:before { - content: "\f00a"; } - -.fa-th-large:before { - content: "\f009"; } - -.fa-th-list:before { - content: "\f00b"; } - -.fa-themeisle:before { - content: "\f2b2"; } - -.fa-thermometer:before { - content: "\f491"; } - -.fa-thermometer-empty:before { - content: "\f2cb"; } - -.fa-thermometer-full:before { - content: "\f2c7"; } - -.fa-thermometer-half:before { - content: "\f2c9"; } - -.fa-thermometer-quarter:before { - content: "\f2ca"; } - -.fa-thermometer-three-quarters:before { - content: "\f2c8"; } - -.fa-thumbs-down:before { - content: "\f165"; } - -.fa-thumbs-up:before { - content: "\f164"; } - -.fa-thumbtack:before { - content: "\f08d"; } - -.fa-ticket-alt:before { - content: "\f3ff"; } - -.fa-times:before { - content: "\f00d"; } - -.fa-times-circle:before { - content: "\f057"; } - -.fa-tint:before { - content: "\f043"; } - -.fa-toggle-off:before { - content: "\f204"; } - -.fa-toggle-on:before { - content: "\f205"; } - -.fa-trademark:before { - content: "\f25c"; } - -.fa-train:before { - content: "\f238"; } - -.fa-transgender:before { - content: "\f224"; } - -.fa-transgender-alt:before { - content: "\f225"; } - -.fa-trash:before { - content: "\f1f8"; } - -.fa-trash-alt:before { - content: "\f2ed"; } - -.fa-tree:before { - content: "\f1bb"; } - -.fa-trello:before { - content: "\f181"; } - -.fa-tripadvisor:before { - content: "\f262"; } - -.fa-trophy:before { - content: "\f091"; } - -.fa-truck:before { - content: "\f0d1"; } - -.fa-tty:before { - content: "\f1e4"; } - -.fa-tumblr:before { - content: "\f173"; } - -.fa-tumblr-square:before { - content: "\f174"; } - -.fa-tv:before { - content: "\f26c"; } - -.fa-twitch:before { - content: "\f1e8"; } - -.fa-twitter:before { - content: "\f099"; } - -.fa-twitter-square:before { - content: "\f081"; } - -.fa-typo3:before { - content: "\f42b"; } - -.fa-uber:before { - content: "\f402"; } - -.fa-uikit:before { - content: "\f403"; } - -.fa-umbrella:before { - content: "\f0e9"; } - -.fa-underline:before { - content: "\f0cd"; } - -.fa-undo:before { - content: "\f0e2"; } - -.fa-undo-alt:before { - content: "\f2ea"; } - -.fa-uniregistry:before { - content: "\f404"; } - -.fa-universal-access:before { - content: "\f29a"; } - -.fa-university:before { - content: "\f19c"; } - -.fa-unlink:before { - content: "\f127"; } - -.fa-unlock:before { - content: "\f09c"; } - -.fa-unlock-alt:before { - content: "\f13e"; } - -.fa-untappd:before { - content: "\f405"; } - -.fa-upload:before { - content: "\f093"; } - -.fa-usb:before { - content: "\f287"; } - -.fa-user:before { - content: "\f007"; } - -.fa-user-circle:before { - content: "\f2bd"; } - -.fa-user-md:before { - content: "\f0f0"; } - -.fa-user-plus:before { - content: "\f234"; } - -.fa-user-secret:before { - content: "\f21b"; } - -.fa-user-times:before { - content: "\f235"; } - -.fa-users:before { - content: "\f0c0"; } - -.fa-ussunnah:before { - content: "\f407"; } - -.fa-utensil-spoon:before { - content: "\f2e5"; } - -.fa-utensils:before { - content: "\f2e7"; } - -.fa-vaadin:before { - content: "\f408"; } - -.fa-venus:before { - content: "\f221"; } - -.fa-venus-double:before { - content: "\f226"; } - -.fa-venus-mars:before { - content: "\f228"; } - -.fa-viacoin:before { - content: "\f237"; } - -.fa-viadeo:before { - content: "\f2a9"; } - -.fa-viadeo-square:before { - content: "\f2aa"; } - -.fa-viber:before { - content: "\f409"; } - -.fa-video:before { - content: "\f03d"; } - -.fa-vimeo:before { - content: "\f40a"; } - -.fa-vimeo-square:before { - content: "\f194"; } - -.fa-vimeo-v:before { - content: "\f27d"; } - -.fa-vine:before { - content: "\f1ca"; } - -.fa-vk:before { - content: "\f189"; } - -.fa-vnv:before { - content: "\f40b"; } - -.fa-volleyball-ball:before { - content: "\f45f"; } - -.fa-volume-down:before { - content: "\f027"; } - -.fa-volume-off:before { - content: "\f026"; } - -.fa-volume-up:before { - content: "\f028"; } - -.fa-vuejs:before { - content: "\f41f"; } - -.fa-warehouse:before { - content: "\f494"; } - -.fa-weibo:before { - content: "\f18a"; } - -.fa-weight:before { - content: "\f496"; } - -.fa-weixin:before { - content: "\f1d7"; } - -.fa-whatsapp:before { - content: "\f232"; } - -.fa-whatsapp-square:before { - content: "\f40c"; } - -.fa-wheelchair:before { - content: "\f193"; } - -.fa-whmcs:before { - content: "\f40d"; } - -.fa-wifi:before { - content: "\f1eb"; } - -.fa-wikipedia-w:before { - content: "\f266"; } - -.fa-window-close:before { - content: "\f410"; } - -.fa-window-maximize:before { - content: "\f2d0"; } - -.fa-window-minimize:before { - content: "\f2d1"; } - -.fa-window-restore:before { - content: "\f2d2"; } - -.fa-windows:before { - content: "\f17a"; } - -.fa-won-sign:before { - content: "\f159"; } - -.fa-wordpress:before { - content: "\f19a"; } - -.fa-wordpress-simple:before { - content: "\f411"; } - -.fa-wpbeginner:before { - content: "\f297"; } - -.fa-wpexplorer:before { - content: "\f2de"; } - -.fa-wpforms:before { - content: "\f298"; } - -.fa-wrench:before { - content: "\f0ad"; } - -.fa-xbox:before { - content: "\f412"; } - -.fa-xing:before { - content: "\f168"; } - -.fa-xing-square:before { - content: "\f169"; } - -.fa-y-combinator:before { - content: "\f23b"; } - -.fa-yahoo:before { - content: "\f19e"; } - -.fa-yandex:before { - content: "\f413"; } - -.fa-yandex-international:before { - content: "\f414"; } - -.fa-yelp:before { - content: "\f1e9"; } - -.fa-yen-sign:before { - content: "\f157"; } - -.fa-yoast:before { - content: "\f2b1"; } - -.fa-youtube:before { - content: "\f167"; } - -.fa-youtube-square:before { - content: "\f431"; } - -.sr-only { - border: 0; - clip: rect(0, 0, 0, 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; } - -.sr-only-focusable:active, .sr-only-focusable:focus { - clip: auto; - height: auto; - margin: 0; - overflow: visible; - position: static; - width: auto; } -@font-face { - font-family: 'Font Awesome 5 Brands'; - font-style: normal; - font-weight: normal; - src: url("../webfonts/fa-brands-400.eot"); - src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } - -.fab { - font-family: 'Font Awesome 5 Brands'; } -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 400; - src: url("../webfonts/fa-regular-400.eot"); - src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); } - -.far { - font-family: 'Font Awesome 5 Free'; - font-weight: 400; } -@font-face { - font-family: 'Font Awesome 5 Free'; - font-style: normal; - font-weight: 900; - src: url("../webfonts/fa-solid-900.eot"); - src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); } - -.fa, -.fas { - font-family: 'Font Awesome 5 Free'; - font-weight: 900; } diff --git a/vendor/fontawesome/css/fontawesome-all.min.css b/vendor/fontawesome/css/fontawesome-all.min.css deleted file mode 100644 index 0bc2ee8..0000000 --- a/vendor/fontawesome/css/fontawesome-all.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:a 2s infinite linear;animation:a 2s infinite linear}.fa-pulse{-webkit-animation:a 1s infinite steps(8);animation:a 1s infinite steps(8)}@-webkit-keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comments:before{content:"\f086"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crosshairs:before{content:"\f05b"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-dot-circle:before{content:"\f192"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-excel:before{content:"\f1c3"}.fa-file-image:before{content:"\f1c5"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-firstdraft:before{content:"\f3a1"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frown:before{content:"\f119"}.fa-futbol:before{content:"\f1e3"}.fa-gamepad:before{content:"\f11b"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-male:before{content:"\f183"}.fa-map:before{content:"\f279"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-maxcdn:before{content:"\f136"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-meh:before{content:"\f11a"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-moon:before{content:"\f186"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-paragraph:before{content:"\f1dd"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-percent:before{content:"\f295"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phone:before{content:"\f095"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-print:before{content:"\f02f"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-rebel:before{content:"\f1d0"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-road:before{content:"\f018"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-rupee-sign:before{content:"\f156"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shower:before{content:"\f2cc"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-spotify:before{content:"\f1bc"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-strava:before{content:"\f428"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-circle:before{content:"\f2bd"}.fa-user-md:before{content:"\f0f0"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-warehouse:before{content:"\f494"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:Font Awesome\ 5 Free}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/vendor/fontawesome/css/fontawesome.css b/vendor/fontawesome/css/fontawesome.css deleted file mode 100644 index 460706e..0000000 --- a/vendor/fontawesome/css/fontawesome.css +++ /dev/null @@ -1,2696 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa, -.fas, -.far, -.fal, -.fab { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; } - -.fa-lg { - font-size: 1.33333em; - line-height: 0.75em; - vertical-align: -.0667em; } - -.fa-xs { - font-size: .75em; } - -.fa-sm { - font-size: .875em; } - -.fa-1x { - font-size: 1em; } - -.fa-2x { - font-size: 2em; } - -.fa-3x { - font-size: 3em; } - -.fa-4x { - font-size: 4em; } - -.fa-5x { - font-size: 5em; } - -.fa-6x { - font-size: 6em; } - -.fa-7x { - font-size: 7em; } - -.fa-8x { - font-size: 8em; } - -.fa-9x { - font-size: 9em; } - -.fa-10x { - font-size: 10em; } - -.fa-fw { - text-align: center; - width: 1.25em; } - -.fa-ul { - list-style-type: none; - margin-left: 2.5em; - padding-left: 0; } - .fa-ul > li { - position: relative; } - -.fa-li { - left: -2em; - position: absolute; - text-align: center; - width: 2em; - line-height: inherit; } - -.fa-border { - border: solid 0.08em #eee; - border-radius: .1em; - padding: .2em .25em .15em; } - -.fa-pull-left { - float: left; } - -.fa-pull-right { - float: right; } - -.fa.fa-pull-left, -.fas.fa-pull-left, -.far.fa-pull-left, -.fal.fa-pull-left, -.fab.fa-pull-left { - margin-right: .3em; } - -.fa.fa-pull-right, -.fas.fa-pull-right, -.far.fa-pull-right, -.fal.fa-pull-right, -.fab.fa-pull-right { - margin-left: .3em; } - -.fa-spin { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; } - -.fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); } - -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } - -.fa-rotate-90 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); } - -.fa-rotate-180 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; - -webkit-transform: rotate(180deg); - transform: rotate(180deg); } - -.fa-rotate-270 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; - -webkit-transform: rotate(270deg); - transform: rotate(270deg); } - -.fa-flip-horizontal { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; - -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } - -.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(1, -1); - transform: scale(1, -1); } - -.fa-flip-horizontal.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } - -:root .fa-rotate-90, -:root .fa-rotate-180, -:root .fa-rotate-270, -:root .fa-flip-horizontal, -:root .fa-flip-vertical { - -webkit-filter: none; - filter: none; } - -.fa-stack { - display: inline-block; - height: 2em; - line-height: 2em; - position: relative; - vertical-align: middle; - width: 2em; } - -.fa-stack-1x, -.fa-stack-2x { - left: 0; - position: absolute; - text-align: center; - width: 100%; } - -.fa-stack-1x { - line-height: inherit; } - -.fa-stack-2x { - font-size: 2em; } - -.fa-inverse { - color: #fff; } - -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen -readers do not read off random characters that represent icons */ -.fa-500px:before { - content: "\f26e"; } - -.fa-accessible-icon:before { - content: "\f368"; } - -.fa-accusoft:before { - content: "\f369"; } - -.fa-address-book:before { - content: "\f2b9"; } - -.fa-address-card:before { - content: "\f2bb"; } - -.fa-adjust:before { - content: "\f042"; } - -.fa-adn:before { - content: "\f170"; } - -.fa-adversal:before { - content: "\f36a"; } - -.fa-affiliatetheme:before { - content: "\f36b"; } - -.fa-algolia:before { - content: "\f36c"; } - -.fa-align-center:before { - content: "\f037"; } - -.fa-align-justify:before { - content: "\f039"; } - -.fa-align-left:before { - content: "\f036"; } - -.fa-align-right:before { - content: "\f038"; } - -.fa-amazon:before { - content: "\f270"; } - -.fa-amazon-pay:before { - content: "\f42c"; } - -.fa-ambulance:before { - content: "\f0f9"; } - -.fa-american-sign-language-interpreting:before { - content: "\f2a3"; } - -.fa-amilia:before { - content: "\f36d"; } - -.fa-anchor:before { - content: "\f13d"; } - -.fa-android:before { - content: "\f17b"; } - -.fa-angellist:before { - content: "\f209"; } - -.fa-angle-double-down:before { - content: "\f103"; } - -.fa-angle-double-left:before { - content: "\f100"; } - -.fa-angle-double-right:before { - content: "\f101"; } - -.fa-angle-double-up:before { - content: "\f102"; } - -.fa-angle-down:before { - content: "\f107"; } - -.fa-angle-left:before { - content: "\f104"; } - -.fa-angle-right:before { - content: "\f105"; } - -.fa-angle-up:before { - content: "\f106"; } - -.fa-angrycreative:before { - content: "\f36e"; } - -.fa-angular:before { - content: "\f420"; } - -.fa-app-store:before { - content: "\f36f"; } - -.fa-app-store-ios:before { - content: "\f370"; } - -.fa-apper:before { - content: "\f371"; } - -.fa-apple:before { - content: "\f179"; } - -.fa-apple-pay:before { - content: "\f415"; } - -.fa-archive:before { - content: "\f187"; } - -.fa-arrow-alt-circle-down:before { - content: "\f358"; } - -.fa-arrow-alt-circle-left:before { - content: "\f359"; } - -.fa-arrow-alt-circle-right:before { - content: "\f35a"; } - -.fa-arrow-alt-circle-up:before { - content: "\f35b"; } - -.fa-arrow-circle-down:before { - content: "\f0ab"; } - -.fa-arrow-circle-left:before { - content: "\f0a8"; } - -.fa-arrow-circle-right:before { - content: "\f0a9"; } - -.fa-arrow-circle-up:before { - content: "\f0aa"; } - -.fa-arrow-down:before { - content: "\f063"; } - -.fa-arrow-left:before { - content: "\f060"; } - -.fa-arrow-right:before { - content: "\f061"; } - -.fa-arrow-up:before { - content: "\f062"; } - -.fa-arrows-alt:before { - content: "\f0b2"; } - -.fa-arrows-alt-h:before { - content: "\f337"; } - -.fa-arrows-alt-v:before { - content: "\f338"; } - -.fa-assistive-listening-systems:before { - content: "\f2a2"; } - -.fa-asterisk:before { - content: "\f069"; } - -.fa-asymmetrik:before { - content: "\f372"; } - -.fa-at:before { - content: "\f1fa"; } - -.fa-audible:before { - content: "\f373"; } - -.fa-audio-description:before { - content: "\f29e"; } - -.fa-autoprefixer:before { - content: "\f41c"; } - -.fa-avianex:before { - content: "\f374"; } - -.fa-aviato:before { - content: "\f421"; } - -.fa-aws:before { - content: "\f375"; } - -.fa-backward:before { - content: "\f04a"; } - -.fa-balance-scale:before { - content: "\f24e"; } - -.fa-ban:before { - content: "\f05e"; } - -.fa-band-aid:before { - content: "\f462"; } - -.fa-bandcamp:before { - content: "\f2d5"; } - -.fa-barcode:before { - content: "\f02a"; } - -.fa-bars:before { - content: "\f0c9"; } - -.fa-baseball-ball:before { - content: "\f433"; } - -.fa-basketball-ball:before { - content: "\f434"; } - -.fa-bath:before { - content: "\f2cd"; } - -.fa-battery-empty:before { - content: "\f244"; } - -.fa-battery-full:before { - content: "\f240"; } - -.fa-battery-half:before { - content: "\f242"; } - -.fa-battery-quarter:before { - content: "\f243"; } - -.fa-battery-three-quarters:before { - content: "\f241"; } - -.fa-bed:before { - content: "\f236"; } - -.fa-beer:before { - content: "\f0fc"; } - -.fa-behance:before { - content: "\f1b4"; } - -.fa-behance-square:before { - content: "\f1b5"; } - -.fa-bell:before { - content: "\f0f3"; } - -.fa-bell-slash:before { - content: "\f1f6"; } - -.fa-bicycle:before { - content: "\f206"; } - -.fa-bimobject:before { - content: "\f378"; } - -.fa-binoculars:before { - content: "\f1e5"; } - -.fa-birthday-cake:before { - content: "\f1fd"; } - -.fa-bitbucket:before { - content: "\f171"; } - -.fa-bitcoin:before { - content: "\f379"; } - -.fa-bity:before { - content: "\f37a"; } - -.fa-black-tie:before { - content: "\f27e"; } - -.fa-blackberry:before { - content: "\f37b"; } - -.fa-blind:before { - content: "\f29d"; } - -.fa-blogger:before { - content: "\f37c"; } - -.fa-blogger-b:before { - content: "\f37d"; } - -.fa-bluetooth:before { - content: "\f293"; } - -.fa-bluetooth-b:before { - content: "\f294"; } - -.fa-bold:before { - content: "\f032"; } - -.fa-bolt:before { - content: "\f0e7"; } - -.fa-bomb:before { - content: "\f1e2"; } - -.fa-book:before { - content: "\f02d"; } - -.fa-bookmark:before { - content: "\f02e"; } - -.fa-bowling-ball:before { - content: "\f436"; } - -.fa-box:before { - content: "\f466"; } - -.fa-boxes:before { - content: "\f468"; } - -.fa-braille:before { - content: "\f2a1"; } - -.fa-briefcase:before { - content: "\f0b1"; } - -.fa-btc:before { - content: "\f15a"; } - -.fa-bug:before { - content: "\f188"; } - -.fa-building:before { - content: "\f1ad"; } - -.fa-bullhorn:before { - content: "\f0a1"; } - -.fa-bullseye:before { - content: "\f140"; } - -.fa-buromobelexperte:before { - content: "\f37f"; } - -.fa-bus:before { - content: "\f207"; } - -.fa-buysellads:before { - content: "\f20d"; } - -.fa-calculator:before { - content: "\f1ec"; } - -.fa-calendar:before { - content: "\f133"; } - -.fa-calendar-alt:before { - content: "\f073"; } - -.fa-calendar-check:before { - content: "\f274"; } - -.fa-calendar-minus:before { - content: "\f272"; } - -.fa-calendar-plus:before { - content: "\f271"; } - -.fa-calendar-times:before { - content: "\f273"; } - -.fa-camera:before { - content: "\f030"; } - -.fa-camera-retro:before { - content: "\f083"; } - -.fa-car:before { - content: "\f1b9"; } - -.fa-caret-down:before { - content: "\f0d7"; } - -.fa-caret-left:before { - content: "\f0d9"; } - -.fa-caret-right:before { - content: "\f0da"; } - -.fa-caret-square-down:before { - content: "\f150"; } - -.fa-caret-square-left:before { - content: "\f191"; } - -.fa-caret-square-right:before { - content: "\f152"; } - -.fa-caret-square-up:before { - content: "\f151"; } - -.fa-caret-up:before { - content: "\f0d8"; } - -.fa-cart-arrow-down:before { - content: "\f218"; } - -.fa-cart-plus:before { - content: "\f217"; } - -.fa-cc-amazon-pay:before { - content: "\f42d"; } - -.fa-cc-amex:before { - content: "\f1f3"; } - -.fa-cc-apple-pay:before { - content: "\f416"; } - -.fa-cc-diners-club:before { - content: "\f24c"; } - -.fa-cc-discover:before { - content: "\f1f2"; } - -.fa-cc-jcb:before { - content: "\f24b"; } - -.fa-cc-mastercard:before { - content: "\f1f1"; } - -.fa-cc-paypal:before { - content: "\f1f4"; } - -.fa-cc-stripe:before { - content: "\f1f5"; } - -.fa-cc-visa:before { - content: "\f1f0"; } - -.fa-centercode:before { - content: "\f380"; } - -.fa-certificate:before { - content: "\f0a3"; } - -.fa-chart-area:before { - content: "\f1fe"; } - -.fa-chart-bar:before { - content: "\f080"; } - -.fa-chart-line:before { - content: "\f201"; } - -.fa-chart-pie:before { - content: "\f200"; } - -.fa-check:before { - content: "\f00c"; } - -.fa-check-circle:before { - content: "\f058"; } - -.fa-check-square:before { - content: "\f14a"; } - -.fa-chess:before { - content: "\f439"; } - -.fa-chess-bishop:before { - content: "\f43a"; } - -.fa-chess-board:before { - content: "\f43c"; } - -.fa-chess-king:before { - content: "\f43f"; } - -.fa-chess-knight:before { - content: "\f441"; } - -.fa-chess-pawn:before { - content: "\f443"; } - -.fa-chess-queen:before { - content: "\f445"; } - -.fa-chess-rook:before { - content: "\f447"; } - -.fa-chevron-circle-down:before { - content: "\f13a"; } - -.fa-chevron-circle-left:before { - content: "\f137"; } - -.fa-chevron-circle-right:before { - content: "\f138"; } - -.fa-chevron-circle-up:before { - content: "\f139"; } - -.fa-chevron-down:before { - content: "\f078"; } - -.fa-chevron-left:before { - content: "\f053"; } - -.fa-chevron-right:before { - content: "\f054"; } - -.fa-chevron-up:before { - content: "\f077"; } - -.fa-child:before { - content: "\f1ae"; } - -.fa-chrome:before { - content: "\f268"; } - -.fa-circle:before { - content: "\f111"; } - -.fa-circle-notch:before { - content: "\f1ce"; } - -.fa-clipboard:before { - content: "\f328"; } - -.fa-clipboard-check:before { - content: "\f46c"; } - -.fa-clipboard-list:before { - content: "\f46d"; } - -.fa-clock:before { - content: "\f017"; } - -.fa-clone:before { - content: "\f24d"; } - -.fa-closed-captioning:before { - content: "\f20a"; } - -.fa-cloud:before { - content: "\f0c2"; } - -.fa-cloud-download-alt:before { - content: "\f381"; } - -.fa-cloud-upload-alt:before { - content: "\f382"; } - -.fa-cloudscale:before { - content: "\f383"; } - -.fa-cloudsmith:before { - content: "\f384"; } - -.fa-cloudversify:before { - content: "\f385"; } - -.fa-code:before { - content: "\f121"; } - -.fa-code-branch:before { - content: "\f126"; } - -.fa-codepen:before { - content: "\f1cb"; } - -.fa-codiepie:before { - content: "\f284"; } - -.fa-coffee:before { - content: "\f0f4"; } - -.fa-cog:before { - content: "\f013"; } - -.fa-cogs:before { - content: "\f085"; } - -.fa-columns:before { - content: "\f0db"; } - -.fa-comment:before { - content: "\f075"; } - -.fa-comment-alt:before { - content: "\f27a"; } - -.fa-comments:before { - content: "\f086"; } - -.fa-compass:before { - content: "\f14e"; } - -.fa-compress:before { - content: "\f066"; } - -.fa-connectdevelop:before { - content: "\f20e"; } - -.fa-contao:before { - content: "\f26d"; } - -.fa-copy:before { - content: "\f0c5"; } - -.fa-copyright:before { - content: "\f1f9"; } - -.fa-cpanel:before { - content: "\f388"; } - -.fa-creative-commons:before { - content: "\f25e"; } - -.fa-credit-card:before { - content: "\f09d"; } - -.fa-crop:before { - content: "\f125"; } - -.fa-crosshairs:before { - content: "\f05b"; } - -.fa-css3:before { - content: "\f13c"; } - -.fa-css3-alt:before { - content: "\f38b"; } - -.fa-cube:before { - content: "\f1b2"; } - -.fa-cubes:before { - content: "\f1b3"; } - -.fa-cut:before { - content: "\f0c4"; } - -.fa-cuttlefish:before { - content: "\f38c"; } - -.fa-d-and-d:before { - content: "\f38d"; } - -.fa-dashcube:before { - content: "\f210"; } - -.fa-database:before { - content: "\f1c0"; } - -.fa-deaf:before { - content: "\f2a4"; } - -.fa-delicious:before { - content: "\f1a5"; } - -.fa-deploydog:before { - content: "\f38e"; } - -.fa-deskpro:before { - content: "\f38f"; } - -.fa-desktop:before { - content: "\f108"; } - -.fa-deviantart:before { - content: "\f1bd"; } - -.fa-digg:before { - content: "\f1a6"; } - -.fa-digital-ocean:before { - content: "\f391"; } - -.fa-discord:before { - content: "\f392"; } - -.fa-discourse:before { - content: "\f393"; } - -.fa-dna:before { - content: "\f471"; } - -.fa-dochub:before { - content: "\f394"; } - -.fa-docker:before { - content: "\f395"; } - -.fa-dollar-sign:before { - content: "\f155"; } - -.fa-dolly:before { - content: "\f472"; } - -.fa-dolly-flatbed:before { - content: "\f474"; } - -.fa-dot-circle:before { - content: "\f192"; } - -.fa-download:before { - content: "\f019"; } - -.fa-draft2digital:before { - content: "\f396"; } - -.fa-dribbble:before { - content: "\f17d"; } - -.fa-dribbble-square:before { - content: "\f397"; } - -.fa-dropbox:before { - content: "\f16b"; } - -.fa-drupal:before { - content: "\f1a9"; } - -.fa-dyalog:before { - content: "\f399"; } - -.fa-earlybirds:before { - content: "\f39a"; } - -.fa-edge:before { - content: "\f282"; } - -.fa-edit:before { - content: "\f044"; } - -.fa-eject:before { - content: "\f052"; } - -.fa-elementor:before { - content: "\f430"; } - -.fa-ellipsis-h:before { - content: "\f141"; } - -.fa-ellipsis-v:before { - content: "\f142"; } - -.fa-ember:before { - content: "\f423"; } - -.fa-empire:before { - content: "\f1d1"; } - -.fa-envelope:before { - content: "\f0e0"; } - -.fa-envelope-open:before { - content: "\f2b6"; } - -.fa-envelope-square:before { - content: "\f199"; } - -.fa-envira:before { - content: "\f299"; } - -.fa-eraser:before { - content: "\f12d"; } - -.fa-erlang:before { - content: "\f39d"; } - -.fa-ethereum:before { - content: "\f42e"; } - -.fa-etsy:before { - content: "\f2d7"; } - -.fa-euro-sign:before { - content: "\f153"; } - -.fa-exchange-alt:before { - content: "\f362"; } - -.fa-exclamation:before { - content: "\f12a"; } - -.fa-exclamation-circle:before { - content: "\f06a"; } - -.fa-exclamation-triangle:before { - content: "\f071"; } - -.fa-expand:before { - content: "\f065"; } - -.fa-expand-arrows-alt:before { - content: "\f31e"; } - -.fa-expeditedssl:before { - content: "\f23e"; } - -.fa-external-link-alt:before { - content: "\f35d"; } - -.fa-external-link-square-alt:before { - content: "\f360"; } - -.fa-eye:before { - content: "\f06e"; } - -.fa-eye-dropper:before { - content: "\f1fb"; } - -.fa-eye-slash:before { - content: "\f070"; } - -.fa-facebook:before { - content: "\f09a"; } - -.fa-facebook-f:before { - content: "\f39e"; } - -.fa-facebook-messenger:before { - content: "\f39f"; } - -.fa-facebook-square:before { - content: "\f082"; } - -.fa-fast-backward:before { - content: "\f049"; } - -.fa-fast-forward:before { - content: "\f050"; } - -.fa-fax:before { - content: "\f1ac"; } - -.fa-female:before { - content: "\f182"; } - -.fa-fighter-jet:before { - content: "\f0fb"; } - -.fa-file:before { - content: "\f15b"; } - -.fa-file-alt:before { - content: "\f15c"; } - -.fa-file-archive:before { - content: "\f1c6"; } - -.fa-file-audio:before { - content: "\f1c7"; } - -.fa-file-code:before { - content: "\f1c9"; } - -.fa-file-excel:before { - content: "\f1c3"; } - -.fa-file-image:before { - content: "\f1c5"; } - -.fa-file-pdf:before { - content: "\f1c1"; } - -.fa-file-powerpoint:before { - content: "\f1c4"; } - -.fa-file-video:before { - content: "\f1c8"; } - -.fa-file-word:before { - content: "\f1c2"; } - -.fa-film:before { - content: "\f008"; } - -.fa-filter:before { - content: "\f0b0"; } - -.fa-fire:before { - content: "\f06d"; } - -.fa-fire-extinguisher:before { - content: "\f134"; } - -.fa-firefox:before { - content: "\f269"; } - -.fa-first-aid:before { - content: "\f479"; } - -.fa-first-order:before { - content: "\f2b0"; } - -.fa-firstdraft:before { - content: "\f3a1"; } - -.fa-flag:before { - content: "\f024"; } - -.fa-flag-checkered:before { - content: "\f11e"; } - -.fa-flask:before { - content: "\f0c3"; } - -.fa-flickr:before { - content: "\f16e"; } - -.fa-flipboard:before { - content: "\f44d"; } - -.fa-fly:before { - content: "\f417"; } - -.fa-folder:before { - content: "\f07b"; } - -.fa-folder-open:before { - content: "\f07c"; } - -.fa-font:before { - content: "\f031"; } - -.fa-font-awesome:before { - content: "\f2b4"; } - -.fa-font-awesome-alt:before { - content: "\f35c"; } - -.fa-font-awesome-flag:before { - content: "\f425"; } - -.fa-fonticons:before { - content: "\f280"; } - -.fa-fonticons-fi:before { - content: "\f3a2"; } - -.fa-football-ball:before { - content: "\f44e"; } - -.fa-fort-awesome:before { - content: "\f286"; } - -.fa-fort-awesome-alt:before { - content: "\f3a3"; } - -.fa-forumbee:before { - content: "\f211"; } - -.fa-forward:before { - content: "\f04e"; } - -.fa-foursquare:before { - content: "\f180"; } - -.fa-free-code-camp:before { - content: "\f2c5"; } - -.fa-freebsd:before { - content: "\f3a4"; } - -.fa-frown:before { - content: "\f119"; } - -.fa-futbol:before { - content: "\f1e3"; } - -.fa-gamepad:before { - content: "\f11b"; } - -.fa-gavel:before { - content: "\f0e3"; } - -.fa-gem:before { - content: "\f3a5"; } - -.fa-genderless:before { - content: "\f22d"; } - -.fa-get-pocket:before { - content: "\f265"; } - -.fa-gg:before { - content: "\f260"; } - -.fa-gg-circle:before { - content: "\f261"; } - -.fa-gift:before { - content: "\f06b"; } - -.fa-git:before { - content: "\f1d3"; } - -.fa-git-square:before { - content: "\f1d2"; } - -.fa-github:before { - content: "\f09b"; } - -.fa-github-alt:before { - content: "\f113"; } - -.fa-github-square:before { - content: "\f092"; } - -.fa-gitkraken:before { - content: "\f3a6"; } - -.fa-gitlab:before { - content: "\f296"; } - -.fa-gitter:before { - content: "\f426"; } - -.fa-glass-martini:before { - content: "\f000"; } - -.fa-glide:before { - content: "\f2a5"; } - -.fa-glide-g:before { - content: "\f2a6"; } - -.fa-globe:before { - content: "\f0ac"; } - -.fa-gofore:before { - content: "\f3a7"; } - -.fa-golf-ball:before { - content: "\f450"; } - -.fa-goodreads:before { - content: "\f3a8"; } - -.fa-goodreads-g:before { - content: "\f3a9"; } - -.fa-google:before { - content: "\f1a0"; } - -.fa-google-drive:before { - content: "\f3aa"; } - -.fa-google-play:before { - content: "\f3ab"; } - -.fa-google-plus:before { - content: "\f2b3"; } - -.fa-google-plus-g:before { - content: "\f0d5"; } - -.fa-google-plus-square:before { - content: "\f0d4"; } - -.fa-google-wallet:before { - content: "\f1ee"; } - -.fa-graduation-cap:before { - content: "\f19d"; } - -.fa-gratipay:before { - content: "\f184"; } - -.fa-grav:before { - content: "\f2d6"; } - -.fa-gripfire:before { - content: "\f3ac"; } - -.fa-grunt:before { - content: "\f3ad"; } - -.fa-gulp:before { - content: "\f3ae"; } - -.fa-h-square:before { - content: "\f0fd"; } - -.fa-hacker-news:before { - content: "\f1d4"; } - -.fa-hacker-news-square:before { - content: "\f3af"; } - -.fa-hand-lizard:before { - content: "\f258"; } - -.fa-hand-paper:before { - content: "\f256"; } - -.fa-hand-peace:before { - content: "\f25b"; } - -.fa-hand-point-down:before { - content: "\f0a7"; } - -.fa-hand-point-left:before { - content: "\f0a5"; } - -.fa-hand-point-right:before { - content: "\f0a4"; } - -.fa-hand-point-up:before { - content: "\f0a6"; } - -.fa-hand-pointer:before { - content: "\f25a"; } - -.fa-hand-rock:before { - content: "\f255"; } - -.fa-hand-scissors:before { - content: "\f257"; } - -.fa-hand-spock:before { - content: "\f259"; } - -.fa-handshake:before { - content: "\f2b5"; } - -.fa-hashtag:before { - content: "\f292"; } - -.fa-hdd:before { - content: "\f0a0"; } - -.fa-heading:before { - content: "\f1dc"; } - -.fa-headphones:before { - content: "\f025"; } - -.fa-heart:before { - content: "\f004"; } - -.fa-heartbeat:before { - content: "\f21e"; } - -.fa-hips:before { - content: "\f452"; } - -.fa-hire-a-helper:before { - content: "\f3b0"; } - -.fa-history:before { - content: "\f1da"; } - -.fa-hockey-puck:before { - content: "\f453"; } - -.fa-home:before { - content: "\f015"; } - -.fa-hooli:before { - content: "\f427"; } - -.fa-hospital:before { - content: "\f0f8"; } - -.fa-hospital-symbol:before { - content: "\f47e"; } - -.fa-hotjar:before { - content: "\f3b1"; } - -.fa-hourglass:before { - content: "\f254"; } - -.fa-hourglass-end:before { - content: "\f253"; } - -.fa-hourglass-half:before { - content: "\f252"; } - -.fa-hourglass-start:before { - content: "\f251"; } - -.fa-houzz:before { - content: "\f27c"; } - -.fa-html5:before { - content: "\f13b"; } - -.fa-hubspot:before { - content: "\f3b2"; } - -.fa-i-cursor:before { - content: "\f246"; } - -.fa-id-badge:before { - content: "\f2c1"; } - -.fa-id-card:before { - content: "\f2c2"; } - -.fa-image:before { - content: "\f03e"; } - -.fa-images:before { - content: "\f302"; } - -.fa-imdb:before { - content: "\f2d8"; } - -.fa-inbox:before { - content: "\f01c"; } - -.fa-indent:before { - content: "\f03c"; } - -.fa-industry:before { - content: "\f275"; } - -.fa-info:before { - content: "\f129"; } - -.fa-info-circle:before { - content: "\f05a"; } - -.fa-instagram:before { - content: "\f16d"; } - -.fa-internet-explorer:before { - content: "\f26b"; } - -.fa-ioxhost:before { - content: "\f208"; } - -.fa-italic:before { - content: "\f033"; } - -.fa-itunes:before { - content: "\f3b4"; } - -.fa-itunes-note:before { - content: "\f3b5"; } - -.fa-jenkins:before { - content: "\f3b6"; } - -.fa-joget:before { - content: "\f3b7"; } - -.fa-joomla:before { - content: "\f1aa"; } - -.fa-js:before { - content: "\f3b8"; } - -.fa-js-square:before { - content: "\f3b9"; } - -.fa-jsfiddle:before { - content: "\f1cc"; } - -.fa-key:before { - content: "\f084"; } - -.fa-keyboard:before { - content: "\f11c"; } - -.fa-keycdn:before { - content: "\f3ba"; } - -.fa-kickstarter:before { - content: "\f3bb"; } - -.fa-kickstarter-k:before { - content: "\f3bc"; } - -.fa-korvue:before { - content: "\f42f"; } - -.fa-language:before { - content: "\f1ab"; } - -.fa-laptop:before { - content: "\f109"; } - -.fa-laravel:before { - content: "\f3bd"; } - -.fa-lastfm:before { - content: "\f202"; } - -.fa-lastfm-square:before { - content: "\f203"; } - -.fa-leaf:before { - content: "\f06c"; } - -.fa-leanpub:before { - content: "\f212"; } - -.fa-lemon:before { - content: "\f094"; } - -.fa-less:before { - content: "\f41d"; } - -.fa-level-down-alt:before { - content: "\f3be"; } - -.fa-level-up-alt:before { - content: "\f3bf"; } - -.fa-life-ring:before { - content: "\f1cd"; } - -.fa-lightbulb:before { - content: "\f0eb"; } - -.fa-line:before { - content: "\f3c0"; } - -.fa-link:before { - content: "\f0c1"; } - -.fa-linkedin:before { - content: "\f08c"; } - -.fa-linkedin-in:before { - content: "\f0e1"; } - -.fa-linode:before { - content: "\f2b8"; } - -.fa-linux:before { - content: "\f17c"; } - -.fa-lira-sign:before { - content: "\f195"; } - -.fa-list:before { - content: "\f03a"; } - -.fa-list-alt:before { - content: "\f022"; } - -.fa-list-ol:before { - content: "\f0cb"; } - -.fa-list-ul:before { - content: "\f0ca"; } - -.fa-location-arrow:before { - content: "\f124"; } - -.fa-lock:before { - content: "\f023"; } - -.fa-lock-open:before { - content: "\f3c1"; } - -.fa-long-arrow-alt-down:before { - content: "\f309"; } - -.fa-long-arrow-alt-left:before { - content: "\f30a"; } - -.fa-long-arrow-alt-right:before { - content: "\f30b"; } - -.fa-long-arrow-alt-up:before { - content: "\f30c"; } - -.fa-low-vision:before { - content: "\f2a8"; } - -.fa-lyft:before { - content: "\f3c3"; } - -.fa-magento:before { - content: "\f3c4"; } - -.fa-magic:before { - content: "\f0d0"; } - -.fa-magnet:before { - content: "\f076"; } - -.fa-male:before { - content: "\f183"; } - -.fa-map:before { - content: "\f279"; } - -.fa-map-marker:before { - content: "\f041"; } - -.fa-map-marker-alt:before { - content: "\f3c5"; } - -.fa-map-pin:before { - content: "\f276"; } - -.fa-map-signs:before { - content: "\f277"; } - -.fa-mars:before { - content: "\f222"; } - -.fa-mars-double:before { - content: "\f227"; } - -.fa-mars-stroke:before { - content: "\f229"; } - -.fa-mars-stroke-h:before { - content: "\f22b"; } - -.fa-mars-stroke-v:before { - content: "\f22a"; } - -.fa-maxcdn:before { - content: "\f136"; } - -.fa-medapps:before { - content: "\f3c6"; } - -.fa-medium:before { - content: "\f23a"; } - -.fa-medium-m:before { - content: "\f3c7"; } - -.fa-medkit:before { - content: "\f0fa"; } - -.fa-medrt:before { - content: "\f3c8"; } - -.fa-meetup:before { - content: "\f2e0"; } - -.fa-meh:before { - content: "\f11a"; } - -.fa-mercury:before { - content: "\f223"; } - -.fa-microchip:before { - content: "\f2db"; } - -.fa-microphone:before { - content: "\f130"; } - -.fa-microphone-slash:before { - content: "\f131"; } - -.fa-microsoft:before { - content: "\f3ca"; } - -.fa-minus:before { - content: "\f068"; } - -.fa-minus-circle:before { - content: "\f056"; } - -.fa-minus-square:before { - content: "\f146"; } - -.fa-mix:before { - content: "\f3cb"; } - -.fa-mixcloud:before { - content: "\f289"; } - -.fa-mizuni:before { - content: "\f3cc"; } - -.fa-mobile:before { - content: "\f10b"; } - -.fa-mobile-alt:before { - content: "\f3cd"; } - -.fa-modx:before { - content: "\f285"; } - -.fa-monero:before { - content: "\f3d0"; } - -.fa-money-bill-alt:before { - content: "\f3d1"; } - -.fa-moon:before { - content: "\f186"; } - -.fa-motorcycle:before { - content: "\f21c"; } - -.fa-mouse-pointer:before { - content: "\f245"; } - -.fa-music:before { - content: "\f001"; } - -.fa-napster:before { - content: "\f3d2"; } - -.fa-neuter:before { - content: "\f22c"; } - -.fa-newspaper:before { - content: "\f1ea"; } - -.fa-nintendo-switch:before { - content: "\f418"; } - -.fa-node:before { - content: "\f419"; } - -.fa-node-js:before { - content: "\f3d3"; } - -.fa-npm:before { - content: "\f3d4"; } - -.fa-ns8:before { - content: "\f3d5"; } - -.fa-nutritionix:before { - content: "\f3d6"; } - -.fa-object-group:before { - content: "\f247"; } - -.fa-object-ungroup:before { - content: "\f248"; } - -.fa-odnoklassniki:before { - content: "\f263"; } - -.fa-odnoklassniki-square:before { - content: "\f264"; } - -.fa-opencart:before { - content: "\f23d"; } - -.fa-openid:before { - content: "\f19b"; } - -.fa-opera:before { - content: "\f26a"; } - -.fa-optin-monster:before { - content: "\f23c"; } - -.fa-osi:before { - content: "\f41a"; } - -.fa-outdent:before { - content: "\f03b"; } - -.fa-page4:before { - content: "\f3d7"; } - -.fa-pagelines:before { - content: "\f18c"; } - -.fa-paint-brush:before { - content: "\f1fc"; } - -.fa-palfed:before { - content: "\f3d8"; } - -.fa-pallet:before { - content: "\f482"; } - -.fa-paper-plane:before { - content: "\f1d8"; } - -.fa-paperclip:before { - content: "\f0c6"; } - -.fa-paragraph:before { - content: "\f1dd"; } - -.fa-paste:before { - content: "\f0ea"; } - -.fa-patreon:before { - content: "\f3d9"; } - -.fa-pause:before { - content: "\f04c"; } - -.fa-pause-circle:before { - content: "\f28b"; } - -.fa-paw:before { - content: "\f1b0"; } - -.fa-paypal:before { - content: "\f1ed"; } - -.fa-pen-square:before { - content: "\f14b"; } - -.fa-pencil-alt:before { - content: "\f303"; } - -.fa-percent:before { - content: "\f295"; } - -.fa-periscope:before { - content: "\f3da"; } - -.fa-phabricator:before { - content: "\f3db"; } - -.fa-phoenix-framework:before { - content: "\f3dc"; } - -.fa-phone:before { - content: "\f095"; } - -.fa-phone-square:before { - content: "\f098"; } - -.fa-phone-volume:before { - content: "\f2a0"; } - -.fa-php:before { - content: "\f457"; } - -.fa-pied-piper:before { - content: "\f2ae"; } - -.fa-pied-piper-alt:before { - content: "\f1a8"; } - -.fa-pied-piper-pp:before { - content: "\f1a7"; } - -.fa-pills:before { - content: "\f484"; } - -.fa-pinterest:before { - content: "\f0d2"; } - -.fa-pinterest-p:before { - content: "\f231"; } - -.fa-pinterest-square:before { - content: "\f0d3"; } - -.fa-plane:before { - content: "\f072"; } - -.fa-play:before { - content: "\f04b"; } - -.fa-play-circle:before { - content: "\f144"; } - -.fa-playstation:before { - content: "\f3df"; } - -.fa-plug:before { - content: "\f1e6"; } - -.fa-plus:before { - content: "\f067"; } - -.fa-plus-circle:before { - content: "\f055"; } - -.fa-plus-square:before { - content: "\f0fe"; } - -.fa-podcast:before { - content: "\f2ce"; } - -.fa-pound-sign:before { - content: "\f154"; } - -.fa-power-off:before { - content: "\f011"; } - -.fa-print:before { - content: "\f02f"; } - -.fa-product-hunt:before { - content: "\f288"; } - -.fa-pushed:before { - content: "\f3e1"; } - -.fa-puzzle-piece:before { - content: "\f12e"; } - -.fa-python:before { - content: "\f3e2"; } - -.fa-qq:before { - content: "\f1d6"; } - -.fa-qrcode:before { - content: "\f029"; } - -.fa-question:before { - content: "\f128"; } - -.fa-question-circle:before { - content: "\f059"; } - -.fa-quidditch:before { - content: "\f458"; } - -.fa-quinscape:before { - content: "\f459"; } - -.fa-quora:before { - content: "\f2c4"; } - -.fa-quote-left:before { - content: "\f10d"; } - -.fa-quote-right:before { - content: "\f10e"; } - -.fa-random:before { - content: "\f074"; } - -.fa-ravelry:before { - content: "\f2d9"; } - -.fa-react:before { - content: "\f41b"; } - -.fa-rebel:before { - content: "\f1d0"; } - -.fa-recycle:before { - content: "\f1b8"; } - -.fa-red-river:before { - content: "\f3e3"; } - -.fa-reddit:before { - content: "\f1a1"; } - -.fa-reddit-alien:before { - content: "\f281"; } - -.fa-reddit-square:before { - content: "\f1a2"; } - -.fa-redo:before { - content: "\f01e"; } - -.fa-redo-alt:before { - content: "\f2f9"; } - -.fa-registered:before { - content: "\f25d"; } - -.fa-rendact:before { - content: "\f3e4"; } - -.fa-renren:before { - content: "\f18b"; } - -.fa-reply:before { - content: "\f3e5"; } - -.fa-reply-all:before { - content: "\f122"; } - -.fa-replyd:before { - content: "\f3e6"; } - -.fa-resolving:before { - content: "\f3e7"; } - -.fa-retweet:before { - content: "\f079"; } - -.fa-road:before { - content: "\f018"; } - -.fa-rocket:before { - content: "\f135"; } - -.fa-rocketchat:before { - content: "\f3e8"; } - -.fa-rockrms:before { - content: "\f3e9"; } - -.fa-rss:before { - content: "\f09e"; } - -.fa-rss-square:before { - content: "\f143"; } - -.fa-ruble-sign:before { - content: "\f158"; } - -.fa-rupee-sign:before { - content: "\f156"; } - -.fa-safari:before { - content: "\f267"; } - -.fa-sass:before { - content: "\f41e"; } - -.fa-save:before { - content: "\f0c7"; } - -.fa-schlix:before { - content: "\f3ea"; } - -.fa-scribd:before { - content: "\f28a"; } - -.fa-search:before { - content: "\f002"; } - -.fa-search-minus:before { - content: "\f010"; } - -.fa-search-plus:before { - content: "\f00e"; } - -.fa-searchengin:before { - content: "\f3eb"; } - -.fa-sellcast:before { - content: "\f2da"; } - -.fa-sellsy:before { - content: "\f213"; } - -.fa-server:before { - content: "\f233"; } - -.fa-servicestack:before { - content: "\f3ec"; } - -.fa-share:before { - content: "\f064"; } - -.fa-share-alt:before { - content: "\f1e0"; } - -.fa-share-alt-square:before { - content: "\f1e1"; } - -.fa-share-square:before { - content: "\f14d"; } - -.fa-shekel-sign:before { - content: "\f20b"; } - -.fa-shield-alt:before { - content: "\f3ed"; } - -.fa-ship:before { - content: "\f21a"; } - -.fa-shipping-fast:before { - content: "\f48b"; } - -.fa-shirtsinbulk:before { - content: "\f214"; } - -.fa-shopping-bag:before { - content: "\f290"; } - -.fa-shopping-basket:before { - content: "\f291"; } - -.fa-shopping-cart:before { - content: "\f07a"; } - -.fa-shower:before { - content: "\f2cc"; } - -.fa-sign-in-alt:before { - content: "\f2f6"; } - -.fa-sign-language:before { - content: "\f2a7"; } - -.fa-sign-out-alt:before { - content: "\f2f5"; } - -.fa-signal:before { - content: "\f012"; } - -.fa-simplybuilt:before { - content: "\f215"; } - -.fa-sistrix:before { - content: "\f3ee"; } - -.fa-sitemap:before { - content: "\f0e8"; } - -.fa-skyatlas:before { - content: "\f216"; } - -.fa-skype:before { - content: "\f17e"; } - -.fa-slack:before { - content: "\f198"; } - -.fa-slack-hash:before { - content: "\f3ef"; } - -.fa-sliders-h:before { - content: "\f1de"; } - -.fa-slideshare:before { - content: "\f1e7"; } - -.fa-smile:before { - content: "\f118"; } - -.fa-snapchat:before { - content: "\f2ab"; } - -.fa-snapchat-ghost:before { - content: "\f2ac"; } - -.fa-snapchat-square:before { - content: "\f2ad"; } - -.fa-snowflake:before { - content: "\f2dc"; } - -.fa-sort:before { - content: "\f0dc"; } - -.fa-sort-alpha-down:before { - content: "\f15d"; } - -.fa-sort-alpha-up:before { - content: "\f15e"; } - -.fa-sort-amount-down:before { - content: "\f160"; } - -.fa-sort-amount-up:before { - content: "\f161"; } - -.fa-sort-down:before { - content: "\f0dd"; } - -.fa-sort-numeric-down:before { - content: "\f162"; } - -.fa-sort-numeric-up:before { - content: "\f163"; } - -.fa-sort-up:before { - content: "\f0de"; } - -.fa-soundcloud:before { - content: "\f1be"; } - -.fa-space-shuttle:before { - content: "\f197"; } - -.fa-speakap:before { - content: "\f3f3"; } - -.fa-spinner:before { - content: "\f110"; } - -.fa-spotify:before { - content: "\f1bc"; } - -.fa-square:before { - content: "\f0c8"; } - -.fa-square-full:before { - content: "\f45c"; } - -.fa-stack-exchange:before { - content: "\f18d"; } - -.fa-stack-overflow:before { - content: "\f16c"; } - -.fa-star:before { - content: "\f005"; } - -.fa-star-half:before { - content: "\f089"; } - -.fa-staylinked:before { - content: "\f3f5"; } - -.fa-steam:before { - content: "\f1b6"; } - -.fa-steam-square:before { - content: "\f1b7"; } - -.fa-steam-symbol:before { - content: "\f3f6"; } - -.fa-step-backward:before { - content: "\f048"; } - -.fa-step-forward:before { - content: "\f051"; } - -.fa-stethoscope:before { - content: "\f0f1"; } - -.fa-sticker-mule:before { - content: "\f3f7"; } - -.fa-sticky-note:before { - content: "\f249"; } - -.fa-stop:before { - content: "\f04d"; } - -.fa-stop-circle:before { - content: "\f28d"; } - -.fa-stopwatch:before { - content: "\f2f2"; } - -.fa-strava:before { - content: "\f428"; } - -.fa-street-view:before { - content: "\f21d"; } - -.fa-strikethrough:before { - content: "\f0cc"; } - -.fa-stripe:before { - content: "\f429"; } - -.fa-stripe-s:before { - content: "\f42a"; } - -.fa-studiovinari:before { - content: "\f3f8"; } - -.fa-stumbleupon:before { - content: "\f1a4"; } - -.fa-stumbleupon-circle:before { - content: "\f1a3"; } - -.fa-subscript:before { - content: "\f12c"; } - -.fa-subway:before { - content: "\f239"; } - -.fa-suitcase:before { - content: "\f0f2"; } - -.fa-sun:before { - content: "\f185"; } - -.fa-superpowers:before { - content: "\f2dd"; } - -.fa-superscript:before { - content: "\f12b"; } - -.fa-supple:before { - content: "\f3f9"; } - -.fa-sync:before { - content: "\f021"; } - -.fa-sync-alt:before { - content: "\f2f1"; } - -.fa-syringe:before { - content: "\f48e"; } - -.fa-table:before { - content: "\f0ce"; } - -.fa-table-tennis:before { - content: "\f45d"; } - -.fa-tablet:before { - content: "\f10a"; } - -.fa-tablet-alt:before { - content: "\f3fa"; } - -.fa-tachometer-alt:before { - content: "\f3fd"; } - -.fa-tag:before { - content: "\f02b"; } - -.fa-tags:before { - content: "\f02c"; } - -.fa-tasks:before { - content: "\f0ae"; } - -.fa-taxi:before { - content: "\f1ba"; } - -.fa-telegram:before { - content: "\f2c6"; } - -.fa-telegram-plane:before { - content: "\f3fe"; } - -.fa-tencent-weibo:before { - content: "\f1d5"; } - -.fa-terminal:before { - content: "\f120"; } - -.fa-text-height:before { - content: "\f034"; } - -.fa-text-width:before { - content: "\f035"; } - -.fa-th:before { - content: "\f00a"; } - -.fa-th-large:before { - content: "\f009"; } - -.fa-th-list:before { - content: "\f00b"; } - -.fa-themeisle:before { - content: "\f2b2"; } - -.fa-thermometer:before { - content: "\f491"; } - -.fa-thermometer-empty:before { - content: "\f2cb"; } - -.fa-thermometer-full:before { - content: "\f2c7"; } - -.fa-thermometer-half:before { - content: "\f2c9"; } - -.fa-thermometer-quarter:before { - content: "\f2ca"; } - -.fa-thermometer-three-quarters:before { - content: "\f2c8"; } - -.fa-thumbs-down:before { - content: "\f165"; } - -.fa-thumbs-up:before { - content: "\f164"; } - -.fa-thumbtack:before { - content: "\f08d"; } - -.fa-ticket-alt:before { - content: "\f3ff"; } - -.fa-times:before { - content: "\f00d"; } - -.fa-times-circle:before { - content: "\f057"; } - -.fa-tint:before { - content: "\f043"; } - -.fa-toggle-off:before { - content: "\f204"; } - -.fa-toggle-on:before { - content: "\f205"; } - -.fa-trademark:before { - content: "\f25c"; } - -.fa-train:before { - content: "\f238"; } - -.fa-transgender:before { - content: "\f224"; } - -.fa-transgender-alt:before { - content: "\f225"; } - -.fa-trash:before { - content: "\f1f8"; } - -.fa-trash-alt:before { - content: "\f2ed"; } - -.fa-tree:before { - content: "\f1bb"; } - -.fa-trello:before { - content: "\f181"; } - -.fa-tripadvisor:before { - content: "\f262"; } - -.fa-trophy:before { - content: "\f091"; } - -.fa-truck:before { - content: "\f0d1"; } - -.fa-tty:before { - content: "\f1e4"; } - -.fa-tumblr:before { - content: "\f173"; } - -.fa-tumblr-square:before { - content: "\f174"; } - -.fa-tv:before { - content: "\f26c"; } - -.fa-twitch:before { - content: "\f1e8"; } - -.fa-twitter:before { - content: "\f099"; } - -.fa-twitter-square:before { - content: "\f081"; } - -.fa-typo3:before { - content: "\f42b"; } - -.fa-uber:before { - content: "\f402"; } - -.fa-uikit:before { - content: "\f403"; } - -.fa-umbrella:before { - content: "\f0e9"; } - -.fa-underline:before { - content: "\f0cd"; } - -.fa-undo:before { - content: "\f0e2"; } - -.fa-undo-alt:before { - content: "\f2ea"; } - -.fa-uniregistry:before { - content: "\f404"; } - -.fa-universal-access:before { - content: "\f29a"; } - -.fa-university:before { - content: "\f19c"; } - -.fa-unlink:before { - content: "\f127"; } - -.fa-unlock:before { - content: "\f09c"; } - -.fa-unlock-alt:before { - content: "\f13e"; } - -.fa-untappd:before { - content: "\f405"; } - -.fa-upload:before { - content: "\f093"; } - -.fa-usb:before { - content: "\f287"; } - -.fa-user:before { - content: "\f007"; } - -.fa-user-circle:before { - content: "\f2bd"; } - -.fa-user-md:before { - content: "\f0f0"; } - -.fa-user-plus:before { - content: "\f234"; } - -.fa-user-secret:before { - content: "\f21b"; } - -.fa-user-times:before { - content: "\f235"; } - -.fa-users:before { - content: "\f0c0"; } - -.fa-ussunnah:before { - content: "\f407"; } - -.fa-utensil-spoon:before { - content: "\f2e5"; } - -.fa-utensils:before { - content: "\f2e7"; } - -.fa-vaadin:before { - content: "\f408"; } - -.fa-venus:before { - content: "\f221"; } - -.fa-venus-double:before { - content: "\f226"; } - -.fa-venus-mars:before { - content: "\f228"; } - -.fa-viacoin:before { - content: "\f237"; } - -.fa-viadeo:before { - content: "\f2a9"; } - -.fa-viadeo-square:before { - content: "\f2aa"; } - -.fa-viber:before { - content: "\f409"; } - -.fa-video:before { - content: "\f03d"; } - -.fa-vimeo:before { - content: "\f40a"; } - -.fa-vimeo-square:before { - content: "\f194"; } - -.fa-vimeo-v:before { - content: "\f27d"; } - -.fa-vine:before { - content: "\f1ca"; } - -.fa-vk:before { - content: "\f189"; } - -.fa-vnv:before { - content: "\f40b"; } - -.fa-volleyball-ball:before { - content: "\f45f"; } - -.fa-volume-down:before { - content: "\f027"; } - -.fa-volume-off:before { - content: "\f026"; } - -.fa-volume-up:before { - content: "\f028"; } - -.fa-vuejs:before { - content: "\f41f"; } - -.fa-warehouse:before { - content: "\f494"; } - -.fa-weibo:before { - content: "\f18a"; } - -.fa-weight:before { - content: "\f496"; } - -.fa-weixin:before { - content: "\f1d7"; } - -.fa-whatsapp:before { - content: "\f232"; } - -.fa-whatsapp-square:before { - content: "\f40c"; } - -.fa-wheelchair:before { - content: "\f193"; } - -.fa-whmcs:before { - content: "\f40d"; } - -.fa-wifi:before { - content: "\f1eb"; } - -.fa-wikipedia-w:before { - content: "\f266"; } - -.fa-window-close:before { - content: "\f410"; } - -.fa-window-maximize:before { - content: "\f2d0"; } - -.fa-window-minimize:before { - content: "\f2d1"; } - -.fa-window-restore:before { - content: "\f2d2"; } - -.fa-windows:before { - content: "\f17a"; } - -.fa-won-sign:before { - content: "\f159"; } - -.fa-wordpress:before { - content: "\f19a"; } - -.fa-wordpress-simple:before { - content: "\f411"; } - -.fa-wpbeginner:before { - content: "\f297"; } - -.fa-wpexplorer:before { - content: "\f2de"; } - -.fa-wpforms:before { - content: "\f298"; } - -.fa-wrench:before { - content: "\f0ad"; } - -.fa-xbox:before { - content: "\f412"; } - -.fa-xing:before { - content: "\f168"; } - -.fa-xing-square:before { - content: "\f169"; } - -.fa-y-combinator:before { - content: "\f23b"; } - -.fa-yahoo:before { - content: "\f19e"; } - -.fa-yandex:before { - content: "\f413"; } - -.fa-yandex-international:before { - content: "\f414"; } - -.fa-yelp:before { - content: "\f1e9"; } - -.fa-yen-sign:before { - content: "\f157"; } - -.fa-yoast:before { - content: "\f2b1"; } - -.fa-youtube:before { - content: "\f167"; } - -.fa-youtube-square:before { - content: "\f431"; } - -.sr-only { - border: 0; - clip: rect(0, 0, 0, 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; } - -.sr-only-focusable:active, .sr-only-focusable:focus { - clip: auto; - height: auto; - margin: 0; - overflow: visible; - position: static; - width: auto; } diff --git a/vendor/fontawesome/css/fontawesome.min.css b/vendor/fontawesome/css/fontawesome.min.css deleted file mode 100644 index 67ae218..0000000 --- a/vendor/fontawesome/css/fontawesome.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.0.7 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:a 2s infinite linear;animation:a 2s infinite linear}.fa-pulse{-webkit-animation:a 1s infinite steps(8);animation:a 1s infinite steps(8)}@-webkit-keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comments:before{content:"\f086"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crosshairs:before{content:"\f05b"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-dot-circle:before{content:"\f192"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-excel:before{content:"\f1c3"}.fa-file-image:before{content:"\f1c5"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-firstdraft:before{content:"\f3a1"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frown:before{content:"\f119"}.fa-futbol:before{content:"\f1e3"}.fa-gamepad:before{content:"\f11b"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-male:before{content:"\f183"}.fa-map:before{content:"\f279"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-maxcdn:before{content:"\f136"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-meh:before{content:"\f11a"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-moon:before{content:"\f186"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-paragraph:before{content:"\f1dd"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-percent:before{content:"\f295"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phone:before{content:"\f095"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-print:before{content:"\f02f"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-rebel:before{content:"\f1d0"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-road:before{content:"\f018"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-rupee-sign:before{content:"\f156"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shower:before{content:"\f2cc"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-spotify:before{content:"\f1bc"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-strava:before{content:"\f428"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-circle:before{content:"\f2bd"}.fa-user-md:before{content:"\f0f0"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-warehouse:before{content:"\f494"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto} \ No newline at end of file diff --git a/vendor/fontawesome/webfonts/fa-brands-400.eot b/vendor/fontawesome/webfonts/fa-brands-400.eot deleted file mode 100644 index fa56790..0000000 Binary files a/vendor/fontawesome/webfonts/fa-brands-400.eot and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-brands-400.svg b/vendor/fontawesome/webfonts/fa-brands-400.svg deleted file mode 100644 index 2235f3e..0000000 --- a/vendor/fontawesome/webfonts/fa-brands-400.svg +++ /dev/null @@ -1,1008 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/fontawesome/webfonts/fa-brands-400.ttf b/vendor/fontawesome/webfonts/fa-brands-400.ttf deleted file mode 100644 index 6bef897..0000000 Binary files a/vendor/fontawesome/webfonts/fa-brands-400.ttf and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-brands-400.woff b/vendor/fontawesome/webfonts/fa-brands-400.woff deleted file mode 100644 index 2b5d946..0000000 Binary files a/vendor/fontawesome/webfonts/fa-brands-400.woff and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-brands-400.woff2 b/vendor/fontawesome/webfonts/fa-brands-400.woff2 deleted file mode 100644 index 7008b03..0000000 Binary files a/vendor/fontawesome/webfonts/fa-brands-400.woff2 and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-regular-400.eot b/vendor/fontawesome/webfonts/fa-regular-400.eot deleted file mode 100644 index e7e4726..0000000 Binary files a/vendor/fontawesome/webfonts/fa-regular-400.eot and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-regular-400.svg b/vendor/fontawesome/webfonts/fa-regular-400.svg deleted file mode 100644 index 7dbd736..0000000 --- a/vendor/fontawesome/webfonts/fa-regular-400.svg +++ /dev/null @@ -1,366 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/fontawesome/webfonts/fa-regular-400.ttf b/vendor/fontawesome/webfonts/fa-regular-400.ttf deleted file mode 100644 index d61aedd..0000000 Binary files a/vendor/fontawesome/webfonts/fa-regular-400.ttf and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-regular-400.woff b/vendor/fontawesome/webfonts/fa-regular-400.woff deleted file mode 100644 index 8153f4f..0000000 Binary files a/vendor/fontawesome/webfonts/fa-regular-400.woff and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-regular-400.woff2 b/vendor/fontawesome/webfonts/fa-regular-400.woff2 deleted file mode 100644 index 44b010a..0000000 Binary files a/vendor/fontawesome/webfonts/fa-regular-400.woff2 and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-solid-900.eot b/vendor/fontawesome/webfonts/fa-solid-900.eot deleted file mode 100644 index af7eea4..0000000 Binary files a/vendor/fontawesome/webfonts/fa-solid-900.eot and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-solid-900.svg b/vendor/fontawesome/webfonts/fa-solid-900.svg deleted file mode 100644 index 41d8bee..0000000 --- a/vendor/fontawesome/webfonts/fa-solid-900.svg +++ /dev/null @@ -1,1518 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/fontawesome/webfonts/fa-solid-900.ttf b/vendor/fontawesome/webfonts/fa-solid-900.ttf deleted file mode 100644 index 5f00b17..0000000 Binary files a/vendor/fontawesome/webfonts/fa-solid-900.ttf and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-solid-900.woff b/vendor/fontawesome/webfonts/fa-solid-900.woff deleted file mode 100644 index 0f0fa09..0000000 Binary files a/vendor/fontawesome/webfonts/fa-solid-900.woff and /dev/null differ diff --git a/vendor/fontawesome/webfonts/fa-solid-900.woff2 b/vendor/fontawesome/webfonts/fa-solid-900.woff2 deleted file mode 100644 index 64840a6..0000000 Binary files a/vendor/fontawesome/webfonts/fa-solid-900.woff2 and /dev/null differ diff --git a/vendor/html2canvas/html2canvas.js b/vendor/html2canvas/html2canvas.js deleted file mode 100644 index 3a44bc3..0000000 --- a/vendor/html2canvas/html2canvas.js +++ /dev/null @@ -1,2868 +0,0 @@ -/* - html2canvas 0.4.1 - Copyright (c) 2013 Niklas von Hertzen - - Released under MIT License -*/ - -(function(window, document, undefined){ - -"use strict"; - -var _html2canvas = {}, -previousElement, -computedCSS, -html2canvas; - -_html2canvas.Util = {}; - -_html2canvas.Util.log = function(a) { - if (_html2canvas.logging && window.console && window.console.log) { - window.console.log(a); - } -}; - -_html2canvas.Util.trimText = (function(isNative){ - return function(input) { - return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' ); - }; -})(String.prototype.trim); - -_html2canvas.Util.asFloat = function(v) { - return parseFloat(v); -}; - -(function() { - // TODO: support all possible length values - var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g; - var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g; - _html2canvas.Util.parseTextShadows = function (value) { - if (!value || value === 'none') { - return []; - } - - // find multiple shadow declarations - var shadows = value.match(TEXT_SHADOW_PROPERTY), - results = []; - for (var i = 0; shadows && (i < shadows.length); i++) { - var s = shadows[i].match(TEXT_SHADOW_VALUES); - results.push({ - color: s[0], - offsetX: s[1] ? s[1].replace('px', '') : 0, - offsetY: s[2] ? s[2].replace('px', '') : 0, - blur: s[3] ? s[3].replace('px', '') : 0 - }); - } - return results; - }; -})(); - - -_html2canvas.Util.parseBackgroundImage = function (value) { - var whitespace = ' \r\n\t', - method, definition, prefix, prefix_i, block, results = [], - c, mode = 0, numParen = 0, quote, args; - - var appendResult = function(){ - if(method) { - if(definition.substr( 0, 1 ) === '"') { - definition = definition.substr( 1, definition.length - 2 ); - } - if(definition) { - args.push(definition); - } - if(method.substr( 0, 1 ) === '-' && - (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) { - prefix = method.substr( 0, prefix_i); - method = method.substr( prefix_i ); - } - results.push({ - prefix: prefix, - method: method.toLowerCase(), - value: block, - args: args - }); - } - args = []; //for some odd reason, setting .length = 0 didn't work in safari - method = - prefix = - definition = - block = ''; - }; - - appendResult(); - for(var i = 0, ii = value.length; i -1){ - continue; - } - switch(c) { - case '"': - if(!quote) { - quote = c; - } - else if(quote === c) { - quote = null; - } - break; - - case '(': - if(quote) { break; } - else if(mode === 0) { - mode = 1; - block += c; - continue; - } else { - numParen++; - } - break; - - case ')': - if(quote) { break; } - else if(mode === 1) { - if(numParen === 0) { - mode = 0; - block += c; - appendResult(); - continue; - } else { - numParen--; - } - } - break; - - case ',': - if(quote) { break; } - else if(mode === 0) { - appendResult(); - continue; - } - else if (mode === 1) { - if(numParen === 0 && !method.match(/^url$/i)) { - args.push(definition); - definition = ''; - block += c; - continue; - } - } - break; - } - - block += c; - if(mode === 0) { method += c; } - else { definition += c; } - } - appendResult(); - - return results; -}; - -_html2canvas.Util.Bounds = function (element) { - var clientRect, bounds = {}; - - if (element.getBoundingClientRect){ - clientRect = element.getBoundingClientRect(); - - // TODO add scroll position to bounds, so no scrolling of window necessary - bounds.top = clientRect.top; - bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height); - bounds.left = clientRect.left; - - bounds.width = element.offsetWidth; - bounds.height = element.offsetHeight; - } - - return bounds; -}; - -// TODO ideally, we'd want everything to go through this function instead of Util.Bounds, -// but would require further work to calculate the correct positions for elements with offsetParents -_html2canvas.Util.OffsetBounds = function (element) { - var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0}; - - return { - top: element.offsetTop + parent.top, - bottom: element.offsetTop + element.offsetHeight + parent.top, - left: element.offsetLeft + parent.left, - width: element.offsetWidth, - height: element.offsetHeight - }; -}; - -function toPX(element, attribute, value ) { - var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute], - left, - style = element.style; - - // Check if we are not dealing with pixels, (Opera has issues with this) - // Ported from jQuery css.js - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - - if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) { - // Remember the original values - left = style.left; - - // Put in the new values to get a computed value out - if (rsLeft) { - element.runtimeStyle.left = element.currentStyle.left; - } - style.left = attribute === "fontSize" ? "1em" : (value || 0); - value = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if (rsLeft) { - element.runtimeStyle.left = rsLeft; - } - } - - if (!/^(thin|medium|thick)$/i.test(value)) { - return Math.round(parseFloat(value)) + "px"; - } - - return value; -} - -function asInt(val) { - return parseInt(val, 10); -} - -function parseBackgroundSizePosition(value, element, attribute, index) { - value = (value || '').split(','); - value = value[index || 0] || value[0] || 'auto'; - value = _html2canvas.Util.trimText(value).split(' '); - - if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) { - //these values will be handled in the parent function - } else { - value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0]; - if(value[1] === undefined) { - if(attribute === 'backgroundSize') { - value[1] = 'auto'; - return value; - } else { - // IE 9 doesn't return double digit always - value[1] = value[0]; - } - } - value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1]; - } - return value; -} - -_html2canvas.Util.getCSS = function (element, attribute, index) { - if (previousElement !== element) { - computedCSS = document.defaultView.getComputedStyle(element, null); - } - - var value = computedCSS[attribute]; - - if (/^background(Size|Position)$/.test(attribute)) { - return parseBackgroundSizePosition(value, element, attribute, index); - } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) { - var arr = value.split(" "); - if (arr.length <= 1) { - arr[1] = arr[0]; - } - return arr.map(asInt); - } - - return value; -}; - -_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){ - var target_ratio = target_width / target_height, - current_ratio = current_width / current_height, - output_width, output_height; - - if(!stretch_mode || stretch_mode === 'auto') { - output_width = target_width; - output_height = target_height; - } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') { - output_height = target_height; - output_width = target_height * current_ratio; - } else { - output_width = target_width; - output_height = target_width / current_ratio; - } - - return { - width: output_width, - height: output_height - }; -}; - -function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) { - var bgposition = _html2canvas.Util.getCSS( el, prop, imageIndex ) , - topPos, - left, - percentage, - val; - - if (bgposition.length === 1){ - val = bgposition[0]; - - bgposition = []; - - bgposition[0] = val; - bgposition[1] = val; - } - - if (bgposition[0].toString().indexOf("%") !== -1){ - percentage = (parseFloat(bgposition[0])/100); - left = bounds.width * percentage; - if(prop !== 'backgroundSize') { - left -= (backgroundSize || image).width*percentage; - } - } else { - if(prop === 'backgroundSize') { - if(bgposition[0] === 'auto') { - left = image.width; - } else { - if (/contain|cover/.test(bgposition[0])) { - var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]); - left = resized.width; - topPos = resized.height; - } else { - left = parseInt(bgposition[0], 10); - } - } - } else { - left = parseInt( bgposition[0], 10); - } - } - - - if(bgposition[1] === 'auto') { - topPos = left / image.width * image.height; - } else if (bgposition[1].toString().indexOf("%") !== -1){ - percentage = (parseFloat(bgposition[1])/100); - topPos = bounds.height * percentage; - if(prop !== 'backgroundSize') { - topPos -= (backgroundSize || image).height * percentage; - } - - } else { - topPos = parseInt(bgposition[1],10); - } - - return [left, topPos]; -} - -_html2canvas.Util.BackgroundPosition = function( el, bounds, image, imageIndex, backgroundSize ) { - var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image, imageIndex, backgroundSize ); - return { left: result[0], top: result[1] }; -}; - -_html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) { - var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex ); - return { width: result[0], height: result[1] }; -}; - -_html2canvas.Util.Extend = function (options, defaults) { - for (var key in options) { - if (options.hasOwnProperty(key)) { - defaults[key] = options[key]; - } - } - return defaults; -}; - - -/* - * Derived from jQuery.contents() - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - */ -_html2canvas.Util.Children = function( elem ) { - var children; - try { - children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) { - var ret = []; - if (array !== null) { - (function(first, second ) { - var i = first.length, - j = 0; - - if (typeof second.length === "number") { - for (var l = second.length; j < l; j++) { - first[i++] = second[j]; - } - } else { - while (second[j] !== undefined) { - first[i++] = second[j++]; - } - } - - first.length = i; - - return first; - })(ret, array); - } - return ret; - })(elem.childNodes); - - } catch (ex) { - _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message); - children = []; - } - return children; -}; - -_html2canvas.Util.isTransparent = function(backgroundColor) { - return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)"); -}; -_html2canvas.Util.Font = (function () { - - var fontData = {}; - - return function(font, fontSize, doc) { - if (fontData[font + "-" + fontSize] !== undefined) { - return fontData[font + "-" + fontSize]; - } - - var container = doc.createElement('div'), - img = doc.createElement('img'), - span = doc.createElement('span'), - sampleText = 'Hidden Text', - baseline, - middle, - metricsObj; - - container.style.visibility = "hidden"; - container.style.fontFamily = font; - container.style.fontSize = fontSize; - container.style.margin = 0; - container.style.padding = 0; - - doc.body.appendChild(container); - - // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif) - img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs="; - img.width = 1; - img.height = 1; - - img.style.margin = 0; - img.style.padding = 0; - img.style.verticalAlign = "baseline"; - - span.style.fontFamily = font; - span.style.fontSize = fontSize; - span.style.margin = 0; - span.style.padding = 0; - - span.appendChild(doc.createTextNode(sampleText)); - container.appendChild(span); - container.appendChild(img); - baseline = (img.offsetTop - span.offsetTop) + 1; - - container.removeChild(span); - container.appendChild(doc.createTextNode(sampleText)); - - container.style.lineHeight = "normal"; - img.style.verticalAlign = "super"; - - middle = (img.offsetTop-container.offsetTop) + 1; - metricsObj = { - baseline: baseline, - lineWidth: 1, - middle: middle - }; - - fontData[font + "-" + fontSize] = metricsObj; - - doc.body.removeChild(container); - - return metricsObj; - }; -})(); - -(function(){ - var Util = _html2canvas.Util, - Generate = {}; - - _html2canvas.Generate = Generate; - - var reGradients = [ - /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/, - /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/, - /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/, - /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/, - /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/ - ]; - - /* - * TODO: Add IE10 vendor prefix (-ms) support - * TODO: Add W3C gradient (linear-gradient) support - * TODO: Add old Webkit -webkit-gradient(radial, ...) support - * TODO: Maybe some RegExp optimizations are possible ;o) - */ - Generate.parseGradient = function(css, bounds) { - var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl; - - for(i = 0; i < len; i+=1){ - m1 = css.match(reGradients[i]); - if(m1) { - break; - } - } - - if(m1) { - switch(m1[1]) { - case '-webkit-linear-gradient': - case '-o-linear-gradient': - - gradient = { - type: 'linear', - x0: null, - y0: null, - x1: null, - y1: null, - colorStops: [] - }; - - // get coordinates - m2 = m1[2].match(/\w+/g); - if(m2){ - m2Len = m2.length; - for(i = 0; i < m2Len; i+=1){ - switch(m2[i]) { - case 'top': - gradient.y0 = 0; - gradient.y1 = bounds.height; - break; - - case 'right': - gradient.x0 = bounds.width; - gradient.x1 = 0; - break; - - case 'bottom': - gradient.y0 = bounds.height; - gradient.y1 = 0; - break; - - case 'left': - gradient.x0 = 0; - gradient.x1 = bounds.width; - break; - } - } - } - if(gradient.x0 === null && gradient.x1 === null){ // center - gradient.x0 = gradient.x1 = bounds.width / 2; - } - if(gradient.y0 === null && gradient.y1 === null){ // center - gradient.y0 = gradient.y1 = bounds.height / 2; - } - - // get colors and stops - m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3] === '%'){ - stop /= 100; - } else { // px - stupid opera - stop /= bounds.width; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); - } - } - break; - - case '-webkit-gradient': - - gradient = { - type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions - x0: 0, - y0: 0, - x1: 0, - y1: 0, - colorStops: [] - }; - - // get coordinates - m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/); - if(m2){ - gradient.x0 = (m2[1] * bounds.width) / 100; - gradient.y0 = (m2[2] * bounds.height) / 100; - gradient.x1 = (m2[3] * bounds.width) / 100; - gradient.y1 = (m2[4] * bounds.height) / 100; - } - - // get colors and stops - m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g); - if(m2){ - m2Len = m2.length; - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/); - stop = parseFloat(m3[2]); - if(m3[1] === 'from') { - stop = 0.0; - } - if(m3[1] === 'to') { - stop = 1.0; - } - gradient.colorStops.push({ - color: m3[3], - stop: stop - }); - } - } - break; - - case '-moz-linear-gradient': - - gradient = { - type: 'linear', - x0: 0, - y0: 0, - x1: 0, - y1: 0, - colorStops: [] - }; - - // get coordinates - m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); - - // m2[1] == 0% -> left - // m2[1] == 50% -> center - // m2[1] == 100% -> right - - // m2[2] == 0% -> top - // m2[2] == 50% -> center - // m2[2] == 100% -> bottom - - if(m2){ - gradient.x0 = (m2[1] * bounds.width) / 100; - gradient.y0 = (m2[2] * bounds.height) / 100; - gradient.x1 = bounds.width - gradient.x0; - gradient.y1 = bounds.height - gradient.y0; - } - - // get colors and stops - m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3]){ // percentage - stop /= 100; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); - } - } - break; - - case '-webkit-radial-gradient': - case '-moz-radial-gradient': - case '-o-radial-gradient': - - gradient = { - type: 'circle', - x0: 0, - y0: 0, - x1: bounds.width, - y1: bounds.height, - cx: 0, - cy: 0, - rx: 0, - ry: 0, - colorStops: [] - }; - - // center - m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/); - if(m2){ - gradient.cx = (m2[1] * bounds.width) / 100; - gradient.cy = (m2[2] * bounds.height) / 100; - } - - // size - m2 = m1[3].match(/\w+/); - m3 = m1[4].match(/[a-z\-]*/); - if(m2 && m3){ - switch(m3[0]){ - case 'farthest-corner': - case 'cover': // is equivalent to farthest-corner - case '': // mozilla removes "cover" from definition :( - tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2)); - tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2)); - gradient.rx = gradient.ry = Math.max(tl, tr, br, bl); - break; - case 'closest-corner': - tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2)); - tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2)); - bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2)); - gradient.rx = gradient.ry = Math.min(tl, tr, br, bl); - break; - case 'farthest-side': - if(m2[0] === 'circle'){ - gradient.rx = gradient.ry = Math.max( - gradient.cx, - gradient.cy, - gradient.x1 - gradient.cx, - gradient.y1 - gradient.cy - ); - } else { // ellipse - - gradient.type = m2[0]; - - gradient.rx = Math.max( - gradient.cx, - gradient.x1 - gradient.cx - ); - gradient.ry = Math.max( - gradient.cy, - gradient.y1 - gradient.cy - ); - } - break; - case 'closest-side': - case 'contain': // is equivalent to closest-side - if(m2[0] === 'circle'){ - gradient.rx = gradient.ry = Math.min( - gradient.cx, - gradient.cy, - gradient.x1 - gradient.cx, - gradient.y1 - gradient.cy - ); - } else { // ellipse - - gradient.type = m2[0]; - - gradient.rx = Math.min( - gradient.cx, - gradient.x1 - gradient.cx - ); - gradient.ry = Math.min( - gradient.cy, - gradient.y1 - gradient.cy - ); - } - break; - - // TODO: add support for "30px 40px" sizes (webkit only) - } - } - - // color stops - m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g); - if(m2){ - m2Len = m2.length; - step = 1 / Math.max(m2Len - 1, 1); - for(i = 0; i < m2Len; i+=1){ - m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/); - if(m3[2]){ - stop = parseFloat(m3[2]); - if(m3[3] === '%'){ - stop /= 100; - } else { // px - stupid opera - stop /= bounds.width; - } - } else { - stop = i * step; - } - gradient.colorStops.push({ - color: m3[1], - stop: stop - }); - } - } - break; - } - } - - return gradient; - }; - - function addScrollStops(grad) { - return function(colorStop) { - try { - grad.addColorStop(colorStop.stop, colorStop.color); - } - catch(e) { - Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]); - } - }; - } - - Generate.Gradient = function(src, bounds) { - if(bounds.width === 0 || bounds.height === 0) { - return; - } - - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - gradient, grad; - - canvas.width = bounds.width; - canvas.height = bounds.height; - - // TODO: add support for multi defined background gradients - gradient = _html2canvas.Generate.parseGradient(src, bounds); - - if(gradient) { - switch(gradient.type) { - case 'linear': - grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1); - gradient.colorStops.forEach(addScrollStops(grad)); - ctx.fillStyle = grad; - ctx.fillRect(0, 0, bounds.width, bounds.height); - break; - - case 'circle': - grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx); - gradient.colorStops.forEach(addScrollStops(grad)); - ctx.fillStyle = grad; - ctx.fillRect(0, 0, bounds.width, bounds.height); - break; - - case 'ellipse': - var canvasRadial = document.createElement('canvas'), - ctxRadial = canvasRadial.getContext('2d'), - ri = Math.max(gradient.rx, gradient.ry), - di = ri * 2; - - canvasRadial.width = canvasRadial.height = di; - - grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri); - gradient.colorStops.forEach(addScrollStops(grad)); - - ctxRadial.fillStyle = grad; - ctxRadial.fillRect(0, 0, di, di); - - ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry); - break; - } - } - - return canvas; - }; - - Generate.ListAlpha = function(number) { - var tmp = "", - modulus; - - do { - modulus = number % 26; - tmp = String.fromCharCode((modulus) + 64) + tmp; - number = number / 26; - }while((number*26) > 26); - - return tmp; - }; - - Generate.ListRoman = function(number) { - var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"], - decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1], - roman = "", - v, - len = romanArray.length; - - if (number <= 0 || number >= 4000) { - return number; - } - - for (v=0; v < len; v+=1) { - while (number >= decimal[v]) { - number -= decimal[v]; - roman += romanArray[v]; - } - } - - return roman; - }; -})(); -function h2cRenderContext(width, height) { - var storage = []; - return { - storage: storage, - width: width, - height: height, - clip: function() { - storage.push({ - type: "function", - name: "clip", - 'arguments': arguments - }); - }, - translate: function() { - storage.push({ - type: "function", - name: "translate", - 'arguments': arguments - }); - }, - fill: function() { - storage.push({ - type: "function", - name: "fill", - 'arguments': arguments - }); - }, - save: function() { - storage.push({ - type: "function", - name: "save", - 'arguments': arguments - }); - }, - restore: function() { - storage.push({ - type: "function", - name: "restore", - 'arguments': arguments - }); - }, - fillRect: function () { - storage.push({ - type: "function", - name: "fillRect", - 'arguments': arguments - }); - }, - createPattern: function() { - storage.push({ - type: "function", - name: "createPattern", - 'arguments': arguments - }); - }, - drawShape: function() { - - var shape = []; - - storage.push({ - type: "function", - name: "drawShape", - 'arguments': shape - }); - - return { - moveTo: function() { - shape.push({ - name: "moveTo", - 'arguments': arguments - }); - }, - lineTo: function() { - shape.push({ - name: "lineTo", - 'arguments': arguments - }); - }, - arcTo: function() { - shape.push({ - name: "arcTo", - 'arguments': arguments - }); - }, - bezierCurveTo: function() { - shape.push({ - name: "bezierCurveTo", - 'arguments': arguments - }); - }, - quadraticCurveTo: function() { - shape.push({ - name: "quadraticCurveTo", - 'arguments': arguments - }); - } - }; - - }, - drawImage: function () { - storage.push({ - type: "function", - name: "drawImage", - 'arguments': arguments - }); - }, - fillText: function () { - storage.push({ - type: "function", - name: "fillText", - 'arguments': arguments - }); - }, - setVariable: function (variable, value) { - storage.push({ - type: "variable", - name: variable, - 'arguments': value - }); - return value; - } - }; -} -_html2canvas.Parse = function (images, options) { - window.scroll(0,0); - - var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default - numDraws = 0, - doc = element.ownerDocument, - Util = _html2canvas.Util, - support = Util.Support(options, doc), - ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"), - body = doc.body, - getCSS = Util.getCSS, - pseudoHide = "___html2canvas___pseudoelement", - hidePseudoElements = doc.createElement('style'); - - hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' + - '.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }'; - - body.appendChild(hidePseudoElements); - - images = images || {}; - - function documentWidth () { - return Math.max( - Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), - Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), - Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) - ); - } - - function documentHeight () { - return Math.max( - Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), - Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), - Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) - ); - } - - function getCSSInt(element, attribute) { - var val = parseInt(getCSS(element, attribute), 10); - return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html - } - - function renderRect (ctx, x, y, w, h, bgcolor) { - if (bgcolor !== "transparent"){ - ctx.setVariable("fillStyle", bgcolor); - ctx.fillRect(x, y, w, h); - numDraws+=1; - } - } - - function capitalize(m, p1, p2) { - if (m.length > 0) { - return p1 + p2.toUpperCase(); - } - } - - function textTransform (text, transform) { - switch(transform){ - case "lowercase": - return text.toLowerCase(); - case "capitalize": - return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize); - case "uppercase": - return text.toUpperCase(); - default: - return text; - } - } - - function noLetterSpacing(letter_spacing) { - return (/^(normal|none|0px)$/.test(letter_spacing)); - } - - function drawText(currentText, x, y, ctx){ - if (currentText !== null && Util.trimText(currentText).length > 0) { - ctx.fillText(currentText, x, y); - numDraws+=1; - } - } - - function setTextVariables(ctx, el, text_decoration, color) { - var align = false, - bold = getCSS(el, "fontWeight"), - family = getCSS(el, "fontFamily"), - size = getCSS(el, "fontSize"), - shadows = Util.parseTextShadows(getCSS(el, "textShadow")); - - switch(parseInt(bold, 10)){ - case 401: - bold = "bold"; - break; - case 400: - bold = "normal"; - break; - } - - ctx.setVariable("fillStyle", color); - ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" ")); - ctx.setVariable("textAlign", (align) ? "right" : "left"); - - if (shadows.length) { - // TODO: support multiple text shadows - // apply the first text shadow - ctx.setVariable("shadowColor", shadows[0].color); - ctx.setVariable("shadowOffsetX", shadows[0].offsetX); - ctx.setVariable("shadowOffsetY", shadows[0].offsetY); - ctx.setVariable("shadowBlur", shadows[0].blur); - } - - if (text_decoration !== "none"){ - return Util.Font(family, size, doc); - } - } - - function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) { - switch(text_decoration) { - case "underline": - // Draws a line at the baseline of the font - // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size - renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color); - break; - case "overline": - renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color); - break; - case "line-through": - // TODO try and find exact position for line-through - renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color); - break; - } - } - - function getTextBounds(state, text, textDecoration, isLast, transform) { - var bounds; - if (support.rangeBounds && !transform) { - if (textDecoration !== "none" || Util.trimText(text).length !== 0) { - bounds = textRangeBounds(text, state.node, state.textOffset); - } - state.textOffset += text.length; - } else if (state.node && typeof state.node.nodeValue === "string" ){ - var newTextNode = (isLast) ? state.node.splitText(text.length) : null; - bounds = textWrapperBounds(state.node, transform); - state.node = newTextNode; - } - return bounds; - } - - function textRangeBounds(text, textNode, textOffset) { - var range = doc.createRange(); - range.setStart(textNode, textOffset); - range.setEnd(textNode, textOffset + text.length); - return range.getBoundingClientRect(); - } - - function textWrapperBounds(oldTextNode, transform) { - var parent = oldTextNode.parentNode, - wrapElement = doc.createElement('wrapper'), - backupText = oldTextNode.cloneNode(true); - - wrapElement.appendChild(oldTextNode.cloneNode(true)); - parent.replaceChild(wrapElement, oldTextNode); - - var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement); - parent.replaceChild(backupText, wrapElement); - return bounds; - } - - function renderText(el, textNode, stack) { - var ctx = stack.ctx, - color = getCSS(el, "color"), - textDecoration = getCSS(el, "textDecoration"), - textAlign = getCSS(el, "textAlign"), - metrics, - textList, - state = { - node: textNode, - textOffset: 0 - }; - - if (Util.trimText(textNode.nodeValue).length > 0) { - textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform")); - textAlign = textAlign.replace(["-webkit-auto"],["auto"]); - - textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ? - textNode.nodeValue.split(/(\b| )/) - : textNode.nodeValue.split(""); - - metrics = setTextVariables(ctx, el, textDecoration, color); - - if (options.chinese) { - textList.forEach(function(word, index) { - if (/.*[\u4E00-\u9FA5].*$/.test(word)) { - word = word.split(""); - word.unshift(index, 1); - textList.splice.apply(textList, word); - } - }); - } - - textList.forEach(function(text, index) { - var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix); - if (bounds) { - drawText(text, bounds.left, bounds.bottom, ctx); - renderTextDecoration(ctx, textDecoration, bounds, metrics, color); - } - }); - } - } - - function listPosition (element, val) { - var boundElement = doc.createElement( "boundelement" ), - originalType, - bounds; - - boundElement.style.display = "inline"; - - originalType = element.style.listStyleType; - element.style.listStyleType = "none"; - - boundElement.appendChild(doc.createTextNode(val)); - - element.insertBefore(boundElement, element.firstChild); - - bounds = Util.Bounds(boundElement); - element.removeChild(boundElement); - element.style.listStyleType = originalType; - return bounds; - } - - function elementIndex(el) { - var i = -1, - count = 1, - childs = el.parentNode.childNodes; - - if (el.parentNode) { - while(childs[++i] !== el) { - if (childs[i].nodeType === 1) { - count++; - } - } - return count; - } else { - return -1; - } - } - - function listItemText(element, type) { - var currentIndex = elementIndex(element), text; - switch(type){ - case "decimal": - text = currentIndex; - break; - case "decimal-leading-zero": - text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString(); - break; - case "upper-roman": - text = _html2canvas.Generate.ListRoman( currentIndex ); - break; - case "lower-roman": - text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase(); - break; - case "lower-alpha": - text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase(); - break; - case "upper-alpha": - text = _html2canvas.Generate.ListAlpha( currentIndex ); - break; - } - - return text + ". "; - } - - function renderListItem(element, stack, elBounds) { - var x, - text, - ctx = stack.ctx, - type = getCSS(element, "listStyleType"), - listBounds; - - if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) { - text = listItemText(element, type); - listBounds = listPosition(element, text); - setTextVariables(ctx, element, "none", getCSS(element, "color")); - - if (getCSS(element, "listStylePosition") === "inside") { - ctx.setVariable("textAlign", "left"); - x = elBounds.left; - } else { - return; - } - - drawText(text, x, listBounds.bottom, ctx); - } - } - - function loadImage (src){ - var img = images[src]; - return (img && img.succeeded === true) ? img.img : false; - } - - function clipBounds(src, dst){ - var x = Math.max(src.left, dst.left), - y = Math.max(src.top, dst.top), - x2 = Math.min((src.left + src.width), (dst.left + dst.width)), - y2 = Math.min((src.top + src.height), (dst.top + dst.height)); - - return { - left:x, - top:y, - width:x2-x, - height:y2-y - }; - } - - function setZ(element, stack, parentStack){ - var newContext, - isPositioned = stack.cssPosition !== 'static', - zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto', - opacity = getCSS(element, 'opacity'), - isFloated = getCSS(element, 'cssFloat') !== 'none'; - - // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context - // When a new stacking context should be created: - // the root element (HTML), - // positioned (absolutely or relatively) with a z-index value other than "auto", - // elements with an opacity value less than 1. (See the specification for opacity), - // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post) - - stack.zIndex = newContext = h2czContext(zIndex); - newContext.isPositioned = isPositioned; - newContext.isFloated = isFloated; - newContext.opacity = opacity; - newContext.ownStacking = (zIndex !== 'auto' || opacity < 1); - - if (parentStack) { - parentStack.zIndex.children.push(stack); - } - } - - function renderImage(ctx, element, image, bounds, borders) { - - var paddingLeft = getCSSInt(element, 'paddingLeft'), - paddingTop = getCSSInt(element, 'paddingTop'), - paddingRight = getCSSInt(element, 'paddingRight'), - paddingBottom = getCSSInt(element, 'paddingBottom'); - - drawImage( - ctx, - image, - 0, //sx - 0, //sy - image.width, //sw - image.height, //sh - bounds.left + paddingLeft + borders[3].width, //dx - bounds.top + paddingTop + borders[0].width, // dy - bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw - bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh - ); - } - - function getBorderData(element) { - return ["Top", "Right", "Bottom", "Left"].map(function(side) { - return { - width: getCSSInt(element, 'border' + side + 'Width'), - color: getCSS(element, 'border' + side + 'Color') - }; - }); - } - - function getBorderRadiusData(element) { - return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) { - return getCSS(element, 'border' + side + 'Radius'); - }); - } - - var getCurvePoints = (function(kappa) { - - return function(x, y, r1, r2) { - var ox = (r1) * kappa, // control point offset horizontal - oy = (r2) * kappa, // control point offset vertical - xm = x + r1, // x-middle - ym = y + r2; // y-middle - return { - topLeft: bezierCurve({ - x:x, - y:ym - }, { - x:x, - y:ym - oy - }, { - x:xm - ox, - y:y - }, { - x:xm, - y:y - }), - topRight: bezierCurve({ - x:x, - y:y - }, { - x:x + ox, - y:y - }, { - x:xm, - y:ym - oy - }, { - x:xm, - y:ym - }), - bottomRight: bezierCurve({ - x:xm, - y:y - }, { - x:xm, - y:y + oy - }, { - x:x + ox, - y:ym - }, { - x:x, - y:ym - }), - bottomLeft: bezierCurve({ - x:xm, - y:ym - }, { - x:xm - ox, - y:ym - }, { - x:x, - y:y + oy - }, { - x:x, - y:y - }) - }; - }; - })(4 * ((Math.sqrt(2) - 1) / 3)); - - function bezierCurve(start, startControl, endControl, end) { - - var lerp = function (a, b, t) { - return { - x:a.x + (b.x - a.x) * t, - y:a.y + (b.y - a.y) * t - }; - }; - - return { - start: start, - startControl: startControl, - endControl: endControl, - end: end, - subdivide: function(t) { - var ab = lerp(start, startControl, t), - bc = lerp(startControl, endControl, t), - cd = lerp(endControl, end, t), - abbc = lerp(ab, bc, t), - bccd = lerp(bc, cd, t), - dest = lerp(abbc, bccd, t); - return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)]; - }, - curveTo: function(borderArgs) { - borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]); - }, - curveToReversed: function(borderArgs) { - borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]); - } - }; - } - - function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) { - if (radius1[0] > 0 || radius1[1] > 0) { - borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]); - corner1[0].curveTo(borderArgs); - corner1[1].curveTo(borderArgs); - } else { - borderArgs.push(["line", x, y]); - } - - if (radius2[0] > 0 || radius2[1] > 0) { - borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]); - } - } - - function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) { - var borderArgs = []; - - if (radius1[0] > 0 || radius1[1] > 0) { - borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]); - outer1[1].curveTo(borderArgs); - } else { - borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]); - } - - if (radius2[0] > 0 || radius2[1] > 0) { - borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]); - outer2[0].curveTo(borderArgs); - borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]); - inner2[0].curveToReversed(borderArgs); - } else { - borderArgs.push([ "line", borderData.c2[0], borderData.c2[1]]); - borderArgs.push([ "line", borderData.c3[0], borderData.c3[1]]); - } - - if (radius1[0] > 0 || radius1[1] > 0) { - borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]); - inner1[1].curveToReversed(borderArgs); - } else { - borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]); - } - - return borderArgs; - } - - function calculateCurvePoints(bounds, borderRadius, borders) { - - var x = bounds.left, - y = bounds.top, - width = bounds.width, - height = bounds.height, - - tlh = borderRadius[0][0], - tlv = borderRadius[0][1], - trh = borderRadius[1][0], - trv = borderRadius[1][1], - brh = borderRadius[2][0], - brv = borderRadius[2][1], - blh = borderRadius[3][0], - blv = borderRadius[3][1], - - topWidth = width - trh, - rightHeight = height - brv, - bottomWidth = width - brh, - leftHeight = height - blv; - - return { - topLeftOuter: getCurvePoints( - x, - y, - tlh, - tlv - ).topLeft.subdivide(0.5), - - topLeftInner: getCurvePoints( - x + borders[3].width, - y + borders[0].width, - Math.max(0, tlh - borders[3].width), - Math.max(0, tlv - borders[0].width) - ).topLeft.subdivide(0.5), - - topRightOuter: getCurvePoints( - x + topWidth, - y, - trh, - trv - ).topRight.subdivide(0.5), - - topRightInner: getCurvePoints( - x + Math.min(topWidth, width + borders[3].width), - y + borders[0].width, - (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, - trv - borders[0].width - ).topRight.subdivide(0.5), - - bottomRightOuter: getCurvePoints( - x + bottomWidth, - y + rightHeight, - brh, - brv - ).bottomRight.subdivide(0.5), - - bottomRightInner: getCurvePoints( - x + Math.min(bottomWidth, width + borders[3].width), - y + Math.min(rightHeight, height + borders[0].width), - Math.max(0, brh - borders[1].width), - Math.max(0, brv - borders[2].width) - ).bottomRight.subdivide(0.5), - - bottomLeftOuter: getCurvePoints( - x, - y + leftHeight, - blh, - blv - ).bottomLeft.subdivide(0.5), - - bottomLeftInner: getCurvePoints( - x + borders[3].width, - y + leftHeight, - Math.max(0, blh - borders[3].width), - Math.max(0, blv - borders[2].width) - ).bottomLeft.subdivide(0.5) - }; - } - - function getBorderClip(element, borderPoints, borders, radius, bounds) { - var backgroundClip = getCSS(element, 'backgroundClip'), - borderArgs = []; - - switch(backgroundClip) { - case "content-box": - case "padding-box": - parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width); - parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width); - parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width); - parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width); - break; - - default: - parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top); - parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top); - parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height); - parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height); - break; - } - - return borderArgs; - } - - function parseBorders(element, bounds, borders){ - var x = bounds.left, - y = bounds.top, - width = bounds.width, - height = bounds.height, - borderSide, - bx, - by, - bw, - bh, - borderArgs, - // http://www.w3.org/TR/css3-background/#the-border-radius - borderRadius = getBorderRadiusData(element), - borderPoints = calculateCurvePoints(bounds, borderRadius, borders), - borderData = { - clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds), - borders: [] - }; - - for (borderSide = 0; borderSide < 4; borderSide++) { - - if (borders[borderSide].width > 0) { - bx = x; - by = y; - bw = width; - bh = height - (borders[2].width); - - switch(borderSide) { - case 0: - // top border - bh = borders[0].width; - - borderArgs = drawSide({ - c1: [bx, by], - c2: [bx + bw, by], - c3: [bx + bw - borders[1].width, by + bh], - c4: [bx + borders[3].width, by + bh] - }, borderRadius[0], borderRadius[1], - borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner); - break; - case 1: - // right border - bx = x + width - (borders[1].width); - bw = borders[1].width; - - borderArgs = drawSide({ - c1: [bx + bw, by], - c2: [bx + bw, by + bh + borders[2].width], - c3: [bx, by + bh], - c4: [bx, by + borders[0].width] - }, borderRadius[1], borderRadius[2], - borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner); - break; - case 2: - // bottom border - by = (by + height) - (borders[2].width); - bh = borders[2].width; - - borderArgs = drawSide({ - c1: [bx + bw, by + bh], - c2: [bx, by + bh], - c3: [bx + borders[3].width, by], - c4: [bx + bw - borders[3].width, by] - }, borderRadius[2], borderRadius[3], - borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner); - break; - case 3: - // left border - bw = borders[3].width; - - borderArgs = drawSide({ - c1: [bx, by + bh + borders[2].width], - c2: [bx, by], - c3: [bx + bw, by + borders[0].width], - c4: [bx + bw, by + bh] - }, borderRadius[3], borderRadius[0], - borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner); - break; - } - - borderData.borders.push({ - args: borderArgs, - color: borders[borderSide].color - }); - - } - } - - return borderData; - } - - function createShape(ctx, args) { - var shape = ctx.drawShape(); - args.forEach(function(border, index) { - shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1)); - }); - return shape; - } - - function renderBorders(ctx, borderArgs, color) { - if (color !== "transparent") { - ctx.setVariable( "fillStyle", color); - createShape(ctx, borderArgs); - ctx.fill(); - numDraws+=1; - } - } - - function renderFormValue (el, bounds, stack){ - - var valueWrap = doc.createElement('valuewrap'), - cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'], - textValue, - textNode; - - cssPropertyArray.forEach(function(property) { - try { - valueWrap.style[property] = getCSS(el, property); - } catch(e) { - // Older IE has issues with "border" - Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message); - } - }); - - valueWrap.style.borderColor = "black"; - valueWrap.style.borderStyle = "solid"; - valueWrap.style.display = "block"; - valueWrap.style.position = "absolute"; - - if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){ - valueWrap.style.lineHeight = getCSS(el, "height"); - } - - valueWrap.style.top = bounds.top + "px"; - valueWrap.style.left = bounds.left + "px"; - - textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value; - if(!textValue) { - textValue = el.placeholder; - } - - textNode = doc.createTextNode(textValue); - - valueWrap.appendChild(textNode); - body.appendChild(valueWrap); - - renderText(el, textNode, stack); - body.removeChild(valueWrap); - } - - function drawImage (ctx) { - ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1)); - numDraws+=1; - } - - function getPseudoElement(el, which) { - var elStyle = window.getComputedStyle(el, which); - if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") { - return; - } - var content = elStyle.content + '', - first = content.substr( 0, 1 ); - //strips quotes - if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) { - content = content.substr( 1, content.length - 2 ); - } - - var isImage = content.substr( 0, 3 ) === 'url', - elps = document.createElement( isImage ? 'img' : 'span' ); - - elps.className = pseudoHide + "-before " + pseudoHide + "-after"; - - Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) { - // Prevent assigning of read only CSS Rules, ex. length, parentRule - try { - elps.style[prop] = elStyle[prop]; - } catch (e) { - Util.log(['Tried to assign readonly property ', prop, 'Error:', e]); - } - }); - - if(isImage) { - elps.src = Util.parseBackgroundImage(content)[0].args[0]; - } else { - elps.innerHTML = content; - } - return elps; - } - - function indexedProperty(property) { - return (isNaN(window.parseInt(property, 10))); - } - - function injectPseudoElements(el, stack) { - var before = getPseudoElement(el, ':before'), - after = getPseudoElement(el, ':after'); - if(!before && !after) { - return; - } - - if(before) { - el.className += " " + pseudoHide + "-before"; - el.parentNode.insertBefore(before, el); - parseElement(before, stack, true); - el.parentNode.removeChild(before); - el.className = el.className.replace(pseudoHide + "-before", "").trim(); - } - - if (after) { - el.className += " " + pseudoHide + "-after"; - el.appendChild(after); - parseElement(after, stack, true); - el.removeChild(after); - el.className = el.className.replace(pseudoHide + "-after", "").trim(); - } - - } - - function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) { - var offsetX = Math.round(bounds.left + backgroundPosition.left), - offsetY = Math.round(bounds.top + backgroundPosition.top); - - ctx.createPattern(image); - ctx.translate(offsetX, offsetY); - ctx.fill(); - ctx.translate(-offsetX, -offsetY); - } - - function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) { - var args = []; - args.push(["line", Math.round(left), Math.round(top)]); - args.push(["line", Math.round(left + width), Math.round(top)]); - args.push(["line", Math.round(left + width), Math.round(height + top)]); - args.push(["line", Math.round(left), Math.round(height + top)]); - createShape(ctx, args); - ctx.save(); - ctx.clip(); - renderBackgroundRepeat(ctx, image, backgroundPosition, bounds); - ctx.restore(); - } - - function renderBackgroundColor(ctx, backgroundBounds, bgcolor) { - renderRect( - ctx, - backgroundBounds.left, - backgroundBounds.top, - backgroundBounds.width, - backgroundBounds.height, - bgcolor - ); - } - - function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) { - var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex), - backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize), - backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText); - - image = resizeImage(image, backgroundSize); - - backgroundRepeat = backgroundRepeat[imageIndex] || backgroundRepeat[0]; - - switch (backgroundRepeat) { - case "repeat-x": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left, bounds.top + backgroundPosition.top, 99999, image.height); - break; - - case "repeat-y": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left + backgroundPosition.left, bounds.top, image.width, 99999); - break; - - case "no-repeat": - backgroundRepeatShape(ctx, image, backgroundPosition, bounds, - bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height); - break; - - default: - renderBackgroundRepeat(ctx, image, backgroundPosition, { - top: bounds.top, - left: bounds.left, - width: image.width, - height: image.height - }); - break; - } - } - - function renderBackgroundImage(element, bounds, ctx) { - var backgroundImage = getCSS(element, "backgroundImage"), - backgroundImages = Util.parseBackgroundImage(backgroundImage), - image, - imageIndex = backgroundImages.length; - - while(imageIndex--) { - backgroundImage = backgroundImages[imageIndex]; - - if (!backgroundImage.args || backgroundImage.args.length === 0) { - continue; - } - - var key = backgroundImage.method === 'url' ? - backgroundImage.args[0] : - backgroundImage.value; - - image = loadImage(key); - - // TODO add support for background-origin - if (image) { - renderBackgroundRepeating(element, bounds, ctx, image, imageIndex); - } else { - Util.log("html2canvas: Error loading background:", backgroundImage); - } - } - } - - function resizeImage(image, bounds) { - if(image.width === bounds.width && image.height === bounds.height) { - return image; - } - - var ctx, canvas = doc.createElement('canvas'); - canvas.width = bounds.width; - canvas.height = bounds.height; - ctx = canvas.getContext("2d"); - drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height ); - return canvas; - } - - function setOpacity(ctx, element, parentStack) { - return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1)); - } - - function removePx(str) { - return str.replace("px", ""); - } - - var transformRegExp = /(matrix)\((.+)\)/; - - function getTransform(element, parentStack) { - var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform"); - var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px"; - - transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat); - - var matrix; - if (transform && transform !== "none") { - var match = transform.match(transformRegExp); - if (match) { - switch(match[1]) { - case "matrix": - matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat); - break; - } - } - } - - return { - origin: transformOrigin, - matrix: matrix - }; - } - - function createStack(element, parentStack, bounds, transform) { - var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), - stack = { - ctx: ctx, - opacity: setOpacity(ctx, element, parentStack), - cssPosition: getCSS(element, "position"), - borders: getBorderData(element), - transform: transform, - clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null - }; - - setZ(element, stack, parentStack); - - // TODO correct overflow for absolute content residing under a static position - if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){ - stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds; - } - - return stack; - } - - function getBackgroundBounds(borders, bounds, clip) { - var backgroundBounds = { - left: bounds.left + borders[3].width, - top: bounds.top + borders[0].width, - width: bounds.width - (borders[1].width + borders[3].width), - height: bounds.height - (borders[0].width + borders[2].width) - }; - - if (clip) { - backgroundBounds = clipBounds(backgroundBounds, clip); - } - - return backgroundBounds; - } - - function getBounds(element, transform) { - var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element); - transform.origin[0] += bounds.left; - transform.origin[1] += bounds.top; - return bounds; - } - - function renderElement(element, parentStack, pseudoElement, ignoreBackground) { - var transform = getTransform(element, parentStack), - bounds = getBounds(element, transform), - image, - stack = createStack(element, parentStack, bounds, transform), - borders = stack.borders, - ctx = stack.ctx, - backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip), - borderData = parseBorders(element, bounds, borders), - backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"); - - - createShape(ctx, borderData.clip); - - ctx.save(); - ctx.clip(); - - if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) { - renderBackgroundColor(ctx, bounds, backgroundColor); - renderBackgroundImage(element, backgroundBounds, ctx); - } else if (ignoreBackground) { - stack.backgroundColor = backgroundColor; - } - - ctx.restore(); - - borderData.borders.forEach(function(border) { - renderBorders(ctx, border.args, border.color); - }); - - if (!pseudoElement) { - injectPseudoElements(element, stack); - } - - switch(element.nodeName){ - case "IMG": - if ((image = loadImage(element.getAttribute('src')))) { - renderImage(ctx, element, image, bounds, borders); - } else { - Util.log("html2canvas: Error loading :" + element.getAttribute('src')); - } - break; - case "INPUT": - // TODO add all relevant type's, i.e. HTML5 new stuff - // todo add support for placeholder attribute for browsers which support it - if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){ - renderFormValue(element, bounds, stack); - } - break; - case "TEXTAREA": - if ((element.value || element.placeholder || "").length > 0){ - renderFormValue(element, bounds, stack); - } - break; - case "SELECT": - if ((element.options||element.placeholder || "").length > 0){ - renderFormValue(element, bounds, stack); - } - break; - case "LI": - renderListItem(element, stack, backgroundBounds); - break; - case "CANVAS": - renderImage(ctx, element, element, bounds, borders); - break; - } - - return stack; - } - - function isElementVisible(element) { - return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); - } - - function parseElement (element, stack, pseudoElement) { - if (isElementVisible(element)) { - stack = renderElement(element, stack, pseudoElement, false) || stack; - if (!ignoreElementsRegExp.test(element.nodeName)) { - parseChildren(element, stack, pseudoElement); - } - } - } - - function parseChildren(element, stack, pseudoElement) { - Util.Children(element).forEach(function(node) { - if (node.nodeType === node.ELEMENT_NODE) { - parseElement(node, stack, pseudoElement); - } else if (node.nodeType === node.TEXT_NODE) { - renderText(element, node, stack); - } - }); - } - - function init() { - var background = getCSS(document.documentElement, "backgroundColor"), - transparentBackground = (Util.isTransparent(background) && element === document.body), - stack = renderElement(element, null, false, transparentBackground); - parseChildren(element, stack); - - if (transparentBackground) { - background = stack.backgroundColor; - } - - body.removeChild(hidePseudoElements); - return { - backgroundColor: background, - stack: stack - }; - } - - return init(); -}; - -function h2czContext(zindex) { - return { - zindex: zindex, - children: [] - }; -} - -_html2canvas.Preload = function( options ) { - - var images = { - numLoaded: 0, // also failed are counted here - numFailed: 0, - numTotal: 0, - cleanupDone: false - }, - pageOrigin, - Util = _html2canvas.Util, - methods, - i, - count = 0, - element = options.elements[0] || document.body, - doc = element.ownerDocument, - domImages = element.getElementsByTagName('img'), // Fetch images of the present element only - imgLen = domImages.length, - link = doc.createElement("a"), - supportCORS = (function( img ){ - return (img.crossOrigin !== undefined); - })(new Image()), - timeoutTimer; - - link.href = window.location.href; - pageOrigin = link.protocol + link.host; - - function isSameOrigin(url){ - link.href = url; - link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/ - var origin = link.protocol + link.host; - return (origin === pageOrigin); - } - - function start(){ - Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")"); - if (!images.firstRun && images.numLoaded >= images.numTotal){ - Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")"); - - if (typeof options.complete === "function"){ - options.complete(images); - } - - } - } - - // TODO modify proxy to serve images with CORS enabled, where available - function proxyGetImage(url, img, imageObj){ - var callback_name, - scriptUrl = options.proxy, - script; - - link.href = url; - url = link.href; // work around for pages with base href="" set - WARNING: this may change the url - - callback_name = 'html2canvas_' + (count++); - imageObj.callbackname = callback_name; - - if (scriptUrl.indexOf("?") > -1) { - scriptUrl += "&"; - } else { - scriptUrl += "?"; - } - scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name; - script = doc.createElement("script"); - - window[callback_name] = function(a){ - if (a.substring(0,6) === "error:"){ - imageObj.succeeded = false; - images.numLoaded++; - images.numFailed++; - start(); - } else { - setImageLoadHandlers(img, imageObj); - img.src = a; - } - window[callback_name] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9) - try { - delete window[callback_name]; // for all browser that support this - } catch(ex) {} - script.parentNode.removeChild(script); - script = null; - delete imageObj.script; - delete imageObj.callbackname; - }; - - script.setAttribute("type", "text/javascript"); - script.setAttribute("src", scriptUrl); - imageObj.script = script; - window.document.body.appendChild(script); - - } - - function loadPseudoElement(element, type) { - var style = window.getComputedStyle(element, type), - content = style.content; - if (content.substr(0, 3) === 'url') { - methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]); - } - loadBackgroundImages(style.backgroundImage, element); - } - - function loadPseudoElementImages(element) { - loadPseudoElement(element, ":before"); - loadPseudoElement(element, ":after"); - } - - function loadGradientImage(backgroundImage, bounds) { - var img = _html2canvas.Generate.Gradient(backgroundImage, bounds); - - if (img !== undefined){ - images[backgroundImage] = { - img: img, - succeeded: true - }; - images.numTotal++; - images.numLoaded++; - start(); - } - } - - function invalidBackgrounds(background_image) { - return (background_image && background_image.method && background_image.args && background_image.args.length > 0 ); - } - - function loadBackgroundImages(background_image, el) { - var bounds; - - _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) { - if (background_image.method === 'url') { - methods.loadImage(background_image.args[0]); - } else if(background_image.method.match(/\-?gradient$/)) { - if(bounds === undefined) { - bounds = _html2canvas.Util.Bounds(el); - } - loadGradientImage(background_image.value, bounds); - } - }); - } - - function getImages (el) { - var elNodeType = false; - - // Firefox fails with permission denied on pages with iframes - try { - Util.Children(el).forEach(getImages); - } - catch( e ) {} - - try { - elNodeType = el.nodeType; - } catch (ex) { - elNodeType = false; - Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message); - } - - if (elNodeType === 1 || elNodeType === undefined) { - loadPseudoElementImages(el); - try { - loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el); - } catch(e) { - Util.log("html2canvas: failed to get background-image - Exception: " + e.message); - } - loadBackgroundImages(el); - } - } - - function setImageLoadHandlers(img, imageObj) { - img.onload = function() { - if ( imageObj.timer !== undefined ) { - // CORS succeeded - window.clearTimeout( imageObj.timer ); - } - - images.numLoaded++; - imageObj.succeeded = true; - img.onerror = img.onload = null; - start(); - }; - img.onerror = function() { - if (img.crossOrigin === "anonymous") { - // CORS failed - window.clearTimeout( imageObj.timer ); - - // let's try with proxy instead - if ( options.proxy ) { - var src = img.src; - img = new Image(); - imageObj.img = img; - img.src = src; - - proxyGetImage( img.src, img, imageObj ); - return; - } - } - - images.numLoaded++; - images.numFailed++; - imageObj.succeeded = false; - img.onerror = img.onload = null; - start(); - }; - } - - methods = { - loadImage: function( src ) { - var img, imageObj; - if ( src && images[src] === undefined ) { - img = new Image(); - if ( src.match(/data:image\/.*;base64,/i) ) { - img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''); - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - } else if ( isSameOrigin( src ) || options.allowTaint === true ) { - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - img.src = src; - } else if ( supportCORS && !options.allowTaint && options.useCORS ) { - // attempt to load with CORS - - img.crossOrigin = "anonymous"; - imageObj = images[src] = { - img: img - }; - images.numTotal++; - setImageLoadHandlers(img, imageObj); - img.src = src; - } else if ( options.proxy ) { - imageObj = images[src] = { - img: img - }; - images.numTotal++; - proxyGetImage( src, img, imageObj ); - } - } - - }, - cleanupDOM: function(cause) { - var img, src; - if (!images.cleanupDone) { - if (cause && typeof cause === "string") { - Util.log("html2canvas: Cleanup because: " + cause); - } else { - Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms."); - } - - for (src in images) { - if (images.hasOwnProperty(src)) { - img = images[src]; - if (typeof img === "object" && img.callbackname && img.succeeded === undefined) { - // cancel proxy image request - window[img.callbackname] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9) - try { - delete window[img.callbackname]; // for all browser that support this - } catch(ex) {} - if (img.script && img.script.parentNode) { - img.script.setAttribute("src", "about:blank"); // try to cancel running request - img.script.parentNode.removeChild(img.script); - } - images.numLoaded++; - images.numFailed++; - Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal); - } - } - } - - // cancel any pending requests - if(window.stop !== undefined) { - window.stop(); - } else if(document.execCommand !== undefined) { - document.execCommand("Stop", false); - } - if (document.close !== undefined) { - document.close(); - } - images.cleanupDone = true; - if (!(cause && typeof cause === "string")) { - start(); - } - } - }, - - renderingDone: function() { - if (timeoutTimer) { - window.clearTimeout(timeoutTimer); - } - } - }; - - if (options.timeout > 0) { - timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout); - } - - Util.log('html2canvas: Preload starts: finding background-images'); - images.firstRun = true; - - getImages(element); - - Util.log('html2canvas: Preload: Finding images'); - // load images - for (i = 0; i < imgLen; i+=1){ - methods.loadImage( domImages[i].getAttribute( "src" ) ); - } - - images.firstRun = false; - Util.log('html2canvas: Preload: Done.'); - if (images.numTotal === images.numLoaded) { - start(); - } - - return methods; -}; - -_html2canvas.Renderer = function(parseQueue, options){ - - // http://www.w3.org/TR/CSS21/zindex.html - function createRenderQueue(parseQueue) { - var queue = [], - rootContext; - - rootContext = (function buildStackingContext(rootNode) { - var rootContext = {}; - function insert(context, node, specialParent) { - var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex), - contextForChildren = context, // the stacking context for children - isPositioned = node.zIndex.isPositioned, - isFloated = node.zIndex.isFloated, - stub = {node: node}, - childrenDest = specialParent; // where children without z-index should be pushed into - - if (node.zIndex.ownStacking) { - // '!' comes before numbers in sorted array - contextForChildren = stub.context = { '!': [{node:node, children: []}]}; - childrenDest = undefined; - } else if (isPositioned || isFloated) { - childrenDest = stub.children = []; - } - - if (zi === 0 && specialParent) { - specialParent.push(stub); - } else { - if (!context[zi]) { context[zi] = []; } - context[zi].push(stub); - } - - node.zIndex.children.forEach(function(childNode) { - insert(contextForChildren, childNode, childrenDest); - }); - } - insert(rootContext, rootNode); - return rootContext; - })(parseQueue); - - function sortZ(context) { - Object.keys(context).sort().forEach(function(zi) { - var nonPositioned = [], - floated = [], - positioned = [], - list = []; - - // positioned after static - context[zi].forEach(function(v) { - if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) { - // http://www.w3.org/TR/css3-color/#transparency - // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’. - positioned.push(v); - } else if (v.node.zIndex.isFloated) { - floated.push(v); - } else { - nonPositioned.push(v); - } - }); - - (function walk(arr) { - arr.forEach(function(v) { - list.push(v); - if (v.children) { walk(v.children); } - }); - })(nonPositioned.concat(floated, positioned)); - - list.forEach(function(v) { - if (v.context) { - sortZ(v.context); - } else { - queue.push(v.node); - } - }); - }); - } - - sortZ(rootContext); - - return queue; - } - - function getRenderer(rendererName) { - var renderer; - - if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) { - renderer = _html2canvas.Renderer[rendererName](options); - } else if (typeof rendererName === "function") { - renderer = rendererName(options); - } else { - throw new Error("Unknown renderer"); - } - - if ( typeof renderer !== "function" ) { - throw new Error("Invalid renderer defined"); - } - return renderer; - } - - return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas); -}; - -_html2canvas.Util.Support = function (options, doc) { - - function supportSVGRendering() { - var img = new Image(), - canvas = doc.createElement("canvas"), - ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d"); - if (ctx === false) { - return false; - } - canvas.width = canvas.height = 10; - img.src = [ - "data:image/svg+xml,", - "", - "", - "
    ", - "sup", - "
    ", - "
    ", - "
    " - ].join(""); - try { - ctx.drawImage(img, 0, 0); - canvas.toDataURL(); - } catch(e) { - return false; - } - _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available'); - return true; - } - - // Test whether we can use ranges to measure bounding boxes - // Opera doesn't provide valid bounds.height/bottom even though it supports the method. - - function supportRangeBounds() { - var r, testElement, rangeBounds, rangeHeight, support = false; - - if (doc.createRange) { - r = doc.createRange(); - if (r.getBoundingClientRect) { - testElement = doc.createElement('boundtest'); - testElement.style.height = "123px"; - testElement.style.display = "block"; - doc.body.appendChild(testElement); - - r.selectNode(testElement); - rangeBounds = r.getBoundingClientRect(); - rangeHeight = rangeBounds.height; - - if (rangeHeight === 123) { - support = true; - } - doc.body.removeChild(testElement); - } - } - - return support; - } - - return { - rangeBounds: supportRangeBounds(), - svgRendering: options.svgRendering && supportSVGRendering() - }; -}; -window.html2canvas = function(elements, opts) { - elements = (elements.length) ? elements : [elements]; - var queue, - canvas, - options = { - // general - logging: false, - elements: elements, - background: "#fff", - - // preload options - proxy: null, - timeout: 0, // no timeout - useCORS: false, // try to load images as CORS (where available), before falling back to proxy - allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true - - // parse options - svgRendering: false, // use svg powered rendering where available (FF11+) - ignoreElements: "IFRAME|OBJECT|PARAM", - useOverflow: true, - letterRendering: false, - chinese: false, - - // render options - - width: null, - height: null, - taintTest: true, // do a taint test with all images before applying to canvas - renderer: "Canvas" - }; - - options = _html2canvas.Util.Extend(opts, options); - - _html2canvas.logging = options.logging; - options.complete = function( images ) { - - if (typeof options.onpreloaded === "function") { - if ( options.onpreloaded( images ) === false ) { - return; - } - } - queue = _html2canvas.Parse( images, options ); - - if (typeof options.onparsed === "function") { - if ( options.onparsed( queue ) === false ) { - return; - } - } - - canvas = _html2canvas.Renderer( queue, options ); - - if (typeof options.onrendered === "function") { - options.onrendered( canvas ); - } - - - }; - - // for pages without images, we still want this to be async, i.e. return methods before executing - window.setTimeout( function(){ - _html2canvas.Preload( options ); - }, 0 ); - - return { - render: function( queue, opts ) { - return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) ); - }, - parse: function( images, opts ) { - return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) ); - }, - preload: function( opts ) { - return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) ); - }, - log: _html2canvas.Util.log - }; -}; - -window.html2canvas.log = _html2canvas.Util.log; // for renderers -window.html2canvas.Renderer = { - Canvas: undefined // We are assuming this will be used -}; -_html2canvas.Renderer.Canvas = function(options) { - options = options || {}; - - var doc = document, - safeImages = [], - testCanvas = document.createElement("canvas"), - testctx = testCanvas.getContext("2d"), - Util = _html2canvas.Util, - canvas = options.canvas || doc.createElement('canvas'); - - function createShape(ctx, args) { - ctx.beginPath(); - args.forEach(function(arg) { - ctx[arg.name].apply(ctx, arg['arguments']); - }); - ctx.closePath(); - } - - function safeImage(item) { - if (safeImages.indexOf(item['arguments'][0].src ) === -1) { - testctx.drawImage(item['arguments'][0], 0, 0); - try { - testctx.getImageData(0, 0, 1, 1); - } catch(e) { - testCanvas = doc.createElement("canvas"); - testctx = testCanvas.getContext("2d"); - return false; - } - safeImages.push(item['arguments'][0].src); - } - return true; - } - - function renderItem(ctx, item) { - switch(item.type){ - case "variable": - ctx[item.name] = item['arguments']; - break; - case "function": - switch(item.name) { - case "createPattern": - if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) { - try { - ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat"); - } - catch(e) { - Util.log("html2canvas: Renderer: Error creating pattern", e.message); - } - } - break; - case "drawShape": - createShape(ctx, item['arguments']); - break; - case "drawImage": - if (item['arguments'][8] > 0 && item['arguments'][7] > 0) { - if (!options.taintTest || (options.taintTest && safeImage(item))) { - ctx.drawImage.apply( ctx, item['arguments'] ); - } - } - break; - default: - ctx[item.name].apply(ctx, item['arguments']); - } - break; - } - } - - return function(parsedData, options, document, queue, _html2canvas) { - var ctx = canvas.getContext("2d"), - newCanvas, - bounds, - fstyle, - zStack = parsedData.stack; - - canvas.width = canvas.style.width = options.width || zStack.ctx.width; - canvas.height = canvas.style.height = options.height || zStack.ctx.height; - - fstyle = ctx.fillStyle; - ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = fstyle; - - queue.forEach(function(storageContext) { - // set common settings for canvas - ctx.textBaseline = "bottom"; - ctx.save(); - - if (storageContext.transform.matrix) { - ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]); - ctx.transform.apply(ctx, storageContext.transform.matrix); - ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]); - } - - if (storageContext.clip){ - ctx.beginPath(); - ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); - ctx.clip(); - } - - if (storageContext.ctx.storage) { - storageContext.ctx.storage.forEach(function(item) { - renderItem(ctx, item); - }); - } - - ctx.restore(); - }); - - Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj"); - - if (options.elements.length === 1) { - if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") { - // crop image to the bounds of selected (single) element - bounds = _html2canvas.Util.Bounds(options.elements[0]); - newCanvas = document.createElement('canvas'); - newCanvas.width = Math.ceil(bounds.width); - newCanvas.height = Math.ceil(bounds.height); - ctx = newCanvas.getContext("2d"); - - ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height); - canvas = null; - return newCanvas; - } - } - - return canvas; - }; -}; -})(window,document); \ No newline at end of file diff --git a/vendor/jquery-ui/css/jquery-ui-1.12.1.min.css b/vendor/jquery-ui/css/jquery-ui-1.12.1.min.css deleted file mode 100644 index 776e259..0000000 --- a/vendor/jquery-ui/css/jquery-ui-1.12.1.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6 -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;font-size:100%}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-button{padding:.4em 1em;display:inline-block;position:relative;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2em;box-sizing:border-box;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-button-icon-only{text-indent:0}.ui-button-icon-only .ui-icon{position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.ui-button.ui-icon-notext .ui-icon{padding:0;width:2.1em;height:2.1em;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-icon-notext .ui-icon{width:auto;height:auto;text-indent:0;white-space:normal;padding:.4em 1em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-controlgroup{vertical-align:middle;display:inline-block}.ui-controlgroup > .ui-controlgroup-item{float:left;margin-left:0;margin-right:0}.ui-controlgroup > .ui-controlgroup-item:focus,.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus{z-index:9999}.ui-controlgroup-vertical > .ui-controlgroup-item{display:block;float:none;width:100%;margin-top:0;margin-bottom:0;text-align:left}.ui-controlgroup-vertical .ui-controlgroup-item{box-sizing:border-box}.ui-controlgroup .ui-controlgroup-label{padding:.4em 1em}.ui-controlgroup .ui-controlgroup-label span{font-size:80%}.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item{border-left:none}.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item{border-top:none}.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content{border-right:none}.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content{border-bottom:none}.ui-controlgroup-vertical .ui-spinner-input{width:75%;width:calc( 100% - 2.4em )}.ui-controlgroup-vertical .ui-spinner .ui-spinner-up{border-top-style:solid}.ui-checkboxradio-label .ui-icon-background{box-shadow:inset 1px 1px 1px #ccc;border-radius:.12em;border:none}.ui-checkboxradio-radio-label .ui-icon-background{width:16px;height:16px;border-radius:1em;overflow:visible;border:none}.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon{background-image:none;width:8px;height:8px;border-width:4px;border-style:solid}.ui-checkboxradio-disabled{pointer-events:none}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-n{height:2px;top:0}.ui-dialog .ui-resizable-e{width:2px;right:0}.ui-dialog .ui-resizable-s{height:2px;bottom:0}.ui-dialog .ui-resizable-w{width:2px;left:0}.ui-dialog .ui-resizable-se,.ui-dialog .ui-resizable-sw,.ui-dialog .ui-resizable-ne,.ui-dialog .ui-resizable-nw{width:7px;height:7px}.ui-dialog .ui-resizable-se{right:0;bottom:0}.ui-dialog .ui-resizable-sw{left:0;bottom:0}.ui-dialog .ui-resizable-ne{right:0;top:0}.ui-dialog .ui-resizable-nw{left:0;top:0}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-text{display:block;margin-right:20px;overflow:hidden;text-overflow:ellipsis}.ui-selectmenu-button.ui-button{text-align:left;white-space:nowrap;width:14em}.ui-selectmenu-icon.ui-icon{float:right;margin-top:0}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:.222em 0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:2em}.ui-spinner-button{width:1.6em;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top-style:none;border-bottom-style:none;border-right-style:none}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.003;filter:Alpha(Opacity=.3)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file diff --git a/vendor/jquery-ui/js/jquery-ui-1.12.1.min.js b/vendor/jquery-ui/js/jquery-ui-1.12.1.min.js deleted file mode 100644 index 25398a1..0000000 --- a/vendor/jquery-ui/js/jquery-ui-1.12.1.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("
    "))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
    "),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

    ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
    ").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
    ").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("