[
  {
    "path": ".gitignore",
    "content": "\n.DS_Store\nPuzzlePublisher.sketchplugin/Contents/Sketch/.vscode/sftp.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\nSee discussion on https://github.com/ingrammicro/puzzle-publisher/discussions site\n\n##  Version 17.9.0 (25 Sep 2022)\nAddded publishing by HTTPS\n\n##  Version 17.8.3 (18 Aug 2022)\nCheck local mockup existing before publishing\n\n##  Version 17.8.1 (18 Aug 2022)\nShow comment for symbol instance\nWorkaround for Sketch bug (thanks to cargeo@ for report)\n\n##  Version 17.8.0 (4 Aug 2022)\nSwitch to prev page work in loop\n\n##  Version 17.7.1 (3 Aug 2022)\nScrolling in overlay fixed\n\n##  Version 17.7.0 (13 Jul 2022)\nSymbol instance also can be scrollable (without a parent group)\n\n##  Version 17.6.5 (13 Jul 2022)\nShow overlays inside scrollable container\n\n##  Version 17.6.4 (7 Jul 2022)\nTo find a layer with shadow for overlay a layer with bigger height will be used\n\n##  Version 17.6.3 (28 Jun 2022)\nImproved scollable containers\n\n##  Version 17.6.1 (03 Jun 2022)\nshow color: instead of background-color: for icons\n\n##  Version 17.6.0 (25 May 2022)\nFixed hotpots\n\n##  Version 17.5.1 (21 May 2022)\nFixed hotpots\n\n##  Version 17.5.0 (19 May 2022)\nNew layer settings: Vertical scrollbar\nExample: https://github.com/ingrammicro/puzzle-publisher/tree/master/examples/VScrollbar\n\n##  Version 17.4.2 (16 May 2022)\nFixed bottom-pined panel\n\n##  Version 17.4.1 (11 May 2022)\nFixed overlay inside a fixed panel issue resolved\nWrong overlay transition behaviour fixed\n\n##  Version 17.4.0 (25 Apr 2022)\nCustom artboard size function fixed\n\n##  Version 17.3.3 (15 Apr 2022)\nImproved Handoff\n\n##  Version 17.3.2 (15 Apr 2022)\nIntegration with Miro works again\n\n##  Version 17.3.1 (6 Apr 2022)\nCmd+click opens external URLs in the new tab/window\n\n##  Version 17.3.0 (5 Apr 2022)\nViewer file structure changed\n\n##  Version 17.2.3 (30 Mar 2022)\nBack links behaviour fixed\n\n##  Version 17.2.2 (30 Mar 2022)\nHandoff shows background color token if its value is \"none\"\n\n##  Version 17.2.0 (28 Mar 2022)\nExperimental Widgets Viewer has new mode - grouping by widgets\n\n##  Version 17.1.0 (14 Mar 2022)\nImproved PP plugin to show icon colors (finally!)\n\n##  Version 17.0.5 (11 Mar 2022)\nSelective export fixed\n\n##  Version 17.0.4 (01 Mar 2022)\nBugfixes\n\n##  Version 17.0.3 (22 Feb 2022)\nHOTFIX FOR 17.0.0 - Symbol internal links are broken if Element Inspector is disabled\n\n##  Version 17.0.2 (22 Feb 2022)\nHOTFIX FOR 17.0.0 - Support Sketch.app installed into non-Applications folder\n\n##  Version 17.0.1 (15 Feb 2022)\nHOTFIX FOR 17.0.0 - Fixed browser page title\n\n##  Version 17.0.0 (14 Feb 2022)\nChanged method of symbol master information processing to make it compatible with the latest Sketch.app\n\n##  Version 16.17.1 (1 Feb 2022)\nHotfix - return overlay shadows back\n\n##  Version 16.17.0 (1 Feb 2022)\nAdded fixed layer shadow mode to Configure layer\n\n##  Version 16.16.1 (1 Feb 2022)\nHotfix\n\n##  Version 16.16.0 (31 Jan 2022)\nAdded ability to enable \"render fixed layers as regular\" per artboard\n\n##  Version 16.15.0 (19 Jan 2022)\nAdded ability to highlight EXPERIMENTAL widgets\n\n##  Version 16.14.4 (18 Jan 2022)\nFixed support for custom JS code (thanks @cargeo for bug report)\n\n##  Version 16.14.3 (11 Jan 2022)\nUpdated menu icons\n\n##  Version 16.14.2 (28 Dec 2021)\nImproved icon visualization in Handoff\n\n##  Version 16.14.1 (23 Dec 2021)\nAuto-scale switch fixes\n\n##  Version 16.14.0 (16 Dec 2021)\nAdded full-screen mode\nImproved links to experimental widgets\n\n##  Version 16.13.0 (6 Dec 2021)\nEsc closed menu\nNew \"Full page image\" function added to menu\n\n##  Version 16.12.1 (24 Nov 2021)\nCleaned Viewer UI\n\n##  Version 16.12.0 (18 Nov 2021)\nSearch continues to next pages\n\n##  Version 16.11.4 (17 Nov 2021)\nFixed wrong commit\n\n##  Version 16.11.3 (08 Nov 2021)\nSeveral Element Inspector improvements and fixes\n\n##  Version 16.11.2 (27 Oct 2021)\nFixed Artboards Sort Order\n\n##  Version 16.11.1 (27 Oct 2021)\nMinor improvements in Element Inspector\n\n##  Version 16.11.0 (24 Oct 2021)\nUse 'n' key to hide/show navigation tools\n\n##  Version 16.9.2 (26 Sep 2021)\nElement Inspector now shows valid value for style defined as \"@token1 + @token2\"\n\n##  Version 16.9.0 (21 Sep 2021)\nShow links to external documentation (for usage with Puzzle Tokens)\n\n##  Version 16.8.1 (20 Sep 2021)\nFixed layout issues in Chrome and FF \n\n##  Version 16.8.0 (21 Aug 2021)\nExporting with Element Inspector enabled is fast again! So, \"Enable Element Inspector\" checkbox moved back to Configure Export dialo\n\n##  Version 16.7.4 (19 Aug 2021)\n'S' button should return to a page marked as \"Start Page\"\n\n##  Version 16.7.0 (12 Aug 2021)\nReplaced \"Use slow but...\" checkbox\nMoved \"Enable Element Inspector\" from Configure Export to Export dialog\n\n##  Version 16.6.3 (29 Jul 2021)\nFix auto-transition for the last page\nSupport for undocumented font weight = 14\n\n##  Version 16.6.2 (29 Jul 2021)\nAdded \"Use slow but stable symbols detection\" checkbox to Export dialog. The fast method depends of unstable Sketch behaviour. The slow method works always stable.\n\n##  Version 16.6.1 (28 Jul 2021)\nShow icon names correctly in Element Inspector\n\n##  Version 16.6.0 (26 Jul 2021)\nFixed Element Inspector behaviour\n\n##  Version 16.5.6 (15 Jul 2021)\nFixed Element Inspector\n\n##  Version 16.5.5 (14 Jul 2021)\nHotfix\n\n##  Version 16.5.4 (14 Jul 2021)\n- Rolled back icon name support to fix other tokens\n\n##  Version 16.5.3 (10 Jul 2021)\nImproving Changes Inspector\n\n##  Version 16.5.2 (07 Jul 2021)\nImproving Changes Inspector\n\n##  Version 16.5.1 (29 Jun 2021)\nCorrected \"Show symbols\" toggler behaviour\nShow icon names (Part II)\n\n##  Version 16.5.0 (29 Jun 2021)\nUse full images in Gallery\nChanges Inspector improved\nShow icon names\n\n##  Version 16.4.3 (26 Jun 2021)\nFixed internal error related to color variables detection\n\n##  Version 16.4.1 (12 May 2021)\nText search inside Gallery now finds text layers also\n\n##  Version 16.3.1 (30 Apr 2021)\nImproved scrolling in Text Search\n\n##  Version 16.3.0 (29 Apr 2021)\nAdded text search (Cmd+F,Cmd+G)\n\n##  Version 16.2.4 (23 Apr 2021)\nDon't export masked layers without Export Preset configured\n\n##  Version 16.2.3 (20 Apr 2021)\nWorkaround for export crash\n\n##  Version 16.2.2 (16 Apr 2021)\nImproved Element Inspector\n\n##  Version 16.2.0 (5 Apr 2021)\nAdded comment counters to Gallery\nAdded page labels to Gallery Map view\n\n##  Version 16.1.2 (19 Mar 2021)\nComments Viewer improvements\n\n##  Version 16.1.1 (15 Mar 2021)\nFixed Version Viewer\n\n##  Version 16.1.0 (12 Mar 2021)\nAdded ability to ignore links to library internal artboards (see Configure Exporting - Artboards)\n\n##  Version 16.0.3 (12 Mar 2021)\nImproved comments\n\n##  Version 16.0.2 (05 Mar 2021)\nFixed mockup version up/down browsing\n\n##  Version 16.0.1 (04 Mar 2021)\nImproved comments\n\n##  Version 16.0.0 (25 Feb 2021)\nAdded comments to published mockups\n\n##  Version 15.3.0 (30 Dec 2020)\nElement Inspector allows to review overlapped layers by multiply clicking\n\n##  Version 15.2.7 (28 Dec 2020)\nFixed modal poisitioning on large displays\n\n##  Version 15.2.5 (21 Dec 2020)\nEscaped \",\" in image file names to be compatible with Miro\nOther fixed for publishing to Miro\n\n##  Version 15.2.4 (06 Dec 2020)\nCorrected font size for Linux developers\n\n##  Version 15.2.0 (04 Dec 2020)\nFixed browser page background color\nAdded option to see font size adjusted for Linux developers\n\n##  Version 15.1.4 (03 Dec 2020)\nFixed Miro issues\n\n##  Version 15.1.3 (25 Nov 2020)\nAdded support for @XSpacer@ and @YSpacer@ layer name magic keys\n\n##  Version 15.1.3 (25 Nov 2020)\nFixed issues with Gallery Viewer and Embedded Mode\n\n##  Version 15.1.1 (25 Nov 2020)\nRedesigned map view by @zubr133\nAdded page titles to map view\n\n##  Version 15.1.0 (19 Nov 2020)\nImpoved map view:\n1) Added page interactions\n2) Added own URLs to gallery and map views\n3) Other improvements\n\n##  Version 15.0.0 (14 Nov 2020)\nAdded map view to All Screens page\nCode refactoring\n\n##  Version 14.11.1 (9 Nov 2020)\nFix: URLs are lowercased again\n\n##  Version 14.11.0 (9 Nov 2020)\nFixed \"Open HTML in browser\" checkbox behaviour\nSuport @Redirect@ for modals too\n\n##  Version 14.10.4 (2 Nov 2020)\nFixed crash\n\n##  Version 14.10.3 (30 Oct 2020)\nFixed support for Shape shadows in Inspector\nFixed \"Open in new window\" icon behaviour (Embedded mode)\nSupported tokens for color variables\n\n##  Version 14.10.2 (28 Sep 2020)\nFixes overlay multishadows\n\n##  Version 14.10.1 (16 Sep 2020)\nFixed navigation menu layout\n\n##  Version 14.10.0 (16 Sep 2020)\nNow it's possible to inject any custom JS code into Viewer. See Plugin > Configure Export > JS Code option.\nAs example - you can hide some navigation menu ites using the following code:\n$(\"#menu #zoom\").hide();$(\"#menu #embed\").hide();$(\"#menu #grid\").hide();\n\n\"View All Screens\" mode now handles \"s\" key correctly\n\n##  Version 14.9.1 (19 Sep 2020)\nGallery can be opened on document load using &g=1 search param\n\n##  Version 14.8.14 (17 Sep 2020)\nFixed publishing to Miro\n\n##  Version 14.8.13 (17 Sep 2020)\nMany improvements in image generation\n- Generate full images only if Miro settings configured\n- Generate preview images by Sketch, not by external \"sips\" tool\n- Use retina images in Gallery on non-retinal diplays too\n\n##  Version 14.8.12 (10 Sep 2020)\nFixed direct link to modal called from overlay\n\n##  Version 14.8.11 (2 Sep 2020)\nFixed exporting of external \"external\" artboards\nAdded success messages to Miro operations\n\n##  Version 14.8.10 (2 Sep 2020)\nFixed login to Miro for passwords with special characters\nImproved publishing to Miro\n\n##  Version 14.8.8 (1 Sep 2020)\nGroup Miro boards by project\n\n##  Version 14.8.7 (31 Aug 2020)\nFixed image paths in Gallery\nImproved shared style/symbol information in Element Inspector\n\n##  Version 14.8.6 (27 Aug 2020)\nFixed unstable behaviour of Miro publishing\n\n##  Version 14.8.5 (26 Aug 2020)\nImproved publishing to Miro\n\n##  Version 14.8.4 (20 Aug 2020)\n\"Show All Images\" feature now shows full artboard pictures included fixed images\n\"Publish to Miro\" now places artboards correctly to prevent overlaping\n\n##  Version 14.8.3 (19 Aug 2020)\nNew feature: The plugin can publish mockups on Miro whiteboards\n\n##  Version 14.7.3 (10 Aug 2020)\nLink inside a modal to the same modal closes it (similer to overlays)\n\n##  Version 14.7.2 (4 Aug 2020)\nFixed Document Settings modal (height increased to show all fields)\n\n##  Version 14.7.1 (24 July 2020)\nImproved External URL dialog\n\n##  Version 14.7.0 (24 July 2020)\nAdded support for relative URLs\n\n##  Version 14.6.1 (25 June 2020)\nImprovement for Element Inspector: click outside of any element unselect current element\n\n##  Version 14.6.0 (19 June 2020)\nAdded optional Secret Key pair settings to Configure Publishing dialod and server_tools/config.json\n\n##  Version 14.5.0 (19 June 2020)\nElement Inspector improved\n- build layer tree using valid z-index\n- skip text layers with empty (or whitespace only) content\n- suppor page navigation (left,right keys)\n\n##  Version 14.4.1 (10 June 2020)\nAdded support for fixed layers to Element Inspector\nAdded ability to disable a library sync for document during automation\n\n##  Version 14.4.0 (8 June 2020)\nAdded support for Image layers to Element Inspector\n\n##  Version 14.3.0 (8 June 2020)\nAdded margins to Element Inspector\n\n##  Version 14.2.0 (4 June 2020)\nNew features:\n- Element Inspector shows FA icon details\n\n##  Version 14.1.2 (2 June 2020)\nFixed internal error on Sketch startup\n\n##  Version 14.1.1 (2 June 2020)\nImproved async mode for sending statistics\n\n##  Version 14.1.0 (1 June 2020)\nAdded ability to enable debug logging\nPP now sends anonymous usage data (using Google Analytics). You can disable it in Configure Plugin.\nWorkaround two Sketch issues\n\n##  Version 14.0.2 (28 May 2020)\n- Improvements and fixes for Element Inspector \n\n##  Version 14.0.1 (27 May 2020)\n- Totally reworked Element Inspector to show all text/shape layer styles\n- Updated file protocol between Puzzle Publisher and Puzzle Tokens\n\n##  Version 13.1.2 (29 Apr 2020)\n- Fixed error in Sketch 65\n\n##  Version 13.1.1 (22 Mar 2020)\n- Improved Version Viewer\n- Disable show/hide animations for modal (due to other open issue)\n\n##  Version 13.1.0 (17 Mar 2020)\nSynced with PT 8.2.0 changes\n\n##  Version 13.0.5 (14 Mar 2020)\nFixed issues:\n- Browser back button doesn't work in PP 13.0.1\n- Please fix overlay position when you open overlay from another overlay (GCUX-7530)\n- Fix link to close overlay (GCUX-7525)\n- Cur trailed \"/\" in Remote Folder URL \n\n##  Version 13.0.1 (14 Mar 2020)\nViewer moved from URL format\nhttps://site.com/dd/index.html?embed#home/o/10  \nto\nhttps://site.com/dd/index.html?home&o=10&e=1\nWe need this change because URL with # doesn't work correctly on Apache sites with enabled Azure AD integration.\nAttention! The new viewer also supports old URLs.\n\n##  Version 12.7.0 (6 Mar 2020)\nNew cool design for sidebar (thanks to @zubr133 )\nFixed issue: Element Inspector doesn't show token values for the document which is a library itself\n\n##  Version 12.6.0 (30 Mar 2020)\n- Added support for Google Tag Manager codes (GTM* format)\n\n##  Version 12.5.2 (5 Mar 2020)\nFixed case \"two links open the same overlay\"\nDisabled links highlighting for close overlay click event\n\n##  Version 12.5.1 (4 Mar 2020)\nFixed nested overlay behaviour\n\n##  Version 12.5.0 (2 Mar 2020)\nAdded custom SSH port settings to Configure Publishing dialog\n\n##  Version 12.4.2 (26 Feb 2020)\nRespin for 12.4.0\n\n##  Version 12.4.0 (22 Feb 2020)\nAdded new \"Up from top center of hotspot\" overlay position\n\n##  Version 12.3.1 (7 Feb 2020)\n- Now redirect overlay has its own URL to open \n\n##  Version 12.3.0 (6 Feb 2020)\n- Added ability to export into JPG files (see Configure Export)\n- Replaced Show Last Info menu item by Show Change Log\n- Accelerated export data for Element Inspector\n\n##  Version 12.2.0 (4 Feb 2020)\n- Added Redirect Overlays (see [details](https://spectrum.chat/puzzle-publisher/general/12-2-0-released~77f1e2a4-e3df-4667-b0bb-9067efce29ec))\n\n##  Version 12.1.2 (20 Jan 2020)\n- Show a version of published mockups in navigation bar\n- Allow browser to handle its own keyboard shortcuts (Cmd+L on mac)\n\n##  Version 12.1.1 (10 Jan 2020)\n- Hotfix for 12.1.0 (Element Inspector can not be closed using \"m\" key)\n\n##  Version 12.1.0 (09 Jan 2020)\n- Improved search in Gallery\n\n##  Version 12.0.4 (26 Dec 2019)\n- Hide fixed panes under modal shadow\n\n##  Version 12.0.3 (25 Dec 2019)\n- Element Inspector shows LESS token values\n\n##  Version 12.0.2 (24 Dec 2019)\n- Element Inspector works for overlays too\n\n##  Version 12.0.1 (17 Dec 2019)\n- Fix overflow on center layout (by @form-follows-function)\n\n##  Version 12.0.0 (13 Dec 2019)\n1) Added artboard transition animations. \nYou can select an animation it in Configure Artboard > Transitions.\nOverlays use FADE animation by default.\nAnimation for standalone pages and modals is not stable for now. Will be improved.\n2) You can send custom CSS styles to Viewer placing \"<SOME LIB>-viewer.css\" file together with any enabled library\n\n##  Version 11.6.1 (5 Dec 2019)\n- Fixed Element Inspector (Issue #11)\n- Fixed overlays in modal\n- Removed modal scroller (Issue #12)\n\n##  Version 11.6.0 (3 Dec 2019)\n- Added \"Replace the previous overlay if called from overlay\" option to Artboard Overlay setttings\n\n##  Version 11.5.4 (28 Nov 2019)\n- Improved hotspot highlighting key logic to allow users to make screenshots on macOS without highlighted hotposts\n- Element Insprector now shows style library names\n\n##  Version 11.5.3 (13 Nov 2019)\n- fixed crash on broken symbols\n\n##  Version 11.5.2 (5 Nov 2019)\n- test Sketch update system\n\n##  Version 11.5.1 (5 Nov 2019)\n- test Sketch update system\n\n##  Version 11.5.0 (5 Nov 2019)\n- Improved Configure Artboard dialog (added tabs and images)\n\n##  Version 11.4.1 (30 Oct 2019)\n- Several fixed and improvements for Version Viewer\n\n##  Version 11.4.0 (29 Oct 2019)\n- Completed image difff viewer (added Shift+Left and Shift+Right to switch diff mode from keyboard)\n- Click inside an overlay outside of any hotspots should not close it\n\n##  Version 11.3.0 (28 Oct 2019)\n- Render fixed layers as standlanone layer, not as a part of artboard image (to support non-rectangle layers )\n- Improved image diffs\n- Fixed: Empty Sketch document opening in background when run via Sketchtool (by Arek Talun)\n- Improved token inspector\n\n##  Version 11.2.0 (24 Oct 2019)\n- Added Search in Gallery\n- Show changed screen differences\n\n##  Version 11.0.4 (23 Oct 2019)\n- Fixed zoom disabling for wide artboards (PART II) \n\n##  Version 11.0.3 (22 Oct 2019)\n- Fixed zoom disabling for wide artboards\n- Fixed *artboard exclusion\n- Link in Telegram post opens a page with Version Info mode enabled by default\n- Added context option currentPath to provide destination path for prototype\n\n##  Version 11.0.2 (4 Oct 2019)\n- Fixed undo of changes which a user did beforee exporting\n- Added \"Up-to-down\" artboard sorting\n\n##  Version 11.0.1 (2 Oct 2019)\n- Added additional checks to skip wrong page objects\n- Fixed JSON generator\n\n##  Version 11.0.0 (2 Oct 2019)\n- Totally changed a way to apply symbol overrides in order to make it compatible with Smart Layouts\n\n##  Version 10.3.3 (26 Sep 2019)\n- Fixed rendering of artboard with fixed layers (on some installations Sketch was too lazy) (part II)\n\n##  Version 10.3.2 (24 Sep 2019)\n- Fixed \"Hotspot top center\" and \"Hotspot top right corner\" overlay aligmnent modes\n- Fixed rendering of artboard with fixed layers (on some installations Sketch was too lazy)\n\n##  Version 10.3.1 (23 Sep 2019)\n- Hotspot in fixed panel aligned to bottom opens overlay also aligned to bottom\n\n##  Version 10.3.0 (16 Sep 2019)\n- Redesigned Gallery\n\n##  Version 10.2.2 (14 Sep 2019)\n- Add additional check for valid libraries\n- Disabled unstable image compression\n\n##  Version 10.2.1 (13 Sep 2019)\n- Icon updated\n\n##  Version 10.2.0 (06 Sep 2019)\n- Added Version Viewer (requires [server tools|https://github.com/ingrammicro/puzzle-publisher/tree/master/server_tools]) installed on your WWW server)\n\n##  Version 10.1.0 (03 Sep 2019)\n- Added Announce Changes feature for mockups published on SFTP (requires [server tools|https://github.com/ingrammicro/puzzle-publisher/tree/master/server_tools]) installed on your WWW server.\n\n##  Version 10.0.2 (02 Sep 2019)\n- Fixed \"Export selected page artboards\" feature\n\n##  Version 10.0.1 (28 Aug 2019)\n- Fixed mouse-over artboards for \"page scrolled down\" case\n- Added Up/Down Version feature for mockups published on SFTP (requires [server support](https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/server_tools/folder_info.php))\n\n##  Version 10.0.0 (20 Aug 2019)\n- Moved from https://github.com/MaxBazarov/exporter/\n"
  },
  {
    "path": "Hints.md",
    "content": "# Puzzle Publisher plugin Hints\n\n## [Hint 1](#hint1): Use post-processing to inject your own information in generated HTML\n\nThe main index.html contains a special placeholder **\\<!\\-\\-VERSION\\-\\-\\>**.\n\n\t<ul id=\"nav-title\">\n    \t<li><div class=\"nav-title-label\">Screen title <!--VERSION--></div><div class=\"title\">Title</div></li>\n    </ul>\n\nYou can replace it with some your own information, for example — you can show prototype version here.\nThe following command uses \"sed\" tool.\n\n\tsed -i '' \"s/<!--VERSION-->/(v123)/g\" \"index.html\"\n\t\nIn the same way you can add inject your own CSS file by locating to <!--HEAD_INJECT--> code.\n\n\n## [Hint 2](#hint2): How to set external link for overrided symbol hotspot \n\nSometimes you need to set an external URL for hotspot target. You can't use \"Set External Link for layer\" command in this case because it's not possible to select some of symbol childs. \n\nBut you can follow the another way. \n- Create small empty artboard\n- Use \"Set External Link for layer\" command for this artboard\n- Select this artobard as a overrided target for your hotsport \n- Run Export to HTML to see a result\n\n[Illustration](https://github.com/ingrammicro/puzzle-publisher/raw/master/tests/Pictures/Link-ExternalArtboard.png), [Example file](https://github.com/ingrammicro/puzzle-publisher/raw/master/tests/Link-ExternalArtboard.sketch)\n\n\n## [Hint 3](#hint3): How to set a start/home page for a prototype\nSelect \"Prototyping > Use Artboard as Start Point\" menu item to mark/unmark the selected artboard as home.\n\n## [Hint 4](#hint4): How to export Sketch document to HTML using command line\nRun the following command (don't forget to inject a path to your file into  a \"--context\" JSON file)\n\n\t/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool --without-activating=YES --new-instance=No run ~/Library/Application\\ Support/com.bohemiancoding.sketch3/Plugins/PuzzlePublisher.sketchplugin \"cmdRun\"  --context='{\"file\":\"/Users/baza/GitHub/puzzle-publisher/tests/Links2.sketch\",\"commands\":\"sync,export,publish,save\"}'\n\n## [Hint 5](#hint5): How to see the plugin log\n\ttail -f ~/Library/Logs/com.bohemiancoding.sketch3/Plugin\\ Log.log\n\n\n## [Hint 6](#hint6): How to change a browser page background for all documents\n Add \"@MainBackground@\" magic string to any libary symbol layer name to use its background color as a default color for browser pages\n\n## [Hint 7](#hint7): How to treat symbols as artboards \nSee discission [here](https://github.com/ingrammicro/puzzle-publisher/discussions/34)\n"
  },
  {
    "path": "Ideas.md",
    "content": "### Add page transition effects\n\n### Auto transition to other artboard after delay\nNew artboard setting:  \n**Transition to artboard**  \n[ To Artboard Selector]\n\n### Open HTML in special window with webpage inside (not in a standalone browser)\n\n### Add check for Sketch-compatible version\n\n### Mass-operation for artboards and layers\nExample: Setup Auto-transition for many artboard at once\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/cmd-actions.js",
    "content": "@import \"lib/ga.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\nfunction onStartup(context) {\n    const Settings = require('sketch/settings')\n\n    const installedBefore = Settings.settingForKey(SettingKeys.PLUGIN_INSTALLED)\n    if (!installedBefore) {\n        track(TRACK_INSTALLED)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_INSTALLED, 1)\n    } else {\n        track(TRACK_STARTED)\n    }\n}"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/cmdline-functions.js",
    "content": "@import \"exporter/exporter-run.js\"\n\n// osascript -e 'quit app \"Sketch\"'\nconst example = `\n/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool --without-activating=YES --new-instance=No run ~/Library/Application\\ Support/com.bohemiancoding.sketch3/Plugins/PuzzlePublisher.sketchplugin \"cmdRun\"  --context='{\"file\":\"/Users/baza/GitHub/puzzle-publisher/tests/Links2.sketch\",\"commands\":\"sync,export,publish,save,close\"}'\n`\n\nfunction syncDocumentStyles(styles)\n{\n    log(\" SYNCING \" + styles.length + \" STYLES ...\")\n    for (var style of styles)\n    {\n        if (null == style.getLibrary()) continue // we need only library-based style\n        if (!style.syncWithLibrary())\n        {\n            log(\"  Failed to sync symbol \" + style.name)\n        }\n    }\n}\n\nfunction syncDocument(document)\n{\n    const jSymbols = document.getSymbols()\n    if (Settings.documentSettingForKey(document, SettingKeys.DOC_SKIP_AUTOSYNC) == 1)\n    {\n        log(\" SKIP SYNCINC\")\n        return\n    }\n    log(\" SYNCING \" + jSymbols.length + \" SYMBOLS ...\")\n    for (var master of jSymbols)\n    {\n        if (null == master.getLibrary()) continue // we need only library-based master\n\n        if (!master.syncWithLibrary())\n        {\n            log(\"  Failed to sync symbol \" + master.name)\n            for (var i of master.getAllInstances())\n            {\n                log(\"     instance: \" + i.name)\n            }\n        }\n    }\n    syncDocumentStyles(document.sharedTextStyles)\n    syncDocumentStyles(document.sharedLayerStyles)\n}\n\nfunction exportDocument(context, runOptions)\n{\n    log(\" EXPORTING...\")\n    runExporter(context, runOptions)\n}\n\nfunction publishDocument(context, document)\n{\n    log(\" PUBLISHING...\")\n    context.fromCmd = true\n    const publisher = new Publisher(context, document.sketchObject);\n    publisher.authorName = \"[BOT]\";\n    publisher.message = \"--TELE\";\n    publisher.publish();\n}\n\nfunction showError(error)\n{\n    log(error + \"\\n\")\n    log(\"Command line example:\")\n    log(example + \"\\n\")\n}\n\n\nfunction saveDocument(document)\n{\n    log(\" SAVING DOCUMENT...\")\n    document.save(err =>\n    {\n        if (err)\n        {\n            log(\" Failed to save a document. Error: \" + err)\n        }\n    })\n}\n\nfunction closeDocument(document)\n{\n    log(\" CLOSING DOCUMENT...\")\n    document.close()\n}\n\nvar cmdRun = function (context)\n{\n    let Document = require('sketch/dom').Document\n\n    // Parse command line arguments    \n    let path = context.file + \"\"\n    if ('' == path)\n    {\n        return showError(\"context.file is not specified\")\n    }\n\n    log(\"PROCESS \" + path)\n\n    let argCommands = context.commands + \"\"\n    if ('' == argCommands)\n    {\n        return showError(\"context.commands is not specified\")\n    }\n\n    const commandsList = argCommands.split(',')\n    const allCommands = ['save', 'sync', 'export', 'publish', 'close']\n    const cmds = {}\n    for (var cmd of allCommands)\n    {\n        cmds[cmd] = commandsList.includes(cmd)\n    }\n\n    // Open Sketch document \n    Document.open(path, (err, document) =>\n    {\n        if (err || !document)\n        {\n            log(\"ERROR: Can't open  \" + path)\n            return\n        }\n\n        if (cmds.sync) syncDocument(document)\n        if (cmds.export)\n        {\n            const runOptions = {\n                cmd: \"exportHTML\",\n                mode: context.mode,\n                fromCmd: true,\n                nDoc: document.sketchObject,\n                currentPage: context.currentPageID ? document.getLayerWithID(context.currentPageID) : undefined,\n                customArtboardHeight: context.customArtboardHeight,\n                customArtboardWidth: context.customArtboardWidth,\n            }\n            //\n            if (Constants.EXPORT_MODE_CURRENT_PAGE == context.mode)\n            {\n            } else if (Constants.EXPORT_MODE_SELECTED_ARTBOARDS == context.mode)\n            {\n                runOptions.selectedArtboards = context.selectedArtboardIDS.split(\",\").map(id => document.getLayerWithID(id))\n            }\n            //\n            exportDocument(context, runOptions)\n        }\n        if (cmds.save) saveDocument(document)\n        if (cmds.publish) publishDocument(context, document)\n        if (cmds.save) saveDocument(document)\n        if (cmds.close) closeDocument(document)\n\n    })\n\n};\n\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/constants.js",
    "content": "const Constants = {\n    TAB_SIZE: 2,\n    HOTSPOT_PADDING: 0,\n    LOGGING: false,\n    SERVER_ANNOUNCE_SCRIPT: \"announce.php\",\n    IMAGES_DIRECTORY: \"images/\",\n    PREVIEWS_DIRECTORY: \"previews/\",\n    FULLIMAGE_DIRECTORY: \"full/\",\n    DEST_PROTO_DIRECTORY: \"data/\",\n    DEST_RESOURCES_DIRECTORY: \"resources/\",\n    PLUGIN_IDENTIFIER: \"com.cloudblue.sketch.exporter\",\n    TEMP_PAGE_PREFIX: \"(temp)\",\n    SORT_RULE_X: 0,\n    SORT_RULE_SKETCH: 1,\n    SORT_RULE_REVERSIVE_SKETCH: 2,\n    SORT_RULE_Y: 3,\n    SORT_RULE_OPTIONS: [\"Left-to-right\", \"Sketch default\", \"Reversive Sketch default\", \"Up-to-down\"],\n    FONT_SIZE_FORMAT_SKETCH: 0,\n    FONTSIZE_FORMAT_OPTIONS: [\"Sketch sizes\", \"Linux-specific sizes\"],\n    POSITION_DEFAULT: 0,\n    POSITION_TOP: 1,\n    POSITION_CENTER: 2,\n    EXPORT_MODE_SELECTED_ARTBOARDS: 0,\n    EXPORT_MODE_CURRENT_PAGE: 1,\n    DEF_BACK_COLOR: \"#646464\",\n    //\n    SITE_CHANGELOG_URL: \"https://github.com/ingrammicro/puzzle-publisher/blob/master/CHANGELOG.md\",\n    //\n    ASSETS_FOLDER_PREFIX: \"_pt-assets\",\n    SYMBOLTOKENFILE_POSTFIX: \"inspector.json\",\n    CSSFILE_POSTFIX: \"viewer.css\",\n    VARSFILE_POSTFIX: \"vars.json\",\n    ///\n    INT_LAYER_NAME_BACKCOLOR: \"@MainBackground@\",\n    INT_LAYER_NAME_SITEICON: \"@SiteIcon@\",\n    INT_LAYER_NAME_SPACER_PART: \"Spacer@\",\n    INT_LAYER_NAME_SPACER: \"@Spacer@\",\n    INT_LAYER_NAME_XSPACER: \"@XSpacer@\",\n    INT_LAYER_NAME_YSPACER: \"@YSpacer@\",\n    INT_LAYER_NAME_REDIRECT: \"@Redirect@\",\n    //\n    ARTBOARD_TYPE_REGULAR: 0,\n    ARTBOARD_TYPE_MODAL: 1,\n    ARTBOARD_TYPE_EXTERNAL_URL: 2,\n    ARTBOARD_TYPE_OVERLAY: 3,\n    //    \n    ARTBOARD_TRANS_ANIM_NONE: 0,\n    ARTBOARD_TRANS_ANIM_SLIDEIN_UP: 1,\n    ARTBOARD_TRANS_ANIM_SLIDEIN_LEFT: 2,\n    ARTBOARD_TRANS_ANIM_FADE: 3,\n    ARTBOARD_TRANS_ANIM_SLIDEIN_RIGHT: 4,\n    ARTBOARD_TRANS_ANIM_SLIDEIN_DOWN: 5,\n    //\n    ARTBOARD_OVERLAY_BY_EVENT_CLICK: 0,\n    ARTBOARD_OVERLAY_BY_EVENT_MOUSEOVER: 1,\n    //\n    ARTBOARD_OVERLAY_PIN_HOTSPOT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE: 1,\n    //\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT: 6,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER: 7,\n    //\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_PAGE_CENTER: 6,\n    //\n    //\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_LEFT: 0,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_CENTER: 1,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_RIGHT: 2,\n    OLD_ARTBOARD_OVERLAY_ALIGN_TOP_LEFT: 3,\n    OLD_ARTBOARD_OVERLAY_ALIGN_TOP_CENTER: 4,\n    OLD_ARTBOARD_OVERLAY_ALIGN_TOP_RIGHT: 5,\n    OLD_ARTBOARD_OVERLAY_ALIGN_CENTER: 6,\n    OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_LEFT: 7,\n    OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_CENTER: 8,\n    OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_RIGHT: 9,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_LEFT: 10,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_CENTER: 11,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT: 12,\n    OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT_ALIGN_RIGHT: 13,\n\n    LAYER_OVERLAY_DEFAULT: 0,\n    LAYER_OVERLAY_TRANSP_TOP: 1,\n    LAYER_OVERLAY_TRANSP_LEFT: 2,\n    LAYER_OVERLAY_VSCROLL: 3,\n\n    LAYER_VSCROLL_NONE: 0,\n    LAYER_VSCROLL_DEFAULT: 1,\n    LAYER_VSCROLL_ALWAYS: 2,\n    LAYER_VSCROLL_NEVER: 3,\n    //\n    GA_ID: \"UA-84277242-5\",\n    //\n    CURL_PATH: \"/usr/bin/curl\"\n};\n\nconst TRACK_INSTALLED = \"installed\"\nconst TRACK_STARTED = \"started\"\nconst TRACK_EXPORT_DIALOG_SHOWN = \"export-dialog-shown\"\nconst TRACK_EXPORT_DIALOG_CLOSED = \"export-dialog-closed\"  // cmd:ok,cancel\nconst TRACK_EXPORT_COMPLETED = \"export-completed\"\nconst TRACK_PUBLISH_DIALOG_SHOWN = \"publish-dialog-shown\"\nconst TRACK_PUBLISH_DIALOG_CLOSED = \"publish-dialog-closed\"  // cmd:ok,cancel\nconst TRACK_PUBLISH_COMPLETED = \"publish-completed\"\nconst TRACK_PUBLISH_MIRO_DIALOG_SHOWN = \"publish-miro-dialog-shown\"\nconst TRACK_PUBLISH_MIRO_DIALOG_CLOSED = \"publish-miro-dialog-closed\"  // cmd:ok,cancel\nconst TRACK_PUBLISH_MIRO_COMPLETED = \"publish-miro-completed\"\n\n\nconst PublishKeys = {\n    SHOW_OUTPUT: false,\n    TMP_FILE: \"publish.sh\",\n    RESOURCES_FOLDER: \"scripts\",\n\n}\n\nconst SettingKeys = {\n    PLUGIN_INFO_11: \"pluginShown11\",\n\n    PLUGIN_POSITION: \"positon\",\n    PLUGIN_FILETYPE: \"pluginFileType\",\n    PLUGIN_DONT_OPEN_BROWSER: \"dontOpenBrowser\",\n    PLUGIN_COMPRESS: \"pluginCompress\",\n    PLUGIN_DONT_RETINA_IMAGES: \"dontRetinaImages\",\n    PLUGIN_DISABLE_ZOOM: \"pluginDisableZoom\",\n    PLUGIN_COMMENTS_URL: \"commentsURL\",\n    PLUGIN_GOOGLE_CODE: \"googleCode\",\n    PLUGIN_EXPORT_JS_CODE: \"pluginExportJSCode\",\n    PLUGIN_SERVERTOOLS_PATH: \"pluginServerToolsPath\",\n    PLUGIN_AUTHOR_NAME: \"pluginAuthorName\",\n    PLUGIN_AUTHOR_EMAIL: \"pluginAuthorEmail\",\n    PLUGIN_EXPORT_MODE: \"exportMode\",\n    PLUGIN_HIDE_NAV: \"hideNavigation\",\n    PLUGIN_SORT_RULE: \"pluginSortRule\",\n    PLUGIN_FONTSIZE_FORMAT: \"PLUGIN_FONTSIZE_FORMAT\",\n    PLUGIN_DISABLE_HOTSPOTS: \"pluginDisableHotspots\",\n    PLUGIN_ASK_CUSTOM_SIZE: \"PLUGIN_ASK_CUSTOM_SIZE\",\n    PLUGIN_EXPORT_CUSTOM_WIDTH: \"PLUGIN_EXPORT_CUSTOM_WIDTH2\",\n    PLUGIN_EXPORT_CUSTOM_HEIGHT: \"PLUGIN_EXPORT_CUSTOM_HEIGHT2\",\n    PLUGIN_EXPORT_DISABLE_LIB_ARTBOARDS: \"PLUGIN_EXPORT_DISABLE_LIB_ARTBOARDS\",\n    PLUGIN_EXPORT_DISABLE_INSPECTOR: \"PLUGIN_EXPORT_DISABLE_INSPECTOR\",\n    PLUGIN_PUBLISH_LOGIN: \"publishLogin\",\n    PLUGIN_PUBLISH_SITEROOT: \"publishSiteRoot\",\n    PLUGIN_PUBLISH_SECRET: \"PLUGIN_PUBLISH_SECRET\",\n    PLUGIN_PUBLISH_SSH_PORT: \"publishSSHPort\",\n    PLUGIN_PUBLISH_LAST_MSG: \"PLUGIN_PIBLISH_LAST_MSG\",\n    PLUGIN_PUBLISH_CURL_PATH: \"PLUGIN_PUBLISH_CURL_PATH\",\n    PLUGIN_EXPORTING_URL: \"pluginExportingURL\",\n    PLUGIN_SHARE_IFRAME_SIZE: \"pluginShareiFrameSize\",\n    PLUGIN_PUBLISH_MIRO_ENABLED: \"PLUGIN_PUBLISH_MIRO_ENABLED\",\n    PLUGIN_INSTALLED: \"pluginInstalled\",\n    PLUGIN_GA_DISABLED: \"pluginGADisabled\",\n    PLUGIN_LOGDEBUG_ENABLED: \"pluginLogDebugEnabled\",\n    \n    PLUGIN_USER_ID:\"PLUGIN_USER_ID\",\n\n    ARTBOARD_TYPE: \"artboardType\",\n    ARTBOARD_DISABLE_FIXED: \"ARTBOARD_DISABLE_FIXED\",\n\n    LEGACY_ARTBOARD_MODAL: \"artboardOverlay\", //legacy, replaced by ARTBOARD_TYPE\n    LEGACY_ARTBOARD_MODAL_SHADOW: \"artboardOverlayShadow\", // replaced by  ARTBOARD_SHADOW, Outdated on 14 Frev 2018\n    OLD_ARTBOARD_OVERLAY_ALIGN: \"artboardOverlayPosition\",\n\n    ARTBOARD_SHADOW: \"artboardShadow\",\n    ARTBOARD_DISABLE_AUTOSCROLL: \"artboardDisableAutoScroll\",\n    ARTBOARD_TRANS_TO_NEXT_SECS: \"artboardTransNextSecs\",\n    ARTBOARD_TRANS_ANIM_TYPE: \"artboardTransAnimType\",\n    ARTBOARD_OVERLAY_BY_EVENT: \"artboardOverlayByEvent\",\n    ARTBOARD_OVERLAY_PIN: \"artboardOverlayPin\",\n    ARTBOARD_OVERLAY_PIN_HOTSPOT: \"artboardOverlayPinHotspot\",\n    ARTBOARD_OVERLAY_PIN_PAGE: \"artboardOverlayPinPage\",\n    ARTBOARD_OVERLAY_OVERFIXED: \"artboardOverFixed\",\n    ARTBOARD_OVERLAY_ALSOFIXED: \"artboardAlsoFixed\",\n    ARTBOARD_OVERLAY_CLOSE_PREVOVERLAY: \"artboardClosePrevOverlay\",\n\n    DOC_EXPORTING_URL: \"docExportingURL\", // legacy, replaced by PLUGIN_EXPORTING_URL\n    DOC_PUBLISH_MIRO_BOARD: \"DOC_PUBLISH_MIRO_BOARD\",\n    DOC_PUBLISH_MIRO_BOARDID: \"DOC_PUBLISH_MIRO_BOARDID\",\n    DOC_PUBLISH_COMPRESS: \"docPublishCompress\",\n    DOC_DISABLE_FIXED_LAYERS: \"docDisablFixedLayers\",\n    DOC_PUBLISH_VERSION: \"mockupsVersion\",\n    DOC_PUBLISH_REMOTE_FOLDER: \"remoteFolder\",\n    DOC_CUSTOM_HIDE_NAV: \"docCustomHideNavigation\",\n    DOC_CUSTOM_SORT_RULE: \"docCustomSortRule\", // How to sort artboards\n    DOC_CUSTOM_FONTSIZE_FORMAT: \"DOC_CUSTOM_FONTSIZE_FORMAT\", // How to show font size in Element Inspector\n    DOC_BACK_COLOR: \"docBackColor\",\n    DOC_SKIP_AUTOSYNC: \"DOC_SKIP_AUTOSYNC\",\n    DOC_OWNER_NAME: \"DOC_OWNER_NAME\",\n    DOC_OWNER_EMAIL: \"DOC_OWNER_EMAIL\",\n\n    LAYER_ANNOTATIONS: \"layerAnnotations\",\n    LAYER_OVERLAY_TYPE: \"layerOverlayType\",\n    LAYER_EXTERNAL_LINK: \"externalLink\",\n    LAYER_EXTERNAL_LINK_BLANKWIN: \"layerNewWindow\",\n    LAYER_COMMENT: 'layerComment',\n    LAYER_FIXED_SHADOW_TYPE: \"LAYER_FIXED_SHADOW_TYPE\",\n    LAYER_VSCROLL_TYPE: \"LAYER_VSCROLL_TYPE\"\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/PZArtboard.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\n@import(\"exporter/PZLayer.js\")\n@import(\"exporter/PZDoc.js\")\n\nSketch = require('sketch/dom')\n\n\n\nclass PZArtboard extends PZLayer\n{\n\n    constructor(slayer)\n    {\n        if (DEBUG) exporter.logMsg(\"PZArtboard.create name=\" + slayer.name)\n\n        // init Artboard own things !!! before object construction !!!\n        let artboardType = exporter.Settings.layerSettingForKey(slayer, SettingKeys.ARTBOARD_TYPE)\n        if (undefined == artboardType || '' == artboardType)\n        {\n            if (exporter.Settings.layerSettingForKey(slayer, SettingKeys.LEGACY_ARTBOARD_MODAL) == 1)\n            {\n                artboardType = Constants.ARTBOARD_TYPE_MODAL // use legacy setting\n            } else\n                artboardType = Constants.ARTBOARD_TYPE_REGULAR // set default 0 value\n        }\n        let externalArtboardURL =\n            exporter.Settings.layerSettingForKey(slayer, SettingKeys.LAYER_EXTERNAL_LINK)\n        if (externalArtboardURL != undefined && ('' == externalArtboardURL || 'http://' == externalArtboardURL))\n            externalArtboardURL = undefined\n\n\n        // Resize before exporting\n        const needResize = exporter.customArtboardFrame && Constants.ARTBOARD_TYPE_REGULAR == artboardType && undefined == externalArtboardURL\n        if (needResize)\n        {\n            if (exporter.customArtboardFrame.width > 0)\n                slayer.frame.width = exporter.customArtboardFrame.width\n            if (exporter.customArtboardFrame.height > 0)\n                slayer.frame.height = exporter.customArtboardFrame.height\n        }\n\n        super(slayer, undefined)\n\n        this.overlayLayers = []\n        this.fixedLayers = [] // list of layers which are configured as fixed\n        this.nextLinkIndex = 0 // we need it to generate uniq id of the every link\n        this.imageLayers = [] // list of all Image childs        \n        this.shadowLayers = undefined // list of layer with shaows (needs to show overlay shadow)\n\n        // check if the page name is unique in document\n        if (this.name in pzDoc.artboardsDict)\n        {\n            // we need to find a new name                        \n            for (let i = 1; i < 1000; i++)\n            {\n                const newName = this.name + \"(\" + i + \")\"\n                if (!(newName in pzDoc.artboardsDict))\n                {\n                    // found new unique name!\n                    this.name = newName\n                    break\n                }\n            }\n        }\n        // init Artboard own things\n        this.artboardType = artboardType\n        this.isModal = Constants.ARTBOARD_TYPE_MODAL == this.artboardType\n        this.externalArtboardURL = externalArtboardURL\n\n        if (this.isModal || Constants.ARTBOARD_TYPE_OVERLAY == this.artboardType)\n        {\n            this.showShadow = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_SHADOW)\n            if (undefined != this.showShadow)\n                this.showShadow = this.showShadow == 1\n            else\n            {\n                const legacyShadow = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.LEGACY_ARTBOARD_MODAL_SHADOW)\n                if (undefined != legacyShadow && Constants.ARTBOARD_TYPE_MODAL == this.artboardType)\n                {\n                    this.showShadow = legacyShadow\n                } else\n                {\n                    this.showShadow = true\n                }\n            }\n        }\n\n        const disableFixed = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_DISABLE_FIXED)\n        this.disableFixedLayers = (disableFixed != undefined && disableFixed > 0) ? disableFixed == 1 : exporter.disableFixedLayers\n\n        this.overlayOverFixed = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_OVERFIXED) == 1\n        {\n            var overlayAlsoFixed = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_ALSOFIXED)\n            this.overlayAlsoFixed = overlayAlsoFixed != undefined ? overlayAlsoFixed : true\n        }\n        this.overlayClosePrevOverlay = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_CLOSE_PREVOVERLAY) == 1\n\n        this.disableAutoScroll =\n            exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_DISABLE_AUTOSCROLL)\n        this.transNextSecs =\n            exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_TRANS_TO_NEXT_SECS)\n        if (undefined != this.transNextSecs && '' == this.transNextSecs)\n            this.transNextSecs = undefined\n\n        this.transAnimType = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_TRANS_ANIM_TYPE)\n        if (undefined == this.transAnimType)\n            this.transAnimType = Constants.ARTBOARD_TYPE_OVERLAY == this.artboardType ?\n                Constants.ARTBOARD_TRANS_ANIM_FADE :\n                Constants.ARTBOARD_TRANS_ANIM_NONE\n        if (Constants.ARTBOARD_TRANS_ANIM_NONE != this.transAnimType)\n        {\n            exporter.enableTransitionAnimation = true\n        }\n\n        this.overlayByEvent = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_BY_EVENT)\n        if (this.overlayByEvent == undefined || this.overlayByEvent == \"\") this.overlayByEvent = 0\n\n        this.oldOverlayAlign = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.OLD_ARTBOARD_OVERLAY_ALIGN)\n        if (this.oldOverlayAlign == undefined || this.oldOverlayAlign == \"\") this.oldOverlayAlign = 0\n\n        this.overlayPin = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_PIN)\n        if (this.overlayPin == undefined)\n        {\n            const newValues = Utils.upgradeArtboardOverlayPosition(this.oldOverlayAlign)\n            this.overlayPin = newValues.pinTo\n            this.overlayPinHotspot = newValues.hotspotTo\n            this.overlayPinPage = newValues.pageTo\n        } else\n        {\n            this.overlayPinHotspot = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_PIN_HOTSPOT)\n            this.overlayPinPage = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.ARTBOARD_OVERLAY_PIN_PAGE)\n        }\n\n    }\n\n    collectLayers(space)\n    {\n        //if(DEBUG) exporter.logMsg(space+\"PZArtboard.collectLayers() name=\"+this.name)\n        this.childs = this.collectAChilds(this.slayer.layers, space + \" \")\n    }\n\n    export()\n    {\n        this._exportImages()\n        this._findFixedPanelHotspots()\n        //this._exportOverlayLayers()\n        this._pushIntoStoryData(this.index)\n    }\n    \n    //------------------- FIND HOTSPOTS WHICH LOCATE OVER FIXED HOTPOSTS ----------------------------\n    //------------------- AND MOVE THEM INTO FIXED LAYER SPECIAL HOTSPOTS ---------------------------\n    _findFixedPanelHotspots()\n    {\n        function isChild(parent,child){\n            if(!child.parent) return false\n            if(child.parent==parent) return true\n            return isChild(parent,child.parent)\n        }\n        for (var l of this.fixedLayers)\n        {\n            for (let hIndex = 0; hIndex < this.hotspots.length; hIndex++)\n            {\n                // move hotspot from artboard hotspots to fixed layer hotspots                \n                let hotspot = this.hotspots[hIndex]\n                let frame = l.frame\n\n                if(l.isVertScroll){\n                    if(!isChild(l,hotspot.owner)) continue\n                    //const maskedLayer = l.isVertScroll ? l.findMaskLayer():null \n                    //frame = maskedLayer ? maskedLayer.frame : l.frame\n                }else{\n\n                }\n                if (hotspot.r.insideRectangle(frame))\n                {\n                    if (!hotspot.fixedAncestorID || hotspot.fixedAncestorID == l.objectID)\n                    {\n                        this.hotspots.splice(hIndex--, 1)\n                        hotspot.r.x -= l.frame.x\n                        hotspot.r.y -= l.frame.y\n                        l.hotspots.push(hotspot)\n                    }\n                }\n            }\n\n        }\n    }\n\n\n    //------------------ GENERATE STORY.JS FILE  ------------------\n    _pushIntoStoryData(pageIndex)\n    {\n        const mainName = this.name\n\n        if (DEBUG) exporter.logMsg(\"process main artboard \" + mainName);\n        pzDoc.totalImages++\n\n        let data = {}\n\n        data['id'] = this.objectID\n        data['groupID'] = String(this.slayer.parent.id)\n        data['index'] = pageIndex\n        data['image'] = Utils.toFilename(mainName + '.' + exporter.fileType, false)\n        if (exporter.retinaImages)\n            data['image2x'] = Utils.toFilename(mainName + '@2x.' + exporter.fileType, false)\n\n        data['width'] = this.frame.width\n        data['height'] = this.frame.height\n        data['x'] = this.frame.x\n        data['y'] = this.frame.y\n        data['title'] = mainName\n\n        if (this.transNextSecs != undefined)\n        {\n            data['transNextMsecs'] = parseFloat(this.transNextSecs) * 1000\n        }\n\n        data['transAnimType'] = this.transAnimType\n\n        if (this.disableAutoScroll)\n        {\n            data['disableAutoScroll'] = true\n        }\n\n        {\n            var layoutGrid = this.nlayer.layout() // class: MSLayoutGrid\n            if (!layoutGrid) layoutGrid = MSDefaultLayoutGrid.defaultLayout();\n            if (layoutGrid)\n            {\n                var grid = {\n                    offset: layoutGrid.horizontalOffset(),\n                    totalWidth: layoutGrid.totalWidth(),\n                    numberOfColumns: layoutGrid.numberOfColumns(),\n                    columnWidth: layoutGrid.columnWidth(),\n                    gutterWidth: layoutGrid.gutterWidth()\n                }\n                data['layout'] = grid\n            }\n        }\n\n        if (this.isModal)\n        {\n            data['type'] = 'modal'\n            data['isModal'] = true\n            data['showShadow'] = this.showShadow ? 1 : 0\n        } else if (this.externalArtboardURL != undefined && this.externalArtboardURL != '')\n        {\n            data['type'] = 'external'\n        } else if (Constants.ARTBOARD_TYPE_OVERLAY == this.artboardType)\n        {\n            data['type'] = 'overlay'\n            // try to find a shadow\n            if (this.showShadow)\n            {\n                const shadowInfo = this._getShadowLayerShadowInfo()\n                if (shadowInfo)\n                {\n                    data['overlayShadow'] = shadowInfo.style\n                    data['overlayShadowX'] = shadowInfo.x\n                }\n            } else if ((Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT == this.overlayPin) && (Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT == this.overlayPinHotspot))\n            {\n                const shadowInfo = this._findLayersShadowInfo()\n                if (shadowInfo)\n                {\n                    data['overlayShadowX'] = shadowInfo.x\n                }\n            }\n            data['overlayByEvent'] = this.overlayByEvent\n            data['overlayPin'] = this.overlayPin\n            data['overlayPinHotspot'] = this.overlayPinHotspot\n            data['overlayPinPage'] = this.overlayPinPage\n            data['overlayOverFixed'] = !!this.overlayOverFixed\n            data['overlayAlsoFixed'] = !!this.overlayAlsoFixed\n            data['overlayClosePrevOverlay'] = !!this.overlayClosePrevOverlay\n        } else\n        {\n            data['type'] = 'regular'\n        }\n\n        // add fixed layers\n        data['fixedPanels'] = this._getFixedLayersForJSON()\n\n        // add hotspots \n        data['links'] = this._buildHotspots(this.hotspots)\n\n        if (this.overlayRedirectTargetPage != undefined)\n            data['overlayRedirectTargetPage'] = this.overlayRedirectTargetPage\n\n        let js = pageIndex ? ',' : '';\n        js += \"$.extend(new ViewerPage(),\" + JSON.stringify(data, null, ' ') + \")\\n\"\n\n        exporter.storyData.pages.push(data)\n    }\n\n\n    _getShadowLayerShadowInfo()\n    {\n        if (!this.shadowLayers) return undefined\n        // sort layers to find largest\n        const resorted = this.shadowLayers.sort((l1,l2)=>l1.frame.height<l2.frame.height)\n        //\n        return resorted[0].getShadowInfo()\n    }\n\n    _findLayersShadowInfo(layers = undefined, checkKeepFixedShadow = false)\n    {\n        if (layers === undefined) layers = this.childs\n        //\n        let shadowInfo = undefined\n        for (const l of layers)\n        {\n            if (checkKeepFixedShadow && l.keepFixedShadow) continue\n            shadowInfo = l.getShadowInfo()\n            if (shadowInfo && shadowInfo.style.fills!==undefined && shadowInfo.style.fills.length)\n            {\n                break\n            }\n            shadowInfo = this._findLayersShadowInfo(l.childs, checkKeepFixedShadow)\n            if (shadowInfo) break\n        }\n        return shadowInfo\n    }\n\n    clearRefsBeforeJSON()\n    {\n        super.clearRefsBeforeJSON()\n        this.overlayLayers = undefined\n        this.fixedLayers = undefined\n        this.imageLayers = undefined\n    }\n\n\n    addShadowLayer(layer){\n        if(this.shadowLayers===undefined) this.shadowLayers = []\n        this.shadowLayers.push(layer)\n    }\n\n    addLayerAsExportableImage(layer)\n    {\n        layer.imageIndex = this.imageLayers.length\n        this.imageLayers.push(layer)\n        if (DEBUG) exporter.logMsg(\"Add image layer: \" + layer.name)\n    }\n\n    _getFixedLayersForJSON()\n    {\n        let recs = []\n\n        if (this.fixedLayers.length)\n        {\n            const mainName = this.name\n            const foundPanels = []\n            for (var l of this.fixedLayers)\n            {\n                let type = l.fixedType\n                if (type == \"\")\n                {\n                    exporter.logError(\"pushFixedLayersIntoJSStory: can't understand fixed panel type for artboard '\" + this.name\n                        + \"' layer='\" + l.name + \"' layer.frame=\" + l.frame + \" this.frame=\" + this.frame)\n                    continue\n                }\n                pzDoc.totalImages++\n\n                if (!l.isFloat && foundPanels[type])\n                {\n                    exporter.logError(\"pushFixedLayersIntoJSStory: found more than one panel with type '\" + type + \"' for artboard '\"\n                        + this.name + \"' layer='\" + l.name + \"' layer.frame=\" + l.frame + \" this.frame=\" + this.frame)\n                    const existedPanelLayer = foundPanels[type]\n                    exporter.logError(\"pushFixedLayersIntoJSStory: already exists panel layer='\" + existedPanelLayer.name\n                        + \"' layer.frame=\" + existedPanelLayer.frame)\n                    continue\n                }\n                foundPanels[type] = l\n\n                const fileNamePostfix = !(l.isFloat || l.isVertScroll) ? \"\" : ('-' + l.fixedIndex)\n\n\n                const rec = {\n                    constrains: l.constrains,\n                    x: l.frame.x,\n                    y: l.frame.y,\n                    width: l.frame.width,\n                    height: l.frame.height,\n                    type: type,\n                    index: l.fixedIndex,\n                    isFloat: l.isFloat,\n                    isVertScroll: l.isVertScroll,\n                    divID: l.layerDivID != undefined ? l.layerDivID : \"\",\n                    links: this._buildHotspots(l.hotspots, true),\n                    image: Utils.quoteString(Utils.toFilename(mainName, false) + fileNamePostfix + '.' + exporter.fileType)\n                }\n\n                if (l.isVertScroll)\n                {\n                    const maskLayer = l.findMaskLayer()\n                    rec.mskH = maskLayer.frame.height - (l.frame.y - maskLayer.frame.y)\n                    rec.vst = l.vScrollType\n                }\n\n                if (exporter.retinaImages)\n                    rec.image2x = Utils.quoteString(Utils.toFilename(mainName, false) + fileNamePostfix + '@2x.' + exporter.fileType, false)\n\n                // setup shadow\n                const shadowInfo = this._findLayersShadowInfo([l], true)\n                if (shadowInfo)\n                {\n                    rec.shadow = shadowInfo.style\n                    rec.shadowX = shadowInfo.x\n                }\n                recs.push(rec)\n            }\n        }\n\n        return recs\n    }\n\n\n\n    _buildHotspots(srcHotspots, isParentFixed = false)\n    {\n        let newHotspots = []\n        for (var hotspot of srcHotspots)\n        {\n            const newHotspot = {\n                rect: hotspot.r,\n                isParentFixed: isParentFixed,\n            }\n\n\n            if (hotspot.linkType == 'back')\n            {\n                newHotspot.action = 'back'\n            } else if (hotspot.linkType == 'artboard' && pzDoc.artboardsDict[hotspot.artboardID] != undefined\n                && pzDoc.artboardIDsDict[hotspot.artboardID].externalArtboardURL != undefined\n            )\n            {\n                newHotspot.url = pzDoc.artboardIDsDict[hotspot.artboardID].externalArtboardURL\n            } else if (hotspot.linkType == 'artboard')\n            {\n                const targetPage = pzDoc.artboardIDsDict[hotspot.artboardID]\n                if (targetPage == undefined)\n                {\n                    if (DEBUG) exporter.logMsg(\"undefined artboard: '\" + hotspot.artboardName + '\"');\n                    continue\n                }\n                const targetPageIndex = targetPage.index;\n                newHotspot.page = targetPageIndex\n            } else if (hotspot.linkType == 'href')\n            {\n                newHotspot.url = hotspot.href\n            } else if (hotspot.target != undefined)\n            {\n                newHotspot.target = hotspot.target\n            } else\n            {\n                if (DEBUG) exporter.logMsg(\"_pushHotspotIntoJSStory: Uknown hotspot link type: '\" + hotspot.linkType + \"'\")\n            }\n\n            if (hotspot.target != undefined)\n            {\n                newHotspot.target = hotspot.target\n            }\n\n            if (hotspot.overlayRedirect && newHotspot.page != undefined)\n            {\n                this.overlayRedirectTargetPage = newHotspot.page\n            }\n\n            newHotspot.index = this.nextLinkIndex++\n            newHotspots.push(newHotspot)\n\n        }\n        return newHotspots\n    }\n\n\n    //------------------ GENERATE IMAGES  ------------------\n\n\n    _getImageName(scale, injectScaleToName = true, panelPostix = \"\")\n    {\n        const suffix = injectScaleToName && scale == 2 ? \"@2x\" : \"\";\n        return Utils.toFilename(this.name, false) + panelPostix + suffix + \".\" + exporter.fileType;\n    }\n\n    // exportType:  full, layer, preview, artboard\n    _exportImage(exportType, nlayer = null, panelPostix = \"\", forFixedLayer = false)\n    {\n        nlayer = nlayer || this.nlayer\n        if (DEBUG) exporter.logMsg(\"   exportImage() for \" + nlayer.name())\n\n        let scales = null\n        let imageBasePath = exporter.imagesPath\n        let injectScaleToName = true\n\n        if ('artboard' == exportType || 'layer' == exportType)\n        {\n            scales = exporter.retinaImages ? [1,2] : [1]\n        } else if ('full' == exportType)\n        {\n            scales = [2]\n            imageBasePath = exporter.fullImagesPath\n            injectScaleToName = false\n        } else if ('preview' == exportType)\n        {\n            scales = [522 / nlayer.frame().width()]\n            imageBasePath = exporter.previewsImagePath\n            injectScaleToName = false\n        }\n\n        for (let scale of scales)\n        {\n            const imageName = this._getImageName(scale, injectScaleToName, panelPostix)\n            const imagePath = imageBasePath + imageName\n            let slice = null\n\n            slice = MSExportRequest.exportRequestsFromExportableLayer(nlayer).firstObject();\n            slice.scale = scale;\n            slice.saveForWeb = false;\n            slice.format = exporter.fileType;\n\n            const bounds = nlayer.absoluteRect();\n            if (forFixedLayer) slice.setRect(bounds.rect())\n\n            exporter.ndoc.saveArtboardOrSlice_toFile(slice, imagePath);\n        }\n    }\n\n    // new experimental code to export images\n    // we don't use it because it doesn't allow to set a file name\n    _exportImage2(scales, slayer)\n    {\n        if (DEBUG) exporter.logMsg(\"exportImage()\");\n\n        const imagePath = exporter.imagesPath // + this._getImageName(scales)\n        exporter.logMsg('_exportImage2 name=' + slayer.name)\n        const options = {\n            scales: scales,\n            output: exporter.imagesPath,\n            overwriting: true,\n            'save-for-web': true,\n            formats: exporter.fileType\n        }\n        Sketch.export(slayer, options)\n\n    }\n\n    _exportImages()\n    {\n        if(this.artboardType===Constants.ARTBOARD_TYPE_EXTERNAL_URL) return\n        //this._getAllLayersMatchingPredicate(Sketch.getSelectedDocument().sketchObject)\n\n        if (DEBUG) exporter.logMsg(\"PZArtboard._exportImages: running... \" + this.name)\n        let scales = exporter.retinaImages ? [1, 2] : [1]\n\n        // export fixed panels to their own image files\n        this._exportFixedLayersToImages(scales)\n\n        // hide fixed panels to generate a main page content without fixed panels \n        // and their artefacts (shadows)\n        this._hideFixedLayers(true)\n\n        this._exportImage(\"artboard\")\n\n        // export images for Element Inspector\n        if (exporter.enabledJSON)\n        {\n            this._exportImageLayers()\n        }\n\n        // show fixed panels back\n        // ! temporary disabled because an exported image still shows hidden layers\n        this._hideFixedLayers(false)\n\n        if (exporter.exportFullImages &&  (this.artboardType===Constants.ARTBOARD_TYPE_REGULAR || this.artboardType===Constants.ARTBOARD_TYPE_OVERLAY))\n        {\n            // export full image        \n            if (DEBUG) exporter.logMsg(\"PZArtboard._exportImages: export full image\")\n            this._exportImage(\"full\")\n        }\n\n        // export preview images (to use by Gallery and Inspector Viewer)      \n        this._exportImage(\"preview\")\n\n        if (DEBUG) exporter.logMsg(\"PZArtboard._exportImages: done!\")\n    }\n\n\n    _exportOverlayLayers()\n    {\n        if (DEBUG) exporter.logMsg('_exportOverlayLayers: running')\n        let scales = exporter.retinaImages ? [1, 2] : [1]\n        for (const layer of this.overlayLayers)\n        {\n            // log('_exportOverlayLayers: '+layer.name)               \n            // need \n            const artboard = this._findArtboardByName(layer.name + \"@\")\n            if (!artboard) continue\n            //\n            this._exportImage(\"layer\", artboard.sketchObject, \"-\" + layer.name)\n            //\n        }\n        if (DEBUG) exporter.logMsg('_exportOverlayLayers: done!')\n    }\n\n    _exportImageLayers()\n    {\n        if (DEBUG) exporter.logMsg('_exportImageLayers: running')\n        for (var layer of this.imageLayers)\n        {\n            const path = exporter._outputPath + \"/\" + layer._buildImageURL()\n            if (DEBUG) exporter.logMsg(path)\n            if (\"Image\" == layer.slayer.type)\n            {\n                // The folowing code source — https://stackoverflow.com/a/17510651/9384835\n                let image = layer.slayer.image.nsimage\n                let cgRef = [image CGImageForProposedRect: nil context: nil hints: nil]\n                let newRep = [[NSBitmapImageRep alloc] initWithCGImage: cgRef]\n                [newRep setSize: [image size]];\n                let pngData = [newRep representationUsingType: NSPNGFileType properties: nil];\n                [pngData writeToFile: path atomically: true];\n                [newRep autorelease];\n            } else if (\"Group\" == layer.slayer.type)\n            {\n                if (DEBUG) exporter.logMsg(\"Export group\")\n                const slice = MSExportRequest.exportRequestsFromExportableLayer(layer.nlayer).firstObject();\n                slice.scale = 2\n                slice.saveForWeb = false\n                slice.format = exporter.fileType\n                exporter.ndoc.saveArtboardOrSlice_toFile(slice, path)\n            }\n        }\n        if (DEBUG) exporter.logMsg('_exportImageLayers: done')\n    }\n\n\n    _exportFixedLayersToImages(scales)\n    {\n        for (var layer of this.fixedLayers)\n        {\n            layer.calculateFixedType()\n\n            // temporary disable fixed panel shadows\n            let orgShadows = undefined\n            let shadowInfo = this._findLayersShadowInfo([layer], true)\n            if (shadowInfo)\n            {\n                orgShadows = shadowInfo.layer.slayer.style.shadows\n                shadowInfo.layer.slayer.style.shadows = []\n            }\n\n            let orgHeight = undefined, maskLayer = undefined, orgResizesContent = undefined\n\n            if (layer.overlayType === Constants.LAYER_OVERLAY_VSCROLL)\n            {\n                const layerIndex = layer.parent.childs.indexOf(layer)\n                maskLayer = layer.parent.childs[layerIndex - 1]\n                maskLayer.nlayer.hasClippingMask = false\n                //\n                // check artboard height\n                if ((layer.frame.y + layer.frame.height) > this.frame.height)\n                {\n                    orgHeight = this.frame.height\n                    orgResizesContent = this.nlayer.resizesContent\n                    this.nlayer.resizesContent = false\n                    this.slayer.frame.height = layer.frame.y + layer.frame.height + 10\n                }\n            }\n\n            // for div and  float fixed layer we need to generate its own image files\n            if (layer.isFloat || layer.isVertScroll)\n            {\n                //this._exportImage2('1, 2',layer.parent.slayer)         \n                const l = layer.parent.isSymbolInstance ? layer : layer\n                this._exportImage(\"layer\", l.nlayer, \"-\" + layer.fixedIndex, true)\n            }\n\n            // restore original artboard height\n            if (orgHeight !== undefined)\n            {\n                this.slayer.frame.height = orgHeight\n                this.nlayer.resizesContent = orgResizesContent\n            }\n\n            // restore original fixed panel shadows\n            if (shadowInfo)\n            {\n                shadowInfo.layer.slayer.style.shadows = orgShadows\n            }\n\n            if (maskLayer) maskLayer.nlayer.hasClippingMask = true\n        }\n    }\n\n    _hideFixedLayers(hide)\n    {\n        const show = !hide\n        for (var layer of this.fixedLayers)\n        {\n            // we need to hide/show only div and  float panels\n            if (undefined == layer.slayer.style) continue\n            if (layer.isFloat || layer.isVertScroll)\n            {\n                layer.slayer.hidden = hide\n            }\n\n            // temporary remove fixed panel shadows\n            if (hide)\n            {\n                layer.fixedShadows = layer.slayer.style.shadows\n                layer.slayer.style.shadows = []\n            }\n\n            // restore original fixed panel shadows\n            if (show)\n            {\n                layer.slayer.style.shadows = layer.fixedShadows\n            }\n\n            // Commented to make it worked in Sketch 65\n            //Sketch.getSelectedDocument().sketchObject.documentData().invalidateAffectedSymbolInstancesWithDiff(layer.objectID)\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/PZDoc.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\nSketch = require('sketch/dom')\n\nconst replaceValidKeys = [\n    \"x\", \"y\", \"w\", \"h\",\n    \"c\", // childs\n    \"s\", // smName\n    \"l\", //styleName\n    \"text\", \"comment\", \"sharedLib\"]\n// smName: symbol master Name\nfunction replacer(key, value)\n{\n    // Pass known keys and array indexes\n    if (value != undefined && (replaceValidKeys.indexOf(key) >= 0 || !isNaN(key)))\n    {\n        return value\n    }\n    return undefined\n}\n\nvar pzDoc = null\n\nclass PZDoc\n{\n    constructor()\n    {\n        pzDoc = this\n        var Document = require('sketch/dom').Document\n        this.sDoc = Document.getSelectedDocument()\n        this.mPages = []\n        this.mAllLayers = []\n        this.mLinkedLayers = []\n        this.usedLibs = {}\n        this.swatchesMap = undefined\n        this.sSymbols = {}\n        this.artboardCount = 0\n        this.startArtboardIndex = 0\n\n        this.mAllArtboards = []\n        this.artboardsDict = {}\n        this.artboardIDsDict = {}\n        this.jsLibs = undefined\n\n    }\n\n    collectData()\n    {\n        // init required data\n        this._buildSymbolDict()\n\n        // build local pages\n        const mPages = []\n\n\n        if (Constants.EXPORT_MODE_CURRENT_PAGE == exporter.exportOptions.mode)\n        {\n            // build only current page \n            const mPage = new PZPage(Sketch.fromNative(exporter.exportOptions.currentPage))\n            mPage.collectData()\n            mPages.push(mPage)\n        } else if (Constants.EXPORT_MODE_SELECTED_ARTBOARDS == exporter.exportOptions.mode)\n        {\n            // build only selected artboards on current page         \n            const mPage = new PZPage(Sketch.fromNative(exporter.exportOptions.currentPage))\n            mPage.collectData(exporter.exportOptions.selectedArtboards)\n            mPages.push(mPage)\n\n        } else\n        {\n            // build all pages and artboards\n            for (var sPage of this.sDoc.pages)\n            {\n                if (DEBUG) exporter.logMsg(\"PZDoc:collectData() process page=\" + sPage.name)\n\n                if (exporter.filterAster && sPage.name.indexOf(\"*\") == 0) continue\n\n                // create new local Page object\n                const mPage = new PZPage(sPage)\n                mPage.collectData()\n                mPages.push(mPage)\n            }\n        }\n        this.mPages = mPages\n\n        ///\n        this._collectLibArtboards()\n    }\n\n\n    buildLinks()\n    {\n        exporter.logMsg('PZDoc.buildLinks: running')\n        for (var mLayer of this.mLinkedLayers)\n        {\n            mLayer.buildLinks(' ');\n        }\n        exporter.logMsg('PZDoc.buildLinks: stop')\n    }\n\n\n    export()\n    {\n        exporter.logMsg(\" PZDoc:run running...\")\n\n        /// Export pages\n        this.totalImages = 0\n        for (var page of this.mPages)\n        {\n            page.export();\n        }\n\n        exporter.logMsg(\" PZDoc:run done!\")\n    }\n\n\n    _getLibAssetsPath(lib)\n    {\n        return Utils.cutLastPathFolder(lib.sDoc.path) + \"/\" + Constants.ASSETS_FOLDER_PREFIX + \"/\" + lib.jsLib.name\n    }\n\n    getSymbolData()\n    {\n        // load library inspector file\n        let inspectors = \"\"\n        let vars = \"\"\n        const libs = this._getLibraries()\n\n        for (const lib of libs)\n        {\n            if (!this.usedLibs[lib.jsLib.name]) continue\n            const libAssetsPath = this._getLibAssetsPath(lib)\n            //Utils.cutLastPathFolder(lib.sDoc.path) + \"/\" + lib.jsLib.name\n            const pathToSymbolTokens = libAssetsPath + \"/\" + Constants.SYMBOLTOKENFILE_POSTFIX\n            exporter.logMsg('pathToSymbolTokens = ' + pathToSymbolTokens + \" name=\" + lib.jsLib.name)\n            const inspectorData = Utils.readFile(pathToSymbolTokens)\n            if (inspectors != \"\") inspectors += \",\"\n            inspectors += '\"' + Utils.toFilename(lib.jsLib.name, true, false) + '\":' + (inspectorData ? inspectorData : \"{}\")\n            //\n            const pathToVars = libAssetsPath + \"/\" + Constants.VARSFILE_POSTFIX\n            const varsData = Utils.readFile(pathToVars)\n            if (vars != \"\") vars += \",\"\n            vars += \"'\" + Utils.toFilename(lib.jsLib.name, true, false) + \"':\" + (varsData ? varsData : \"{}\")\n        }\n        return \"const SYMBOLS_DICT = {\" + inspectors + \"};\\n\" +\n            \"const TOKENS_DICT = {\" + vars + \"};\"\n    }\n\n\n    getCSSIncludes()\n    {\n        const cssIncludes = []\n        const libs = this._getLibraries()\n        for (const lib of libs)\n        {\n            const libAssetsPath = this._getLibAssetsPath(lib)\n            //const libPath = Utils.cutLastPathFolder(lib.sDoc.path) + \"/\" + lib.jsLib.name\n            // Copy library CSS to Resources folder\n            {\n                const pathSrcCSS = libAssetsPath + \"/\" + Constants.CSSFILE_POSTFIX\n                const cssFileName = Utils.toFilename(lib.jsLib.name + \".css\")\n                const css = Utils.readFile(pathSrcCSS)\n                if (undefined != css)\n                {\n                    const pathResultCSS = exporter.createDestFile(cssFileName, Constants.DEST_RESOURCES_DIRECTORY)\n                    if (!Utils.writeToFile(css, pathResultCSS))\n                    {\n                        exporter.logError(\"getSymbolData: can't library save CSS to \" + pathResultCSS)\n                    }\n                    cssIncludes.push(cssFileName)\n                }\n            }\n            //\n        }\n        return cssIncludes\n    }\n\n\n    getJSON()\n    {\n\n        exporter.logMsg(\" getJSON: cleanup before saving...\")\n        this.mAllLayers.forEach(l =>\n        {\n            l.clearRefsBeforeJSON()\n        });\n\n        this.mAllArtboards.forEach(l =>\n        {\n            l.clearRefsBeforeJSON()\n        });\n\n        exporter.logMsg(\" getJSON: running...\")\n        const json = JSON.stringify(this.mAllArtboards)//, replacer, null)\n        exporter.logMsg(\" getJSON: done!\")\n\n        return json\n    }\n\n    undoChanges()\n    {\n        if (!exporter.enabledJSON) Utils.actionWithType(this.sDoc.sketchObject, \"MSUndoAction\").doPerformAction(nil);\n\n        this.jsLibs = []\n\n    }\n\n\n\n\n    //////////////////////////// PUBLIC HELPERS  ///////////////////////\n\n\n    // return index of new artboard\n    addArtboard(mArtboard)\n    {\n        this.artboardsDict[mArtboard.name] = mArtboard\n        this.artboardIDsDict[mArtboard.objectID] = mArtboard\n        this.mAllArtboards.push(mArtboard)\n\n        if (mArtboard.nlayer.isFlowHome())\n        {\n            this.startArtboardIndex = this.artboardCount\n        }\n\n        return this.artboardCount++\n\n    }\n\n\n    // return Sketch native object\n    findArtboardByID(artboardID)\n    {\n        let artboard = this.artboardIDsDict[artboardID]\n        if (artboard) return artboard\n        return this._findLibraryArtboardByID(artboardID)\n    }\n\n    getLayerWithID(id)\n    {\n        return this.sDoc.getLayerWithID(id)\n    }\n\n    getSymbolMasterByID(id)\n    {\n        if (!(id in this.sSymbols))\n        {\n            exporter.logMsg('getSymbolMasterByID can not find symbol by ID=' + id)\n            return undefined\n        }\n        return this.sSymbols[id]\n    }\n\n    // result: array [{sn: swatch name,ln: library name},..] OR null\n    getSwatchInfoByID(swatchID)\n    {\n        // load all swatched initially\n        if (undefined == this.swatchesMap)\n        {\n            this.swatchesMap = {}\n            // load library colors\n            var libs = require('sketch/dom').getLibraries()\n            libs.filter(l => l.valid && l.enabled).forEach(function (lib)\n            {\n                var stylesReferences = null\n                try\n                {\n                    stylesReferences = lib.getImportableSwatchReferencesForDocument(this.sDoc)\n                }\n                catch (error)\n                {\n                    stylesReferences = null\n                }\n                if (!stylesReferences) return\n                stylesReferences.forEach(function (s)\n                {\n                    this.swatchesMap[s.id] = {\n                        sn: s.name,\n                        ln: lib.name\n                    }\n                }, this)\n            }, this)\n            // load local colors\n            //          log(require('sketch').globalAssets.colors)\n        }\n        // find\n        //        log(\"getSwatchInfoByID\")\n        const res = this.swatchesMap[swatchID]\n        if (!res) return null\n        //    log(this.swatchesMap)\n        //  log(res.ln)\n        this.usedLibs[res.ln] = true\n        return res\n    }\n\n    //////////////////////////// PRIVATE ///////////////////////\n\n\n    _collectLibArtboards()\n    {\n        for (const mLayer of this.mLinkedLayers)\n        {\n            if (mLayer.flow && mLayer.flow.targetID && !(mLayer.flow.targetID in this.artboardIDsDict))\n            {\n                const mArtboard = this._findLibraryArtboardByID(artboardID)\n            }\n        }\n    }\n\n    _sortArboards()\n    {\n        const exportOptions = exporter.exportOptions\n\n        for (const mPage of this.mPages)\n        {\n            mPage.sortArboards()\n        }\n\n    }\n\n    // return Sketch native object\n    _findLibraryArtboardByID(artboardID)\n    {\n        if (exporter.ignoreLibArtboards) return false\n        if (DEBUG) exporter.logMsg(\"findLibraryArtboardByID running...  artboardID:\" + artboardID)\n        // find Sketch Artboard\n        var sArtboard = undefined\n        var lib = undefined\n        for (lib of this._getLibraries())\n        {\n            if (DEBUG) exporter.logMsg(\"findLibraryArtboardByID getLayerWithID for lib \" + lib.jsLib.name)\n            sArtboard = lib.sDoc.getLayerWithID(artboardID)\n            if (sArtboard) break\n        }\n        // check artboard existing\n        if (!sArtboard)\n        {\n            if (DEBUG) exporter.logMsg(\"findLibraryArtboardByID FAILED\")\n            return false\n        }\n\n        // Create new page\n        this._buildSymbolDict(lib.sDoc)\n        const mPage = new PZPage(null)\n        mPage.collectData([sArtboard])\n        this.mPages.push(mPage)\n\n        return this.artboardIDsDict[artboardID]\n    }\n\n    _getLibraries()\n    {\n        if (undefined != this.jsLibs) return this.jsLibs\n\n        exporter.logMsg(\"_getLibraries: start\")\n        this.jsLibs = []\n\n        var libraries = require('sketch/dom').getLibraries()\n        for (const jsLib of libraries)\n        {\n            if (!jsLib.valid || !jsLib.enabled) continue\n            exporter.logMsg(\"_getLibraries: try to load document for library \" + jsLib.name + \"\")\n\n            const sDoc = jsLib.getDocument()\n            if (!sDoc)\n            {\n                exporter.logMsg(\"_getLibraries: can't load document for library \" + sDoc.path + \"\")\n                continue\n            }\n            this.jsLibs.push({\n                jsLib: jsLib,\n                sDoc: sDoc\n            })\n        }\n        exporter.logMsg(\"_getLibraries: finish\")\n        return this.jsLibs\n    }\n\n\n    _buildSymbolDict(sDoc = null)\n    {\n        if (!sDoc) sDoc = this.sDoc\n\n        for (const sSymbol of sDoc.getSymbols())\n        {\n            const sid = sSymbol.symbolId\n            if (sid in this.sSymbols) continue\n            this.sSymbols[sid] = sSymbol\n        }\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/PZLayer.js",
    "content": "const { off } = require(\"process\")\n@import(\"constants.js\")\n@import(\"lib/utils.js\")\n@import(\"exporter/PZDoc.js\")\n\nvar Sketch = require('sketch/dom')\nvar Flow = require('sketch/dom').Flow\nvar Text = require('sketch/dom').Text\nvar Style = require('sketch/dom').Style\n\nvar LAYER_COUNTER = 0\n\nvar ResizingConstraint = {\n    NONE: 0,\n    RIGHT: 1 << 0,\n    WIDTH: 1 << 1,\n    LEFT: 1 << 2,\n    BOTTOM: 1 << 3,\n    HEIGHT: 1 << 4,\n    TOP: 1 << 5\n}\n\nconst ICON_TAG = \" / ic-\" // Use this string to find icon symbol\n\nconst alignMap2 = {\n    [Text.Alignment.left]: \"left\",\n    [Text.Alignment.center]: \"center\",\n    [Text.Alignment.right]: \"right\",\n    [Text.Alignment.justify]: \"justify\"\n}\nconst vertAlignMap2 = {\n    [Text.VerticalAlignment.top]: \"top\",\n    [Text.VerticalAlignment.center]: \"middle\",\n    [Text.VerticalAlignment.bottom]: \"bottom\",\n}\n\nconst weights = [\n    { label: 'thin', sketch: 2, css: 100, title: \"Thin\" },\n    { label: 'extra-light', sketch: 3, css: 200, title: \"Extra Light\" },\n    { label: 'light', sketch: 4, css: 300, title: \"Light\" },\n    { label: 'regular', sketch: 5, css: 400, title: \"Regular\" },\n    { label: 'medium', sketch: 6, css: 500, title: \"Medium\" },\n    { label: 'semi-bold', sketch: 8, css: 600, title: \"Semi Bold\" },\n    { label: 'semibold', sketch: 8, css: 600, title: \"Semi Bold#2\" },\n    { label: 'bold', sketch: 9, css: 700, title: \"Bold\" },\n    { label: 'extra-bold', sketch: 10, css: 800, title: \"Extra Bold\" },\n    { label: 'black', sketch: 11, css: 900, title: \"Black\" },\n    { label: 'black', sketch: 12, css: 900, title: \"Black\" },\n    { label: 'solid', sketch: 14, css: 900, title: \"Solid\" },\n]\n\nclass PZLayer\n{\n\n    // nlayer: ref to native MSLayer Layer\n    // myParent: ref to parent MyLayer\n    constructor(sLayer, myParent)\n    {\n        this.nlayer = sLayer.sketchObject\n        this.name = sLayer.name\n        this.parent = myParent\n        this.objectID = String(sLayer.id)\n        this.ii = LAYER_COUNTER++\n        this.originalID = undefined\n        this.slayer = sLayer\n        this.artboard = myParent ? myParent.artboard : this\n        this.isParentFixed = undefined != myParent && (myParent.isFixed || myParent.isParentFixed)\n\n        // define type    \n        this.isArtboard = false\n        this.isGroup = false\n        this.isSymbolInstance = false\n        this.customLink = undefined\n        this.isLink = false\n        this.tp = undefined\n\n        if (\"Group\" == sLayer.type || \"Artboard\" == sLayer.type) this.isGroup = true\n\n        let symbolID = null\n        let targetID = null\n\n\n        //////////////////////////////////////////////////////// RESTORE SYMBOL INFO\n        // find a symbol and flow information saved by sketchtool during --detach (see export.sh)\n        if (this.isGroup && exporter.enabledJSON)\n        {\n            const info = this.nlayer.userInfo()\n            if (null != info)\n            {\n                const detach = info['com.sketch.detach']\n                if (detach && detach['symbolMaster']) symbolID = detach['symbolMaster']['symbolID']\n            }\n        }\n\n\n        // save found symbol information\n        const sSymbolMaster = symbolID ? pzDoc.getSymbolMasterByID(symbolID) : undefined\n        if (sSymbolMaster)\n        {\n            // This layer is Symbol instance\n            const smName = sSymbolMaster.name + \"\"\n\n            if (smName.indexOf(ICON_TAG) > 0)\n            {\n                // Found Icon symbol instance\n                this.tp = \"Icon\"\n                function getLayerStyleName(layer)\n                {\n                    return layer && layer.isGroup && layer.styleName !== \"\" ? layer.styleName : undefined\n                }\n                this.styleName = getLayerStyleName(myParent) || getLayerStyleName(myParent.parent)\n\n                this.smName = smName\n                this.isGroup = false\n                //\n                if (exporter.enabledJSON)\n                {\n                    this.pr = this._buildIconsPropsForJSON()\n                }\n            } else\n            {\n                // Regular symbol instance\n                this.isSymbolInstance = true\n                this.targetId = targetID\n\n                // prepare data for Element Inspector\n                const lib = sSymbolMaster.getLibrary()\n                this.smName = smName\n                if (lib)\n                {\n                    this.sharedLib = lib.name\n                    pzDoc.usedLibs[lib.name] = true\n                } else\n                {\n\n                }\n            }\n\n        } else\n        {\n            // Check layer shared styles\n            this.smName = undefined\n\n            // prepare data for Element Inspector            \n            var sharedStyle = this.slayer.sharedStyle\n            if (sharedStyle)\n            {\n                this.styleName = sharedStyle.name\n                const lib = sharedStyle.getLibrary()\n                if (lib)\n                {\n                    this.sharedLib = lib.name\n                    pzDoc.usedLibs[lib.name] = true\n                }\n            }\n            if (\"Text\" == sLayer.type)\n            {\n                this.text = this.slayer.text + \"\"\n            }\n            this.cv = this._getColorVariable()\n        }\n        this.targetId = this.slayer.flow ? this.slayer.flow.targetId : null\n\n        //////////////////////////////////////////////////////// Process artboard-special things\n        if (\"Artboard\" == sLayer.type) this.isArtboard = true\n        if (!this.isArtboard)\n        {\n            pzDoc.mAllLayers.push(this)\n\n\n            // Check if this layer has a link\n            if (this.targetId)\n            {\n                this.isLink = true\n            } else\n            {\n                const externalLinkHref = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.LAYER_EXTERNAL_LINK)\n                if (externalLinkHref != null && externalLinkHref != \"\" && externalLinkHref != \"http://\")\n                {\n                    this.externalLinkHref = externalLinkHref\n                    this.isLink = true\n                }\n            }\n            if (this.isLink)\n            {\n                pzDoc.mLinkedLayers.push(this)\n\n            }\n\n            // Check if this layer shadow will be used as artboard overlay shadow            \n            if (Constants.ARTBOARD_TYPE_OVERLAY == this.artboard.artboardType)\n            {\n                if (this.slayer && this.slayer.style\n                    && (\n                        (this.slayer.style.shadows && this.slayer.style.shadows.length)\n                        ||\n                        (this.slayer.style.innerShadows && this.slayer.style.innerShadows.length)\n                    )\n                )\n                {\n                    this.artboard.addShadowLayer(this)\n                }\n            }\n\n        }\n\n        //////////////////////////////////////////////////////// COMMENT\n        var comment = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.LAYER_COMMENT)\n        if (undefined != comment && '' != comment)\n        {\n            this.comment = comment\n        }\n\n        //////////////////////////////////////////////////////// IMAGE MASKED\n        if (\"Image\" == sLayer.type && this.nlayer.isMasked())\n        {\n            // sLayer.hidden = true\n            this.isMasked = true\n        }\n        //////////////////////////////////////////////////////// EXPORTABLE LAYER\n        else if (\"Image\" == sLayer.type || ((\"Group\" == sLayer.type || \"ShapePath\" == sLayer.type) && undefined != sLayer.exportFormats && sLayer.exportFormats.length > 0))\n        {\n            this.artboard.addLayerAsExportableImage(this)\n        }\n\n\n        this.childs = []\n        this.hotspots = []\n\n        //////////////////////////////////////////////////////// RECALCULTE COORDS FROM ABS TO LOCAL\n        this.frame = Utils.copyRectToRectangle(this.nlayer.absoluteRect())\n        if (!this.isArtboard)\n        {\n            this.frame.x -= this.artboard.frame.x\n            this.frame.y -= this.artboard.frame.y\n        }\n\n        //////////////////////////////////////////////////////// SAVE CONSTRAINS\n        if (myParent != undefined) this.constrains = this._calculateConstrains()\n\n        //////////////////////////////////////////////////////// LAYER IS SCROLLABLE CONTAINER        \n        if (sLayer.type === \"Group\")\n        {\n            const vScrollType = Utils.getLayerSetting(sLayer, SettingKeys.LAYER_VSCROLL_TYPE, Constants.LAYER_VSCROLL_NONE)\n            if (vScrollType !== Constants.LAYER_VSCROLL_NONE)\n            {\n                this.addSelfAsFixedLayerToArtboad(Constants.LAYER_OVERLAY_VSCROLL)\n                this.vScrollType = vScrollType\n            }\n        }\n        //////////////////////////////////////////////////////// OVERLAY & FIXED\n        if (!this.isArtboard && !this.artboard.disableFixedLayers && !this.isParentFixed)\n        {\n            var overlayType = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.LAYER_OVERLAY_TYPE)\n            if (undefined == overlayType || '' == overlayType)\n                overlayType = Constants.LAYER_OVERLAY_DEFAULT\n\n            if (this.nlayer.isFixedToViewport() || overlayType != Constants.LAYER_OVERLAY_DEFAULT)\n            {\n                this.addSelfAsFixedLayerToArtboad(overlayType)\n            }\n        }\n        //////////////////////////////////////////////////////// LAYER PROVIDES PAGE BACKGROUND\n        // check special internal properties\n        // check: if this layer provides browser window background color\n        if (\"\" == exporter.backColor)\n        {\n            while (true)\n            {\n                if (this.name.indexOf(Constants.INT_LAYER_NAME_BACKCOLOR) < 0) break\n                let fills = this.slayer.style.fills\n                if (undefined == fills) break\n                fills = fills.filter(function (el) { return el.enabled })\n                if (0 == fills.length) break\n                exporter.backColor = fills[0].color\n                break\n            }\n        }\n        //////////////////////////////////////////////////////// LAYER PROVIDES FAVICON\n        // check: if this layer provides browser favicon\n        if (this.name.includes(Constants.INT_LAYER_NAME_SITEICON))\n        {\n            exporter.siteIconLayer = this\n        }\n        //////////////////////////////////////////////////////// LAYER IS SPACER\n        // check: if this layer should be hiddden during export\n        if (this.name.includes(Constants.INT_LAYER_NAME_SPACER_PART)\n            && (\n                this.name.includes(Constants.INT_LAYER_NAME_SPACER)\n                || this.name.includes(Constants.INT_LAYER_NAME_XSPACER)\n                || this.name.includes(Constants.INT_LAYER_NAME_YSPACER)\n            ))\n        {\n            this.isSpacer = true\n            this.slayer.hidden = true\n        }\n\n        //////////////////////////////////////////////////////// OVERLAY REDIRECT\n        // check: if this layer contains special overlay\n        if (!this.isArtboard && this.name.indexOf(Constants.INT_LAYER_NAME_REDIRECT) >= 0)\n        {\n            this.overlayRedirect = true\n        }\n\n        //////////////////////////////////////////////////////// ICON OR IMAAGE\n        // checl if the layer is an image symbol name and we don't want to show the child parts\n        if (this.isSymbolInstance && this.smName.startsWith(\"images/\"))\n        {\n            this.isImageSymbol = true\n        }\n\n    }\n\n    _calculateConstrains()\n    {\n        const resizingConstraint = 63 ^ this.nlayer.resizingConstraint()\n        const res = {\n            top: (resizingConstraint & ResizingConstraint.TOP) === ResizingConstraint.TOP,\n            bottom: (resizingConstraint & ResizingConstraint.BOTTOM) === ResizingConstraint.BOTTOM,\n            left: (resizingConstraint & ResizingConstraint.LEFT) === ResizingConstraint.LEFT,\n            right: (resizingConstraint & ResizingConstraint.RIGHT) === ResizingConstraint.RIGHT,\n            height: (resizingConstraint & ResizingConstraint.HEIGHT) === ResizingConstraint.HEIGHT,\n            width: (resizingConstraint & ResizingConstraint.WIDTH) === ResizingConstraint.WIDTH\n        }\n        return res\n    }\n\n    collectAChilds(sLayers, space)\n    {\n\n        var aLayers = []\n        if (undefined == sLayers)\n        {\n            exporter.logMsg(\"PZLayer:collectAChilds() empty sLayers. this.name=\" + this.name)\n        }\n        for (const sl of sLayers.filter(l => !l.hidden || l.sketchObject.hasClippingMask()))\n        {\n            //            \n            const al = new PZLayer(sl, this)\n            if (al.isGroup && !this.isImageSymbol) al.childs = al.collectAChilds(sl.layers, space + \" \")\n            aLayers.push(al)\n        }\n        return aLayers\n    }\n\n\n    addSelfAsFixedLayerToArtboad(overlayType)\n    {\n        {\n            const shadowType = exporter.Settings.layerSettingForKey(this.slayer, SettingKeys.LAYER_FIXED_SHADOW_TYPE)\n            this.keepFixedShadow = shadowType != undefined && shadowType == 1\n        }\n        this.isFixed = true\n        this.overlayType = overlayType\n        this.fixedIndex = this.artboard.fixedLayers.length\n        this.artboard.fixedLayers.push(this)\n    }\n\n    calculateFixedType()\n    {\n        let type = \"\";\n\n        if (Constants.LAYER_OVERLAY_VSCROLL == this.overlayType)\n        {\n            type = 'vscroll'\n\n\n        } else if (Constants.LAYER_OVERLAY_TRANSP_TOP == this.overlayType)\n        {\n            type = \"top\";\n        } else if (Constants.LAYER_OVERLAY_TRANSP_LEFT == this.overlayType)\n        {\n            type = \"left\";\n        } else\n            type = \"float\"\n\n        this.fixedType = type\n        this.isFloat = type == 'float'\n        if (type == 'vscroll') this.isVertScroll = type === 'vscroll'\n    }\n\n    findMaskLayer()\n    {\n        const layerIndex = this.parent.childs.indexOf(this)\n        return this.parent.childs[layerIndex - 1]\n    }\n\n\n    buildLinks(space)\n    {\n        this._processHotspots(space)\n    }\n\n    getShadowInfo()\n    {\n        if (this.slayer.style == undefined || (this.slayer.style.shadows == undefined && this.slayer.style.innerShadows == undefined)) return undefined\n\n        let shadowInfo = this.getShadowSetInfo(this.slayer.style.shadows, false) || this.getShadowSetInfo(this.slayer.style.innerShadows, true)\n        return shadowInfo\n    }\n\n\n    getShadowSetInfo(shadows, inset)\n    {\n        if (!shadows) return\n\n        let shadowInfo = undefined\n        for (var shadow of shadows.filter(s => s.enabled))\n        {\n            let res = \"\"\n            //if (res != \"\") res += \",\"\n            if (inset) res += \"inset \"\n            res += shadow.x + \"px \"\n\n            if (null != shadow.y)\n            {\n                res += \" \" + shadow.y + \"px\"\n                if (null != shadow.blur)\n                {\n                    res += \" \" + shadow.blur + \"px\"\n                    if (null != shadow.spread)\n                    {\n                        res += \" \" + shadow.spread\n                    }\n                }\n            }\n            res += \" \" + shadow.color\n\n            if (shadowInfo)\n            {\n                shadowInfo.style += \", \" + res\n            } else\n            {\n                shadowInfo = {\n                    style: res,\n                    x: shadow.x + shadow.blur ? shadow.blur : 0,\n                    layer: this\n                }\n            }\n        }\n        return shadowInfo\n    }\n\n    _processHotspots(prefix)\n    {\n        const l = this\n        const hotspots = []\n\n        let finalHotspot = {\n            r: this.frame.copy(),\n            linkType: 'undefined',\n            artboardID: null,\n            target: null,\n            overlayRedirect: this.overlayRedirect,\n            ancestorFixed: null,\n            owner:this\n        }\n        let p = this\n        while (!p.isArtboard)\n        {\n            if (p.nlayer.isFixedToViewport())\n            {\n                finalHotspot.fixedAncestorID = p.objectID\n                break\n            }\n            p = p.parent\n        }\n\n        while (true)\n        {\n\n            // check link to external URL\n            if (this.externalLinkHref != null)\n            {\n                const externalLink = {\n                    'href': this.externalLinkHref,\n                    'openNewWindow': exporter.Settings.layerSettingForKey(l.slayer, SettingKeys.LAYER_EXTERNAL_LINK_BLANKWIN) == 1\n\n                }\n                if (!this._specifyExternalURLHotspot(prefix + \" \", finalHotspot, externalLink)) return\n                break\n            }\n\n            // check native link\n            if (null != l.targetId)\n            {\n                if (!this._specifyHotspot(prefix + \" \", l, finalHotspot)) return\n                break\n            }\n\n            // No any link on layer\n            return\n        }\n        hotspots.push(finalHotspot);\n\n        // finalization\n        Array.prototype.push.apply(this.artboard.hotspots, hotspots);\n\n        if (DEBUG) exporter.logMsg(prefix + \"_processLayerLinks\")\n    }\n\n\n    _specifyHotspot(prefix, l, finalHotspot)\n    {\n        const targetArtboardID = l.targetId;\n\n        if (targetArtboardID == 'back')\n        {\n            // hande Back action\n            finalHotspot.linkType = \"back\";\n            if (DEBUG) exporter.logMsg(prefix + \"hotspot: back\")\n        } else if (targetArtboardID != null && targetArtboardID != \"\" && targetArtboardID != \"null\")\n        {\n            // hande direct link\n            let targetArtboard = pzDoc.findArtboardByID(targetArtboardID)\n\n            if (!targetArtboard)\n            {\n                exporter.logWarning(\"Broken link to missed artboard on layer '\" + l.name + \"' on artboard '\" + l.artboard.name + \"' target=\")\n                return false\n            }\n\n            if (targetArtboard.externalArtboardURL != undefined)\n            {\n                const externalLink = {\n                    'href': targetArtboard.externalArtboardURL,\n                    'openNewWindow': exporter.Settings.layerSettingForKey(targetArtboard.slayer, SettingKeys.LAYER_EXTERNAL_LINK_BLANKWIN) == 1\n                }\n                finalHotspot.artboardID = targetArtboard.objectID\n                this._specifyExternalURLHotspot(prefix + \" \", finalHotspot, externalLink)\n            } else\n            {\n                finalHotspot.linkType = \"artboard\";\n                finalHotspot.artboardID = targetArtboardID;\n                finalHotspot.href = Utils.toFilename(targetArtboard.name) + \".html\";\n            }\n\n        } else\n        {\n            return false\n        }\n        return true\n    }\n\n    _specifyExternalURLHotspot(prefix, finalHotspot, externalLink)\n    {\n        if (DEBUG) exporter.logMsg(prefix + \"_specifyExternalURLHotspothotspot: href\")\n        // found external link        \n        var href = externalLink.href\n        /*const regExp = new RegExp(\"^http(s?)://\");\n        if (!regExp.test(href.toLowerCase())) {\n            href = \"http://\" + href;\n        }*/\n        const target = externalLink.openNewWindow ? \"_blank\" : null;\n\n        finalHotspot.linkType = \"href\"\n        finalHotspot.href = href\n        finalHotspot.target = target\n\n        return true\n    }\n\n\n\n    clearRefsBeforeJSON()\n    {\n        // need to cleanup temp object to allow dump it into JSON\n        // but keep nlayer because Exporter.exportImage() needs it\n        // \n        ///\n        this.x = this.frame.x\n        this.y = this.frame.y\n        this.w = this.frame.width\n        this.h = this.frame.height\n        this.s = this.smName\n        this.l = this.styleName\n        this.b = this.sharedLib\n        if (this.keepFixedShadow) this.ks = true\n        if (this.childs.length) this.c = this.childs\n        if (!this.tp) this.tp = this.isSymbolInstance ? \"SI\" : this.slayer.type\n        if (!this.isSymbolInstance) this.n = this.name\n        if (this.slayer.hidden) this.hd = true\n        //\n        if (this.tp == \"Icon\")\n        {\n            // this.pr is already enabled\n        } else if (\"Text\" == this.slayer.type)\n        {\n            this.pr = this._buildTextPropsForJSON()\n        } else if (\"ShapePath\" == this.slayer.type || \"Shape\" == this.slayer.type)\n        {\n            this.pr = this._buildShapePropsForJSON()\n            this.tp = \"ShapePath\"\n        } else if (this.isImageSymbol)\n        {\n            this.tp = \"ImageSymbol\"\n            this.isImageSymbol = undefined\n        } else if (\"Image\" == this.slayer.type)\n        {\n            if (this.isMasked)\n            {\n                this.hd = true\n                this.isMasked = undefined\n            } else\n            {\n                this.iu = this._buildImageURL()\n            }\n        } else if (this.isVertScroll !== undefined)\n        {\n            //this.tp = \"Image\"\n            this.iu = this._buildImageURL()\n            this.vst = this.vScrollType\n            this.vScrollType = undefined\n        } else if (undefined != this.imageIndex)\n        {\n            this.tp = \"Image\"\n            this.iu = this._buildImageURL()\n        }\n        //\n        this.name = undefined\n        this.frame = undefined\n        this.width = undefined\n        this.height = undefined\n        this.constrains = undefined\n        this.smName = undefined\n        this.styleName = undefined\n        this.sharedLib = undefined\n        this.text = undefined\n        this.keepFixedShadow = undefined\n        this.childs = undefined\n        //\n        this.tempOverrides = undefined\n        this.slayer = undefined\n        //l.nlayer = undefined\n        this.customLink = undefined\n        this.nlayer = undefined\n        this.parent = undefined\n        this.artboard = undefined\n        this.objectID = undefined\n        this.isParentFixed = undefined\n        this.isArtboard = undefined\n        this.isGroup = undefined\n        this.isSymbolInstance = undefined\n        this.isLink = undefined\n        this.hotspots = undefined\n        this.targetId = undefined\n        this.imageIndex = undefined\n        this.icpn = undefined\n        this.icpi = undefined\n    }\n\n    _buildImageURL()\n    {\n        return Constants.IMAGES_DIRECTORY + Utils.toFilename(this.artboard.name, false) + \"--\" + this.imageIndex + \".\" + exporter.fileType;\n    }\n\n    _buildTextPropsForJSON()\n    {\n        this.tx = this.text\n        //\n        const pte = exporter.getTokensExporter()\n        return pte._getTextStylePropsAsText(this.slayer.style)\n    }\n\n    _buildShapePropsForJSON()\n    {\n        const pte = exporter.getTokensExporter()\n        return pte._getLayerStylePropsAsText(null, this.slayer, this.slayer.style)\n    }\n\n    _buildIconsPropsForJSON()\n    {\n        const pte = exporter.getTokensExporter()\n        if (this.parent && this.parent.slayer && this.parent.slayer.style)\n            return pte._getLayerStylePropsAsText(null, this.slayer, this.parent.slayer.style)\n        else\n            return undefined\n    }\n    _getColorVariable()\n    {\n\n        const style = this.slayer.style\n        if (!style || !style.sketchObject.primitiveTextStyle()) return undefined\n\n        // Try to find that color variables was used                \n        var attributes = style.sketchObject.primitiveTextStyle().attributes()\n        if (!attributes || !attributes.MSAttributedStringColorAttribute || !attributes.MSAttributedStringColorAttribute.swatchID) return undefined\n        var swatchID = attributes.MSAttributedStringColorAttribute.swatchID()\n        if (!swatchID) return undefined\n        //\n        var swatchInfo = pzDoc.getSwatchInfoByID(swatchID)\n        return swatchInfo\n    }\n\n    _clearColor(color)\n    {\n        // drop FF transparency as default\n        if (color.length == 9 && color.substring(7).toUpperCase() == \"FF\")\n        {\n            color = color.substring(0, 7)\n        }\n        return color.toUpperCase()\n    }\n\n    exportSiteIcon()\n    {\n        const nlayer = this.nlayer\n        const layer = this\n\n        const imageName = \"icon.png\"\n        const imagePath = exporter._outputPath + \"/resources/\" + imageName;\n\n        let slice = null\n\n        slice = MSExportRequest.exportRequestsFromExportableLayer(nlayer).firstObject();\n\n        slice.scale = 1;\n        slice.saveForWeb = false;\n        slice.format = \"png\";\n        exporter.ndoc.saveArtboardOrSlice_toFile(slice, imagePath);\n\n        /*const options = { \n            scales: [1],\n            output: imagePath,\n            overwriting: true,\n            'save-for-web': true, \n            formats: 'png' \n        }\n        Sketch.export(this.slayer, options)       */\n    }\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/PZPage.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\nSketch = require('sketch/dom')\n\nvar PZPage_touched = false\n\nclass PZPage {\n\n    // spage: ref to Sketch Page\n    constructor(sPage) {\n        this.sPage = sPage\n        this.mArtboards = []\n    }\n\n    collectData(sArtboards = null) {\n        if (DEBUG) exporter.logMsg(\"PZPage.collectData() starting... name=\" + (this.sPage ? this.sPage.name : ''))\n        // \n        if (!sArtboards) sArtboards = this.sPage.layers\n\n        // Run in sync mode, doesn't need Element Inspector data to be exported\n        if (!exporter.enabledJSON) {\n            // prepare layers for collecting\n            exporter.logMsg(\"PZPage.collectData() preparing...\")\n            for (const sa of sArtboards) {\n                if (\"Artboard\" != sa.type) continue\n                if (exporter.filterAster && sa.name.indexOf(\"*\") == 0) continue\n\n                // special trick to add some data change event to Sketch as Undo point\n                if (!PZPage_touched) {\n                    sa.frame.y += 10\n                    PZPage_touched = true\n                }\n\n                this._scanLayersToDetachSymbols(sa)\n            }\n        }\n\n        // collect layers\n        exporter.logMsg(\"PZPage.collectData() collecting...\")\n        this._collectArtboards(sArtboards)\n        exporter.logMsg(\"PZPage.collectData() collected\")\n\n    }\n\n\n    export() {\n        for (const a of this.mArtboards) {\n            a.export()\n        }\n        //// export itself\n        if (this.sPage != null) {\n            const data = {\n                'id': String(this.sPage.id),\n                name: this.sPage.name\n            }\n            exporter.storyData.groups.push(data)\n        }\n    }\n\n\n    // return index of new artboard\n    addArtboard(mArtboard) {\n        this.mArtboards.push(mArtboard)\n        mArtboard.index = pzDoc.addArtboard(mArtboard)\n    }\n\n\n    //////////////////////// PRIVATE FUNCTIONS //////////////////////////////////////\n\n    _scanLayersToDetachSymbols(sParent) {\n        if (DEBUG) exporter.logMsg(\"PZPage._scanLayersToDetachSymbols() runnning...name=\" + (this.sPage ? this.sPage.name : ''))\n        const nParent = sParent.sketchObject\n\n        const symbolPredicate = NSPredicate.predicateWithFormat(\"className == %@\", 'MSSymbolInstance');\n        const symbolInstances = nParent.children().filteredArrayUsingPredicate_(symbolPredicate);\n\n        symbolInstances.forEach(function (nl) {\n            var sl = Sketch.fromNative(nl)\n            sl = sl.detach({\n                recursively: true\n            })\n            if (DEBUG) exporter.logMsg(\"PZPage._scanLayersToDetachSymbols() symbol:\" + sl.name)\n        }, this)\n\n        if (DEBUG) exporter.logMsg(\"PZPage._scanLayersToDetachSymbols() completed\")\n    }\n\n    _collectArtboards(sArtboards) {\n        for (var sa of this._sortArtboards(sArtboards)) {\n            if (\"SymbolMaster\" == sa.type) continue\n            if (\"Artboard\" != sa.type) continue\n            if (exporter.filterAster && sa.name.indexOf(\"*\") == 0) continue\n            const ma = new PZArtboard(sa)\n            ma.collectLayers(' ')\n            this.addArtboard(ma)\n        }\n    }\n\n\n    // Resort artboards using configuration settings\n    _sortArtboards(sSrcArtboards) {\n        var sArtboards = sSrcArtboards.slice()\n        if (Constants.SORT_RULE_X == exporter.sortRule) {\n            sArtboards.sort((\n                function (a, b) {\n                    return a.frame.x != b.frame.x ? (a.frame.x - b.frame.x) : (a.frame.y - b.frame.y)\n                }))\n        } else if (Constants.SORT_RULE_Y == exporter.sortRule) {\n            sArtboards.sort((\n                function (a, b) {\n                    return a.frame.y != b.frame.y ? (a.frame.y - b.frame.y) : (a.frame.x - b.frame.x)\n                }))\n        } else if (Constants.SORT_RULE_REVERSIVE_SKETCH == exporter.sortRule) {\n            sArtboards = sArtboards.reverse()\n        } else {\n        }\n        return sArtboards\n    }\n\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/exporter-run.js",
    "content": "@import \"constants.js\"\n@import \"exporter/exporter.js\"\n@import \"lib/uidialog.js\"\n@import \"lib/uipanel.js\"\n@import \"lib/utils.js\"\n\n\nconst Settings = require('sketch/settings')\nconst UI = require('sketch/ui')\nconst Dom = require('sketch/dom')\n\n\nlet exportInfo = {\n    timeout: undefined,\n    panel: undefined\n}\n\nfunction closePanel()\n{\n    if (exportInfo.panel != undefined)\n    {\n        exportInfo.panel.finish()\n        exportInfo.panel = undefined\n    }\n    if (exportInfo.timeout != undefined)\n    {\n        exportInfo.timeout.cancel() // fibers takes care of keeping coscript around\n        exportInfo.timeout = undefined\n    }\n    coscript.setShouldKeepAround(false)\n}\n\nfunction panelSwitchFinished()\n{\n    exportInfo.panel.addButton(\"cancel\", \"  Ok   \", function ()\n    {\n        closePanel()\n    })\n}\n\nfunction openBrowser(currentPath, doc)\n{\n    const docName = doc.sketchObject.cloudName() + \"\"\n    const openPath = currentPath + \"/\" + docName + \"/\"\n    const fullPath = \"\" + openPath + (openPath.endsWith('/') ? '' : '/') + 'index.html'\n    NSWorkspace.sharedWorkspace().openFile(fullPath);\n}\n\nfunction exportHTML(currentPath, nDoc, exportOptions, context)\n{\n    let fromCmd = ('fromCmd' in exportOptions) && exportOptions.fromCmd\n\n    const currentPage = exportOptions.currentPage || nDoc.currentPage()\n    new Exporter(currentPath, nDoc, currentPage, exportOptions, context);\n\n    let exportedOk = false\n    if (fromCmd)\n    {\n        exportedOk = exporter.exportArtboards()\n        track(TRACK_EXPORT_COMPLETED)\n    } else\n    {\n        let panel = new UIPanel(\"Exporting to HTML\")\n        exportInfo.panel = panel\n        panel.addLabel(\"\", \"Please wait...\")\n        panel.show()\n\n        // export HTML  \n        coscript.setShouldKeepAround(true)\n\n        exportInfo.timeout = coscript.scheduleWithInterval_jsFunction(1, function ()\n        {\n\n            // Exporting...\n            exportedOk = exporter.exportArtboards()\n            // \n            //panelSwitchFinished()\n            closePanel()\n            track(TRACK_EXPORT_COMPLETED)\n\n            // show final message\n            if (exporter.errors.length > 0)\n            {\n                UI.alert('Export failed with errors', exporter.errors.join(\"\\n\\n\"))\n            } else if (false && exporter.warnings.length > 0)\n            {\n                UI.alert('Export completed with warnings', exporter.warnings.join(\"\\n\\n\"))\n            } else\n            {\n                UI.message('HTML exported.')\n                // open HTML in browser \n                if (!exportOptions.dontOpenBrowser)\n                {\n                    openBrowser(currentPath, Dom.fromNative(nDoc))\n                }\n            }\n        })\n    }\n}\n\n\nfunction saveDocumentAs(document, filePath)\n{\n    if (DEBUG) log(\" SAVING DOCUMENT TO \" + filePath)\n\n    var newFileURL = NSURL.fileURLWithPath(filePath)\n    document.sketchObject.writeToURL_ofType_forSaveOperation_originalContentsURL_error_(newFileURL, \"com.bohemiancoding.sketch.drawing\",\n        NSSaveOperation, nil, nil);\n}\n\nfunction asyncExportHTML(context, doc, exportOptions)\n{\n    // Clone current doc to temp file\n    const docName = doc.sketchObject.cloudName() + \"\"\n    const tempDir = Utils.getPathToTempFolder()\n    const tempFile = tempDir + \"/\" + \"tmp\" + \".sketch\"\n    saveDocumentAs(doc, tempFile)\n\n    const fileManager = NSFileManager.defaultManager()\n    const scriptName = \"export.sh\"\n\n    const scriptPath = context.plugin.url().URLByAppendingPathComponent(\"Contents\")\n        .URLByAppendingPathComponent(\"Sketch\").URLByAppendingPathComponent(\"scripts\")\n        .URLByAppendingPathComponent(scriptName)\n\n    const newContext = {\n        file: tempFile,\n        name: docName,\n        commands: \"export,close\",\n        async: true,\n        mode: exportOptions.mode,\n        currentPageID: exportOptions.currentPage != undefined ? (exportOptions.currentPage.id + \"\") : undefined,\n    }\n\n    if (exportOptions.customArtboardHeight != \"\" && exportOptions.customArtboardWidth != \"\")\n    {\n        const customArtboardHeight = parseInt(exportOptions.customArtboardHeight)\n        const customArtboardWidth = parseInt(exportOptions.customArtboardWidth)\n        if (!isNaN(customArtboardHeight) && !isNaN(customArtboardWidth))\n        {\n            newContext.customArtboardHeight = customArtboardHeight\n            newContext.customArtboardWidth = customArtboardWidth\n        }\n    }\n\n    // Prepare options to send\n    if (exportOptions.mode === undefined)\n    {\n    } else if (exportOptions.mode === Constants.EXPORT_MODE_SELECTED_ARTBOARDS)\n    {\n        const ids = exportOptions[\"selectedArtboards\"].map(a => a.id).join(\",\")\n        newContext.selectedArtboardIDS = ids\n    }\n    const newContextStr = JSON.stringify(newContext)\n\n    // Run other Sketch instance to export    \n    //const result = Utils.runCommand('/bin/bash', [scriptPath, tempDir, tempFile, docName, Utils.escapeDoudleQuote(JSON.stringify(exportOptions))], true)\n    const result = Utils.runCommand('/bin/bash', [scriptPath, tempDir, tempFile, newContextStr], true)\n    return result\n}\n\nfunction runExporter(context, exportOptions = null)\n{\n\n    if (exportOptions === null)\n    {\n        exportOptions = {\n            cmd: 'exportHTML'\n        }\n    }\n\n    const nDoc = exportOptions.nDoc ? exportOptions.nDoc : context.document\n    const doc = Dom.fromNative(nDoc)\n    const Settings = require('sketch/settings')\n\n    let fromCmd = ('fromCmd' in exportOptions) && exportOptions.fromCmd\n\n    const isCmdExportToHTML = exportOptions['cmd'] == \"exportHTML\"\n    var dontOpenBrowser = Settings.settingForKey(SettingKeys.PLUGIN_DONT_OPEN_BROWSER) == 1\n    var customWidth = Utils.getPluginSetting(SettingKeys.PLUGIN_EXPORT_CUSTOM_WIDTH) \n    var customHeight =Utils.getPluginSetting(SettingKeys.PLUGIN_EXPORT_CUSTOM_HEIGHT)\n    var askSize = Settings.settingForKey(SettingKeys.PLUGIN_ASK_CUSTOM_SIZE) == 1\n    var compress = Settings.settingForKey(SettingKeys.PLUGIN_COMPRESS) == 1\n\n\n    // ask for output path\n    let currentPath = context.currentPath ? context.currentPath : Settings.settingForKey(SettingKeys.PLUGIN_EXPORTING_URL)\n    if (currentPath == null)\n    {\n        // check legacy settings\n        currentPath = Settings.documentSettingForKey(doc, SettingKeys.DOC_EXPORTING_URL)\n        if (currentPath == null)\n            currentPath = ''\n\n    }\n\n    if (!fromCmd)\n    {\n        UIDialog.setUp(context);\n\n        const dialog = new UIDialog(\"Export to HTML\", NSMakeRect(0, 0, 500, 100 + (askSize ? 100 : 0)), \"Export\")\n        dialog.removeLeftColumn()\n\n\n        dialog.addPathInput({\n            id: \"path\", label: \"Destination folder\", labelSelect: \"Select Folder\",\n            textValue: currentPath,\n            inlineHint: 'e.g. ~/HTML', width: 450\n        })\n        dialog.addCheckbox(\"open\", \"Open HTML in browser\", !dontOpenBrowser)\n        if (askSize)\n        {\n            dialog.addTextInput(\"customWidth\", \"Artboard custom width (px)\", customWidth, 'e.g. 1920')\n            dialog.addTextInput(\"customHeight\", \"Artboard custom height (px)\", customHeight, 'e.g. 1080')\n        }\n\n        track(TRACK_EXPORT_DIALOG_SHOWN)\n        while (true)\n        {\n            const result = dialog.run()\n            if (!result)\n            {\n                track(TRACK_EXPORT_DIALOG_CLOSED, { \"cmd\": \"cancel\" })\n                return false\n            }\n\n            if (askSize)\n            {\n                customWidth = dialog.views['customWidth'].stringValue()\n                if (customWidth != '')\n                {\n                    if (isNaN(customWidth)) continue\n                } else\n                    customWidth = ''\n\n                customHeight = dialog.views['customHeight'].stringValue()\n                if (customHeight != '')\n                {\n                    if (isNaN(customHeight)) continue\n                } else\n                    customHeight = ''\n\n            }\n\n            currentPath = dialog.views['path'].stringValue() + \"\"\n            if (currentPath == \"\") continue\n\n            dontOpenBrowser = dialog.views['open'].state() != 1\n            compress = false //dialog.views['compress'].state() == 1\n\n\n            break\n        }\n        dialog.finish()\n        track(TRACK_EXPORT_DIALOG_CLOSED, { \"cmd\": \"ok\" })\n\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORTING_URL, currentPath)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_DONT_OPEN_BROWSER, dontOpenBrowser)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_COMPRESS, compress)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_CUSTOM_WIDTH, customWidth)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_CUSTOM_HEIGHT, customHeight)\n\n        exportOptions.dontOpenBrowser = dontOpenBrowser\n        exportOptions.compress = compress\n        if(askSize){\n            if (customHeight !== \"\") exportOptions.customArtboardHeight = customHeight\n            if (customWidth !== \"\") exportOptions.customArtboardWidth = customWidth\n        }\n\n        // Export in background        \n        var enabledJSON = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_INSPECTOR) != 1\n        if (enabledJSON)\n        {\n            const result = asyncExportHTML(context, doc, exportOptions)\n            if (result.result == 1)\n            {\n                if (!dontOpenBrowser) openBrowser(currentPath, doc)\n            } else\n            {\n                UI.alert('Export failed with errors', result.output)\n            }\n            return\n        }\n    }\n\n\n    exportHTML(currentPath, nDoc, exportOptions, context)\n\n};\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/exporter.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\n@import(\"lib/ga.js\")\n@import(\"pp-viewer/exporter/exporter-build-html.js\")\n@import(\"exporter/PZLayer.js\")\n@import(\"exporter/PZArtboard.js\")\n@import(\"exporter/PZPage.js\")\n@import(\"exporter/PZDoc.js\")\n@import(\"exporter/publisher.js\") // we need it to run resize.sh script\n@import(\"tokens/DSExporter.js\")\n\nvar exporter = undefined\n\n\nclass Exporter\n{\n\n    constructor(selectedPath, ndoc, page, exportOptions, context)\n    {\n        this.Settings = require('sketch/settings');\n        this.Sketch = require('sketch/dom');\n        this.ndoc = ndoc\n        this.doc = this.Sketch.fromNative(ndoc)\n        this.page = page;\n        this.context = context;\n        this.customArtboardFrame = undefined\n        this.enableTransitionAnimation = false\n        this.siteIconLayer = undefined\n        this.myLayers = []\n        this.errors = []\n        this.warnings = []\n\n        if (context['async'])\n        {\n            this.docName = context[\"name\"] + \"\"\n        } else\n        {\n            // workaround for Sketch 52s\n            this.docName = this._clearCloudName(this.ndoc.cloudName() + \"\")\n            let posSketch = this.docName.indexOf(\".sketch\")\n            if (posSketch > 0)\n            {\n                this.docName = this.docName.slice(0, posSketch)\n            }\n            // @workaround for Sketch 52\n        }\n\n        this.initPaths(selectedPath)\n\n        this.exportOptions = exportOptions\n        this._readSettings()\n\n        this.filterAster = null == this.exportOptions || !('mode' in this.exportOptions) || Constants.EXPORT_MODE_SELECTED_ARTBOARDS != this.exportOptions.mode\n\n        // init global variable\n        exporter = this\n    }\n\n    _readSettings()\n    {\n        if (this.exportOptions.customArtboardWidth > 0 && this.exportOptions.customArtboardHeight > 0)\n        {\n            this.customArtboardFrame = new Rectangle(0, 0\n                , parseInt(this.exportOptions.customArtboardWidth, 10)\n                , parseInt(this.exportOptions.customArtboardHeight, 10)\n            )\n        }\n\n        this.retinaImages = this.Settings.settingForKey(SettingKeys.PLUGIN_DONT_RETINA_IMAGES) != 1\n        this.enabledJSON = this.Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_INSPECTOR) != 1\n        this.disableFixedLayers = this.customArtboardFrame || this.Settings.documentSettingForKey(this.doc, SettingKeys.DOC_DISABLE_FIXED_LAYERS) == 1\n\n        let pluginSortRule = this.Settings.settingForKey(SettingKeys.PLUGIN_SORT_RULE)\n        if (undefined == pluginSortRule) pluginSortRule = Constants.SORT_RULE_X\n        const docCustomSortRule = this.Settings.documentSettingForKey(this.doc, SettingKeys.DOC_CUSTOM_SORT_RULE)\n        this.sortRule = undefined == docCustomSortRule || docCustomSortRule < 0 ? pluginSortRule : docCustomSortRule\n\n        let fontSizeFormat = this.Settings.settingForKey(SettingKeys.PLUGIN_FONTSIZE_FORMAT)\n        const docCustomFontSize = this.Settings.documentSettingForKey(this.doc, SettingKeys.DOC_CUSTOM_FONTSIZE_FORMAT)\n        if (undefined != docCustomFontSize && docCustomFontSize != 0) fontSizeFormat = docCustomFontSize\n        this.fontSizeFormat = undefined != fontSizeFormat ? fontSizeFormat - 1 : Constants.FONT_SIZE_FORMAT_SKETCH\n\n        let backColor = this.Settings.documentSettingForKey(this.doc, SettingKeys.DOC_BACK_COLOR)\n        if (undefined == backColor) backColor = \"\"\n        this.backColor = backColor\n\n        let serverTools = this.Settings.settingForKey(SettingKeys.PLUGIN_SERVERTOOLS_PATH)\n        if (serverTools == undefined) serverTools = ''\n        this.serverTools = serverTools\n\n        let fileType = Settings.settingForKey(SettingKeys.PLUGIN_FILETYPE)\n        if (fileType == undefined || fileType == \"\") fileType = \"PNG\"\n        this.fileType = fileType.toLowerCase()\n\n        // To know do we need full-size images or not\n        const miroEnabled = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_MIRO_ENABLED) == 1\n        this.exportFullImages = miroEnabled || true\n\n        this.ignoreLibArtboards = this.Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_LIB_ARTBOARDS) == 1\n\n    }\n\n    getManifest()\n    {\n        var manifestPath = this.context.plugin.url().URLByAppendingPathComponent(\"Contents\").URLByAppendingPathComponent(\"Sketch\").URLByAppendingPathComponent(\"manifest.json\").path()\n        return NSJSONSerialization.JSONObjectWithData_options_error(NSData.dataWithContentsOfFile(manifestPath), 0, nil)\n\n    }\n\n\n    logMsg(msg)\n    {\n        const d = new Date()\n        log(d.getHours() + \":\" + d.getMinutes() + \".\" + d.getSeconds() + \" \" + msg)\n    }\n\n\n    logWarning(text)\n    {\n        this.logMsg(\"[ WARNING ] \" + text)\n        this.warnings.push(text)\n    }\n\n    logError(error)\n    {\n        this.logMsg(\"[ ERROR ] \" + error)\n        this.errors.push(error)\n    }\n\n    stopWithError(error)\n    {\n        const UI = require('sketch/ui')\n        UI.alert('Error', error)\n        exit = true\n    }\n\n    _clearCloudName(cloudName)\n    {\n        let name = cloudName\n        let posSketch = name.indexOf(\".sketch\")\n        if (posSketch > 0)\n        {\n            name = name.slice(0, posSketch)\n        }\n        return name\n    }\n\n    getTokensExporter()\n    {\n        if (undefined == this.tokensExporter)\n        {\n            this.tokensExporter = new DSExporter(this.context)\n            this.tokensExporter.initForPublisher()\n        }\n        return this.tokensExporter\n    }\n\n    prepareFilePath(filePath, fileName)\n    {\n        const fileManager = NSFileManager.defaultManager();\n        const targetPath = filePath + '/' + fileName;\n\n        let error = MOPointer.alloc().init();\n        if (!fileManager.fileExistsAtPath(filePath))\n        {\n            if (!fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(filePath, true, null, error))\n            {\n                this.logError(\"prepareFilePath(): Can't create directory '\" + filePath + \"'. Error: \" + error.value().localizedDescription());\n                return undefined\n            }\n        }\n\n        if (fileManager.fileExistsAtPath(targetPath))\n        {\n            if (!fileManager.removeItemAtPath_error(targetPath, error))\n            {\n                this.logError(\"prepareFilePath(): Can't remove old directory '\" + targetPath + \"'. Error: \" + error.value().localizedDescription());\n                return undefined\n            }\n        }\n        return targetPath\n    }\n\n\n    copyStatic(resFolder)\n    {\n        const fileManager = NSFileManager.defaultManager();\n        const targetPath = this.prepareFilePath(this._outputPath, resFolder);\n        if (undefined == targetPath) return false\n\n        const sourcePath = this.context.plugin.url().URLByAppendingPathComponent(\"Contents\").URLByAppendingPathComponent(\"Sketch\")\n            .URLByAppendingPathComponent(\"pp-viewer\").URLByAppendingPathComponent(\"viewer\").URLByAppendingPathComponent(resFolder)\n        //const sourcePath = this.context.plugin.url().URLByAppendingPathComponent(\"Contents\").URLByAppendingPathComponent(\"Sketch\").URLByAppendingPathComponent(resFolder).path();        \n\n        let error = MOPointer.alloc().init();\n        if (!fileManager.copyItemAtPath_toPath_error(sourcePath, targetPath, error))\n        {\n            this.logMsg(error.value().localizedDescription());\n            return this.logError(\"copyStatic(): Can't copy '\" + sourcePath + \"' to directory '\" + targetPath + \"'. Error: \" + error.value().localizedDescription());\n        }\n\n        return true\n    }\n\n    startStoryData()\n    {\n        const disableHotspots = this.Settings.settingForKey(SettingKeys.PLUGIN_DISABLE_HOTSPOTS) == 1\n\n        var ownerName = Utils.getDocSetting(this.ndoc, SettingKeys.DOC_OWNER_NAME)\n        if ('' == ownerName) ownerName = Utils.getPluginSetting(SettingKeys.PLUGIN_AUTHOR_NAME)\n        var ownerEmail = Utils.getDocSetting(this.ndoc, SettingKeys.DOC_OWNER_EMAIL)\n        if ('' == ownerEmail) ownerEmail = Utils.getPluginSetting(SettingKeys.PLUGIN_AUTHOR_EMAIL)\n\n        this.storyData = {\n            docName: Utils.toFilename(this.docName),\n            docPath: \"P_P_P\",\n            docVersion: ExporterConstants.DOCUMENT_VERSION_PLACEHOLDER,\n            ownerName: ownerName,\n            ownerEmail: ownerEmail,\n            authorName: ExporterConstants.DOCUMENT_AUTHOR_NAME_PLACEHOLDER,\n            authorEmail: ExporterConstants.DOCUMENT_AUTHOR_EMAIL_PLACEHOLDER,\n            commentsURL: ExporterConstants.DOCUMENT_COMMENTS_URL_PLACEHOLDER,\n            hasRetina: this.retinaImages,\n            serverToolsPath: this.serverTools,\n            fontSizeFormat: this.fontSizeFormat,\n            fileType: this.fileType,\n            disableHotspots: disableHotspots,\n            zoomEnabled: this.Settings.settingForKey(SettingKeys.PLUGIN_DISABLE_ZOOM) != 1,\n            title: this.docName,\n            layersExist: this.enabledJSON,\n            centerContent: false, // because too many issues\n            highlightLinks: false,\n            pages: [],\n            groups: []\n        }\n        //\n    }\n\n\n    createMainHTML()\n    {\n        const pluginVer = exporter.getManifest().version\n        const buildOptions = {\n            docName: this.docName,\n            serverTools: this.serverTools,\n            backColor: this.backColor,\n            enableExpViewer: this.storyData.experimentalExisting,\n            centerContent: false, // because too many issues\n            loadLayers: this.enabledJSON,\n            cssFileNames: this.enabledJSON ? this.mDoc.getCSSIncludes() : undefined,\n            enableAnimations: this.enableTransitionAnimation,\n            generatorText: \"Generated by Puzzle Publisher \" + pluginVer + \" plugin for Sketch.app - https://github.com/ingrammicro/puzzle-publisher\"\n        }\n\n        const docHideNav = this.Settings.documentSettingForKey(this.doc, SettingKeys.DOC_CUSTOM_HIDE_NAV)\n        buildOptions.hideNav = docHideNav == undefined || docHideNav == 0 ? this.Settings.settingForKey(SettingKeys.PLUGIN_HIDE_NAV) == 1 : docHideNav == 2\n\n        let googleCode = this.Settings.settingForKey(SettingKeys.PLUGIN_GOOGLE_CODE)\n        if (googleCode == undefined) googleCode = ''\n        buildOptions.googleCode = googleCode\n        let jsCode = this.Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_JS_CODE)\n        if (jsCode == undefined) jsCode = ''\n        buildOptions.jsCode = jsCode\n\n        if (\"\" == buildOptions.backColor) buildOptions.backColor = Constants.DEF_BACK_COLOR\n\n        const s = buildMainHTML(buildOptions);\n\n        const filePath = this.prepareFilePath(this._outputPath, 'index.html');\n        if (undefined == filePath) return false\n\n        Utils.writeToFile(s, filePath);\n        return true\n    }\n\n\n\n    compressImages()\n    {\n        if (!this.exportOptions.compress) return true\n\n        this.logMsg(\" compressImages: running...\")\n        const pub = new Publisher(this.context, this.ndoc);\n        pub.copyScript(\"compress2.sh\")\n        var url = pub.context.plugin.urlForResourceNamed('advpng').path()\n        const res = pub.runScriptWithArgs(\"compress2.sh\", [this.imagesPath, url])\n        if (!res.result)\n        {\n            this.logMsg(\" compressImages: failed!\")\n        } else\n            this.logMsg(\" compressImages: done!\")\n\n        pub.showOutput(res)\n    }\n\n    buildPreviews()\n    {\n        this.logMsg(\" buildPreviews: running...\")\n        // WE NEED THE FOLLOWING DUMMY CODE TO GET UNDO CHANGES ( see PZDoc.undoChanges() )\n        const pub = new Publisher(this.context, this.ndoc);\n        let args = [\"-Z\", \"300\", \"fileName\", \"--out\", this.imagesPath + \"previews/\"]\n        let res = pub.runToolWithArgs(\"/usr/bin/sips\", args)\n\n        this.logMsg(\" buildPreviews: done!!!!!\")\n    }\n\n    createDestFile(fileName, folder = Constants.DEST_PROTO_DIRECTORY)\n    {\n        return this.prepareFilePath(this._outputPath + \"/\" + folder, fileName);\n    }\n\n    // result: true OR false\n    finishSaveStoryData()\n    {\n        const iFrameSizeSrc = this.Settings.settingForKey(SettingKeys.PLUGIN_SHARE_IFRAME_SIZE)\n        let iFrameSize = undefined\n        if (iFrameSizeSrc != undefined && iFrameSizeSrc != '')\n        {\n            const size = iFrameSizeSrc.split(':')\n            if (2 == size.length)\n            {\n                iFrameSize = {\n                    width: size[0],\n                    height: size[1]\n                }\n            }\n        }\n\n        this.storyData['startPageIndex'] = this.mDoc.startArtboardIndex\n        this.storyData['totalImages'] = this.mDoc.totalImages\n        if (undefined != iFrameSize)\n        {\n            this.storyData['iFrameSizeWidth'] = iFrameSize.width\n            this.storyData['iFrameSizeHeight'] = iFrameSize.height\n        }\n        if (\"\" != this.backColor)\n        {\n            this.storyData['backColor'] = this.backColor\n        }\n\n        // Convert data to JSON\n        let jsStory = \"var story = \" + JSON.stringify(this.storyData, null, ' ')\n\n        // And save it\n        const pathStoryJS = this.createDestFile('story.js')\n        if (undefined == pathStoryJS) return false\n        Utils.writeToFile(jsStory, pathStoryJS)\n        return true\n    }\n\n\n\n    exportArtboards()\n    {\n        this.logMsg(\"exportArtboards: running...\")\n\n        // Prepare folders\n        this.prepareOutputFolder()\n\n        // Copy static files\n        if (!this.copyStatic(\"resources\")) return false\n        if (!this.copyStatic(\"js\")) return false\n\n        this.mDoc = new PZDoc()\n        try\n        {\n            // Collect layers information\n            this.mDoc.collectData()\n            this.mDoc.buildLinks()\n\n            // Build Story.js with hotspots  \n            this.startStoryData();\n\n            // Export every artboard into image\n            this.mDoc.export()\n\n            // Dump document layers to JSON file\n            this.saveToJSON()\n\n            // Build main HTML file\n            if (!this.createMainHTML()) return false\n\n            if (!this.finishSaveStoryData()) return false\n\n            // Compress Images\n            this.compressImages()\n\n            // Build image small previews for Gallery\n            this.buildPreviews()\n\n            // Save site icon\n            if (this.siteIconLayer != undefined)\n            {\n                this.siteIconLayer.exportSiteIcon()\n            }\n\n        }\n        catch (error)\n        {\n            this.logError(error)\n        }\n        finally\n        {\n            if (!exporter.context['async'])\n            {\n                if (DEBUG || true) exporter.logMsg(\"exportArtboards: undo changes\")\n                this.mDoc.undoChanges()\n            }\n\n        }\n\n        this.logMsg(\"exportArtboards: done!\")\n\n        return true\n    }\n\n    saveToJSON()\n    {\n        if (!this.enabledJSON) return true\n\n        const symbolData = this.mDoc.getSymbolData()\n        const json = this.mDoc.getJSON()\n\n        this.storyData.experimentalExisting = json.includes(\"EXPERIMENTAL\")\n\n        const pathJSFile = this.createDestFile('handoff.js')\n        if (!Utils.writeToFile(symbolData + \"var layersData = \" + json, pathJSFile)) return false\n\n    }\n\n\n    initPaths(selectedPath)\n    {\n        this._outputPath = selectedPath + \"/\" + this.docName\n        this.imagesPath = this._outputPath + \"/\" + Constants.IMAGES_DIRECTORY;\n        this.fullImagesPath = this.imagesPath + Constants.FULLIMAGE_DIRECTORY\n        this.previewsImagePath = this.imagesPath + Constants.PREVIEWS_DIRECTORY\n    }\n\n\n    prepareOutputFolder()\n    {\n        Utils.deleteFile(this._outputPath)\n\n        function createSubfolder(path)\n        {\n            let error = MOPointer.alloc().init();\n            const fileManager = NSFileManager.defaultManager();\n            if (!fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(path, false, null, error))\n            {\n                exporter.logMsg(error.value().localizedDescription());\n            }\n        }\n\n        createSubfolder(this.previewsImagePath)\n        createSubfolder(this.fullImagesPath)\n\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/exporter/publisher.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\n@import(\"lib/ga.js\")\n@import(\"lib/uidialog.js\")\n\n@import \"miro/api.js\";\n@import \"miro/utils.js\";\n\nlet publisher = null\n\nApi.prototype.artboardsToPNG = function (context, exportAll, scale)\n{\n    return publisher.miroExportInfoList\n}\n\nclass Publisher\n{\n    constructor(context, doc)\n    {\n        this.doc = doc;\n        this.UI = require('sketch/ui')\n        this.context = context;\n        this.Settings = require('sketch/settings');\n\n        this.login = ''\n        this.sshPort = ''\n        this.siteRoot = ''\n        this.ver = ''\n        this.remoteFolder = ''\n\n        this.allMockupsdDir = Utils.getPluginSetting(SettingKeys.PLUGIN_EXPORTING_URL, '1')\n        this.serverToolsPath = Utils.getPluginSetting(SettingKeys.PLUGIN_SERVERTOOLS_PATH)\n        this.authorName = Utils.getPluginSetting(SettingKeys.PLUGIN_AUTHOR_NAME)\n        this.authorEmail = Utils.getPluginSetting(SettingKeys.PLUGIN_AUTHOR_EMAIL)\n        this.commentsURL = Utils.getPluginSetting(SettingKeys.PLUGIN_COMMENTS_URL)\n\n        this.curlPath = Utils.getPluginSetting(SettingKeys.PLUGIN_PUBLISH_CURL_PATH)\n        if(this.curlPath===\"\") this.curlPath = Constants.CURL_PATH\n        this.filesChunkLimit = 50\n\n        this.docFolder = this.doc.cloudName();\n        let posSketch = this.docFolder.indexOf(\".sketch\")\n        if (posSketch > 0)\n        {\n            this.docFolder = this.docFolder.slice(0, posSketch)\n        }\n\n        this.message = Utils.getPluginSetting(SettingKeys.PLUGIN_PUBLISH_LAST_MSG)\n        publisher = this\n\n        this.story = null\n        this.mockupsPath = this.allMockupsdDir + \"/\" + this.docFolder\n        this.fullImagesPath = this.mockupsPath + \"/\" + Constants.IMAGES_DIRECTORY + Constants.FULLIMAGE_DIRECTORY\n\n        this.miroExportInfoList = []\n        this.miroEnabled = null\n\n        this.userID = Utils.getUserID()\n    }\n\n\n    readOptions()\n    {\n        // read current version from document settings\n        let Settings = this.Settings\n\n        this.ver = Settings.documentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_VERSION)\n        if (this.ver == undefined || this.ver == null) this.ver = '1'\n\n        this.login = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_LOGIN)\n        if (this.login == undefined || this.login == null) this.login = ''\n\n        this.sshPort = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SSH_PORT)\n        if (this.sshPort == undefined || this.sshPort == null || this.sshPort == '') this.sshPort = '22'\n\n        this.siteRoot = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SITEROOT)\n        if (this.siteRoot == undefined || this.siteRoot == null) this.siteRoot = ''\n\n        this.secret = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SECRET)\n        if (this.secret == undefined || this.secret == null) this.secret = ''\n\n        this.remoteFolder = Settings.documentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_REMOTE_FOLDER)\n        if (this.remoteFolder == undefined || this.remoteFolder == null) this.remoteFolder = ''\n\n        this.miroEnabled = null == this.miroEnabled ? Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_MIRO_ENABLED) == 1 : this.miroEnabled\n        this.miroBoards = null\n        if (this.miroEnabled)\n        {\n            this.miroBoardName = Utils.getDocSetting(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARD)\n            this.oldMiroBoardName = this.miroBoardName\n            this.miroBoardID = Utils.getDocSetting(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARDID)\n            if ((\"\" == this.miroBoardID || \"\" == this.miroBoardName) && (this.miroBoardID + this.miroBoardName) != '')\n            {\n                this._initMiro()\n                this._validateMiroParams()\n            }\n        }\n\n        this.authorName = Settings.settingForKey(SettingKeys.PLUGIN_AUTHOR_NAME)\n        if (this.authorName == undefined || this.authorName == '') this.authorName = 'None'\n        this.authorEmail = Settings.settingForKey(SettingKeys.PLUGIN_AUTHOR_EMAIL)\n        if (this.authorEmail == undefined || this.authorEmail == '') this.authorEmail = 'None'\n\n        this.commentsURL = Settings.settingForKey(SettingKeys.PLUGIN_COMMENTS_URL)\n        if (this.commentsURL == undefined) this.commentsURL = ''\n\n        ///        \n        //\n        return true\n    }\n\n    log(msg)\n    {\n        //log(msg)\n    }\n\n    publish()\n    {\n        this.readOptions()\n\n        if (!this.checkMockupExists(this.allMockupsdDir, this.docFolder))\n        {\n            return false\n        }\n\n        // Show this.UI\n        if (!this.context.fromCmd)\n        {\n            while (true)\n            {\n                if (!this.askOptions()) return false\n                if (this.checkOptions()) break\n            }\n        }\n\n        const version = this.ver\n        let destFolder = this.remoteFolder\n        if ('' == destFolder) return true\n        // drop trailed /\n        destFolder = destFolder.replace(/(\\/)$/, \"\")\n\n\n        // copy publish script\n        if (!this.copyScript(\"publish.sh\")) return false\n        if (!this.copyScript(\"preparePublish.sh\")) return false\n        \n\n        // \n        if (this.miroEnabled && this.miroBoardID != \"\")\n        {\n            this.publishToMiro()\n        }\n\n        this.Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_LAST_MSG, this.message)\n\n        // run publish script\n        this.tempFolder = this.allMockupsdDir+\"/_tmp/\"+this.docFolder\n        log(\"tempFolder=\"+this.tempFolder)\n\n        let commentsID = destFolder\n        commentsID = Utils.toFilename(commentsID)\n        let runResult = this.runPreparationScript(version, this.allMockupsdDir, this.docFolder, destFolder, commentsID)\n        if (!runResult.result){\n            this.showMessage(runResult)\n            return false\n        }\n        if(this.login!==\"\")\n            runResult = this.runPublishScript(version, this.allMockupsdDir, this.docFolder, destFolder, commentsID)\n        else\n            runResult = this.publishMockupsByHTTPS(destFolder, commentsID)\n\n        track(TRACK_PUBLISH_COMPLETED)\n        // success\n        if (runResult.result)\n        {\n            const openURL = this.siteRoot + destFolder + (version == \"-1\" ? \"\" : (\"/\" + version)) + \"/index.html\"\n            const announceFolder = destFolder + (version == \"-1\" ? \"\" : (\"/\" + version))\n\n            // save changed document\n            log(\" SAVING DOCUMENT...\")\n            const Dom = require('sketch/dom')\n            const jDoc = Dom.fromNative(this.doc)\n            jDoc.save(err =>\n            {\n                if (err)\n                {\n                    log(\" Failed to save a document. Error: \" + err)\n                }\n            })\n            // inform server about new version\n            if (this.message != \"--\" && this.serverToolsPath != \"\")\n            {\n                try\n                {\n                    var url = this.siteRoot + this.serverToolsPath + Constants.SERVER_ANNOUNCE_SCRIPT\n                    url += \"?author=\" + encodeURI(this.authorName).replace(/[#]/g, '')\n                    if (\"\" != this.authorEmail) url += \"&email=\" + encodeURI(this.authorEmail).replace(/[#]/g, '')\n                    if (\"\" != this.secret) url += \"&sec=\" + encodeURI(this.secret).replace(/[#]/g, '')\n                    url += \"&msg=\" + encodeURI(this.message).replace(/[#]/g, '')\n                    url += \"&ver=\" + encodeURI(this.ver).replace(/[#]/g, '')\n                    url += \"&dir=\" + encodeURI(announceFolder).replace(/[#]/g, '')\n                    if (this.message.includes('--NOTELE'))\n                    {\n                        url += \"&NOTELE=1\"\n                    }\n                    if (DEBUG)\n                    {\n                        log(url)\n                    }\n                    var nURL = NSURL.URLWithString(url);\n                    var data = NSData.dataWithContentsOfURL(nURL);\n\n                    //var json = NSJSONSerialization.JSONObjectWithData_options_error(data, 0, nil)\n                    //log(json)\n\n                } catch (e)\n                {\n                    log(\"Exception: \" + e);\n                }\n            }\n            if (!this.context.fromCmd)\n            {\n                // open browser                \n                if (this.siteRoot != '')\n                {\n                    const openResult = Utils.runCommand('/usr/bin/open', [openURL])\n                    log(\" OPENING PUBLISHED PAGE...\")\n                    if (openResult.result)\n                    {\n                    } else\n                    {\n                        this.UI.alert('Can not open HTML in browser', openResult.output)\n                    }\n                }\n                this.Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_LAST_MSG, \"\")\n                this.showMessage(runResult)\n            }\n        } else\n        {\n            this.showMessage(runResult)\n            return false\n        }\n\n        return true\n    }\n\n    publishToMiro(standalone = false)\n    {\n        if (standalone)\n        {\n            this.miroEnabled = true\n            this.readOptions()\n        }\n        if (standalone && !this.askMiroOptions()) return false\n\n\n        try\n        {\n            log(\"publishToMiro: start\")\n\n            // Load story.js file and eval it\n            const storyPath = this.mockupsPath + \"/data/story.js\"\n            let storyJS = Utils.readFile(storyPath)\n            if (undefined == storyJS)\n            {\n                this.UI.alert('Error', \"Can't find mockups on path: \" + this.mockupsPath)\n                return false\n            }\n            String.prototype.replaceAllMe = function (search, replacement)\n            {\n                return this.split(search).join(replacement)\n            }\n            storyJS = Utils.readFile(storyPath).replace(\"var story = {\", \"this.story = {\")\n            storyJS = storyJS.replaceAllMe(\"$.extend(new ViewerPage(),\", \"\").replaceAllMe(\"})\", \"}\")\n            eval(storyJS)\n\n            // Build page list\n            this.miroExportInfoList = this.getArtboardsListForMiro()\n\n            // Publish        \n            log(\"publishToMiro: start publishing\")\n            const result = api.uploadArtboardsToRTB(this.context, this.miroBoardID, true)\n            if (result != api.UploadEnum.SUCCESS)\n            {\n                throw \"Failed to publish\"\n            }\n\n            // Show in browser\n            if (standalone)\n            {\n                var fullBoardURL = boardURL + this.miroBoardID;\n                const openResult = Utils.runCommand('/usr/bin/open', [fullBoardURL])\n                if (openResult.result)\n                {\n                } else\n                {\n                    this.UI.alert('Can not open HTML in browser', openResult.output)\n                }\n                require('sketch/ui').alert('Success', 'Published successfully')\n            }\n        }\n        catch (error)\n        {\n            this.UI.alert('Publishing to Miro failed', error)\n        }\n        finally\n        {\n            log(\"publishToMiro: done\")\n        }\n    }\n\n\n    getArtboardsListForMiro()\n    {\n        var imagePath = this.fullImagesPath\n        var exportInfoList = [];\n        const Dom = require('sketch/dom')\n        const jDoc = Dom.fromNative(publisher.doc)\n\n        let errors = \"\"\n\n        log(\"Miro: build page list: start\")\n        for (var page of this.story.pages.filter(el => \"external\" != el.type))\n        {\n            const artboard = jDoc.getLayerWithID(page[\"id\"])\n            if (!artboard)\n            {\n                //if (\"\" != errors) errors += \"\\n\"\n                //errors += page['title']\n                continue\n            }\n            var exportInfo = { \"artboardID\": page[\"id\"], \"artboard\": artboard.sketchObject, \"path\": imagePath + page['image'] };\n            exportInfoList.push(exportInfo);\n        }\n        log(\"Miro: build page list: done\")\n        if (\"\" != errors)\n        {\n            this.UI.alert('Can not find by ID the following artboards', errors)\n            return null\n        }\n        return exportInfoList;\n    }\n\n\n\n    showMessage(result)\n    {\n        if (result.result)\n        {\n            this.UI.alert('Success', PublishKeys.SHOW_OUTPUT ? result.output : 'Mockups published!')\n        } else\n        {\n            this.showOutput(result)\n        }\n    }\n\n    showOutput(result)\n    {\n        if (result.result && !PublishKeys.SHOW_OUTPUT) return true\n        this.UI.alert(result.result ? 'Output' : 'Error', result.output)\n    }\n\n    checkOptions()\n    {\n\n        if (this.ver == '')\n        {\n            this.UI.alert('Error', 'Version should be specified')\n            return false\n        }     \n        if (this.remoteFolder == '')\n        {\n            this.UI.alert('Error', 'Remote site folder should be specified')\n            return false\n        }\n        return true\n    }\n\n\n    askOptions()\n    {\n        let Settings = this.Settings\n\n        let askMessage = '' != this.serverToolsPath\n        let askMiro = this.miroEnabled\n\n        if(this.login===\"\" &&  this.siteRoot===\"\"){\n            publisher.UI.alert(\"Error\", \"Configure SFTP login ot HTTPS Site URL\")\n            return false\n        }        \n\n        // show dialod        \n        const dialog = new UIDialog(\"Publish HTML\", NSMakeRect(0, 0, 400,\n            180 + (askMessage ? 65 : 0) + (askMiro ? 60 : 0)),\n            \"Publish\", \"Generated HTML will be uploaded to external site by SFTP.\")\n        dialog.removeLeftColumn()\n\n        if (askMessage)\n        {\n            dialog.addTextBox(\"message\", \"Change Description\", this.message, 'Added Remove button', 40)\n            dialog.addHint(\"messageHint\", \"Describe briefly was changed\")\n        }\n\n        dialog.addTextInput(\"version\", \"Version\", this.ver, '1', 50)\n        dialog.addHint(\"versionHint\", \"Exporter will publish two HTML sets - live and <version>\")\n\n        dialog.addTextInput(\"remoteFolder\", \"Remote Site Folder\", this.remoteFolder, 'myprojects/project1', 350)\n        dialog.addHint(\"remoteFolderHint\", \"Relative path on server\")\n\n        if (askMiro)\n        {\n            this.addMiroBoardSelector(dialog, 350, \" (optional)\")\n        }\n\n\n        track(TRACK_PUBLISH_DIALOG_SHOWN)\n        while (true)\n        {\n            const result = dialog.run()\n            if (!result)\n            {\n                track(TRACK_PUBLISH_DIALOG_CLOSED, { \"cmd\": \"cancel\" })\n                return false\n            }\n\n            // Read data\n            if (askMiro)\n            {\n                this.miroBoardName = dialog.views['miroBoard'].stringValue() + \"\"\n            }\n\n            this.remoteFolder = dialog.views['remoteFolder'].stringValue() + \"\"\n\n            if (askMessage)\n            {\n                this.message = dialog.views['message'].stringValue() + \"\"\n            }\n\n            let ver = dialog.views['version'].stringValue() + \"\"\n            let verInt = parseInt(ver)\n            this.ver = ver\n\n            // check data\n            if (askMiro)\n            {\n                if (\"\" == this.miroBoardName)\n                {\n                    // Set empty board\n                    this.miroBoardID = \"\"\n                    this.miroBoardIndex = -1\n                } else if (this.oldMiroBoardName != this.miroBoardName)\n                {\n                    // Change name\n\n                    // load Miro boards to find the new ID\n                    if (!this._initMiro()) return false\n                    this.miroBoardIndex = this.miroBoards.boards.indexOf(this.miroBoardName)\n                    this.miroBoardID = this.miroBoardIndex >= 0 ? this.miroBoards.indexIdsMap[this.miroBoardIndex] : \"\"\n                }\n                if (\"\" != this.miroBoardName && \"\" == this.miroBoardID)\n                {\n                    this.UI.alert(\"Error\", \"No such board in Miro\")\n                    continue\n                }\n            }\n            if ('' == this.remoteFolder) continue\n            if ('' == this.ver) continue\n            if (askMessage && '' == this.message) continue\n\n\n            dialog.finish()\n            track(TRACK_PUBLISH_DIALOG_CLOSED, { \"cmd\": \"ok\" })\n            // save new version into document settings         \n            if (askMiro)\n            {\n                Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARDID, this.miroBoardID)\n                Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARD, this.miroBoardName)\n            }\n\n            Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_REMOTE_FOLDER, this.remoteFolder)\n            Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_VERSION, (verInt >= 0 ? verInt + 1 : verInt) + \"\")\n            return true\n        }\n        return false\n    }\n\n    askMiroOptions()\n    {\n        if (!this._initMiro() || !this._validateMiroParams()) return false\n\n        const dialog = new UIDialog(\"Select Miro Board \", NSMakeRect(0, 0, 350, 60), \"Select\", \"Previously exported pages will be uploaded to Miro whiteboard as images\")\n        dialog.removeLeftColumn()\n        dialog.addSelect(\"miroBoard\", \"\", this.miroBoardIndex, this.miroBoards.boards, 350)\n\n        while (true)\n        {\n            const result = dialog.run()\n            if (!result)\n            {\n                return false\n            }\n            const miroBoardIndex = dialog.views['miroBoard'].indexOfSelectedItem()\n            if (0 > miroBoardIndex)\n            {\n                publisher.UI.alert(\"Error\", \"Miro board should be specified\")\n                continue\n            }\n            let miroBoardID = this.miroBoards.indexIdsMap[miroBoardIndex]\n            if (\"\" == miroBoardID)\n            {\n                publisher.UI.alert(\"Error\", \"Miro board should be specified\")\n                continue\n            }\n            this.miroBoardID = miroBoardID\n\n            dialog.finish()\n            // save \n            this.Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARDID, this.miroBoardID)\n            if (this.oldMiroBoardName != \"\") this.Settings.setDocumentSettingForKey(this.doc, SettingKeys.DOC_PUBLISH_MIRO_BOARD, \"\")\n\n            return true\n        }\n        return false\n    }\n\n    addMiroBoardSelector(dialog, width = 520, inlineHintPostfix = \"\")\n    {\n\n        //dialog.addTextInput(\"miroBoard\", \"Miro board\", this.miroBoard, 'Board name', 350)\n\n        const input = dialog.addPathInput({\n            id: \"miroBoard\", label: \"Miro board\", labelSelect: \"Select\",\n            textValue: this.miroBoardName,\n            inlineHint: 'Board name' + inlineHintPostfix, width,\n            customHandler: function ()\n            {\n                if (!publisher._initMiro()) return false\n\n                const dialog = new UIDialog(\"Select Miro Board \", NSMakeRect(0, 0, 350, 60), \"Select\")\n                dialog.removeLeftColumn()\n\n                const currentBoard = input.stringValue() + \"\"\n                let currentBoardIndex = currentBoard != \"\" ? publisher.miroBoards.boards.indexOf(currentBoard) : 0\n                if (currentBoardIndex < 0) currentBoardIndex = 0\n\n                dialog.addSelect(\"miroBoard\", \"\", currentBoardIndex, publisher.miroBoards.boards, 350)\n\n                while (true)\n                {\n                    const result = dialog.run()\n                    if (!result)\n                    {\n                        return false\n                    }\n                    const miroBoardIndex = dialog.views['miroBoard'].indexOfSelectedItem()\n                    if (0 > miroBoardIndex)\n                    {\n                        publisher.UI.alert(\"Error\", \"Miro board should be specified\")\n                        continue\n                    }\n                    input.setStringValue(publisher.miroBoards.boards[miroBoardIndex])\n\n                    dialog.finish()\n                    // save \n                    return true\n                }\n                return false\n            }\n        })\n    }\n\n    checkMockupExists(allMockupsdDir, docFolder)\n    {\n        const fullPath = allMockupsdDir + \"/\" + docFolder\n        if (Utils.isFolderExists(fullPath)) return true\n        this.UI.alert('Error', `Local HTML is not found on \\n${fullPath}\\n\\nYou need to run Export to HTML before publishing`)\n        return false\n    }\n\n\n    runPreparationScript(version, allMockupsdDir, docFolder,remoteFolder,commentsID) {\n        let args = [version, allMockupsdDir, docFolder, remoteFolder, commentsID]\n        args.push(this.login)\n        args.push(this.sshPort)\n        args.push(this.authorName)\n        args.push(this.authorEmail)\n        args.push(this.commentsURL.replace(/(\\/)/g, '\\\\/'))\n        args.push(this.tempFolder)\n        //args.push(Constants.MIRROR2)        \n        return this.runScriptWithArgs(\"preparePublish.sh\", args)\n    }\n\n\n    runPublishScript(version, allMockupsdDir, docFolder, remoteFolder, commentsID) {\n        let args = [version, allMockupsdDir, docFolder, remoteFolder, commentsID]\n        args.push(this.login)\n        args.push(this.sshPort)\n        args.push(this.authorName)\n        args.push(this.authorEmail)\n        args.push(this.commentsURL.replace(/(\\/)/g, '\\\\/'))\n        args.push(this.tempFolder)\n        //args.push(Constants.MIRROR2)        \n        return this.runScriptWithArgs(\"publish.sh\", args)\n    }\n\n    publishMockupsByHTTPS(remoteFolder)\n    {\n        const fullPath = this.tempFolder\n        const localImagesPath = fullPath + \"/images\"\n\n        //////////// PUBLISH IMAGES /////////\n        this.publishedImages = 0\n        // Publish images im /images folder\n        let res = this.publishImagesInFolderByHTTPS(localImagesPath, \"2x\")\n        if (res && !res.result) return res\n\n        // Publish images im /images/full folder\n        res = this.publishImagesInFolderByHTTPS(localImagesPath + \"/full\", \"full\")\n        if (res && !res.result) return res\n        // Publish images im /images/full folder\n        res = this.publishImagesInFolderByHTTPS(localImagesPath + \"/previews\", \"preview\")\n        if (res && !res.result) return res\n\n        //////////// PUBLISH OTHJER /////////\n        res = this.publishFilesByHTTPS(fullPath, [\"index.html\"])\n        if (res && !res.result) return res\n        const folders = [\"data\", \"resources\", \"js\", \"js/other\"]\n        folders.forEach(function (folderName)\n        {\n            res = this.publishFilesInFolderByHTTPS(fullPath, folderName)\n            if (res && !res.result) return res\n        }, this)\n\n        // COMPLETE\n        res = this.publishCompleteByHTTPS(fullPath)\n        if (res && !res.result) return res\n\n        return res\n    }\n\n\n    publishCompleteByHTTPS(fullPath)\n    {\n        let args = [\"--no-progress-meter\"]\n        const cmd = \"cms\"\n        let url = this.siteRoot + this.serverToolsPath + \"/upload.php?cmd=\" + cmd\n        url += `&tid=${encodeURI(this.secret)}`\n        url += `&uid=${encodeURI(this.userID)}`\n        url += `&ver=${encodeURI(this.ver)}`\n        url += `&docid=${encodeURI(this.remoteFolder)}`\n        args.push(url)\n\n        return Utils.runCommand(this.curlPath, args)\n    }\n\n    publishImagesInFolderByHTTPS(localPath, defaultImageType)\n    {\n        if (!Utils.isFolderExists(localPath)) return {\n            result: 0,\n            output: \"No folder on path \" + localPath\n        }\n\n\n        const allImages = Utils.listFiles(localPath)\n        let result = null\n\n        // process images\n        let fileNames = []\n        allImages.forEach(function (file)\n        {\n            if (result && !result.result) return\n            const fileName = file + \"\"\n            if (!(fileName.endsWith(\".png\") || fileName.endsWith(\".jpg\"))) return\n            //        \n            const imageType = fileName.includes(\"@2x.\") ? \"2x\" : defaultImageType\n            //\n            if (DEBUG) log(`Upload #${this.publishedImages} ${fileName}`)\n            fileNames.push(fileName)\n        }, this);\n        //  \n        return this.publishFilesByHTTPS(localPath, fileNames, defaultImageType)             \n    }\n\n    publishFilesInFolderByHTTPS(localPath, folderName)\n    {\n        const fullLocalPath = localPath + \"/\" + folderName\n        const allFiles = Utils.listFiles(fullLocalPath)\n        let result = null\n\n        // process files\n        let fileNames = []\n        allFiles.forEach(function (file)\n        {\n            if (result && !result.result) return\n            const fileName = file + \"\"\n            if (fileName === \"other\") return\n            //\n            if (DEBUG) log(`Upload ${fileName}`)          \n            fileNames.push(fileName)  \n        }, this);\n        //       \n        result = this.publishFilesByHTTPS(fullLocalPath, fileNames, \"\", folderName)\n        if (!result.result) log(result.output)\n        return result\n    }\n\n\n    publishFilesByHTTPS(filePath, fileNames, imageType = \"\", dirType = \"\")\n    {                \n        let result = null\n        let chunk = []        \n        fileNames.forEach(function(fileName){\n            chunk.push(fileName)\n            if(chunk.length===this.filesChunkLimit){\n                result = this.publishFilesChunkByHTTPS(filePath, chunk, imageType, dirType)\n                //\n                chunk = []                \n            }\n        },this)        \n        if(chunk.length) result = this.publishFilesChunkByHTTPS(filePath, chunk, imageType, dirType)\n        return result\n    }\n\n    publishFilesChunkByHTTPS(filePath, fileNames, imageType, dirType)\n    {       \n        if(DEBUG) log(filePath)\n        if(DEBUG) log(fileNames)\n        let isStart = this.publishedImages++==0\n        const cmd = imageType != \"\" ? \"uploadFrame\" : \"uploadFile\"\n        let args = [\"--no-progress-meter\",\"-X\",\"POST\",\"-H\",\"Content-Type: multipart/form-data\"]\n        let url = this.siteRoot + this.serverToolsPath + \"/upload.php?cmd=\" + cmd\n        url += `&tid=${encodeURI(this.secret)}`\n        url += `&uid=${encodeURI(this.userID)}`\n        url += `&s=${isStart ? 1 : 0}`\n        if (imageType != \"\") url += `&t=${imageType}`\n        if (dirType != \"\") url += `&dt=${dirType}`\n        fileNames.forEach(function(fileName){\n            const fullPath = filePath + \"/\" + fileName\n            if (!Utils.isFolderExists(fullPath)) return {\n                result: 0,\n                output: \"No file on path \" + fullPath\n            }\n            //\n            args.push(\"-F\")\n            const fileStr = `${fileName}=@${fullPath}`\n            args.push(fileStr)\n            if(DEBUG) log(fileStr)\n        },this)          \n        args.push(url)\n\n        return Utils.runCommand(this.curlPath, args)\n    }\n\n    runScriptWithArgs(scriptName, args)\n    {\n        const scriptPath = this.allMockupsdDir + \"/\" + scriptName\n        args.unshift(scriptPath) // add script itself as a first argument\n        const res = Utils.runCommand('/bin/bash', args)\n\n        // delete script\n        Utils.deleteFile(scriptPath)\n\n        return res\n    }\n\n    runToolInResourcesWithArgs(toolName, args)\n    {\n        var url = this.context.plugin.urlForResourceNamed(toolName).path()\n        //args.unshift(toolName)\n        //const regex = / /gi;\n        //const pathTo = this._getFilePathInResourceFolder(toolName).replace(regex,\"\\\\ \")\n        const res = Utils.runCommand(url, args)\n        return res\n    }\n\n    runToolWithArgs(toolName, args)\n    {\n        const res = Utils.runCommand(toolName, args)\n        return res\n    }\n\n\n    copyScript(scriptName)\n    {\n\n        const scriptPath = this.allMockupsdDir + \"/\" + scriptName\n\n        const fileManager = NSFileManager.defaultManager()\n        const targetPath = scriptPath\n\n        // delete old copy\n        Utils.deleteFile(targetPath)\n\n        let sourcePath = this._getFileURLInResourceFolder(scriptName)\n        let error = MOPointer.alloc().init()\n\n        if (!fileManager.copyItemAtPath_toPath_error(sourcePath, targetPath, error))\n        {\n            log(\"copyScript(): Can't copy '\" + sourcePath + \"' to '\" + targetPath + \"'. Error: \" + error.value().localizedDescription());\n\n            this.UI.alert('Can`t copy script', error.value().localizedDescription())\n            return false\n        }\n\n        return true\n    }\n\n    _initMiro()\n    {\n        log(\"_initMiro\")\n        if (null != this.miroBoards) return true\n        // Get request\n        log(\"_initMiro start\")\n        var response = api.authCheckRequest(this.context);\n        if (response)\n        {\n            if (response.success == 1)\n            {\n            } else if (response.error && response.error.code == 401)\n            {\n                api.setToken(nil);\n                log(response.error)\n                response = null\n            } else\n            {\n                response = null\n            }\n        }\n        if (!response)\n        {\n            this.UI.alert(\"Error\", \"You need to log into Miro using Miro plugin\\n\\nhttps://github.com/miroapp/sketch_plugin\")\n            return false\n        } else\n        {\n\n            this.miroBoards = Utils.getMiroBoardsGroupedByProject()\n        }\n        return true\n    }\n\n    _validateMiroParams()\n    {\n        if (null == this.miroBoards) return true\n\n        // Find board ID for name\n        if (\"\" == this.miroBoardID && \"\" != this.miroBoardName)\n        {\n            let index = this.miroBoards.boards.indexOf(this.miroBoardName)\n            if (index >= 0)\n            {\n                let miroBoardID = this.miroBoards.indexIdsMap[index]\n                if (undefined == miroBoardID) miroBoardID = \"\"\n                this.miroBoardID = miroBoardID\n            }\n        }\n        // Find board name for ID\n        if (\"\" != this.miroBoardID && \"\" == this.miroBoardName)\n        {\n            let index = this.miroBoards.indexIdsMap.indexOf(this.miroBoardID)\n            if (index >= 0)\n            {\n                let miroBoardName = this.miroBoards.boards[index]\n                if (undefined == miroBoardName) miroBoardName = \"\"\n                this.miroBoardName = miroBoardName\n            }\n        }\n        // Reset if something is wrong\n        if (\"\" == this.miroBoardID || \"\" == this.miroBoardName)\n        {\n            this.miroBoardID = \"\"\n            this.miroBoardName = \"\"\n        }\n\n        let currentBoardIndex = this.miroBoardID != \"\" ? this.miroBoards.indexIdsMap.indexOf(this.miroBoardID) : \"\"\n        if (currentBoardIndex < 0) currentBoardIndex = 0\n        this.miroBoardIndex = currentBoardIndex\n\n        return true\n    }\n\n    _getFileURLInResourceFolder(file)\n    {\n        return this.context.plugin.url().URLByAppendingPathComponent(\"Contents\").URLByAppendingPathComponent(\"Sketch\").URLByAppendingPathComponent(PublishKeys.RESOURCES_FOLDER).URLByAppendingPathComponent(file)\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/lib/ga.js",
    "content": "@import \"lib/utils.js\";\n@import \"constants.js\";\n\nfunction jsonToQueryString(json) {\n    return Object.keys(json)\n        .map(function (key) {\n            return encodeURIComponent(key) + \"=\" + encodeURIComponent(json[key]);\n        })\n        .join(\"&\");\n}\n\nfunction _completeGA(d, r, e) {\n    if (DEBUG) log(\"Completed GA\")\n}\n\n\n\nfunction track(page, props = undefined) {\n    coscript.scheduleWithInterval_jsFunction(1, function () {\n\n        var trackingId = Constants.GA_ID\n\n        var Settings = require(\"sketch/settings\");\n        var kUUIDKey = \"google.analytics.uuid\";\n        var uuid = null\n        var uuid = NSUserDefaults.standardUserDefaults().objectForKey(kUUIDKey);\n        if (!uuid) {\n            uuid = NSUUID.UUID().UUIDString();\n            NSUserDefaults.standardUserDefaults().setObject_forKey(uuid, kUUIDKey)\n        }\n\n        var variant = \"\"//MSApplicationMetadata.metadata().variant;\n        var source =\n            \"Sketch \" +\n            (variant == \"NONAPPSTORE\" ? \"\" : variant + \" \") +\n            Settings.version.sketch;\n\n\n        if (Settings.settingForKey(SettingKeys.PLUGIN_GA_DISABLED)) {\n            // the user didn't enable sharing analytics\n            return 'the user didn\\'t enable sharing analytics';\n        }\n\n        var payload = {\n            v: 1,\n            tid: trackingId,\n            ds: source,\n            cid: uuid,\n            t: \"pageview\",\n            dp: page\n        };\n\n        if (typeof __command !== \"undefined\") {\n            payload.an = __command.pluginBundle().name();\n            payload.aid = __command.pluginBundle().identifier();\n            payload.av = __command.pluginBundle().version();\n        }\n\n        if (props) {\n            Object.keys(props).forEach(function (key) {\n                payload[key] = props[key];\n            });\n        }\n\n        var nURL = NSURL.URLWithString(\n            \"https://www.google-analytics.com/collect?payload_data&\" +\n            jsonToQueryString(payload) +\n            \"&z=\" +\n            NSUUID.UUID().UUIDString()\n        );\n        if (DEBUG) log(\"Started GA \" + nURL)\n        //NSData.dataWithContentsOfURL(nURL);\n        let session = NSURLSession.sharedSession()\n        //let task = [session dataTaskWithURL: nURL completionHandler: _completeGA]//, _completeGA)\n        //let task = session.dataTaskWithURL_completionHandler_(nURL, _completeGA)\n        let task = session.dataTaskWithURL(nURL)\n        task.resume()\n    })\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/lib/uidialog.js",
    "content": "var UIDialog_iconImage = null\nconst TAB_HEIGHT = 55\n\nfunction Class(className, BaseClass, selectorHandlerDict)\n{\n    var uniqueClassName = className + NSUUID.UUID().UUIDString();\n    var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, BaseClass);\n    for (var selectorString in selectorHandlerDict)\n    {\n        delegateClassDesc.addInstanceMethodWithSelector_function_(selectorString, selectorHandlerDict[selectorString]);\n    }\n    delegateClassDesc.registerClass();\n    return NSClassFromString(uniqueClassName);\n};\n\nclass UIAbstractWindow\n{\n\n    constructor(window, intRect)\n    {\n        this.window = window\n\n        var container = NSView.alloc().initWithFrame(intRect)\n        this.container = container\n        this.topContainer = container\n        this.views = []\n\n        this.leftOffset = 0\n\n        this.rect = intRect\n        this.y = NSHeight(this.rect)\n\n        this.leftColumn = true\n        this.leftColWidth = 120\n        this.textOffset = 2\n    }\n\n    removeLeftColumn()\n    {\n        this.leftColumn = false\n        this.leftColWidth = 0\n        this.textOffset = 0\n    }\n\n    initTabs(tabs)\n    {\n        const intRect = this.rect\n\n        this.tabs = tabs.map(function (tab) { return { label: tab } })\n\n        var tabView = NSTabView.alloc().initWithFrame(intRect)\n\n        this.tabs.forEach(function (tab, index)\n        {\n\n            let viewController = NSViewController.alloc().init()\n            viewController.originalSize = intRect\n\n            var view = NSView.alloc().initWithFrame(intRect)\n            view.wantsLayer = false\n            viewController.view = view\n\n            let tabViewIem = NSTabViewItem.alloc().init()\n            tabViewIem.viewController = viewController\n            tabViewIem.label = tab.label\n            tabViewIem.initialFirstResponder = view\n\n            tabView.addTabViewItem(tabViewIem)\n\n            tab.container = view\n\n        }, this)\n\n        this.tabView = tabView\n        this.container = this.tabs[0].container\n        this.topContainer = tabView\n\n        this.leftOffset = 20\n        this.y = NSHeight(this.rect) - TAB_HEIGHT\n    }\n\n    setTabForViewsCreating(tabIndex)\n    {\n        this.container = this.tabs[tabIndex].container\n\n        this.y = NSHeight(this.rect) - TAB_HEIGHT\n    }\n\n\n    enableTextByID(id, enabled)\n    {\n        if (!(id in this.views)) return\n\n        var text = this.views[id]\n        if (!enabled)\n            text.textColor = NSColor.disabledControlTextColor()\n        else\n            text.textColor = NSColor.controlTextColor()\n\n    }\n\n    enableHintByID(id, enabled)\n    {\n        if (!(id in this.views)) return\n\n        var text = this.views[id]\n        if (!enabled)\n            text.textColor = NSColor.disabledControlTextColor()\n        else\n            text.textColor = NSColor.secondaryLabelColor()\n\n    }\n\n    enableControlByID(id, enabled)\n    {\n        var control = this.views[id]\n        control.enabled = enabled\n\n        this.enableTextByID(id + 'Label', enabled)\n        this.enableHintByID(id + 'Hint', enabled)\n\n    }\n\n    getNewFrame(height = 25, width = -1, yinc = -1)\n    {\n        var frame = NSMakeRect(this.leftColWidth, this.y - height, width == -1 ? NSWidth(this.rect) - 10 - this.leftColWidth : width, height)\n        this.y -= height + (yinc >= 0 ? yinc : 10)\n        return frame\n    }\n\n    addSpace()\n    {\n        this.getNewFrame(0)\n    }\n\n    addLeftLabel(id, text, height = 40)\n    {\n        var frame = null\n\n        if (this.leftColumn)\n            frame = NSMakeRect(0, this.y - height - this.textOffset, this.leftColWidth - 10, height)\n        else\n            frame = this.getNewFrame(height)\n\n        const label = NSTextField.alloc().initWithFrame(frame);\n        label.setStringValue(text);\n        label.setBezeled(false);\n        label.setDrawsBackground(false);\n        label.setEditable(false);\n        label.setSelectable(false);\n        if (this.leftColumn)\n        {\n            label.setFont(NSFont.boldSystemFontOfSize(12))\n            label.setAlignment(NSTextAlignmentRight)\n        }\n\n        if ('' != id) this.views[id] = label\n\n        this.container.addSubview(label)\n        return label\n    }\n\n    addLabel(id, text, height = 25)\n    {\n        const label = NSTextField.alloc().initWithFrame(this.getNewFrame(height));\n        label.setStringValue(text);\n        label.setBezeled(false);\n        label.setDrawsBackground(false);\n        label.setEditable(false);\n        label.setSelectable(false);\n\n        if ('' != id) this.views[id] = label\n\n        this.container.addSubview(label)\n        this.y += 5\n        return label\n    }\n\n    // required:  id:, options:\n    // opional:  label: \"\", width: 220, frame: undefined\n    addComboBox(opt)\n    {\n        if (undefined == opt.label) opt.label = \"\"\n        if (undefined == opt.width) opt.width = 220\n\n        if (opt.label != '')\n            this.addLabel(id + \"Label\", opt.label, 17)\n\n        const v = NSComboBox.alloc().initWithFrame(opt.frame ? opt.frame : this.getNewFrame(20, opt.width))\n        if (opt.options.length > 0)\n        {\n            v.addItemsWithObjectValues(opt.options)\n            v.setNumberOfVisibleItems(opt.options.length);\n            v.selectItemAtIndex(0)\n        }\n        v.setCompletes(1);\n\n        this.container.addSubview(v)\n        this.views[opt.id] = v\n\n        return v\n    }\n\n    addCheckbox(id, label, checked, height = 18)\n    {\n        checked = (checked == false) ? NSOffState : NSOnState;\n\n        const checkbox = NSButton.alloc().initWithFrame(this.getNewFrame(height, -1, 6));\n        checkbox.setButtonType(NSSwitchButton);\n        checkbox.setBezelStyle(0);\n        checkbox.setTitle(label);\n        checkbox.setState(checked);\n\n        this.container.addSubview(checkbox)\n        this.views[id] = checkbox\n        return checkbox\n    }\n\n    addTextBox(id, label, textValue, inlineHint = \"\", height = 120)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 17)\n\n        const textBox = NSTextField.alloc().initWithFrame(this.getNewFrame(height))\n        textBox.setEditable(true)\n        textBox.setBordered(true)\n        textBox.setStringValue(textValue)\n        if (inlineHint != \"\")\n        {\n            textBox.setPlaceholderString(inlineHint)\n        }\n\n        this.container.addSubview(textBox)\n        this.views[id] = textBox\n\n        return textBox\n    }\n\n\n    addTextViewBox(id, label, textValue, height = 120)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 17)\n\n        const frame = this.getNewFrame(height)\n        const scrollView = NSScrollView.alloc().initWithFrame(frame)\n        scrollView.setHasVerticalScroller(true)\n        scrollView.setHasHorizontalScroller(true)\n\n        const textView = NSTextView.alloc().initWithFrame(frame)\n        textView.setEditable(false)\n        textView.setString(textValue)\n\n        scrollView.addSubview(textView)\n        scrollView.setDocumentView(textView)\n        this.container.addSubview(scrollView)\n\n\n        this.views[id] = textView\n\n        return textView\n    }\n\n    addTextInput(id, label, textValue, inlineHint = \"\", width = 220, frame = undefined)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 17)\n\n        const input = NSTextField.alloc().initWithFrame(frame ? frame : this.getNewFrame(20, width))\n        input.setEditable(true)\n        input.setBordered(true)\n        input.maximumNumberOfLines = 1\n        input.setStringValue(textValue)\n        if (inlineHint != \"\")\n        {\n            input.setPlaceholderString(inlineHint)\n        }\n\n        this.container.addSubview(input)\n        this.views[id] = input\n\n        return input\n    }\n\n    addSecureTextInput(id, label, textValue, inlineHint = \"\", width = 220, frame = undefined)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 17)\n\n        const input = NSSecureTextField.alloc().initWithFrame(frame ? frame : this.getNewFrame(20, width))\n        input.setEditable(true)\n        input.setBordered(true)\n        input.maximumNumberOfLines = 1\n        input.setStringValue(textValue)\n        if (inlineHint != \"\")\n        {\n            input.setPlaceholderString(inlineHint)\n        }\n\n        this.container.addSubview(input)\n        this.views[id] = input\n\n        return input\n    }\n\n    // opt: required: id, label, labelSelect, textValue\n    //      optional: inlineHint = \"\", width = 220, widthSelect = 50), askFilePath=false\n    //       comboBoxOptions: string array\n    //      customHandler: custom JS handler for Select button\n    addPathInput(opt)\n    {\n        if (!('width' in opt)) opt.width = 220\n        if (!('widthSelect' in opt)) opt.widthSelect = 50\n        if (!('inlineHint' in opt)) opt.inlineHint = \"\"\n        if (!('askFilePath' in opt)) opt.askFilePath = false\n        if (!('customHandler' in opt)) opt.customHandler = null\n\n        if (opt.label != '') this.addLabel(opt.id + \"Label\", opt.label, 17)\n\n        const frame = this.getNewFrame(28, opt.width - opt.widthSelect - 5)\n        const frame2 = Utils.copyRect(frame)\n        frame2.origin.x = frame2.origin.x + opt.width - opt.widthSelect\n        frame2.origin.y -= 3\n\n        const input = 'comboBoxOptions' in opt ?\n            this.addComboBox({ id: opt.id, options: opt.comboBoxOptions, width: 0, frame: frame })\n            : this.addTextInput(opt.id, \"\", opt.textValue, opt.inlineHint, 0, frame)\n\n        this.addButton(opt.id + \"Select\", opt.labelSelect, opt.customHandler ? opt.customHandler : function ()\n        {\n            const newPath = opt.askFilePath\n                ? Utils.askFilePath(input.stringValue() + \"\")\n                : Utils.askPath(input.stringValue() + \"\")\n            if (newPath != null)\n            {\n                input.setStringValue(newPath)\n            }\n            return\n        }, 0, frame2)\n        return input\n    }\n\n    addSelect(id, label, selectItem, options, width = 100)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 15)\n\n        const v = NSPopUpButton.alloc().initWithFrame(this.getNewFrame(23, width));\n        v.addItemsWithTitles(options)\n        v.selectItemAtIndex(selectItem)\n\n        this.container.addSubview(v)\n        this.views[id] = v\n        return v\n    }\n\n\n    addRadioButtons(id, label, selectItem, options, width = 100)\n    {\n        if (label != '') this.addLabel(id + \"Label\", label, 15)\n\n        // pre-select the first item\n        if (selectItem < 0) selectItem = 0\n\n        let group = this.startRadioButtions(id, selectItem)\n\n        for (var item of options)\n        {\n            const index = group.btns.length\n\n            const btn = NSButton.alloc().initWithFrame(this.getNewFrame(18, width))\n            btn.setButtonType(NSRadioButton)\n            btn.setTitle(item)\n            btn.setState(index != selectItem ? NSOffState : NSOnState)\n            btn.myGroup = group\n            btn.myIndex = index\n            btn.setCOSJSTargetFunction(group.radioTargetFunction)\n\n            this.container.addSubview(btn)\n            group.btns.push(btn)\n        }\n\n        return group\n    }\n\n    startRadioButtions(idGroup, selectedIndex)\n    {\n        const groups = {\n            id: idGroup,\n            btns: [],\n            selectedIndex: selectedIndex,\n            radioTargetFunction: (sender) =>\n            {\n                sender.myGroup.selectedIndex = sender.myIndex\n            }\n\n        }\n        this._buttonsGroups = groups\n        this.views[idGroup] = this._buttonsGroups\n        return this._buttonsGroups\n    }\n\n    addRadioButton(id, title, index, frame)\n    {\n        const selected = this._buttonsGroups.selectedIndex == index\n\n        const btn = NSButton.alloc().initWithFrame(frame)\n        btn.setButtonType(NSRadioButton)\n        if (title != '') btn.setTitle(title)\n        btn.setState(!selected ? NSOffState : NSOnState)\n        btn.myGroup = this._buttonsGroups\n        btn.myIndex = index\n        btn.setCOSJSTargetFunction(this._buttonsGroups.radioTargetFunction)\n\n        this.views[id] = btn\n        this.container.addSubview(btn)\n        this._buttonsGroups.btns.push(btn)\n\n        return btn\n    }\n\n    addButton(id, label, func, width = 100, frame = undefined)\n    {\n        // create OK button\n        var btn = NSButton.alloc().initWithFrame(frame ? frame : this.getNewFrame(20, width));\n        btn.setTitle(label)\n        btn.setBezelStyle(NSRoundedBezelStyle)\n        btn.sizeToFit()\n        btn.setCOSJSTargetFunction(func)\n\n        this.container.addSubview(btn)\n        this.views[id] = btn\n        return btn\n\n    }\n\n    addHint(id, label, height = 23)\n    {\n        this.y += 3\n\n        const hint = NSTextField.alloc().initWithFrame(this.getNewFrame(height, -1, 3));\n        hint.setStringValue(label);\n        hint.setColor = NSColor.secondaryLabelColor()\n        hint.setBezeled(false);\n        hint.setDrawsBackground(false);\n        hint.setEditable(false);\n        hint.setSelectable(false);\n        hint.setFont(NSFont.systemFontOfSize(10))\n\n        this.container.addSubview(hint)\n        if ('' != id) this.views[id] = hint\n        return hint\n    }\n\n    addDivider()\n    {\n\n        const height = 1\n        var frame = NSMakeRect(0, this.y - height, NSWidth(this.rect) - 10, height)\n        this.y -= height + 10\n\n        var divider = NSView.alloc().initWithFrame(frame);\n\n        divider.setWantsLayer(1);\n        divider.layer().setBackgroundColor(CGColorCreateGenericRGB(204 / 255, 204 / 255, 204 / 255, 1));\n\n        this.container.addSubview(divider)\n\n        return divider;\n    }\n\n    // image: NSImage\n\n    addImage(id, image, frame)\n    {\n        var nImageView = NSImageView.alloc().initWithFrame(frame)\n        nImageView.setImage(image)\n        this.container.addSubview(nImageView)\n        this.views[id] = nImageView\n        return nImageView\n    }\n\n\n    finish()\n    {\n        this.window = null\n    }\n\n}\n\nclass UIDialog extends UIAbstractWindow\n{\n\n    static setUp(context)\n    {\n        UIDialog_iconImage = NSImage.alloc().initByReferencingFile(context.plugin.urlForResourceNamed(\"icon.png\").path())\n    }\n\n    constructor(title, rect, okButtonTitle, description = '', cancelButtonTitle = \"Cancel\")\n    {\n        var window = NSAlert.alloc().init()\n        window.setIcon(UIDialog_iconImage)\n        window.setMessageText(title)\n        if (description != '')\n        {\n            window.setInformativeText(description)\n        }\n        if (undefined != okButtonTitle)\n        {\n            window.addButtonWithTitle(okButtonTitle)\n        }\n        if (cancelButtonTitle != \"\")\n            window.addButtonWithTitle(cancelButtonTitle)\n\n        super(window, rect)\n    }\n\n\n\n    run()\n    {\n        this.userClickedOK = false\n        this.window.setAccessoryView(this.topContainer)\n        return this.window.runModal() == '1000'\n    }\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/lib/uipanel.js",
    "content": "class UIPanel extends UIAbstractWindow {\n\n    constructor(title) {\n        let winRect = NSMakeRect(300, 500, 400, 270)\n        let window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer(winRect, NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView, NSBackingStoreBuffered, true)\n        window.title = title\n\n        let contRect = window.contentLayoutRect()\n\n        super(window, contRect)\n        window.setContentView(this.container)\n\n        window.ReleasedWhenClosed = true;\n    }\n\n\n    show() {\n        this.window.makeKeyAndOrderFront(this.window)\n    }\n\n    finish() {\n        log('closing...')\n        this.window.orderOut(null)\n\n        //this.window.close()    // close() crashes Sketch\n        log('closed')\n        this.window = null\n    }\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/lib/utils.js",
    "content": "@import \"constants.js\";\nvar DEBUG = Constants.LOGGING || require('sketch/settings').settingForKey(SettingKeys.PLUGIN_LOGDEBUG_ENABLED)\n\n\nconst Rectangle = require('sketch/dom').Rectangle\n\n\nRectangle.prototype.round = function ()\n{\n    this.x = Math.round(this.x)\n    this.y = Math.round(this.y)\n    this.height = Math.round(this.height)\n    this.width = Math.round(this.width)\n}\n\nRectangle.prototype.insideRectangle = function (r)\n{\n    return this.x >= r.x && this.y >= r.y\n        && ((this.x + this.width) <= (r.x + r.width))\n        && ((this.y + this.height) <= (r.y + r.height))\n}\n\nRectangle.prototype.copy = function ()\n{\n    return new Rectangle(this.x, this.y, this.width, this.height)\n}\nRectangle.prototype.copyToRect = function ()\n{\n    return NSMakeRect(this.x, this.y, this.width, this.height)\n}\n\nclass Utils\n{\n    static getUserID(){\n        let userID = Utils.getPluginSetting(SettingKeys.PLUGIN_USER_ID)\n        if(userID===\"\"){\n            userID = NSUUID.UUID().UUIDString().substr(0,8)\n            const Settings = require('sketch/settings')\n            Settings.setSettingForKey(SettingKeys.PLUGIN_USER_ID,userID)\n        }\n        return userID\n    }\n\n    static getDocSetting(doc, key, defaultValue = '')\n    {\n        const Settings = require('sketch/settings')\n        let value = Settings.documentSettingForKey(doc, key)\n        if (undefined == value || null == value) value = defaultValue\n        return value\n    }\n\n    static getLayerSetting(layer, key, defaultValue = '')\n    {\n        const Settings = require('sketch/settings')\n        let value = Settings.layerSettingForKey(layer, key)\n        if (undefined == value || null == value) value = defaultValue\n        return value\n    }\n\n    static getPluginSetting(key, defaultValue = '')\n    {\n        const Settings = require('sketch/settings')\n        let value = Settings.settingForKey(key)\n        if (undefined == value || null == value) value = defaultValue\n        return value\n    }\n\n\n    static upgradeArtboardOverlayPosition(oldValue)\n    {\n        const newValues = {\n            pinTo: 0,\n            hotspotTo: 0,\n            pageTo: 0\n        }\n        if (undefined == oldValue) return newValues\n        //\n        switch (oldValue)\n        {\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_LEFT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_CENTER:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_RIGHT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_LEFT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_CENTER:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT_ALIGN_RIGHT:\n                newValues.pinTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT;\n                break\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_LEFT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_CENTER:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_RIGHT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_LEFT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_CENTER:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_RIGHT:\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_CENTER:\n                newValues.pinTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE;\n                break\n            ///\n        }\n        switch (oldValue)\n        {\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_LEFT:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_CENTER:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_RIGHT:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_LEFT:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_CENTER:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_RIGHT_ALIGN_RIGHT:\n                newValues.hotspotTo = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT; break;\n            //\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_LEFT:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_CENTER:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_TOP_RIGHT:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_LEFT:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_CENTER:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER; break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_BOTTOM_RIGHT:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT;\n                break;\n            case Constants.OLD_ARTBOARD_OVERLAY_ALIGN_CENTER:\n                newValues.pageTo = Constants.ARTBOARD_OVERLAY_PIN_PAGE_CENTER;\n                break;\n\n        }\n        //\n        return newValues\n    }\n\n\n\n    static askPath(currentPath = null, buttonName = \"Select\")\n    {\n        let panel = NSOpenPanel.openPanel()\n        panel.setTitle(\"Choose a location...\")\n        panel.setPrompt(buttonName)\n        panel.setCanChooseDirectories(true)\n        panel.setCanChooseFiles(false)\n        panel.setAllowsMultipleSelection(false)\n        panel.setShowsHiddenFiles(false)\n        panel.setExtensionHidden(false)\n        if (currentPath != null && currentPath != undefined)\n        {\n            let url = [NSURL fileURLWithPath: currentPath]\n            panel.setDirectoryURL(url)\n        }\n        const buttonPressed = panel.runModal()\n        const newURL = panel.URL()\n        panel.close()\n        panel = null\n        if (buttonPressed == NSFileHandlingPanelOKButton)\n        {\n            return newURL.path() + ''\n        }\n        return null\n    }\n\n    static writeToFile(str, filePath)\n    {\n        const objcStr = NSString.stringWithFormat(\"%@\", str);\n        return objcStr.writeToFile_atomically_encoding_error(filePath, true, NSUTF8StringEncoding, null);\n    }\n\n    static isFolderExists(path)\n    {\n        const fileManager = NSFileManager.defaultManager();\n        return fileManager.fileExistsAtPath(path)\n    }\n\n    static readFile(path)\n    {\n        const fileManager = NSFileManager.defaultManager();\n        if (!fileManager.fileExistsAtPath(path))\n        {\n            return undefined\n        }\n\n        return NSString.stringWithContentsOfFile_encoding_error(path, NSUTF8StringEncoding, null);\n    }\n\n\n    static cutLastPathFolder(path)\n    {\n        return path.substring(0, path.lastIndexOf(\"/\"))\n    }\n\n\n    static deleteFile(filePath)\n    {\n        const fileManager = NSFileManager.defaultManager();\n\n        let error = MOPointer.alloc().init();\n        if (fileManager.fileExistsAtPath(filePath))\n        {\n            if (!fileManager.removeItemAtPath_error(filePath, error))\n            {\n                log(error.value().localizedDescription());\n            }\n        }\n    }\n\n    static cloneDict(dict)\n    {\n        return Object.assign({}, dict);\n    }\n\n    static escapeFileNameAsArgument(path)\n    {\n        const regex = / /gi;\n        return path.replace(regex, \"\\\\ \")\n    }\n\n    static escapeSpaces(path)\n    {\n        const regex = / /gi;\n        return path.replace(regex, \"\\\\ \")\n    }\n    static escapeDoudleQuote(path)\n    {\n        return path.replace(/[\\\"\"]/g, '__')\n    }\n\n\n    static copyRect(rect)\n    {\n        return NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)\n    }\n\n\n    // rect: GRect instnct\n    static copyRectToRectangle(rect)\n    {\n        return new Rectangle(rect.x(), rect.y(), rect.width(), rect.height())\n    }\n\n    // rect: Rectangle instance\n    static transformRect(rect, cw, ch)\n    {\n        rect.x = rect.x * cw\n        rect.y = rect.y * ch\n        rect.width = rect.width * cw\n        rect.width = rect.height * ch\n    }\n\n    static quoteString(str)\n    {\n        return str.split('\"').join('\\\\\"')\n    }\n\n    static getPathToTempFolder()\n    {\n        const fileManager = NSFileManager.defaultManager()\n        return fileManager.temporaryDirectory().path() + \"\"\n    }\n\n    static toFilename(name, dasherize = true, lowercase = true)\n    {\n        if (dasherize == null)\n        {\n            dasherize = true;\n        }\n        const dividerCharacter = dasherize ? \"-\" : \"_\"\n        const resName = name.replace(/[\\\\/,&:]/g, \"_\").replace(/[\\s-]+/g, dividerCharacter)\n        return lowercase ? resName.toLowerCase() : resName\n    }\n\n\n\n    static isSymbolsPage(page)\n    {\n        return page.artboards()[0].isKindOfClass(MSSymbolMaster);\n    }\n\n    static listFiles(path)\n    {\n        const error = MOPointer.alloc().init();\n        const fileManager = NSFileManager.defaultManager();\n        const files = fileManager.contentsOfDirectoryAtPath_error(path, error);\n        if (files === null)\n        {\n            log(error.value().localizedDescription());\n            return null\n        }\n        return files\n    }\n\n    static removeFilesWithExtension(path, extension, extension2 = null)\n    {\n        const error = MOPointer.alloc().init();\n        const fileManager = NSFileManager.defaultManager();\n        const files = fileManager.contentsOfDirectoryAtPath_error(path, null);\n        files.forEach(function (file)\n        {\n            if (file.pathExtension() == extension || (extension2 != null && file.pathExtension() == extension2))\n            {\n                if (!fileManager.removeItemAtPath_error(path + \"/\" + file, error))\n                {\n                    log(error.value().localizedDescription());\n                }\n            }\n        });\n    }\n\n\n    static runCommand(command, args, sync = true)\n    {\n        var task = NSTask.alloc().init();\n        var pipe = NSPipe.alloc().init()\n        task.setStandardOutput_(pipe);\n        task.setStandardError_(pipe);\n        task.setLaunchPath(command);\n        task.arguments = args;\n        task.launch();\n        if (!sync) return { result: true }\n        task.waitUntilExit()\n\n        var fileHandle = pipe.fileHandleForReading()\n        var data = [fileHandle readDataToEndOfFile];\n        var outputString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];\n\n        return {\n            result: (task.terminationStatus() == 0),\n            output: outputString\n        }\n    }\n\n    static actionWithType(nDoc, type)\n    {\n        var controller = nDoc.actionsController();\n\n        if (controller.actionWithName)\n        {\n            return controller.actionWithName(type);\n        } else if (controller.actionWithID)\n        {\n            return controller.actionWithID(type);\n        } else\n        {\n            return controller.actionForID(type);\n        }\n    }\n\n    static getMiroBoardsGroupedByProject(context)\n    {\n        //  Get token\n        var token = api.getToken();\n        if (!token) return null\n        log(\"publishToMiro: got token\")\n\n        // Get request\n        var response = api.authCheckRequest(this.context);\n        if (response && response.success != 1) return null\n\n        log(\"publishToMiro: established connect\")\n\n        const boards = api.getBoards()\n        let projects = boards.map(el => el[\"project\"]).filter(function (x, i, a)\n        {\n            return x != undefined && a.indexOf(x) === i;\n        });\n        projects.sort()\n        let groupedBoards = []\n        let indexIdsMap = []\n        projects.forEach(function (project)\n        {\n            groupedBoards.push(\"--- \" + project + \" ----\")\n            indexIdsMap.push(\"\")\n            boards.filter(el => el[\"project\"] == project).forEach(function (el)\n            {\n                groupedBoards.push(el['title'])\n                indexIdsMap.push(el['boardId'])\n            })\n        })\n        const boardsWithouProject = boards.filter(el => !(\"project\" in el))\n        if (boardsWithouProject.length)\n        {\n            if (projects.length)\n            {\n                groupedBoards.push(\"--- \" + \"Out of projects\" + \" ----\")\n                indexIdsMap.push(\"\")\n            }\n            boardsWithouProject.forEach(function (el)\n            {\n                groupedBoards.push(el['title'])\n                indexIdsMap.push(el['boardId'])\n            })\n        }\n\n        return {\n            indexIdsMap: indexIdsMap,\n            boards: groupedBoards\n        }\n    }\n\n    static testMiro(context, email, password, board)\n    {\n        const UI = require('sketch/ui')\n\n        // Drop old token\n        if (api.getToken())\n        {\n            api.logoutRequest(context);\n            api.setToken(nil);\n        }\n\n        var keys = [NSMutableArray array];\n        [keys addObject: 'email'];\n        [keys addObject: 'password'];\n\n        var values = [NSMutableArray array];\n        [values addObject: email];\n        [values addObject: encodeHtmlSpecialCharacters(password)];\n\n        var data = [[NSDictionary alloc] initWithObjects: values forKeys: keys]\n\n        var response = api.authRequest(context, data);\n        if (response)\n        {\n            if (response.error)\n            {\n                var messages = getMessagesByError(response.error);\n\n                if (messages.alert)\n                {\n                    UI.alert(\"Can't connect to Miro\", messages.alert)\n                } else if (messages.label)\n                {\n                    UI.alert(\"Can't connect to Miro\", messages.label)\n                }\n            } else\n            {\n                // Set new token\n                var token = response.token;\n                api.setToken(token);\n                api.setFromRetina(true)\n                return true\n            }\n        } else\n        {\n            UI.alert(\"Can't connect to Miro\", \"No response\")\n        }\n        return false\n    }\n}\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/manifest.json",
    "content": "{\n    \"name\": \"Puzzle Publisher\",\n    \"description\": \"Generates and publishes a HTML clickable prototype\",\n    \"author\": \"Maxim Bazarov, Alexey Kalenyuk and Kostantin Smirnov\",\n    \"authorEmail\": \"mbazarov@gmail.com\",\n    \"homepage\": \"https://github.com/ingrammicro/puzzle-publisher\",\n    \"identifier\": \"com.cloudblue.sketch.exporter\",\n    \"appcast\": \"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/appcast.xml\",\n    \"compatibleVersion\": \"52\",\n    \"version\": \"17.9.1\",\n    \"icon\": \"icon.png\",\n    \"commands\": [\n        {\n            \"script\": \"cmd-actions.js\",\n            \"name\": \"My Action Listener\",\n            \"handlers\": {\n                \"actions\": {\n                    \"Startup\": \"onStartup\"\n                }\n            },\n            \"identifier\": \"my-action-listener-identifier\"\n        },\n        {\n            \"script\": \"cmdline-functions.js\",\n            \"handler\": \"cmdRun\",\n            \"name\": \"Run from command line\",\n            \"identifier\": \"cmdRun\"\n        },\n        {\n            \"script\": \"menu-external-link.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Set External Link for Layer or Artboard\",\n            \"identifier\": \"externalLink\"\n        },\n        {\n            \"script\": \"menu-conf-artboard.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Artboard\",\n            \"identifier\": \"confArtboard\"\n        },\n        {\n            \"script\": \"menu-conf-layer.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Layer\",\n            \"identifier\": \"confLayer\"\n        },\n        {\n            \"script\": \"menu-conf-document.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Document\",\n            \"identifier\": \"confDocument\"\n        },\n        {\n            \"script\": \"menu-conf-plugin.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Plugin\",\n            \"identifier\": \"confPlugin\"\n        },\n        {\n            \"script\": \"menu-conf-plugin-export.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Export\",\n            \"identifier\": \"confPluginExport\"\n        },\n        {\n            \"script\": \"menu-conf-plugin-publishing.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Configure Publishing\",\n            \"identifier\": \"confPluginPublishing\"\n        },\n        {\n            \"script\": \"menu-edit-annotations.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"Edit Annotations\",\n            \"identifier\": \"editAnnotations\"\n        },\n        {\n            \"script\": \"menu-export-html-adv.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"✈️ Export selected to HTML\",\n            \"identifier\": \"exportToHTMLAdv\"\n        },\n        {\n            \"script\": \"menu-export-html.js\",\n            \"handler\": \"onRun\",\n            \"shortcut\": \"command alt e\",\n            \"name\": \"✈️ Export to HTML\",\n            \"identifier\": \"exportToHTML\"\n        },\n        {\n            \"script\": \"menu-cmd-publish.js\",\n            \"handler\": \"onRun\",\n            \"shortcut\": \"command alt p\",\n            \"name\": \"📡 Publish HTML\",\n            \"identifier\": \"publish\"\n        },\n        {\n            \"script\": \"menu-cmd-publish-miro.js\",\n            \"handler\": \"onRun\",\n            \"name\": \"📡 Publish to Miro\",\n            \"identifier\": \"publishMiro\"\n        },\n        {\n            \"script\": \"menu-cmd-other.js\",\n            \"handler\": \"showChangeLog\",\n            \"shortcut\": \"\",\n            \"name\": \"Show Change Log\",\n            \"identifier\": \"showChangeLog\"\n        }\n    ],\n    \"menu\": {\n        \"title\": \"✈️ Puzzle Publisher\",\n        \"items\": [\n            \"exportToHTML\",\n            \"exportToHTMLAdv\",\n            \"-\",\n            \"publish\",\n            \"publishMiro\",\n            \"-\",\n            \"externalLink\",\n            \"confLayer\",\n            \"confArtboard\",\n            \"confDocument\",\n            \"-\",\n            \"confPluginExport\",\n            \"confPluginPublishing\",\n            \"confPlugin\",\n            \"-\",\n            \"showChangeLog\"\n        ]\n    }\n}"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-cmd-other.js",
    "content": "@import(\"constants.js\")\n\nvar showChangeLog = function (context) {\n    NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(Constants.SITE_CHANGELOG_URL))\n};"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-cmd-publish-miro.js",
    "content": "@import(\"exporter/publisher.js\")\n\nvar onRun = function (context) {\n\n    UIDialog.setUp(context);\n\n    const publisher = new Publisher(context, context.document);\n    publisher.publishToMiro(true);\n\n};"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-cmd-publish.js",
    "content": "@import(\"exporter/publisher.js\")\n\nvar onRun = function (context) {\n\n    UIDialog.setUp(context);\n\n    const publisher = new Publisher(context, context.document);\n    publisher.publish();\n\n};"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-artboard.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\nvar dialog = undefined\n\nfunction enableTypeRelated() {\n    var selectedIndex = dialog.views['artboardType'].indexOfSelectedItem()\n\n    const isOverlay = Constants.ARTBOARD_TYPE_OVERLAY == selectedIndex\n\n    dialog.enableControlByID('enableShadow',\n        Constants.ARTBOARD_TYPE_MODAL == selectedIndex || Constants.ARTBOARD_TYPE_OVERLAY == selectedIndex\n    )\n    dialog.enableTextByID('overlayAlignLabel', isOverlay)\n    dialog.enableTextByID('overlayFixedLabel', isOverlay)\n    dialog.enableControlByID('overlayByEvent', isOverlay)\n    dialog.enableControlByID('overlayPin', isOverlay)\n    dialog.enableControlByID('overlayOverFixed', isOverlay)\n    dialog.enableControlByID('overlayAlsoFixed', isOverlay)\n    dialog.enableControlByID('overlayClosePrevOverlay', isOverlay)\n    dialog.enableControlByID('enableAutoScroll',\n        Constants.ARTBOARD_TYPE_REGULAR == selectedIndex || Constants.ARTBOARD_TYPE_MODAL == selectedIndex\n    )\n    dialog.enableControlByID('transNextSecs',\n        Constants.ARTBOARD_TYPE_EXTERNAL_URL != selectedIndex\n    )\n    handleOverlayPin()\n}\n\nfunction handleOverlayPin() {\n    var selectedTypeIndex = dialog.views['artboardType'].indexOfSelectedItem()\n    var selectedPinIndex = dialog.views['overlayPin'].indexOfSelectedItem()\n    const enabled = Constants.ARTBOARD_TYPE_OVERLAY == selectedTypeIndex\n\n    dialog.overlayPositions.forEach(function (positions, pinIndex) {\n        const hidden = pinIndex != selectedPinIndex\n        const imageView = dialog.views[\"overlayAlignImage-\" + pinIndex]\n        imageView.hidden = hidden\n        positions.forEach(function (label, index) {\n            const radioControl = dialog.views[\"overlayAlignRadio-\" + pinIndex + \"-\" + index]\n            radioControl.hidden = hidden\n            radioControl.enabled = enabled\n        })\n    })\n\n}\n\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const document = sketch.fromNative(context.document)\n    const selection = document.selectedLayers\n\n    UIDialog.setUp(context);\n\n    // We need the only one artboard\n    if (selection.length != 1 || selection.layers[0].type != 'Artboard') {\n        // \n        //\n        const l = selection.layers[0]\n        Settings.setLayerSettingForKey(l, \"zzzz\", \"1234\")\n        //\n        const UI = require('sketch/ui')\n        UI.alert(\"Alert\", \"Select a single artboard.\")\n        return\n    }\n    const artboard = selection.layers[0]\n\n    ///////////////// READ SETTINGS ///////////////////////\n    const enabledModal = Settings.layerSettingForKey(artboard, SettingKeys.LEGACY_ARTBOARD_MODAL) == 1\n    let artboardType = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_TYPE)\n    if (artboardType == undefined || artboardType == \"\") {\n        if (enabledModal) // take legacy settings\n            artboardType = Constants.ARTBOARD_TYPE_MODAL\n        else\n            artboardType = 0\n    }\n\n    let enableShadow = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_SHADOW)\n    if (undefined == enableShadow) {\n        const enableModalShadow = Settings.layerSettingForKey(artboard, SettingKeys.LEGACY_ARTBOARD_MODAL_SHADOW)\n        if (undefined != enableModalShadow && Constants.ARTBOARD_TYPE_MODAL == artboardType) {\n            enableShadow = enableModalShadow\n        } else {\n            enableShadow = true\n        }\n    }\n\n\n    let artboardDisableFixed = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_DISABLE_FIXED)\n    if (artboardDisableFixed == undefined || artboardDisableFixed == \"\") {\n        artboardDisableFixed = 0\n    }\n\n    const overlayOverFixed = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_OVERFIXED) == 1\n    let overlayAlsoFixed = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_ALSOFIXED)\n    overlayAlsoFixed = (overlayAlsoFixed != undefined) ? overlayAlsoFixed == 1 : true\n    const overlayClosePrevOverlay = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_CLOSE_PREVOVERLAY) == 1\n\n    const enableAutoScroll = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_DISABLE_AUTOSCROLL) != 1\n\n    let transNextSecs = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_TRANS_TO_NEXT_SECS)\n    if (undefined == transNextSecs) transNextSecs = \"\"\n\n    let transAnimType = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_TRANS_ANIM_TYPE)\n    if (undefined == transAnimType) transAnimType = Constants.ARTBOARD_TYPE_OVERLAY == artboardType ?\n        Constants.ARTBOARD_TRANS_ANIM_FADE :\n        Constants.ARTBOARD_TRANS_ANIM_NONE\n\n\n    let overlayByEvent = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_BY_EVENT)\n    if (overlayByEvent == undefined || overlayByEvent == \"\") {\n        overlayByEvent = 0\n    }\n\n    let oldOverlayAlign = Settings.layerSettingForKey(artboard, SettingKeys.OLD_ARTBOARD_OVERLAY_ALIGN)\n    let overlayPin = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN)\n    let overlayPinHotspot = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN_HOTSPOT)\n    let overlayPinPage = Settings.layerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN_PAGE)\n    if (undefined == overlayPin) {\n        const newValues = Utils.upgradeArtboardOverlayPosition(oldOverlayAlign)\n        overlayPin = newValues.pinTo\n        overlayPinHotspot = newValues.hotspotTo\n        overlayPinPage = newValues.pageTo\n    }\n\n    ///////////////// CREATE DIALOG ///////////////////////\n    dialog = new UIDialog(\"Artboard Settings\", NSMakeRect(0, 0, 480, 450), \"Save\", \"Configure exporting options for the selected artboard. \")\n    dialog.initTabs([\"General\", \"Overlay\", \"Transitions\"])\n\n    /////////////////////////// PAGE 1\n    dialog.addLeftLabel(\"\", \"Artboard Type\")\n    const types = [\"Regular page\", \"Modal Dialog\", \"External URL Page\", \"Overlay\"]\n    const typeControl = dialog.addSelect(\"artboardType\", \"\", artboardType, types, 250)\n    typeControl.setCOSJSTargetFunction(enableTypeRelated)\n\n    const enableShadowControl = dialog.addCheckbox(\"enableShadow\", \"Show modal dialog or overlay shadow\", enableShadow)\n    dialog.addHint(\"enableShadowHint\", \"Dim a previous artboard to set visual focus on an modal.\")\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Export\")\n    const artboardDisableFixedControl = dialog.addSelect(\"artboardDisableFixed\", \"Render fixed layers as regular\",\n        artboardDisableFixed, [\"(Use document setting)\", \"Yes\", \"No\"], 250)\n    /////////////////////////// PAGE 2\n\n    dialog.setTabForViewsCreating(1)\n\n    dialog.addLeftLabel(\"overlayByEventLabel\", \"Show Overlay On\")\n    const overlayByEventControl = dialog.addSelect(\"overlayByEvent\", \"\", overlayByEvent, [\"Click\", \"Mouse Over\"], 250)\n    ///\n    const overlayPins = [\n        { name: \"Hotspot\", id: \"overlay_pin_hotspot_position\", selectedIndex: overlayPinHotspot },\n        { name: \"Page\", id: \"overlay_pin_page_position\", selectedIndex: overlayPinPage }\n    ]\n    dialog.addLeftLabel(\"overlayPinLabel\", \"Pin Overlay To\")\n    const overlayPinControl = dialog.addSelect(\"overlayPin\", \"\", overlayPin, overlayPins.map(value => value.name), 250)\n    overlayPinControl.setCOSJSTargetFunction(handleOverlayPin)\n\n    ////\n    dialog.overlayPositions = [\n        [\"Under hotspot from left corner to right\", \"Under hostpot on center\", \"Under hotspot from right corner to left\",\n            \"From hotspot top left corner\", \"From hotspot top center\", \"From hotspot top right corner\",\n            \"To the right of the bottom right corner of hotspot\", \"Up from the top center of the hotspot\"],\n        [\"Page top left\", \"Page top center\", \"Page top right\",\n            \"Page bottom left\", \"Page bottom center\", \"Page bottom right\",\n            \"Page center\"]\n    ]\n    dialog.addSpace()\n    dialog.addLeftLabel(\"overlayAlignLabel\", \"Overlay Position\")\n    var overlayAlignControlRadios = []\n    const imageWidth = 342\n    const imageHeight = 170\n    const radioWidth = 20\n    const radioHeight = 18\n    const xDelta = 100\n    const yDelta = 65\n    const hSpace = 10\n    var orgFrame = dialog.getNewFrame(radioHeight, radioWidth)\n\n    dialog.overlayPositions.forEach(function (positions, pinIndex) {\n        var frame = Utils.copyRect(orgFrame)\n        const pin = overlayPins[pinIndex]\n        //\n        const imageName = \"artboard_layerposition_\" + pinIndex + \"@2x.png\"\n        var image = NSImage.alloc().initByReferencingFile(context.plugin.urlForResourceNamed(imageName).path())\n        const imageFrame = Utils.copyRect(orgFrame)\n        imageFrame.origin.y -= imageHeight - radioHeight\n        imageFrame.size.width = imageWidth\n        imageFrame.size.height = imageHeight\n        const imageView = dialog.addImage(\"overlayAlignImage-\" + pinIndex, image, imageFrame)\n        imageView.hidden = true\n        //        \n        dialog.startRadioButtions(pin.id, pin.selectedIndex)\n        //\n        positions.forEach(function (label, index) {\n            const radioControl = dialog.addRadioButton(\"overlayAlignRadio-\" + pinIndex + \"-\" + index, \" \", index, frame)\n            radioControl.toolTip = label\n            overlayAlignControlRadios.push(radioControl)\n            frame.origin.x += radioWidth\n            radioControl.hidden = true\n            //\n            if ((index + 1) % 3 === 0) {\n                frame.origin.x = dialog.leftColWidth\n                frame.origin.y -= yDelta\n            } else {\n                frame.origin.x += xDelta\n            }\n\n        }, context)\n    }, context)\n\n    dialog.y -= imageHeight\n    ///\n    dialog.addDivider()\n    dialog.addLeftLabel(\"overlayFixedLabel\", \"Fixed Panels\")\n    const overlayOverFixedControl = dialog.addCheckbox(\"overlayOverFixed\", \"Show overlay over fixed panels\", overlayOverFixed)\n    const overlayAlsoFixedControl = dialog.addCheckbox(\"overlayAlsoFixed\", \"Show overlay as fixed panel if called from fixed panel\", overlayAlsoFixed)\n    const overlayClosePrevOverlayControl = dialog.addCheckbox(\"overlayClosePrevOverlay\", \"Close the previous overlay if called from overlay\", overlayClosePrevOverlay)\n\n    // PAGE \"TRANSITION\"\n    dialog.setTabForViewsCreating(2)\n    dialog.addSpace()\n\n    dialog.addLeftLabel(\"\", \"Transition Effect\")\n    {\n        const aniTypePositions = [\n            { x: 0, y: 0, label: 'None' },\n            { x: 1, y: 0, label: \"Slide-in Up\" },\n            { x: 0, y: 1, label: \"Slide-in Left\" },\n            { x: 1, y: 1, label: \"Fade-in\" },\n            { x: 2, y: 1, label: \"Slide-in Right\" },\n            { x: 1, y: 2, label: \"Slide-in Down\" },\n        ]\n        var transAnimTypelRadios = []\n        //\n        var orgFrame = dialog.getNewFrame(radioHeight, radioWidth)\n        //\n        const imageName = \"artboard_transition_animation@2x.png\"\n        var image = NSImage.alloc().initByReferencingFile(context.plugin.urlForResourceNamed(imageName).path())\n        const imageFrame = Utils.copyRect(orgFrame)\n        imageFrame.origin.y -= imageHeight - radioHeight\n        imageFrame.size.width = imageWidth\n        imageFrame.size.height = imageHeight\n        const imageView = dialog.addImage(\"animationImage\", image, imageFrame)\n        //imageView.hidden = true\n        //        \n        dialog.startRadioButtions(\"transAnimType\", transAnimType)\n        //\n        aniTypePositions.forEach(function (info, index) {\n            var frame = Utils.copyRect(orgFrame)\n            frame.origin.x = dialog.leftColWidth + (xDelta + radioWidth) * info.x\n            frame.origin.y = orgFrame.origin.y - yDelta * info.y\n\n            const radioControl = dialog.addRadioButton(\"transAnimType-\" + index, \" \", index, frame)\n            radioControl.toolTip = info.label\n            transAnimTypelRadios.push(radioControl)\n        }, context)\n        dialog.y -= imageHeight\n    }\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Auto-transition\")\n\n    const transNextSecsControl = dialog.addTextInput(\"transNextSecs\", \"Delay (Secs)\", transNextSecs, '', 60)\n    dialog.addHint(\"transNextSecsHint\", \"Go to the next page auto the delay (0.001 - 60 secs)\")\n\n    dialog.addDivider()\n    const enableAutoScrollControl = dialog.addCheckbox(\"enableAutoScroll\", \"Scroll browser page to top\", enableAutoScroll)\n    dialog.addHint(\"enableAutoScrollHint\", \"The artboard will be scrolled on top after showing\")\n\n    enableTypeRelated()\n    ///////////////// RUN EVENT LOOP ///////////////////////\n    while (true) {\n        // Cancel clicked\n        if (!dialog.run()) break;\n\n        // OK clicked\n        // read data\n        transNextSecs = transNextSecsControl.stringValue() + \"\"\n        artboardType = typeControl.indexOfSelectedItem()\n        artboardDisableFixed = artboardDisableFixedControl.indexOfSelectedItem()\n\n        // check data        \n        if (transNextSecs != '' && isNaN(parseFloat(transNextSecs))) {\n            continue\n        }\n\n        // save data\n\n        Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_TYPE, artboardType)\n        Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_DISABLE_FIXED, artboardDisableFixed)\n\n        if (overlayByEventControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_BY_EVENT, overlayByEventControl.indexOfSelectedItem())\n        if (overlayPinControl.isEnabled) {\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN, overlayPinControl.indexOfSelectedItem())\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN_HOTSPOT, dialog.views['overlay_pin_hotspot_position'].selectedIndex)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_PIN_PAGE, dialog.views['overlay_pin_page_position'].selectedIndex)\n        }\n        if (enableShadowControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_SHADOW, enableShadowControl.state() == 1)\n        if (overlayOverFixedControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_OVERFIXED, overlayOverFixedControl.state() == 1)\n        if (overlayAlsoFixedControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_ALSOFIXED, overlayAlsoFixedControl.state() == 1)\n        if (overlayOverFixedControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_OVERLAY_CLOSE_PREVOVERLAY, overlayClosePrevOverlayControl.state() == 1)\n        if (overlayAlsoFixedControl.isEnabled)\n            if (enableAutoScrollControl.isEnabled)\n                Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_DISABLE_AUTOSCROLL, enableAutoScrollControl.state() != 1)\n        if (transNextSecsControl.isEnabled)\n            Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_TRANS_TO_NEXT_SECS, transNextSecs)\n        Settings.setLayerSettingForKey(artboard, SettingKeys.ARTBOARD_TRANS_ANIM_TYPE, dialog.views['transAnimType'].selectedIndex)\n\n        break\n\n    }\n    dialog.finish()\n\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-document.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const doc = context.document\n    const document = sketch.fromNative(doc)\n\n    UIDialog.setUp(context);\n\n    let customHideNavigation = Utils.getDocSetting(doc, SettingKeys.DOC_CUSTOM_HIDE_NAV, 0)\n    let customSortRule = Utils.getDocSetting(doc, SettingKeys.DOC_CUSTOM_SORT_RULE, -1) // 0 - use \"plugin global\" setting\n    customSortRule++ // take care about the first \"plugin global\" option\n    let customFontSizeFormat = Utils.getDocSetting(doc, SettingKeys.DOC_CUSTOM_FONTSIZE_FORMAT, 0)// 0 - use \"plugin global\" setting\n    let backColor = Utils.getDocSetting(doc, SettingKeys.DOC_BACK_COLOR)\n    let DOC_OWNER_NAME = Utils.getDocSetting(doc, SettingKeys.DOC_OWNER_NAME)\n    let DOC_OWNER_EMAIL = Utils.getDocSetting(doc, SettingKeys.DOC_OWNER_EMAIL)\n    let disableFixed = Utils.getDocSetting(doc, SettingKeys.DOC_DISABLE_FIXED_LAYERS, undefined) == 1\n    let skipAutoSync = Utils.getDocSetting(doc, SettingKeys.DOC_SKIP_AUTOSYNC, undefined) == 1\n\n    //\n    const dialog = new UIDialog(\"Document Settings\", NSMakeRect(0, 0, 500, 520), \"Save\", \"Configure settings common for all document artboards. \")\n\n    dialog.addLeftLabel(\"\", \"Export\")\n    dialog.addCheckbox(\"disableFixed\", \"Render fixed layers as regular\", disableFixed)\n    dialog.addCheckbox(\"skipAutoSync\", \"Disable auto sync with library\", skipAutoSync)\n    dialog.addHint(\"\", \"The document will not be synced with library during automation\", 30)\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Viewer\")\n    dialog.addSelect(\"customHideNavigation\", \"Navigation\", customHideNavigation, [\"(Use plugin setting)\", \"Visible\", \"Hidden\"], 250)\n\n    const sortOptions = Constants.SORT_RULE_OPTIONS.slice()\n    sortOptions.splice(0, 0, \"(Use plugin settings)\")\n    dialog.addSelect(\"customSortRule\", \"Artboards Sort Order\", customSortRule, sortOptions, 250)\n    dialog.addHint(\"\", \"Specify how artboards will sorted in HTML story.\")\n\n    const customFontSizeOptions = Constants.FONTSIZE_FORMAT_OPTIONS.slice()\n    customFontSizeOptions.splice(0, 0, \"(Use plugin settings)\")\n    dialog.addSelect(\"customFontSizeFormat\", \"Font Size Inspector Format\", customFontSizeFormat, customFontSizeOptions, 250)\n    dialog.addHint(\"\", \"Specify how font size will diplayed in Element Inspector.\")\n\n    dialog.addTextInput(\"backColor\", \"Custom Background Color\", backColor, 'e.g. #FFFFFF')\n    dialog.addHint(\"\", \"Default color is \" + Constants.DEF_BACK_COLOR, 20)\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Document Owner\")\n    dialog.addTextInput(\"DOC_OWNER_NAME\", \"Name\", DOC_OWNER_NAME, 'John Smith')\n    dialog.addHint(\"\", \"Leave empty to use global plugin settings\", 20)\n    dialog.addTextInput(\"DOC_OWNER_EMAIL\", \"Email\", DOC_OWNER_EMAIL, 'john@smith.com')\n\n    //\n    if (dialog.run()) {\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_CUSTOM_HIDE_NAV, dialog.views['customHideNavigation'].indexOfSelectedItem())\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_BACK_COLOR, dialog.views['backColor'].stringValue() + \"\")\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_OWNER_NAME, dialog.views['DOC_OWNER_NAME'].stringValue() + \"\")\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_OWNER_EMAIL, dialog.views['DOC_OWNER_EMAIL'].stringValue() + \"\")\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_DISABLE_FIXED_LAYERS, dialog.views['disableFixed'].state() == 1)\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_SKIP_AUTOSYNC, dialog.views['skipAutoSync'].state() == 1)\n\n        let customSortRule = dialog.views['customSortRule'].indexOfSelectedItem()\n        // skip back first \"plugin global\" option to use \"custom\" option\n        customSortRule--\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_CUSTOM_SORT_RULE, customSortRule)\n\n        let customFontSizeFormat = dialog.views['customFontSizeFormat'].indexOfSelectedItem()\n        Settings.setDocumentSettingForKey(doc, SettingKeys.DOC_CUSTOM_FONTSIZE_FORMAT, customFontSizeFormat)\n\n    }\n    dialog.finish()\n\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-layer.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\n\nvar onRun = function (context)\n{\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    var UI = require('sketch/ui')\n    const document = sketch.fromNative(context.document)\n    var selection = document.selectedLayers\n    var layers = selection.layers;\n\n    UIDialog.setUp(context);\n\n    // We need at least one layer\n    if (layers.length != 1)\n    {\n        UI.alert(\"alert\", \"Select some single layer.\")\n        return\n    }\n\n    var layer = layers[0]\n    if ('Artboard' == layer.type)\n    {\n        UI.alert(\"alert\", \"Select some single layer.\")\n        return\n    }\n    // read settings\n\n    var layerComment = Settings.layerSettingForKey(layer, SettingKeys.LAYER_COMMENT)\n    if (undefined == layerComment) layerComment = \"\"\n\n    let overlayType = Settings.layerSettingForKey(layer, SettingKeys.LAYER_OVERLAY_TYPE)\n    if (overlayType == undefined || overlayType == \"\")\n        overlayType = SettingKeys.LAYER_OVERLAY_DEFAULT\n\n    const isFixed = layer.sketchObject.isFixedToViewport()\n    let fixedShadowType = undefined\n    if (isFixed)\n    {\n        fixedShadowType = Settings.layerSettingForKey(layer, SettingKeys.LAYER_FIXED_SHADOW_TYPE)\n        if (fixedShadowType == undefined) fixedShadowType = 0\n    }\n\n    let vScrollType = Utils.getLayerSetting(layer, SettingKeys.LAYER_VSCROLL_TYPE, Constants.LAYER_VSCROLL_NONE)\n\n    // create dialog\n    const dialog = new UIDialog(\"Layer Settings\", NSMakeRect(0, 0, 400, 320), \"Save\", \"Configure selected layer options \")\n    dialog.removeLeftColumn()\n\n    dialog.addSelect(\"overlayType\", \"Overlay Mode\", overlayType, [\"Default (using \\\"Fix position\\\" setting)\", \"Trasparent overlay with fixed position (TOP)\", \"Trasparent overlay with fixed position (LEFT)\"], 300)\n    if (isFixed)\n    {\n        dialog.addSelect(\"fixedShadowType\", \"Fixed Layer Shadow Mode\", fixedShadowType, [\"Viewer shows a CSS-based shadow around the layer\", \"Sketch renders the layer shadow during export\"], 300)\n    }\n\n    dialog.addSelect(\"vScrollType\", \"Vertical scrolling\", vScrollType, [\"None\", \"Enabled with default scrollbar behaviour\", \"Enabled with always visible scrollbar\", \"Enabled with always hidden scrollbar\"], 300)\n    if (layer.type === \"Group\"|| layer.type === \"SymbolInstance\") \n    {\n        dialog.addHint(\"vScrollTypeHint\", \"The layer needs to be masked\")\n    } else\n    {\n        dialog.addHint(\"vScrollTypeHint\", \"The layer should be a group or a symbol instance\")\n        dialog.enableControlByID('vScrollType', false)\n    }\n\n\n    dialog.addTextBox(\"layerComment\", \"Comments\", layerComment, '')\n\n    dialog.addSpace()\n\n    //\n    while (true)\n    {\n        // Cancel clicked\n        if (!dialog.run()) break;\n\n        // OK clicked\n        // read data\n        layerComment = dialog.views['layerComment'].stringValue() + \"\"\n        overlayType = dialog.views['overlayType'].indexOfSelectedItem()\n        if (isFixed) fixedShadowType = dialog.views['fixedShadowType'].indexOfSelectedItem()\n\n\n        // check data\n        if (false)\n        {\n            continue\n        }\n\n        // save data    \n        Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_OVERLAY_TYPE, overlayType)\n        if (isFixed) Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_FIXED_SHADOW_TYPE, fixedShadowType)\n        Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_COMMENT, layerComment)\n\n        Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_VSCROLL_TYPE, dialog.views['vScrollType'].indexOfSelectedItem())\n\n        break\n\n    }\n    dialog.finish()\n\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-plugin-export.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\nconst FILE_TYPES = [\"PNG\", \"JPG\"]\n\n\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const document = sketch.fromNative(context.document)\n    const UI = require('sketch/ui')\n\n    UIDialog.setUp(context);\n\n    //let position = Settings.settingForKey(SettingKeys.PLUGIN_POSITION)\n    //if (position == undefined || position == \"\") position = 0\n\n    let sortRule = Settings.settingForKey(SettingKeys.PLUGIN_SORT_RULE)\n    if (sortRule == undefined || sortRule == \"\") sortRule = 0\n\n    let fontSizeFormat = Settings.settingForKey(SettingKeys.PLUGIN_FONTSIZE_FORMAT)\n    if (fontSizeFormat == undefined || fontSizeFormat == \"\") fontSizeFormat = 0\n\n    let fileTypeStr = Settings.settingForKey(SettingKeys.PLUGIN_FILETYPE)\n    if (fileTypeStr == undefined || fileTypeStr == \"\") fileTypeStr = \"PNG\"\n    let fileType = FILE_TYPES.indexOf(fileTypeStr)\n\n\n    const dontRetina = Settings.settingForKey(SettingKeys.PLUGIN_DONT_RETINA_IMAGES) == 1\n    const disableZoom = Settings.settingForKey(SettingKeys.PLUGIN_DISABLE_ZOOM) == 1\n    const hideNav = Settings.settingForKey(SettingKeys.PLUGIN_HIDE_NAV) == 1\n    const disableHotspots = Settings.settingForKey(SettingKeys.PLUGIN_DISABLE_HOTSPOTS) == 1\n    const askCustomSize = Settings.settingForKey(SettingKeys.PLUGIN_ASK_CUSTOM_SIZE) == 1\n    const disableLibArtboards = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_LIB_ARTBOARDS) == 1\n\n    var enabledJSON = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_INSPECTOR) != 1\n\n    let googleCode = Settings.settingForKey(SettingKeys.PLUGIN_GOOGLE_CODE)\n    if (googleCode == undefined) googleCode = ''\n    let jsCode = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_JS_CODE)\n    if (jsCode == undefined) jsCode = ''\n    let shareiFrameSize = Settings.settingForKey(SettingKeys.PLUGIN_SHARE_IFRAME_SIZE)\n    if (shareiFrameSize == undefined) shareiFrameSize = ''\n    //\n\n\n    const dialog = new UIDialog(\"Configure Export\", NSMakeRect(0, 0, 500, 560), \"Save\", \"Edit settings which are common for all documents\")\n    dialog.leftColWidth = 160\n\n\n    dialog.addLeftLabel(\"\", \"Image Format\")\n    dialog.addSelect(\"fileType\", \"\", fileType, [\"PNG\", \"JPG\"], 150)\n    dialog.addCheckbox(\"retina\", \"Support Retina Displays\", !dontRetina)\n\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Artboards\")\n\n    dialog.addCheckbox(\"askCustomSize\", \"Ask custom artboard Size\", askCustomSize)\n    dialog.addCheckbox(\"disableLibArtboards\", \"Ignore library artboards\", disableLibArtboards)\n    //dialog.addSelect(\"position\", \"Position on Browser Page\", position, [\"Default (Top)\", \"Top\", \"Center\"], 150)\n    //dialog.addHint(\"\", \"Specify how artboard will be aligned in browser page\")\n\n    dialog.addSelect(\"sortRule\", \"Artboards Sort Order\", sortRule, Constants.SORT_RULE_OPTIONS, 250)\n    dialog.addHint(\"\", \"Specify how artboards will sorted in HTML story.\")\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Viewer\")\n\n    dialog.addCheckbox(\"zoom\", \"Enable auto-scale\", !disableZoom)\n    dialog.addCheckbox(\"hidenav\", \"Show navigation\", !hideNav)\n    dialog.addCheckbox(\"disableHotspots\", \"Highlight hotspots on mouse over\", !disableHotspots)\n    dialog.addTextInput(\"jsCode\", \"Custom JS Code\", jsCode, 'e.g. alert(\"Hello\")')\n    dialog.addTextInput(\"googleCode\", \"Google Code\", googleCode, 'e.g. UA-XXXXXXXX-X')\n    dialog.addTextInput(\"shareiFrameSize\", \"Embed Code iFrame Size\", shareiFrameSize)\n    dialog.addHint(\"\", \"Use width:height format\")\n\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Element Inspector\")\n    dialog.addCheckbox(\"enabledJSON\", \"Enable Element Inspector\", enabledJSON)\n\n    const fontSizeOptions = Constants.FONTSIZE_FORMAT_OPTIONS.slice()\n    fontSizeOptions.splice(0, 0, \"Default (\" + fontSizeOptions[0] + \")\")\n    dialog.addSelect(\"fontSizeFormat\", \"Font Size Format\", fontSizeFormat, fontSizeOptions, 250)\n    dialog.addHint(\"\", \"Specify how font size will diplayed in Element Inspector.\")\n\n    if (dialog.run()) {\n        //Settings.setSettingForKey(SettingKeys.PLUGIN_POSITION, dialog.views['position'].indexOfSelectedItem())\n        Settings.setSettingForKey(SettingKeys.PLUGIN_SORT_RULE, dialog.views['sortRule'].indexOfSelectedItem())\n        Settings.setSettingForKey(SettingKeys.PLUGIN_FONTSIZE_FORMAT, dialog.views['fontSizeFormat'].indexOfSelectedItem())\n        Settings.setSettingForKey(SettingKeys.PLUGIN_DONT_RETINA_IMAGES, dialog.views['retina'].state() != 1)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_DISABLE_ZOOM, dialog.views['zoom'].state() != 1)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_GOOGLE_CODE, dialog.views['googleCode'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_JS_CODE, dialog.views['jsCode'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_SHARE_IFRAME_SIZE, dialog.views['shareiFrameSize'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_HIDE_NAV, dialog.views['hidenav'].state() != 1)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_DISABLE_HOTSPOTS, dialog.views['disableHotspots'].state() != 1)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_ASK_CUSTOM_SIZE, dialog.views['askCustomSize'].state() == 1)\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_LIB_ARTBOARDS, dialog.views['disableLibArtboards'].state() == 1)\n\n        enabledJSON = dialog.views['enabledJSON'].state() == 1\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_DISABLE_INSPECTOR, !enabledJSON)\n\n        // convert position in FILE_TYPES to file type string\n        fileType = dialog.views['fileType'].indexOfSelectedItem()\n        Settings.setSettingForKey(SettingKeys.PLUGIN_FILETYPE, FILE_TYPES[fileType])\n    }\n    dialog.finish()\n\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-plugin-publishing.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\nconst UI = require('sketch/ui')\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const document = sketch.fromNative(context.document)\n\n    UIDialog.setUp(context);\n\n    let login = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_LOGIN)\n    if (login == undefined || login == null) login = ''\n    let siteRoot = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SITEROOT)\n    if (siteRoot == undefined || siteRoot == null) siteRoot = ''\n    let secret = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SECRET)\n    if (secret == undefined || secret == null) secret = ''\n\n    let sshPort = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_SSH_PORT)\n    if (sshPort == undefined || sshPort == null) sshPort = ''\n\n    let commentsURL = Settings.settingForKey(SettingKeys.PLUGIN_COMMENTS_URL)\n    if (commentsURL == undefined) commentsURL = ''\n    let serverToolsPath = Settings.settingForKey(SettingKeys.PLUGIN_SERVERTOOLS_PATH)\n    if (serverToolsPath == undefined) serverToolsPath = ''\n    let curlPath = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_CURL_PATH)\n    if (curlPath == undefined) curlPath = ''\n    let authorName = Settings.settingForKey(SettingKeys.PLUGIN_AUTHOR_NAME)\n    if (authorName == undefined) authorName = ''\n    let authorEmail = Settings.settingForKey(SettingKeys.PLUGIN_AUTHOR_EMAIL)\n    if (authorEmail == undefined) authorEmail = ''\n\n    const miroEnabled = Settings.settingForKey(SettingKeys.PLUGIN_PUBLISH_MIRO_ENABLED) == 1\n\n\n    const dialog = new UIDialog(\"Configure Publishing\", NSMakeRect(0, 0, 700, 700), \"Save\", \"Edit settings which are common for all documents.\")\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"SFTP Server\\nCredentials\", 40)\n\n    dialog.addTextInput(\"login\", \"Login\", login, 'html@mysite.com:/var/www/html/', 500)\n    dialog.addHint(\"loginHint\", \"SSH key should be uploaded to the site already\")\n\n    dialog.addTextInput(\"sshPort\", \"SSH Port\", sshPort, '22', 350)\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Site Settings\", 40)\n\n    dialog.addTextInput(\"siteRoot\", \"Site Root URL\", siteRoot, 'http://mysite.com', 350)\n    dialog.addHint(\"siteRootHint\", \"Specify to open uploaded HTML in web browser automatically\")\n    dialog.addTextInput(\"serverToolsPath\", \"Relative URL to Server Tools\", serverToolsPath, '/_tools/')\n    dialog.addTextInput(\"secret\", \"Site Secret Key\", secret, '', 350)\n    dialog.addTextInput(\"commentsURL\", \"Commenting URL\", commentsURL, '', 500)\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Integration with Miro\", 40)\n    dialog.addCheckbox(\"miroEnabled\", \"Enabled\", miroEnabled)\n    dialog.addHint(\"miroEnabledHint\", \"You need to log into Miro using Miro plugin https://github.com/miroapp/sketch_plugin\")\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Author\", 40)\n\n    dialog.addTextInput(\"authorName\", \"Name\", authorName, 'John Smith')\n    dialog.addTextInput(\"authorEmail\", \"Email\", authorEmail, 'johns@company.com')\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Other\", 40)\n    dialog.addTextInput(\"curlPath\", \"Path to curl\", curlPath, Constants.CURL_PATH)\n\n\n    let resultOk = false\n    while (dialog.run()) {\n        sshPort = dialog.views['sshPort'].stringValue() + \"\"\n        if (sshPort != \"\") {\n            let sshPortInt = parseInt(sshPort)\n            if (isNaN(sshPortInt)) continue\n            sshPort = sshPortInt + \"\"\n        }\n\n        Settings.setSettingForKey(SettingKeys.PLUGIN_COMMENTS_URL, dialog.views['commentsURL'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_LOGIN, dialog.views['login'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_SSH_PORT, sshPort)\n\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_MIRO_ENABLED, dialog.views['miroEnabled'].state() == 1)\n\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_SITEROOT, dialog.views['siteRoot'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_SECRET, dialog.views['secret'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_SERVERTOOLS_PATH, dialog.views['serverToolsPath'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_AUTHOR_NAME, dialog.views['authorName'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_AUTHOR_EMAIL, dialog.views['authorEmail'].stringValue() + \"\")\n        Settings.setSettingForKey(SettingKeys.PLUGIN_PUBLISH_CURL_PATH, dialog.views['curlPath'].stringValue() + \"\")\n        resultOk = true\n        break\n    }\n    dialog.finish()\n\n    if (resultOk) {\n    }\n};"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-conf-plugin.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\";\n\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const document = sketch.fromNative(context.document)\n    const UI = require('sketch/ui')\n\n    UIDialog.setUp(context);\n\n    // Read settings\n    let logDebug = Settings.settingForKey(SettingKeys.PLUGIN_LOGDEBUG_ENABLED) == 1\n    let gaEnabled = !Settings.settingForKey(SettingKeys.PLUGIN_GA_DISABLED)\n\n    // Build dialog\n    const dialog = new UIDialog(\"Configure\", NSMakeRect(0, 0, 400, 200), \"Save\", \"Edit Puzzle Publisher common configuration settings.\")\n\n    dialog.addCheckbox(\"logDebug\", \"Enable debug logging\", logDebug)\n\n    dialog.addDivider()\n    dialog.addLeftLabel(\"\", \"Privacy\", 40)\n    dialog.addCheckbox(\"gaEnabled\", \"Share analytics with PT developer\", gaEnabled)\n    dialog.addHint(\"gaEnabledHint\", \"Help improve Puzzle Publisher by automatically sending usage data. Usage data is collected anonymously and cannot be used to identify you.\", 40)\n\n    // Run event loop\n    while (true) {\n        const result = dialog.run()\n        if (!result) {\n            dialog.finish()\n            return false\n        }\n        logDebug = dialog.views['logDebug'].state() == 1\n        gaEnabled = dialog.views['gaEnabled'].state() == 1\n\n        break\n    }\n    dialog.finish()\n\n    // Save updated settings\n    Settings.setSettingForKey(SettingKeys.PLUGIN_LOGDEBUG_ENABLED, logDebug)\n    Settings.setSettingForKey(SettingKeys.PLUGIN_GA_DISABLED, !gaEnabled)\n\n    return true\n\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-edit-annotations.js",
    "content": "@import(\"lib/uidialog.js\")\n@import(\"lib/utils.js\")\n@import(\"constants.js\")\n\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const UI = require('sketch/ui')\n    const document = sketch.fromNative(context.document)\n    const selection = document.selectedLayers\n\n    UIDialog.setUp(context)\n\n    // Check selection\n    if (selection.length != 1) {\n        UI.alert(\"alert\", \"Select only one layer.\")\n        return\n    }\n    var layer = selection.layers[0]\n\n    // Get current settings for this layer (and reset to default if undefined)\n    //--------------------------------------------------------------------\n    var annotations = Settings.layerSettingForKey(layer, SettingKeys.LAYER_ANNOTATIONS)\n    if (annotations == undefined || annotations == null) {\n        annotations = \"\"\n    }\n\n    const dialog = new UIDialog(\"Edit Layer Annotations\", NSMakeRect(0, 0, 300, 200), \"Save\", \"Annotations will be visible in generated HTML.\")\n    dialog.addTextBox(\"annotations\", \"\", annotations, \"\", 200)\n\n\n    if (dialog.run()) {\n        Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_ANNOTATIONS, dialog.views['annotations'].stringValue() + \"\")\n    }\n    dialog.finish()\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-export-html-adv.js",
    "content": "@import \"lib/utils.js\"\n@import \"lib/uidialog.js\"\n@import \"exporter/exporter-run.js\"\n@import \"constants.js\"\n\n\nfunction askMode(context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n    const document = sketch.fromNative(context.document)\n\n    UIDialog.setUp(context);\n\n    let mode = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_MODE)\n    if (mode == undefined || mode == \"\") mode = Constants.EXPORT_MODE_SELECTED_ARTBOARDS\n\n    //\n    const dialog = new UIDialog(\"Export to HTML\", NSMakeRect(0, 0, 300, 80), \"Export\", \"\")\n    dialog.removeLeftColumn()\n\n    dialog.addRadioButtons(\"mode\", \"\", mode, [\"Selected artboards\", \"Current page artboards\"], 250)\n\n    if (dialog.run()) {\n        mode = dialog.views['mode'].selectedIndex\n        Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_MODE, mode)\n    } else {\n        mode = -1\n    }\n    dialog.finish()\n\n    return mode\n}\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    const Settings = require('sketch/settings')\n\n    const nDoc = require('sketch/dom').Document.getSelectedDocument()\n    const document = sketch.fromNative(nDoc)\n    var UI = require('sketch/ui')\n\n    const mode = askMode(context)\n    if (mode < 0) return\n\n    const modeOptions = {\n        \"mode\": mode,\n        \"selectedLayers\": null,\n        \"selectedArtboards\": null,\n        \"currentPage\": document.selectedPage\n    }\n\n    // check is something to export    \n    if (mode == Constants.EXPORT_MODE_SELECTED_ARTBOARDS) {\n        const filteredArtboards = []\n        for (var i = 0; i < document.selectedLayers.length; i++) {\n            const l = document.selectedLayers.layers[i]\n            if (l.type == 'Artboard') filteredArtboards.push(l)\n        }\n        if (filteredArtboards.length == 0) {\n            UI.alert(\"alert\", \"There are no selected artboards to export.\")\n            return\n        }\n        modeOptions['selectedArtboards'] = filteredArtboards\n\n    } else if (mode == Constants.EXPORT_MODE_CURRENT_PAGE) {\n    } else {\n        return\n    }\n    runExporter(context, modeOptions)\n};\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-export-html.js",
    "content": "@import \"exporter/exporter-run.js\"\n\nvar onRun = function (context) {\n    return runExporter(context)\n};\n\n\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/menu-external-link.js",
    "content": "@import \"lib/uidialog.js\";\n@import \"lib/utils.js\";\n@import \"constants.js\"\n\nvar onRun = function (context) {\n    const sketch = require('sketch')\n    var UI = require('sketch/ui')\n    var Settings = require('sketch/settings')\n    UIDialog.setUp(context);\n\n\n    const document = sketch.fromNative(context.document)\n    var selection = document.selectedLayers\n\n    // We need at least one layer\n    //--------------------------------------------------------------------\n    if (selection.length == 0) {\n        UI.alert(\"alert\", \"Select at least one layer.\")\n        return\n    }\n    var layers = selection.layers;\n\n    // Get current settings for this layer (and reset to default if undefined)\n    //--------------------------------------------------------------------\n    var link = \"\"\n    var openNewWindow = false\n\n    if (layers.length == 1) {\n        var layer = layers[0]\n        // restore settings for a single layer selected\n        link = Settings.layerSettingForKey(layer, SettingKeys.LAYER_EXTERNAL_LINK)\n        if (undefined == link || 'http://' == link) link = \"\"// workaround to fix previous wrong dialog behaviour\n        openNewWindow = Settings.layerSettingForKey(layer, SettingKeys.LAYER_EXTERNAL_LINK_BLANKWIN) == 1\n    }\n\n    // Ask user for external URL\n    //--------------------------------------------------------------------\n    const dialog = new UIDialog(\"Provide some external URL\", NSMakeRect(0, 0, 400, 200), \"Save\", \"The selected layers or artboards will be linked to the specified URL.\")\n\n    dialog.removeLeftColumn()\n    dialog.addTextBox(\"url\", \"URL\", link, 'http://', 60)\n    dialog.addHint(\"url_hint\", \"You can specify absolut URL, like http://news.com, or any relative URL, like /test.html\", 30)\n    dialog.addSpace()\n    dialog.addCheckbox(\"sameWindow\", \"Open URL in new browser window\", openNewWindow)\n\n\n    //Save new external URL\n    //--------------------------------------------------------------------\n    while (true) {\n        // Cancel clicked\n        if (!dialog.run()) break;\n\n        // OK clicked\n        // read data\n        link = dialog.views['url'].stringValue() + \"\"\n        openNewWindow = dialog.views['sameWindow'].state() == 1\n\n        // check data\n        if (false) {\n            continue\n        }\n\n        // save data  \n        layers.forEach(function (layer) {\n            Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_EXTERNAL_LINK, link)\n            Settings.setLayerSettingForKey(layer, SettingKeys.LAYER_EXTERNAL_LINK_BLANKWIN, openNewWindow)\n        })\n\n        break\n    }\n\n\n    dialog.finish()\n}"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/miro/api.js",
    "content": "/*\nThe MIT License (MIT)\n\nCopyright (c) 2019 Miro Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\nvar api = new Api();\nvar siteURL = \"https://miro.com/\";\nvar path = siteURL + \"api/v1/\";\nvar appURL = siteURL + \"app/\";\nvar boardURL = appURL + \"board/\";\nvar ssoURL = siteURL + \"sso/login/?sketch=1\";\nvar googleOAuthURL = \"https://accounts.google.com/Logout?continue=https://accounts.google.com/o/oauth2/auth?access_type%3Doffline%26response_type%3Dcode%26client_id%3D1062019541050-8mvc17gv9c3ces694hq5k1h6uqio1cfn.apps.googleusercontent.com%26scope%3Dprofile%2520email%26include_granted_scopes%3Dtrue%26redirect_uri%3Dhttps%3A%2F%2Fmiro.com%2Fsocial%2Fgoogle%2F\";\nvar exportPath = NSTemporaryDirectory() + \"sketch-rtb-export/\";\n\nfunction dealWithErrors(context, message) {\n    var alert = [[NSAlert alloc] init];\n    [alert setMessageText: \"Connection error\"];\n\n    if (message) {\n        [alert setInformativeText: message];\n    } else {\n        [alert setInformativeText: \"Please check your internet connection and retry.\"];\n    }\n\n    [alert runModal];\n}\n\nfunction removeForbiddenCharacters(string) {\n    return string.replace(/[\\\\/,]/g, \"\");\n}\n\nfunction encodeHtmlSpecialCharacters(string) {\n    return string.replace(/&/gi, '&amp;')\n        .replace(/</gi, '&lt;')\n        .replace(/>/gi, '&gt;')\n        .split('\"')\n        .join('&quot;')\n}\n\nfunction Api() {\n    Api.prototype.UploadEnum = {\n        SUCCESS: 1,\n        NO_ARTBOARDS: 2,\n        NO_ARTBOARDS_SELECTED: 3,\n        UPLOAD_FAILED: 4\n    }\n\n    Api.prototype.setSetting = function (name, value) {\n        [[NSUserDefaults standardUserDefaults] setObject: value forKey: name];\n        [[NSUserDefaults standardUserDefaults] synchronize];\n    }\n\n    Api.prototype.getSetting = function (name) {\n        var value = [[NSUserDefaults standardUserDefaults] objectForKey: name];\n\n        if (value) {\n            return value;\n        } else {\n            return false;\n        }\n    }\n\n    Api.prototype.setFromRetina = function (fromRetina) {\n        this.setSetting(\"from_retina\", fromRetina);\n    }\n\n    Api.prototype.getFromRetina = function () {\n        return this.getSetting(\"from_retina\");\n    }\n\n    Api.prototype.setToken = function (token) {\n        this.setSetting(\"rtb_token\", token);\n    }\n\n    Api.prototype.getToken = function () {\n        return this.getSetting(\"rtb_token\");\n    }\n\n    Api.prototype.setLastBoardId = function (boardId) {\n        this.setSetting(\"last_board_id\", boardId);\n    }\n\n    Api.prototype.getLastBoardId = function () {\n        return this.getSetting(\"last_board_id\");\n    }\n\n    Api.prototype.setOpenBoard = function (openBoard) {\n        this.setSetting(\"open_board\", openBoard);\n    }\n\n    Api.prototype.getOpenBoard = function () {\n        return this.getSetting(\"open_board\");\n    }\n\n    Api.prototype.getBoards = function (context) {\n        var accountsResult = api.accountsRequest(context);\n\n        if (accountsResult) {\n            accountsResult = accountsResult.filter(function (item) {\n                return !item.expired;\n            });\n            var boards = [];\n\n            for (var i = 0; i < accountsResult.length; i++) {\n                var accountBoards = api.boardsRequest(context, accountsResult[i].id);\n\n                if (accountBoards) {\n                    accountBoards = accountBoards.data;\n\n                    for (var j = 0; j < accountBoards.length; j++) {\n                        if (accountBoards[j].currentUserPermission.role == \"EDITOR\"\n                            || accountBoards[j].currentUserPermission.role == \"OWNER\") {\n                            var title = accountBoards[j][\"title\"]\n                            var board = {\n                                boardId: accountBoards[j][\"id\"],\n                                title: title,\n                                lastOpenedByMeDate: accountBoards[j][\"lastOpenedByMeDate\"]\n                            };\n                            if (accountBoards[j]['project'] != null) {\n                                board['project'] = accountBoards[j]['project'][\"title\"]\n                            }\n\n                            // fix name duplicates\n                            let dupIndex = 0\n                            while (boards.find(\n                                function (el) {\n                                    return el['title'] == (title + (dupIndex == 0 ? \"\" : (\" \" + dupIndex)))\n                                }\n                            )) {\n                                dupIndex++\n                            }\n                            if (dupIndex > 0) {\n                                board[\"title\"] = board[\"title\"] + \" \" + dupIndex\n                            }\n                            //\n\n                            boards.push(board);\n                        }\n                    }\n                }\n            }\n\n            boards.sort(function (a, b) {\n                if (a[\"title\"] > b[\"title\"]) {\n                    return 1;\n                }\n                if (a[\"title\"] < b[\"title\"]) {\n                    return -1;\n                }\n            })\n            return boards;\n        }\n        return false;\n    }\n\n    Api.prototype.authRequest = function (context, data) {\n        var result = this.request(context, \"auth\", \"POST\", data);\n        return result;\n    }\n\n    Api.prototype.authCheckRequest = function (context, errorHandlingInfo) {\n        var token = this.getToken();\n        var result = false;\n\n        if (token) {\n            var url = \"auth/check\";\n            var data = { token: token };\n            result = this.request(context, url, \"POST\", data, errorHandlingInfo);\n        }\n\n        return result;\n    }\n\n    Api.prototype.logoutRequest = function (context) {\n        var token = this.getToken();\n        var result = false;\n\n        if (token) {\n            var url = \"auth/logout\";\n            var data = { token: token };\n            result = this.request(context, url, \"POST\", data);\n        }\n\n        return result;\n    }\n\n    Api.prototype.SSOAuth = function (context, email) {\n        var result = false;\n        var url = \"sso/saml/info?email=\" + email;\n\n        result = this.request(context, url, \"GET\");\n\n        return result;\n    }\n\n    Api.prototype.accountsRequest = function (context) {\n        var token = this.getToken();\n        var result = null;\n\n        if (token) {\n            var url = \"accounts/?fields=id,title,currentUserPermission,expired\";\n            result = this.request(context, url, \"GET\", null);\n        }\n\n        return result;\n    }\n\n    Api.prototype.boardsRequest = function (context, accountId) {\n        var token = this.getToken();\n        var result = null;\n\n        if (token) {\n            var url = \"boards/?sort=LAST_OPENED&attachment=\" + accountId + \"&fields=title,project,id,currentUserPermission{role},lastOpenedByMeDate&limit=1000\";\n            result = this.request(context, url, \"GET\", null);\n        }\n\n        return result;\n    }\n\n    Api.prototype.request = function (context, url, method, data, errorHandlingInfo) {\n        var fullURL = path + url;\n        var stringURL = [NSString stringWithFormat: fullURL];\n        var webStringURL = [stringURL stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding].replace(/\\+/g, '%2B');\n        var request = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: webStringURL]];\n\n        [request setHTTPMethod: method];\n        [request setValue: \"application/json\" forHTTPHeaderField: \"Accept\"];\n        [request setValue: \"application/json\" forHTTPHeaderField: \"Content-Type\"];\n\n        var token = this.getToken();\n        var auth = \"hash \" + token;\n        var authHeader = \"Authorization\";\n\n        [request setValue: auth forHTTPHeaderField: authHeader];\n\n        if (data) {\n            var postData = [NSJSONSerialization dataWithJSONObject: data options: NSUTF8StringEncoding error: nil];\n            [request setHTTPBody: postData];\n        }\n\n        var response = [[MOPointer alloc] init];\n        var dataResp = [NSURLConnection sendSynchronousRequest: request returningResponse: response error: nil];\n\n        if (dataResp != nil) {\n            var responseText = [[NSString alloc] initWithData: dataResp encoding: NSUTF8StringEncoding];\n\n            try {\n                var json = JSON.parse(responseText);\n\n                if (json.error) {\n                    logErr(JSON.stringify(json.error));\n                }\n\n                return json;\n            } catch (e) {\n                var message = \"Unable to parse response data for path: \" + url;\n\n                dealWithErrors(context, message);\n            }\n        } else {\n            logErr('dataResp == null');\n\n            dealWithErrors(context);\n        }\n\n        return false;\n    }\n\n    Api.prototype.uploadArtboardsToRTB = function (context, boardId, exportAll) {\n        var fullURL = path + \"boards/\" + boardId + \"/integrations/imageplugin\";\n        var stringURL = [NSString stringWithFormat: fullURL];\n        var webStringURL = [stringURL stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];\n        var token = this.getToken();\n        var auth = \"hash \" + token;\n        var scale = this.getFromRetina() == 1 ? 2 : 1;\n        var exportInfoList = this.artboardsToPNG(context, exportAll, scale);\n\n        if (exportInfoList.length == 0) {\n            var document = context.document;\n            var page = [document currentPage];\n            var artboards = [page artboards];\n\n            if (artboards.length == 0) {\n                return this.UploadEnum.NO_ARTBOARDS;\n            } else {\n                return this.UploadEnum.NO_ARTBOARDS_SELECTED;\n            }\n        }\n\n        var task = [[NSTask alloc] init];\n        [task setLaunchPath: \"/usr/bin/curl\"];\n\n        var makeDataString = function (transformationData, sizeData, identifier) {\n            if (!transformationData) {\n                transformationData = '';\n            }\n\n            var idField = identifier ? '\"id\": \"' + identifier + '\",' : '';\n\n            return '{' + idField + '\"type\": \"ImageWidget\",\"json\": \"{\\\\\"transformationData\\\\\": { ' + transformationData + '}}\"}';\n        };\n\n        var dataString = '',\n            dataArray = [];\n        var largeX = 0;\n        var lastPageId = null;\n        var marginX = 0;\n        var pageMarginX = 0\n        var pageMarginY = 0\n        var rightX = 0\n        const sketch = require('sketch');\n\n        for (var i = 0; i < exportInfoList.length; i++) {\n            var artboard = exportInfoList[i].artboard;\n            var resourceId = context.command.valueForKey_onLayer_forPluginIdentifier(boardId, artboard, \"rtb_sync\");\n            var originalId = context.command.valueForKey_onLayer_forPluginIdentifier(\"originalId\", artboard, \"rtb_sync\");\n            var objectId = [artboard objectID];\n            var absoluteInfluenceRect = [artboard absoluteInfluenceRect];\n            var pageId = artboard.parentGroup().objectID()\n\n            if (pageId != lastPageId) {\n                marginX = largeX + 200\n                lastPageId = pageId\n                pageMarginX = 0 - absoluteInfluenceRect.origin.x\n                pageMarginY = 0 - absoluteInfluenceRect.origin.y\n            }\n\n            var xPos = absoluteInfluenceRect.origin.x + marginX + pageMarginX;\n            var yPos = absoluteInfluenceRect.origin.y + pageMarginY;\n            var width = absoluteInfluenceRect.size.width;\n            var height = absoluteInfluenceRect.size.height;\n\n            if ((xPos - largeX) > 300) {\n                log(\"name: \" + artboard.name())\n                log(\"old xPos: \" + xPos)\n                log(\"largeX: \" + largeX)\n                log(\"old MarginX: \" + marginX)\n                marginX -= xPos - largeX - 300\n                log(\"new MarginX: \" + marginX)\n                xPos = absoluteInfluenceRect.origin.x + marginX + pageMarginX;\n                log(\"new xPos: \" + xPos)\n            }\n\n            var centralXPos = width / 2 + xPos;\n            var centralYPos = height / 2 + yPos;\n            var transformationData = '\\\\\"positionData\\\\\":{\\\\\"x\\\\\": ' + centralXPos + ', \\\\\"y\\\\\":' + centralYPos + ' }';\n\n            rightX = xPos + width\n            if (rightX > largeX) largeX = rightX  // save new right edge\n\n            if (scale == 2) {\n                transformationData += ', \\\\\"scaleData\\\\\":{\\\\\"scale\\\\\": ' + 0.5 + ' }';\n            }\n\n            var sizeData = '\\\\\"width\\\\\": ' + width + ', \\\\\"height\\\\\":' + height;\n\n            if (resourceId != nil && (originalId == nil || [objectId isEqualToString: originalId])) {\n                dataArray.push(makeDataString(transformationData, sizeData, resourceId));\n            } else {\n                dataArray.push(makeDataString(transformationData, sizeData));\n            }\n\n            if (originalId != objectId) {\n                if (resourceId != nil) {\n                    context.command.setValue_forKey_onLayer_forPluginIdentifier(nil, boardId, artboard, \"rtb_sync\");\n                }\n\n                context.command.setValue_forKey_onLayer_forPluginIdentifier(objectId, \"originalId\", artboard, \"rtb_sync\");\n            }\n        }\n\n        dataString = dataArray.join(', ');\n\n        var graphicsPluginRequest = 'GraphicsPluginRequest={\"data\":[' + dataString + ']};type=application/json ';\n        var args = [[NSMutableArray alloc] init];\n\n        args.addObject(\"-v\");\n        args.addObject(\"POST\");\n        args.addObject(\"--header\");\n        args.addObject(\"Content-Type: multipart/form-data\");\n        args.addObject(\"--header\");\n        args.addObject(\"Authorization: \" + auth);\n        args.addObject(\"--header\");\n        args.addObject(\"Accept: application/json\");\n        args.addObject(\"--header\");\n        args.addObject(\"X-Requested-With: XMLHttpRequest\");\n        args.addObject(\"--header\");\n        args.addObject(\"Connection: keep-alive\");\n        args.addObject(\"--compressed\");\n        args.addObject(\"-F\");\n        args.addObject(graphicsPluginRequest);\n\n        for (var i = 0; i < exportInfoList.length; i++) {\n            args.addObject(\"-F\");\n            args.addObject(\"ArtboardName1=@\" + exportInfoList[i][\"path\"]);\n        }\n\n        args.addObject(fullURL);\n\n        [task setArguments: args];\n\n        var outputPipe = [NSPipe pipe];\n\n        [task setStandardOutput: outputPipe];\n        [task launch];\n\n        var outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile];\n\n        this.clearExportFolder();\n\n        var classNameOfOuput = NSStringFromClass([outputData class]);\n\n        if (classNameOfOuput != \"_NSZeroData\") {\n            var res = [NSJSONSerialization JSONObjectWithData: outputData options: NSJSONReadingMutableLeaves error: nil]\n            if (res != null) {\n                if (res.error != nil) {\n                    logErr(res.error)\n                } else {\n                    for (var i = 0; i < res.widgets.length; i++) {\n                        var artboard = exportInfoList[i];\n                        context.command.setValue_forKey_onLayer_forPluginIdentifier(res.widgets[i][\"resourceId\"], boardId, artboard.artboard, \"rtb_sync\");\n                    }\n                    return this.UploadEnum.SUCCESS;\n                }\n            } else {\n                logErr('res == null')\n            }\n        } else {\n            logErr('classNameOfOuput == _NSZeroData')\n        }\n\n        return this.UploadEnum.UPLOAD_FAILED;\n    }\n\n    Api.prototype.artboardsToPNG = function (context, exportAll, scale) {\n        var document = context.document;\n        var page = [document currentPage];\n        var artboards = [page artboards];\n        var exportInfoList = [];\n\n        for (var i = 0; i < artboards.length; i++) {\n            if (exportAll == 1 || (artboards[i].isSelected() && exportAll == 0)) {\n                var msartboard = artboards[i];\n                var artboardID = [msartboard objectID];\n                var name = removeForbiddenCharacters([msartboard name]);\n                var path = exportPath + \"/\" + artboardID + \"/\" + name + \".png\";\n                var format = [[MSExportFormat alloc] init];\n\n                format.fileFormat = \"png\";\n                format.scale = scale;\n\n                var exportRequest = [[MSExportRequest exportRequestsFromExportableLayer: msartboard exportFormats: [format] useIDForName: true] firstObject];\n                [document saveArtboardOrSlice: exportRequest toFile: path];\n\n                var exportInfo = { \"artboardID\": artboardID, \"artboard\": msartboard, \"path\": path };\n                exportInfoList.push(exportInfo);\n            }\n        }\n\n        return exportInfoList;\n    }\n\n    Api.prototype.clearExportFolder = function () {\n        var manager = [NSFileManager defaultManager];\n        [manager removeItemAtPath: exportPath error: nil];\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/miro/utils.js",
    "content": "/*\nThe MIT License (MIT)\n\nCopyright (c) 2017 RealtimeBoard Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\nvar ErrorReasons = {\n  userBlocked: \"userBlocked\",\n  authorizationFailed: \"authorizationFailed\",\n  suspiciousActivity: \"suspiciousActivity\",\n  termsViolation: \"termsViolation\",\n  userDeleted: \"userDeleted\",\n  userLockout: \"userLockout\"\n};\n\nvar ErrorMessages = {\n  suspiciousActivity: 'Your account is locked due to suspicious activity.',\n  termsViolation: 'Your account is locked due to a Terms of Use violation.',\n  userDeleted: 'Your account is currently being deleted.',\n  userDeletedDesc: 'Until then, you cannot log in or create an account with this email.',\n  userLockout: 'Your account is locked due to multiple failed login attempts for 60 minutes. To unlock your account please wait or visit https://realtimeboard.com/email-unlock/',\n  contactUs: 'Please contact our support team at support@realtimeboard.com for further assistance.',\n  passwordIncorrect: 'The username or password you entered is incorrect.',\n  defaultLockout: 'Your account is locked.'\n};\n\nfunction getMessagesByError(error) {\n  var messages = {\n    label: nil,\n    alert: nil\n  }\n\n  if (error.code == 403) {\n    var messageEnd = \"\";\n\n    if (error.reason == ErrorReasons.suspiciousActivity || error.reason == ErrorReasons.termsViolation) {\n      messageEnd = ErrorMessages.contactUs;\n    } else if (error.reason == ErrorReasons.userDeleted) {\n      messageEnd = ErrorMessages.userDeletedDesc;\n    } else if (error.reason == ErrorReasons.userLockout) {\n      messageEnd = ErrorMessages.userLockout;\n    }\n\n    if (error.reason == ErrorReasons.userLockout) {\n      messages.label = ErrorMessages.defaultLockout;\n      messages.alert = ErrorMessages[error.reason];\n    } else {\n      messages.label = ErrorMessages[error.reason];\n      messages.alert = ErrorMessages[error.reason] + \" \" + messageEnd;\n    }\n  } else {\n    messages.label = ErrorMessages.passwordIncorrect;\n  }\n\n  return messages;\n}\n\nfunction makeSubclass(className, BaseClass, selectorHandlerDict) {\n  var uniqueClassName = className + NSUUID.UUID().UUIDString();\n  var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, BaseClass);\n\n  for (var selectorString in selectorHandlerDict) {\n    delegateClassDesc.addInstanceMethodWithSelector_function_(selectorString, selectorHandlerDict[selectorString]);\n  }\n\n  delegateClassDesc.registerClass();\n\n  return NSClassFromString(uniqueClassName);\n}\n\nfunction logMsg(msg) {\n  log('[sketch-rtb]: ' + msg)\n}\n\nfunction logErr(err) {\n  log('[sketch-rtb-error]: ' + err)\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/exporter/exporter-build-html.js",
    "content": "const ExporterConstants = {\n    DOCUMENT_VERSION: \"docVersion\",\n    DOCUMENT_VERSION_PLACEHOLDER: \"V_V_V\",\n    DOCUMENT_AUTHOR_NAME_PLACEHOLDER: \"V_V_N\",\n    DOCUMENT_AUTHOR_EMAIL_PLACEHOLDER: \"V_V_E\",\n    DOCUMENT_COMMENTS_URL_PLACEHOLDER: \"V_V_C\"\n}\n\nfunction buildMainHTML_NavigationIcons(options)\n{\n    return `<div class=\"containerSVG\"> <svg class=\"svgIcon\">\n    <symbol ID=\"icMenu\" viewBox=\"0 0 24 24\">\n        <path d=\"M4,14 C2.8954305,14 2,13.1045695 2,12 C2,10.8954305 2.8954305,10 4,10 C5.1045695,10 6,10.8954305 6,12 C6,13.1045695 5.1045695,14 4,14 Z M12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 Z M20,14 C18.8954305,14 18,13.1045695 18,12 C18,10.8954305 18.8954305,10 20,10 C21.1045695,10 22,10.8954305 22,12 C22,13.1045695 21.1045695,14 20,14 Z\" />\n    </symbol>\n    <symbol ID=\"icArrwLeft\" viewBox=\"0 0 24 24\">\n        <path d=\"M14.7071068,16.2928932 C15.0976311,16.6834175 15.0976311,17.3165825 14.7071068,17.7071068 C14.3165825,18.0976311 13.6834175,18.0976311 13.2928932,17.7071068 L8.29289322,12.7071068 C7.90236893,12.3165825 7.90236893,11.6834175 8.29289322,11.2928932 L13.2928932,6.29289322 C13.6834175,5.90236893 14.3165825,5.90236893 14.7071068,6.29289322 C15.0976311,6.68341751 15.0976311,7.31658249 14.7071068,7.70710678 L10.4142136,12 L14.7071068,16.2928932 Z\" />\n    </symbol>\n    <symbol ID=\"icArrwRight\" viewBox=\"0 0 24 24\">\n        <path d=\"M15.7071068,16.2928932 C16.0976311,16.6834175 16.0976311,17.3165825 15.7071068,17.7071068 C15.3165825,18.0976311 14.6834175,18.0976311 14.2928932,17.7071068 L9.29289322,12.7071068 C8.90236893,12.3165825 8.90236893,11.6834175 9.29289322,11.2928932 L14.2928932,6.29289322 C14.6834175,5.90236893 15.3165825,5.90236893 15.7071068,6.29289322 C16.0976311,6.68341751 16.0976311,7.31658249 15.7071068,7.70710678 L11.4142136,12 L15.7071068,16.2928932 Z\" transform=\"matrix(-1 0 0 1 25 0)\" />\n    </symbol>\n    <symbol ID=\"icHeart\" viewBox=\"0 0 24 24\">\n        <path fill=\"none\" stroke=\"#404B58\" stroke-width=\"2\" d=\"M12,18.8536369 C17.3943819,16.1015046 20,12.9784118 20,9.5 C20,7.01471863 17.9852814,5 15.5,5 C14.4391705,5 13.4374107,5.36699819 12.6367778,6.02820949 L12,6.55409926 L11.3632222,6.02820949 C10.5625893,5.36699819 9.56082953,5 8.5,5 C6.01471863,5 4,7.01471863 4,9.5 C4,12.9784118 6.60561807,16.1015046 12,18.8536369 Z\"/>\n    </symbol>\n     <symbol ID=\"icPointer\" viewBox=\"0 0 24 24\">\n        <path d=\"M7.16743376,4.34579076 C7.66363057,3.87908025 8.39151976,3.755899 9.01365224,4.03335379 L9.01365224,4.03335379 L9.10700534,4.08777143 C9.63104823,4.47472439 10.0217699,5.01508408 10.2127512,5.60062907 C10.4917675,6.2824399 10.6779761,6.99862675 10.7637203,7.71100475 L10.7637203,7.71100475 L10.817,8.033 L10.870648,7.99008307 C10.9508584,7.93108653 11.0375587,7.87866583 11.130137,7.83371233 L11.130137,7.83371233 L11.2733366,7.7719939 C11.645598,7.65777539 12.0393934,7.63209308 12.4423174,7.7005808 C12.8744158,7.79091478 13.2571639,8.03945252 13.515467,8.3974312 L13.515467,8.3974312 L13.525,8.415 L13.5580532,8.38060854 C13.7375777,8.20417748 13.9573664,8.06689595 14.2126677,7.97661745 L14.2126677,7.97661745 L14.3700719,7.92815354 C14.7601243,7.85683728 15.1598757,7.85683728 15.6185326,7.94579813 C15.9857599,8.06856757 16.3070009,8.30005011 16.5396674,8.609557 L16.5396674,8.609557 L16.6043493,8.72458234 C16.6428414,8.82097976 16.6788016,8.91827539 16.7122125,9.01653061 L16.7122125,9.01653061 L16.726,9.06 L16.8208864,8.97721759 C16.9681747,8.85926223 17.1379522,8.76879133 17.3224355,8.71266304 L17.3224355,8.71266304 L17.4634337,8.67711323 C17.9896455,8.5711603 18.5324104,8.75398638 18.8872742,9.15672267 C19.242138,9.55945895 19.3551885,10.1209202 19.1838405,10.6296094 L19.2093224,10.5357799 L19.2090334,12.7610745 C19.1862391,13.1271355 19.1470105,13.4918634 19.0880685,13.8664949 L19.0224339,14.2448438 L18.9429284,14.6322922 C18.7541803,15.1823144 18.4914657,15.7040948 18.1217893,16.2343243 C17.6699093,16.7368429 17.2965379,17.3047439 17.0143136,17.9188042 L17.0363224,17.8727799 L17.0047955,18.0384429 C16.9929046,18.1111666 16.9833046,18.1844595 16.9760091,18.2584435 L16.9610493,18.4825906 L16.9599974,18.7116152 C16.9591372,18.9778721 16.9937268,19.2430586 17.0628545,19.5001867 C17.1426598,19.7970308 16.9382454,20.0949145 16.6325641,20.1272293 C16.1887579,20.1741459 15.7412421,20.1741459 15.2739714,20.1241858 C14.8192803,20.0542334 14.4188874,19.6281882 14.0808095,19.0818271 L14.0808095,19.0818271 L14.007,18.959 C13.6458971,19.5469161 13.2283691,20.0164429 12.7971825,20.133474 L12.7971825,20.133474 L12.6792804,20.1564734 C12.40804,20.1888603 12.1178623,20.1996559 11.5976025,20.1951344 L11.5976025,20.1951344 L10.2584286,20.169736 L9.48,20.16 C9.16840558,20.16 8.93270845,19.8780895 8.98790834,19.5714235 L8.98790834,19.5714235 L9.0027436,19.4616806 C9.02119919,19.2959265 9.02585489,19.1230993 9.00917907,18.9708257 C8.99125835,18.8071844 8.95160807,18.7033862 8.92009941,18.6757201 L8.92009941,18.6757201 L8.68126124,18.4610804 L7.73371165,17.5692523 L6.87532389,16.5869703 C6.7408966,16.4141352 6.12289613,15.3607699 5.76460288,14.7906072 L5.76460288,14.7906072 L5.62038404,14.5667312 C5.59696768,14.5332792 5.57867016,14.5070634 5.55981671,14.4808928 L5.55981671,14.4808928 L4.6386181,13.3268316 C4.38383819,13.0013876 4.22702059,12.7785585 4.13684781,12.6063498 L4.13684781,12.6063498 L4.05648317,12.4645383 C3.86064293,12.0784209 3.81297141,11.6326253 3.92414472,11.2219126 C4.14055922,10.3315035 4.98520633,9.74084121 5.93847381,9.84979299 C6.54529028,9.97169156 7.1029491,10.2691096 7.53281665,10.6960265 L7.53281665,10.6960265 L7.723,10.883 L7.6912417,10.723928 L7.64251772,10.5011802 C7.57779705,10.2174049 7.53759574,10.0512485 7.49682897,9.89744992 L7.49682897,9.89744992 L7.32963781,9.29184575 L7.04456938,8.13982119 C6.92154004,7.6413934 6.82477772,7.13684703 6.75976794,6.65834902 C6.61137215,5.91865308 6.7193326,5.15047299 7.06586723,4.48033484 L7.06586723,4.48033484 Z M8.50934031,4.91178804 C8.27952269,4.84813061 8.02983182,4.9074776 7.85256624,5.07420924 L7.91432244,5.02377994 L7.87117294,5.11837118 C7.74604513,5.42118721 7.68689523,5.74791709 7.69799852,6.08431903 L7.71312508,6.28720691 L7.81882079,6.96398474 C7.87322009,7.27747387 7.93828923,7.58776101 8.01328078,7.89178265 L8.01328078,7.89178265 L8.29601637,9.03471155 L8.44911047,9.58764732 L8.58001699,10.1158847 L8.67625156,10.548595 C8.75149962,10.9085229 8.81587907,11.2760953 8.87882633,11.6904775 L8.87882633,11.6904775 L8.97335261,12.3520537 C9.01060121,12.4927706 9.0255715,12.5703226 9.01950838,12.6680061 C9.00562698,12.8916511 9.00162999,12.9288159 8.65656558,13.0620472 L8.65656558,13.0620472 L8.43241435,13.1459443 C8.23545672,13.065262 8.18621731,13.0450914 8.15393215,13.0140564 L8.15393215,13.0140564 L8.08883861,12.9370646 L7.80233685,12.5590928 L7.5578657,12.2238297 L7.29668723,11.8970782 C7.15496439,11.7304554 7.00269692,11.5703689 6.83776679,11.4148687 C6.53749664,11.1168285 6.15631213,10.9135301 5.78418862,10.8368753 C5.37391601,10.7907916 4.99336072,11.0569141 4.892676,11.4704756 C4.83729564,11.6753474 4.87010001,11.8940432 5.00167451,12.1056421 L5.00167451,12.1056421 L5.03690334,12.1679323 C5.11596703,12.3004962 5.24732182,12.482014 5.44275702,12.7314353 L5.44275702,12.7314353 L5.92017745,13.3249945 L6.29953254,13.8026386 L6.44532166,14.0015976 C6.74304028,14.4457352 7.37243772,15.5180187 7.58358848,15.8552149 L7.58358848,15.8552149 L7.64628835,15.9507477 L8.44514313,16.8689487 L9.35653146,17.7235091 L9.57990059,17.9242799 C9.83717242,18.1501771 9.96070027,18.4735533 10.003236,18.8619643 C10.0118351,18.9404865 10.0168432,19.0197358 10.0187392,19.0991214 L10.0187392,19.0991214 L10.017,19.165 L11.2459156,19.1902367 C11.83561,19.2002217 12.1681116,19.1973958 12.4192519,19.177468 L12.4192519,19.177468 L12.5615794,19.1634247 C12.6170658,19.1568969 13.0000528,18.7092498 13.2226316,18.3267031 L13.2226316,18.3267031 L13.2778839,18.2314035 C13.4396915,17.9886975 13.7135208,17.8397056 14.01,17.8397056 C14.3435391,17.8397056 14.648412,18.0282734 14.789646,18.3118584 L14.789646,18.3118584 L14.8465822,18.411971 C15.047575,18.7505088 15.3490101,19.1240166 15.4025641,19.1327707 C15.5895219,19.1525347 15.7772609,19.1624167 15.965,19.1624167 L15.965,19.1624167 L15.985,19.161 L15.9680577,18.9804543 L15.9601727,18.7231423 C15.9502275,18.3449039 15.9880257,17.9669214 16.0726727,17.5981423 L16.0726727,17.5981423 L16.1056864,17.5011958 C16.429971,16.7956214 16.8589864,16.1430854 17.3379853,15.6167264 C17.61604,15.2123026 17.8377695,14.7719231 17.981188,14.3648199 C18.099272,13.8160386 18.1760798,13.2591823 18.21,12.73 L18.21,12.73 L18.21,10.47 L18.2361595,10.3103906 C18.2934615,10.1402752 18.2556553,9.95251178 18.136982,9.81782907 C18.0183088,9.68314635 17.8367978,9.62200582 17.6608226,9.6574385 C17.4848473,9.69287119 17.3411425,9.81949403 17.2838405,9.98960943 L17.2838405,9.98960943 L17.2333183,10.0960858 C17.2060315,10.1394966 17.1784846,10.1946376 17.1518918,10.2591212 C17.1098746,10.3610069 17.0769839,10.4664928 17.0409555,10.6029732 L17.0409555,10.6029732 L16.996796,10.7581274 L16.9595346,10.8379862 C16.9006729,10.9958359 16.8849765,11.0379292 16.5932412,10.9992882 L16.5932412,10.9992882 L16.1501329,10.9267917 C16.0348274,10.6676813 16.009204,10.6101012 16.0212304,10.6050553 C15.9848193,10.0869965 15.8682391,9.57772504 15.6756507,9.09541766 L15.7,9.162 L15.6694688,9.12668516 C15.5949633,9.04790444 15.511666,8.98482947 15.4387784,8.94442971 L15.4387784,8.94442971 L15.3700719,8.91184646 C15.098938,8.86227297 14.821062,8.86227297 14.5895196,8.90293341 C14.4230985,8.94756646 14.2774688,9.04887406 14.1777454,9.18938502 L14.2223224,9.13377994 L14.1675121,9.33868789 C14.1182734,9.53586776 14.0787072,9.73525859 14.0489695,9.93588797 L14.011754,10.2376394 L13.9893984,10.5405193 C13.9584597,11.1706655 13.0294935,11.1762842 12.9909347,10.5465584 L12.9909347,10.5465584 L12.9730784,10.2516986 C12.930789,9.77937294 12.8204225,9.3155109 12.6453027,8.87454375 L12.669,8.94 L12.6310282,8.89504189 C12.5518534,8.81362497 12.456908,8.75113321 12.3573482,8.71292346 L12.3573482,8.71292346 L12.2566634,8.6830061 C12.0262976,8.64406561 11.7900203,8.659475 11.5981215,8.71719666 C11.4132489,8.78768046 11.2781507,8.94899175 11.2402903,9.14805807 C11.2189407,9.25480644 11.2137839,9.47449295 11.224132,9.79681583 L11.224132,9.79681583 L11.25,10.55 C11.25,11.2056512 10.2721205,11.2219446 10.2502775,10.5666574 C10.2493885,10.539987 10.2483981,10.5176836 10.244244,10.482217 L10.244244,10.482217 L9.90052097,8.58822404 L9.77361364,7.85000483 C9.69594584,7.20750256 9.53236284,6.57833719 9.27496435,5.94601645 L9.27496435,5.94601645 L9.20787946,5.76867193 C9.05849627,5.42113224 8.81912344,5.11827389 8.51299466,4.89222857 L8.51732244,4.89577994 L8.557,4.928 Z M15.375,13 C15.5562184,13 15.707414,13.1282379 15.7423813,13.2987132 L15.75,13.3741092 L15.75,16.8258906 C15.75,17.0325054 15.5821068,17.1999998 15.375,17.1999998 C15.1937816,17.1999998 15.042586,17.0717619 15.0076187,16.9012866 L15,16.8258906 L15,13.3741092 C15,13.1674944 15.1678932,13 15.375,13 Z M13.3728388,13 C13.5540542,12.998966 13.7059881,13.1260313 13.7419398,13.2958993 L13.7499939,13.3710717 L13.7699939,16.8246259 C13.7711876,17.0307477 13.6042648,17.1988055 13.3971615,17.1999998 C13.2159461,17.2010338 13.0640121,17.0739685 13.0280605,16.9041005 L13.0200064,16.8289281 L13,13.3753739 C12.9988127,13.1692522 13.1657354,13.0011944 13.3728388,13 Z M11.3728136,13 C11.5540289,12.998944 11.7059714,13.127215 11.7419346,13.2987061 L11.7499938,13.3745973 L11.7699938,16.8210084 C11.7712014,17.0291026 11.6042899,17.1987799 11.3971867,17.1999998 C11.2159713,17.2010558 11.0640288,17.0727848 11.0280657,16.9012937 L11.0200065,16.8254025 L11,13.3789914 C10.9987989,13.1708972 11.1657103,13.0012199 11.3728136,13 Z\" />\n    </symbol>\n    <symbol ID=\"icAnnotation\" viewBox=\"0 0 24 24\">\n        <path d=\"M5,16 L13,16 C13.5522847,16 14,16.4477153 14,17 C14,17.5522847 13.5522847,18 13,18 L5,18 C4.44771525,18 4,17.5522847 4,17 C4,16.4477153 4.44771525,16 5,16 Z M5,6 L19,6 C19.5522847,6 20,6.44771525 20,7 C20,7.55228475 19.5522847,8 19,8 L5,8 C4.44771525,8 4,7.55228475 4,7 C4,6.44771525 4.44771525,6 5,6 Z M5,11 L19,11 C19.5522847,11 20,11.4477153 20,12 C20,12.5522847 19.5522847,13 19,13 L5,13 C4.44771525,13 4,12.5522847 4,12 C4,11.4477153 4.44771525,11 5,11 Z\" />\n    </symbol>\n    <symbol ID=\"icExperimental\" viewBox=\"0 0 24 24\">\n        <path fill=\"white\" d=\"M16,3 C16.5522847,3 17,3.44771525 17,4 C17,4.55228475 16.5522847,5 16,5 L14.9999117,5 L15,10.667 L20.2,17.6 C20.3622777,17.8163702 20.4624259,18.0717378 20.4913009,18.3386714 L20.4913009,18.3386714 L20.5,18.5 C20.5,19.3284271 19.8284271,20 19,20 L19,20 L5,20 C4.67544468,20 4.35964426,19.8947332 4.1,19.7 C3.4372583,19.2029437 3.30294373,18.2627417 3.8,17.6 L3.8,17.6 L9,10.667 L8.99991172,5 L8,5 C7.44771525,5 7,4.55228475 7,4 C7,3.44771525 7.44771525,3 8,3 L16,3 Z M13,5 L11,5 L11,11.3333333 L5.999,18 L17.999,18 L13,11.3333333 L13,5 Z\"/>\n    </symbol>\n    <symbol ID=\"icEmbed\" viewBox=\"0 0 24 24\">\n        <path d=\"M6.7080808,14.2938686 C7.09806642,14.6849308 7.09719364,15.3180952 6.70613141,15.7080808 C6.31506919,16.0980664 5.68190481,16.0971936 5.2919192,15.7061314 L2.2919192,12.6978494 C1.90193253,12.3067862 1.90280651,11.6736197 2.29387128,11.2836345 L5.29387128,8.29191651 C5.684935,7.90193238 6.31809937,7.90280757 6.70808349,8.29387128 C7.09806762,8.684935 7.09719243,9.31809937 6.70612872,9.70808349 L4.41421491,11.9936701 L6.7080808,14.2938686 Z M17.2938713,9.70808349 C16.9028076,9.31809937 16.9019324,8.684935 17.2919165,8.29387128 C17.6819006,7.90280757 18.315065,7.90193238 18.7061287,8.29191651 L21.7061287,11.2836345 C22.0971935,11.6736197 22.0980675,12.3067862 21.7080808,12.6978494 L18.7080808,15.7061314 C18.3180952,16.0971936 17.6849308,16.0980664 17.2938686,15.7080808 C16.9028064,15.3180952 16.9019336,14.6849308 17.2919192,14.2938686 L19.5857851,11.9936701 L17.2938713,9.70808349 Z M13.0513167,5.68377223 C13.2259645,5.15982892 13.7922844,4.87666893 14.3162278,5.0513167 C14.8401711,5.22596447 15.1233311,5.79228445 14.9486833,6.31622777 L10.9486833,18.3162278 C10.7740355,18.8401711 10.2077156,19.1233311 9.68377223,18.9486833 C9.15982892,18.7740355 8.87666893,18.2077156 9.0513167,17.6837722 L13.0513167,5.68377223 Z\" />\n    </symbol>\n    <symbol ID=\"icGrid\" viewBox=\"0 0 24 24\">\n        <path d=\"M12,17 C13.1045695,17 14,17.8954305 14,19 C14,20.1045695 13.1045695,21 12,21 C10.8954305,21 10,20.1045695 10,19 C10,17.8954305 10.8954305,17 12,17 Z M18.5,17 C19.3284271,17 20,17.6715729 20,18.5 C20,19.3284271 19.3284271,20 18.5,20 C17.6715729,20 17,19.3284271 17,18.5 C17,17.6715729 17.6715729,17 18.5,17 Z M5.5,17 C6.32842712,17 7,17.6715729 7,18.5 C7,19.3284271 6.32842712,20 5.5,20 C4.67157288,20 4,19.3284271 4,18.5 C4,17.6715729 4.67157288,17 5.5,17 Z M12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 Z M19,10 C20.1045695,10 21,10.8954305 21,12 C21,13.1045695 20.1045695,14 19,14 C17.8954305,14 17,13.1045695 17,12 C17,10.8954305 17.8954305,10 19,10 Z M5,10 C6.1045695,10 7,10.8954305 7,12 C7,13.1045695 6.1045695,14 5,14 C3.8954305,14 3,13.1045695 3,12 C3,10.8954305 3.8954305,10 5,10 Z M12,3 C13.1045695,3 14,3.8954305 14,5 C14,6.1045695 13.1045695,7 12,7 C10.8954305,7 10,6.1045695 10,5 C10,3.8954305 10.8954305,3 12,3 Z M18.5,4 C19.3284271,4 20,4.67157288 20,5.5 C20,6.32842712 19.3284271,7 18.5,7 C17.6715729,7 17,6.32842712 17,5.5 C17,4.67157288 17.6715729,4 18.5,4 Z M5.5,4 C6.32842712,4 7,4.67157288 7,5.5 C7,6.32842712 6.32842712,7 5.5,7 C4.67157288,7 4,6.32842712 4,5.5 C4,4.67157288 4.67157288,4 5.5,4 Z\" />\n    </symbol>\n    <symbol ID=\"icClose\" viewBox=\"0 0 24 24\">\n      <path d=\"M10.5857864,12 L7.29289322,8.70710678 C6.90236893,8.31658249 6.90236893,7.68341751 7.29289322,7.29289322 C7.68341751,6.90236893 8.31658249,6.90236893 8.70710678,7.29289322 L12,10.5857864 L15.2928932,7.29289322 C15.6834175,6.90236893 16.3165825,6.90236893 16.7071068,7.29289322 C17.0976311,7.68341751 17.0976311,8.31658249 16.7071068,8.70710678 L13.4142136,12 L16.7071068,15.2928932 C17.0976311,15.6834175 17.0976311,16.3165825 16.7071068,16.7071068 C16.3165825,17.0976311 15.6834175,17.0976311 15.2928932,16.7071068 L12,13.4142136 L8.70710678,16.7071068 C8.31658249,17.0976311 7.68341751,17.0976311 7.29289322,16.7071068 C6.90236893,16.3165825 6.90236893,15.6834175 7.29289322,15.2928932 L10.5857864,12 Z\" transform=\"rotate(-90 12 12)\" />\n    </symbol>\n    <symbol ID=\"icBack\" viewBox=\"0 0 24 24\">\n      <path d=\"M8.36500685,12.7725895 C8.14212731,12.5891835 8,12.3112069 8,12.0000346 C8,11.7624738 8.08283717,11.5442607 8.22120202,11.3727048 L8.22132741,11.3368041 L12.2213274,6.37260414 C12.5678477,5.94255515 13.1973815,5.87484175 13.6274305,6.22136203 C14.0574795,6.56788232 14.1251929,7.1974161 13.7786726,7.6274651 L11.0611595,11.0000346 L19,11.0000346 C19.5522847,11.0000346 20,11.4477499 20,12.0000346 C20,12.5523194 19.5522847,13.0000346 19,13.0000346 L11.0998289,13.0000346 L13.7830365,16.3780588 C14.1265442,16.8105179 14.0544349,17.4395633 13.6219758,17.7830711 C13.1895167,18.1265788 12.5604713,18.0544695 12.2169635,17.6220104 L8.36500685,12.7725895 Z M5,6.00003462 C5.55228475,6.00003462 6,6.44774987 6,7.00003462 L6,17.0000346 C6,17.5523194 5.55228475,18.0000346 5,18.0000346 C4.44771525,18.0000346 4,17.5523194 4,17.0000346 L4,7.00003462 C4,6.44774987 4.44771525,6.00003462 5,6.00003462 Z\" />\n    </symbol>\n    <symbol ID=\"icResize\" viewBox=\"0 0 24 24\">\n      <path d=\"M15.5857864,7 L13,7 C12.4477153,7 12,6.55228475 12,6 C12,5.44771525 12.4477153,5 13,5 L18,5 C18.5522847,5 19,5.44771525 19,6 L19,11 C19,11.5522847 18.5522847,12 18,12 C17.4477153,12 17,11.5522847 17,11 L17,8.41421356 L14.7071068,10.7071068 C14.3165825,11.0976311 13.6834175,11.0976311 13.2928932,10.7071068 C12.9023689,10.3165825 12.9023689,9.68341751 13.2928932,9.29289322 L15.5857864,7 Z M7,15.5857864 L9.29289322,13.2928932 C9.68341751,12.9023689 10.3165825,12.9023689 10.7071068,13.2928932 C11.0976311,13.6834175 11.0976311,14.3165825 10.7071068,14.7071068 L8.41421356,17 L11,17 C11.5522847,17 12,17.4477153 12,18 C12,18.5522847 11.5522847,19 11,19 L6,19 C5.44771525,19 5,18.5522847 5,18 L5,13 C5,12.4477153 5.44771525,12 6,12 C6.55228475,12 7,12.4477153 7,13 L7,15.5857864 Z\" />\n    </symbol>\n    <symbol ID=\"icGridLayout\" viewBox=\"0 0 24 24\">\n      <path d=\"M6.07692308,5 C5.48215488,5 5,5.44771525 5,6 L5,17 C5,17.5522847 5.48215488,18 6.07692308,18 L17.9230769,18 C18.5178451,18 19,17.5522847 19,17 L19,6 C19,5.44771525 18.5178451,5 17.9230769,5 L6.07692308,5 Z M6.17647059,3 L17.8235294,3 C19.5778457,3 21,4.34314575 21,6 L21,17 C21,18.6568542 19.5778457,20 17.8235294,20 L6.17647059,20 C4.42215432,20 3,18.6568542 3,17 L3,6 C3,4.34314575 4.42215432,3 6.17647059,3 Z M14,5 L16,5 L16,18 L14,18 L14,5 Z M8,5 L10,5 L10,18 L8,18 L8,5 Z\" />\n    </symbol>\n    <symbol ID=\"icElementInspector\" viewBox=\"0 0 24 24\">\n      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />\n    </symbol>\n    <symbol ID=\"icVersionInspector\" viewBox=\"0 0 24 24\">\n      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />\n    </symbol>\n    <symbol ID=\"icIncreaseVersion\" viewBox=\"0 0 24 24\">\n      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M11.3086198,9.29124083 C11.7000598,8.90162823 12.333229,8.90311306 12.7228373,9.29455727 L12.7228373,9.29455727 L15.7087669,12.2945573 C16.0983722,12.6859984 16.0968839,13.3191617 15.7054427,13.7087669 C15.3140016,14.0983722 14.6808383,14.0968839 14.2912331,13.7054427 L14.2912331,13.7054427 L12.0107539,11.4142175 L9.70545052,13.7087592 C9.31401364,14.0983687 8.6808504,14.0968874 8.29124083,13.7054505 C7.90163127,13.3140136 7.9031126,12.6808504 8.29454948,12.2912408 L8.29454948,12.2912408 Z\" />\n    </symbol>\n    <symbol ID=\"icDecreaseVersion\" viewBox=\"0 0 24 24\">\n      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M8.29124083,10.2945495 C8.6808504,9.9031126 9.31401364,9.90163127 9.70545052,10.2912408 L9.70545052,10.2912408 L12.0107539,12.5857825 L14.2912331,10.2945573 C14.6808383,9.90311611 15.3140016,9.90162781 15.7054427,10.2912331 C16.0968839,10.6808383 16.0983722,11.3140016 15.7087669,11.7054427 L15.7087669,11.7054427 L12.7228373,14.7054427 C12.333229,15.0968869 11.7000598,15.0983718 11.3086198,14.7087592 L11.3086198,14.7087592 L8.29454948,11.7087592 C7.9031126,11.3191496 7.90163127,10.6859864 8.29124083,10.2945495 Z\" />\n    </symbol>\n    <symbol ID=\"icCloseBtn\" viewBox=\"0 0 24 24\">\n      <path fill=\"#FFFFFF\" d=\"M4.29289322,4.29289322 C4.68341751,3.90236893 5.31658249,3.90236893 5.70710678,4.29289322 L5.70710678,4.29289322 L12,10.585 L18.2928932,4.29289322 C18.6533772,3.93240926 19.2206082,3.90467972 19.6128994,4.20970461 L19.7071068,4.29289322 C20.0976311,4.68341751 20.0976311,5.31658249 19.7071068,5.70710678 L19.7071068,5.70710678 L13.415,12 L19.7071068,18.2928932 C20.0675907,18.6533772 20.0953203,19.2206082 19.7902954,19.6128994 L19.7071068,19.7071068 C19.3165825,20.0976311 18.6834175,20.0976311 18.2928932,19.7071068 L18.2928932,19.7071068 L12,13.415 L5.70710678,19.7071068 C5.34662282,20.0675907 4.77939176,20.0953203 4.38710056,19.7902954 L4.29289322,19.7071068 C3.90236893,19.3165825 3.90236893,18.6834175 4.29289322,18.2928932 L4.29289322,18.2928932 L10.585,12 L4.29289322,5.70710678 C3.93240926,5.34662282 3.90467972,4.77939176 4.20970461,4.38710056 Z\" />\n    </symbol>\n    <symbol ID=\"icAddComment\" viewBox=\"0 0 24 24\">\n    <path d=\"M19,3 L5,3 C3.34314575,3 2,4.34314575 2,6 L2,16 L2.00509269,16.1762728 C2.09633912,17.75108 3.40231912,19 5,19 L15.65,19 L18.7506099,21.4811128 C19.105236,21.7648136 19.545857,21.9193752 20,21.9193752 C21.1045695,21.9193752 22,21.0239447 22,19.9193752 L22,6 C22,4.34314575 20.6568542,3 19,3 Z M5,5 L19,5 C19.5522847,5 20,5.44771525 20,6 L20,19.9193752 L16.3507811,17 L5,17 C4.44771525,17 4,16.5522847 4,16 L4,6 C4,5.44771525 4.44771525,5 5,5 Z M12,7 C12.5522847,7 13,7.44771525 13,8 L13,14 C13,14.5522847 12.5522847,15 12,15 C11.4477153,15 11,14.5522847 11,14 L11,8 C11,7.44771525 11.4477153,7 12,7 Z M9,10 L15,10 C15.5522847,10 16,10.4477153 16,11 C16,11.5522847 15.5522847,12 15,12 L9,12 C8.44771525,12 8,11.5522847 8,11 C8,10.4477153 8.44771525,10 9,10 Z\"/>\n    </symbol>\n    <symbol ID=\"icComments\" viewBox=\"0 0 24 24\">\n    <path d=\"M19,3 C20.6568542,3 22,4.34314575 22,6 L22,6 L22,19.9193752 C22,21.0239447 21.1045695,21.9193752 20,21.9193752 C19.545857,21.9193752 19.105236,21.7648136 18.7506099,21.4811128 L18.7506099,21.4811128 L15.65,19 L5,19 C3.40231912,19 2.09633912,17.75108 2.00509269,16.1762728 L2.00509269,16.1762728 L2,16 L2,6 C2,4.34314575 3.34314575,3 5,3 L5,3 Z M19,5 L5,5 C4.44771525,5 4,5.44771525 4,6 L4,6 L4,16 C4,16.5522847 4.44771525,17 5,17 L5,17 L16.3507811,17 L20,19.9193752 L20,6 C20,5.44771525 19.5522847,5 19,5 L19,5 Z M12,12 C12.5522847,12 13,12.4477153 13,13 C13,13.5522847 12.5522847,14 12,14 L8,14 C7.44771525,14 7,13.5522847 7,13 C7,12.4477153 7.44771525,12 8,12 L12,12 Z M16,8 C16.5522847,8 17,8.44771525 17,9 C17,9.55228475 16.5522847,10 16,10 L8,10 C7.44771525,10 7,9.55228475 7,9 C7,8.44771525 7.44771525,8 8,8 L16,8 Z\"/>\n    </symbol>\n    <symbol ID=\"icHide\" viewBox=\"0 0 24 24\">\n    <path d=\"M5.38615132,2.21064778 C5.82209852,1.87157774 6.45037412,1.95011219 6.78944416,2.38605939 L6.78944416,2.38605939 L20.7894442,20.3860594 L20.8603054,20.4898575 C21.1143171,20.9169531 21.0164453,21.4763645 20.6140326,21.7893522 C20.1780854,22.1284223 19.5498098,22.0498878 19.2107397,21.6139406 L19.2107397,21.6139406 L16.8780344,18.6156594 C15.5498874,19.5708529 14.041875,20 12,20 C8.6859351,20 6.77821955,18.8695019 4.76330809,16.1942038 L4.76330809,16.1942038 L4.61751058,15.9953594 L2.63509259,13.1986701 L2.28770167,12.7182294 L1.75,12 L2.2,11.4 L2.49341785,10.9985181 L4.31543313,8.42073233 L4.75555556,7.80740741 C5.42384589,6.91635363 6.0806072,6.19680427 6.78216144,5.63325828 L5.21073972,3.61394061 L5.13987849,3.51014248 C4.88586677,3.08304691 4.98373853,2.52363551 5.38615132,2.21064778 Z M8.00925198,7.2123467 C7.45172452,7.66214724 6.92187523,8.25231451 6.35555556,9.00740741 L6.35555556,9.00740741 L6.30731957,9.07271659 L4.236,11.999 L6.30450475,14.9148846 L6.53802015,15.2223052 C8.12271813,17.2561888 9.46831678,18 12,18 C13.5528699,18 14.6595013,17.7201572 15.6487821,17.0344116 L14.2866626,15.2823731 C13.6385783,15.7347043 12.8502707,16 12,16 C9.790861,16 8,14.209139 8,12 C8,10.7921161 8.535385,9.70927802 9.38179517,8.97584565 Z M12,4 C15.3306702,4 17.2354638,5.12876655 19.2444444,7.80740741 L19.2444444,7.80740741 L19.3777773,7.98990371 L21.2554675,10.6474617 L21.7154062,11.2859885 L22.25,12 L21.7122983,12.7182294 L21.3649074,13.1986701 L19.7160653,15.5283237 L19.623,15.657954 L18.34,14.007954 L19.763,11.999 L17.9370251,9.41365436 L17.6444444,9.00740741 C15.9867584,6.79715938 14.641552,6 12,6 L12.112,6.00095403 L10.6134966,4.07512187 C10.9529438,4.03563361 11.3083548,4.01198764 11.6820769,4.00353786 Z M10,12 C10,13.1045695 10.8954305,14 12,14 C12.3873079,14 12.7489023,13.8899073 13.0551963,13.6993088 L10.6131393,10.5589495 C10.23517,10.9227924 10,11.4339327 10,12 Z\"/>\n    </symbol>\n    <symbol ID=\"icList\" viewBox=\"0 0 24 24\">\n     <path d=\"M5,16 C5.55228475,16 6,16.4477153 6,17 C6,17.5522847 5.55228475,18 5,18 L4,18 C3.44771525,18 3,17.5522847 3,17 C3,16.4477153 3.44771525,16 4,16 L5,16 Z M20,16 C20.5522847,16 21,16.4477153 21,17 C21,17.5522847 20.5522847,18 20,18 L10,18 C9.44771525,18 9,17.5522847 9,17 C9,16.4477153 9.44771525,16 10,16 L20,16 Z M5,11 C5.55228475,11 6,11.4477153 6,12 C6,12.5522847 5.55228475,13 5,13 L4,13 C3.44771525,13 3,12.5522847 3,12 C3,11.4477153 3.44771525,11 4,11 L5,11 Z M20,11 C20.5522847,11 21,11.4477153 21,12 C21,12.5522847 20.5522847,13 20,13 L10,13 C9.44771525,13 9,12.5522847 9,12 C9,11.4477153 9.44771525,11 10,11 L20,11 Z M5,6 C5.55228475,6 6,6.44771525 6,7 C6,7.55228475 5.55228475,8 5,8 L4,8 C3.44771525,8 3,7.55228475 3,7 C3,6.44771525 3.44771525,6 4,6 L5,6 Z M20,6 C20.5522847,6 21,6.44771525 21,7 C21,7.55228475 20.5522847,8 20,8 L10,8 C9.44771525,8 9,7.55228475 9,7 C9,6.44771525 9.44771525,6 10,6 L20,6 Z\"/>\n    </symbol>\n    <symbol ID=\"icPlay\" viewBox=\"0 0 24 24\">\n      <path d=\"M8,3.53238076 C6.34314575,3.53238076 5,4.87552651 5,6.53238076 L5,17.4676192 C5,18.0113511 5.1477735,18.5448603 5.42752122,19.0111065 C6.2799657,20.4318473 8.12274647,20.8925425 9.54348727,20.040098 L18.656186,14.5724788 C19.0784348,14.3191295 19.4318282,13.9657361 19.6851775,13.5434873 C20.537622,12.1227465 20.0769268,10.2799657 18.656186,9.42752122 L9.54348727,3.95990198 C9.07724106,3.68015426 8.54373184,3.53238076 8,3.53238076 Z M8,5.53238076 C8.18124395,5.53238076 8.35908035,5.58163859 8.51449576,5.67488783 L17.6271945,11.1425071 C18.1007748,11.4266552 18.2543398,12.0409155 17.9701917,12.5144958 C17.8857419,12.6552454 17.7679441,12.7730432 17.6271945,12.8574929 L8.51449576,18.3251122 C8.04091549,18.6092603 7.42665523,18.4556953 7.14250707,17.982115 C7.04925783,17.8266996 7,17.6488632 7,17.4676192 L7,6.53238076 C7,5.98009601 7.44771525,5.53238076 8,5.53238076 Z\"/>\n    </symbol>\n    <symbol ID=\"icImage\" viewBox=\"0 0 24 24\">\n      <path d=\"M17,2 C19.7614237,2 22,4.23857625 22,7 L22,7 L22,17 C22,19.7614237 19.7614237,22 17,22 L17,22 L7,22 C4.23857625,22 2,19.7614237 2,17 L2,17 L2,7 C2,4.23857625 4.23857625,2 7,2 L7,2 Z M16,12 C15.8297561,12 15.5946209,12.2378497 15.2734196,12.8038837 L15.1055488,13.1146676 L14.9227455,13.4826581 C14.8910012,13.5488965 14.8586103,13.6176231 14.8255604,13.6888901 L14.3890648,14.6566547 L14.1803282,15.0984849 C13.53366,16.4191512 13.0025432,17 12,17 C11.3307761,17 10.812497,16.8368878 10.0090244,16.4495 L9.31831527,16.1119469 L9.09036118,16.0093391 L8.90697512,15.9359222 L8.76121803,15.8889974 C8.6104198,15.8486563 8.52228012,15.8568939 8.4472136,15.8944272 C8.30414881,15.9659596 8.11698867,16.1194358 7.88932876,16.3525162 L7.68435724,16.5717397 C7.64857023,16.6115079 7.61197765,16.6528871 7.57458999,16.6958703 L7.340807,16.9729646 C7.30028096,17.022337 7.25898082,17.0732999 7.21691704,17.1258464 L6.95545469,17.4600748 L6.67616841,17.8319832 C6.62816333,17.8970898 6.57943654,17.9637527 6.52999854,18.0319652 L6.22491981,18.4597799 L5.90327507,18.9244561 L5.5655675,19.4256665 C5.53243943,19.4754556 5.49909687,19.5257438 5.46554181,19.5765298 C5.91415838,19.846071 6.43910316,20 7,20 L7,20 L17,20 C18.6568542,20 20,18.6568542 20,17 L20,17 L19.9998707,16.5619291 C19.4411584,16.1605501 18.9628238,15.5931969 18.3332607,14.6771273 L17.6679497,13.6797002 C17.486047,13.4068462 17.3213607,13.1723124 17.1710577,12.972542 L16.9560365,12.6985143 L16.7604657,12.4739638 C16.4495425,12.1394621 16.210809,12 16,12 Z M17,4 L7,4 C5.34314575,4 4,5.34314575 4,7 L4,7 L4,17 C4,17.3295217 4.05312788,17.6466347 4.15128526,17.9432406 L4.3462045,17.6548857 C4.40810659,17.5647094 4.46932646,17.4762377 4.5298746,17.3894636 L4.88518652,16.8891368 C5.0009928,16.7291156 5.11419602,16.5758306 5.22488003,16.4292273 L5.54945832,16.0094075 C6.3424293,15.0096976 6.99841223,14.3827599 7.5527864,14.1055728 C8.38270441,13.6906138 8.94469995,13.7459368 10.0020647,14.2244794 L10.4232877,14.4236794 L10.6541098,14.5379859 C10.7780334,14.6000077 10.8906334,14.6547492 10.993886,14.7028076 L11.2775772,14.8275305 C11.537178,14.9333981 11.7304355,14.9844907 11.9107022,14.996928 L11.9443746,14.9967053 C11.9280519,14.9921104 11.9287454,14.9811437 11.9454696,14.9550286 L12,14.88 C12.1185243,14.7219676 12.2462046,14.4994343 12.4032934,14.1784306 L12.9005785,13.0896353 C13.8754872,10.9231715 14.5678658,10 16,10 C17.2095852,10 17.9825322,10.6374853 19.0141196,12.1049639 L19.8067485,13.286551 L20,13.568 L20,7 C20,5.40231912 18.75108,4.09633912 17.1762728,4.00509269 L17,4 Z M9,6 C10.6568542,6 12,7.34314575 12,9 C12,10.6568542 10.6568542,12 9,12 C7.34314575,12 6,10.6568542 6,9 C6,7.34314575 7.34314575,6 9,6 Z M9,8 C8.44771525,8 8,8.44771525 8,9 C8,9.55228475 8.44771525,10 9,10 C9.55228475,10 10,9.55228475 10,9 C10,8.44771525 9.55228475,8 9,8 Z\"/>\n    </symbol>\n    <symbol ID=\"icImage2\" viewBox=\"0 0 24 24\">\n      <path d=\"M16,7 C17.1143424,7 18.4295271,8.57121215 19.5441438,10.5880323 L19.8075954,11.0800485 C19.8939919,11.2465216 19.9788993,11.4152703 20.062112,11.5857182 L20.3064606,12.1015835 C20.3462695,12.1882186 20.3856034,12.2751344 20.4244365,12.362259 L20.6512209,12.8869343 L20.8647543,13.4131606 L20.9661666,13.6757748 L20.9661666,13.6757748 L21.1575115,14.1978441 C21.2492033,14.4573935 21.33477,14.7142827 21.4135179,14.9665669 L21.5617164,15.4644186 C21.8391079,16.4455364 22,17.3322244 22,18 C22,19.6060049 20.4344017,20.3060052 17.7640217,20.6899553 C16.1972149,20.9152325 14.4637981,20.9933553 12.4336944,20.9995105 L11.5677648,20.9995105 C9.53620185,20.9933553 7.80278511,20.9152325 6.23597834,20.6899553 C3.56559832,20.3060052 2,19.6060049 2,18 C2,15.4380799 4.88475194,11 7,11 C7.75239916,11 8.30673559,11.2622061 8.95745456,11.7977366 L9.17606666,11.9844701 L9.47686384,12.2525907 C10.1075624,12.8132116 10.4577906,13 11,13 C11.111969,13 11.2802962,12.847819 11.522927,12.4940925 L11.662748,12.2808661 C11.6873156,12.2418981 11.7125242,12.2011903 11.7383874,12.1587055 L11.9015287,11.8821773 L12.0810259,11.5612224 L12.2775261,11.1940608 L13.0048253,9.76854174 C13.0468792,9.68786397 13.088229,9.6092967 13.1289398,9.53279694 L13.3660553,9.09826444 C14.2495881,7.52502439 14.8638399,7 16,7 Z M16,9 C15.888031,9 15.7197038,9.15218104 15.477073,9.50590753 L15.337252,9.71913394 C15.3126844,9.75810191 15.2874758,9.79880972 15.2616126,9.84129446 L15.0984713,10.1178227 L14.9189741,10.4387776 L14.7224739,10.8059392 L13.9951747,12.2314583 C13.9531208,12.312136 13.911771,12.3907033 13.8710602,12.4672031 L13.6339447,12.9017356 C12.7504119,14.4749756 12.1361601,15 11,15 C9.93106209,15 9.21210949,14.6545653 8.33800348,13.9125104 L7.82246575,13.4574247 C7.42268428,13.1085245 7.21920091,13 7,13 C6.21524806,13 4,16.4080739 4,18 C4,18.1050885 4.17043163,18.2116137 4.47915482,18.3140104 L4.70510522,18.3816021 L4.96991468,18.4476042 L5.27146698,18.5116501 L5.6076459,18.5733736 L5.97633523,18.632408 L6.37541875,18.688387 C6.44434795,18.6974419 6.5144554,18.7063542 6.58569701,18.7151163 L7.25630347,18.7897132 L7.73387224,18.8343275 L8.23337033,18.8744206 L9.01910569,18.9252817 L9.56416861,18.952469 L10.4083315,18.9822536 L10.9857328,18.9942448 L11.5723662,18.9995163 L12.4290929,18.9995163 L13.015523,18.9942419 L13.5927409,18.9822511 L14.4366635,18.9524671 L14.981588,18.9252801 L15.767146,18.8744194 L16.2665445,18.8343265 L16.7440273,18.7897124 L17.4145284,18.7151157 L18.02381,18.6324077 L18.3924581,18.5733733 L18.7286045,18.51165 L19.0301318,18.4476041 L19.294923,18.3816021 L19.5208606,18.3140103 C19.8295684,18.2116137 20,18.1050885 20,18 C20,17.4665715 19.8092698,16.6373572 19.5083571,15.6935894 L19.3490918,15.2129984 L19.1737002,14.7183493 L18.9844196,14.2146764 L18.7834876,13.707014 L18.5731416,13.2003962 L18.355619,12.6998572 L18.1331573,12.2104314 L17.9079938,11.7371528 C17.8703657,11.6598984 17.8327183,11.5835265 17.7950981,11.508142 L17.5700772,11.0685236 L17.3479482,10.6576382 C17.311292,10.5918313 17.2748495,10.5274315 17.2386674,10.4645434 L17.0250714,10.1061965 C16.605619,9.42914287 16.2370793,9 16,9 Z M7.5,2 C9.43299662,2 11,3.56700338 11,5.5 C11,7.43299662 9.43299662,9 7.5,9 C5.56700338,9 4,7.43299662 4,5.5 C4,3.56700338 5.56700338,2 7.5,2 Z M7.5,4 C6.67157288,4 6,4.67157288 6,5.5 C6,6.32842712 6.67157288,7 7.5,7 C8.32842712,7 9,6.32842712 9,5.5 C9,4.67157288 8.32842712,4 7.5,4 Z\" id=\"Combined-Shape\" fill-rule=\"nonzero\"></path>\n    </symbol>\n    <symbol ID=\"icSettings\" viewBox=\"0 0 24 24\">\n      <path d=\"M17,3 C17.5128358,3 17.9355072,3.38604019 17.9932723,3.88337887 L18,4 L18.0007613,5.12621352 C19.7256022,5.57052105 21,7.13643475 21,9 C21,10.8635652 19.7256022,12.429479 18.0007613,12.8737865 L18,19 C18,19.5522847 17.5522847,20 17,20 C16.4871642,20 16.0644928,19.6139598 16.0067277,19.1166211 L16,19 L16.0002435,12.8740452 C14.2748927,12.4300871 13,10.8639271 13,9 C13,7.13607289 14.2748927,5.56991294 16.0002435,5.12595483 L16,4 C16,3.44771525 16.4477153,3 17,3 Z M17,7 C15.8954305,7 15,7.8954305 15,9 C15,10.1045695 15.8954305,11 17,11 C18.1045695,11 19,10.1045695 19,9 C19,7.8954305 18.1045695,7 17,7 Z M7,4 C7.51283584,4 7.93550716,4.38604019 7.99327227,4.88337887 L8,5 L8.00076134,11.1262135 C9.72560224,11.570521 11,13.1364348 11,15 C11,16.8635652 9.72560224,18.429479 8.00076134,18.8737865 L8,20 C8,20.5522847 7.55228475,21 7,21 C6.48716416,21 6.06449284,20.6139598 6.00672773,20.1166211 L6,20 L6.00024347,18.8740452 C4.27489272,18.4300871 3,16.8639271 3,15 C3,13.1360729 4.27489272,11.5699129 6.00024347,11.1259548 L6,5 C6,4.44771525 6.44771525,4 7,4 Z M7,13 C5.8954305,13 5,13.8954305 5,15 C5,16.1045695 5.8954305,17 7,17 C8.1045695,17 9,16.1045695 9,15 C9,13.8954305 8.1045695,13 7,13 Z\"/>\n    </symbol>\n    <symbol ID=\"icVersions\" viewBox=\"0 0 24 24\">\n      <path d=\"M18,6 C19.6568542,6 21,7.34314575 21,9 L21,9 L21,19 C21,20.6568542 19.6568542,22 18,22 L18,22 L11,22 C9.34314575,22 8,20.6568542 8,19 L8,19 L8,9 C8,7.34314575 9.34314575,6 11,6 L11,6 Z M18,8 L11,8 C10.4477153,8 10,8.44771525 10,9 L10,9 L10,19 C10,19.5522847 10.4477153,20 11,20 L11,20 L18,20 C18.5522847,20 19,19.5522847 19,19 L19,19 L19,9 C19,8.44771525 18.5522847,8 18,8 L18,8 Z M13,2 C14.3062521,2 15.4175144,2.8348501 15.8293257,4.00008893 L6,4 C5.48716416,4 5.06449284,4.38604019 5.00672773,4.88337887 L5,5 L5,15 C5,15.5128358 5.38604019,15.9355072 5.88337887,15.9932723 L6,16 L6,18 C4.40231912,18 3.09633912,16.75108 3.00509269,15.1762728 L3,15 L3,5 C3,3.40231912 4.24891996,2.09633912 5.82372721,2.00509269 L6,2 L13,2 Z\"/>\n    </symbol>\n    </svg></div>`\n}\n// options{\n//      generatorText: \"\"\n//      figma: true|undefined - Enable customization for Figma users\n// }\nfunction buildMainHTML(options)\n{\n\n    const verPostfix = \"?\" + ExporterConstants.DOCUMENT_VERSION_PLACEHOLDER\n    const srcPath = \"srcPath\" in options ? options.srcPath : \"\"\n\n    let s = \"\";\n    s += `\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <meta name=\"generator\" content=\"${options.generatorText}\">\n        <title>${options.docName}</title>\n        <link rel=\"shortcut icon\"  type=\"image/png?\" href=\"${srcPath}resources/icon.png${verPostfix}\">\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"${srcPath}resources/viewer.css${verPostfix}\">\n    `\n    if (options.enableAnimations)\n    {\n        s += `\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"${srcPath}resources/animations.css${verPostfix}\">`\n    }\n    if (undefined != options.cssFileNames)\n    {\n        options.cssFileNames.forEach(function (cssFile)\n        {\n            s += `\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"${srcPath}resources/${cssFile}${verPostfix}\">`\n        })\n    }\n    s += `\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"${srcPath}resources/viewer-top.css${verPostfix}\">\n        <script type=\"text/javascript\" src=\"${srcPath}js/other/jquery-3.3.1.min.js\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/other/jquery.hotkeys.js\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/other/jquery.ba-hashchange.min.js\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/ViewerPage.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"data/story.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/Viewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/AbstractViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/CommentsViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/GalleryViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/PresenterViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n\t`\n    if (options.loadLayers)\n    {\n        s += `\n        <script type=\"text/javascript\" src=\"data/handoff.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\" src=\"${srcPath}js/SymbolViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n\t\t`\n        if (options.enableExpViewer)\n        {\n            s += `\n              <script type=\"text/javascript\" src=\"${srcPath}js/ExpViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n            `\n        }\n    }\n    s += `\n        <script type=\"text/javascript\" src=\"${srcPath}js/InfoViewer.js${verPostfix}\" charset=\"UTF-8\"></script>\n        <script type=\"text/javascript\">\n    `\n    if (options.jsCode && options.jsCode != '')\n    {\n        s += `\n        function runJSCode(){${options.jsCode}}\n        `\n    }\n    s += `\n        var viewer = new Viewer(story, \"images\")\n        `\n    if (options.figma)\n    {\n        s += `viewer.figma = true`\n    }\n    s += '</script>'\n\n    if (options.googleCode != '')\n    {\n        if (options.googleCode.startsWith(\"GTM\"))\n        {\n            s += `\n        <!--Google Tag Manager-->\n            <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\n            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\n            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n            'https://www.googletagmanager.com/gtm.js?ID='+i+dl;f.parentNode.insertBefore(j,f);\n            })(window,document,'script','dataLayer','${options.googleCode}');</script>\n            <!--End Google Tag Manager--> `\n        } else\n        {\n            s += `\n        <!--Global site tag(gtag.js) - Google Analytics-->\n            <script async src=\"https://www.googletagmanager.com/gtag/js?ID=${options.googleCode}\"></script>\n            <script>\n            window.dataLayer = window.dataLayer || [];\n             function gtag(){dataLayer.push(arguments);}\n            gtag('js', new Date());\n            gtag('config', '${options.googleCode}');\n            </script>\n    `\n        }\n    }\n    s += `\n        <script>\n        function copyToBuffer(elID) {\n            var copyText = document.getElementById(elID);\n\n            var $temp = $(\"<input>\");\n            $(\"body\").append($temp);\n            $temp.val($(copyText).text()).select();\n            document.execCommand(\"copy\");\n            $temp.remove();\n        }\n    function showFAIconInfo(code) {\n        window.open(\"https://fontawesome.com/icons?d=gallery&q=\" + code, \"_blank\")\n    }\n        </script>\n        <!--HEAD_INJECT-->\n    </head>\n    <style>\n        /* Safari syntax */\n        :-webkit-full-screen {\n            background-color: ${options.backColor};\n        }\n        /* IE11 */\n        :-ms-fullscreen {\n            background-color: ${options.backColor};\n        }\n        /* Standard syntax */\n        :fullscreen {\n            background-color: ${options.backColor};\n        }\n\n    </style>\n    <body class=\"screen\" style=\"background:${options.backColor}\" onload=\"${options.jsCode && options.jsCode != \"\" ? \"runJSCode()\" : \"\"}\">\n            `\n    if (options.googleCode != '')\n    {\n        if (options.googleCode.startsWith(\"GTM\"))\n        {\n            s += `\n            <!--Google Tag Manager(noscript)-->\n        <noscript><iframe src=\"https://www.googletagmanager.com/ns.html?ID=${options.googleCode}\"\n        height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>\n        <!--End Google Tag Manager(noscript)--> `\n        }\n    }\n\n    s += buildMainHTML_NavigationIcons(options)\n    s += `\n        <div class=\"shaft1\"></div><div class=\"shaft2\"></div><div class=\"shaft3\"></div>\n        <div class=\"shaft4\"></div><div class=\"shaft5\"></div><div class=\"shaft6\"></div><div class=\"shaft7\"></div>\n    </div>\n   <!--/load indicator-->\n        <div ID = \"container\">\n        <div ID=\"marker\"></div>\n        <div ID=\"content\" onclick=\"viewer.onContentClick()\"></div>\n        <div ID=\"sidebar\" class=\"hidden\">\n            <div ID=\"symbol_viewer\" class=\"hidden viewer\">\n                <div class=\"title\">\n                  <div style=\"width:100%;\">Element Inspector</div>\n                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.symbolViewer.toggle();  return false;\">\n                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>\n                  </div>\n                </div>\n                <div class=\"checkbox-container\" style=\"margin-top:62px;\">\n                  <input type=\"checkbox\" ID=\"symbol_viewer_symbols\" />\n                  <label for=\"symbol_viewer_symbols\"></label>\n                  <span class=\"checkbox-label\">Show symbols&nbsp;&nbsp;</span>\n                  <select ID=\"lib_selector\" style=\"width:200px;display:none;\"></select>\n                </div>\n                <div ID=\"empty\" style=\"padding: 16px 20px 0 20px;margin-top:20px;\"></div>\n                <div ID=\"symbol_viewer_content\" style=\"margin-top:20px;\">\n                </div>\n            </div>\n            <div ID=\"comments_viewer\" class=\"hidden viewer\">\n                <div class=\"title\">\n                    <div style=\"width:100%;\">Comments</div>\n                    <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.commentsViewer.toggle();  return false;\">\n                        <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>\n                    </div>\n                </div>\n                <div ID=\"comments_viewer_content\">\n                </div>\n            </div>\n            <div ID=\"info_viewer\" class=\"hidden viewer\">\n                <div class=\"title\">\n                  <div style=\"width:100%;\">Changes Inspector</div>\n                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.infoViewer.toggle();  return false;\">\n                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>\n                  </div>\n                </div>\n                <div ID=\"info_viewer_content\" style=\"padding: 72px 20px 0 20px\"></div>\n            </div>\n            <div ID=\"exp_viewer\" class=\"hidden viewer\">\n                <div class=\"title\">\n                  <div style=\"width:100%;\">Widgets</div>\n                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.expViewer.toggle();  return false;\">\n                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>\n                  </div>\n                </div>\n                <div ID=\"controls\" style=\"padding: 62px 20px 0 20px\">\n                    <div class=\"label\">Show</div>\n                    <div>\n                        <input type=\"radio\" ID=\"exp-scope-project\" name=\"exp-scope\" checked onclick=\"viewer.expViewer.setScope('project')\"/><label for=\"exp-scope-project\">all pages</label>&nbsp;\n                        <input type=\"radio\" ID=\"exp-scope-page\" name=\"exp-scope\" onclick=\"viewer.expViewer.setScope('page')\"/><label for=\"exp-scope-page\">current page</label>\n                    </div>\n                    <div class=\"label\">Group by</div>\n                    <div>\n                        <input type=\"radio\" ID=\"exp-mode-widgets\" name=\"exp-mode\" checked onclick=\"viewer.expViewer.setMode('widgets')\"/><label for=\"exp-mode-widgets\">Widgets</label>&nbsp;\n                        <input type=\"radio\" ID=\"exp-mode-pages\" name=\"exp-mode\" onclick=\"viewer.expViewer.setMode('pages')\"/><label for=\"exp-mode-pages\">Pages</label>\n                    </div>\n                    <div class=\"label\">Filter by</div>\n                    <div>\n                        <input type=\"radio\" ID=\"exp-filter-exp\" name=\"exp-filter\" checked onclick=\"viewer.expViewer.setFilter('exp')\"/><label for=\"exp-filter-exp\">Experimental</label>&nbsp;\n                        <input type=\"radio\" ID=\"exp-filter-all\" name=\"exp-filter\" onclick=\"viewer.expViewer.setFilter('all')\"/><label for=\"exp-filter-all\">All</label>\n                    </div>\n                </div>\n                <div ID=\"exp_viewer_content\" style=\"padding: 20px 20px 0 20px\"></div>\n            </div>\n        </div>\n    <div ID=\"content-shadow\" class=\"hidden\" onclick=\"viewer.onContentClick()\"></div>\n    <div ID=\"content-modal\" class=\"contentModal hidden\" onclick=\"viewer.onModalClick()\"></div>\n           <div ID=\"gallery-modal\" class=\"hidden\">\n    <div ID=\"gallery-header\">\n        <div ID=\"gallery-header-container\">\n            <div ID=\"title\"><div>${options.docName}</div><div ID=\"screensamount\"></div></div>\n            <div ID=\"search\"><input type=\"text\" placeholder=\"Search screen...\" ID=\"searchInput\" onkeyup=\"viewer.galleryViewer.onSearchInputChange()\"></div>\n            <div ID=\"right\">\n                <div class=\"checkbox-container\">\n                    <input type=\"checkbox\" ID=\"galleryShowMap\" onclick=\"viewer.galleryViewer.enableMapMode(this.checked)\" />\n                    <label for=\"galleryShowMap\"></label>\n                    <span class=\"checkbox-label\">Show map (M)</span>\n                </div>\n                <div ID=\"closebtn\" onclick=\"viewer.galleryViewer.hide(); return false;\"><svg class=\"svgIcon\"><use xlink:href=\"#icCloseBtn\"></use></svg></div>\n            </div>\n        </div>\n    </div>\n        <div ID = \"gallery\">\n            <div ID=\"grid\"></div></div>\n                <div ID = \"map-controls\">\n                    <div ID = \"map-controls-container\">\n                    <div class=\"checkbox-container\">\n                        <input type = \"checkbox\" ID = \"galleryShowMapLinks\" onclick = \"viewer.galleryViewer.showMapLinks(this.checked)\" />\n                        <label for= \"galleryShowMapLinks\"></label>\n                        <span class=\"checkbox-label\"> Show all links(L)</span>\n                    </div>\n                    <input type = \"range\" min = \"0\" max = \"100\" value = \"50\" class=\"mapZoom\" onclick = \"viewer.galleryViewer.mapZoomChanged(this.value)\">\n                    <span onclick = \"viewer.galleryViewer.resetMapZoom();return false;\" class=\"mapResetZoom\"> Reset zoom</span>\n               </div>\n             </div>\n           </div>\n        <div ID = \"nav\" class=\"${options.hideNav ? \"hidden\" : \"nav\"}\">\n        <div class=\"navLeft\">\n            `\n    //////////////////////////////////////////// GENERATE MENU //////////////////////////////////////\n    // init menu content\n    const menu = []\n    menu.push(\n        {\n            ID: \"\", items: [\n                { ID: \"symbols\", label: \"Handoff\", icon: \"icElementInspector\", key: \"M\", onclick: \"viewer.symbolViewer.toggle();\", on: options.loadLayers },\n                { ID: \"embed\", label: \"Embed code\", icon: \"icEmbed\", key: \"E\", onclick: \"viewer.share();\" },\n                { ID: \"img\", label: \"Full page image\", icon: \"icImage2\", key: \"I\", onclick: \"viewer.openFulImage();\" },\n                { ID: \"menu_comments_viewer\", label: \"Comments\", icon: \"icAnnotation\", key: \"C\", onclick: \"viewer.commentsViewer.toggle();\", hidden: true },\n            ]\n        },\n        {\n            label: \"Settings\",\n            icon: \"icSettings\",\n            ID: \"\", items: [\n                { ID: \"links\", label: \"Hot spots\", icon: \"icPointer\", key: \"Shift\", onclick: \"viewer.toggleLinks(undefined,false);\" },\n                { ID: \"zoom\", label: \"Autoscale\", icon: \"icResize\", key: \"Z\", onclick: \"viewer.toggleZoom(undefined,false);\" },\n                { ID: \"pagegrid\", label: \"Grid layout\", icon: \"icGridLayout\", key: \"L\", onclick: \"viewer.toogleLayout(undefined,false);\" },\n                { ID: \"ui\", label: \"Viewer controls\", icon: \"icHide\", key: \"N\", onclick: \"viewer.toogleUI()\", checked: true },\n                { ID: \"fullscreen\", label: \"Full screen\", icon: \"\", key: \"F\", onclick: \"viewer.hideMenu();viewer.toogleFullScreen(undefined,false);\" },\n            ],\n            switchers: true,\n        },\n        {\n            label: \"Versions\",\n            icon: \"icVersions\",\n            ID: \"\", items: [\n                { ID: \"\", label: \"Up version\", icon: \"icIncreaseVersion\", key: \"⇧ ↑\", onclick: \"viewer.increaseVersion();\", on: options.serverTools !== \"\" },\n                { ID: \"\", label: \"Down version\", icon: \"icDecreaseVersion\", key: \"⇧ ↓\", onclick: \"viewer.decreaseVersion();\", on: options.serverTools !== \"\" },\n                { ID: \"menu_info_viewer\", label: \"Changes history\", icon: \"icList\", key: \"V\", onclick: \"viewer.infoViewer.toggle();\", on: options.serverTools != null && options.serverTools != \"\" },\n            ]\n        },\n        {\n            ID: \"\", items: [\n                { ID: \"\", label: \"View all screens\", icon: \"icGrid\", key: \"G\", onclick: \"viewer.galleryViewer.show()\" },\n                { ID: \"start\", label: \"Go to start\", icon: \"icBack\", key: \"S\", onclick: \"viewer.goToPage(0)\" },\n                { ID: \"play\", label: \"Play\", icon: \"icPlay\", key: \"P\", onclick: \"viewer.presenterViewer.play()\" },\n            ]\n        }\n    )\n    // render menu\n    s += `\n            <div ID=\"menu\" class=\"menu\">\n                `\n    menu.forEach(function (group, index)\n    {\n        //\n        if (group.on != null && !group.on) return\n        const liveItems = group.items.filter(i => i.on == null || i.on)\n        if (!liveItems.length) return\n        //\n        if (index > 0 && (menu[index - 1].label === undefined || group.label === undefined)) s += \"<hr>\\n\"\n        if (group.label !== undefined)\n        {\n            s += `\n            <div class=\"groupe\">\n                <div ID=\"${group.ID}\" class=\"item sub\">\n            `\n            if (group.icon !== undefined && group.icon !== \"\")\n                s += `<svg class =\"svgIcon\"><use xlink: href=\"#${group.icon}\"></use></svg>`\n            s += `\n                <span>${group.label}</span>\n                    <div class =\"tips\">\n                        <svg class ='svgIcon'><use xlink: href=\"#icArrwRight\"></use></svg>\n                    </div>\n                    <div class=\"submenu\">\n                        <div class=\"groupe\">\n            `\n        } else\n        {\n            s += `<div class=\"groupe\" ID=\"${group.ID}\">`\n        }\n        liveItems.forEach(function (item)\n        {\n            if (item.on != null && !item.on) return\n            if (group.switchers)\n            {\n                s += `\n                <div ID=\"${item.ID}-div\" class =\"item item-switcher${item.hidden ? ' hidden' : ''}\">\n                    <div class=\"checkbox-container\" onclick=\"document.getElementById('${item.ID}').checked=!document.getElementById('${item.ID}').checked;${item.onclick};\">\n                        <input type=\"checkbox\" ID=\"${item.ID}\" onclick=\"${item.onclick}; return true;\" ${item.checked ? \"checked\" : \"\"}/>\n                        <label for=\"${item.ID}\"></label>\n                        <span class=\"checkbox-label\">${item.label}</span>\n                    </div>\n                    <div class =\"tips\">${item.key}</div>\n                </div>\n                `\n            } else\n            {\n                s += `\n                <div ID=\"${item.ID}\" class =\"item${item.hidden ? ' hidden' : ''}\" onclick=\"viewer.hideMenu(); ${item.onclick}; return false; \">\n                    <svg class ='svgIcon'><use xlink: href=\"#${item.icon}\"></use></svg>\n                    <span>${item.label}</span>\n                    <div class =\"tips\">${item.key}</div>\n                </div>\n                `\n            }\n        })\n        if (group.label !== undefined)\n        {\n            s += `\n                    </div>\n                  </div>\n              </div>\n            </div>\n            `\n        } else\n            s += `</div>`\n    })\n    s += `\n        </div>\n            `\n    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    s += `\n                <div ID = \"btnMenu\" class=\"btnMenu\" onclick = \"viewer.showMenu()\">\n                    <svg class='svgIcon'><use xlink:href=\"#icMenu\"></use></svg>\n        </div>\n        <!--Button to embed mode-->\n        <div ID=\"btnOpenNew\" style='display:none' class=\"btnMenu\" onclick=\"viewer.openNewWindow();return false;\">\n            <svg class='svgIcon'><use xlink:href=\"#icResize\"></use></svg>\n        </div>\n        <!--Next / Back button-->\n                <div class=\"navPreviewNext\">\n                    <div ID=\"nav-left-prev\" class=\"btnPreview\" onclick=\"viewer.previous(); return false;\" title=\"Previous screen\">\n                        <svg class='svgIcon'><use xlink:href=\"#icArrwLeft\"></use></svg>\n                    </div>\n                    <div ID=\"nav-left-next\" class=\"btnNext\" onclick=\"viewer.next(); return false;\" title=\"Next screen\"><svg class='svgIcon'><use xlink:href=\"#icArrwRight\"></use></svg></div>\n                </div>\n        </div>\n                <div class=\"navCenter\">\n                    <div class=\"pageName title\">Default button</div>\n                    <div ID=\"info_viewer_options\" class=\"infoViewerMode hidden\">\n                        <input type=\"radio\" name=\"info_viewer_mode\" ID=\"info_viewer_mode_diff\" value=\"diff\" checked onclick=\"viewer.infoViewer.pageChanged()\" disabled /><label for=\"info_viewer_mode_diff\">Differences</label>\n                        <input type=\"radio\" name=\"info_viewer_mode\" ID=\"info_viewer_mode_prev\" value=\"prev\" onclick=\"viewer.infoViewer.pageChanged()\" disabled><label for=\"info_viewer_mode_prev\">Prev version</label>\n                            <input type=\"radio\" name=\"info_viewer_mode\" ID=\"info_viewer_mode_new\" value=\"new \" onclick=\"viewer.infoViewer.pageChanged()\" disabled><label for=\"info_viewer_mode_new\">New version</label>\n                            </div>\n                    </div>\n                    <div class=\"navRight\">\n                        <div ID=\"loading\" class=\"hidden\">\n                            <div class=\"lds-ring\"><div></div><div></div><div></div><div></div></div>\n                        </div>\n                        <div ID=\"pageComments\" onclick=\"commentsViewer.toggle(); return false;\" class=\"hidden\">\n                            <svg class=\"svgIcon\"> <use xlink:href=\"#icAddComment\"></use></svg>\n                            <div ID=\"counter\"></div>\n                        </div>\n                        <div ID=\"experimental\" onclick=\"viewer.expViewer.toggle();return false;\" class=\"hidden\">\n                            <svg class=\"svgIcon\"> <use xlink:href=\"#icExperimental\"></use></svg>\n                        </div>\n                    </div>\n                </div>\n        </div>\n    </div>\n                `\n    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    s += `\n    </body>\n</htm>\n                `\n    return s\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/AbstractViewer.js",
    "content": "\nclass AbstractViewer {\n    constructor(divID) {\n        this.divID = divID\n        // common viewer settings, can be changed in child constructors\n        this.isSidebarChild = true\n        this.blockMainNavigation = false\n        this.enableTopNavigation = false\n        this.alwaysHandlePageChanged = false\n        this.preventCustomTextSearch = true\n\n        // internal viewer props, can be read by child \n        this.inited = false\n        this.visible = false\n        this.mouseover = false\n\n        //\n        viewer.allChilds.push(this)\n    }\n\n\n    initialize(force = false) {\n        if (!force && this.inited) return false\n        //\n        if (this.preventCustomTextSearch) {\n            const div = $('#' + this.divID)\n            div.mouseenter(function () {\n                this.mouseover = true\n            })\n            div.mouseleave(function () {\n                this.mouseover = false\n            })\n        }\n        //\n        this.inited = true\n        return true\n    }\n\n    // called by Viewer\n    customTextSearchPrevented() {\n        return this.preventCustomTextSearch && this.mouseover\n    }\n\n    pageChanged() {\n\n    }\n\n    // called by viewer\n    viewerResized() {\n\n    }\n\n    hide() {\n        viewer.hideChild()\n    }\n\n    show() {\n        viewer.showChild(this)\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    handleKeyDown(jevent) {\n        return false\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        return false\n    }\n    onContentClick() {\n        return false\n    }\n\n\n    isVisible() {\n        return this.visible\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    _showSelf() {\n        this.visible = true\n    }\n\n    _hideSelf() {\n        this.visible = false\n        this.mouseover = false\n    }\n\n\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/CommentsViewer.js",
    "content": "\nlet commentsViewer = null;\n\nclass CommentsViewer extends AbstractViewer {\n    constructor() {\n        super(\"comments_viewer\")\n\n        this.alwaysHandlePageChanged = true\n        this.preventCustomTextSearch = true\n\n        this.comments = null\n        this.inputFocused = false\n        commentsViewer = this\n    }\n\n    initialize(force = false) {\n        if (!super.initialize(force)) return\n\n        this._showLoadingMessage()\n        this._showComments();\n\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        $('#comments_viewer').addClass(\"hidden\")\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").show()\n        this.comments.hideViewer()\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (67 == event.which) { // c\n            // Key \"C\" activates self\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    pageChanged() {\n        this._showCommentCounter()\n        if (!this.visible) {\n            return\n        }\n        if (!this.inited) return this.initialize();\n        comments.reloadComments()\n    }\n\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!comments.inputFocused && 67 == event.which) { // key \"g\"\n            // Key \"G\" deactivates Symbol Viewer\n            this.toggle()\n        } else if (comments.inputFocused) {\n            return true\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n    /////////////////////////////////////////////////\n\n    askCommentCounters(handler) {\n        var formData = new FormData();\n        this.sendRequest(formData, \"getProjectInfo\", handler)\n    }\n\n    _showCommentCounter() {\n        var formData = new FormData();\n        this.sendRequest(formData, \"getPageInfo\", function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                commentsViewer.updateCommentCounter(result.data.commentsTotal)\n            } else {\n                console.log(result.message);\n            }\n            return\n\n        })\n        /*\n            var xhr = new XMLHttpRequest();\n            xhr.open(\"POST\", story.commentsURL + \"&cmd=getPageInfo\", true);\n            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n            xhr.onload = function () {\n                var result = JSON.parse(this.responseText);\n                //\n                if (\"ok\" == result.status) {\n                    commentsViewer.updateCommentCounter(result.data.commentsTotal)\n                } else {\n                    console.log(result.message);\n                }\n                return\n    \n            };\n            xhr.send(formData);\n            */\n    }\n\n    sendRequest(formData, cmd, handler) {\n        var xhr = new XMLHttpRequest()\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=\" + cmd, true)\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')\n        xhr.onload = handler\n        xhr.send(formData)\n    }\n\n    updateCommentCounter(total) {\n        var div = $('#nav #pageComments #counter')\n        if (total > 0) {\n            div.html(total);\n            div.show()\n        } else {\n            div.hide()\n        }\n    }\n\n    _showComments() {\n        var formData = new FormData();\n        //\n        var uid = window.localStorage.getItem(\"comments-uid\")\n        var sid = window.localStorage.getItem(\"comments-sid\")\n        if (null != uid && null != sid) {\n            formData.append(\"uid\", uid);\n            formData.append(\"sid\", sid);\n        }\n        //\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=buildFullHTML\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                $('#comments_viewer_content').html(result.data);\n            } else {\n                $('#comments_viewer_content').html(result.message);\n            }\n            return\n\n        };\n        xhr.send(formData);\n    }\n\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#comments_viewer').removeClass(\"hidden\")\n        super._showSelf()\n        //\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").hide()\n        //\n        if (this.comments) this.comments.showViewer()\n    }\n\n    _showLoadingMessage() {\n        $(\"#comments_viewer_content\").html(\"Loading...\")\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/ExpViewer.js",
    "content": "const EXP_SCOPE_PAGE = \"page\"\nconst EXP_SCOPE_PROJECT = \"project\"\n\nconst EXP_MODE_PAGES = \"pages\"\nconst EXP_MODE_WIDGETS = \"widgets\"\n\nconst EXP_FILTER_EXP = \"exp\"\nconst EXP_FILTER_ALL = \"all\"\n\nclass ExpViewer extends AbstractViewer {\n\n    constructor() {\n        super(\"exp_viewer\")\n        this.preventCustomTextSearch = true\n        //\n        this.scope = EXP_SCOPE_PROJECT\n        this.mode = EXP_MODE_WIDGETS\n        this.filter = EXP_FILTER_EXP\n        //\n        this.highlightWidgetName = null\n    }\n\n    initialize(force = false) {\n        if (!super.initialize(force)) return\n        // load data\n        this._buildContent()\n    }\n\n    goPage(index) {\n        this.hide()\n        viewer.goToPage(index)\n        viewer.symbolViewer.showFromExpViewer(this.highlightWidgetName)\n    }\n\n    //////////\n    _buildContent() {\n        if (this.mode === EXP_MODE_PAGES)\n            this._buildContentPages()\n        else\n            this._buildContentWidgets()\n    }\n\n    //////////\n    _buildContentPages() {\n        let html = `<div class=\"pages\">`\n\n        if (this.scope === EXP_SCOPE_PAGE) {\n            html += this._getContentForPage(layersData[viewer.currentPage.index])\n        } else {\n            // scan all pages\n            layersData.filter(page => \"c\" in page).forEach(page => {\n                html += this._getContentForPage(page)\n            }, this);\n        }\n\n        html += `</div>`\n        //\n        let contentDiv = $(\"#exp_viewer_content\")\n        contentDiv.html(html)\n    }\n\n    _getContentForPage(page) {\n        let html = \"\"\n        // try to find experimenal components\n        let foundExpLayers = {}\n        this._findExpLayers(page.c, foundExpLayers)\n        const symbols = Object.keys(foundExpLayers)\n        if (!symbols.length) return html\n\n        // show page with experimental components\n        html += `\n     <div ID=\"${page.index}\" class=\"page\">\n         <a href=\"#\" onclick=\"viewer.expViewer.goPage(${page.index})\" class=\"link\">${page.n}</a>\n     `\n        //\n        function cleanLabel(label) {\n            return label.replace(\"-EXPERIMENTAL\", \"-EXP\")\n        }\n        symbols.forEach(symbolName => {\n            html += `\n     <div class=\"layer\">\n         ${cleanLabel(symbolName)}\n     `\n            const total = foundExpLayers[symbolName]\n            if (total > 1) {\n                html += `\n             <span class=\"counter\">(${total})</span>\n         `\n            }\n            html += `\n     </div>\n     `\n        }, this)\n        //\n        html += `\n     </div>\n     `\n        return html\n    }\n\n    _findExpLayers(layers, foundExpLayers, layersExt = null, topPage = null) {\n        layers.forEach(l => {\n            let page = topPage\n            if (layersExt != null && !topPage) page = l\n            if (l.tp === \"SI\" && l.s && (this.filter == EXP_FILTER_ALL || l.s.includes(\"EXPERIMENTAL\"))) {\n                let name = this.filter == EXP_FILTER_ALL ? l.s.replace(\"-EXPERIMENTAL\", \"-EXP\") : (l.s.split(\"-EXPERIMENTAL\")[0] + \"-EXP\")\n                if (!(name in foundExpLayers)) foundExpLayers[name] = 0\n                foundExpLayers[name]++\n                if (layersExt != null) {\n                    if (!(name in layersExt)) layersExt[name] = {\n                        pages: {}\n                    }\n                    layersExt[name].pages[page.index] = page\n                }\n            }\n            if (l.c && l.c.length) this._findExpLayers(l.c, foundExpLayers, layersExt, page)\n        }, this)\n    }\n    ////////////////////////////////////////////////\n    _buildContentWidgets() {\n        const [widgets, widgetSet, widgetsExt] = this._getWidgetsSorted()\n\n        let html = `<div class=\"widgets\">`\n        // Scan top-level layers(pages) with childs\n        widgets.forEach(widget => {\n            html += this._getContentForWidget(widgetSet, widgetsExt, widget)\n        }, this);\n\n        html += `</div>`\n        //\n        let contentDiv = $(\"#exp_viewer_content\")\n        contentDiv.html(html)\n    }\n\n    _getWidgetsSorted() {\n        const pages = this.scope === EXP_SCOPE_PROJECT ? layersData.filter(page => \"c\" in page) : [layersData[viewer.currentPage.index]]\n        let widgetSet = {}, widgetsExt = {}\n        this._findExpLayers(pages, widgetSet, widgetsExt)\n        const widgets = Object.keys(widgetSet).sort()\n        return [widgets, widgetSet, widgetsExt]\n    }\n\n    _getContentForWidget(widgetSet, widgetsExt, widgetName) {\n        let html = \"\"\n        let widgetNameCleaned = widgetName\n        let widgetInfo = widgetsExt[widgetName]\n        let pageCount = Object.keys(widgetInfo.pages).length\n        const highlightWidgetClass = this.highlightWidgetName && widgetNameCleaned.includes(this.highlightWidgetName) ? \"highlight\" : \"\"\n\n        // show page with experimental components\n        html += `\n     <div ID=\"exp-widget-${widgetName}\" class=\"widget ${highlightWidgetClass}\">\n         <a href=\"#\" onclick=\"viewer.expViewer.goWidget('${widgetName}')\" class=\"link\">${widgetNameCleaned}</a> <span class=\"counter\">(${pageCount})</span>\n     `\n        //     \n        Object.keys(widgetInfo.pages).forEach(pageIndex => {\n            const page = layersData[pageIndex]\n            html += `        \n     <div onclick=\"viewer.expViewer.goPage(${pageIndex})\" class=\"page hidden\">\n         ${page.n}\n     </div>\n     `\n        }, this)\n        //\n        html += `\n     </div >\n                `\n        return html\n    }\n\n\n    goWidget(widgetName) {\n        const pagesDiv = $(document.getElementById(\"exp-widget-\" + widgetName))\n        const widgetsDiv = pagesDiv.find(\".page\")\n        this._toogleClass(widgetsDiv, \"hidden\")\n    }\n\n    _toogleClass(obj, className) {\n        if (!obj.hasClass(className)) obj.addClass(className); else obj.removeClass(className)\n    }\n\n    ////////////////////////////\n    setScope(scope) {\n        this.scope = scope\n        this._buildContent()\n    }\n    setMode(mode) {\n        this.mode = mode\n        this._buildContent()\n    }\n    setFilter(filter) {\n        this.filter = filter\n        this._buildContent()\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n    _hideSelf() {\n        $('#exp_viewer').addClass(\"hidden\")\n        //this.highlightWidgetName = null\n\n        super._hideSelf()\n    }\n\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#exp_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n    }\n\n    // called by Viewer\n    pageChanged() {\n        if (this.scope === EXP_SCOPE_PAGE) {\n            this._buildContent()\n        }\n    }\n\n    highlightWidget(widgetName) {\n        this.highlightWidgetName = widgetName.replace(\"-EXPERIMENTAL\", \"-EXP\")\n        if (!this.highlightWidgetName.includes(\"-EXP\")) {\n            $(\"#exp-filter-exp\").prop(\"checked\", false)\n            $(\"#exp-filter-all\").prop(\"checked\", true)\n            this.setFilter(EXP_FILTER_ALL)\n        }\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/GalleryViewer.js",
    "content": "const GALLERY_TOP_MARGIN = 80\nconst GALLERY_LEFTRIGH_MARGIN = 40\n\n\nclass GalleryViewerMapLink {\n    constructor(index, link, spage, dpage) {\n        this.index = index\n        this.link = link\n        this.spage = spage\n        this.dpage = dpage\n        //\n        if (undefined == spage.slinks) spage.slinks = []\n        if (undefined == dpage.dlinks) dpage.dlinks = []\n        spage.slinks.push(this)\n        dpage.dlinks.push(this)\n        //\n    }\n\n    buildCode(zoom, visible) {\n        const page = this.spage\n        const dpage = this.dpage\n        const l = this.link\n        let svg = \"\"\n        //\n        var lsx = l.rect.x + l.rect.width / 2 + page.finalLeft\n        var lsy = l.rect.y + l.rect.height / 2 + page.finalTop\n        //\n        var ldx0 = dpage.finalLeft\n        var ldx1 = dpage.finalLeft + dpage.width\n        var ldy0 = dpage.finalTop\n        var ldx = 0, ldy = 0, dc = 0\n        // find the best target edge to connect with\n        if (ldx0 > lsx) {\n            ldx = ldx0\n            ldy = ldy0 + dpage.height / 2\n            lsx += l.rect.width / 2 // place start to hotspot right edge\n            dc = 50\n        } else if (lsx > ldx1) {\n            ldx = ldx1\n            ldy = ldy0 + dpage.height / 2\n            lsx -= l.rect.width / 2 // place start to hotspot left edge\n            dc = 50\n        } else if (ldy0 > lsy) {\n            lsy += l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0\n            dc = 0\n        } else {\n            lsy -= l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0 + dpage.height\n            dc = 0\n        }\n        //\n        //\n        const styleCode = visible ? \"\" : \" style='display:none' \"\n        svg += \"<path \" + styleCode + \"marker-end='url(#arrow)' id='l\" + this.index + \"' d='M \"\n            + Math.round(lsx * zoom) + \" \"\n            + Math.round(lsy * zoom) + \" \"\n            + \"q \"\n            + Math.round((ldx - lsx) / 2 * zoom) + \" \"\n            + dc + \" \"\n            + Math.round((ldx - lsx) * zoom) + \" \"\n            + Math.round((ldy - lsy) * zoom) + \" \"\n            + \"'/>\"\n        //\n        svg += \"<circle \" + styleCode + \"' id='s\" + this.index + \"' cx='\" + Math.round(lsx * zoom) + \"' cy='\" + Math.round(lsy * zoom) + \"' r='3'/>\"\n        //\n        return svg\n    }\n}\n\nclass GalleryViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.isSidebarChild = false\n        this.blockMainNavigation = true\n        this.enableTopNavigation = true\n        this.mapLinks = null\n        this.mapFocusedPage = null\n        this.isMapMode = viewer.figma === true // Show map by default for Figma users\n        //\n        const restoredMode = window.localStorage.getItem(\"galleryIsModeAbs\") == \"true\"\n        if (null != restoredMode) this.isMapMode = restoredMode\n        $(\"#gallery-header-container #right #galleryShowMap\").prop('checked', this.isMapMode);\n        //\n        this.isMapLinksVisible = false\n        if (window.localStorage.getItem(\"galleryIsLinkVisible\") == \"true\") this.isMapLinksVisible = true\n        $(\"#map-controls #map-controls-container #galleryShowMapLinks\").prop('checked', this.isMapLinksVisible);\n        //\n        this.mapZoom = 0.2\n        this.isCustomMapZoom = false\n        this.currentFullWidth = null\n        this.searchInputFocused = false\n        //\n        this._initPages()\n    }\n\n    _initPages() {\n        this.pages = this.isMapMode ? viewer.visStoryPages : viewer.userStoryPages\n    }\n\n    initialize(force = false, skipZoomUpdate = false) {\n        if (!force && this.inited) return\n\n        // Load page comment counters\n        if (viewer.commentsViewer) {\n            var formData = new FormData();\n            viewer.commentsViewer.askCommentCounters(function () {\n                var result = JSON.parse(this.responseText);\n                //\n                if (\"ok\" == result.status) {\n                    viewer.galleryViewer._updateCommentCounters(result.data)\n                } else {\n                    console.log(result.message);\n                }\n                return\n            })\n        }\n\n        // adjust main container for current mode\n        if (this.isMapMode) {\n            $(\"#gallery\").removeClass(\"gallery-grid\")\n        } else {\n            $(\"#gallery\").addClass(\"gallery-grid\")\n        }\n\n        $('#gallery #grid').empty()\n        this.loadPages();\n\n        //load amount of pages to gallery title\n        document.getElementById(\"screensamount\").innerHTML = this.pages.length + \" screens\";\n\n        // Adjust map zoom\n        const zoomContainter = $(\"#map-controls\")\n        if (this.isMapMode) {\n            if (!skipZoomUpdate) {\n                const zoomControl = $(\".mapZoom\")\n                zoomControl.val(this.mapZoom * 100)\n            }\n            zoomContainter.show();\n        } else {\n            zoomContainter.hide()\n        }\n\n        this.inited = true\n    }\n\n    _updateCommentCounters(pagesInfo) {\n        this.pages.forEach(function (page) {\n            const pageID = page.getHash()\n            const pageInfo = pagesInfo[pageID]\n            if (!pageInfo) {\n                console.log(\"Can't find page info for \" + pageID);\n                return\n            }\n            //\n            let text = \"\"\n            if (pageInfo['commentsTotal'] != 0) text = \"  (\" + pageInfo['commentsTotal'] + \")\"\n            //\n            if (this.isMapMode) {\n                $(\"#gallery #grid #t\" + page.index + \" #comm\").text(text)\n            } else {\n                $(\"#gallery #grid #\" + page.index + \" #comm\").text(text)\n            }\n\n        }, this);\n    }\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!this.searchInputFocused && 71 == event.which && !event.metaKey) { // key \"g\"\n            // Key \"G\" activates/deactivates Symbol Viewer\n            this.toggle()\n        } else if (this.searchInputFocused) {\n            return true\n        } else if (76 == event.which) { // key \"l\"\n            $(\"#galleryShowMapLinks\").click()\n        } else if (77 == event.which) { // key \"m\"\n            $(\"#galleryShowMap\").click()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    mapZoomChanged(zoomValue) {\n        this.mapZoom = zoomValue / 200\n        this.isCustomMapZoom = true\n        this.initialize(true, true)\n    }\n\n    viewerResized() {\n        if (!this.isMapMode) return\n        this.initialize(true)\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (71 == event.which && !event.metaKey) { // g\n            // Key \"G\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    /// calling from parent\n    handleURLParam(paramValue) {\n        const enableMap = \"m\" == paramValue\n        if (enableMap != this.isMapMode) {\n            this.enableMapMode(enableMap)\n            $(\"#galleryShowMap\").prop('checked', enableMap);\n        }\n    }\n\n    // Calling from UI\n    enableMapMode(enabled) {\n        window.localStorage.setItem(\"galleryIsModeAbs\", enabled)\n        this.isMapMode = enabled\n        this._initPages()\n        this.initialize(true)\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    // Calling from UI\n    showMapLinks(visible) {\n        window.localStorage.setItem(\"galleryIsLinkVisible\", visible)\n        this.isMapLinksVisible = visible\n        this._showHideMapLinks(visible ? null : false)\n    }\n\n    // Calling from UI\n    resetMapZoom() {\n        this.isCustomMapZoom = false\n        this.initialize(true)\n    }\n\n    _showSelf() {\n        if (!this.inited || this.currentFullWidth != viewer.fullWidth) this.initialize(true)\n\n        $('#gallery-modal').removeClass('hidden');\n\n        $('#searchInput').focusin(function () {\n            viewer.galleryViewer.searchInputFocused = true\n        })\n        $('#searchInput').focusout(function () {\n            viewer.galleryViewer.searchInputFocused = false\n        })\n        $('#searchInput').focus()\n\n\n        super._showSelf()\n\n        // Redraw search results\n        this.onSearchInputChange()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    _hideSelf() {\n        $('#gallery-modal').addClass('hidden');\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n\n\n    loadPages() {\n        if (this.isMapMode) return this.loadPagesAbs()\n        this.pages.forEach(function (page) {\n            this.loadOnePage(page);\n        }, this);\n    }\n\n    loadPagesAbs() {\n        let groupSpace = 80\n\n        // find maximum width of Sketch page with artoards\n        let maxGroupWidth = null\n\n        story.groups.forEach(function (group) {\n            // find group pages\n            const pages = this.pages.filter(s => s.groupID == group.id)\n            group.pages = pages // save for below\n            if (pages.length == 0) return\n            ///\n            let left = null, right = null, top = null, bottom = null\n            pages.forEach(function (page) {\n                page.group = group\n                page.slinks = []\n                page.dlinks = []\n                //\n                if (null == top || page.y < top) top = page.y\n                if (null == left || page.x < left) left = page.x\n                if (null == right || (page.x + page.width) > right) right = page.x + page.width\n                if (null == bottom || (page.y + page.height) > bottom) bottom = page.y + page.height\n            }, this);\n            const groupWidth = right - left\n            if (null == maxGroupWidth || groupWidth > maxGroupWidth) maxGroupWidth = groupWidth\n            // // save for below\n            group.top = top\n            group.bottom = bottom\n            group.left = left\n            group.right = right\n            group.height = bottom - top\n        }, this);\n\n        // Calculate zoom to fit max width\n        if (!this.isCustomMapZoom) {\n            this.mapZoom = (viewer.fullWidth - GALLERY_LEFTRIGH_MARGIN * 2) / maxGroupWidth\n            if (this.mapZoom > 0.6) this.mapZoom = 0.6\n        }\n        this.currentFullWidth = viewer.fullWidth\n\n        // show pages using their coordinates and current zoom\n        let deltaY = 0\n        let fullHeight = 0\n        const groupTitleHeight = 40 / this.mapZoom\n        story.groups.forEach(function (group) {\n            if (group.pages.length == 0) return\n            ///\n            let top = deltaY - group.top\n            const left = group.left\n            group.finalTop = deltaY\n            top += groupTitleHeight\n            //// show group title\n            this.addMapPageGroupTitle(group)\n            //// show pages\n            group.pages.forEach(function (page) {                //\n                this.loadOnePageAbs(page, left, top);\n            }, this);\n            //\n            fullHeight += group.height\n            //\n            deltaY += group.height + groupSpace + groupTitleHeight + 30\n        }, this);\n        fullHeight = deltaY //+= groupSpace * (story.groups.length - 1)\n\n        //\n        this._buildMapLinks(maxGroupWidth, fullHeight)\n    }\n\n\n    addMapPageGroupTitle(group) {\n        let style = this._valueToStyle(\"left\", 0, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", group.finalTop, GALLERY_TOP_MARGIN)\n\n        var div = $('<div/>', {\n            id: \"g\" + group.id,\n            class: \"groupTitle\",\n            style: style,\n        });\n        div.html(group.name)\n        div.appendTo($('#gallery #grid'));\n\n    }\n\n\n    selectPage(index) {\n        this.hide()\n        viewer.goToPage(index, this.actualSearchText)\n    }\n\n    mouseEnterPage(index) {\n        if (this.isMapLinksVisible) return\n        //\n        if (this.mapFocusedPage) this.mapFocusedPage.showHideGalleryLinks(false)\n        const page = story.pages[index]\n        page.showHideGalleryLinks(true)\n        this.mapFocusedPage = page\n    }\n\n\n    loadOnePage(page) {\n        var imageURI = page.image\n\n        var div = $('<div/>', {\n            id: page.index,\n            class: \"grid-cell\",\n        });\n\n        var divWrapper = $('<div/>', {\n            class: \"grid-cell-wrapper\"\n        });\n        var divMain = $('<div/>', {\n            class: \"grid-cell-main\"\n        });\n        div.click(function (e) {\n            viewer.galleryViewer.selectPage(parseInt(this.id));\n        });\n        div.appendTo($('#gallery #grid'));\n\n        var img = $('<img/>', {\n            class: \"gallery-image\",\n            alt: page.title,\n            src: encodeURIComponent(viewer.files) + '/previews/' + encodeURIComponent(imageURI),\n        });\n\n        img.appendTo(divMain);\n        divMain.appendTo(divWrapper);\n        divWrapper.appendTo(div);\n\n        var divTitle = $('<div/>', {\n            class: \"div-page-title\"\n        });\n\n        var spanTitle = $('<span/>', {\n            id: \"page-title\",\n            alt: page.title,\n        });\n        spanTitle.appendTo(divTitle);\n\n        var title = $('<div/>', {\n            text: page.title\n        });\n        title.appendTo(spanTitle);\n\n        if (viewer.commentsViewer) {\n            var comments = $('<div/>', {\n                id: \"comm\",\n                text: \"\"\n            });\n            comments.appendTo(spanTitle);\n        }\n\n        divTitle.appendTo(divMain);\n    }\n\n    loadOnePageAbs(page, pageLeft, pageTop) {\n        page.finalTop = pageTop + page.y\n        page.finalLeft = page.x - pageLeft\n\n        {\n            let style = this._valueToStyle(\"left\", page.finalLeft, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", page.finalTop, GALLERY_TOP_MARGIN)\n                + this._valueToStyle(\"width\", page.width) + this._valueToStyle(\"height\", page.height)\n            if (undefined != story.backColor) {\n                style += \"background-color:\" + story.backColor + \";\"\n            }\n\n            var div = $('<div/>', {\n                id: page.index,\n                class: \"galleryArtboardAbs\",\n                style: style,\n            });\n\n            div.click(function (e) {\n                viewer.galleryViewer.selectPage(parseInt(this.id));\n            });\n            div.mouseenter(function () {\n                viewer.galleryViewer.mouseEnterPage(this.id)\n            })\n            div.appendTo($('#gallery #grid'));\n\n            const width = Math.round(this.mapZoom * page.width)\n            // Show large image for large width\n            const previewWidth = 522\n            let src = encodeURIComponent(viewer.files)\n            if (width < previewWidth) {\n                src += '/previews/' + encodeURIComponent(page.image)\n            } else {\n                src += '/full/' + encodeURIComponent(page.image)\n            }\n\n            var img = $('<img/>', {\n                class: \"gallery-map-image\",\n                alt: page.title,\n                width: width,\n                height: Math.round(this.mapZoom * page.height) + \"px\",\n                src: src\n            });\n            img.appendTo(div);\n        }\n\n        /// add title\n        {\n            let style = this._valueToStyle(\"left\", page.finalLeft, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", page.finalTop + page.height, GALLERY_TOP_MARGIN)\n                + this._valueToStyle(\"width\", page.width) //+ this._valueToStyle(\"height\", 20)\n            var div = $('<div/>', {\n                id: \"t\" + page.index,\n                class: \"galleryArtboardLabelAbs\",\n                style: style,\n            });\n            div.text(page.title)\n            div.appendTo($('#gallery #grid'))\n            var span = $('<span/>', {\n                id: \"comm\"\n            });\n            span.appendTo(div)\n        }\n\n    }\n\n    _valueToStyle(styleName, v, absDelta = 0) {\n        return styleName + \": \" + Math.round(v * this.mapZoom + absDelta) + \"px;\"\n    }\n\n    _showHideMapLinks(show) {\n        this.pages.forEach(function (page) {\n            page.showHideGalleryLinks(show)\n        });\n    }\n\n\n    _buildMapLinks(finalWidth, finalHeight) {\n\n        // build scene\n        let svg = \"<svg\"\n            + \" height='\" + Math.abs(Math.round(finalHeight * this.mapZoom)) + \"'\"\n            + \" width='\" + Math.abs(Math.round(finalWidth * this.mapZoom)) + \"'\"\n            + \" >\"\n        svg += `\n            <defs>\n             <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\"\n                markerWidth=\"6\" markerHeight=\"6\" fill=\"#F89000\"\n                orient=\"auto\">\n                 <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#F89000\"/>\n             </marker>\n            </defs>\n        `\n        //\n        let indexCounter = 0\n        this.mapLinks = []\n        //\n        this.pages.forEach(function (page) {\n            /// Show links to other pages\n            page.links.forEach(function (l) {\n                // valide destination page\n                if (l.page == page.index) return\n                const dpage = story.pages[l.page]\n                if (!dpage || \"external\" == dpage.type) return\n                // build SVG coode for the link\n                const link = new GalleryViewerMapLink(indexCounter++, l, page, dpage)\n                svg += link.buildCode(this.mapZoom, this.isMapLinksVisible)\n                this.mapLinks.push(link)\n            }, this)\n        }, this)\n\n        svg += \"</svg>\"\n        $('#gallery #grid').append(svg)\n    }\n\n    //Search page in gallery by page name\n    onSearchInputChange() {\n        var keyword = $(\"#searchInput\").val().toLowerCase().trim()\n        if (undefined == this.actualSearchText && \"\" == keyword) return\n\n        var foundScreenAmount = 0;\n\n        this.pages.forEach(function (page) {\n            const title = page.title.toLowerCase().trim()\n            const div = $(\"#gallery #grid #\" + page.index)\n            let visible = keyword == ''\n            let foundTextLayers = []\n\n            // Reset prev results\n            div.find(\".searchFocusedResultDiv,.searchResultDiv\").remove()\n\n            // Search in artboard title and image name            \n            if (keyword != '') {\n                visible = title.includes(keyword) || page.image.includes(keyword)\n\n                // Search in text layers                \n                page.findTextLayersByText(keyword, foundTextLayers)\n                if (foundTextLayers.length > 0) {\n                    visible = true\n                }\n                //\n            }\n\n            if (visible) foundScreenAmount++\n            page.visibleInGallery = visible\n            if (visible) {\n                div.removeClass(\"galleryArtboardAbsHidden\")\n                if (visible) {\n                    foundTextLayers.forEach(function (l) {\n                        viewer.galleryViewer._findTextShowElement(page, l, div)\n                    })\n                }\n            } else {\n                div.addClass(\"galleryArtboardAbsHidden\")\n            }\n        });\n\n        // Final procedures\n        this.actualSearchText = keyword != '' ? keyword : undefined\n        viewer.galleryViewer._showHideMapLinks()\n\n        //load amount of pages to gallery title\n        $(\"#screensamount\").html(foundScreenAmount + \" screens\")\n    }\n\n    _findTextShowElement(page, l, div) {\n        const isFocused = true\n        const padding = isFocused ? 2 : 0\n        const divWidth = div.innerWidth()\n        const zoom = page.width / divWidth\n\n        let x = l.x / zoom\n        let y = l.y / zoom\n\n        // show layer border\n        var style = \"left: \" + x + \"px; top:\" + y + \"px; \"\n        style += \"width: \" + (l.w / zoom + padding * 2) + \"px; height:\" + (l.h / zoom + padding * 2) + \"px; \"\n        var elemDiv = $(\"<div>\", {\n            class: isFocused ? \"searchFocusedResultDiv\" : \"searchResultDiv\",\n        }).attr('style', style)\n\n        elemDiv.appendTo(div)\n    }\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/InfoViewer.js",
    "content": "function getVersionInfoRequest() {\n    var resp = this\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['recs']) {\n                viewer.infoViewer._loadData(data);\n                return true\n            }\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return false\n}\n\n\nclass infoViewer extends AbstractViewer {\n    constructor() {\n        super(\"info_viewer\")\n\n        this.preventCustomTextSearch = true\n\n        this.screenDiffs = []\n        this.mode = 'diff'\n        this.published = story.docVersion != 'V_V_V'\n        this.currentRec = null\n    }\n\n    initialize(force = false) {\n        if (!super.initialize(force)) return\n\n        // init document common data here        \n        this._showStatic()\n        if (this.published) {\n            this._showLoadingMessage()\n            this._askServerTools();\n        }\n    }\n\n    goToVersion(recIndex) {\n        const rec = this.data['recs'][recIndex]\n        const newURL = rec['url'] + '?' + encodeURIComponent(viewer.currentPage.getHash())\n        window.open(newURL, \"_self\");\n    }\n\n\n    goToScreen(recIndex, screenIndex, pageIndex) {\n        this.currentRec = this.data['recs'][recIndex]\n        viewer.goToPage(pageIndex)\n    }\n\n    // delta = -1 or +1\n    switchMode(delta) {\n        const modes = ['diff', 'prev', 'new']\n        var posMode = modes.indexOf(this.mode)\n        if (undefined == posMode) return\n\n        posMode += delta\n        if (posMode < 0) posMode = modes.length - 1\n        if (posMode >= modes.length) posMode = 0\n\n        modes.forEach(function (mode, pos) {\n            var radio = $(\"#info_viewer_mode_\" + mode)\n            radio.prop('checked', pos == posMode)\n        }, this)\n\n        this.pageChanged()\n\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        this._restoreNewImages()\n        $('#info_viewer').addClass(\"hidden\")\n        $('info_viewer_options').addClass(\"hidden\")\n        if (document.location.search.includes('v')) {\n            document.location.search = \"\" // remove ?v\n        }\n        super._hideSelf()\n    }\n\n    pageChanged() {\n\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        $(\"#info_viewer_mode_diff\").prop('disabled', disabled);\n        $(\"#info_viewer_mode_new\").prop('disabled', disabled);\n        $(\"#info_viewer_mode_prev\").prop('disabled', disabled);\n        if (disabled) return\n        $('#info_viewer_options').removeClass(\"hidden\")\n\n        this._showCurrentPageDiffs()\n    }\n\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (38 == event.which && event.shiftKey) {   // shift + up\n            viewer.increaseVersion()\n        } else if (40 == event.which && event.shiftKey) {   // shift + down\n            viewer.decreaseVersion()\n        } else if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else if (!disabled && 37 == event.which && event.shiftKey) {   // left + shift\n            this.switchMode(-1)\n        } else if (!disabled && 39 == event.which && event.shiftKey) {   // right + shift\n            this.switchMode(1)\n        } else if (event.shiftKey) {  //shift\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    showRecDetails(index, forNew) {\n        const rec = this.data['recs'][index]\n        if (!rec) return\n        ///\n        const div = $(\"#info_viewer .record .\" + (forNew ? 'n' : 'u') + \"screens#\" + index)\n        if (!div) return\n        div.html(\"\")\n        var info = \"\"\n        ///\n        if (rec.isVisible) {\n            rec.isVisible = false\n        } else {\n            info += this._showScreens(rec, index, forNew)\n            rec.isVisible = true\n        }\n        div.html(info)\n    }\n    /////////////////////////////////////////////////\n\n    _restoreNewImages() {\n        story.pages.forEach(function (page) {\n            if (page.srcImageObjSrc) page.imageObj.attr(\"src\", page.srcImageObjSrc)\n        })\n\n    }\n\n\n    _showScreens(rec, recIndex, showNew) {\n        var info = \"\";\n        for (const [screenIndex, screen] of rec['screens_changed'].entries()) {\n            if (screen['is_new'] != showNew) continue;\n            const pageIndex = viewer.getPageIndex(screen['screen_name'], -1)\n            const page = pageIndex >= 0 ? story.pages[pageIndex] : undefined\n\n            // We don't need to show external artboards here\n            if (page && (\"external\" == page.type)) continue\n\n            var pageName = page ? page.title : screen['screen_name'];\n\n            if (page && screen['is_diff']) {\n                this.screenDiffs[screen['screen_name']] = screen\n            }\n\n            info += \"<div class='version-screen-div' onclick='viewer.infoViewer.goToScreen(\" + recIndex + \",\" + screenIndex + \",\" + pageIndex + \")'>\";\n            info += \"<div>\";\n            info += pageName;\n            info += \"</div><div>\";\n            if (showNew)\n                info += \"<img src='\" + screen['image_url'] + \"' border='0' width='360px'/>\";\n            else\n                info += \"<img src='\" + rec['journals_path'] + '/' + rec['dir'] + \"/diffs/\" + screen['screen_name'] + \".\" + story.fileType + \"' border='0' width='360px'/>\";\n            info += \"</div>\";\n            info += \"</div>\";\n        }\n        return info;\n    }\n\n\n    _showCurrentPageDiffs() {\n        const data = this.currentRec\n        const page = viewer.currentPage\n        if (!page || !data) return false\n\n        const screen = this.screenDiffs[page.getHash()]\n        if (!screen) return false\n\n        this.mode = $(\"#info_viewer_mode_diff\").prop('checked') ? 'diff' : ($(\"#info_viewer_mode_prev\").prop('checked') ? 'prev' : 'new')\n        var newSrc = ''\n\n        // save original image srcs\n        if (!page.srcImageObjSrc) page.srcImageObjSrc = page.imageObj.attr(\"src\")\n\n        if ('diff' == this.mode) {\n            newSrc = data['journals_path'] + '/' + data['dir'] + \"/diffs/\" + screen['screen_name'] + \".\" + story.fileType\n        } else if ('new' == this.mode) {\n            if (page.imageObj.attr(\"src\") != page.srcImageObjSrc) {\n                newSrc = page.srcImageObjSrc\n            }\n        } else {\n            newSrc = \"../\" + data['down_ver'] + \"/\" + page.srcImageObjSrc\n        }\n\n\n        page.imageObj.attr(\"src\", newSrc)\n        return true\n    }\n\n    _showStatic() {\n        var info = \"\"\n\n        if (story.ownerEmail != '') {\n            info += `<div class=\"head\" style=\"font-weight:bold;\"><div class=\"tooltip\">Owner: ${story.ownerName}<span class=\"tooltiptext\">${story.ownerEmail}</span></div></div>`\n        } else {\n            info += \"Unknown\"\n        }\n        info += `<div id = \"info_viewer_content_dynamic\"/>`\n\n        $(\"#info_viewer_content\").html(info)\n    }\n\n    _loadData(data) {\n        var info = \"\"\n\n        info += `<div id=\"title\" style=\"font-weight:bold;\">Changes</div>`\n\n        data['recs'].forEach(function (rec, index) {\n            var authorHTML = undefined != rec['email'] ? `<div class=\"tooltip\">by ${rec['author']}<span class=\"tooltiptext\">${rec['email']}</span></div>` : rec['author']\n            info += `\n            <div class=\"record\">\n                <div class=\"ver\"><a href=\"#\" onclick=\"viewer.infoViewer.goToVersion(${index})\">#${rec['ver']}</a> ${new Date(rec['time'] * 1000).toLocaleDateString()} ${authorHTML}</div>\n                <div class=\"message\">${rec['message'].replaceAll('--NOTELE', '')}</div>\n                <div class=\"info\">\n            `\n            if (rec['screens_total_new']) {\n                info += `Added: <a href=\"#\" onclick=\"viewer.infoViewer.showRecDetails(${index},true)\">${rec['screens_total_new']} screen(s)</a>`\n                info += `<div class=\"nscreens\" id=\"${index}\"/>`\n            }\n            if (rec['screens_total_changed']) {\n                info += `Updated: <a href=\"#\" onclick=\"viewer.infoViewer.showRecDetails(${index},false)\">${rec['screens_total_changed']} screen(s)</a>`\n                info += `<div class=\"uscreens\" id=\"${index}\"/>`\n            }\n            if (!rec['screens_total_new'] && !rec['screens_total_changed']) {\n                info += \"No visual changes\"\n            }\n            info += `</div></div>`\n        }, this)\n\n        this.data = data\n        $(\"#info_viewer_content_dynamic\").html(info)\n    }\n\n    _askServerTools() {\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"GET\", story.serverToolsPath + \"version_info.php?ver=\" + story.docVersion, true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onreadystatechange = getVersionInfoRequest;\n        xhr.send(null);\n    }\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#info_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n    }\n\n    _showLoadingMessage() {\n        $(\"#info_viewer_content_dynamic\").html(\"Loading...\")\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/PresenterViewer.js",
    "content": "let presenterViewer = null;\nconst STORAGE_TRANSPERIOD = \"presenterViewer.transPeriod\"\n\nfunction doPresenterViewerNext() {\n    const nextPage = viewer.getNextUserPage()\n    // check if we need to stop\n    if ((!nextPage || nextPage.userIndex === 0 && false)) {\n        presenterViewer.stop()\n        return\n    }\n    presenterViewer.gotoPageWithDelay(nextPage.index)\n}\n\nclass PresenterViewer extends AbstractViewer {\n    constructor() {\n        super()\n\n        this.isSidebarChild = false\n        this.blockMainNavigation = true\n\n        presenterViewer = this\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n        //\n        {\n            const savedPeriod = window.localStorage.getItem(STORAGE_TRANSPERIOD)\n            this.transPeriod = savedPeriod != null ? savedPeriod : 3000 // msec                \n        }\n        //\n        this.inited = true\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        super._hideSelf()\n        // Hide all UI controls\n        viewer.toogleUI(true)\n    }\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        //\n        super._showSelf()\n        // Start a presentation from first page    \n        const startIndex = viewer.getFirstUserPage()\n        if (startIndex === null) {\n            this._hideSelf()\n            return\n        }\n        //\n        viewer.toogleUI(false)\n        this.gotoPageWithDelay(startIndex)\n    }\n\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (80 == event.which && !this.visible) { // p\n            // Key \"P\" activates self\n            this.play()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which || 80 == event.which) { // esc or p\n            this.stop()\n        } else if (event.which >= 49 && event.which < 57) { // 1..9\n            this.transPeriod = 1000 * (event.which - 48)\n            window.localStorage.setItem(STORAGE_TRANSPERIOD, this.transPeriod)\n        } else {\n            //return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    ///////////////////////// OWN METHODS\n    play() {\n        viewer._enableFullScreen()\n        this.show()\n    }\n\n    gotoPageWithDelay(pageIndex) {\n        // Probalbly we have stopped already\n        if (!this.visible) return\n        // Show page\n        viewer.goTo(pageIndex, false)\n        // Go to the next page with delay\n        setTimeout(doPresenterViewerNext, this.transPeriod)\n    }\n\n    stop(exitFullScreen = true) {\n        if (!this.visible) return\n        // Disable full screen\n        if (exitFullScreen) {\n            viewer._disableFullScreen()\n        }\n        //        \n        this.hide()\n    }\n\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/SymbolViewer.js",
    "content": "const ELEMENTINSPECTOR_LINUX_FONT_SIZES = {\n    \"8px\": \"6px\",\n    \"10px\": \"7px\",\n    \"11px\": \"8px\",\n    \"12px\": \"9px\",\n    \"13px\": \"10px\",\n    \"15px\": \"11px\",\n    \"16px\": \"12px\",\n    \"17px\": \"13px\",\n    \"19px\": \"14px\",\n    \"20px\": \"15px\",\n    \"21px\": \"16px\",\n    \"23px\": \"17px\",\n    \"24px\": \"18px\",\n    \"25px\": \"19px\",\n    \"26px\": \"20px\"\n}\nconst ICON_TAG = \" / ic-\" // Use this string to find icon symbol\nconst ICON_TAG2 = \"ic-\" // Use this string to find icon symbol\nconst SUPPORT_TYPES = [\"Text\", \"ShapePath\", \"Image\", \"ImageSymbol\"]\n\nclass SymbolViewer extends AbstractViewer {\n    constructor() {\n        super(\"symbol_viewer\")\n        //\n        this.preventCustomTextSearch = true\n        //\n        this.createdPages = {}\n        //this.symbolIDs = {} // layer indexes ( in pages[].layers ) of symbols\n        this.currentLib = \"\"\n        this.selected = null\n        this.showSymbols = false\n        this.insideExpViewer = false\n        this.highlightWidgetName = null\n    }\n\n    initialize (force = false) {\n        if (!super.initialize(force)) return\n\n        // populate library select\n        const libSelect = $('#symbol_viewer #lib_selector')\n        libSelect.append($('<option>', {\n            value: \"\",\n            text: 'Library autoselection'\n        }));\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            libSelect.append($('<option>', {\n                value: libName,\n                text: libName\n            }));\n        }\n        libSelect.change(function () {\n            var libName = $(this).children(\"option:selected\").val();\n            viewer.symbolViewer._selectLib(libName)\n\n        })\n        //\n        const symCheck = $('#symbol_viewer_symbols')\n        symCheck.click(function () {\n            viewer.symbolViewer._setSymCheck($(this).is(':checked'))\n\n        })\n    }\n\n    _setSymCheck (showSymbols) {\n        this.showSymbols = showSymbols\n        $('#lib_selector').toggle()\n        this._reShowContent()\n\n    }\n\n    // called by Viewer\n    pageChanged () {\n        this._reShowContent()\n    }\n\n    _selectLib (libName) {\n        this.currentLib = libName\n        this._reShowContent()\n    }\n\n    _reShowContent () {\n        delete this.createdPages[viewer.currentPage.index]\n\n        // remove existing symbol links        \n        this.page.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        for (const panel of this.page.fixedPanels) {\n            panel.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        }\n\n        // drop selection\n        this.setSelected()\n\n        // rebuild links\n        this._buildElementLinks()\n\n        // redraw inspector\n        this._showEmptyContent()\n\n    }\n\n\n    toggle () {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    hide () {\n        super.hide()\n        if (this.insideExpViewer) {\n            this.insideExpViewer = false\n            viewer.expViewer.show()\n        }\n        this.highlightWidgetName = null\n    }\n\n    showFromExpViewer (highlightWidgetName = null) {\n        this.insideExpViewer = true\n        this.highlightWidgetName = highlightWidgetName\n        this.show()\n    }\n\n    _hideSelf () {\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        if (isModal) {\n            $(\".modalSymbolLink\").remove()\n            delete this.createdPages[viewer.currentPage.index]\n        }\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.removeClass(\"contentSymbolsVisible\")\n\n        viewer.linksDisabled = false\n        $('#symbol_viewer').addClass(\"hidden\")\n\n        this.setSelected(null, null, null)\n\n        super._hideSelf()\n    }\n\n    onContentClick () {\n        this.setSelected(null)\n        return true\n    }\n\n    handleKeyDown (jevent) {\n\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" eactivates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    handleKeyDownWhileInactive (jevent) {\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    _showSelf () {\n        if (!this.inited) this.initialize()\n\n        viewer.toggleLinks(false)\n        viewer.toogleLayout(false)\n        viewer.linksDisabled = true\n\n        this._buildElementLinks()\n\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.addClass(\"contentSymbolsVisible\")\n\n        this._showEmptyContent()\n\n        $('#symbol_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n\n    }\n\n    _showEmptyContent () {\n        $(\"#symbol_viewer_content\").html(\"\")\n        $('#symbol_viewer #empty').html(story.experimentalExisting ?\n            \"Click any element to inspect.<br/>EXPERIMENTAL widgets are in <span style='color:orange'>orange</span>.\" :\n            \"Click any element to inspect\"\n        );\n        $('#symbol_viewer #empty').removeClass(\"hidden\")\n    }\n\n\n    _buildElementLinks () {\n        this._buildElementLinksForPage(viewer.currentPage)\n        for (let overlay of viewer.currentPage.currentOverlays) {\n            this._buildElementLinksForPage(overlay)\n        }\n    }\n\n\n    _buildElementLinksForPage (page) {\n        var pageIndex = page.index\n        this.pageIndex = pageIndex\n        this.page = page\n        if (!(pageIndex in this.createdPages)) {\n            const newPageInfo = {\n                layerArray: [],\n                siLayerIndexes: {}\n            }\n            // cache only standalone pages\n            this.createdPages[pageIndex] = newPageInfo\n\n            this.pageInfo = newPageInfo\n        } else {\n            this.pageInfo = this.createdPages[pageIndex]\n        }\n        //\n        const layers = layersData[this.pageIndex].c\n        if (undefined != layers) {\n            if (this.showSymbols)\n                this._processSymbolList(layers)\n            else\n                this._processLayerList(layers)\n        }\n    }\n\n    _processSymbolList (layers, isParentSymbol = false) {\n        for (var l of layers.slice().reverse()) {\n            // l.b: library name\n            if (\n                l.s &&\n                (\"\" == this.currentLib || (this.currentLib != \"\" && l.b && l.b == this.currentLib))\n            ) {\n                this._showElement(l)\n            }/* else\n                // l.s: symbol name\n                // l.l: style name\n                if (l.s != undefined || (!isParentSymbol && l.l != undefined)) {\n                    this._showElement(l)\n                }\n            */\n            // l.c : childs\n            if (undefined != l.c)\n                this._processSymbolList(l.c, l.s != undefined)\n        }\n    }\n\n    _processLayerList (layers, sSI = null) {\n        for (var l of layers.slice().reverse()) {\n            const isIcon = l.s && l.s.indexOf(ICON_TAG) > 0;\n            if (isIcon || (SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd)) {\n                this._showElement(l, sSI)\n            }\n            // don't go deep inside an icon\n            if (isIcon) continue\n            // process childs\n            if (undefined != l.c)\n                this._processLayerList(l.c, \"SI\" == l.tp ? l : sSI)\n        }\n    }\n\n    _showElement (l, siLayer = null) {\n        if (l.hd) return\n\n        var currentPanel = this.page\n        l.finalX = l.x\n        l.finalY = l.y\n\n        for (const panel of this.page.fixedPanels) {\n            if (l.x >= panel.x && l.y >= panel.y &&\n                ((l.x + l.w) <= (panel.x + panel.width)) && ((l.y + l.h) <= (panel.y + panel.height))\n            ) {\n                l.finalX = l.x - panel.x\n                l.finalY = l.y - panel.y\n                currentPanel = panel\n                break\n            }\n        }\n        l.parentPanel = currentPanel\n\n\n        // Check if some layer on top of current\n        for (const pl of this.pageInfo.layerArray.filter(s => s.tp != \"SI\")) {\n            if (pl.finalX <= l.finalX && pl.finalY <= l.finalY && (pl.finalX + pl.w) >= (l.finalX + l.w) && (pl.finalY + pl.h) >= (l.finalY + l.h)) return\n        }\n\n        // Check if layer is empty\n        if (\"Text\" == l.tp) {\n            if (\"\" == l.tx.trim()) return\n        }\n\n        // also push symbol instance to a list of layers (if was not aded before)        \n        let indexOfSO = -1\n        if (siLayer) {\n            if (siLayer.s in this.pageInfo.siLayerIndexes) {\n                indexOfSO = this.pageInfo.siLayerIndexes[siLayer.s]\n            } else {\n                indexOfSO = this.pageInfo.layerArray.length\n                this.pageInfo.layerArray.push(siLayer)\n            }\n        }\n        //\n\n        l.infoIndex = this.pageInfo.layerArray.length\n        this.pageInfo.layerArray.push(l)\n\n        var a = $(\"<a>\", {\n            class: viewer.currentPage.isModal ? \"modalSymbolLink\" : \"symbolLink\",\n            pi: this.pageIndex,\n            li: l.infoIndex,\n            si: indexOfSO\n        })\n\n        a.click(function (event) {\n            const sv = viewer.symbolViewer\n            const pageIndex = $(this).attr(\"pi\")\n            const layerIndex = $(this).attr(\"li\")\n            const siLayerIndex = $(this).attr(\"si\")\n            const pageInfo = sv.createdPages[pageIndex]\n            let topLayer = pageInfo.layerArray[layerIndex]\n            const siLayer = siLayerIndex >= 0 ? pageInfo.layerArray[siLayerIndex] : null\n\n            sv.setSelected(event, topLayer, $(this))\n            if (!sv.selected) {\n                return false\n            }\n            const layer = sv.selected.layer // selection can be changed inside setSelected\n\n            var symName = layer.s ? layer.s : (siLayer ? siLayer.s : null)\n            //sv.showSymbols && layer.s ? layer.s : (siLayer ? siLayer.s : null)\n            var styleName = layer.l\n\n            const styleInfo = styleName != undefined ? viewer.symbolViewer._findStyleAndLibByStyleName(styleName) : undefined\n            const symInfo = symName != undefined ? viewer.symbolViewer._findSymbolAndLibBySymbolName(symName) : undefined\n\n            sv.docLinkAdded = false\n            var info = \"\"\n            // layer.b : shared library name, owner of style or symbol\n            // layer.s : symbol name\n            // layer.l : style name\n            // layer.tp : layer type: SI, Text, ShapePath or Image\n            // siLayer : symbol master, owner of the layer            \n\n            info += sv._showLayerDimensions(layer)\n            info += sv._showLayerSymbol(layer, symName, siLayer)\n            info += sv._showLayerComment(layer,siLayer)\n            info += sv._showLayerStyle(layer, siLayer)\n\n            // if layer has CSS classes described\n            if (layer.pr != undefined) {\n                let tokens = null\n                if (styleInfo)\n                    tokens = styleInfo.style.tokens\n                if (symInfo) {\n                    const foundLayer = symInfo.symbol.layers[layer.n]\n                    if (foundLayer) {\n                        if (null == tokens)\n                            tokens = foundLayer.tokens\n                        else\n                            tokens = sv._mergeTokens(tokens, foundLayer.tokens)\n                    }\n                }\n                const decRes = sv._decorateCSS(layer, tokens, layer.b ? layer : siLayer)\n                info += decRes.css\n\n                if (\"Text\" == layer.tp) {\n                    info += sv._showLayerTextContent(layer, decRes)\n                }\n            } else {\n                if (\"Text\" == layer.tp) {\n                    info += sv._showLayerTextContent(layer, null)\n                }\n            }\n\n            // Process image layar\n            if (\"Image\" == layer.tp) {\n                info += sv._showLayerImage(layer)\n            }\n\n            $('#symbol_viewer #empty').addClass(\"hidden\")\n            $(\"#symbol_viewer_content\").html(info)\n            $(\"#symbol_viewer_content\").removeClass(\"hidden\")\n\n            //alert(info)\n            return false\n        })\n\n        a.prependTo(currentPanel.linksDiv)\n\n        var style = \"left: \" + l.finalX + \"px; top:\" + l.finalY + \"px; \"\n        style += \"width: \" + l.w + \"px; height:\" + l.h + \"px; \"\n        const highlight = siLayer && siLayer.s && (\n            (this.highlightWidgetName === null && siLayer.s.includes(\"EXPERIMENTAL\")) ||\n            (this.highlightWidgetName !== null && siLayer.s.includes(this.highlightWidgetName))\n        )\n        var symbolDiv = $(\"<div>\", {\n            class: \"symbolDiv\" + (highlight ? \" exp\" : \"\"),\n        }).attr('style', style)\n        symbolDiv.mouseenter(function () {\n            viewer.symbolViewer.mouseEnterLayerDiv($(this))\n        })\n\n        symbolDiv.appendTo(a)\n    }\n\n    _mergeTokens (list1, list2) {\n        let adding = []\n        list2.forEach(function (t2) {\n            const res1 = list1.filter(t1 => t1[0] == t2[0])\n            if (!res1.length) adding.push(t2)\n        })\n        if (adding.length)\n            return list1.concat(adding)\n        else\n            return list1\n    }\n\n    // Show Text layer content with Copy button\n    _showLayerTextContent (layer, decRes) {\n        if (layer.tx == undefined || layer.tx == \"\") return \"\"\n        let info = \"\"\n\n        info += `\n                <hr>\n                <div class='block'>\n                <div class='label'>Text Content&nbsp;<button onclick = \"copyToBuffer('sv_content')\">Copy</button>`\n        let afterContent = \"\"\n        let cssClass = \"\"\n        if (decRes && decRes.styles[\"font-family\"].startsWith(\"Font Awesome 5\")) {\n            cssClass += \"icon \"\n            if (decRes.styles[\"font-weight\"] != \"400\") cssClass += \"solid \"\n            const codeText = layer.tx.codePointAt(0).toString(16)\n            afterContent = \"Unicode: \" + codeText\n            info += `<button onclick = \"showFAIconInfo('` + codeText + `')\">Info</button>`\n        } else {\n            cssClass += \"code value\"\n        }\n        info += `</div ><div id='sv_content' class=\"` + cssClass + `\">` + layer.tx + \"</div>\"\n        if (afterContent != \"\") {\n            info += \"<div  class='code value'>\" + afterContent + \"</div>\"\n        }\n        info += \"</div>\"\n\n        return info\n    }\n\n    _showLayerSymbol (layer, symName, siLayer) {\n        if (undefined == symName) return \"\"\n        let categoryName = viewer.figma ? \"Figma component\" : \"Sketch Symbol\"\n        // Drop path to icon, leave only name\n        const iconTagPos = symName.indexOf(ICON_TAG)\n        this.currLayerIsIcon = iconTagPos >= 0\n        if (this.currLayerIsIcon) {\n            symName = symName.substring(iconTagPos).replace(ICON_TAG, ICON_TAG2) + \"-\" + symName.substring(0, 2)\n            categoryName = \"Icon\"\n        }\n        //\n        let info = \"<hr>\" +\n            \"<div class='block'>\" +\n            \"<div class='label'>\" + categoryName + \"</div>\" +\n            \"<div class='value'>\" + symName + \"</div>\"\n        const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n            (siLayer && siLayer.b ? siLayer.b + \" (external)\" : \"Document\")\n        info += \"<div style='font-size:12px; color:var(--color-secondary)'>\" + libName + \"</div></div>\"\n        return this._showExtDocRef(layer, symName, siLayer) + info\n    }\n\n    _showExtDocRef (layer, symName, siLayer) {\n        const emptyRes = \"\"\n        if (this.docLinkAdded) return emptyRes\n        if (undefined == layer.b && (undefined == siLayer || undefined == siLayer.b)) return emptyRes\n        //\n        let href = undefined\n        let name = \"\"\n        let parts = symName.split(\"/\")\n\n        const libName = layer.b ? layer.b : siLayer.b\n        //  check if library has a dictionary file\n        if (!(libName in SYMBOLS_DICT)) return emptyRes\n\n        const attrs = SYMBOLS_DICT[libName].attrs\n        // check if dictionary file has attrs defined\n        if (undefined == attrs) return emptyRes\n\n        while (parts.length) {\n            name = parts.join(\"/\")\n            if (name in attrs) {\n                href = attrs[name][\"ext-doc-href\"]\n                if (undefined != href) {\n                    break\n                }\n            }\n            parts.pop()\n        }\n        if (!href) return emptyRes\n        //        \n        name = name.replace(\"_atoms/\", \"\")\n        if (href.toLowerCase().includes(\"experimental\") && !name.toLowerCase().includes(\"experimental\")) name += \"-EXPERIMENTAL\"\n        this.docLinkAdded = true\n        return `\n                <hr>\n                <div class=\"block\">\n                    <div class=\"label\">Documentation</div>\n                    <div style=\"value\"><a href=\"${href}\" target=\"_blank\">${name}</a></div>\n                </div>`\n    }\n\n    _showLayerComment (layer,siLayer) {\n        var comment = layer.comment\n        if (comment===undefined && siLayer!=undefined) comment = siLayer.comment\n        if (comment===undefined) return \"\"\n\n        return `\n                <hr>\n                <div class=\"block\">\n                    <div class=\"label\">Comment</div>\n                    <div style=\"value\">${comment}</div>\n                </div>`\n    }\n\n    _showLayerImage (layer) {\n        let info = \"\"\n        const url = layer.iu\n        info += `\n                <hr>\n                <div class='block'>\n                <div class='label'>Image Content&nbsp;<a class=\"svlink\" href=\"`+ url + `\">Download</a>`\n        let cssClass = \"code value\"\n        const width = \"100%\" //viewer.defSidebarWidth - 40\n        info += `</div ><div id='sv_content' class=\"` + cssClass + `\"><img ` + `width=\"` + width + `\" src=\"` + url + `\"/></div>`\n        return info\n    }\n\n    // siLayer: parent symbol \n    _showLayerStyle (layer, siLayer) {\n        if (undefined == layer.l) return \"\"\n\n        let info = \"\"\n        let styleName = layer.l\n        const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n            (siLayer ? siLayer.b + \" (external)\" : \"Document\")\n\n        info = `<hr>\n                <div class='block'>\n                    <div class='label'>${viewer.figma ? \"Figma Style\" : \"Sketch Style\"}</div>\n                    <div class='value'>${styleName}</div>\n                    <div style='font-size:12px; color:var(--color-secondary)'>${libName}</div>\n                </div>`\n\n        return this._showExtDocRef(layer, styleName, siLayer) + info\n    }\n\n    _showLayerDimensions (layer) {\n        let info = \"\"\n\n        var frameX = layer.finalX\n        var frameY = layer.finalY\n        var frameWidth = layer.w\n        var frameHeight = layer.h\n\n        info += \"<hr>\" +\n            \"<div class='block twoColumn'>\" +\n            \"<div>\" +\n            \"<span class='label'>\" + \"X: </span>\" + Math.round(frameX) + \"px\" +\n            \"</div>\" +\n            \"<div>\" +\n            \"<span class='label'>\" + \"Y: </span>\" + Math.round(frameY) + \"px\" +\n            \"</div>\" +\n            \"</div>\"\n\n        info += \"<div class='block twoColumn'>\" +\n            \"<div>\" +\n            \"<span class='label'>\" + \"Width: </span>\" + Math.round(frameWidth) + \"px\" +\n            \"</div>\" +\n            \"<div>\" +\n            \"<span class='label'>\" + \"Height: </span>\" + Math.round(frameHeight) + \"px\" +\n            \"</div>\" +\n            \"</div>\"\n        return info\n    }\n\n    setSelected (event = null, layer = null, a = null, force = false) {\n        const prevClickedLayer = this.lastClickedLayer\n        this.lastClickedLayer = layer\n        //\n        const click = event ? {\n            x: event.offsetX * viewer.currentZoom + layer.finalX,\n            y: event.offsetY * viewer.currentZoom + layer.finalY\n        } : {}\n        let foundLayers = []\n        this.findOtherSelection(click, null, foundLayers)\n        // reset previous selection                \n        if (this.selected) {\n            if (!force && event && layer) {\n                if (foundLayers.length > 1) {\n                    let newIndex = undefined\n                    if (undefined != prevClickedLayer && layer.ii != prevClickedLayer.ii) {\n                        // clicked on an other layer, find its index\n                        newIndex = foundLayers.indexOf(layer)\n                    } else if (undefined != this.selectedLayerIndex) {\n                        // clicked on the some layer, but \n                        // we have several overlaped objects under a cursor, so switch to the next \n                        newIndex = (this.selectedLayerIndex + 1) >= foundLayers.length ? 0 : this.selectedLayerIndex + 1\n                    } else {\n                        newIndex = foundLayers.indexOf(layer)\n                    }\n                    layer = foundLayers[newIndex]\n                    this.selectedLayerIndex = newIndex\n                }\n            }\n            this.selected.marginDivs.forEach(d => d.remove())\n            this.selected.borderDivs.forEach(d => d.remove())\n        } else {\n            this.selectedLayerIndex = foundLayers.indexOf(layer)\n        }\n\n        if (!layer) {\n            this.selected = null\n            this.lastClickedLayer = undefined\n            this.selectedLayerIndex = undefined\n            ////\n            $('#symbol_viewer #empty').removeClass(\"hidden\")\n            $(\"#symbol_viewer_content\").addClass(\"hidden\")\n            ////\n            return\n        }\n        // select new\n        this.selected = {\n            layer: layer,\n            a: $(this),\n            marginDivs: [],\n            borderDivs: [],\n        }\n        // draw left vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw right vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX + layer.w, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw top horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n        // draw bottom horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY + layer.h, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n    }\n\n    findOtherSelection (click, layers, foundLayers) {\n        if (null == layers) layers = layersData[this.pageIndex].c\n\n        if (undefined == layers) return\n        for (var l of layers.slice().reverse()) {\n            if ((!this.showSymbols || l.s != undefined) &&\n                SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd) {\n                if (click.x >= l.finalX && click.x <= (l.finalX + l.w) && click.y >= l.finalY && click.y <= (l.finalY + l.h)) {\n                    foundLayers.push(l)\n                }\n            }\n            if (undefined != l.c)\n                this.findOtherSelection(click, l.c, foundLayers)\n        }\n    }\n\n\n    mouseEnterLayerDiv (div) {\n        // get a layer under mouse \n        const a = div.parent()\n        const sv = viewer.symbolViewer\n        const pageIndex = a.attr(\"pi\")\n        const layerIndex = a.attr(\"li\")\n        const layer = sv.createdPages[pageIndex].layerArray[layerIndex]\n        if (!layer) return\n        // get a currently selected layer\n        if (!sv.selected) return\n        const slayer = sv.selected.layer\n        //\n        if (!slayer || !layer) return\n        // check if layers are in the same panel\n        if (slayer.parentPanel != layer.parentPanel) return\n        // remove previous margins\n        this.selected.marginDivs.forEach(d => d.remove())\n        this.selected.marginDivs = []\n        // show margins\n        this._drawTopVMargin(slayer.parentPanel, layer, slayer)\n        this._drawBottomVMargin(slayer.parentPanel, layer, slayer)\n        this._drawLeftHMargin(slayer.parentPanel, layer, slayer)\n        this._drawRightHMargin(slayer.parentPanel, layer, slayer)\n    }\n\n    _drawLeftHMargin (currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n        if (layer.finalX == slayer.finalX) {\n        } else if ((slayer.finalX + slayer.w) < layer.finalX) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalX + layer.w) < slayer.finalX) {\n            // layer bottom over slayer.top\n            x = layer.finalX + layer.w\n            hmargin = slayer.finalX - x\n        } else if (layer.finalX < slayer.finalX) {\n            // layer top over slayer.top\n            x = layer.finalX\n            hmargin = slayer.finalX - x\n        } else {\n            // layer top over slayer.top\n            x = slayer.finalX\n            hmargin = layer.finalX - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawRightHMargin (currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n\n        const layerRight = layer.finalX + layer.w\n        const slayerRight = slayer.finalX + slayer.w\n\n        if (layerRight == slayerRight) {\n        } else if (layerRight < slayer.finalX) {\n            // if layer bottom over slayer bottom => don't show bottom margin                \n        } else if (slayerRight < layer.finalX) {\n            // slayer bottom over layer.top\n            x = slayerRight\n            hmargin = layer.finalX - x\n        } else if (slayerRight < layerRight) {\n            // slayer bottom over layer.bottom\n            x = slayerRight\n            hmargin = layerRight - x\n        } else {\n            // slayer bottom over layer.bottom\n            x = layerRight\n            hmargin = slayerRight - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawTopVMargin (currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n        if (layer.finalY == slayer.finalY) {\n        } else if ((slayer.finalY + slayer.h) < layer.finalY) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalY + layer.h) < slayer.finalY) {\n            // layer bottom over slayer.top\n            y = layer.finalY + layer.h\n            vmargin = slayer.finalY - y\n        } else if (layer.finalY < slayer.finalY) {\n            // layer top over slayer.top\n            y = layer.finalY\n            vmargin = slayer.finalY - y\n        } else {\n            // layer top over slayer.top\n            y = slayer.finalY\n            vmargin = layer.finalY - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n    _drawBottomVMargin (currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n\n        const layerBottom = layer.finalY + layer.h\n        const slayerBottom = slayer.finalY + slayer.h\n\n        if (layerBottom == slayerBottom) {\n        } else if (layerBottom < slayer.finalY) {\n            // if layer bottom over slayer bottom => don't show bottom margin        \n        } else if (slayerBottom < layer.finalY) {\n            // slayer bottom over layer.top\n            y = slayerBottom\n            vmargin = layer.finalY - y\n        } else if (slayerBottom < layerBottom) {\n            // slayer bottom over layer.bottom\n            y = slayerBottom\n            vmargin = layerBottom - y\n        } else {\n            // slayer bottom over layer.bottom\n            y = layerBottom\n            vmargin = slayerBottom - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _findLayersCenterX (l, sl) {\n        let c = l.finalX + l.w / 2\n        let sc = sl.finalX + sl.w / 2\n        return sl.finalX > l.finalX && ((sl.finalX + sl.w) < (l.finalX + l.w)) ? sc : c\n    }\n\n    _findLayersCenterY (l, sl) {\n        let c = l.finalY + l.h / 2\n        let sc = sl.finalY + sl.h / 2\n        return sl.finalY > l.finalY && ((sl.finalY + sl.h) < (l.finalY + l.h)) ? sc : c\n    }\n\n    _drawMarginLine (currentPanel, x, y, width, height, className) {\n        var style = \"left: \" + x + \"px; top:\" + y + \"px; \"\n        style += \"width: \" + width + \"px; height:\" + height + \"px; \"\n        var div = $(\"<div>\", { class: className }).attr('style', style)\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n    _drawMarginValue (currentPanel, x, y, value) {\n        const valueHeight = 20\n        const valueWidth = 30\n        var style = \"left: \" + (x - valueWidth / 2) + \"px; top:\" + (y - valueHeight / 2) + \"px; \"\n        //style += \"width: \" + valueWidth + \"px; height:\" + valueHeight + \"px; \"\n        var div = $(\"<div>\", {\n            class: \"svMarginValueDiv\",\n        }).attr('style', style)\n        //\n        div.html(\" \" + Number.parseFloat(value).toFixed(0) + \" \")\n        //\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n\n    _decorateCSS (layer, tokens, siLayer) {\n        let css = layer.pr\n        let result = \"\"\n        let styles = {}\n\n        result += \"<hr>\" +\n            \"<div class='block'>\" +\n            \"<div class='label'>CSS Styles\" +\n            (1 == story.fontSizeFormat ? \" (font size adjusted for Linux)\" : \"\") +\n            \"</div > \" +\n            \"<div class='value code'>\"\n\n        // Decorate styles already described in CSS \n        css.split(\"\\n\").forEach(line => {\n            if (\"\" == line) return\n            const props = line.split(\": \", 2)\n            if (!props.length) return\n            const styleName = props[0]\n            const styleValue = props[1].slice(0, -1)\n\n            result += this._decorateCSSOneStyle(tokens, layer, siLayer, styleName, styleValue)\n            styles[styleName] = styleValue\n\n        }, this);\n        // Add missed CSS styles based on tokens\n        result += this._decorateCSSLostTokens(tokens, styles, layer, siLayer)\n        // Decorate non-CSS common styles\n        result += this._decorateCSSOtherTokens(tokens, layer, siLayer)\n\n\n        result += \"</div></div>\"\n        return { \"css\": result, \"styles\": styles }\n    }\n\n\n    _decorateCSSOneStyle (tokens, layer, siLayer, styleName, styleValue) {\n        let result = \"\"\n        // Decorate style name\n        let styleNameTxt = styleName\n        if(this.currLayerIsIcon && styleName===\"background-color\")  styleNameTxt = \"color\"\n        result += \"\" + styleNameTxt + \": \"\n        result += \"<span class='tokenName'>\"\n        //\n        let cvTokens = null\n        if (layer.cv && \"color\" == styleName) {\n            // get token for color variable\n            cvTokens = this._findSwatchTokens(layer.cv)\n            if (cvTokens) {\n                const tokenStr = this._decorateSwatchToken(cvTokens, styleValue)\n                result += tokenStr != \"\" ? tokenStr : (styleValue + \";\")\n            }\n        }\n        if (null == cvTokens) {\n            const tokenStr = tokens != null ? this._decorateStyleToken(styleName, tokens, siLayer, styleValue) : \"\"\n            if (tokenStr === undefined) return \"\"\n            result += tokenStr != \"\" ? tokenStr : (this._formatStyleValue(styleName, styleValue) + \";\")\n        }\n        //\n        result += \"</span>\"\n        result += \"<br/>\"\n        return result\n    }\n\n    _decorateCSSLostTokens (tokens, styles, layer, siLayer) {\n        if (null == tokens) return \"\"\n        let result = \"\"\n        const knownOtherStyles = [\"width2\", \"height2\"]\n        const reversed = tokens.slice().reverse()\n        const processed = {}\n        tokens.filter(token => !(token[0] in styles) && knownOtherStyles.indexOf(token[0]) < 0).forEach(token => {\n            if (token[0] != \"background-color\" && (token[0] in processed)) return\n            processed[token[0]] = true\n            result += this._decorateCSSOneStyle(tokens, layer, siLayer, token[0], token[1])\n        }, this)\n        return result\n    }\n\n    _decorateCSSOtherTokens (tokens, layer, siLayer) {\n        if (null == tokens) return \"\"\n        let result = \"\"\n        const knownOtherStyles = [\"width2\", \"height2\"]\n        tokens.filter(t => knownOtherStyles.indexOf(t[0]) >= 0 || t[0].startsWith(\"margin\") || t[0].startsWith(\"padding\")).forEach(function (token) {\n            result += this._decorateCSSOneStyle(tokens, layer, siLayer, token[0], token[1])\n        }, this)\n        return result\n    }\n\n    _decorateSwatchToken (tokens, styleValue) {\n        const tokenName = tokens[0][1]\n        //\n        return tokenName + \";</span><span class='tokenValue'>//\" + styleValue\n    }\n\n    _decorateStyleToken (style, tokens, siLayer, styleValue) {\n        // search tokan name by style name \n        const foundTokens = tokens.filter(t => t[0] == style)\n        if (!foundTokens.length) return \"\"\n        const tokenName = foundTokens[foundTokens.length - 1][1]\n        //\n        const libName = siLayer && undefined != siLayer.b ? siLayer.b : story.docName\n        const finalTokenInfo = this._findTokenValueByName(tokenName, libName, styleValue)\n        //\n        if (finalTokenInfo)\n            return finalTokenInfo[0] + \";</span><span class='tokenValue'>//\" + this._formatStyleValue(style, finalTokenInfo[1])\n        else if (foundTokens[0].length == 3)\n            return tokenName + \";</span><span class='tokenValue'>//\" + foundTokens[0][2]\n        else if (foundTokens[0].length == 2) {\n            if (foundTokens[0][1].includes(\"@\"))\n                return undefined\n            else\n                return tokenName + \";</span><span class='tokenValue'>//\" + foundTokens[0][1]\n        } else\n            return \"\"\n\n    }\n\n    _formatStyleValue (style = \"font-size\", styleValue = \"13px\") {\n        if (\"font-size\" == style && 1 == story.fontSizeFormat) {\n            if (styleValue in ELEMENTINSPECTOR_LINUX_FONT_SIZES) {\n                styleValue = ELEMENTINSPECTOR_LINUX_FONT_SIZES[styleValue]\n            } else {\n                styleValue = Math.round(Number(styleValue.replace(\"px\", \"\")) / 1.333) + \"px\"\n            }\n        }\n        return styleValue\n    }\n\n\n    _showTextPropery (propName, propValue, postfix = \"\") {\n        let text = propName + \": \" + propValue + postfix + \";\"\n        return text + \"<br/>\"\n    }\n\n    // result: undefined or [tokenName,tokenValue]\n    _findTokenValueByName (tokenName, libName, styleValue = null) {\n        const lib = TOKENS_DICT[libName]\n        if (undefined == lib) return undefined\n        let value = lib[tokenName]\n        if (value != undefined || null == styleValue) return [tokenName, lib[tokenName]]\n\n        ///// try to find a token with a similar name\n        // cut magic postfix to get a string for search\n        const pos = tokenName.indexOf(\"--token\")\n        if (pos < 0) return undefined\n        styleValue = styleValue.toLowerCase()\n        const newName = tokenName.slice(0, pos)\n        // filter lib tokens by name and value\n        const similarTokens = Object.keys(lib).filter(function (n) {\n            return n.startsWith(newName) && lib[n].toLowerCase() == styleValue\n        }, this)\n        if (!similarTokens.length) return undefined\n        //\n        return [\n            similarTokens[0],\n            lib[similarTokens[0]]\n        ]\n    }\n\n    _findSymbolAndLibBySymbolName (symName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(symName in lib)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                symbol: lib[symName]\n            }\n        }\n        return undefined\n    }\n\n    _findStyleAndLibByStyleName (styleName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(\"styles\" in lib) || !(styleName in lib.styles)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                style: lib.styles[styleName]\n            }\n        }\n        return undefined\n    }\n\n    // cv:{\n    //   sn: swatch name\n    //   ln:  lib name\n    // }\n    _findSwatchTokens (cv) {\n        const lib = SYMBOLS_DICT[cv.ln]\n        if (!lib) {\n            console.log(\"Can not find lib \" + cv.ln)\n            return null\n        }\n        //\n        const swatch = lib.colors__[cv.sn]\n        if (!swatch) {\n            console.log(\"Can not find color name \" + cv.sn)\n            return null\n        }\n\n        return swatch\n    }\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/Viewer.js",
    "content": "// =============================== PRELOAD IMAGES =========================\nvar pagerLoadingTotal = 0\n\nfunction getQuery(uri, q) {\n    return (uri.match(new RegExp('[?&]' + q + '=([^&]+)')) || [, null])[1];\n}\n\nfunction showError(error) {\n    alert(error)\n}\n\nfunction showMessage(message) {\n    alert(message)\n}\n\nfunction checkFolderInfoRequest(resp) {\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['project_url'] && '' != data['project_url']) return data\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return undefined\n}\n\nfunction handleDecreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    if ('' == data.link_down) return showMessage('This is the oldest version.')\n    window.open(data.link_down + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction handleIncreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    let link = data.link_up\n    if ('' == link) {\n        if (!window.confirm('This is the newest version. Go to live version?')) return\n        link = data.link_live\n    }\n    window.open(link + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction doTransNext() {\n    // get oldest transition\n    const trans = viewer.transQueue[0]\n    // if it still active then run it\n    if (trans.active) {\n        viewer.next()\n        console.log(\"RUN transition\")\n    } else {\n        console.log(\"skip transition\")\n    }\n\n    // remove this transtion from stack\n    viewer.transQueue.shift()\n}\n\n$.fn.preload = function (callback) {\n    var length = this.length;\n    var iterator = 0;\n\n    return this.each(function () {\n        var self = this;\n        var tmp = new Image();\n\n        if (callback) tmp.onload = function () {\n            callback.call(self, 100 * ++iterator / length, iterator === length);\n            pagerMarkImageAsLoaded()\n        };\n        tmp.src = this.src;\n    });\n};\n\nfunction pagerMarkImageAsLoaded() {\n    console.log(pagerLoadingTotal);\n    if (--pagerLoadingTotal == 0) {\n        $(\"#nav #loading\").addClass(\"hidden\")\n    }\n}\n\nasync function preloadAllPageImages() {\n    $(\"#nav #loading\").removeClass(\"hidden\")\n    pagerLoadingTotal = story.totalImages\n    var pages = story.pages;\n    for (var page of story.pages) {\n        if (page.imageObj == undefined) {\n            page.loadImages()\n            page.imageDiv.addClass(\"hidden\")\n        }\n    }\n}\n\nfunction reloadAllPageImages() {\n    for (var page of story.pages) {\n        page.imageObj.parent().remove();\n        page.imageObj = undefined\n        for (var p of page.fixedPanels) {\n            p.imageObj.parent().remove();\n            p.imageObj = undefined\n        }\n    }\n    preloadAllPageImages()\n}\n\nfunction doBlinkHotspots() {\n    viewer.toggleLinks()\n}\n\n\n// str: .transit .slideInDown\"\nfunction splitStylesStr(str) {\n    return str.split(\" \").map(s => s.replace(\".\", \"\"))\n}\n\n// ============================ VIEWER ====================================\n\nclass Viewer {\n    constructor(story, files) {\n        this.highlightLinks = story.highlightLinks\n        this.showLayout = false\n        this.showUI = true\n        this.isFullScreen = false\n        this.isEmbed = false\n        this.figma = false\n\n        this.searchText = \"\"\n\n        this.fullBaseURL = \"\"\n        this.fullCurrentPageURL = \"\"\n\n        this.prevPage = undefined\n        this.currentPage = undefined\n        this.lastRegularPage = undefined\n\n        this.currentMarginLeft = undefined\n        this.currentMarginTop = undefined\n\n        this.backStack = []\n        this.urlLastIndex = -1\n        this.urlLocked = false\n        this.stateChangeIgnore = false\n        this.files = files\n        this.userStoryPages = []\n        this.visStoryPages = []\n        this.zoomEnabled = story.zoomEnabled\n        this.menuVisible = false\n\n        this.sidebarVisible = false\n        this.child = null // some instance of Viewer\n        this.allChilds = [] // list of all inited instances of Viewer\n\n        this.symbolViewer = null\n        this.infoViewer = null\n        this.commentsViewer = null\n        this.presenterViewer = null\n        this.expViewer = null\n\n        this.defSidebarWidth = 400\n\n        this.transQueue = []\n    }\n\n    initialize() {\n        this.initParseGetParams()\n        this.buildUserStory();\n        this.initializeHighDensitySupport();\n        this.initAnimations()\n\n        /// Init UI            \n        $(\"#menu #zoom\").prop('checked', this.zoomEnabled);\n\n        /// Init Viewers\n        this.galleryViewer = new GalleryViewer()\n\n        if (story.layersExist) {\n            this.symbolViewer = new SymbolViewer()\n            if (story.experimentalExisting) this.expViewer = new ExpViewer()\n        }\n        this.infoViewer = new infoViewer()\n        this.presenterViewer = new PresenterViewer()\n\n        if (story.commentsURL != 'V_V_C' && story.commentsURL != \"\") {\n            this.commentsViewer = new CommentsViewer()\n            $(\"#nav #pageComments\").removeClass(\"hidden\")\n        }\n\n        if (story.experimentalExisting) {\n            $(\"#nav #experimental\").removeClass(\"hidden\")\n        }\n\n    }\n\n    initAnimations() {\n        if (story.layersExist) {\n            // TODO\n        }\n        // transform \".transit .slideInDown\" strings into class name arrays\n        TRANS_ANIMATIONS.forEach(function (t, index) {\n            if (0 == index) return\n            t.in_classes = splitStylesStr(t.in_str_classes)\n            t.out_classes = splitStylesStr(t.out_str_classes)\n        }, this)\n    }\n\n    initializeLast() {\n\n        $(\"body\").keydown(function (event) {\n            viewer.handleKeyDown(event)\n        })\n        window.addEventListener('mousemove', function (e) {\n            viewer.onMouseMove(e.pageX, e.pageY)\n        });\n        jQuery(window).resize(function () { viewer.zoomContent() });\n\n        // Activate galleryViewer\n        const gParam = this.urlParams.get('g')\n        const av = this.urlParams.get('av')\n        if (gParam != null && this.galleryViewer) {\n            this.galleryViewer.handleURLParam(gParam)\n            this.galleryViewer.show()\n        } else if (this.urlParams.get('v') != null && this.infoViewer) {\n            // Activate Changes Inspector\n            this.infoViewer.toggle()\n        } else if (this.urlParams.get('c') != null && this.commentsViewer) {\n            // Activate Comment Viewer\n            this.commentsViewer.toggle()\n        } else if (av != null && av === \"exp\" && this.expViewer) {\n            const widgetName = this.urlParams.get('expn')\n            if (widgetName !== null) this.expViewer.highlightWidget(decodeURIComponent(widgetName))\n            // Activate Experimental Viewer widget\n            this.expViewer.toggle()\n        }\n    }\n\n    initParseGetParams() {\n        const loc = document.location\n        this.fullBaseURL = loc.protocol + \"//\" + loc.hostname + loc.pathname\n        this.urlParams = new URLSearchParams(loc.search.substring(1));\n        this.urlSearch = loc.search\n\n        if (this.urlParams.get('e') != null) {\n            this.isEmbed = true\n            // hide image preload indicator\n            $('#nav loading').hide()\n            // hide Navigation\n            $('.navCenter').hide()\n            $('.navPreviewNext').hide()\n            $('#btnMenu').hide()\n            $('#btnOpenNew').show()\n        }\n    }\n    initializeHighDensitySupport() {\n        if (window.matchMedia) {\n            this.hdMediaQuery = window\n                .matchMedia(\"only screen and (min--moz-device-pixel-ratio: 1.1), only screen and (-o-min-device-pixel-ratio: 2.2/2), only screen and (-webkit-min-device-pixel-ratio: 1.1), only screen and (min-device-pixel-ratio: 1.1), only screen and (min-resolution: 1.1dppx)\");\n            var v = this;\n            this.hdMediaQuery.addListener(function (e) {\n                v.refresh();\n            });\n        }\n    }\n    isHighDensityDisplay() {\n        return (this.hdMediaQuery && this.hdMediaQuery.matches || (window.devicePixelRatio && window.devicePixelRatio > 1));\n    }\n    buildUserStory() {\n        //\n        let opages = []\n        story.pages.forEach(function (page) {\n            opages.push($.extend(new ViewerPage(), page))\n        })\n        story.pages = opages\n        //\n        this.userStoryPages = []\n        this.visStoryPages = []\n        for (var page of story.pages) {\n            if ('regular' == page.type || 'modal' == page.type) {\n                page.userIndex = this.userStoryPages.length\n                this.userStoryPages.push(page)\n            } else {\n                page.userIndex = -1\n            }\n            //\n            if ('regular' == page.type || 'modal' == page.type || 'overlay' == page.type) {\n                page.visIndex = this.visStoryPages.length\n                this.visStoryPages.push(page)\n            } else {\n                page.visIndex = -1\n            }\n        }\n    }\n\n    handleKeyDown(jevent) {\n        const v = viewer\n        const event = jevent.originalEvent\n\n        const allowNavigation = !this.child || !this.child.blockMainNavigation\n        const enableTopNavigation = !this.child || this.child.enableTopNavigation\n\n        // allow all childs to handle global keys\n        if (!this.child) {\n            for (const child of this.allChilds) {\n                if (child.handleKeyDownWhileInactive(jevent)) return true\n            }\n        }\n\n        // allow currently active childs to handle global keys\n        if (this.child && this.child.handleKeyDown(jevent)) return true\n\n        if (allowNavigation && 91 == event.which) { // cmd\n            if (this.highlightLinks) v.toggleLinks(false) // hide hightlights to allow user to make a screenshot on macOS\n        }\n\n        if (allowNavigation && (13 == event.which || 39 == event.which)) { // enter OR right\n            v.next()\n        } else if (allowNavigation && (8 == event.which || 37 == event.which)) { // backspace OR left\n            v.previous()\n        } else if (allowNavigation && story.layersExist && event.metaKey && (70 == event.which) && (!this.child || !this.child.customTextSearchPrevented())) { // Cmd+F\n            this.showTextSearch()\n        } else if (allowNavigation && story.layersExist && event.metaKey && (71 == event.which) && (!this.child || !this.child.customTextSearchPrevented())) { // Cmd+G -> Next search\n            this.currentPage.findTextNext()\n        } else if (allowNavigation && (16 == event.which)) { // shift\n            if (!jevent.metaKey) {  // no cmd to allow user to make a screenshot on macOS\n                v.toggleLinks()\n            }\n        } else if (event.metaKey || event.altKey || event.ctrlKey) { // skip any modificator active to allow a browser to handle its own shortkeys\n            return false\n        } else if (allowNavigation && 90 == event.which) { // z\n            v.toggleZoom()\n        } else if (allowNavigation && 69 == event.which) { // e\n            v.share()\n        } else if (73 == event.which) { // i\n            v.openFulImage()\n        } else if (allowNavigation && 76 == event.which) { // l\n            v.toogleLayout();\n        } else if (allowNavigation && 78 == event.which) { // n\n            v.toogleUI();\n        } else if (70 == event.which) { // f\n            v.toogleFullScreen()\n        } else if (enableTopNavigation && 83 == event.which) { // s\n            var first = null != story.startPageIndex ? story.pages[story.startPageIndex] : v.getFirstUserPage()\n            if (first && (first.index != v.currentPage.index || this.child)) {\n                this.hideChild()\n                v.goToPage(first.index)\n            }\n        } else if (allowNavigation && 27 == event.which) { // esc\n            v.onKeyEscape()\n        } else {\n            return false\n        }\n        jevent.preventDefault()\n        return true\n    }\n\n    showTextSearch() {\n        const search = prompt(\"Type text to find:\", this.searchText)\n        if (null != search) {\n            this.searchText = search\n            if (this.currentPage.findText(this.searchText)) {\n            }\n        }\n    }\n\n    blinkHotspots() {\n        if (this.symbolViewer && this.symbolViewer.visible) return\n        this.toggleLinks()\n        setTimeout(doBlinkHotspots, 500)\n    }\n\n    setMouseMoveHandler(obj) {\n        this.mouseMoveHandler = obj\n    }\n\n    onMouseMove(x, y) {\n        if (this.mouseMoveHandler && this.mouseMoveHandler.onMouseMove(x, y)) return\n        if (this.currentPage) this.currentPage.onMouseMove(x, y)\n    }\n\n    onContentClick() {\n        // Do we need to close a menu?\n        if (this.menuVisible) this.hideMenu()\n\n        // allow currently active child to handle click\n        if (this.child && this.child.onContentClick()) return true\n\n        if (this.linksDisabled) return false\n        if (this.onKeyEscape()) return\n        this.blinkHotspots()\n    }\n    onModalClick() {\n        this.blinkHotspots()\n    }\n\n\n    showMenu() {\n        addRemoveClass('class', 'menu', 'active')\n        this.menuVisible = true\n        return true\n    }\n    hideMenu() {\n        addRemoveClass('class', 'menu', 'active')\n        this.menuVisible = false\n        return true\n    }\n\n    _setupFolderinfoRequest(func) {\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"GET\", story.serverToolsPath + \"folder_info.php\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onreadystatechange = func;\n        xhr.send(null);\n    }\n\n    decreaseVersion() {\n        this._setupFolderinfoRequest(handleDecreaseVersion)\n    }\n\n    increaseVersion() {\n        this._setupFolderinfoRequest(handleIncreaseVersion)\n    }\n\n    showChild(child) {\n        // Hide currently visible child\n        if (this.child) {\n            this.hideChild(this.child)\n        }\n\n        // Show new child\n        this.child = child;\n        if (child.isSidebarChild) {\n            this._showSidebar()\n        }\n        child._showSelf()\n    }\n\n    _showSidebar() {\n        this.sidebarVisible = true\n        $('#sidebar').removeClass(\"hidden\")\n        viewer.zoomContent()\n    }\n\n    _hideSidebar() {\n        this.sidebarVisible = false\n        $('#sidebar').addClass(\"hidden\")\n        this.zoomContent()\n    }\n\n    hideChild() {\n        if (!this.child) return;\n        if (this.child.isSidebarChild) {\n            this._hideSidebar()\n        }\n        this.child._hideSelf()\n        this.child = null;\n\n    }\n\n    share() {\n        var page = this.currentPage\n        let url = this._getPageFullURL()\n        url += '&e=1'\n\n        var iframe = '<iframe src=\"' + url + '\" style=\"border: none;\" noborder=\"0\"'\n        iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n        iframe += ' height=\"' + (story.iFrameSizeHeight ? story.iFrameSizeHeight : page.height) + '\"'\n        iframe += ' scrolling=\"auto\" seamless id=\"iFrame1\"></iframe>'\n\n        iframe += '\\n\\n'\n\n        var ihref = url.substring(0, url.lastIndexOf(\"/\"))\n\n        ihref = ihref + \"/images/\" + page['image2x']\n        iframe += \"<a target='_blank' href='\" + url + \"'>\" + \"<img border='0' \"\n        iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n        //iframe += ' height=\"'+(story.iFrameSizeHeight?story.iFrameSizeHeight:page.height) + '\"'\n        iframe += \"src='\" + ihref + \"'\"\n        iframe += \"/></a>\"\n\n        alert(iframe)\n    }\n\n\n    openFulImage() {\n        let page = this.currentPage\n        let url = this._getPageFullURL(page)\n        url = url.substring(0, url.lastIndexOf(\"/\")) + \"/images/full/\" + page['image']\n\n        window.open(url, \"_blank\")\n    }\n\n\n\n    toggleZoom(newState = undefined, updateToogler = true) {\n        this.zoomEnabled = newState !== undefined ? newState : !this.zoomEnabled\n        if (updateToogler) $(\"#menu #zoom\").prop('checked', this.zoomEnabled)\n        this.zoomContent()\n    }\n\n    openNewWindow() {\n        let url = this.fullCurrentPageURL\n        // ok, now open it in the new browse window\n        window.open(url, \"_blank\")\n    }\n\n    zoomContent() {\n        var page = this.lastRegularPage\n        if (undefined == page) return\n\n\n        if (undefined == this.marker) {\n            this.marker = $('#marker')\n        }\n        var marker = this.marker\n\n        var content = $('#content')\n        //var contentShadow = $('#content-shadow')\n        var contentModal = $('#content-modal')\n        var elems = [content, contentModal] //,contentShadow\n\n        var fullWidth = marker.innerWidth()\n        var availableWidth = fullWidth\n        var zoom = \"\"\n        var scale = \"\"\n\n        // check sidebar\n        var sidebarWidth = 0\n        if (this.sidebarVisible) {\n            var sidebar = $(\"#sidebar\")\n\n            sidebarWidth = this.defSidebarWidth\n\n            /* commented because it works in bad way with small artboards and large screen\n            sidebarWidth = Math.round((fullWidth - page.width) / 2)\n            if (sidebarWidth < defSidebarWidth) {\n                sidebarWidth = defSidebarWidth\n                availableWidth = fullWidth - sidebarWidth\n            }*/\n            if (((fullWidth - page.width) / 2) < sidebarWidth) {\n                availableWidth = fullWidth - sidebarWidth\n            }\n\n            sidebar.css(\"margin-left\", (fullWidth - sidebarWidth) + \"px\")\n            sidebar.css(\"margin-top\", (0) + \"px\")\n            sidebar.css(\"width\", sidebarWidth + \"px\")\n            sidebar.css(\"height\", \"100%\")\n        }\n\n\n        if (this.zoomEnabled && ((availableWidth < page.width) || screen.width <= 800)) {\n            zoom = availableWidth / page.width\n            scale = \"scale(\" + zoom + \")\"\n        }\n\n        var newZoom = zoom != '' ? (zoom + 0) : 1\n\n        if (undefined == this.currentZoom || this.currentZoom != newZoom) {\n            for (var el of elems) {\n                el.css(\"zoom\", zoom)\n                el.css(\"-moz-transform\", scale)\n            }\n            content.css(\"-moz-transform-origin\", \"left top\")\n            contentModal.css(\"-moz-transform-origin\", \"center top\")\n\n        }\n\n        this.currentZoom = newZoom\n        this.fullWidth = fullWidth\n\n        // Calculate margins\n        this.currentMarginLeft = Math.round(availableWidth / 2) - Math.round(page.width / 2 * this.currentZoom)\n        this.currentMarginTop = 0\n\n        if (this.currentMarginLeft < 0) this.currentMarginLeft = 0\n\n        // Set content to new left positions\n        content.css(\"margin-left\", this.currentMarginLeft + \"px\")\n        content.css(\"margin-top\", this.currentMarginTop + \"px\")\n        this.currentPage.updatePosition()\n\n        //\n        if (this.child) {\n            this.child.viewerResized()\n        }\n    }\n\n    getPageHashes() {\n        if (this.pageHashes == null) {\n            var hashes = {};\n            for (var page of story.pages) {\n                hashes[page.getHash()] = page.index;\n            }\n            this.pageHashes = hashes;\n        }\n        return this.pageHashes;\n    }\n\n    getModalFirstParentPageIndex(modalIndex) {\n        var foundPageIndex = null\n        // scan all regular pages\n        story.pages.filter(page => \"regular\" == page.type).some(function (page) {\n            const foundLinks = page.links.filter(link => link.page != null && link.page == modalIndex)\n            if (foundLinks.length != 0) {\n                // return the page index which has link to modal\n                foundPageIndex = page.index\n                return true\n            }\n            // save a first regular page as a \"found\" for case if we will not\n            // find any page with a link to a specified modal\n            if (null == foundPageIndex) foundPageIndex = page.index\n            return false\n        }, this)\n\n        // ok, we found some regular page which has a link to specified modal ( or it was a fist regular page)\n        return foundPageIndex\n    }\n\n    getPageIndex(page, defIndex = 0) {\n        var index;\n\n        if (typeof page === \"number\") {\n            index = page;\n        } else if (page === \"\") {\n            index = defIndex;\n        } else {\n            index = this.getPageHashes()[page];\n            if (index == undefined) {\n                index = defIndex;\n            }\n        }\n        return index;\n    }\n\n    goBack() {\n        if (this.backStack.length > 0) {\n            this.goTo(this.backStack[this.backStack.length - 1], true, undefined, false);\n            this.backStack.pop();\n        } else if (this.currentPage.isModal && this.lastRegularPage) {\n            this.goTo(this.lastRegularPage.index, true, undefined, false);\n        } else {\n            window.history.back();\n        }\n    }\n    closeModal() {\n        return this.goBack()\n    }\n    goToPage(page, searchText) {\n        this.clear_context();\n        this.goTo(page);\n        //\n        if (undefined != searchText) {\n            this.searchText = searchText\n            this.currentPage.findText(this.searchText)\n        }\n    }\n\n    goTo(page, refreshURL = true, link = undefined, incBackStack = true) {\n\n        var index = this.getPageIndex(page);\n        var newPage = story.pages[index];\n\n        // Need to build a context for overlay\n        if (newPage.type === \"overlay\") {\n            if (newPage.showOverlayOverParent()) return\n        }\n\n        // We don't need any waiting page transitions anymore\n        this._resetTransQueue()\n\n        //if(this.symbolViewer) this.symbolViewer.hide()\n        var currentPage = this.currentPage\n\n        if (incBackStack && currentPage && !currentPage.isModal) {\n            this.backStack.push(currentPage.index);\n        }\n\n        var oldcurrentPageModal = currentPage && currentPage.isModal\n\n        if (index < 0 || (currentPage && index == currentPage.index) || index >= story.pages.length) return;\n\n\n        if (newPage.type === \"modal\") {\n            // hide parent page links hightlighting\n            this._updateLinksState(false, $('#content'))\n\n            // no any page visible now, need to find something\n            if (undefined == currentPage) {\n                var parentIndex = this.getModalFirstParentPageIndex(index);\n                this.goTo(parentIndex, false);\n                this.zoomContent()\n            }\n\n            // redraw modal links hightlighting\n            this._updateLinksState()\n        } else {\n            if (oldcurrentPageModal) {\n                // hide modal page links hightlighting\n                this._updateLinksState(false, $('#content-modal'))\n                this._updateLinksState(undefined, $('#content'))\n            }\n        }\n        this.prevPage = currentPage\n        var prevRegularPage = this.lastRegularPage\n\n        newPage.show()\n\n        this.refresh_adjust_content_layer(newPage);\n        this.refresh_hide_last_image(newPage)\n        this.refresh_switch_modal_layer(newPage);\n        if (refreshURL) {\n            this.refresh_url(newPage)\n        } else {\n            this._calcCurrentPageURL(newPage)\n        }\n        this.refresh_update_navbar(newPage);\n\n        this.currentPage = newPage;\n        if (!newPage.isModal) {\n            this.lastRegularPage = newPage\n        }\n\n        // zoom content if the new page dimensions differ from the previous\n        if (!newPage.isModal) {\n            if (!prevRegularPage || newPage.width != prevRegularPage.width || newPage.height != prevRegularPage.height) {\n                this.zoomContent()\n            }\n        }\n\n\n        if (newPage.transNextMsecs != undefined) {\n            this._setupTransNext(newPage.transNextMsecs)\n        }\n\n        if (!newPage.disableAutoScroll && (!link || !link.disableAutoScroll)) {\n            window.scrollTo(0, 0)\n        }\n\n        if (this.child) this.child.pageChanged()\n        this.allChilds.filter(c => c.alwaysHandlePageChanged).forEach(function (c) {\n            c.pageChanged()\n        })\n    }\n\n    _setupTransNext(msecs) {\n        // deactivate all waiting transitions\n        for (var trans of this.transQueue) {\n            trans.active = false\n        }\n        // place new active transition over the top of stack\n        this.transQueue.push({\n            page: this.currentPage,\n            active: true\n        })\n        // set timer in milisecs\n        setTimeout(doTransNext, msecs)\n    }\n    // Deactivate all waiting transitions\n    _resetTransQueue() {\n        for (var trans of this.transQueue) {\n            trans.active = false\n        }\n    }\n    refresh_update_navbar(page) {\n        var VERSION_INJECT = story.docVersion != 'V_V_V' ? (\" (v\" + story.docVersion + \")\") : \"\";\n\n        var prevPage = this.getPreviousUserPage(page)\n        var nextPage = this.getNextUserPage(page)\n\n        $('#nav .title').html((page.userIndex + 1) + '/' + this.userStoryPages.length + ' - ' + page.title + VERSION_INJECT);\n        $('#nav-left-prev').toggleClass('disabled', !prevPage)\n        $('#nav-left-next').toggleClass('disabled', !nextPage)\n\n        if (prevPage) {\n            $('#nav-left-prev a').attr('title', prevPage.title);\n        } else {\n            $('#nav-left-prev a').removeAttr('title');\n        }\n\n        if (nextPage) {\n            $('#nav-left-next a').attr('title', nextPage.title);\n        } else {\n            $('#nav-left-next a').removeAttr('title');\n        }\n\n        $('#nav-right-hints').toggleClass('disabled', page.annotations == undefined);\n\n        this.refresh_update_links_toggler(page);\n    }\n    refresh_update_links_toggler(page) {\n        $(\"#menu #links\").prop('checked', this.highlightLinks);\n    }\n    refresh_hide_last_image(page) {\n        var content = $('#content');\n        var contentModal = $('#content-modal');\n        var isModal = page.isModal\n\n        // hide last regular page to show a new regular after modal\n        if (!isModal && this.lastRegularPage && this.lastRegularPage.index != page.index) {\n            var lastPageImg = $('#img_' + this.lastRegularPage.index);\n            if (lastPageImg.length) {\n                this.lastRegularPage.hide()\n            }\n        }\n\n        // hide last modal\n        var prevPageWasModal = this.prevPage != null && this.prevPage.type === \"modal\"\n        if (prevPageWasModal) {\n            var prevImg = $('#img_' + this.prevPage.index);\n            if (prevImg.length) {\n                this.prevPage.hide()\n                //pagerHideImg(prevImg)\n            }\n        }\n    }\n    refresh_adjust_content_layer(page) {\n        if (page.isModal) return;\n\n        var contentShadow = $('#content-shadow');\n        var contentModal = $('#content-modal');\n        var content = $('#content');\n\n        var prevPageWasModal = this.prevPage && this.prevPage.isModal\n        if (prevPageWasModal) {\n            contentShadow.addClass('hidden');\n            contentModal.addClass('hidden');\n        }\n    }\n\n    refresh_switch_modal_layer(page) {\n        if (!page.isModal) return;\n\n        var showShadow = page.showShadow == 1;\n        var contentModal = $('#content-modal');\n        var contentShadow = $('#content-shadow');\n\n        if (showShadow) {\n            contentShadow.removeClass('no-shadow');\n            contentShadow.addClass('shadow');\n            contentShadow.removeClass('hidden');\n        } else {\n            contentModal.addClass('hidden');\n        }\n        contentModal.removeClass('hidden');\n    }\n\n    _getSearchPath(page = null, extURL = null) {\n        if (!page) page = this.currentPage\n        let search = '?' + encodeURIComponent(page.getHash())\n        if (extURL != null && extURL != \"\") search += \"&\" + extURL\n        return search\n    }\n\n    _getPageFullURL(page = null, extURL = null) {\n        if (!page) page = this.currentPage\n        return this.fullBaseURL + this._getSearchPath(page, extURL)\n    }\n\n    _calcCurrentPageURL(page = null, extURL = null) {\n        if (!page) page = this.currentPage\n        this.urlLastIndex = page.index\n        $(document).attr('title', story.title + ': ' + page.title)\n\n        let newPath = this._getPageFullURL(page, extURL)\n        this.fullCurrentPageURL = newPath\n    }\n\n    refresh_url(page, extURL = \"\", pushHistory = true) {\n        if (this.urlLocked) return\n\n        this._calcCurrentPageURL(page, extURL)\n        let newPath = this.fullCurrentPageURL\n        this.fullCurrentPageURL = newPath\n\n        if (this.isEmbed) {\n            newPath += \"&e=1\"\n        }\n        if (this.galleryViewer && this.galleryViewer.isVisible()) {\n            newPath += \"&g=\" + (this.galleryViewer.isMapMode ? \"m\" : \"g\")\n        }\n        if (this.commentsViewer && this.commentsViewer.isVisible()) {\n            newPath += \"&c=1\"\n        }\n\n        if (pushHistory) {\n            window.history.pushState(newPath, page.title, newPath);\n        } else {\n            window.history.replaceState({}, page.title, newPath);\n        }\n    }\n\n    _parseLocationSearch() {\n        //if (document.location.hash != null && document.location.hash != \"\")\n        //  return this._parseLocationHash()\n\n        var result = {\n            page_name: \"\",\n            reset_url: false,\n            overlayLinkIndex: undefined,\n            redirectOverlayLinkIndex: undefined,\n        }\n        this.urlParams.forEach(function (value, key) {\n            if (\"\" == value) result.page_name = key\n        }, this);\n\n        if (null == result.page_name || \"\" == result.page_name || this.urlParams.get(result.page_name) != \"\") {\n            result.page_name = \"\"\n            result.reset_url = true\n        } else {\n            result.overlayLinkIndex = this.urlParams.get(\"o\")\n        }\n        return result\n    }\n\n    handleNewLocation(initial) {\n        var locInfo = this._parseLocationSearch()\n        var pageIndex = locInfo.page_name != null ? this.getPageIndex(locInfo.page_name, null) : null\n        if (null == pageIndex) {\n            if (locInfo.page_name != \"\") alert(\"The requested page is not found. You will be redirected to the default page.\")\n            // get the default page\n            pageIndex = story.startPageIndex\n            locInfo.reset_url = true\n        }\n\n        if (!initial && this.urlLastIndex == pageIndex) {\n            return\n        }\n\n        var page = story.pages[pageIndex];\n\n        // check if this redirect overlay\n        let overlayRedirectInfo = null\n        if (undefined != page.overlayRedirectTargetPage) {\n            overlayRedirectInfo = page._getSrcPageAndLink()\n            if (overlayRedirectInfo) {\n                pageIndex = overlayRedirectInfo.page.index\n                page = overlayRedirectInfo.page\n            }\n\n        }\n\n        if (initial)\n            page.isDefault = true\n        else\n            this.clear_context();\n\n        // check if this page overlay\n        // check if this redirect overlay\n        this.goTo(pageIndex, locInfo.reset_url);\n\n        if (locInfo.overlayLinkIndex != null) {\n            page.showOverlayByLinkIndex(locInfo.overlayLinkIndex)\n        }\n\n        if (!initial) this.urlLastIndex = pageIndex\n\n        // Open redirect overlay over the overlay source page\n        if (overlayRedirectInfo) {\n            overlayRedirectInfo.link.a.click()\n        }\n    }\n\n    clear_context_hide_all_images() {\n        var page = this.currentPage;\n        var content = $('#content');\n        var contentModal = $('#content-modal');\n        var contentShadow = $('#content-shadow');\n        var isModal = page && page.type === \"modal\";\n\n        contentShadow.addClass('hidden');\n        contentModal.addClass('hidden');\n\n        // hide last regular page\n        if (this.lastRegularPage) {\n            var lastPageImg = $('#img_' + this.lastRegularPage.index);\n            if (lastPageImg.length) {\n                this.lastRegularPage.hide()\n            }\n        }\n\n        // hide current modal\n        if (isModal) {\n            var modalImg = $('#img_' + this.currentPage.index);\n            if (modalImg.length) {\n                this.currentPage.hide()\n            }\n        }\n    }\n\n    clear_context() {\n        this.clear_context_hide_all_images()\n\n        this.prevPage = undefined\n        this.currentPage = undefined\n        this.lastRegularPage = undefined\n\n        this.backStack = []\n    }\n\n    refresh() {\n        reloadAllPageImages()\n        this.currentPage.show()\n    }\n\n    onKeyEscape() {\n        // Close menu\n        if (this.menuVisible) return this.hideMenu()\n\n\n        const page = this.currentPage\n        // If the current page has search visible then hide it\n        if (undefined != page.actualSearchText) {\n            page.stopTextSearch()\n            return true\n        }\n        // If the current page has some overlay open then close it\n        if (page.hideCurrentOverlays()) {\n            return true\n        }\n        // If the current page is modal then close it and go to the last non-modal page\n        if (this.currentPage.isModal) {\n            viewer.closeModal()\n            return true\n        }\n        return false\n    }\n    next() {\n        var page = this.getNextUserPage(this.currentPage)\n        if (!page) return\n        this.goToPage(page.index);\n    }\n\n    previous() {\n        // Get previous page\n        var page = this.getPreviousUserPage(this.currentPage)\n        // Go from the first to the latest page\n        if(!page) page =  this.userStoryPages[ this.userStoryPages.length-1]\n        // oops\n        if (!page) return\n        this.goToPage(page.index);\n    }\n\n    getFirstUserPage() {\n        var first = this.userStoryPages[0]\n        return first ? first : null\n    }\n    getNextUserPage(page = null) {\n        let nextUserIndex = 0\n        if (!page) page = this.currentPage\n        if (page) nextUserIndex = page.userIndex + 1\n        if (nextUserIndex >= this.userStoryPages.length) nextUserIndex = 0\n        return this.userStoryPages[nextUserIndex]\n    }\n    getNextVisPage(page, loopSearch = true) {\n        let nexVisIndex = page ? page.visIndex + 1 : 0\n        if (nexVisIndex >= this.visStoryPages.length)\n            if (loopSearch) nexVisIndex = 0; else return null\n        return this.visStoryPages[nexVisIndex]\n    }\n    getPreviousUserPage(page) {\n        var prevUserIndex = page ? page.userIndex - 1 : -1\n        if (prevUserIndex < 0) return null\n        return this.userStoryPages[prevUserIndex]\n    }\n    toggleLinks(newState = undefined, updateToogler = true) {\n        this.highlightLinks = newState != undefined ? newState : !this.highlightLinks\n        if (updateToogler) this.refresh_update_links_toggler(this.currentPage)\n        this._updateLinksState()\n    }\n    toogleLayout(newState = undefined, updateToogler = true) {\n        this.showLayout = newState != undefined ? newState : !this.showLayout\n        if (updateToogler) $(\"#menu #pagegrid\").prop('checked', this.showLayout);\n        const div = $('#content')\n\n        if (this.showLayout) {\n            this.currentPage.showLayout()\n            div.addClass(\"contentLayoutVisible\")\n        } else\n            div.removeClass(\"contentLayoutVisible\")\n    }\n    toogleFullScreen(newState = undefined, updateToogler = true) {\n        this.isFullScreen = newState != undefined ? newState : !this.isFullScreen\n        if (updateToogler) $(\"#menu #fullScreen\").prop('checked', this.isFullScreen);\n        //\n        return this.isFullScreen ? this._enableFullScreen() : this._disableFullScreen()\n    }\n    //\n    toogleUI(newState = undefined, updateToogler = true) {\n        this.showUI = newState != undefined ? newState : !this.showUI\n        if (updateToogler) $(\"#menu #ui\").prop('checked', this.showUI);\n        $('#nav').slideToggle('fast')\n    }\n    _updateLinksState(showLinks = undefined, div = undefined) {\n        if (undefined == div) {\n            if (this.currentPage.isModal) {\n                div = $('#content-modal')\n            } else {\n                div = $('#content')\n            }\n        }\n        if (undefined == showLinks) showLinks = this.highlightLinks\n        if (showLinks)\n            div.addClass(\"contentLinksVisible\")\n        else\n            div.removeClass(\"contentLinksVisible\")\n    }\n\n    showHints() {\n        var text = this.currentPage.annotations;\n        if (text == undefined) return;\n        alert(text);\n    }\n\n    handleStateChanges(e) {\n        if(this.stateChangeIgnore){\n            this.stateChangeIgnore = false\n            return\n        }\n\n        viewer.urlLocked = true\n        viewer.currentPage.hide(true, true)\n        viewer.currentPage = null\n\n        viewer.initParseGetParams()\n        viewer.handleNewLocation(true)\n        viewer.urlLocked = false\n    }\n\n    _enableFullScreen() {\n        ///\n        const elem = document.documentElement\n        if (elem.requestFullscreen) {\n            elem.requestFullscreen();\n        } else if (elem.webkitRequestFullscreen) { /* Safari */\n            elem.webkitRequestFullscreen();\n        } else if (elem.msRequestFullscreen) { /* IE11 */\n            elem.msRequestFullscreen();\n\n        }\n        //\n        const changeHandler = function (event) {\n            if (document.webkitIsFullScreen === false || document.mozFullScreen === false || document.msFullscreenElement === false) {\n                presenterViewer.stop(false)\n            }\n        }\n        document.addEventListener(\"fullscreenchange\", changeHandler, false);\n        document.addEventListener(\"webkitfullscreenchange\", changeHandler, false);\n        document.addEventListener(\"mozfullscreenchange\", changeHandler, false);\n    }\n\n    _disableFullScreen() {\n        if (document.exitFullscreen) {\n            document.exitFullscreen();\n        } else if (document.webkitExitFullscreen) { /* Safari */\n            document.webkitExitFullscreen();\n        } else if (document.msExitFullscreen) { /* IE11 */\n            document.msExitFullscreen();\n        }\n    }\n}\n\n// ADD | REMOVE CLASS\n// mode ID - getELementByID\n// mode CLASS - getELementByClassName\n\nfunction addRemoveClass(mode, el, cls) {\n\n    var el;\n\n    switch (mode) {\n        case 'class':\n            el = document.getElementsByClassName(el)[0];\n            break;\n\n        case 'id':\n            el = document.getElementById(el);\n            break;\n    }\n\n    if (el.classList.contains(cls)) {\n        el.classList.remove(cls)\n    } else {\n        el.classList.add(cls);\n    }\n}\n\nfunction handleStateChanges(e) {   \n    viewer.handleStateChanges(e)\n}\n\n$(document).ready(function () {\n    viewer.initialize();\n    if (!!('ontouchstart' in window) || !!('onmsgesturechange' in window)) {\n        $('body').removeClass('screen');\n    }\n\n    viewer.handleNewLocation(true)\n    if (!viewer.isEmbed) preloadAllPageImages();\n\n    window.addEventListener('popstate', handleStateChanges);\n    $(window).hashchange(handleStateChanges);\n\n    viewer.zoomContent()\n    viewer.initializeLast()\n});\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/ViewerPage.js",
    "content": "\n///\nconst Constants = {\n    ARTBOARD_OVERLAY_PIN_HOTSPOT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE: 1,\n    //\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT: 6,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER: 7,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_RELATIVE: 8,\n    //\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_PAGE_CENTER: 6,\n\n    TRIGGER_ON_CLICK: 0,\n    TRIGGER_ON_HOVER: 1,\n\n    LAYER_VSCROLL_NONE: 0,\n    LAYER_VSCROLL_DEFAULT: 1,\n    LAYER_VSCROLL_ALWAYS: 2,\n    LAYER_VSCROLL_NEVER: 3,\n}\n\nconst EVENT_HOVER = 1\nconst TRANS_ANIM_NONE = 0\nlet TRANS_ANIMATIONS = [\n    {},\n    { in_str_classes: \".transit .slideInUp\", out_str_classes: \".transit .slideOutUp\", in_token: \"transition-slidein-up\", out_token: \"transition-slideout-up\" },\n    { in_str_classes: \".transit .slideInLeft\", out_str_classes: \".transit .slideOutLeft\", in_token: \"transition-slidein-left\", out_token: \"transition-slideout-left\" },\n    { in_str_classes: \".transit .fadeIn\", out_str_classes: \".transit .fadeOut\", in_token: \"transition-fadein_str_classes:\", out_token: \"transition-fadeout\" },\n    { in_str_classes: \".transit .slideInRight\", out_str_classes: \".transit .slideOutRight\", in_token: \"transition-slidein-right\", out_token: \"transition-slideout-right\" },\n    { in_str_classes: \".transit .slideInDown\", out_str_classes: \".transit .slideOutDown\", in_token: \"transition-slidein-down\", out_token: \"transition-slideout-down\" },\n]\n\n\n\nfunction inViewport($el)\n{\n    // source: https://stackoverflow.com/questions/24768795/get-the-visible-height-of-a-div-with-jquery\n    var elH = $el.outerHeight(),\n        H = $(window).height(),\n        r = $el[0].getBoundingClientRect(), t = r.top, b = r.bottom;\n    return [r.top, Math.max(0, t > 0 ? Math.min(elH, H - t) : Math.min(b, H))]\n}\n\nfunction handleAnimationEndOnHide(el)\n{\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnHide)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tch\")]\n    t.out_classes.forEach(function (className)\n    {\n        el.target.classList.remove(className)\n    })\n    el.target.classList.add(\"hidden\")\n}\n\nfunction handleAnimationEndOnShow(el)\n{\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnShow)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tcs\")]\n    t.in_classes.forEach(function (className)\n    {\n        el.target.classList.remove(className)\n    })\n}\n\nclass ViewerPage\n{\n\n    constructor()\n    {\n        this.currentOverlays = []\n        this.parentPage = undefined\n\n        this.visible = false\n        this.image = undefined\n        this.imageDiv = undefined\n        this.imageObj = undefined\n\n        this.currentLeft = undefined\n        this.currentTop = undefined\n\n        this.currentX = undefined\n        this.currentY = undefined\n\n        // this.searchLayer  = undefined\n\n        this.overlayByEvent = undefined\n        this.tmpSrcOverlayByEvent = undefined\n\n        this.visibleInGallery = true\n    }\n\n    showHideGalleryLinks(show = null)\n    {\n\n        if (this.slinks) this._showHideGalleryLinkSet(this.slinks, show)\n        if (this.dlinks) this._showHideGalleryLinkSet(this.dlinks, show)\n    }\n\n    _showHideGalleryLinkSet(links, forceShow = null)\n    {\n        links.forEach(function (link)\n        {\n            let show = forceShow != null ? forceShow : this.visibleInGallery && link.dpage.visibleInGallery && link.spage.visibleInGallery\n            // hide link\n            const o = $(\"#gallery #grid svg #l\" + link.index)\n            if (show) o.show(); else o.hide()\n            // hide start point\n            const sp = $(\"#gallery #grid svg #s\" + link.index)\n            if (show) sp.show(); else sp.hide()\n        }, this)\n    }\n\n    getHash()\n    {\n        var image = this.image;\n        return image.substring(0, image.length - 4); // strip .png suffix\n    }\n\n    hide(hideChilds = false, disableAnim = false)\n    {\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal)\n        {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tch\", this.transAnimType)\n            transInfo.out_classes.forEach(function (className)\n            {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnHide)\n        } else\n        {\n            this.imageDiv.addClass(\"hidden\")\n        }\n\n        if (undefined != this.parentPage)\n        { // current page is overlay      \n\n            if (hideChilds) this.hideChildOverlays()\n\n            const parent = this.parentPage\n            viewer.stateChangeIgnore = true\n            viewer.refresh_url(parent)\n            // remove this from parent overlay\n            const index = parent.currentOverlays.indexOf(this)\n            parent.currentOverlays.splice(index, 1)\n            this.parentPage = undefined\n        } else\n        {\n            this.hideCurrentOverlays()\n        }\n\n        if (undefined != this.tmpSrcOverlayByEvent)\n        {\n            this.overlayByEvent = this.tmpSrcOverlayByEvent\n            this.tmpSrcOverlayByEvent = undefined\n        }\n        // Cleanup\n        this.visible = false\n        this.currentLink = null\n    }\n\n    hideCurrentOverlays()\n    {\n        const overlays = this.currentOverlays.filter(p => p.overlayOutsideClickIgnore != true).slice()\n        for (let overlay of overlays)\n        {\n            overlay.hide()\n        }\n        return overlays.length > 0\n    }\n\n    hideChildOverlays()\n    {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays)\n        {\n            if (overlay.currentLink.orgPage != this) continue\n            overlay.hide()\n        }\n    }\n\n    hideOtherParentOverlays()\n    {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays)\n        {\n            if (overlay == this) continue\n            overlay.hide()\n        }\n    }\n\n\n    show(disableAnim = false)\n    {\n        if (!this.imageObj) this.loadImages(true)\n\n        this.updatePosition()\n\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal)\n        {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tcs\", this.transAnimType)\n            transInfo.in_classes.forEach(function (className, index)\n            {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnShow)\n        } else\n        {\n        }\n        this.imageDiv.removeClass(\"hidden\")\n        this.visible = true\n    }\n\n    // result:{\n    //    foundPage: {}\n    //    foundlink: {}\n    //}\n    showOverlayOverParent()\n    {\n        var foundPage = null\n        var foundLink = null\n        // scan all regular pages\n        story.pages.filter(page => \"regular\" == page.type).some(function (page)\n        {\n            const foundLinks = page.links.filter(link => link.page != null && link.page == this.index)\n            if (foundLinks.length != 0)\n            {\n                // return the page index which has link to modal                    \n                foundPage = page\n                foundLink = foundLinks[0]\n                return true\n            }\n            return false\n        }, this)\n        if (!foundPage) return false\n\n        // ok, we found some regular page which has a link to specified overlay        \n        viewer.goTo(foundPage.index, true);\n        foundPage.showOverlayByLinkIndex(foundLink.index)\n\n        return true\n    }\n\n    findTextNext()\n    {\n        if (undefined == this.textElemIndex) return false\n        //\n        //this.textElemIndex++\n        this.findText(this.actualSearchText)\n    }\n\n    findText(text, interactive = true)\n    {\n        text = text.toLowerCase().trim()\n        //        \n        if (undefined != this.actualSearchText && this.actualSearchText != text)\n        {\n            this.textElemIndex = undefined\n            this.actualSearchText = undefined\n        }\n        if (undefined == this.textElemIndex) this.textElemIndex = 0\n\n        // Search all layers with required text inside\n        let foundLayers = []\n        this.findTextLayersByText(text, foundLayers)\n        foundLayers.sort(function (a, b)\n        {\n            return a.y < b.y ? -1 : 1\n        })\n        //  No results\n        if (0 == foundLayers.length)\n        {\n            if (!interactive) return false\n            const nextPage = this.findNextPageWithText(text)\n            if (!nextPage)\n            {\n                window.alert(\"The text was not found\")\n                return false\n            }\n            if (!window.confirm(\"Not found on the current page. Do you want to check other pages?\"))\n            {\n                return false\n            }\n            viewer.goTo(nextPage.index)\n            return true\n        }\n        if (foundLayers.length <= this.textElemIndex)\n        {\n            // No more results ahead\n            if (interactive)\n            {\n                const nextPage = this.findNextPageWithText(text)\n                if (nextPage)\n                {\n                    if (window.confirm(\"The last result found on the current page. Do you want to check other pages?\"))\n                    {\n                        //this.stopTextSearch()\n                        viewer.goTo(nextPage.index)\n                        return true\n                    }\n                }\n            }\n            this.textElemIndex = 0\n        }\n        // Highlight results\n        this.hideFoundTextResults()\n        foundLayers.forEach(function (l, index)\n        {\n            this._findTextShowElement(l, index == this.textElemIndex)\n        }, this)\n        //\n        this.actualSearchText = text\n        if ((foundLayers.length + 1) > this.textElemIndex) this.textElemIndex++\n        //\n        return foundLayers.length > 0\n    }\n\n    findNextPageWithText(text)\n    {\n        let foundPage = null\n        let page = this\n        while (true)\n        {\n            // if we have some next pages?\n            const nextPage = viewer.getNextVisPage(page)\n            if (!nextPage) break\n            // we don't want to run infinity loop\n            if (nextPage.index == this.index) break\n            // if a next page has this text\n            if (nextPage.findText(text, false))\n            {\n                foundPage = nextPage\n                break\n            }\n            page = nextPage\n        }\n        return foundPage\n    }\n\n    // Arguments:\n    //  foundLayers: ref to list result\n    //  layers: list of layers or null (to get a root layers)\n    findTextLayersByText(text, foundLayers, layers = null)\n    {\n        if (!story.layersExist) return false\n        if (null == layers)\n        {\n            layers = layersData[this.index].c\n            if (!layers) return false\n        }\n\n        for (var l of layers.slice().reverse())\n        {\n            if (\"Text\" == l.tp && l.tx.toLowerCase().includes(text))\n            {\n                foundLayers.push(l)\n            }\n            if (undefined != l.c)\n                this.findTextLayersByText(text, foundLayers, l.c)\n        }\n    }\n    _findTextShowElement(l, isFocused = false)\n    {\n        const padding = isFocused ? 5 : 0\n        let x = l.x - padding\n        let y = l.y - padding\n\n        // show layer border\n        var style = \"left: \" + x + \"px; top:\" + y + \"px; \"\n        style += \"width: \" + (l.w + padding * 2) + \"px; height:\" + (l.h + padding * 2) + \"px; \"\n        var elemDiv = $(\"<div>\", {\n            class: isFocused ? \"searchFocusedResultDiv\" : \"searchResultDiv\",\n        }).attr('style', style)\n\n        elemDiv.appendTo(this.linksDiv)\n\n        // scroll window to show a layer\n        if (isFocused)\n        {\n            this._scrollTo(l.x, l.y)\n        }\n    }\n\n    _scrollTo(x, y)\n    {\n        for (let p of this.fixedPanels)\n        {\n            if (Math.round(p.y) == 0)\n            {\n                y -= p.height\n                break\n            }\n        }\n        window.scrollTo(x, y - 10);\n    }\n\n    hideFoundTextResults()\n    {\n        $(\".searchResultDiv\").remove()\n        $(\".searchFocusedResultDiv\").remove()\n    }\n\n    stopTextSearch()\n    {\n        this.hideFoundTextResults()\n        this.actualSearchText = undefined\n        this.textElemIndex = undefined\n    }\n\n    updatePosition()\n    {\n        this.currentLeft = viewer.currentMarginLeft\n        this.currentTop = viewer.currentMarginTop\n\n        if (this.isModal)\n        {\n            var regPage = viewer.lastRegularPage\n\n            this.currentLeft += Math.round(regPage.width / 2) - Math.round(this.width / 2)\n            const [y, visibleHeight] = inViewport(regPage.imageDiv)\n            this.currentTop += Math.round(visibleHeight / 2) - Math.round(this.height / 2 * viewer.currentZoom)\n            if (this.currentTop < 0) this.currentTop = 0\n            if (this.currentLeft < 0) this.currentLeft = 0\n\n            var contentModal = $('#content-modal');\n            contentModal.css(\"margin-left\", this.currentLeft + \"px\")\n            contentModal.css(\"margin-top\", this.currentTop + \"px\")\n            if (this.height >= visibleHeight)\n                contentModal.css(\"overflow-y\", \"scroll\")\n            else\n                contentModal.css(\"overflow-y\", \"\")\n        } else if (\"overlay\" == this.type)\n        {\n            this.currentLeft = viewer.currentPage ? viewer.currentPage.currentLeft : 0\n            this.currentTop = viewer.currentPage ? viewer.currentPage.currentTop : 0\n        }\n        // Update fixed layers position)\n        /*let py = null, ph = null\n        this.fixedPanels.filter(p => !p.constrains.top && p.constrains.bottom).forEach(p =>\n        {            \n            if (py === null) [py, ph] = inViewport(this.imageDiv)\n            if(viewer.currentZoom>=1)\n                p.imageDiv.css(\"top\", (py + ph - p.height) + \"px\")\n            else\n                p.imageDiv.css(\"top\", (this.height - p.height) + \"px\")\n        }, this)*/\n    }\n\n    showOverlayByLinkIndex(linkIndex)\n    {\n        linkIndex = parseInt(linkIndex, 10)\n\n        var link = this._getLinkByIndex(linkIndex)\n        if (!link)\n        {\n            console.log('Error: can not find link to overlay by index=\"' + linkIndex + '\"')\n            return false\n        }\n\n        // can handle only page-to-page transition\n        if ((link[\"page\"] == undefined)) return false\n\n        var destPage = story.pages[link.page]\n        // for mouseover overlay we need to show it on click, but only one time)\n        if (\"overlay\" == destPage.type && Constants.TRIGGER_ON_HOVER == destPage.overlayByEvent)\n        {\n            destPage.tmpSrcOverlayByEvent = destPage.overlayByEvent\n            destPage.overlayByEvent = Constants.TRIGGER_ON_HOVER\n            viewer.customEvent = {\n                x: link.rect.x,\n                y: link.rect.y,\n                pageIndex: this.index,\n                linkIndex: link.index\n            }\n            handleLinkEvent({})\n            viewer.customEvent = undefined\n        } else\n        {\n            link.a.click()\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMove(x, y)\n    {\n        for (let overlay of this.currentOverlays)\n        {\n            // Commented to hide mouseover-overlay inside onclick-overlay  (ver 12.4.3)\n            //if (overlay.currentLink.orgPage != this) continue \n            overlay.onMouseMoveOverlay(x, y)\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMoveOverlay(x, y)\n    {\n        if (this.imageDiv.hasClass(\"hidden\") || this.overlayByEvent != Constants.TRIGGER_ON_HOVER) return false\n        if (viewer.linksDisabled) return false\n\n        // handle mouse hover if this page is overlay\n        var _hideSelf = false\n        while (true)//Constants.TRIGGER_ON_CLICK == this.overlayByEvent)\n        {\n            var localX = Math.round(x / viewer.currentZoom) - this.currentLeft\n            var localY = Math.round(y / viewer.currentZoom) - this.currentTop\n            //alert(\" localX:\"+localX+\" localY:\"+localY+\" linkX:\"+this.currentLink.x+\" linkY:\"+this.currentLink.y);\n\n\n            if ( // check if we inside in overlay\n                localX >= this.currentX\n                && localY >= this.currentY\n                && localX < (this.currentX + this.width)\n                && localY < (this.currentY + this.height)\n            )\n            {\n                break\n            }\n\n            if ( // check if we out of current hotspot\n                localX < this.currentLink.x\n                || localY < this.currentLink.y\n                || localX >= (this.currentLink.x + this.currentLink.width)\n                || localY >= (this.currentLink.y + this.currentLink.height)\n            )\n            {\n                _hideSelf = true\n                break\n            }\n            break\n        }\n\n        // allow childs to handle mouse move\n        var visibleTotal = 0\n        var total = 0\n\n        for (let overlay of this.parentPage.currentOverlays)\n        {\n            if (overlay.currentLink.orgPage != this) continue\n            total++\n            if (overlay.onMouseMoveOverlay(x, y)) visibleTotal++\n        }\n\n        if (_hideSelf)\n            if (!total || (total && !visibleTotal))\n            {\n                this.hide(false)\n                return false\n            }\n\n        return true\n    }\n\n\n    showAsOverlayInCurrentPage(orgPage, link, posX, posY, linkParentFixed, disableAnim)\n    {\n        const newParentPage = viewer.currentPage\n\n        if (!this.imageDiv) this.loadImages(true)\n        if(link.panel===undefined) link.panel = this\n\n        // check if we need to hide any other already visible overlay\n        var positionCloned = false\n        const currentOverlays = newParentPage.currentOverlays\n\n        let overlayIndex = currentOverlays.indexOf(this.index)\n        if (overlayIndex < 0)\n        {\n            if ('overlay' !== link.orgPage.type || this.overlayClosePrevOverlay)\n            {\n                // if we show new overlay by clicking inside other overlay then we close the original overlay\n                if ('overlay' == orgPage.type && this.overlayClosePrevOverlay)\n                {\n                    orgPage.hide()\n                } else\n                {\n                    for (let overlay of currentOverlays)\n                    {\n                        if (overlay == this) continue\n                        overlay.hide()\n                    }\n                }\n            }\n        }\n\n        // Show overlay on the new position\n        const div = this.imageDiv\n\n        this.inFixedPanel = linkParentFixed && (this.overlayAlsoFixed || link.panel.isVertScroll)\n        if (!this.parentPage || this.parentPage.id != newParentPage.id || div.hasClass('hidden'))\n        {\n            if(link.panel.isVertScroll){\n                div.removeClass('fixedPanelFloat') \n                div.addClass('divPanel')\n            }\n            else if (this.inFixedPanel)\n            {\n                div.removeClass('divPanel')\n                div.addClass('fixedPanelFloat')\n            } else if (newParentPage.isModal)\n            {\n                //div.removeClass('divPanel')\n                //div.removeClass('fixedPanelFloat')        \n            } else\n            {\n                div.removeClass('fixedPanelFloat') // clear after inFixedPanel\n                div.addClass('divPanel')\n            }\n\n            // click on overlay outside of any hotspots should not close it\n            div.click(function ()\n            {\n                const index = parseInt(this.id.substring(this.id.indexOf(\"_\") + 1))\n                if (index >= 0)\n                {\n                    const page = story.pages[index]\n                    const indexOverlay = page.parentPage.currentOverlays.indexOf(page)\n                    if (indexOverlay == 0) page.hideOtherParentOverlays()\n                }\n                return false\n            })\n\n            if (link.fixedBottomPanel)\n            {\n                // show new overlay aligned to bottom\n                const panel = link.fixedBottomPanel;\n                const panelLink = panel.links[link.index]\n                posX = panel.x + panelLink.rect.x\n                posY = orgPage.height - panel.y - panelLink.rect.y// + panelLink.rect.height)\n\n                // check page right border\n                if ((posX + this.width) > orgPage.width) posX = orgPage.width - this.width\n\n                newParentPage.imageDiv.append(div)\n                div.css('top', \"\")\n                div.css('bottom', posY)\n                div.css('margin-left', posX + \"px\")\n            } else\n            {\n                // \n                if (!this.overlayClosePrevOverlay && !positionCloned && undefined != this.overlayShadowX &&\n                    (\n                        (0 == this.overlayPin) // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                        && (3 == this.overlayPinHotspot) //ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                    )\n                ){\n                    posX -= this.overlayShadowX\n                }\n                if(link.panel.isVertScroll){\n                    posY-=link.panel.y\n                    //if(orgPage.type===\"modal\") posY+=orgPage.\n                }\n\n                this.currentX = posX\n                this.currentY = posY\n                \n                if (link.panel.isVertScroll){\n                    link.panel.imageDiv.append(div)\n                }else if (\"modal\" == orgPage.type) \n                    newParentPage.imageDiv.append(div)\n                div.css('top', posY + \"px\")\n                div.css('margin-left', posX + \"px\")\n            }\n\n            this.show(disableAnim)\n            div.css('z-index', 50 + newParentPage.currentOverlays.length)\n            newParentPage.currentOverlays.push(this)\n            this.parentPage = newParentPage\n            this.currentLink = link\n\n            // Change URL\n            if (undefined != this.overlayRedirectTargetPage)\n            {\n                viewer.refresh_url(this)\n            } else\n            {\n                var extURL = 'o=' + link.index\n                viewer.refresh_url(newParentPage, extURL)\n            }\n\n\n        } else if (Constants.TRIGGER_ON_CLICK == this.overlayByEvent && posX == this.currentX && posY == this.currentY)\n        {//handle only mouse hover\n            // cursor returned back from overlay to hotspot -> nothing to do\n        } else\n        {\n            this.hide()\n            viewer.refresh_url(newParentPage)\n        }\n    }\n\n    loadImages(force = false)\n    {\n        /// check if already loaded images for this page\n        if (!force && this.imageObj != undefined)\n        {\n            return pagerMarkImageAsLoaded()\n        }\n\n        const enableLinks = true\n        var isModal = this.type === \"modal\";\n\n        var content = $('#content')\n        var cssStyle = \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n        if (this.overlayShadow != undefined)\n            cssStyle += \"box-shadow:\" + this.overlayShadow + \";\"\n        if ('overlay' == this.type && this.overlayOverFixed)\n            cssStyle += \"z-index: 50;\"\n        var imageDiv = $('<div>', {\n            class: ('overlay' == this.type) ? \"divPanel\" : \"image_div\",\n            id: \"div_\" + this.index,\n            style: cssStyle\n        });\n        this.imageDiv = imageDiv\n\n\n        // create fixed panel images        \n        for (var panel of this.fixedPanels)\n        {\n            const isBottomFloat = panel.isFloat && !panel.constrains.top && panel.constrains.bottom\n\n            let style = \"'\"\n            let cssClass = \"\"\n\n            if (panel.isVertScroll)\n            {\n                cssClass = \"panelVSCroll divPanel\"\n                switch (panel.vst)\n                {\n                    case Constants.LAYER_VSCROLL_ALWAYS:\n                        cssClass += \" always\"; break\n                    case Constants.LAYER_VSCROLL_NEVER:\n                        cssClass += \" never\"; break\n                }\n                style = `height: ${panel.mskH}px; width: ${panel.width}px; `\n                style += \"margin-left:\" + panel.x + \"px;\"\n                style += \"top:\" + panel.y + \"px;\"\n            } else\n            {\n                style = \"height: \" + panel.height + \"px; width: \" + panel.width + \"px; \"\n\n                if (panel.constrains.top || panel.isFixedisVertScrollDiv || (!panel.constrains.top && !panel.constrains.bottom))\n                {\n                    style += \"top:\" + panel.y + \"px;\"\n                } else if (panel.constrains.bottom)\n                {\n                    style += \"bottom:\" + (this.height - panel.y - panel.height) + \"px;\"\n                }\n                if (panel.constrains.left || panel.isVertScroll || (!panel.constrains.left && !panel.constrains.right))\n                {\n                    style += \"margin-left:\" + panel.x + \"px;\"\n                } else if (panel.constrains.right)\n                {\n                    style += \"margin-left:\" + panel.x + \"px;\"\n                }\n                //\n\n                if (panel.shadow != undefined)\n                    style += \"box-shadow:\" + panel.shadow + \";\"\n\n                // create Div for fixed panel            \n                if (isBottomFloat)\n                {\n                    cssClass = 'fixedBottomPanelFloat'\n                } else if (panel.isFloat)\n                {\n                    cssClass = 'fixedPanelFloat'\n                } else if (panel.isVertScroll)\n                {\n                    cssClass = 'divPanel'\n                } else if (\"top\" == panel.type)\n                {\n                    cssClass = 'fixedPanel fixedPanelTop'\n                } else if (\"left\" == panel.type)\n                {\n                    cssClass = 'fixedPanel'\n                }\n            }\n\n            var divID = panel.divID != '' ? panel.divID : (\"fixed_\" + this.index + \"_\" + panel.index)\n\n            var panelDiv = $(\"<div>\", {\n                id: divID,\n                class: cssClass,\n                style: style\n            })\n            if (isBottomFloat)\n            {\n                const wrap1 = $(\"<div>\", {\n                    style: `position: absolute; z-index:13;`\n                })\n                const wrap2 = $(\"<div>\", {\n                    style: `position: fixed; height:100vh; max-height: ${this.height}px; top:0px;`\n                })\n                panelDiv.appendTo(wrap2);\n                wrap2.appendTo(wrap1)\n                wrap1.appendTo(imageDiv)\n            } else\n            {\n                panelDiv.appendTo(imageDiv);\n            }\n            panel.imageDiv = panelDiv\n\n            // create link div\n            panel.linksDiv = $(\"<div>\", {\n                class: \"linksDiv\",\n                style: \"height: \" + panel.height + \"px; width: \" + panel.width + \"px;\"\n            })\n            panel.linksDiv.appendTo(panel.imageDiv)\n            this._createLinks(panel)\n\n            // add image itself\n            panel.imageObj = this._loadSingleImage(panel.isFloat || panel.isVertScroll ? panel : this, 'img_' + panel.index + \"_\")\n            panel.imageObj.appendTo(panelDiv);\n            if (!this.isDefault) panel.imageObj.css(\"webkit-transform\", \"translate3d(0,0,0)\")\n        }\n\n        // create main content image      \n        {\n            var isModal = this.type === \"modal\";\n            var contentModal = $('#content-modal');\n            imageDiv.appendTo(isModal ? contentModal : content);\n\n            // create link div\n            if (enableLinks)\n            {\n                var linksDiv = $(\"<div>\", {\n                    id: \"div_links_\" + this.index,\n                    class: \"linksDiv\",\n                    style: \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n                })\n                linksDiv.appendTo(imageDiv)\n                this.linksDiv = linksDiv\n\n                this._createLinks(this)\n            }\n        }\n        var img = this._loadSingleImage(this, 'img_')\n        this.imageObj = img\n        img.appendTo(imageDiv)\n    }\n\n    showLayout()\n    {\n        if (undefined == this.layoutCreated)\n        {\n            this.layoutCreated = true\n            this._addLayoutLines(this.imageDiv)\n        }\n    }\n\n    _addLayoutLines(imageDiv)\n    {\n        if (this.type != \"regular\" || undefined == this.layout) return\n\n        var x = this.layout.offset\n        var colWidth = this.layout.columnWidth\n        var colWidthInt = Math.round(this.layout.columnWidth)\n        var gutterWidth = this.layout.gutterWidth\n        for (var i = 0; i < this.layout.numberOfColumns; i++)\n        {\n            var style = \"left: \" + Math.trunc(x) + \"px; top:\" + 0 + \"px; width: \" + colWidthInt + \"px; height:\" + this.height + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutColDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n            x += colWidth + gutterWidth\n        }\n\n        for (var y = 0; y < this.height; y += 5)\n        {\n            var style = \"left: \" + 0 + \"px; top:\" + y + \"px; width: \" + this.width + \"px; height:\" + 1 + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutRowDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n        }\n    }\n\n\n    /*------------------------------- INTERNAL METHODS -----------------------------*/\n\n    // Try to find a first page and link which has a link to this page\n    _getSrcPageAndLink()\n    {\n        let res = null\n        for (var page of story.pages)\n        {\n            res = this._getLinkByTargetPage(page, page.links, this.index)\n            if (res) return res\n            for (var panel of page.fixedPanels)\n            {\n                res = this._getLinkByTargetPage(page, panel.links, this.index)\n                if (res) return res\n            }\n        }\n        return null\n    }\n\n    _getLinkByTargetPage(page, links, targetPageIndex)\n    {\n        const link = links.find(link => link.page == targetPageIndex)\n        if (!link) return null\n        return {\n            link: link,\n            page: page\n        }\n    }\n\n\n\n    _getLinkByIndex(index)\n    {\n        var link = this._getLinkByIndexInLinks(index, this.links)\n        if (link != null) return link\n        for (var panel of this.fixedPanels)\n        {\n            link = this._getLinkByIndexInLinks(index, panel.links)\n            if (link != null) return link\n        }\n        return null\n    }\n\n    _getLinkByIndexInLinks(index, links)\n    {\n        var found = links.find(function (el)\n        {\n            return el.index == index\n        })\n        return found != undefined ? found : null\n    }\n\n\n    _loadSingleImage(sizeSrc, idPrefix)\n    {\n        var hasRetinaImages = story.hasRetina\n        var imageURI = hasRetinaImages && viewer.isHighDensityDisplay() ? sizeSrc.image2x : sizeSrc.image;\n        var unCachePostfix = \"V_V_V\" == story.docVersion ? \"\" : (\"?\" + story.docVersion)\n\n        var img = $('<img/>', {\n            id: idPrefix + this.index,\n            class: \"pageImage\",\n            src: encodeURIComponent(viewer.files) + '/' + encodeURIComponent(imageURI) + unCachePostfix,\n        }).attr('width', sizeSrc.width).attr('height', sizeSrc.height);\n\n        img.preload(function (perc, done)\n        {\n            //console.log(perc, done);\n        });\n        return img;\n    }\n\n    // panel: ref to panel or this\n    _createLinks(panel)\n    {\n        var linksDiv = panel.linksDiv\n\n        for (var link of panel.links)\n        {\n            link.panel = panel\n            //\n            let x = link.rect.x + (link.isParentFixed ? panel.x : 0)\n            let y = link.rect.y + (link.isParentFixed ? panel.y : 0)\n\n            var a = $(\"<a>\", {\n                lpi: this.index,\n                li: link.index,\n                lppi: \"fixedPanels\" in panel ? -1 : panel.index,\n                lpx: x,\n                lpy: y\n            })\n\n            var eventType = Constants.TRIGGER_ON_CLICK\n\n            if ('page' in link)\n            {\n                var destPageIndex = viewer.getPageIndex(parseInt(link.page))\n                var destPage = story.pages[destPageIndex];\n                //\n                if (link.triggerOnHover)\n                {\n                    eventType = Constants.TRIGGER_ON_HOVER\n                    destPage.overlayByEvent = Constants.TRIGGER_ON_HOVER\n                } else if ('overlay' == destPage.type)\n                {\n                    eventType = destPage.overlayByEvent\n                }\n            }\n\n            if (EVENT_HOVER == eventType)\n            { // for Mouse over event\n                a.mouseenter(handleLinkEvent)\n                if (\n                    0 == destPage.overlayPin // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                    && 3 == destPage.overlayPinHotspot // ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                )\n                {\n                } else\n                {\n                    // need to pass click event to overlayed layers\n                    a.click(function (e)\n                    {\n                        if (undefined == e.originalEvent) return\n                        var nextObjects = document.elementsFromPoint(e.originalEvent.x, e.originalEvent.y);\n                        for (var i = 0; i < nextObjects.length; i++)\n                        {\n                            var obj = nextObjects[i].parentElement\n                            if (!obj || obj.nodeName != 'A' || obj == this) continue\n                            $(obj).trigger('click', e);\n                            return\n                        }\n                    })\n                }\n            } else\n            { // for On click event\n                a.click(handleLinkEvent)\n            }\n\n            a.appendTo(linksDiv)\n\n            link.a = a\n\n            var style = \"left: \" + link.rect.x + \"px; top:\" + link.rect.y + \"px; width: \" + link.rect.width + \"px; height:\" + link.rect.height + \"px; \"\n            var linkDiv = $(\"<div>\", {\n                class: (EVENT_HOVER == eventType ? \"linkHoverDiv\" : \"linkDiv\") + (story.disableHotspots ? \"\" : \" linkDivHighlight\"),\n            }).attr('style', style)\n            linkDiv.appendTo(a)\n\n            link.div = linkDiv\n\n        }\n    }\n}\n\n//\n// customData:\n//  x,y,pageIndex\nfunction handleLinkEvent(event)\n{\n    var customData = viewer[\"customEvent\"]\n\n    if (viewer.linksDisabled) return false\n\n    let currentPage = viewer.currentPage\n    let orgPage = customData ? story.pages[customData.pageIndex] : story.pages[$(this).attr(\"lpi\")]\n\n    const linkIndex = customData ? customData.linkIndex : $(this).attr(\"li\")\n    const link = orgPage._getLinkByIndex(linkIndex)\n\n    if (link.page != undefined)\n    {\n        var destPageIndex = parseInt(link.page)\n        var linkParentFixed = \"overlay\" != orgPage.type ? link.isParentFixed : orgPage.inFixedPanel\n\n\n        // title = story.pages[link.page].title;                   \n        var destPage = story.pages[destPageIndex]\n        if (!destPage) return\n\n\n        if (('overlay' == destPage.type || 'modal' == destPage.type) && destPage.overlayRedirectTargetPage != undefined)\n        {\n\n            // Change base page\n            viewer.goTo(destPage.overlayRedirectTargetPage, false, link)\n            currentPage = viewer.currentPage\n            orgPage = viewer.currentPage\n        }\n\n        if ('overlay' == destPage.type)\n        {\n\n            var orgLink = {\n                orgPage: orgPage,\n                index: linkIndex,\n                fixedPanelIndex: parseInt($(this).attr(\"lppi\")),\n                this: $(this),\n                x: customData ? customData.x : parseInt($(this).attr(\"lpx\")),\n                y: customData ? customData.y : parseInt($(this).attr(\"lpy\")),\n                width: link.rect.width,\n                height: link.rect.height,\n            }\n            if(orgLink.fixedPanelIndex >= 0){\n                orgLink.panel = currentPage.fixedPanels[orgLink.fixedPanelIndex]\n            }\n\n            // check if link in fixed panel aligned to bottom\n            if (linkParentFixed && destPage.overlayAlsoFixed && orgLink.fixedPanelIndex >= 0 && currentPage.fixedPanels[orgLink.fixedPanelIndex].constrains.bottom && !currentPage.fixedPanels[orgLink.fixedPanelIndex].constrains.top)\n            {\n                orgLink.fixedBottomPanel = currentPage.fixedPanels[orgLink.fixedPanelIndex]\n            } else\n            { // clicked not from fixed panel           \n                const pinHotspot = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT == destPage.overlayPin\n                const pinPage = Constants.ARTBOARD_OVERLAY_PIN_PAGE == destPage.overlayPin\n\n                var pageX = 0\n                var pageY = 0\n\n                if (pinHotspot)\n                {\n                    //////////////////////////////// PIN TO HOTSPOT ////////////////////////////////\n                    // clicked from some other overlay\n                    if ('overlay' == orgPage.type)\n                    {\n                        orgLink.x += orgPage.currentX\n                        orgLink.y += orgPage.currentY\n                    }\n                    pageX = orgLink.x\n                    pageY = orgLink.y\n\n                    var offsetX = pinHotspot\n                        && (\n                            Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot\n                        )\n                        ? 5 : 0\n\n                    if (destPage.overlayClosePrevOverlay && ('overlay' == orgPage.type))\n                    {\n                        pageX = orgPage.currentX\n                        pageY = orgPage.currentY\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot)\n                    {\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot)\n                    {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot)\n                    {\n                        pageX += orgLink.width - destPage.width\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT == destPage.overlayPinHotspot)\n                    {\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER == destPage.overlayPinHotspot)\n                    {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        //pageY -= destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT == destPage.overlayPinHotspot)\n                    {\n                        pageX += orgLink.width - destPage.width\n                        //pageY = pageY - destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT == destPage.overlayPinHotspot)\n                    {\n                        pageX += orgLink.width\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER == destPage.overlayPinHotspot)\n                    {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY -= destPage.height\n                    } else\n                    {\n                        const srcLink = orgPage.links[orgLink.index]\n                        if (srcLink && srcLink.pageOverlayPinHotspot === Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_RELATIVE)\n                        {\n                            pageX += srcLink.pageOverlayPinHotspotX\n                            pageY += srcLink.pageOverlayPinHotspotY\n\n                        }\n                    }\n\n                    // check page right side\n                    if (!pinHotspot || (Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT != destPage.overlayPinHotspot))\n                    {\n                        const fullWidth = destPage.width + offsetX // + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        if ((pageX + fullWidth) > currentPage.width)\n                            pageX = currentPage.width - fullWidth\n\n                        /*if(linkPosX < (offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0))){  \n                            linkPosX = offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        }*/\n                    }\n                } else\n                {\n                    //////////////////////////////// PIN TO PAGE ////////////////////////////////\n\n                    if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT == destPage.overlayPinPage)\n                    {\n                        pageX = 0\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER == destPage.overlayPinPage)\n                    {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT == destPage.overlayPinPage)\n                    {\n                        pageX = currentPage.width - destPage.width\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_CENTER == destPage.overlayPinPage)\n                    {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = parseInt(currentPage.height / 2) - parseInt(destPage.height / 2)\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT == destPage.overlayPinPage)\n                    {\n                        pageX = 0\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER == destPage.overlayPinPage)\n                    {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT == destPage.overlayPinPage)\n                    {\n                        pageX = currentPage.width - destPage.width\n                        pageY = currentPage.height - destPage.height\n                    }\n\n                }\n\n                if (pageX < 0) pageX = 0\n                if (pageY < 0) pageY = 0\n            }\n\n            if (destPage.visible)\n            {\n                const sameLink = destPage.currentLink.index == orgLink.index\n                if (sameLink)\n                {\n                    destPage.hide()\n                } else\n                {\n                    destPage.hide(false, true) // hide without transition animation\n                    if (orgPage != destPage)\n                        destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed, true)\n                }\n                return false\n            }\n            destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed)\n            return false\n        } else\n        {\n            // close modal if some link inside a modal opens the same modal\n            if (destPageIndex == currentPage.index && currentPage.isModal)\n            {\n                viewer.closeModal()\n                return false\n            }\n\n            // check if we need to close current overlay\n            currentPage.hideCurrentOverlays()\n\n            viewer.goTo(parseInt(destPageIndex), true, link)\n            return false\n        }\n    } else if (link.action != null && link.action == 'back')\n    {\n        //title = \"Go Back\";\n        viewer.currentPage.hideCurrentOverlays()\n        viewer.goBack()\n        return false\n    } else if (link.url != null)\n    {\n        //title = link.url;\n        viewer.currentPage.hideCurrentOverlays()\n        var target = event.metaKey ? \"_blank\" : link.target\n        window.open(link.url, target != undefined ? target : \"_self\")\n        return false\n        //document.location = link_url\n        //target = link.target!=null?link.target:null;\t\t\n    }\n\n    // close last current overlay if it still has parent\n    if ('overlay' == orgPage.type && undefined != orgPage.parentPage)\n    {\n        orgPage.hide()\n    }\n\n    return false\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/js/other/jquery.hotkeys.js",
    "content": "/*jslint browser: true*/\n/*jslint jquery: true*/\n\n/*\n * jQuery Hotkeys Plugin\n * Copyright 2010, John Resig\n * Dual licensed under the MIT or GPL Version 2 licenses.\n *\n * Based upon the plugin by Tzury Bar Yochay:\n * https://github.com/tzuryby/jquery.hotkeys\n *\n * Original idea by:\n * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/\n */\n\n/*\n * One small change is: now keys are passed by object { keys: '...' }\n * Might be useful, when you want to pass some other data to your handler\n */\n\n(function(jQuery) {\n\n  jQuery.hotkeys = {\n    version: \"0.2.0\",\n\n    specialKeys: {\n      8: \"backspace\",\n      9: \"tab\",\n      10: \"return\",\n      13: \"return\",\n      16: \"shift\",\n      17: \"ctrl\",\n      18: \"alt\",\n      19: \"pause\",\n      20: \"capslock\",\n      27: \"esc\",\n      32: \"space\",\n      33: \"pageup\",\n      34: \"pagedown\",\n      35: \"end\",\n      36: \"home\",\n      37: \"left\",\n      38: \"up\",\n      39: \"right\",\n      40: \"down\",\n      45: \"insert\",\n      46: \"del\",\n      59: \";\",\n      61: \"=\",\n      96: \"0\",\n      97: \"1\",\n      98: \"2\",\n      99: \"3\",\n      100: \"4\",\n      101: \"5\",\n      102: \"6\",\n      103: \"7\",\n      104: \"8\",\n      105: \"9\",\n      106: \"*\",\n      107: \"+\",\n      109: \"-\",\n      110: \".\",\n      111: \"/\",\n      112: \"f1\",\n      113: \"f2\",\n      114: \"f3\",\n      115: \"f4\",\n      116: \"f5\",\n      117: \"f6\",\n      118: \"f7\",\n      119: \"f8\",\n      120: \"f9\",\n      121: \"f10\",\n      122: \"f11\",\n      123: \"f12\",\n      144: \"numlock\",\n      145: \"scroll\",\n      173: \"-\",\n      186: \";\",\n      187: \"=\",\n      188: \",\",\n      189: \"-\",\n      190: \".\",\n      191: \"/\",\n      192: \"`\",\n      219: \"[\",\n      220: \"\\\\\",\n      221: \"]\",\n      222: \"'\"\n    },\n\n    shiftNums: {\n      \"`\": \"~\",\n      \"1\": \"!\",\n      \"2\": \"@\",\n      \"3\": \"#\",\n      \"4\": \"$\",\n      \"5\": \"%\",\n      \"6\": \"^\",\n      \"7\": \"&\",\n      \"8\": \"*\",\n      \"9\": \"(\",\n      \"0\": \")\",\n      \"-\": \"_\",\n      \"=\": \"+\",\n      \";\": \": \",\n      \"'\": \"\\\"\",\n      \",\": \"<\",\n      \".\": \">\",\n      \"/\": \"?\",\n      \"\\\\\": \"|\"\n    },\n\n    // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url\n    textAcceptingInputTypes: [\n      \"text\", \"password\", \"number\", \"email\", \"url\", \"range\", \"date\", \"month\", \"week\", \"time\", \"datetime\",\n      \"datetime-local\", \"search\", \"color\", \"tel\"],\n\n    // default input types not to bind to unless bound directly\n    textInputTypes: /textarea|input|select/i,\n\n    options: {\n      filterInputAcceptingElements: true,\n      filterTextInputs: true,\n      filterContentEditable: true\n    }\n  };\n\n  function keyHandler(handleObj) {\n    if (typeof handleObj.data === \"string\") {\n      handleObj.data = {\n        keys: handleObj.data\n      };\n    }\n\n    // Only care when a possible input has been specified\n    if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== \"string\") {\n      return;\n    }\n\n    var origHandler = handleObj.handler,\n      keys = handleObj.data.keys.toLowerCase().split(\" \");\n\n    handleObj.handler = function(event) {\n      //      Don't fire in text-accepting inputs that we didn't directly bind to\n      if (this !== event.target &&\n        (jQuery.hotkeys.options.filterInputAcceptingElements &&\n          jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||\n          (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||\n          (jQuery.hotkeys.options.filterTextInputs &&\n            jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {\n        return;\n      }\n\n      var special = event.type !== \"keypress\" && jQuery.hotkeys.specialKeys[event.which],\n        character = String.fromCharCode(event.which).toLowerCase(),\n        modif = \"\",\n        possible = {};\n\n      jQuery.each([\"alt\", \"ctrl\", \"shift\"], function(index, specialKey) {\n\n        if (event[specialKey + 'Key'] && special !== specialKey) {\n          modif += specialKey + '+';\n        }\n      });\n\n      // metaKey is triggered off ctrlKey erronously\n      if (event.metaKey && !event.ctrlKey && special !== \"meta\") {\n        modif += \"meta+\";\n      }\n\n      if (event.metaKey && special !== \"meta\" && modif.indexOf(\"alt+ctrl+shift+\") > -1) {\n        modif = modif.replace(\"alt+ctrl+shift+\", \"hyper+\");\n      }\n\n      if (special) {\n        possible[modif + special] = true;\n      }\n      else {\n        possible[modif + character] = true;\n        possible[modif + jQuery.hotkeys.shiftNums[character]] = true;\n\n        // \"$\" can be triggered as \"Shift+4\" or \"Shift+$\" or just \"$\"\n        if (modif === \"shift+\") {\n          possible[jQuery.hotkeys.shiftNums[character]] = true;\n        }\n      }\n\n      for (var i = 0, l = keys.length; i < l; i++) {\n        if (possible[keys[i]]) {\n          return origHandler.apply(this, arguments);\n        }\n      }\n    };\n  }\n\n  jQuery.each([\"keydown\", \"keyup\", \"keypress\"], function() {\n    jQuery.event.special[this] = {\n      add: keyHandler\n    };\n  });\n\n})(jQuery || this.jQuery || window.jQuery);\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/resources/animations.css",
    "content": "@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/resources/viewer-center.css",
    "content": "#container, #content {\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/resources/viewer-top.css",
    "content": "#container{\n\ttext-align: center;\n    margin: 0 auto;\t\n    overflow: auto;\n}\n\n#content,#map{\n\ttext-align: center;\n    margin: 0 auto;\t   \n    /*width:100%;*/\n    position:absolute;\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: 0 auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/pp-viewer/viewer/resources/viewer.css",
    "content": "/*@import \"https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css\";*/\n\n/* Tooltip container */\n.tooltip {\n    position: relative;\n    display: inline-block;\n    /*border-bottom: 1px dotted black; */\n    cursor: pointer;\n}\n\n/* Tooltip text */\n.tooltip .tooltiptext {\n    visibility: hidden;\n    background-color: var(--color-primary);\n    color: var(--color-background);\n    text-align: center;\n    padding: 10px 10px;\n    border-radius: 4px;\n\n    /* Position the tooltip text - see examples below! */\n    position: absolute;\n    z-index: 1;\n}\n\n/* Show the tooltip text when you mouse over the tooltip container */\n.tooltip:hover .tooltiptext {\n    visibility: visible;\n}\n\n#content .image_div {\n    margin: 0 auto;\n    position: absolute;\n}\n\n#contentModall .image_div {\n    margin: 0 auto;\n    /* Commented to fix long modals\n    position: absolute;\n    */\n}\n\n.image_div img {\n    /*-webkit-transform:translate3d(0,0,0);*/\n}\n\n/*** MAPS ***/\n.linksDiv {\n    width: 100%;\n    position: absolute;\n    z-index: 2;\n}\n\n.linkDiv {\n    position: absolute;\n    cursor: pointer;\n    opacity: 0;\n    transition: opacity 0.5s;\n    background-color: #FFC400;\n    pointer-events: auto;\n}\n\n.linkHoverDiv {\n    position: absolute;\n    opacity: 0;\n    cursor: pointer;\n    transition: opacity 0.5s;\n    background-color: #FFC400;\n    pointer-events: auto;\n}\n\n.linkDivHighlight:hover {\n    opacity: 0.2;\n    transition: opacity 0.5s;\n}\n\n.contentLinksVisible .linkDiv {\n    opacity: 0.4;\n    transition: opacity 0.5s;\n}\n\n/* SEARCH */\n.searchFocusedResultDiv {\n    position: absolute;\n    outline: 2px solid var(--color-primary);\n    opacity: 0.3;\n    background-color: yellow;\n    box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);\n}\n\n.searchResultDiv {\n    position: absolute;\n    outline: 2px solid var(--color-accent);\n    background-color: white;\n    opacity: 0.3;\n}\n\n/************ SYMBOLS ******/\n.symbolLink {}\n\n.modalSymbolLink {}\n\n.symbolDiv {\n    position: absolute;\n    display: none;\n    z-index: 30;\n}\n\n.contentSymbolsVisible .symbolDiv:hover {\n    opacity: 1;\n    outline: 1px dashed red;\n    outline-offset: -1px;\n    background-color: rgba(255, 0, 0, .05);\n    transition: all 0.3s;\n}\n\n.contentSymbolsVisible .symbolDiv {\n    opacity: .3;\n    display: block;\n    cursor: pointer;\n    outline: 1px solid red;\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n.contentSymbolsVisible .symbolDiv.exp:hover {\n    outline: 2px solid orange;\n}\n\n.contentSymbolsVisible .symbolDiv.exp {\n    outline: 2px solid orange;\n}\n\n.svBorderLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 30;\n}\n\n.contentSymbolsVisible .svBorderLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px dashed red;\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n\n.svMarginLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 31;\n}\n\n.contentSymbolsVisible .svMarginLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px solid var(--color-accent);\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n.svMarginValueDiv {\n    position: absolute;\n    display: none;\n    z-index: 32;\n    text-align: center;\n}\n\n.contentSymbolsVisible .svMarginValueDiv {\n    display: block;\n    cursor: pointer;\n    border-radius: 4px;\n    height: 16px;\n    width: 30px;\n    line-height: 16px;\n    font-weight: 500;\n    background-color: var(--color-accent);\n    transition: all 0.3s;\n    font-size: 10px;\n    color: white;\n}\n\n.viewer {\n    width: 100%;\n    overflow-y: auto;\n    padding-bottom: 20px;\n}\n\n\n#info_viewer .head {\n    padding-bottom: 20px;\n    width: 100%;\n}\n\n\n#info_viewer .record {\n    padding-bottom: 20px;\n    width: 100%;\n}\n\nselect {\n    height: 32px;\n    width: 100%;\n    font-size: var(--font-small);\n    color: var(--color-primary);\n    border: none;\n    border-radius: 5px;\n    background: var(--color-hover);\n    cursor: pointer;\n    text-indent: 4px;\n    transition: all .12s;\n\n    /* reset */\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n    -webkit-appearance: none;\n    -moz-appearance: none;\n\n    padding: 0px 8px;\n}\n\nselect:hover {\n    background: var(--color-hover-bright);\n    /* color: white; */\n    transition: all .2s;\n}\n\nselect:focus {\n    outline: none;\n}\n\n.checkbox-container {\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    position: relative;\n    height: 36px;\n    font-size: 14px;\n}\n\n.checkbox-container label {\n    background-color: var(--color-active);\n    border-radius: 20px;\n    display: inline-block;\n    position: relative;\n    transition: all 0.2s ease-out;\n    width: 28px;\n    height: 16px;\n    z-index: 2;\n    cursor: pointer;\n}\n\n#gallery-modal .checkbox-container label {\n    background-color: rgba(255, 255, 255, .12);\n}\n\n.checkbox-container label::after {\n    content: ' ';\n    background-color: rgb(255, 255, 255);\n    border-radius: 50%;\n    position: absolute;\n    top: 2px;\n    left: 2px;\n    transform: translateX(0);\n    transition: transform 0.12s linear;\n    width: 12px;\n    height: 12px;\n    z-index: 3;\n    /* box-shadow: var(--shadow1); */\n    transition: all .2s;\n}\n\n.checkbox-container label:hover::after {\n    transition: all .12s;\n}\n\n.checkbox-container input {\n    visibility: hidden;\n    position: absolute;\n    z-index: 2;\n}\n\n.checkbox-container input:checked+label::after {\n    transform: translateX(calc(100% + 0.5px));\n    background-color: #fff;\n}\n\n.checkbox-container input:checked+label {\n    background-color: var(--color-accent);\n}\n\n#gallery-modal .checkbox-container input:checked+label {\n    background-color: var(--color-accent);\n}\n\n.checkbox-container .checkbox-label {\n    margin-left: 12px;\n}\n\n#symbol_viewer .checkbox-container {\n    padding: 16px 20px 0px 20px;\n}\n\n#gallery-modal .checkbox-container {\n    margin-right: 40px;\n}\n\n.viewer .title {\n    height: 56px;\n    display: flex;\n    align-items: center;\n    position: fixed;\n    box-sizing: border-box;\n    width: 400px;\n    padding: 0 20px;\n    background: var(--color-background);\n    font-weight: var(--font-weight-bold);\n    border-bottom: solid 1px var(--color-border);\n    z-index: 10;\n}\n\n#comments_viewer_content {\n    padding: 72px 20px 0 20px;\n}\n\n#symbol_viewer_content .block {\n    padding: 16px 20px 0 20px;\n}\n\n#symbol_viewer_content .block.twoColumn {\n    display: flex;\n}\n\n.tokenName {\n    /* color: aquamarine; */\n    color: var(--color-accent);\n    white-space: nowrap;\n}\n\n.tokenValue {\n    color: var(--color-secondary);\n    margin-left: 10px;\n}\n\n.twoColumn div {\n    width: 50%;\n}\n\n#symbol_viewer_content .label {\n    color: var(--color-secondary);\n}\n\n#symbol_viewer_content .value {\n    padding-top: 8px;\n}\n\n#symbol_viewer_content .value.code {\n    font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n    font-size: 12px;\n    line-height: 18px;\n}\n\n.svlink {\n    color: var(--color-accent);\n}\n\n#symbol_viewer_content .icon {\n    font-family: \"Font Awesome\";\n    font-size: 60px;\n    line-height: 60px;\n    font-weight: 400;\n}\n\n#symbol_viewer_content .icon.solid {\n    font-weight: 900;\n}\n\n#sv_content {\n    font-size: 20px;\n    line-height: 18px;\n}\n\n\n#exp_viewer .pages .page {\n    padding-bottom: 10px;\n    color: var(--color-accent);\n}\n\n#exp_viewer .widgets .widget {\n    padding-bottom: 10px;\n}\n\n#exp_viewer .widgets .widget.highlight {\n    background-color: var(--color-active);\n}\n\n#exp_viewer .widgets .widget .link {\n    color: var(--color-accent);\n    font-weight: normal;\n}\n\n#exp_viewer .widgets .page {\n    margin-left: 20px;\n    color: var(--color-accent);\n    font-size: 12px;\n    cursor: pointer;\n}\n\n#exp_viewer .layer {\n    margin-left: 20px;\n    padding-bottom: 10px;\n    color: var(--color-primary);\n    font-size: 12px;\n}\n\n#exp_viewer .counter {\n    color: var(--color-secondary);\n    font-size: 12px;\n}\n\n\n#exp_viewer #controls {\n    display: grid;\n    grid-template-columns: 80px 1fr;\n    grid-row-gap: 5px;\n}\n\n\n#exp_viewer #controls .label {\n    width: 100px;\n}\n\n/* #symbol_viewer_content .head {\n  color: var(--color-secondary);\n  border-top: solid 1px var(--color-border);\n}\n\n#info_viewer .head {\n  font-weight: 900;\n} */\n\n/******************/\n/************ LAYOUT ******/\n.layouLineDiv {\n    position: absolute;\n    opacity: 0;\n    transition: opacity 0.5s;\n    z-index: 30;\n    pointer-events: none;\n}\n\n.layoutColDiv {\n    background-color: #FF0000;\n}\n\n.layoutRowDiv {\n    background-color: #000000;\n}\n\n.contentLayoutVisible .layouLineDiv {\n    opacity: 0.05;\n    transition: opacity 0.5s;\n    pointer-events: none;\n}\n\n/******************/\n.map {\n    z-index: 9;\n    position: absolute;\n}\n\n.contentModal {\n    z-index: 50;\n    position: fixed;\n    margin: auto;\n    max-height: 100%;\n    align-items: center;\n}\n\n.contentModal>div {\n    pointer-events: auto;\n}\n\n.contentModal .image_div {\n    z-index: 50;\n}\n\n#content-shadow.shadow {\n    background-color: rgba(5, 4, 4, 0.7);\n}\n\n#content-shadow.no-shadow {\n    background-color: transparent;\n}\n\n/*\n#content-modal>div {\n\tmargin-top: 50px auto 50px;\n\tmargin-bottom: 50px auto 50px;\n}\n*/\n/* GALLERY */\n#gallery-modal {\n    display: flex;\n    justify-content: center;\n    position: fixed;\n    overflow: auto;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    width: 100vw;\n    height: 100vh;\n    background-color: var(--color-background-gallery);\n    z-index: 100;\n}\n\n#gallery {\n    /*width: 100%;*/\n    margin: 80px 0px;\n}\n\n.gallery-grid {\n    width: calc(100% - 80px);\n    max-width: 1200px;\n    margin: 80px 40px;\n}\n\n#gallery-header {\n    display: flex;\n    position: fixed;\n    justify-content: center;\n    top: 0;\n    height: 80px;\n    width: 100%;\n    color: white;\n    font-size: 24px;\n    line-height: 32px;\n    background-color: var(--color-background-gallery-90);\n    z-index: 102;\n}\n\n#gallery-header-container {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    /* max-width: 1200px; */\n    width: 100%;\n    margin: 0 40px;\n}\n\n#gallery-header-container #title {\n    text-align: left;\n}\n\n#gallery-header-container #title div {\n    white-space: nowrap;\n    overflow-x: hidden;\n    text-overflow: ellipsis;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n}\n\n#map-controls {\n    position: fixed;\n    bottom: 0;\n    width: 100%;\n    height: 56px;\n    display: flex;\n    justify-content: center;\n    color: white;\n    background-color: var(--color-background-gallery-90);\n    z-index: 103;\n}\n\n#map-controls-container {\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n    /* max-width: 1200px; */\n    width: 100%;\n    margin: 0 40px;\n}\n\n#gallery-header-container #screensamount {\n    font-size: 14px;\n    line-height: 20px;\n    font-weight: 300;\n    opacity: .7;\n}\n\n#search {\n    max-width: 280px;\n    flex-grow: 1;\n    display: flex;\n    height: 32px;\n    padding: 0 12px;\n    border-radius: 5px;\n    background: rgba(255, 255, 255, 0.12);\n}\n\n#searchInput {\n    width: 200px;\n    height: 30px;\n    font-size: 14px;\n    font-weight: 300;\n    letter-spacing: 0.2px;\n    color: white;\n    border: none;\n    background: none;\n}\n\n#searchInput:focus {\n    outline: none;\n}\n\n#searchInput::placeholder {\n    color: white;\n    opacity: .5;\n}\n\n#right {\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n}\n\n#title,\n#search,\n#right {\n    width: calc(100% / 3);\n}\n\n#gallery-header-container #closebtn {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    min-width: 48px;\n    height: 48px;\n    margin-right: -16px;\n    cursor: pointer;\n    opacity: .7;\n    border-radius: 100%;\n    transition: all .2s;\n    background: rgba(0, 0, 0, 0);\n}\n\n#gallery-header-container #closebtn:hover {\n    opacity: 1;\n    background: rgba(0, 0, 0, .18);\n    transition: all .24s;\n}\n\n.gallery-grid {\n    justify-content: center;\n    margin: 0 -32px 80px -32px;\n}\n\n#gallery #grid {\n    /* display: -webkit-box; */\n    display: flex;\n    justify-content: center;\n    flex-wrap: wrap;\n    /* -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal; */\n    flex-direction: row;\n    margin: 0 -16px 80px -16px;\n    padding-bottom: 40px;\n}\n\n.galleryArtboardAbs {\n    position: absolute;\n    border-radius: 5px;\n    background: none;\n    box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n    cursor: pointer;\n    transition: transform .24s, box-shadow .24s;\n}\n\n.galleryArtboardAbsHidden {\n    opacity: 0.2;\n}\n\n.galleryArtboardLabelAbs {\n    position: absolute;\n    margin-top: 8px;\n    color: var(--color-secondary);\n    font-size: 12px;\n}\n\n.galleryArtboardAbs image {}\n\n#gallery #grid svg {\n    z-index: 61;\n    pointer-events: none;\n    position: absolute;\n    left: 40px;\n    top: 80px;\n}\n\n#gallery #grid svg circle {\n    z-index: 62;\n    stroke: #F89000;\n    stroke-width: 1;\n    fill: white;\n}\n\n#gallery #grid svg path {\n    stroke: #F89000;\n    stroke-width: 1;\n    fill: none;\n    z-index: 61;\n}\n\n/* #gallery-header-container .checkbox-container{\n    width:100%;\n} */\n\n#map-controls-container .mapZoom {\n    width: 100px;\n}\n\n#map-controls-container .mapResetZoom {\n    color: white;\n    font-size: 14px;\n    cursor: pointer;\n    margin-left: 16px;\n}\n\n#commenting {\n    z-index: 100;\n    position: fixed;\n    overflow: auto;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    width: 100vw;\n    height: 100vh;\n    display: grid;\n    margin-left: auto;\n    margin-right: auto;\n    margin-top: auto;\n    margin-bottom: auto;\n    align-items: center;\n    background-color: rgba(255, 255, 255, 0.9);\n}\n\n.grid-cell {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    width: calc((1200px - 16px*6)/4);\n    height: 212px;\n    overflow: hidden;\n    margin: 16px;\n    border-radius: 5px;\n    background: #fff;\n    box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n    cursor: pointer;\n    transition: transform .24s, box-shadow .24s;\n}\n\n.grid-cell:hover,\n.galleryArtboardAbs:hover {\n    transition: all .2s;\n    transform: translateY(-2px);\n    box-shadow: 0 0.3125rem 2rem 0 rgba(0, 0, 0, 0.3);\n}\n\n/*.grid-cell-wrapper{\n\twidth: 100%;\n\tdisplay: inline-block;\n\tposition: relative;\n}*/\n/*.grid-cell-main{\n\tposition: absolute;\n\ttop:0;\n\tbottom:0;\n\tleft:0;\n\tright:0;\n}*/\n.grid-cell img {\n    width: 100%;\n    /*top:0;*/\n    /*position: absolute;*/\n}\n\n\n#gallery #grid .groupTitle {\n    position: absolute;\n    width: 100%;\n    height: 30px;\n    color: var(--color-secondary);\n    font-size: 20px;\n    text-align: left;\n}\n\n#gallery #grid .grid-cell .div-page-title {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n}\n\n.grid-cell span {\n    padding: 0 16px;\n    height: 56px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    align-self: flex-end;\n    font-size: 14px;\n    color: white;\n    font-weight: 400;\n    background-color: var(--color-background-gallery-90);\n}\n\na,\na:focus {\n    outline: none;\n    color: var(--color-link);\n    text-decoration: none;\n    font-weight: bold;\n}\n\n.transparent {\n    visibility: hidden;\n    opacity: 0;\n}\n\n.hidden {\n    display: none !important;\n}\n\n.hotspots-off {\n    pointer-events: none;\n}\n\n/* VIEWER UI */\n:root {\n\n    /* color */\n    --color-primary: #404B58;\n    --color-secondary: #989EA5;\n    --color-accent: #007AFF;\n    --color-hover: #F4F5F6;\n    --color-hover-bright: var(--color-active);\n    --color-active: #EAEDF0;\n    --color-border: var(--color-active);\n    --color-link: #568AF2;\n    --color-background: #FFF;\n    --color-background-gallery: var(--color-primary);\n    --color-background-gallery-90: rgba(64, 75, 88, 0.90);\n\n    /* font size */\n    --font-regular: 14px;\n    --font-small: 12px;\n\n    /* font weight  */\n    --font-weight-regular: 300;\n    --font-weight-bold: 700;\n\n    /* line height */\n    --line-height: 20px;\n\n    /* shadow */\n    --shadow1: 0 1px 3px 0 rgba(0, 0, 0, 0.20);\n    --shadow2: 0 0 1px 0 rgba(0, 0, 0, 0.10), 0 2px 8px 0 rgba(0, 0, 0, 0.10);\n\n    /* border */\n    --border: 1px solid rgba(73, 84, 96, 0.06);\n\n    /* border radius */\n    --border-radius: 5px;\n    --border-radius-circle: 100%;\n\n}\n\n/* NAVIGATION */\nbody {\n    font-size: var(--font-regular);\n    line-height: var(--line-height);\n    color: var(--color-primary);\n    margin: 0px;\n    padding: 0px;\n    font-family: -apple-system, BlinkMacSystemFont,\n        \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif,\n        \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n}\n\n.containerSVG {\n    display: none;\n}\n\n.svgIcon {\n    width: 24px;\n    height: 24px;\n    fill: var(--color-primary);\n}\n\n#sidebar {\n    position: fixed;\n    left: 0;\n    bottom: 0;\n    display: flex;\n    text-align: left;\n    z-index: 60;\n    background: var(--color-background);\n    border-left: var(--border);\n    box-shadow: var(--shadow2);\n}\n\n.nav {\n    position: fixed;\n    left: 0;\n    bottom: 0;\n    width: calc(100% - 24px);\n    display: flex;\n    justify-content: space-between;\n    margin: 12px;\n    user-select: none;\n    pointer-events: none;\n    z-index: 60;\n}\n\n.navLeft,\n.navRight {\n    display: flex;\n}\n\n.navLeft,\n.nav #pageComments,\n.nav #experimental {\n    pointer-events: all;\n}\n\n.navCenter {\n    display: flex;\n    justify-content: center;\n}\n\n.nav .btnMenu,\n.nav .navPreviewNext,\n.nav #pageComments,\n.nav #experimental,\n.nav .infoViewerMode,\n.nav .pageName {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 36px;\n    background: var(--color-background);\n    border-radius: 36px;\n    box-shadow: var(--shadow1);\n    align-self: center;\n    opacity: .8;\n}\n\n.nav .infoViewerMode {\n    pointer-events: all;\n}\n\n\n.nav #pageComments #counter {\n    margin-left: 5px;\n}\n\n.btnMenu,\n.navPreviewNext {\n    margin-right: 12px;\n}\n\n.btnMenu,\n.nav #pageComments,\n.nav #experimental,\n.btnPreview,\n.btnNext {\n    cursor: pointer;\n}\n\n.navPreviewNext svg,\n.btnMenu svg,\n.navPreviewNext svg {\n    padding: 6px;\n}\n\n.navPreviewNext {\n    overflow: hidden;\n}\n\n.navPreviewNext div {\n    height: 36px;\n}\n\n.nav .pageName,\n.nav .infoViewerMode {\n    padding: 0 12px;\n}\n\n.nav #pageComments {\n    padding: 0 12px;\n}\n\n.btnMenu:hover,\n.nav #pageComments:hover,\n.nav #experimental:hover,\n.btnPreview:hover,\n.btnNext:hover {\n    background: var(--color-hover);\n    transition: background .2s;\n    opacity: 1;\n}\n\n.btnMenu:active,\n.nav #pageComments:active,\n.nav #experimental:active,\n.btnPreview:active,\n.btnNext:active {\n    background: var(--color-active);\n    transform: scale(.9);\n    transition: transform .24s;\n    opacity: 1;\n}\n\n.btnPreview:active,\n.btnNext:active {\n    border-radius: 50%;\n}\n\n.nav .disabled {\n    opacity: .5;\n}\n\n.nav #experimental {\n    width: 36px;\n    margin-left: 12px;\n    background: #FFB70A;\n    ;\n}\n\n\n.nav #experimental:hover {\n    background: #FED574;\n    ;\n}\n\n\n/*** MENU ***/\n.menu {\n    position: absolute;\n    bottom: 48px;\n    display: flex;\n    flex-direction: column;\n    width: 240px;\n    padding: 12px 0;\n    background: var(--color-background);\n    border: var(--border);\n    box-shadow: var(--shadow2);\n    border-radius: var(--border-radius);\n    visibility: hidden;\n    opacity: 0;\n    transform: translateY(-8px);\n    transition: all .12s;\n}\n\n.menu.active {\n    opacity: 1;\n    visibility: visible;\n    transform: translateY(0px);\n    transition: all .12s;\n}\n\n.menu .item-switcher {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 0 20px;\n    height: 44px;\n    background: var(--color-background);\n}\n\n.item {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 0 20px;\n    height: 44px;\n    cursor: pointer;\n    background: var(--color-background);\n}\n\n.item:hover {\n    background: var(--color-hover);\n}\n\n.item:active {\n    background: var(--color-active);\n    transition: background .12s;\n}\n\n.item.sub:active {\n  background: var(--color-hover);\n}\n\n.item.sub:hover + div {\n  display: flex;\n}\n\n.item.sub:hover .submenu {\n  display: flex;\n}\n\n.item svg {\n    margin-right: 16px;\n}\n\n.item span {\n    flex-grow: 1;\n    text-align: start;\n}\n\n.submenu {\n  align-self: flex-start;\n  position: absolute;\n  transform: translateX(212px);\n  margin-top: -12px;\n  border-radius: var(--border-radius);\n  overflow: hidden;\n  padding: 12px 0;\n  background: var(--color-background);\n  display: none;\n  box-shadow: var(--shadow1);\n}\n\n.menu .tips {\n    font-size: var(--font-small);\n    color: var(--color-secondary);\n    padding-left: 12px;\n}\n\n.menu .tips svg {\n  margin-right: -6px;\n  /* fill: var(--color-secondary); */\n  width: 18px;\n}\n\nhr {\n    display: block;\n    height: 1px;\n    border: 0;\n    border-top: 1px solid var(--color-border);\n    margin: 12px 0 12px 20px;\n    padding: 0;\n}\n\n#sidebar hr {\n    margin: 16px 0 0 20px;\n}\n\narea {\n    cursor: pointer;\n}\n\n/*** OVERLAYS ***/\n.divPanel {\n    position: absolute;\n    z-index: 3;\n}\n\n/*** FIXED PANELS ***/\n.fixedPanelTop {\n    z-index: 14;\n}\n\n.fixedPanel1 {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    margin: 0 auto;\n    pointer-events: none;\n    user-select: none;\n}\n\n.fixedPanel {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    text-align: center;\n}\n\n.panelVSCroll {\n    overflow-x: hidden;\n    overflow-y: auto\n}\n\n.fixedPanelFloat {\n    position: fixed;\n    z-index: 13;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixedBottomPanelFloat {\n    position: absolute;\n    z-index: 13;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixed_back {\n    z-index: 6;\n    background: rgba(100, 100, 100, 255);\n    color: #f1f1f1;\n    text-align: center;\n    margin: 0 auto;\n    position: absolute;\n}\n\n/*\n\n#fixed_left{\n\tposition: fixed;\n\tz-index: 10;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\toverflow: hidden;\n\ttext-align: center;\n\tmargin: 0 auto;\n}\n\n#fixed_left_back{\n\tz-index: 5;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\ttext-align: center;\n\tmargin: 0 auto;\n\tposition: absolute;\n}\n**/\n/*** LOADING INDICATOR ***/\n#nav #loading {\n    width: 40px;\n}\n\n.lds-ring {\n    display: inline-block;\n    position: relative;\n    width: 36px;\n    height: 28px;\n    margin-top: 4px;\n}\n\n.lds-ring div {\n    box-sizing: border-box;\n    display: block;\n    position: absolute;\n    width: 24px;\n    height: 24px;\n    border: 2px solid var(--color-secondary);\n    border-radius: 50%;\n    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    border-color: var(--color-secondary) transparent transparent transparent;\n}\n\n.lds-ring div:nth-child(1) {\n    animation-delay: -0.45s;\n}\n\n.lds-ring div:nth-child(2) {\n    animation-delay: -0.3s;\n}\n\n.lds-ring div:nth-child(3) {\n    animation-delay: -0.15s;\n}\n\n@keyframes lds-ring {\n    0% {\n        transform: rotate(0deg);\n    }\n\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n.version-screen-div {\n    cursor: pointer;\n}\n\n/*@media only screen and (max-width: 1024px) and (min-width: 768px) {\n\n.grid-cell{ width: calc(100%/2 - 48px);}\n\n}\n\n@media only screen and (max-width: 768px) and (min-width: 320px) {\n\t.grid-cell{ width: 100%; }\n}*/\n\n@media (prefers-color-scheme: dark) {\n\n    /* Dark mode styles go here! */\n    :root {\n        --color-danger: #E40949;\n        --color-warning: #FFB70A;\n        --color-primary: #E0E1E1;\n        --color-secondary: #989EA5;\n        --color-accent: #568AF2;\n        --color-hover: #444549;\n        --color-hover-bright: rgba(255, 255, 255, .12);\n        --color-active: #282828;\n        --color-border: var(--color-active);\n        --color-link: #989EA5;\n        --color-background: #35363A;\n        --color-background-gallery: var(--color-background);\n        --color-background-gallery-90: rgba(53, 54, 58, 0.90);\n        --shadow1: 0 2px 6px 0 rgba(0, 0, 0, 0.24);\n    }\n\n}\n\n@media (prefers-color-scheme: light) {\n    /* Light styles go here! */\n}\n\n/* custom scrollbar */\n.panelVSCroll{\n    padding-right: 8px;\n}\n\n.panelVSCroll.always::-webkit-scrollbar {\n    width: 8px;\n}\n\n.panelVSCroll.always::-webkit-scrollbar-track {\n    background-color: transparent;\n}\n\n.panelVSCroll.always::-webkit-scrollbar-thumb {\n    background-color: white;\n    border-radius: 8px;\n    border: 2px solid #d6dee1;\n    background-clip: content-box;\n    min-height: 80px;    \n}\n\n.panelVSCroll.always::-webkit-scrollbar-thumb:hover {\n    background-color: #a8bbbf;\n}\n\n/* */\n.panelVSCroll.never::-webkit-scrollbar {\n    width: 0px;\n}\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/scripts/export.sh",
    "content": "#!/bin/bash\n\ntempDir=\"$1\"\nfileName=\"$2\"\ncontext=\"$3\"\n#docName=\"$3\"\n#exportOptions=\"$4\"\n\nSKETCH=$(mdfind kMDItemCFBundleIdentifier=='com.bohemiancoding.sketch3' | head -n 1)\n\n\"$SKETCH/Contents/MacOS/sketchtool\" detach ${fileName}\n\"$SKETCH/Contents/MacOS/sketchtool\" run ~/Library/Application\\ Support/com.bohemiancoding.sketch3/Plugins/PuzzlePublisher.sketchplugin \"cmdRun\"  --context=\"${context}\"\nrm $fileName\n#${dstSketchPath}/Contents/Resources/sketchtool/bin/sketchtool --new-instance=YES --concurrent=YES --wait-for-exit=NO --without-activating=YES run ~/Library/Application\\ Support/com.bohemiancoding.sketch3/Plugins/PuzzlePublisher.sketchplugin \"cmdRun\"  --context=\"{\\\"file\\\":\\\"${fileName}\\\",\\\"name\\\":\\\"${docName}\\\",\\\"commands\\\":\\\"export,close\\\",\\\"async\\\":true}\"\n#rm $fileName\n#kill $(ps -Ac -o pid,comm | awk '/^ *[0-9]+ Sketch$/ {print $1}' | tail -n 1)"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/scripts/preparePublish.sh",
    "content": "#!/bin/bash\n\nver=\"$1\"\nallMockupsFolder=\"$2\"\ndocFolder=\"$3\"\nremoteFolder=\"$4\"\ndocPathValue=\"$5\"\nmirror1=\"$6\"\nsshPort=\"$7\"\nauthorName=\"$8\"\nauthorEmail=\"$9\"\ncommentsURL=${10}\norgTmpFolder=\"${11}\"\n\nskipLive=\"\"\n\ndocPathPlaceholder=\"P_P_P\"\ndocVerPlaceholder=\"V_V_V\"\nDOCUMENT_AUTHOR_NAME_PLACEHOLDER=\"V_V_N\"\nDOCUMENT_AUTHOR_EMAIL_PLACEHOLDER=\"V_V_E\"\nDOCUMENT_COMMENTS_URL_PLACEHOLDER=\"V_V_C\"\nstoryVerPlaceholder='VERSION_INJECT=\"\"'\n\n#orgTmpFolder=\"$(mktemp -d)/\"\n#orgTmpFolder=\"/Users/Baza/Temp/\"\ntmpFolder=\"${orgTmpFolder}/\"\nmkdir -p \"${tmpFolder}\"\n\nstoryVerPlaceholderCode=\"VERSION_INJECT=' \"\n\necho \"$commentsURL\"\necho \"$tmpFolder\"\n\nwaitCompressor(){\n    SERVICE=\"advpng\"\n    while pgrep -x \"$SERVICE\" >/dev/null\n    do\n        sleep 2\n    done\n}\n\nprepareMockups()\n{\t\n    echo $tmpFolder$verFolder\n\n\trm -rf \"${tmpFolder}\"\n\tmkdir -p \"$tmpFolder\"$verFolder\t            \n\n\t# copy to version\n\techo \"-- prepare temp folder\"\n\tcp -R \"${allMockupsFolder}/${docFolder}/\" \"${tmpFolder}\"\n\t\n    # inject version\n    if [ \"$ver\" != \"-1\" ]; then\n        sed -i '' \"s/${storyVerPlaceholder}/${storyVerPlaceholderCode}(v${ver})'/g\" \"${tmpFolder}/js/Viewer.js\"\t\n        sed -i '' \"s/${docPathPlaceholder}/${docPathValue}/g\" \"${tmpFolder}/data/story.js\"\n        sed -i '' \"s/${docVerPlaceholder}/${ver}/g\" \"${tmpFolder}/data/story.js\"\t\n        sed -i '' \"s/${DOCUMENT_AUTHOR_NAME_PLACEHOLDER}/${authorName}/g\" \"${tmpFolder}/data/story.js\"\t\n        sed -i '' \"s/${DOCUMENT_AUTHOR_EMAIL_PLACEHOLDER}/${authorEmail}/g\" \"${tmpFolder}/data/story.js\"\t\n        sed -i '' \"s/${DOCUMENT_COMMENTS_URL_PLACEHOLDER}/${commentsURL}/g\" \"${tmpFolder}/data/story.js\"\t\n        sed -i '' \"s/${docVerPlaceholder}/${ver}/g\" \"${tmpFolder}/index.html\"\t      \n    fi\n}\n\nprepareMockups\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/scripts/publish.sh",
    "content": "#!/bin/bash\n\nver=\"$1\"\nallMockupsFolder=\"$2\"\ndocFolder=\"$3\"\nremoteFolder=\"$4\"\ndocPathValue=\"$5\"\nmirror1=\"$6\"\nsshPort=\"$7\"\nauthorName=\"$8\"\nauthorEmail=\"$9\"\ncommentsURL=${10}\norgTmpFolder=\"${11}\"\n\nskipLive=\"\"\n\ndocPathPlaceholder=\"P_P_P\"\ndocVerPlaceholder=\"V_V_V\"\nDOCUMENT_AUTHOR_NAME_PLACEHOLDER=\"V_V_N\"\nDOCUMENT_AUTHOR_EMAIL_PLACEHOLDER=\"V_V_E\"\nDOCUMENT_COMMENTS_URL_PLACEHOLDER=\"V_V_C\"\nstoryVerPlaceholder='VERSION_INJECT=\"\"'\n\ntmpFolder=\"${orgTmpFolder}/\"\n\nstoryVerPlaceholderCode=\"VERSION_INJECT=' \"\n\n#arguments: remoteFolder (nextcp/ux-framework/providercp)\nuploadReadyMockups()\n{\n\n\techo \"-- publish to mirror1 site from ${orgTmpFolder} to ${mirror1}/\"\n\trsync -e \"ssh -p $sshPort\" -r \"$orgTmpFolder\" \"${mirror1}/\"\n\n\tif [ $? != 0 ]; then\n\t\texit 1\n\tfi\t\n\n} \n\nif [ \"$ver\" == \"\" ]; then\n\tif [ \"$docFolder\" == \"\" ]; then\n\t\tif [ \"$remoteFolder\" == \"\" ]; then\n\t\t\techo \"ERROR - not all arguments specified\"\n\t\t\techo \"format: publish.sh VERSION ALLMOCKUPSFOLDER DOCFOLDER REMOTEFOLDER SITE SSHPORT(optional)\"\n\t\t\texit 1\n\t\tfi\n\tfi\nfi\n\nuploadReadyMockups\n"
  },
  {
    "path": "PuzzlePublisher.sketchplugin/Contents/Sketch/tokens/DSExporter.js",
    "content": "@import(\"constants.js\")\n@import(\"lib/utils.js\")\n@import(\"lib/uidialog.js\")\n@import(\"lib/ga.js\")\n\nconst bordedLineJoinMap2 = {\n    [Style.LineJoin.Miter]: \"miter\",\n    [Style.LineJoin.Round]: \"round\",\n    [Style.LineJoin.Bevel]: \"bevel\"\n}\n\nconst bordedArrowheadMap2 = {\n    [Style.Arrowhead.None]: \"none\",\n    [Style.Arrowhead.OpenArrow]: \"openarrow\",\n    [Style.Arrowhead.FilledArrow]: \"filledarrow\",\n    [Style.Arrowhead.OpenCircle]: \"opencircle\",\n    [Style.Arrowhead.FilledCircle]: \"filledcircle\",\n    [Style.Arrowhead.OpenSquare]: \"opensquare\",\n    [Style.Arrowhead.FilledSquare]: \"filledsquare\",\n}\n\n\nconst bordedLineEndMap2 = {\n    [Style.LineEnd.Butt]: \"butt\",\n    [Style.LineEnd.Round]: \"round\",\n    [Style.LineEnd.Projecting]: \"projecting\"\n}\n\nconst BLENDING_MODE_SKETCH_TO_CSS = {\n    [Style.BlendingMode.Normal]: \"normal\",\n    [Style.BlendingMode.Darken]: \"darken\",\n    [Style.BlendingMode.Multiply]: \"multiply\",\n    [Style.BlendingMode.ColorBurn]: \"color-burn\",\n    [Style.BlendingMode.Lighten]: \"lighten\",\n    [Style.BlendingMode.Screen]: \"screen\",\n    [Style.BlendingMode.ColorDodge]: \"color-dodge\",\n    [Style.BlendingMode.Overlay]: \"overlay\",\n    //\"darken\", [Style.BlendingMode.SoftLight]:  // Not supported in CSS\n    //\"darken\", [Style.BlendingMode.HardLight]:  // Not supported in CSS\n    [Style.BlendingMode.Difference]: \"difference\",\n    [Style.BlendingMode.Exclusion]: \"exclusion\",\n    [Style.BlendingMode.Hue]: \"hue\",\n    [Style.BlendingMode.Saturation]: \"saturation\",\n    [Style.BlendingMode.Color]: \"color\",\n    [Style.BlendingMode.Luminosity]: \"luminosity\"\n}\n\n\nconst eol = \";\\n\"\nconst pxeol = \"px\" + eol\nvar app = undefined\n\nconst formatDefs = {\n    [Constants.EXPORT_FORMAT_LESS]: {\n        ext: \".less\",\n        symb: \"@\"\n    },\n    [Constants.EXPORT_FORMAT_SCSS]: {\n        ext: \".scss\",\n        symb: \"$\"\n    }\n}\n\nclass DSExporter {\n    constructor(context) {\n        this.nDoc = context.document\n        this.sDoc = Sketch.fromNative(context.document)\n        this.context = context\n        this.UI = require('sketch/ui')\n\n        this.messages = \"\"\n        this.errors = []\n\n        this.layerStyles = {}\n        this.textStyles = {}\n        this.runningForTokens = true\n\n        this.fileName = \"puzzle-tokens\" //this._clearCloudName(this.nDoc.cloudName())\n        this.pathTo = undefined\n        this.format = undefined\n        this.confOpts = undefined\n        this.opts = {\n            colors: {\n                tokens: {}, index: 0, name: \"color-\", comment: \"COLOR TOKENS\", postix: \"\",\n            },\n            fontSizes: {\n                tokens: {}, index: 0, name: \"font-size-\", comment: \"FONT SIZE TOKENS\", postix: \"px\",\n            },\n            fontWeights: {\n                tokens: {}, index: 0, name: \"font-weight-\", comment: \"FONT WEIGHTS TOKENS\", postix: \"\",\n            },\n            fontFamilies: {\n                tokens: {}, index: 0, name: \"font-family-\", comment: \"FONTS\", postix: \"\",\n            },\n        }\n\n    }\n\n    init() {\n        this.pathTo = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_PATH_TO)\n        if (undefined == this.pathTo) this.pathTo = \"\"\n\n        this.format = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_FORMAT)\n        if (undefined == this.format) this.format = Constants.EXPORT_FORMAT_LESS\n\n        this.openFinder = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_OPEN_FINDER)\n        if (null == this.openFinder) this.openFinder = true\n\n        this.confExportLibStyles = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_LIB_STYLES) == 1\n\n        this.confOpts = Settings.settingForKey(SettingKeys.PLUGIN_EXPORT_OPTS)\n        if (undefined == this.confOpts) this.confOpts = {}\n        if (null == this.confOpts.colorTokens) this.confOpts.colorTokens = true\n        if (null == this.confOpts.fontSizeTokens) this.confOpts.fontSizeTokens = true\n        if (null == this.confOpts.fontWeightTokens) this.confOpts.fontWeightTokens = true\n        if (null == this.confOpts.fontFamilyTokens) this.confOpts.fontFamilyTokens = true\n\n\n\n        // init global variable\n        app = this\n    }\n\n    initForPublisher() {\n        this.format = Constants.EXPORT_FORMAT_LESS\n        this.confOpts = {}\n        this.runningForTokens = false\n\n        // init global variable\n        app = this\n    }\n\n    // Tools\n    logMsg(msg) {\n        if (this.runningForTokens) {\n            this.messages += msg + \"\\n\"\n        } else {\n            log(msg)\n        }\n    }\n\n\n    logError(error) {\n        this.logMsg(\"[ ERROR ] \" + error)\n        this.errors.push(error)\n    }\n\n    stopWithError(error) {\n        this.UI.alert('Error', error)\n        exit = true\n    }\n\n    // Public methods\n\n    run() {\n        const res = this._showDialog()\n        if ('cancel' == res) return false\n\n        var success = this._export()\n\n        // show final message\n        if (this.errors.length > 0) {\n            this._showErrors()\n        } else {\n            if (success) {\n                track(TRACK_EXPORT_COMPLETED)\n                this.UI.message(\"Completed\")\n                if (this.openFinder) this._openFinder()\n            }\n        }\n\n        return true\n    }\n\n    // Internal\n\n    _openFinder() {\n        NSWorkspace.sharedWorkspace().openFile(this.pathTo);\n    }\n\n    _clearCloudName(cloudName) {\n        let name = cloudName\n        let posSketch = name.indexOf(\".sketch\")\n        if (posSketch > 0) {\n            name = name.slice(0, posSketch)\n        }\n        return name\n    }\n\n\n\n    _showMessages() {\n        const dialog = new UIDialog(\"Completed\", NSMakeRect(0, 0, 400, 400), \"Dismiss\", \"\", \"\")\n        dialog.addTextViewBox(\"messages\", \"See what has been changed:\", this.messages, 400)\n        const result = dialog.run()\n        dialog.finish()\n    }\n\n    _showErrors() {\n        var errorsText = this.errors.join(\"\\n\\n\")\n\n        const dialog = new UIDialog(\"Found errors\", NSMakeRect(0, 0, 600, 600), \"Who cares!\", \"\", \"\")\n        dialog.addTextViewBox(\"debug\", \"\", errorsText, 600)\n        const result = dialog.run()\n        dialog.finish()\n    }\n\n    _showDialog() {\n        const dialog = new UIDialog(\"Export Styles\", NSMakeRect(0, 0, 800, 300), \"Export\",\n            \"Export all text styles to \\\"\" + this.fileName + \"\\\".[less|scss] file\")\n        //dialog.removeLeftColumn()\n        dialog.leftColWidth = 200\n\n        dialog.addLeftLabel(\"\", \"Destination\")\n        dialog.addPathInput({\n            id: \"pathTo\", label: \"\", labelSelect: \"Select Folder\",\n            textValue: this.pathTo,\n            inlineHint: 'e.g. ~/Temp', width: 520\n        })\n\n        dialog.addLeftLabel(\"\", \"File Format\")\n        dialog.addRadioButtons(\"format\", \"\", this.format, [\"LESS\", \"SCSS\"], 250)\n\n        dialog.addCheckbox(\"openFinder\", \"Open Finder window on completion\", this.openFinder)\n\n        dialog.addDivider()\n\n        dialog.addLeftLabel(\"\", \"Create tokens for\")\n        dialog.addCheckbox(\"colorTokens\", \"Colors\", this.confOpts.colorTokens)\n        dialog.addCheckbox(\"fontSizeTokens\", \"Font Sizes\", this.confOpts.fontSizeTokens)\n        dialog.addCheckbox(\"fontWeightTokens\", \"Font Weights\", this.confOpts.fontWeightTokens)\n        dialog.addCheckbox(\"fontFamilyTokens\", \"Font Families\", this.confOpts.fontFamilyTokens)\n\n        if (null == this.confOpts.colorTokens) this.confOpts.colorTokens = true\n        if (null == this.confOpts.fontSizeTokens) this.confOpts.fontSizeTokens = true\n        if (null == this.confOpts.fontWeightTokens) this.confOpts.fontWeightTokens = true\n        if (null == this.confOpts.fontFamilyTokens) this.confOpts.fontFamilyTokens = true\n\n        dialog.addSpace()\n        dialog.addLeftLabel(\"\", \"Styles\")\n        dialog.addCheckbox(\"exportLibStyles\", \"Export external library styles\", this.confExportLibStyles)\n\n\n        while (true) {\n            dialog.run()\n            if (dialog.userClickedCancel) {\n                dialog.finish()\n                return \"cancel\"\n            }\n\n            // Check data\n            this.pathTo = dialog.views['pathTo'].stringValue() + \"\"\n            if (\"\" == this.pathTo) continue\n            this.format = dialog.views['format'].selectedIndex\n            this.confExportLibStyles = dialog.views['exportLibStyles'].state() == 1\n            this.less = this.format == Constants.EXPORT_FORMAT_LESS\n            this.scss = this.format == Constants.EXPORT_FORMAT_SCSS\n            this.openFinder = dialog.views['openFinder'].state() == 1\n            this.confOpts.colorTokens = dialog.views['colorTokens'].state() == 1\n            this.confOpts.fontSizeTokens = dialog.views['fontSizeTokens'].state() == 1\n            this.confOpts.fontWeightTokens = dialog.views['fontWeightTokens'].state() == 1\n            this.confOpts.fontFamilyTokens = dialog.views['fontFamilyTokens'].state() == 1\n            this.def = formatDefs[this.format]\n            // Save data\n\n            Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_PATH_TO, this.pathTo)\n            Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_FORMAT, this.format)\n            Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_OPEN_FINDER, this.openFinder)\n            Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_OPTS, this.confOpts)\n            Settings.setSettingForKey(SettingKeys.PLUGIN_EXPORT_LIB_STYLES, this.confExportLibStyles)\n            break\n        }\n\n        return \"ok\"\n    }\n\n    ///////////////////////////////////////////////////////////////\n\n    _export() {\n        const textStylesText = this._getStylesAsText()\n        const tokensText = this._getTokensAsText()\n\n        let res = tokensText + textStylesText\n\n        const fullPathTo = this.pathTo + \"/\" + this.fileName + this.def.ext\n        Utils.writeToFile(res, fullPathTo);\n\n        return true\n\n    }\n\n    _getStylesAsText() {\n        let res = \"\"\n        res += \"///////////////// Text Styles /////////////////\\n\"\n        this.sDoc.sharedTextStyles.forEach(function (sStyle) {\n            if (!this.confExportLibStyles && sStyle.getLibrary()) return\n            if (DEBUG) log(\"export text style: \" + sStyle.name)\n            res += this._getStyleInfoAsText(sStyle)\n            let si = this._parseStyleName(sStyle.name, true)\n            res += si.openTags\n            ///            \n            res += this._getTextStylePropsAsText(sStyle.style, si.spaces)\n            ///\n            res += si.closeTags\n        }, this)\n\n        res += \"///////////////// Layer Styles /////////////////\\n\"\n        this.sDoc.sharedLayerStyles.forEach(function (sStyle) {\n            if (!this.confExportLibStyles && sStyle.getLibrary()) return\n            if (DEBUG) log(\"export layer style: \" + sStyle.name)\n            res += this._getStyleInfoAsText(sStyle)\n            let si = this._parseStyleName(sStyle.name, false)\n            //\n            res += si.openTags\n            res += this._getLayerStylePropsAsText(sStyle, null, sStyle.style, si.spaces)\n            res += si.closeTags\n            // save additional borders\n            const borders = sStyle.style.borders.filter(s => s.enabled)\n            for (let i = 1; i < borders.length; i++) {\n                if (1 == i)\n                    res += \"/////// Additional borders for \" + sStyle.name + \"\\n\"\n                res += si.openTags\n                res += this._getLayerBorderByIndexAsText(sStyle.style, i, si.spaces)\n                res += si.closeTags\n            }\n            // save additional fills\n            const fills = sStyle.style.fills.filter(s => s.enabled)\n            for (let i = 1; i < fills.length; i++) {\n                if (1 == i)\n                    res += \"/////// Additional fills for \" + sStyle.name + \"\\n\"\n                res += si.openTags\n                res += this._getLayerFillByIndexAsText(sStyle.style, i, si.spaces)\n                res += si.closeTags\n            }\n\n        }, this)\n\n        return res\n    }\n\n    _getStyleInfoAsText(sStyle) {\n        let res = \"\"\n        res += \"///////////////\" + sStyle.name + \"\\n\"\n\n        let sLib = sStyle.getLibrary()\n        if (sLib != null) {\n            res += \"///////////////\" + \"library: \" + sLib.name + \"\\n\"\n        }\n        return res\n    }\n\n    _getTokensAsText() {\n        let res = \"\"\n\n        Object.keys(this.opts).forEach((optName) => {\n            res += this.getAbstractTokensAsText(this.opts[optName])\n        }, this)\n\n        return res\n    }\n\n    getAbstractTokensAsText(opt) {\n        let res = \"\"\n        if (opt.index) {\n            const def = this.def\n            res += \"//////////// \" + opt.comment + \" TOKENS //////////\\n\"\n            Object.keys(opt.tokens).forEach((value) => {\n                const token = opt.tokens[value]\n                res += def.symb + token.name + \":         \" + value + opt.postix + \";\\n\"\n            }, this)\n            res += \"\\n\"\n        }\n        return res\n    }\n\n    _getAbstractToken(opt, value) {\n        let token = opt.tokens[value]\n        if (null == token) {\n            // create new token\n            token = {\n                value: value,\n                name: opt.name + (opt.index < 10 ? \"0\" : \"\") + (opt.index < 100 ? \"0\" : \"\") + opt.index++\n            }\n            opt.tokens[value] = token\n        }\n        return this.def.symb + token.name\n    }\n\n    _getColorToken(color) {\n        // drop FF transparency as default\n        if (color.length == 9 && color.substring(7).toUpperCase() == \"FF\") {\n            color = color.substring(0, 7)\n        }\n        if (!this.confOpts.colorTokens) return color.toUpperCase()\n        return this._getAbstractToken(this.opts.colors, color)\n    }\n    _getFontSizeToken(fontSize) {\n        if (!this.confOpts.fontSizeTokens) return fontSize + \"px\"\n        return this._getAbstractToken(this.opts.fontSizes, fontSize)\n    }\n    _getFontFamilyToken(fontFamily) {\n        if (!this.confOpts.fontFamilyTokens) return fontFamily\n        return this._getAbstractToken(this.opts.fontFamilies, fontFamily)\n    }\n    _getFontWeightToken(fontWeight) {\n        if (!this.confOpts.fontWeightTokens) return fontWeight\n        return this._getAbstractToken(this.opts.fontWeights, fontWeight)\n    }\n\n\n    _getTextStylePropsAsText(sStyle, spaces = \"\") {\n        // In some cases text object can has non-text style\n        if (sStyle.styleType != \"Text\") return\n\n        let res = \"\"\n\n        res += spaces + \"font-family\" + \": \" + this._getFontFamilyToken(sStyle.fontFamily) + eol\n        res += spaces + \"font-size\" + \": \" + this._getFontSizeToken(sStyle.fontSize) + eol\n        res += spaces + \"color\" + \": \" + this._getColorToken(sStyle.textColor) + eol\n        if (sStyle.alignment !== Text.Alignment.left)\n            res += spaces + \"text-align\" + \": \" + alignMap2[sStyle.alignment] + eol\n        res += spaces + \"vertical-align\" + \": \" + vertAlignMap2[sStyle.verticalAlignment] + eol\n        {\n            var cssWeights = weights.filter(w => w.sketch == sStyle.fontWeight)\n            if (cssWeights.length == 0)\n                this.logError('Can not find CSS font-weifgt for Sketch fontWeught ' + sStyle.fontWeight)\n            else\n                res += spaces + \"font-weight\" + \": \" + this._getFontWeightToken(cssWeights[0].css) + eol\n        }\n        if (undefined != sStyle.fontStyle) {\n            res += spaces + \"font-style\" + \": \" + sStyle.fontStyle + eol\n        }\n        if (null != sStyle.lineHeight)\n            res += spaces + \"line-height\" + \": \" + sStyle.lineHeight + pxeol\n        if (undefined != sStyle.textTransform && sStyle.textTransform != 'none') {\n            res += spaces + \"text-transform\" + \": \" + sStyle.textTransform + eol\n        }\n        if (undefined != sStyle.textUnderline) {\n            res += spaces + \"text-decoration\" + \": \" + \"underline\" + eol\n        }\n        if (undefined != sStyle.textStrikethrough) {\n            res += spaces + \"text-decoration\" + \": \" + \"line-through\" + eol\n        }\n        if (null != sStyle.kerning) {\n            res += spaces + \"letter-spacing\" + \": \" + sStyle.kerning + pxeol\n        }\n        if (this.runningForTokens) {\n            res += spaces + PT_PARAGRAPH_SPACING + \": \" + sStyle.paragraphSpacing + eol\n        }\n\n        return res\n    }\n\n\n    _getLayerStylePropsAsText(sSharedStyle, layer, sStyle, spaces = \"\") {\n        let res = \"\"\n\n        // process the first fill, other will be processed later\n        res += this._getLayerFillByIndexAsText(sStyle, 0, spaces)\n\n        // process the first border, other will be processed later\n        res += this._getLayerBorderByIndexAsText(sStyle, 0, spaces)\n\n        // process shadows\n        res += this._getLayerShadowsText(sStyle, spaces)\n\n        // try to find and save border radius(es)\n        while (true) {\n            const layers = sSharedStyle ? sSharedStyle.getAllInstancesLayers() : [layer]\n            if (0 == layers.length) break\n            const l = layers[0] // take the first\n            if (\"Shape\" != l.type && \"ShapePath\" != l.type) break;\n\n            // in some case we can got undefined points property\n            if (undefined == l.points) break\n\n            let str = \"\"\n            let radiusesHash = {}\n            l.points.forEach(function (point, index) {\n                radiusesHash[point.cornerRadius] = true\n            })\n            if (Object.keys(radiusesHash).length == 1) {\n                // all points have the same radius\n                // skip zero radius\n                if (0 == l.points[0].cornerRadius) break\n                str = l.points[0].cornerRadius + \"px\"\n            } else {\n\n                str = l.points.map(p => p.cornerRadius).join(\"px \") + \"px\"\n            }\n            //\n            res += spaces + \"border-radius\" + \": \" + str + eol\n            break\n        }\n\n        if (sStyle.opacity < 1) {\n            res += spaces + \"opacity\" + \": \" + Math.round(sStyle.opacity * 100) / 100 + eol\n        }\n\n        return res\n    }\n\n\n    _getLayerBorderByIndexAsText(sStyle, index, spaces) {\n        const borders = sStyle.borders != null ? sStyle.borders.filter(s => s.enabled) : null\n        const border = borders && (borders.length > index) ? borders[index] : null\n        if (null == border) return \"\"\n        let res = \"\"\n\n        res += spaces + \"border-color\" + \": \" + this._getColorToken(border.color) + eol\n        //\n        if (border.position != Style.BorderPosition.Center) {\n            // save non-default position\n            const conversion = {\n                [Style.BorderPosition.Inside]: 'inside',\n                [Style.BorderPosition.Outside]: 'outside'\n            }\n            res += spaces + \"border-position\" + \": \" + conversion[border.position] + eol\n        }\n        //\n        res += spaces + \"border-width\" + \": \" + border.thickness + pxeol\n\n        // Process styles common for all borders\n        if (0 == index) {\n            if (sStyle.borderOptions != null) {\n                let bs = \"\"\n                const dash = sStyle.borderOptions.dashPattern\n                if (null != dash && dash.length) {\n                    bs = dash[0] == border.thickness ? \"dotted\" : \"dashed\"\n                }\n                if (bs != \"\") res += spaces + \"border-style\" + \": \" + bs + eol\n            }\n            //\n            if (sStyle.borderOptions != null) {\n                if (sStyle.borderOptions.lineEnd != Style.LineEnd.Butt) {\n                    // save non-default line end\n                    const borderLineEnd = bordedLineEndMap2[sStyle.borderOptions.lineEnd]\n                    res += spaces + \"border-line-end\" + \": \" + borderLineEnd + eol\n                }\n                if (sStyle.borderOptions.lineJoin != Style.LineJoin.Miter) {\n                    // save non-default join\n                    const borderLineJoin = bordedLineJoinMap2[sStyle.borderOptions.lineJoin]\n                    if (undefined != borderLineJoin)\n                        res += spaces + \"border-line-join\" + \": \" + borderLineJoin + eol\n                }\n                const borderStartArrowHead = bordedArrowheadMap2[sStyle.borderOptions.startArrowhead]\n                if (\"none\" != borderStartArrowHead)\n                    res += spaces + \"border-start-arrowhead\" + \": \" + borderStartArrowHead + eol\n                const borderEndArrowHead = bordedArrowheadMap2[sStyle.borderOptions.endArrowhead]\n                if (\"none\" != borderEndArrowHead)\n                    res += spaces + \"border-end-arrowhead\" + \": \" + borderEndArrowHead + eol\n            }\n        }\n        //    \n\n        return res\n    }\n\n    _getLayerFillByIndexAsText(sStyle, index, spaces) {\n        const fills = sStyle.fills != null ? sStyle.fills.filter(s => s.enabled) : null\n        const fill = fills && (fills.length > index) ? fills[index] : null\n        if (null == fill) return \"\"\n\n        let res = spaces + \"background-color\" + \": \"\n        if (Style.FillType.Color == fill.fill) {\n            res += this._getColorToken(fill.color) + eol\n        } else if (Style.FillType.Gradient == fill.fill) {\n            const g = fill.gradient\n            // fight with gradients\n            if (Style.GradientType.Linear == g.gradientType) {\n                //linear-gradient(134deg, #004B3A 0%, #2D8B61 51%, #9BD77E 100%);\n                const deg = this._calcGradientDeg(g)\n                res += \"linear-gradient(\"\n                if (180 != deg) {\n                    // 180 is default, we can omit it\n                    res += deg + \"deg,\"\n                }\n                g.stops.forEach(function (s, index) {\n                    res += (index > 0 ? \" ,\" : \"\") + this._getColorToken(s.color)\n                    if (undefined != s.position) res += \" \" + (s.position * 100) + \"%\"\n                }, this)\n                res += \")\" + eol\n            }\n        } else {\n            return \"\"\n        }\n        //\n        if (Style.BlendingMode.Normal != sStyle.blendingMode) {\n            const cssValue = BLENDING_MODE_SKETCH_TO_CSS[sStyle.blendingMode]\n            if (undefined == cssValue) {\n                this.logError('Can not set CSS mix-blend-mode: for Sketch blendingMode ' + sStyle.blendingMode)\n            } else {\n                res += spaces + \"mix-blend-mode: \" + cssValue + eol\n            }\n        }\n        //\n        return res\n    }\n\n    _getLayerShadowsText(sStyle, spaces) {\n        let t1 = this._getLayerTypeShadowsText(sStyle, sStyle.shadows, false, spaces)\n        let t2 = this._getLayerTypeShadowsText(sStyle, sStyle.innerShadows, true, spaces)\n        if (\"\" == t1 && \"\" == t2) return \"\"\n\n        let res = spaces + \"box-shadow: \" + t1 + (t1 != \"\" && t2 != \"\" ? \", \" : \"\")\n        res += t2\n        res += eol\n\n        return res\n    }\n\n\n    _getLayerTypeShadowsText(sStyle, shadows, inset, spaces) {\n        if (null == shadows || !shadows.length) return \"\"\n\n        // to:\n        // box-shadow:  0 10px 20px 2 rgba(0,0,0,0.1), inset 0 10px 20px 2 rgba(0,0,0,0.1);\n        //           offset-x | offset-y | blur-radius | spread-radius | color \n        let res = \"\"\n        shadows.filter(s => s.enabled).forEach(function (shadow, index) {\n            if (index) res += \", \"\n            if (inset) res += \"inset \"\n            res += shadow.x + 'px'\n            if (null != shadow.y) {\n                res += \" \" + shadow.y + \"px\"\n                if (null != shadow.blur) {\n                    res += \" \" + shadow.blur + \"px\"\n                    if (null != shadow.spread) {\n                        res += \" \" + shadow.spread\n                    }\n                }\n            }\n            res += \" \" + this._getColorToken(shadow.color)\n        }, this)\n\n        return res\n    }\n\n\n    // g: style.fills[0].gradient\n    _calcGradientDeg(g) {\n        const from = g.from\n        const to = g.to\n\n        let deg = Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI;\n\n        // rotate for CSS \n        deg += 90\n        // check for last 90\n        if (deg < 0) deg = 360 + deg\n\n        return Number.parseFloat(deg).toPrecision(6);\n    }\n\n\n\n    _parseStyleName(nameSrc, isText) {\n        /*\n        let styles = isText ? this.textStyles : this.layerStyles\n        let index = 1\n        let name = nameSrc\n        while (name in styles) {\n            name = nameSrc + \"--PTD-\" + index++\n        }\n        styles[name] = true\n        */\n        const name = nameSrc\n\n        const path = name.split(\"/\").map(s => s.replace(/\\./g, '-DOT-').replace(/^(\\d)/g, '--PT-$1'))\n        let si = {\n            openTags: \".\" + path.join(\" .\") + \"{\\n\",\n            spaces: \"    \",\n            closeTags: \"}\\n\"\n        }\n        return si\n    }\n\n\n\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Puzzle Publisher\n\nA Sketch plugin that exports Sketch artboards into clickable HTML file. \n\n### Join [GitHub Comments](https://github.com/ingrammicro/puzzle-publisher/discussions) for live talk ###\n\nFeatures:\n- Single HTML file with links highlighting\n- Show artboard as an overlay over a previous artboard / [Pict 1](https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/FixedLayers/Overlay1.png), [Pict 2](https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/FixedLayers/Overlay2.png), [Pict 3](https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/FixedLayers/Overlay3.png) / [Example](https://github.com/ingrammicro/puzzle-publisher/tree/master/examples/FixedLayers)\n- Show artboard as a modal over a previous artboard  / [Picture](https://github.com/ingrammicro/puzzle-publisher/raw/master/examples/Pictures/Link-ModalArtboard.png), [Example](https://github.com/ingrammicro/puzzle-publisher/raw/master/examples/Link-ModalArtboard.sketch)\n- Support for layers with fixed position (left,top and float panels) / [Sketch example](https://github.com/ingrammicro/puzzle-publisher/tree/master/examples/FixedLayers) / [HTML Demo](https://ingrammicro.github.io/puzzle-publisher/FixedLayers)\n- Support for Sketch-native links (including Back links, cross-page links, links inside Symbols and overrided hotspot links)\n- Support for external links / [Hint](https://github.com/ingrammicro/puzzle-publisher/blob/master/Hints.md#hint2)\n- Skips pages and artboards with * prefix \n- Ability to insert Google counter\n- Ability to hide navigation controls and hotspot highlighting\n- Automatic compression of images\n- Browser favicon customization [Sketch example](https://github.com/ingrammicro/puzzle-publisher/tree/master/examples/Favicon) / [HTML Demo](https://ingrammicro.github.io/puzzle-publisher/Favicon)\n\nViewer features:\n- Gallery / [Picture](https://github.com/ingrammicro/puzzle-publisher/raw/master/examples/Pictures/Gallery.png)\n- Async pre-loading of all page images\n- Auto-scale of large pages to fit into small browser window\n- Ability to get <iframe> code to embed you prototypes into external web pages (with special UI) or get lightweight code with just <a href...><img...></a/>\n- Page layout viewer (if it was enabled for a page)\n\nPublisher features:\n- Increasing of version counter and injecting it into HTML\n- Publishing to external site by SFTP\n- Publishing to Miro whiteboards\n- Announce new version changes in Telegram channel\n\nRun from command line:\n- Export HTML from command line / [Hint](https://github.com/ingrammicro/puzzle-publisher/blob/master/Hints.md#hint4)\n\n[Change Log](https://github.com/ingrammicro/puzzle-publisher/blob/master/CHANGELOG.md)\n\nPlease send your feedback to max@bazarov.ru\n\n## Screenshots\nCommands:\n\n<img width=\"20%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Menu.png\"/><img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Export-Dialog.png\"/><img width=\"40%\" src=\"https://github.com/ingrammicro/puzzle-publisher/blob/master/examples/Pictures/Publish-Dialog.png?raw=true\"/>\n\nSettings: \n\n<img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Layer-Dialog.png\"/><img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Artboard-Dialog1.png\"/><img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Artboard-Dialog2.png\"/><img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Document-Dialog.png\"/><img width=\"40%\" src=\"https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/examples/Pictures/Plugin-Dialog.png\"/>\n\n## Installation\n\nTo install, [download the zip file](https://github.com/ingrammicro/puzzle-publisher/raw/master/PuzzlePublisher.sketchplugin.zip) and double-click on `PuzzlePublisher.sketchplugin`. The commands will show up under `Plugins > Puzzle Publisher`. \n\n## Usage\n\nYou can use Sketch-native links or add links to external sites. When you're finished adding these you can generate a HTML website of the all document pages by selecting `Export to HTML`. The generated files can then be uploaded to a server so you can show it to your clients. \n\n### Retina Images\n \nBy default it will show 2x images for high pixel density screens. To turn this off uncheck `Export retina images` in Settings and re-export the page.\n\n### Special magic string in layer name\n- @MainBackground@: a shape layer background color will be used as a default color for browser pages\n- @SiteIcon@: an image layer will be rendered as site icon for mockups\n- @Redirect@: a link from a marked hostpot will be used to show a page under an overlay ([example](https://github.com/ingrammicro/puzzle-publisher/tree/master/tests/12.2.0))\n- \"images/\": if a symbol name starts from **images/** string then Element Inspector will not show symbol childs. It can be useful to inform developers about used image name\n"
  },
  {
    "path": "appcast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\"  xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n    <channel>\n        <title>Puzzle Publisher</title>\n        <link>https://raw.githubusercontent.com/ingrammicro/puzzle-publisher/master/appcast.xml</link>\n        <description>Generates and publish HTML clickable prototype</description>\n        <language>en</language>\n         <item>\n            <title>Version 17.9.1</title>\n            <date>26 Sep 2022</date>\n            <enclosure url=\"https://github.com/ingrammicro/puzzle-publisher/raw/master/PuzzlePublisher.sketchplugin.zip\" sparkle:version=\"17.9.1\"/>\n        </item>         \n    </channel>\n</rss>"
  },
  {
    "path": "comments/backend/.gitignore",
    "content": "sendgrid.env\n"
  },
  {
    "path": "comments/backend/config/.htaccess",
    "content": "Deny from all\n"
  },
  {
    "path": "comments/backend/config/forums.json",
    "content": "{\n    \"1234567890\": {\n        \"name\": \"Forum\",\n        \"email\": {\n            \"from-email\": \"\",\n            \"sendgrid-key\": \"\"\n        }\n    }\n}"
  },
  {
    "path": "comments/backend/data/.htaccess",
    "content": "Deny from all\n"
  },
  {
    "path": "comments/backend/lib/Comments.js",
    "content": "function commentReplaceEnds(value) {\n    return value.replace(new RegExp('\\r?\\n', 'g'), '<br/>')\n}\n\nclass CommentsAbstractForm {\n    constructor(formName) {\n        this.formName = formName\n        this.built = false\n        //\n    }\n    _tuneInput(inputName, type = \"input\") {\n        let input = $(\"#comments_viewer #\" + this.formName + \" #\" + inputName)\n        input.focusin(function () {\n            comments.inputFocused = true\n        })\n        input.focusout(function () {\n            comments.inputFocused = false\n        })\n        if (\"input\" == type) {\n            input.keypress(function (e) {\n                if (e.which == 13) {\n                    comments.currentForm.submit()\n                }\n            });\n        } else if (\"textarea\" == type) {\n            input.keypress(function (e) {\n                if (e.which == 13 && e.metaKey) {\n                    comments.currentForm.submit()\n                }\n            });\n        }\n    }\n    _setInputValue(inputName, value) {\n        let input = $(\"#comments_viewer #\" + this.formName + \" #\" + inputName)\n        input.val(value);\n        return input\n    }\n    showError(errorText) {\n        $(\"#comments_viewer #\" + this.formName + \" #error\").html(errorText);\n    }\n    show() {\n        if (!this.built) this.buildHTML();\n        this.putDataInForm()\n        $(\"#comments_viewer #\" + this.formName).show()\n        comments.currentForm = this\n    }\n    hide() {\n        $(\"#comments_viewer #\" + this.formName).hide()\n        this.showError(\"\")\n\n    }\n    showViewer() {\n    }\n    hideViewer() {\n    }\n    //// to overwrite\n    buildHTML() {\n        this.built = true\n    }\n    getDataFromForm() {\n\n    }\n    putDataInForm() { }\n    checkData() { }\n    handleEnterKey() {\n        return false\n    }\n    clear() {\n        this.putDataInForm()\n    }\n    submit() { }\n}\n////////////////// LOGIN FORM /////////\nclass CommentsLoginForm extends CommentsAbstractForm {\n    constructor() {\n        super(\"loginForm\")\n        this.email = \"\"\n    }\n    putDataInForm() {\n        this._setInputValue(\"email\", this.email);\n    }\n    // Check data\n    checkData() {\n        if (\"\" == this.email) {\n            this.showError(\"Specify email\");\n            return false;\n        }\n        return true\n    }\n    getDataFromForm() {\n        this.email = $(\"#comments_viewer #loginForm #email\").val();\n    }\n    buildHTML() {\n        super.buildHTML()\n        let s = `\n    <div id='loginForm' style=\"display:none\">\n        <div id=\"title\" style=\"font-weight:bold;\">Login As</div>\n        <div id=\"error\" style=\"color:red\"></div>\n        <div>\n            <input id=\"email\" style=\"${comments.styles.input}\" placeholder=\"Your email\" />\n        </div>\n        <div id=\"buttons\">\n            <input style=\"${comments.styles.buttonPrimary}\" id=\"send\" type=\"button\" onclick=\"comments.loginForm.submit();return false;\" value=\"Login\" />\n        </div>\n    </div>`\n        $(\"#comments_viewer #top\").append(s);\n        this._tuneInput(\"email\")\n    }\n    submit() {\n        this.getDataFromForm();\n        if (!this.checkData()) return false;\n        ///\n        var formData = new FormData();\n        formData.append(\"email\", this.email);\n        //\n        var handler = function () {\n            var form = comments.loginForm\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //\n            console.log(this.responseText);\n            if (result.status != 'ok') {\n                form.showError(result.message);\n            } else {\n                console.log(result);\n                comments.authForm.userExists = result.data.exists;\n                form.hide()\n                comments.authForm.show()\n            }\n        }\n        //\n        return comments.sendCommand(\"login\", formData, handler);\n    }\n    clear() {\n        this.email = \"\"\n        super.clear()\n    }\n\n}\n////////////////// AUTH FORM /////////\nclass CommentsAuthForm extends CommentsAbstractForm {\n    constructor() {\n        super(\"authForm\")\n        this.code = \"\"\n        this.name = \"\"\n        this.userExists = false\n    }\n    putDataInForm() {\n        this._setInputValue(\"code\", this.code)\n        let nameField = this._setInputValue(\"name\", this.name)\n        if (this.userExists)\n            nameField.hide()\n        else\n            nameField.show()\n\n    }\n    // Check data\n    checkData() {\n        if (\"\" == this.code) {\n            this.showError(\"Specify code\");\n            return false;\n        }\n        if (!this.userExists && \"\" == this.name) {\n            this.showError(\"Specify your name\");\n            return false;\n        }\n        return true\n    }\n    getDataFromForm() {\n        this.code = $(\"#comments_viewer #authForm #code\").val();\n        this.name = $(\"#comments_viewer #authForm #name\").val();\n    }\n    buildHTML() {\n        super.buildHTML()\n        let s = `\n    <div id='authForm' style=\"display:none\">        \n        <div id=\"title\" style=\"font-weight:bold;\">Confirm login</div>\n        <div id=\"msg\">\n            Check new email to get a code\n        </div>\n        <div id=\"error\" style=\"color:red\"></div>         \n        <div>\n            <input id=\"code\" style=\"${comments.styles.input}\" placeholder=\"Authorization code\" />\n        </div>\n        <div>\n            <input id=\"name\" style=\"${comments.styles.input}\" placeholder=\"Your name\" />\n        </div>\n        <div id=\"buttons\">\n            <input id=\"send\" style=\"${comments.styles.buttonPrimary}\" type=\"button\" onclick=\"comments.authForm.submit();return false;\" value=\"Confirm\" />\n            <input id=\"send\" style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.authForm.cancel();return false;\" value=\"Cancel\" />\n        </div>\n    </div>`\n        $(\"#comments_viewer #top\").append(s);\n        this._tuneInput(\"code\")\n        this._tuneInput(\"name\")\n    }\n    cancel() {\n        this.clear()\n        this.hide()\n        comments.loginForm.show()\n    }\n    submit() {\n        this.getDataFromForm();\n        if (!this.checkData()) return false;\n        ///\n        var formData = new FormData();\n        formData.append(\"code\", this.code);\n        formData.append(\"name\", this.name);\n        formData.append(\"email\", comments.loginForm.email);\n        //\n        var handler = function () {\n            var form = comments.authForm\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //                        \n            console.log(this.responseText);\n            if (result.status != 'ok') {\n                if (\"#010.003\" == result.errorCode) {\n                    form.showError(\"Authorization code is wrong\");\n                } else {\n                    form.showError(result.message);\n                }\n            } else {\n                comments.sid = result.data.sid\n                comments.uid = result.data.uid\n                comments.user = result.data.user\n\n                comments.saveSessionInBrowser()\n\n                form.hide()\n                comments.commentForm.show()\n                comments.reloadComments()\n            }\n        }\n        //    \n        return comments.sendCommand(\"auth\", formData, handler);\n    }\n    clear() {\n        this.code = \"\"\n        this.name = \"\"\n        this.email = \"\"\n        super.clear()\n    }\n\n}\n////////////////// NEW COMMENT FORM /////////\nclass CommentsNewCommentForm extends CommentsAbstractForm {\n    constructor() {\n        super(\"commentForm\")\n        this.msg = \"\"\n        this.cursorEnabled = false\n        this.markX = null\n        this.markY = null\n    }\n    putDataInForm() {\n        this._setInputValue(\"msg\", this.msg)\n    }\n    // Check data\n    checkData() {\n        if (\"\" == this.msg) {\n            this.showError(\"Specify message\");\n            return false;\n        }\n        return true\n    }\n    getDataFromForm() {\n        this.msg = $(\"#comments_viewer #commentForm #msg\").val();\n    }\n    getHTML() {\n        let textareaStyle = \"font-size:14px;width:330px\"\n        let s = `\n        <div id=\"commentForm\" style=\"display:none;font-size:14px;\">\n            <div id=\"user\">\n                ${comments.user.name}&nbsp<a href=\"#\" onclick=\"comments.logout();return false;\">Logout</a>\n                <br/><br/>\n            </div>    \n            <div id=\"error\" style=\"color:red\" ></div>\n            <div>\n                <textarea id=\"msg\" style=\"${textareaStyle}\" rows=\"5\" cols=\"20\" placeholder=\"New comment\"></textarea>\n            </div>\n            <div id=\"buttons\" style=\"display: grid; gap:10px;grid-auto-rows: minmax(10px, auto); grid-template-columns: 110px auto\">\n                <div>\n                    <input id=\"send\"  style=\"${comments.styles.buttonPrimary}\" type=\"button\" onclick=\"comments.commentForm.submit();return false;\" value=\"Send\"/>\n                </div>\n                <div id=\"addMarker\" >\n                    <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.startMarkerMove();return false\" value=\"Set Marker\"/>\n                </div>\n                <div id=\"dropMarker\" style=\"display:none\">\n                    <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.stopMarkerMove();return false\" value=\"Drop Marker\"/>\n                </div>\n                <div id=\"editMarker\" style=\"display:none\">\n                    <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.startMarkerMove();return false\" value=\"Move Marker\"/>\n                    &nbsp;\n                    <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.dropMarker();return false\" value=\"Drop\"/>\n                </div>           \n            </div>\n        </div> `\n        return s\n    }\n    buildHTML() {\n        super.buildHTML()\n        let s = this.getHTML()\n        $(\"#comments_viewer #top\").append(s);\n        this._tuneInput(\"msg\", \"textarea\")\n    }\n    startMarkerMove() {\n        //\n        if (null != this.markX) {\n            commentsViewer.comments.removeCircleOnScene(\"new\")\n            this.markX = null\n            this.markY = null\n        }\n        //\n        viewer.currentPage.imageDiv.css(\"cursor\", \"url('resources/cursormap.png'), auto\")\n        viewer.currentPage.imageDiv.click(function () {\n            commentsViewer.comments.commentForm.saveMarker()\n        })\n        $(\"#comments_viewer #addMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").show()\n        $(\"#comments_viewer #editMarker\").hide()\n        //\n        this.cursorEnabled = true\n        viewer.setMouseMoveHandler(this)\n    }\n    stopMarkerMove() {\n        viewer.currentPage.imageDiv.css(\"cursor\", \"\")\n        viewer.currentPage.imageDiv.off(\"click\")\n        viewer.setMouseMoveHandler(null)\n        this.cursorEnabled = false\n        $(\"#comments_viewer #addMarker\").show()\n        $(\"#comments_viewer #dropMarker\").hide()\n        $(\"#comments_viewer #editMarker\").hide()\n    }\n    onMouseMove(x, y) {\n        this.x = Math.round(x / viewer.currentZoom) - viewer.currentPage.currentLeft\n        this.y = Math.round(y / viewer.currentZoom) - viewer.currentPage.currentTop\n    }\n    saveMarker() {\n        this.stopMarkerMove()\n        //\n        this.markX = this.x\n        this.markY = this.y\n        //\n        commentsViewer.comments.addCircleToScene(\"new\", this.markX, this.markY)\n        $(\"#comments_viewer #addMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").hide()\n        $(\"#comments_viewer #editMarker\").show()\n    }\n    dropMarker() {\n        this.markX = null\n        this.markY = null\n        commentsViewer.comments.removeCircleOnScene(\"new\")\n        //\n        $(\"#comments_viewer #addMarker\").show()\n        $(\"#comments_viewer #editMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").hide()\n    }\n    submit() {\n        this.getDataFromForm();\n        if (!this.checkData()) return false;\n        ///\n        var formData = new FormData();\n        formData.append(\"msg\", this.msg);\n        formData.append(\"pageOwnerName\", story.authorName);\n        formData.append(\"pageOwnerEmail\", story.authorEmail);\n        if (null != this.markX) {\n            formData.append(\"markX\", this.markX);\n            formData.append(\"markY\", this.markY);\n        }\n        //\n        var handler = function () {\n            var form = comments.commentForm\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //                        \n            console.log(this.responseText)\n            if (result.status != 'ok') {\n                form.showError(result.message)\n            } else {\n                console.log(result)\n                form.clear()\n                $(\"#comments_viewer #comments\").html(result.data)\n            }\n        }\n        //    \n        return comments.sendCommand(\"addComment\", formData, handler);\n    }\n    clear() {\n        this.msg = \"\"\n        this.dropMarker()\n        this.showError(\"\")\n        super.clear()\n    }\n    hide() {\n        if (this.cursorEnabled) this.stopMarkerMove()\n        super.hide()\n    }\n    hideViewer() {\n        if (this.cursorEnabled) this.stopMarkerMove()\n    }\n}\n////////////////// EDIT COMMENT FORM /////////\nclass CommentsEditCommentForm extends CommentsAbstractForm {\n    constructor(commentID) {\n        super(\"editCommentForm\")\n        this.commentID = \"\"\n        //\n        this.msg = \"\"\n        this.cursorEnabled = false\n        this.markX = null\n        this.markY = null\n    }\n    build(commentID) {\n        this.commentID = commentID\n\n        this.msgDiv = $(\"#comments #\" + this.commentID + \" #msg\")\n        if (!this.msgDiv) return false\n\n        const comment = comments.getCommentByID(commentID)\n        if (undefined == comment) return false\n        this.msg = comment['msg']\n        //\n        this.buildHTML()\n        this.putDataInForm()\n        //\n        return true\n    }\n    putDataInForm() {\n        this._setInputValue(\"msg\", this.msg)\n    }\n    // Check data\n    checkData() {\n        if (\"\" == this.msg) {\n            this.showError(\"Specify message\");\n            return false;\n        }\n        return true\n    }\n    cancel() {\n        this.msgDiv.html(commentReplaceEnds(this.msg))\n        comments.editCommentForm = null\n    }\n    getDataFromForm() {\n        this.msg = $(\"#comments #\" + this.commentID + \" #editCommentForm #msg\").val()\n    }\n    getHTML() {\n        let textareaStyle = \"font-size:14px;width:330px\"\n        let s = `\n    <div id = 'editCommentForm'  style=\"font-size:14px;\">        \n        <div id = \"error\" style = \"color:red\" ></div>\n        <div>\n            <textarea id=\"msg\" style=\"${textareaStyle}\" rows=\"5\" cols=\"20\" ></textarea>\n        </div>\n        <div id=\"buttons\" style=\"display: grid; gap:10px;grid-auto-rows: minmax(10px, auto); grid-template-columns: 110px auto\">\n            <div>\n                <input id=\"send\"  style=\"${comments.styles.buttonPrimary}\" type=\"button\" onclick=\"comments.editCommentForm.submit();return false;\" value=\"Save\"/>\n            </div>\n            <div>\n                <input id=\"send\"  style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.editCommentForm.cancel();return false;\" value=\"Cancel\"/>\n            </div>\n        `/*\n            <div id=\"addMarker\" >\n                <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.startMarkerMove();return false\" value=\"Set Marker\"/>\n            </div>\n            <div id=\"dropMarker\" style=\"display:none\">\n                <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.stopMarkerMove();return false\" value=\"Drop Marker\"/>\n            </div>\n            <div id=\"editMarker\" style=\"display:none\">\n                <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.startMarkerMove();return false\" value=\"Move Marker\"/>\n                &nbsp;\n                <input style=\"${comments.styles.buttonSecondary}\" type=\"button\" onclick=\"comments.commentForm.dropMarker();return false\" value=\"Drop\"/>\n            </div>           \n            */\n        s += `\n        </div>\n    </div > `\n        return s\n    }\n    buildHTML() {\n        super.buildHTML()\n        let s = this.getHTML()\n        this.msgDiv.html(s)\n        this._tuneInput(\"msg\", \"textarea\")\n        return true\n    }\n    startMarkerMove() {\n        //\n        if (null != this.markX) {\n            commentsViewer.comments.removeCircleOnScene(\"new\")\n            this.markX = null\n            this.markY = null\n        }\n        //\n        viewer.currentPage.imageDiv.css(\"cursor\", \"url('resources/cursormap.png'), auto\")\n        viewer.currentPage.imageDiv.click(function () {\n            commentsViewer.comments.commentForm.saveMarker()\n        })\n        $(\"#comments_viewer #addMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").show()\n        $(\"#comments_viewer #editMarker\").hide()\n        //\n        this.cursorEnabled = true\n        viewer.setMouseMoveHandler(this)\n    }\n    stopMarkerMove() {\n        viewer.currentPage.imageDiv.css(\"cursor\", \"\")\n        viewer.currentPage.imageDiv.off(\"click\")\n        viewer.setMouseMoveHandler(null)\n        this.cursorEnabled = false\n        $(\"#comments_viewer #addMarker\").show()\n        $(\"#comments_viewer #dropMarker\").hide()\n        $(\"#comments_viewer #editMarker\").hide()\n    }\n    onMouseMove(x, y) {\n        this.x = Math.round(x / viewer.currentZoom) - viewer.currentPage.currentLeft\n        this.y = Math.round(y / viewer.currentZoom) - viewer.currentPage.currentTop\n    }\n    saveMarker() {\n        this.stopMarkerMove()\n        //\n        this.markX = this.x\n        this.markY = this.y\n        //\n        commentsViewer.comments.addCircleToScene(\"new\", this.markX, this.markY)\n        $(\"#comments_viewer #addMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").hide()\n        $(\"#comments_viewer #editMarker\").show()\n    }\n    dropMarker() {\n        this.markX = null\n        this.markY = null\n        commentsViewer.comments.removeCircleOnScene(\"new\")\n        //\n        $(\"#comments_viewer #addMarker\").show()\n        $(\"#comments_viewer #editMarker\").hide()\n        $(\"#comments_viewer #dropMarker\").hide()\n    }\n    submit() {\n        this.getDataFromForm();\n        if (!this.checkData()) return false;\n        ///\n        var formData = new FormData();\n        formData.append(\"msg\", this.msg);\n        formData.append(\"commentID\", this.commentID);\n        if (null != this.markX) {\n            formData.append(\"markX\", this.markX);\n            formData.append(\"markY\", this.markY);\n        }\n        //\n        var handler = function () {\n            var form = comments.editCommentForm\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //                        \n            console.log(this.responseText)\n            if (result.status != 'ok') {\n                form.showError(result.message)\n            } else {\n                console.log(result)\n                form.cancel()\n                //form.clear()\n                //$(\"#comments_viewer #comments\").html(result.data)\n            }\n        }\n        //    \n        return comments.sendCommand(\"updateComment\", formData, handler);\n    }\n    clear() {\n        this.msg = \"\"\n        this.dropMarker()\n        this.showError(\"\")\n        super.clear()\n    }\n    hide() {\n        if (this.cursorEnabled) this.stopMarkerMove()\n        super.hide()\n    }\n    hideViewer() {\n        if (this.cursorEnabled) this.stopMarkerMove()\n    }\n}\n////\nclass Comments {\n    constructor(forumID, url, sid, user) {\n        this.forumID = forumID\n        this.url = url\n        this.currentPage = null\n\n        // load user data from browser storage   \n        this.sid = sid\n        this.uid = user.uid\n        this.user = user\n        if (\"\" != this.sid) {\n            this.saveSessionInBrowser()\n        }\n        //\n        this.commentList = null\n        //\n        this.currentForm = null\n        this.loginForm = new CommentsLoginForm()\n        this.authForm = new CommentsAuthForm()\n        this.commentForm = new CommentsNewCommentForm()\n        this.editCommentForm = null\n        //\n        this.inputFocused = false\n        commentsViewer.comments = this\n        //\n        this.styles = {\n            buttonPrimary: \"margin-top:4px;border: none;border-radius:4px;font-size:14px;background-color:#008CBA;color:white;width:100px;height:30px\",\n            buttonSecondary: \"margin-top:4px;border: none;border-radius:4px;font-size:14px;background-color:#e7e7e7; color: black;width:100px;height:30px\",\n            input: \"font-size:16px;margin-left:0px;padding: 0.25em 0.5em;background-color:var(--color-background);border:2px solid var(--color-border);border-radius:4px;\"\n        }\n    }\n\n    clearSession() {\n        this.uid = \"\"\n        this.sid = \"\"\n        this.user = []\n        this.saveSessionInBrowser()\n        this.loginForm.clear()\n        this.authForm.clear()\n        this.commentForm.clear()\n        //\n        this.commentForm.hide()\n        this.loginForm.show()\n    }\n    saveSessionInBrowser() {\n        window.localStorage.setItem(\"comments-uid\", this.uid)\n        window.localStorage.setItem(\"comments-sid\", this.sid)\n    }\n    processRequestResult(result) {\n        console.log(result)\n\n        if (\n            (\"ok\" == result.status && result.dropSession)\n            ||\n            (\"error\" == result.status && \"#001.003\" == result.errorCode)\n        ) {\n            this.clearSession()\n        } else\n            return false\n        return true\n    }\n    ///\n    sendCommand(cmd, formData, handler) {\n        var xhr = new XMLHttpRequest()\n        xhr.open('POST', this.url + \"?fid=\" + this.forumID + \"&cmd=\" + cmd, true)\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = handler\n        if (\"\" != this.uid && \"\" != this.sid) {\n            formData.append(\"uid\", this.uid);\n            formData.append(\"sid\", this.sid);\n        }\n        xhr.send(formData);\n    }\n    ///////\n    logout() {\n        var formData = new FormData();\n        var handler = function () {\n            console.log(this.responseText)\n            comments.clearSession()\n        }\n        //    \n        return this.sendCommand(\"logout\", formData, handler);\n    }\n    ////////\n    reloadComments() {\n        $(\"#comments_viewer #comments\").html(\"Loading...\")\n        ///\n        var formData = new FormData();\n        var handler = function () {\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //                        \n            if (result.status != 'ok') {\n                console.log(result.message)\n            } else {\n                comments.build(result.data)\n            }\n        }\n        //    \n        return comments.sendCommand(\"getComments\", formData, handler);\n    }\n    removeComment(commentID) {\n        var formData = new FormData();\n        formData.append(\"commentID\", commentID);\n        var handler = function () {\n            var result = JSON.parse(this.responseText);\n            if (comments.processRequestResult(result)) return\n            //                        \n            if (result.status != 'ok') {\n                console.log(result.message)\n                alert(\"Can't remove the comment. \" + result.message)\n            } else {\n                // remove comment\n                $(\"#comments #\" + commentID).remove()\n                // remove marker (if exists)\n                let m = $('#commentsScene svg #' + commentID)\n                if (m) m.remove()\n            }\n        }\n        //    \n        return comments.sendCommand(\"removeComment\", formData, handler);\n    }\n    editComment(commentID) {\n        // Create edit form\n        this.editCommentForm = new CommentsEditCommentForm()\n        if (!this.editCommentForm.build(commentID)) {\n            this.editCommentForm = null\n            return false\n        }\n    }\n    getCommentByID(commentID) {\n        const found = this.commentList['comments'].find(c => c['id'] == commentID)\n        return found\n    }\n    ///////\n    build(commentList) {\n        this.commentList = commentList\n        //        \n        if (this.sid != \"\") {\n            this.commentForm.show()\n        } else {\n            this.loginForm.show()\n        }\n        this._buildScene()\n        this._buildMarkers()\n        this._buildComments(commentList)\n    }\n    //\n    _buildComments(commentList) {\n        let counterStyle = \"font-weight:bold;\"\n        let visited = commentList['visited']\n        //\n        let code = \"\"\n        //\n        if (commentList['comments'].length > 0) {\n            code += `\n            <div id = \"title\" style = \"font-weight:bold;\" >Comments</div> <br />\n        `\n        }\n        let counter = commentList['comments'].length\n        commentList['comments'].forEach(function (comment) {\n            ///\n            var createdDate = new Date(comment['created'] * 1000)\n            var createdStr = createdDate.toLocaleDateString() + \" \" + createdDate.toLocaleTimeString()\n            if (visited < comment['created']) {\n                createdStr += \"&nbsp;<b>New</b>\"\n            }\n            ///            \n            let uid = comment['uid']\n            let user = commentList['users'][uid]\n            let commentID = comment['id']\n            let actions = \"\"\n            if (uid == this.uid) {\n                actions += `&nbsp; <a href=\"#\" onclick=\"comments.editComment('${commentID}');return false\">Edit</a>`\n                actions += `&nbsp; <a href=\"#\" onclick=\"comments.removeComment('${commentID}');return false\">Remove</a>`\n            }\n            //\n            code += `\n            <div id = \"${commentID}\" style = \"font-size:14px;margin-top:10px\" >\n                <div style=\"display: grid; gap:10px;grid-auto-rows: minmax(10px, auto); grid-template-columns: 10px auto auto\">\n                    <div style=\"${counterStyle}\">${counter}</div>                    \n                    <div style=\"\">\n                        <div class=\"tooltip\">${user['name']}\n                            <span class=\"tooltiptext\">${user['email']}</span>\n                        </div>\n                        ${actions}<br/>${createdStr}<br/>\n                        <span id=\"msg\">${commentReplaceEnds(comment['msg'])}<span>\n                    </div>                    \n                </div>\n            </div>\n            `\n            counter--\n        }, this)\n        $(\"#comments_viewer #comments\").html(code)\n        //\n        commentsViewer.updateCommentCounter(commentList['comments'].length)\n    }\n    _buildMarkers() {\n        this._clearScene()\n        let counter = this.commentList['comments'].length\n        //\n        this.commentList['comments'].reverse().forEach(function (comment) {\n            if (undefined != comment['markX']) {\n                let id = comment['id']\n                this.addCircleToScene(id, comment['markX'], comment['markY'], counter)\n            }\n            counter--\n        }, this)\n    }\n    //\n    _buildScene() {\n        this._dropScene()\n        //\n        let page = viewer.currentPage\n        let width = page.imageDiv.width()\n        let height = page.imageDiv.height()\n\n        let code = `<div id=\"commentsScene\"><svg height=\"${height}\" width=\"${width}\">\n                    </svg>\n                    </div>`\n        page.linksDiv.append(code)\n        //\n        this.currentPage = page\n    }\n    addCircleToScene(id, x, y, number = \"\") {\n        let r = 20\n        x = Number(x) - 10\n        y = Number(y) - 20\n        let code = `\n        <svg id=\"${id}\" x=\"${x}\" y=\"${y}\" width=\"42\" height=\"60\">\n                        <style>\n                            .small {font: 13px sans-serif; }\n        </style>\n                        <g>\n                            <circle cx=\"25\" cy=\"20\" r=\"10\" fill=\"white\"/>\n                            <text x=\"25\" y=\"18\" dy=\"0\" class=\"small\" dominant-baseline=\"middle\" text-anchor=\"middle\">${number}</text>\n                            <path style=\" stroke:none;fill-rule:nonzero;fill:rgb(255,79,79);fill-opacity:1;\" d=\"M 25 0 L 25 7.523438 C 30.1875 7.523438 34.394531 11.730469 34.394531 16.917969 C 34.394531 22.105469 30.1875 26.308594 25 26.308594 L 25 50 C 25 50 41.917969 26.257812 41.917969 16.917969 C 41.917969 7.574219 34.34375 0 25 0 Z M 25 0 \" />\n                            <path style=\" stroke:none;fill-rule:nonzero;fill:rgb(255,179,179);fill-opacity:1;\" d=\"M 25 26.308594 C 19.8125 26.308594 15.605469 22.105469 15.605469 16.917969 C 15.605469 11.730469 19.8125 7.523438 25 7.523438 L 25 0 C 15.65625 0 8.082031 7.574219 8.082031 16.917969 C 8.082031 26.257812 25 50 25 50 Z M 25 26.308594 \" />\n                        </g>\n                    </svg>\n        `\n        //    `<circle id=\"${id}\" cx=\"${x}\" cy=\"${y}\" r=\"${r}\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" />`\n        $('#commentsScene svg').append(code)\n        $('#commentsScene').html($('#commentsScene').html())\n    }\n    removeCircleOnScene(id) {\n        $('#commentsScene svg #' + id).remove()\n    }\n    _dropScene() {\n        $('#commentsScene').remove()\n    }\n    _clearScene() {\n        $('#commentsScene svg').html(\"\")\n    }\n    //\n    showViewer() {\n        if (this.currentPage.index != viewer.currentPage.index) {\n            this.reloadComments()\n        } else {\n            this._buildScene()\n            this._buildMarkers()\n        }\n    }\n    hideViewer() {\n        if (this.currentForm) this.currentForm.hideViewer()\n        //\n        this._dropScene()\n        //        \n    }\n}"
  },
  {
    "path": "comments/backend/lib/Forum.php",
    "content": "<?php\n\n////////////// ERROR LIST ///////////////////////////////////////\n// 001. CONTEXT\nconst ERROR_UNKNOWN_CMD                     = \"#001.001\";\nconst ERROR_FORUM_EMPTY_ID                  = \"#001.002\";\nconst ERROR_COMMON_DROP_SESSION             = \"#001.003\";\n\n// 002. INIT SERVER CONFIG\nconst ERROR_CANT_LOAD_SERVERCONFIG          = \"#002.001\";\nconst ERROR_CANT_PARSE_SERVERCONFIG         = \"#002.002\";\n\n// 003. INIT FORUM CONFIG\nconst ERROR_WRONG_FORUM_ID                  = \"#003.001\";\nconst ERROR_CANT_LOAD_FORUMCONFIG           = \"#003.002\";\nconst ERROR_CANT_PARSE_FORUMCONFIG          = \"#003.003\";\n\n// 004. SAVE FORUM CONFIG\nconst ERROR_CANT_ENCODE_FORUMCONFIG         = \"#004.001\";\nconst ERROR_CANT_SAVE_FORUMCONFIG           = \"#004.002\";\nconst ERROR_CANT_CREATE_FORUM_FOLDER        = \"#004.003\";\n\n// 005. PAGE SAVE/LOAD\nconst ERROR_PAGE_EMPTY_ID                   = \"#005.001\";\nconst ERROR_CANT_SAVE_PAGE                  = \"#005.002\";\n\n// 006. ADD COMMENT\nconst ERROR_CANT_ADD_COMMENT_EMPTY_MSG       = \"#006.001\";\n\n// 007. COMMON COMMENT\nconst ERROR_CANT_LOAD_COMMENTS              = \"#007.001\";\nconst ERROR_CANT_PARSE_COMMENTS             = \"#007.002\";\n\n// 008. USERS\nconst ERROR_CANT_LOAD_USERS                 = \"#008.001\";\nconst ERROR_CANT_PARSE_USERS                = \"#008.002\";\nconst ERROR_CANT_SAVE_USERS                 = \"#008.003\";\nconst ERROR_CANT_ENCODE_USERS               = \"#008.004\";\nconst ERROR_USER_EMAIL_EMPTY                = \"#008.005\";\nconst ERROR_USER_NAME_EMPTY                 = \"#008.006\";\n\n// 009. SEND AUTH CODE\nconst ERROR_LOGIN_CANT_LOAD_AUTH_CODE            = \"#009.001\";\nconst ERROR_LOGIN_CANT_SAVE_AUTH_CODE            = \"#009.002\";\nconst ERROR_LOGIN_CANT_CREATE_AUTHCODES_FOLDER    = \"#009.003\";\nconst ERROR_LOGIN_AUTH_CODE_EMPTY_EMAIL           = \"#009.005\";\n\n// 010. AUTH USER\nconst ERROR_AUTH_CODE_EMPTY                         = \"#010.001\";\nconst ERROR_AUTH_EMAIL_EMPTY                         = \"#010.002\";\nconst ERROR_AUTH_CODE_WRONG                         = \"#010.003\";\nconst ERROR_AUTH_CANT_CREATE_SESSIONS_FOLDER         = \"#010.004\";\nconst ERROR_AUTH_CANT_READ_SESSION                   = \"#010.005\";\nconst ERROR_AUTH_CANT_SAVE_SESSION                   = \"#010.006\";\nconst ERROR_AUTH_CANT_FIND_USER                   = \"#010.007\";\n\n// 011 PAGE VISITS\nconst ERROR_PAGE_VISITS_CANT_CREATE_FOLDER           = \"#011.001\";\nconst ERROR_PAGE_VISITS_CANT_CREATE_FILE           = \"#011.002\";\nconst ERROR_PAGE_VISITS_CANT_DECODE_FILE           = \"#011.003\";\nconst ERROR_PAGE_VISITS_CANT_ENCODE_FILE           = \"#011.004\";\nconst ERROR_PAGE_VISITS_CANT_SAVE_FILE           = \"#011.005\";\n\n\n// 012.REMOVE COMMENT\nconst ERROR_REMOVE_COMMENT_NOPAGE             = \"#012.001\";\nconst ERROR_REMOVE_COMMENT_COMMENTID_EMPTY          =\"#012.002\";\nconst ERROR_REMOVE_COMMENT_NOCOMMENT                =\"#012.003\";\nconst ERROR_REMOVE_COMMENT_WRONG_OWNER              =\"#012.004\";\n\n// 013.UPDATE COMMENT\nconst ERROR_UPDATE_COMMENT_NOPAGE                  = \"#013.001\";\nconst ERROR_UPDATE_COMMENT_COMMENTID_EMPTY          =\"#013.002\";\nconst ERROR_UPDATE_COMMENT_NOCOMMENT                =\"#013.003\";\nconst ERROR_UPDATE_COMMENT_WRONG_OWNER              =\"#013.004\";\nconst ERROR_UPDATE_COMMENT_MSG_EMPTY                =\"#013.005\";\n\n// 014. PROJECT SAVE/LOAD\nconst ERROR_PROJECT_EMPTY_ID                        = \"#014.001\";\nconst ERROR_CANT_LOAD_PROJECT                       = \"#014.003\";\nconst ERROR_CANT_PARSE_PROJECT                      = \"#014.004\";\nconst ERROR_CANT_ENCODE_PROJECT                     = \"#014.005\";\nconst ERROR_CANT_SAVE_PROJECT                       = \"#014.006\";\nconst ERROR_CANT_CREATE_PROJECT_FOLDER              = \"#014.007\";\nconst ERROR_CANT_LOAD_PROJECT_PAGE                  = \"#014.008\";\n\n\n////////////////////////////////////////////////////////////////\nconst DEF_USER_INFO = [\n    \"name\"=>\"\",    \n    \"email\"=>\"\"\n];\n\nconst DEF_COMMENT_DATA = [\n    \"msg\"=>\"\",    \n    \"id\"=>\"\",\n    \"uid\"=>null,\n    \"created\"=>null\n];\n\nconst DEF_PAGE_INFO = [\n    \"created\"=>\"\",\n    \"ownerName\"=>\"\",\n    \"ownerEmail\"=>\"\",\n    \"commentCounter\"=>1,\n    \"comments\"=>[],\n];\n\nif (!function_exists('getallheaders')) {\n    function getallheaders() {\n    $headers = [];\n    foreach ($_SERVER as $name => $value) {\n        if (substr($name, 0, 5) == 'HTTP_') {\n            $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;\n        }\n    }\n    return $headers;\n    }\n}\n\nclass Page\n{\n    private $info =  [];\n    private $intID = null;\n    public  $lastError = \"\";\n    private $pagePath = \"\";\n\n    public static function build($pubID){\n        $obj = new Page();\n        $obj->pubID = $pubID;\n        $obj->init();        \n        return $obj;\n    }\n\n    protected function setError($errorCode){\n        $this->lastError = $errorCode;\n        return False;\n    }\n\n    public function addComment(){\n        $forum = Forum::$o;\n        // Load page\n        if(!$this->load()) return False;\n        // Create if required        \n        if(False==$this->intID && !$this->create()) return False;\n\n        // Validate user\n        $uid = $forum->uid;\n        if(False===$uid) return False;\n        // Init new comment\n        $comment = DEF_COMMENT_DATA;\n        $comment['uid'] = $uid;\n        $comment['created'] =  time();\n        //\n        $comment['msg'] = http_post_param('msg');\n        if(''==$comment['msg']) return $this->setError(ERROR_CANT_ADD_COMMENT_EMPTY_MSG);\n        //\n        if(http_post_param('markX')!=\"\"){\n            $comment['markX'] = http_post_param('markX');\n            $comment['markY'] = http_post_param('markY');\n        }\n        //\n        $comment['id'] = $this->info['commentCounter']++;\n        array_push($this->info['comments'],$comment);\n        /// Save  \n        if(!$this->save()) return False;\n        /// Send notification\n        $this->notify($comment);\n        return True;        \n    }\n\n    public function removeComment(){\n        $forum = Forum::$o;\n        // Load page\n        if(!$this->load()) return False;\n        // Skip non-existing page\n        if(False==$this->intID) return $this->setError(ERROR_REMOVE_COMMENT_NOPAGE);\n\n        // Try to remove the comment by ID\n        $commentID = http_post_param(\"commentID\");\n        if(\"\"==$commentID){\n            return $this->setError(ERROR_REMOVE_COMMENT_COMMENTID_EMPTY);\n        }\n        // Check comment ownership\n        $commentsToRemove = array_values(array_filter($this->info['comments'],function($c) use ($commentID){            \n            return $c['id']==$commentID;\n        }));\n        if(count( $commentsToRemove )==0){\n            return $this->setError(ERROR_REMOVE_COMMENT_NOCOMMENT);\n        }\n        $commentToRemove =  $commentsToRemove[0];\n        if($commentToRemove['uid']!=$forum->uid){\n            return $this->setError(ERROR_REMOVE_COMMENT_WRONG_OWNER);\n        }\n        // Remove\n        $this->info['comments'] = array_values(array_filter($this->info['comments'],function($c) use ($commentID){            \n            return $c['id']!=$commentID;\n        }));\n        /// Save  \n        if(!$this->save()) return False;\n        return True;\n    }\n\n    public function updateComment(){\n        $forum = Forum::$o;\n        // Load page\n        if(!$this->load()) return False;\n        // Skip non-existing page\n        if(False==$this->intID) return $this->setError(ERROR_UPDATE_COMMENT_NOPAGE);\n\n        // Try to remove the comment by ID\n        $commentID = http_post_param(\"commentID\");\n        if(\"\"==$commentID){\n            return $this->setError(ERROR_UPDATE_COMMENT_COMMENTID_EMPTY);\n        }\n        // Check comment ownership\n        $commentsToUpdate = array_values(array_filter($this->info['comments'],function($c) use ($commentID){            \n            return $c['id']==$commentID;\n        }));\n        if(count( $commentsToUpdate )==0){\n            return $this->setError(ERROR_UPDATE_COMMENT_NOCOMMENT);\n        }\n        $commentToUpdate =  $commentsToUpdate[0];\n        if($commentToUpdate['uid']!=$forum->uid){\n            return $this->setError(ERROR_UPDATE_COMMENT_WRONG_OWNER);\n        }\n        // Check new data\n        $msg = http_post_param(\"msg\");\n        if(\"\"==$commentID){\n            return $this->setError(ERROR_UPDATE_COMMENT_MSG_EMPTY);\n        }\n        // UPDATE\n        array_walk($this->info['comments'],function($c,$key) use ($commentID,$msg){\n            if($c['id']==$commentID)\n                $this->info['comments'][$key]['msg'] = $msg;\n        });\n        /// Save  \n        if(!$this->save()) return False;\n        return True;\n    }\n\n    private function notify($comment){\n        $forum = Forum::$o;\n\n        $uid= $comment['uid'];\n        $user = Forum::$o->getUserByUID($uid);\n        $userName = False===$user?\"Unknown user\":$user['name'];\n        $userEmail = False===$user?\"\":$user['email'];\n        $pageURL = $_SERVER['HTTP_REFERER'];\n        $message = $comment['msg'];\n\n        $email = [\n            \"to\"=>[],\n            \"toUserIDs\"=>[],\n            \"subject\"=>\"New comment added by {$userName}\",\n            \"body\"=> <<<EOL\n            {$userName} commented: {$message}<br/>\n            <br/>\n            View <a href=\"{$pageURL}\">comments</>\nEOL\n        ];  \n        $email['toUserIDs'] = array_values(\n            array_unique(\n                array_values(\n                    array_map(\n                        function ($user){return $user['uid'];},$this->info['comments']\n                    )\n                )\n            )\n        );\n        // add page owner to notify\n        if($this->info['ownerEmail']!=\"\" && $forum->user['email']!=$this->info['ownerEmail']){\n            $email['to'] = [\n                [ // add page owner\n                    \"email\"=>$this->info['ownerEmail'],\n                    \"name\"=>$this->info['ownerName'],\n                ]            \n                ];\n        }\n\n        // exclude current comment author\n        $email['toUserIDs'] = array_values(array_filter( \n            $email['toUserIDs'],function($userID) use ($uid){return $userID!=$uid;}\n        ));\n        Forum::$o->sendEmail($email);\n    }\n    \n    public function getInfo(){\n        // return empty data for non-created page\n        $res = [\n            'commentsTotal'=>0,\n            'commentsNew'=>0\n        ];\n        if(null==$this->intID) return $res;\n        //\n        // load data for existing page\n        if(False===$this->load()) return False;\n        //\n        $res['commentsTotal'] = count($this->info['comments']);\n        return $res;\n    }\n\n    public function getExtendedComments(){\n        // return empty data for non-created page\n        if(null==$this->intID) return [\n            'comments'=>[],\n            'users'=>[]\n        ];\n        // load data for existing page\n        if(False===$this->load()) return False;\n        $comments = $this->info['comments'];\n        // load users info\n        $usersInfo = Forum::$o->loadUsersInfo();\n        if(False===$usersInfo) return  $this->setError(Forum::$o->lastError);\n        //\n        $userList = [];\n        foreach ($comments as &$comment) {\n            // find user by UID            \n            $uid = $comment['uid'];            \n            $email = \"\";\n            $user = null;\n            if(!array_key_exists($uid,$usersInfo['list'])){    \n                $user = [\n                    'id'=>$uid,\n                    'email'=>'',\n                    'name'=>'Deleted user #'.$uid.json_encode($usersInfo)\n                ];\n            }else{\n                $user = $usersInfo['list'][$uid];\n            }            \n            $userList[$uid] = $user;            \n            //            \n        }\n        $visited = $this->getUserVisited(Forum::$o->uid);\n        return [\n            'comments'=>$comments,\n            'users'=>$userList,\n            'visited'=>$visited\n        ];\n    }\n\n    protected function init(){\n        $forum  = Forum::$o;\n         // Check data\n         if(\"\" == $this->pubID) return $this->setError(ERROR_PAGE_EMPTY_ID);\n        //        \n        $this->intID = $forum->findPageIntIDByPubID($this->pubID);\n        return True;\n    }\n\n    protected function load(){\n        $forum  = Forum::$o;\n\n         // Check ID\n        if(\"\" == $this->pubID) return $this->setError(ERROR_PAGE_EMPTY_ID);\n        \n        // find page data in forum config   \n        if(False===$this->intID){\n            return True;\n        }else{\n            $this->info = $this->loadJSON();\n            if(\"\"!=$this->lastError) return False;\n        }\n        //        \n        return True;\n    }\n\n    protected function create(){\n     \n        // create new page\n        $this->intID = Forum::$o->generatePageIntIDForPubID($this->pubID);\n\n        // update parent project\n        $project = Forum::$o->buildProject();\n        if(\"\"!=$project->lastError){\n            return $this->setError($project->lastError);\n        }\n        if(!$project->addPageIDs( $this->pubID,$this->intID )){\n            return $this->setError($project->lastError);\n        }\n\n        // configure itself\n        $this->info = DEF_PAGE_INFO;                        \n        $this->info['ownerName'] =  http_post_param(\"pageOwnerName\");\n        $this->info['ownerEmail'] =  http_post_param(\"pageOwnerEmail\");\n        if(False===$this->saveToJSON())\n            return $this->setError(ERROR_CANT_SAVE_PAGE);         \n        return True;   \n    }\n\n    protected function save(){\n        return $this->saveToJSON();\n    }\n\n    private function getJSONPath(){\n        return  Forum::$o->getBasePage().\"/page-\".$this->intID.\".json\";\n    }\n\n\n    private function getUserVisitsBasePath(){\n        return  Forum::$o->getBasePage().\"/page-visits\";\n    }\n    \n    private function getUserVisitsPath($uid){\n        return $this->getUserVisitsBasePath().\"/\".$this->intID.\"-\".$uid.\".json\";\n    }\n\n    protected function loadJSON(){        \n        // check if page config exists\n        if(!file_exists($this->getJSONPath())) return DEF_PAGE_INFO;       \n\n        $content = file_get_contents($this->getJSONPath());\n        if($content===FALSE){\n            $this->setError(ERROR_CANT_LOAD_COMMENTS);        \n            return [];\n        }               \n        // decode a text config into a array\n        $json = json_decode( $content, true );\n        if($json==NULL){\n            $this->setError(ERROR_CANT_PARSE_COMMENTS);        \n            return [];\n        }    \n\n        return $json;\n    }\n\n    protected function saveToJSON(){\n         // encode an array into a json\n         $text = json_encode($this->info);\n         if($text===False){\n            $this->setError(ERROR_CANT_ENCODE_FORUMCONFIG);       \n            return False;\n         }   \n         // save into a file\n         if( False===file_put_contents($this->getJSONPath(),$text)){\n            $this->setError(ERROR_CANT_SAVE_FORUMCONFIG);       \n            return False;\n         }\n         return True;\n\n    }\n\n    // return previous visited date and save new\n    protected function getUserVisited($uid){        \n        $visited = time();\n        if(\"\"==$uid) return $visited;\n        ///\n        $data = [\"visited\"=>$visited];\n        // Check if folder \"/page-visits\" exists\n        if(!file_exists($this->getUserVisitsBasePath())) {\n            if(False==mkdir($this->getUserVisitsBasePath())) return $this->setError(ERROR_PAGE_VISITS_CANT_CREATE_FOLDER);\n        }\n        // Read existing file\n        $fileName = $this->getUserVisitsPath($uid);\n        if(file_exists($fileName)) {\n            $dataStr = file_get_contents($fileName);\n            $data = json_decode($dataStr,true);\n            if($data===False){\n                $this->setError(ERROR_CANT_ENCODE_USERS);       \n                return False;\n            } \n            $visited = $data[\"visited\"];       \n        }\n\n        // Save new visited time\n        $data[\"visited\"] = time();\n    \n        // encode an array into a json\n        $dataStr = json_encode($data);\n        if($dataStr===False){\n           $this->setError(ERROR_PAGE_VISITS_CANT_ENCODE_FILE);       \n           return False;\n        }   \n        // save into a file\n        if( False===file_put_contents($fileName,$dataStr)){\n           $this->setError(ERROR_PAGE_VISITS_CANT_SAVE_FILE);       \n           return False;\n        }\n\n        return $visited;\n   }\n\n    \n}\n\n////////////////////////////////////////////////////////////////\nconst DEF_PROJECT_INFO = [\n    \"pages\"=>[],\n];\n\nclass Project\n{\n    private $info =  [];\n    private $intID = null;\n    public  $lastError = \"\";\n\n    public static function build($pubID){\n        $obj = new Project();\n        $obj->pubID = $pubID;\n        $obj->init();        \n        return $obj;\n    }\n\n    protected function setError($errorCode){\n        $this->lastError = $errorCode;\n        return False;\n    }\n\n    protected function init(){\n        $forum  = Forum::$o;\n         // Check data\n         if(\"\" == $this->pubID) return $this->setError(ERROR_PROJECT_EMPTY_ID);    \n         //\n        $this->intID = $forum->findProjectIntIDByPubID($this->pubID);\n        return True;\n    }\n\n    protected function load(){\n        $forum  = Forum::$o;\n\n         // Check ID\n        if(\"\" == $this->pubID) return $this->setError(ERROR_PROJECT_EMPTY_ID);\n        \n        // find project data in forum config   \n        if(False===$this->intID){\n            return True;\n        }else{\n            $this->info = $this->loadJSON();\n            if(\"\"!=$this->lastError) return False;\n        }\n        //        \n        return True;\n    }\n\n    protected function create(){     \n        // create new project\n        $this->intID = Forum::$o->generateProjectIntIDForPubID($this->pubID);\n        $this->info = DEF_PROJECT_INFO;                        \n        if(False===$this->saveToJSON())\n            return $this->setError(ERROR_CANT_SAVE_PROJECT);\n        return True;   \n    }\n\n\n    public function getProjectInfo(){\n        // Load self\n        if(!$this->load()) return False;\n        //\n        $info = [];\n        foreach ($this->info[\"pages\"] as $pagePubID => $pageIntID) {\n            $page = Page::build($pagePubID);\n            if(\"\"!=$page->lastError){\n                $this->setError(ERROR_CANT_LOAD_PROJECT_PAGE);\n                return False;\n            }\n            //\n            $pageInfo = $page->getInfo();\n            if(False==$pageInfo){\n                $this->setError(ERROR_CANT_LOAD_PROJECT_PAGE);\n                return False;\n            }\n            //\n            $pageName = substr(strrchr($pagePubID, \"?\"),1);\n            $info[$pageName] = $pageInfo;\n            //\n        }\n        return $info;\n    }\n\n    public function addPageIDs($pagePubID,$pageIntID){                \n        // Load self\n        if(!$this->load()) return False;\n        // Create if required        \n        if(False==$this->intID && !$this->create()) return False;\n\n        // Update page list\n        $this->info['pages'][$pagePubID] = $pageIntID;\n        return $this->save();\n    }\n\n    protected function save(){\n        return $this->saveToJSON();\n    }\n\n    private function getJSONPath(){\n        return  Forum::$o->getBaseProject().\"/project-\".$this->intID.\".json\";\n    }\n\n    protected function loadJSON(){        \n        // check if page config exists\n        if(!file_exists($this->getJSONPath())) return DEF_PROJECT_INFO;       \n\n        $content = file_get_contents($this->getJSONPath());\n        if($content===FALSE){\n            $this->setError(ERROR_CANT_LOAD_PROJECT);        \n            return [];\n        }               \n        // decode a text config into a array\n        $json = json_decode( $content, true );\n        if($json==NULL){\n            $this->setError(ERROR_CANT_PARSE_PROJECT);        \n            return [];\n        }    \n\n        return $json;\n    }\n\n    protected function saveToJSON(){\n         // encode an array into a json\n         $text = json_encode($this->info);\n         if($text===False){\n            $this->setError(ERROR_CANT_ENCODE_PROJECT);       \n            return False;\n         }   \n         // save into a file\n         if( False===file_put_contents($this->getJSONPath(),$text)){\n            $this->setError(ERROR_CANT_SAVE_PROJECT);       \n            return False;\n         }\n         return True;\n\n    }\n}\n////////////////////////////////////////////////////////////////\n\nconst DEF_FORUM_CONFIG = [\n    \"name\"=> \"default\",\n    \"pageCounter\"=>1,\n    \"pages\"=>[],    \n    \"projectCounter\"=>1,\n    \"projects\"=>[]\n];\n\nclass Forum\n{\n    public static $o = null;\n\n    public $lastError = \"\";\n    private $forumID = \"\";\n    private $basePath = \"\";\n    private $forumConfigPath = \"\";\n    private $usersFilePath = \"\";\n    private $config =null;\n    private $serverConfig = null;\n    private $users = null;\n    public $user = [];\n    \n    public static function build($forumID){\n        $obj = new Forum();\n        $obj->forumID = $forumID;\n        $obj->init();\n        return $obj;\n    }\n\n    public function forumID(){\n        return $this->forumID;\n    }\n\n    public function generatePageIntIDForPubID($pagePubID){\n        $newIntID = $this->config['pageCounter']++;\n        $this->config['pages'][  $pagePubID ] = $newIntID;\n        \n        if(False===$this->saveForumConfig()) return False;\n\n        return $newIntID;\n    }\n\n    public function findPageIntIDByPubID($pubID){\n        if(!array_key_exists($pubID,$this->config['pages'])){\n            return False;\n        }\n        return $this->config['pages'][$pubID];\n    }    \n\n    public function generateProjectIntIDForPubID($pubID){\n        $newIntID = $this->config['projectCounter']++;\n        $this->config['projects'][  $pubID ] = $newIntID;\n        \n        if(False===$this->saveForumConfig()) return False;\n\n        return $newIntID;\n    }\n\n    public function findProjectIntIDByPubID($pubID){\n        if(!array_key_exists($pubID,$this->config['projects'])){\n            return False;\n        }\n        return $this->config['projects'][$pubID];\n    }        \n    \n    \n    public function logout(){\n        if(\"\"!=$this->sid){\n            $this->_dropSession($this->sid);\n            $this->sid = \"\";\n            $this->uid = \"\";\n            $this->user = [];\n        }\n\n        return [];\n    }\n\n    public function login(){\n        $email = http_post_param('email');\n        if(\"\"==$email){            \n            $this->setError(ERROR_LOGIN_AUTH_CODE_EMPTY_EMAIL);       \n            return False;\n        }\n        //\n        $code = $this->_createAuthCode($email);        \n        if(False===$code) return False;\n        if(False===$this->_loginSendCode($email,$code)) return False;    \n        //\n        $usersInfo = $this->loadUsersInfo();\n        if(False===$usersInfo) return False;\n        $user = $this->findUserByEmail($usersInfo,$email);\n\n        $result = [\n            \"exists\"=>False!==$user\n        ];\n        //\n        return $result;\n    }\n\n    public function auth(){\n        $code = http_post_param('code');\n        $email = http_post_param('email');\n        $name = http_post_param('name');\n        if(\"\"==$code) return  $this->setError(ERROR_AUTH_CODE_EMPTY);\n        if(\"\"==$email) return  $this->setError(ERROR_AUTH_EMAIL_EMPTY);        \n\n        // Check code\n        $email = $this->_getAuthEmail($code);\n        if(False===$email) return False;\n        if(\"\"==$email) return $this->setError(ERROR_AUTH_CODE_WRONG);\n        \n        // Drop auth code file\n        $this->_dropAuthCode($code);\n\n        $user = $this->getUserByEmail($email,$name);\n        if(False===$user) return False;\n        if(null==$user){\n            return $this->setError(ERROR_AUTH_CANT_FIND_USER);\n        }\n        $uid = $user['uid'];\n\n        // Auth user\n        {            \n            $sid = $this->_createSession($uid);\n            if(False===$sid) return False;\n            $this->uid = $uid;\n            $this->sid = $sid;\n        }\n\n        $result = [\n            \"sid\"=>$this->sid,\n            \"uid\"=>$this->uid,\n            \"user\"=>$user,\n        ];\n        //\n        return $result;\n    }\n    /// Auth codes\n    private function _createAuthCode($uid){\n        $code = bin2hex(random_bytes(5));        \n        if(False===$this->_saveAuthCode($code,$uid)) return False;\n        return $code;\n    }\n\n    private function _checkAuthCodesFolder(){        \n        if(file_exists($this->authCodesPath)) return True;\n        // create a folder for session files (only once)\n        if(False==mkdir($this->authCodesPath)) return $this->setError(ERROR_LOGIN_CANT_CREATE_AUTHCODES_FOLDER);\n        \n        return True;\n    }\n    \n    private function _getAuthEmail($code){\n        if(False===$this->_checkAuthCodesFolder()) return False;\n        \n        // Check existing\n        $codePath = $this->authCodesPath.\"/\".$code;\n        if(!file_exists($codePath)) return \"\";\n\n        // Read existing session\n        $email = file_get_contents($codePath);\n        if($email===FALSE) return $this->setError(ERROR_LOGIN_CANT_LOAD_AUTH_CODE);       \n\n        return $email;\n    }\n\n    protected function _saveAuthCode($code,$email){     \n        if(False===$this->_checkAuthCodesFolder()) return False;\n         \n        $codePath = $this->authCodesPath.\"/\".$code;\n        if( False===file_put_contents($codePath,$email)) return $this->setError(ERROR_LOGIN_CANT_SAVE_AUTH_CODE);       \n        return True;\n    }\n\n    protected function _dropAuthCode($code){     \n        $codePath = $this->authCodesPath.\"/\".$code;\n\n        if(!file_exists($codePath)) return True;\n        unlink($codePath);\n        return True;\n    }\n    \n    // Sessions\n    private function _createSession($uid){\n        while(True){\n            // Create session ID\n            $sid = bin2hex(random_bytes(20));\n\n            // Check existing\n            $checkUID = $this->_getSessionUID($sid);\n            if(False===$checkUID) return False;\n\n            // Need to generate some other session ID\n            if(\"\"!=$checkUID) continue;\n\n            // Create session file            \n            if(False===$this->_saveSession($sid,$uid)) return False;\n            return $sid;\n        }\n    }\n\n    private function _checkSessionsFolder(){\n        // create a folder for session files (only once)\n        $isNewForum = !file_exists($this->sessionsPath);\n        if($isNewForum){\n            if(False==mkdir($this->sessionsPath)) return $this->setError(ERROR_AUTH_CANT_CREATE_SESSIONS_FOLDER);\n        }\n        return True;\n    }\n\n    private function _getSessionUID($sid){\n        if(False===$this->_checkSessionsFolder()) return False;\n        \n        // Check existing\n        $sessionPath = $this->sessionsPath.\"/\".$sid;\n        if(!file_exists($sessionPath)) return \"\";\n\n        // Read existing session\n        $uid = file_get_contents($sessionPath);\n        if($uid===FALSE) return $this->setError(ERROR_AUTH_CANT_READ_SESSION);       \n\n        return $uid;\n    }\n\n    protected function _saveSession($sid,$uid){     \n        if(False===$this->_checkSessionsFolder()) return False;\n         \n        $sessionPath = $this->sessionsPath.\"/\".$sid;\n        if( False===file_put_contents($sessionPath,$uid)) return $this->setError(ERROR_AUTH_CANT_SAVE_SESSION);       \n        return True;\n    }\n\n    \n    protected function _dropSession($sid){     \n        $sessionPath = $this->sessionsPath.\"/\".$sid;\n\n        if(!file_exists($sessionPath)) return True;\n        unlink($sessionPath);\n        return True;\n    }\n    \n    // \n    private function _loginSendCode($email,$code){\n        $email = [\n            \"to\"=>[\n                [ // \n                    \"email\"=>$email               \n                ]            \n            ],\n            \"toUserIDs\"=>[],\n            \"subject\"=>\"Authorization code\",\n            \"body\"=> <<<EOL\n            Put the following code into login form<br/>\n            <b>{$code}</b>\nEOL\n        ];        \n    \n        return Forum::$o->sendEmail($email);\n    }    \n\n    private function _getPagePubIDByReferer(){\n        $pagePubID = $_SERVER['HTTP_REFERER'];\n        $pattern = '/(.+)\\/(\\d+)\\/(.+)/i';\n        $pagePubID = preg_replace($pattern, \"$1/live/$3\",$pagePubID);\n        $pagePubID = preg_replace('/(.+)\\&(.+)/', \"$1\",$pagePubID);\n        return $pagePubID;\n    }\n\n    public function buildPage(){        \n        $pagePubID = $this->_getPagePubIDByReferer();\n        return Page::build($pagePubID);\n    }\n\n    public function buildProject(){        \n        $pagePubID = $this->_getPagePubIDByReferer();\n        $projectPubID = explode(\"?\",$pagePubID)[0];\n        return Project::build($projectPubID);\n    }\n\n\n    public function getProjectInfo(){\n        //\n        $project = $this->buildProject();\n        if($project->lastError!=''){\n            $this->lastError = $project->lastError;\n            return False;\n        }\n        //      \n        $info = $project->getProjectInfo();\n        if(False===$info){\n            $this->lastError = $project->lastError;\n            return False;\n        }\n        //\n        return $info;\n    }\n\n    public function sendEmail($email){\n        $serverForumConfig = $this->serverConfig[$this->forumID];\n        // Check if email configured in server config\n        if(!array_key_exists(\"email\",$serverForumConfig)) return;\n\n        // Convert user ID list to [[\"email\"=>,\"name\"=>]]\n        $toEmails = $this->_sendEmail_uidsTo( $email['toUserIDs'] );        \n        if(False===$toEmails) return False;\n\n        if(count($email[\"to\"])>0){\n            // add unique emails to list \n            foreach ($email[\"to\"] as $to) {\n                $toEmail = $to[\"email\"];\n                if(\n                    count(array_filter(\n                        $toEmails,\n                        function($item) use ($toEmail){\n                            return $item['email']==$toEmail;\n                        }\n                    ))>0\n                ) continue;\n                $toEmails = array_merge($toEmails,[$to]);\n            }\n        }\n\n        $data = [\n            \"personalizations\"=>[[\n                \"to\" => $toEmails,\n            ]],\n            \"subject\"=>$email['subject'],            \n            \"from\"=> [\n                \"email\"=> $serverForumConfig[\"email\"][\"from-email\"]\n            ],    \n            \"content\"=>[\n                [\n                    \"type\"=> \"text/html\",\n                    \"value\"=> $email['body']\n                ]\n            ],                \n        ];\n        $dataStr = str_replace(\"\\/\",\"/\",json_encode($data));\n        // Use sendgrid service\n        $sgKey =  $serverForumConfig['email']['sendgrid-key'];\n        if(null!=$sgKey){\n            $cmd = <<<EOL\ncurl --request POST --url https://api.sendgrid.com/v3/mail/send --header \"Authorization: Bearer {$sgKey}\" --header 'Content-Type: application/json' --data '{$dataStr}'\nEOL;\n        //error_log($cmd);\n        $res = shell_exec($cmd);            \n      }\n      return True;\n    }\n\n    private function _sendEmail_uidsTo($uids){\n        $usersInfo = $this->loadUsersInfo();\n        if(False===$usersInfo){\n            $this->setError(Forum::$o->lastError);\n            return False;        \n        }\n        return array_map(function($uid) use ( $usersInfo){\n            if(!array_key_exists($uid,$usersInfo['list'])){    \n                return [\n                    'email'=>'',\n                    'name'=>'Deleted user #'.$uid\n                ];\n            }else{                \n                $user = $usersInfo['list'][$uid];\n                return [\n                    'email'=>$user['email'],\n                    'name'=>$user['name']\n                ];\n            }  \n        },$uids);\n    }\n\n\n    public function getUserByEmail($email,$name){\n        $usersInfo = $this->loadUsersInfo();\n        if(False===$usersInfo) return False;\n        //\n        $user = $this->findUserByEmail($usersInfo,$email);\n        if(False===$user){\n            $uid = $usersInfo['userCounter']++;\n            $user = [\n                \"uid\"=>$uid,\n                \"email\"=>$email,\n                \"name\"=>$name,\n            ];\n            $usersInfo['list'][$uid] = $user;\n            if(False===$this->saveUsersInfo($usersInfo)) return False;                    \n        }\n        return $user;\n    }\n\n    public function getUserByUID($uid){\n        $usersInfo = $this->loadUsersInfo();\n        if(False===$usersInfo) return False;\n        //\n        if(!array_key_exists($uid,$usersInfo['list'])) return null;\n        return $usersInfo['list'][$uid];\n    }\n\n    private function findUserByEmail(&$usersInfo,$email){\n        $foundUsers = array_values(array_filter(array_values($usersInfo['list']),function($u) use ($email){\n            return $u['email']==$email;\n        }));\n        return count( $foundUsers )>0? $foundUsers[0]:False;\n    }\n\n    protected function init(){\n        // Check data\n        if(\"\" == $this->forumID){\n            $this->setError(ERROR_FORUM_EMPTY_ID);\n            return False;\n        }\n        // Check server id\n        {\n            // Load server config\n            $this->serverConfig = $this->loadServerConfig();\n            if($this->lastError!=\"\") return False;\n\n            // Check forum existing\n            if(!array_key_exists($this->forumID,$this->serverConfig)){\n                $this->setError(ERROR_WRONG_FORUM_ID);\n                return False;\n            }\n        }\n        // Load forum config\n        {\n            $this->basePath = 'data/'.$this->forumID;\n            $this->forumConfigPath = $this->basePath.\"/forum.json\";\n            $this->usersFilePath = $this->basePath.\"/users.json\";\n            $this->authCodesPath = $this->basePath.\"/auth-codes\";\n            $this->sessionsPath = $this->basePath.\"/sessions\";\n\n            $this->config = $this->loadForumConfig();\n            if($this->lastError!=\"\")return False;\n\n            // Create new config\n            if(False===$this->config){                \n                $this->config = DEF_FORUM_CONFIG;\n                if(False===$this->saveForumConfig()) return False;\n            }\n    \n        }\n        // Clear context\n        $this->uid = \"\";\n        $this->sid = \"\";\n        $this->authDone = False;\n        \n        // ok\n        Forum::$o = $this;\n\n        // Restore user context\n        {\n            $uid =  http_post_param('uid');\n            $sid =  http_post_param('sid');\n            if(\"\"!=$uid  && \"\"!=$sid){\n                $checkUID = $this->_getSessionUID($sid);\n                if(False===$checkUID || $checkUID!=$uid) return False;\n                $user = $this->getUserByUID($uid);                \n                if(False!==$user){ \n                    // Ok, user context restored\n                    $this->uid = $uid;\n                    $this->sid = $sid;                                 \n                    $this->user = $user;\n                }\n            }else{\n                $headers = getallheaders();\n                while(True){\n                    if(!array_key_exists(\"OIDC_userinfo_json\",$headers)) break;\n                    $oidcJSON = $headers['OIDC_userinfo_json'];\n                    $oidc = json_decode( $oidcJSON, true );\n\n                    $name = $oidc['name'];\n                    $email = $oidc['email'];\n                    if($name===\"\" || $email===\"\") break;\n\n                    $user = $this->_findUserByODC($name,$email);\n                    if(False===$user) break;\n                    $uid = $user['uid'];\n\n                    $sid = $this->_createSession($uid);\n                    if(False===$sid) break;\n\n                    $this->uid = $uid;\n                    $this->sid = $sid;\n                    $this->user = $user;\n                    break;                    \n                }\n            }\n        }\n\n\n        return True;\n    }\n\n    private function _findUserByODC($name,$email){\n        $user = $this->getUserByEmail($email,$name);\n        if(False!==$user) return $user;\n    }\n\n    public function getBasePage(){\n        return $this->basePath;\n    }\n    public function getBaseProject(){\n        return $this->basePath;\n    }\n\n    /*\n        Result: [\n            \"userCounter\"=>1,\n            \"list\":[\n                {id=>2,name=>\"sddd\",email=>\"dddd}\n            ]\n        ]\n    */\n    public function loadUsersInfo(){\n        if(!file_exists($this->usersFilePath)) return [\n            \"userCounter\"=>1,\n            \"list\"=>[]\n        ];\n\n        // read existing file\n        $content = file_get_contents($this->usersFilePath);\n        if($content===FALSE){\n            $this->setError(ERROR_CANT_LOAD_USERS);\n            return False;\n        }\n         // decode a text config into a array\n        $data = json_decode( $content, true );\n        if($data==NULL){\n            $this->setError(ERROR_CANT_PARSE_USERS);        \n            return False;\n        }   \n        return $data;\n    }\n\n\n    ////////////////////////////////////////////////////////////////////////////////\n\n\n    protected function setError($errorCode){\n            $this->lastError = $errorCode;\n            error_log($errorCode);\n            return False;\n    }\n\n\n    protected function loadServerConfig(){\n        $configPath = \"config/forums.json\";\n\n        // read existing test onfig\n        $content = file_get_contents($configPath);\n        if($content===FALSE){\n            $this->setError(ERROR_CANT_LOAD_SERVERCONFIG);\n            return [];\n        }                       \n        // decode a text config into a array\n        $config = json_decode( $content, true );\n        if($config==NULL){\n            $this->setError(ERROR_CANT_PARSE_SERVERCONFIG);        \n            return [];\n        }    \n\n        return $config;\n    }\n    \n    protected function saveUsersInfo($usersInfo){\n         // encode an array into a json\n         $text = json_encode($usersInfo);\n         if($text===False){\n            $this->setError(ERROR_CANT_ENCODE_USERS);       \n            return False;\n         }   \n         // save into a file\n         if( False===file_put_contents($this->usersFilePath,$text)){\n            $this->setError(ERROR_CANT_SAVE_USERS);       \n            return False;\n         }\n         return True;\n\n    }\n\n    protected function loadForumConfig(){\n\n        if(!file_exists($this->forumConfigPath)) return False;\n\n        // read existing config\n        $content = file_get_contents($this->forumConfigPath);\n        if($content===FALSE){\n            $this->setError(ERROR_CANT_LOAD_FORUMCONFIG);\n            return False;\n        }\n         // decode a text config into a array\n        $config = json_decode( $content, true );\n        if($config==NULL){\n            $this->setError(ERROR_CANT_PARSE_FORUMCONFIG);        \n            return False;\n        }   \n        return $config;\n    }\n\n    protected function saveForumConfig(){\n        //\n        $isNewForum = !file_exists($this->forumConfigPath);\n        if($isNewForum){\n            // init new config\n            if(!file_exists($this->basePath)){\n                if(False==mkdir($this->basePath)){\n                    $this->setError(ERROR_CANT_CREATE_FORUM_FOLDER);       \n                    return False;\n                }\n            }\n        }\n\n         // encode an array into a json\n         $text = json_encode($this->config);\n         if($text===False){\n            $this->setError(ERROR_CANT_ENCODE_FORUMCONFIG);       \n            return False;\n         }   \n         // save into a file\n         if( False===file_put_contents($this->forumConfigPath,$text)){\n            $this->setError(ERROR_CANT_SAVE_FORUMCONFIG);       \n            return False;\n         }\n         return True;\n\n    }\n\n}\n?>\n\n"
  },
  {
    "path": "comments/backend/lib/Frontend.php",
    "content": "<?php\n\nclass Frontend\n{   \n    public static function buildFullHTML(&$forum,&$page,&$commentsInfo){\n        $url =  ($_SERVER['HTTPS']==\"on\"?\"https://\":\"http://\").$_SERVER['SERVER_NAME'].\":\".$_SERVER['SERVER_PORT'].$_SERVER['SCRIPT_NAME'];\n        //var_dump($_SERVER);\n        $inputStyle=' style=\"font-size:12px;\"' ;\n        $code = file_get_contents(\"lib/Comments.js\");\n        $userInfo = json_encode($forum->user);\n        $codePrerun = (http_post_param(\"sid\")!=\"\" && \"\"==$forum->sid)?\"comments.clearSession()\":\"\";        \n        $commentList = json_encode($commentsInfo);\n\n        $res = <<<EOL\n        <div id=\"top\"/>\nEOL;\n        $res .= \"<br/><div id='comments'></div>\";\n        $res .= <<<EOL\n        <script>    \n            {$code}            \n            let comments = new Comments(\"{$forum->forumID()}\",\"{$url}\",\"{$forum->sid}\",{$userInfo});\n            {$codePrerun}            \n            comments.build(${commentList});            \n        //\n        </script>        \nEOL;\n        return $res;\n    }\n\n    public static function buildCommentListHTML(&$page,&$commentsInfo){\n        $commentList = json_encode($commentsInfo);\n        $res = <<<EOL\n        <script>                          \n            comments.build(${commentList});            \n        //\n        </script>        \nEOL;\n        return $res;\n    }\n\n}\n\n?>"
  },
  {
    "path": "comments/backend/server.php",
    "content": "<?php\nheader('Access-Control-Allow-Origin: *');\ninclude 'lib/Forum.php';\ninclude 'lib/Frontend.php';\n\nconst temp_folder = \"temp\";\n\nfunction exitError($errorCode)\n{\n    $res =[\n        'status'=> 'error',\n        'message'=>'Error '.$errorCode.' occured',\n        'errorCode'=>$errorCode\n    ];    \n\techo json_encode($res);\n\texit;\n}\n\nfunction exitSuccess($message,$data=null)\n{\n    $res =[\n        'status'=> 'ok',\n        'message'=>$message,\n        'data'=>$data\n    ];    \n    // user send session id, but session was expired\n    if(http_post_param(\"sid\")!=\"\" && Forum::$o && \"\"==Forum::$o->sid) \n        $res['dropSession'] = True;\n\n\techo json_encode($res);\n\texit;\n}\n\nfunction http_get_param($name)\n{\n\tif(!array_key_exists($name,$_GET)) return '';\n\treturn trim($_GET[$name]);\n}\n\nfunction http_post_param($name)\n{\n\tif(!array_key_exists($name,$_POST)) return '';\n\treturn trim($_POST[$name]);\n}\n\n/// INIT FORUM\n$forum = Forum::build($_GET['fid']);\nif($forum->lastError!=\"\") exitError($forum->lastError);\n\n/// PROCESS INCOMING COMMANDS\n$cmd = $_GET['cmd'];\n\n// RUN COMMANDS WITHOUT USER CONTEXT\nif('login'==$cmd){\n    // Send authroization code\n    $res = $forum->login();\n    if(False===$res) exitError($forum->lastError);\n    exitSuccess(\"Sent authorization code\",$res);\n}else if('logout'==$cmd){\n    // Send authroization code\n    $res = $forum->logout();\n    if(False===$res) exitError($forum->lastError);\n    exitSuccess(\"User logout ok\",$res);\n}else if('auth'==$cmd){\n    // Send authroization code\n    $res = $forum->auth();\n    if(False===$res) exitError($forum->lastError);\n    exitSuccess(\"User authorized and logged\",$res);    \n}else if('getPageInfo'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    // load data\n    $info = $page->getInfo();\n    if(False===$info) exitError($page->lastError);\n    // build result\n    exitSuccess(\"Info loaded\",$info);\n}else if('getProjectInfo'==$cmd){    \n    $info = $forum->getProjectInfo();\n    if(False===$info) exitError($forum->lastError);\n    // build result\n    exitSuccess(\"Info loaded\",$info);\n}else if('buildFullHTML'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    // load data\n    $commentsInfo = $page->getExtendedComments();\n    if(False===$commentsInfo) exitError($page->lastError);\n    // build html\n    $html = \"\";\n    $html .= Frontend::buildFullHTML( $forum,$page,$commentsInfo); \n    //\n    exitSuccess(\"\",$html);\n}else if('getComments'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    // load data\n    $commentsInfo = $page->getExtendedComments();\n    if(False===$commentsInfo) exitError($page->lastError);\n    // build html    \n    //\n    exitSuccess(\"\",$commentsInfo);\n}else if('buildCommentsHTML'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    // load data\n    $commentsInfo = $page->getExtendedComments();\n    if(False===$commentsInfo) exitError($page->lastError);\n    // build html\n    $html = \"\";\n    $html .= Frontend::buildCommentListHTML($page,$commentsInfo); \n    //\n    exitSuccess(\"\",$html);\n}\n\n// RUN COMMANDS WITH USER CONTEXT\nif(\"\"==$forum->sid) exitError(\"#001.003\");\n\nif('addComment'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    if(False===$page->addComment()) exitError($page->lastError);\n    // load updated comments\n    $commentsInfo = $page->getExtendedComments();\n    if(False===$commentsInfo) exitError($page->lastError);\n    // build HTML\n    $html = \"\";\n    $html .= Frontend::buildCommentListHTML($page,$commentsInfo); \n    //\n    exitSuccess(\"Added new comment\",$html);\n}else if('removeComment'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    if(False===$page->removeComment()) exitError($page->lastError);\n    exitSuccess(\"Removed comment\",\"\"); \n}else if('updateComment'==$cmd){\n    $page = $forum->buildPage();\n    if($page->lastError!=\"\") exitError($page->lastError);\n    if(False===$page->updateComment()) exitError($page->lastError);\n    exitSuccess(\"Comment updated\",\"\"); \n}else{\n    exitError(ERROR_UNKNOWN_CMD);\n}\n\n?>\n"
  },
  {
    "path": "comments/frontend/index.php",
    "content": "<?php \n\nvar_dump(mail(\"max@bazarov.ru\",\"Test\",\"ddddddd\"));\n\n?>"
  },
  {
    "path": "comments/frontend/test.html",
    "content": "<html>\n\n<head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <!-- Bootstrap CSS -->\n    <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\"\n        integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\" crossorigin=\"anonymous\">\n\n    <link href=\"https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\" rel=\"stylesheet\">\n\n    <script src=\"https://code.jquery.com/jquery-3.4.1.slim.min.js\"\n        integrity=\"sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n\"\n        crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js\"\n        integrity=\"sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo\"\n        crossorigin=\"anonymous\"></script>\n    <script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js\"\n        integrity=\"sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6\"\n        crossorigin=\"anonymous\"></script>\n\n\n    <style>\n\n    </style>\n\n</head>\n<script>\n    var baseURL = \"https://uxteam.int.zone/comments/backend/server.php\"\n\n    var forumID = \"1234567890\"\n    function buildCommentsPage() {\n        var formData = new FormData();\n        //\n        var xhr = new XMLHttpRequest()\n        xhr.open('GET', baseURL + \"?fid=\" + forumID + \"&cmd=buildFullHTML\", true);\n        xhr.onload = function () {\n            // do something to response\n            var result = JSON.parse(this.responseText);\n            $('#content').html(result.data);\n            console.log(result.data)\n            return\n        };\n        xhr.send(formData);\n    }\n</script>\n\n<body>\n    <div id=\"comments_viewer\">\n        <div id=\"content\" style=\"margin-top:20px;\">\n        </div>\n    </div>\n    <script>buildCommentsPage();</script>\n</body>"
  },
  {
    "path": "comments/readme.txt",
    "content": "    chcon -R -t httpd_sys_content_t .\n    chcon -R -t httpd_sys_rw_content_t  .\n    chown -R apache:apache config\n"
  },
  {
    "path": "docs/Favicon/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta name=\"generator\" content=\"Generated using Puzzle Publisher 16.1.2 plugin for Sketch.app - https://github.com/ingrammicro/puzzle-publisher\">\n<title>Favicon</title>\n<link rel=\"shortcut icon\"  type=\"image/png?\" href=\"resources/icon.png?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer-center.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/cb-modern-ui.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/ux1-ui.css?V_V_V\">\n<script type=\"text/javascript\" src=\"resources/jquery-3.3.1.min.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"resources/jquery.hotkeys.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"resources/jquery.ba-hashchange.min.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/viewer-page.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/story.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/viewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/AbstractViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/CommentsViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/GalleryViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/LayersData.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/SymbolViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer-fonts.css?V_V_V\">\n<script type=\"text/javascript\" src=\"viewer/VersionViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\">\n  var viewer = createViewer(story, \"images\");\n</script>\n\n<!-- Google Tag Manager -->\n<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\nnew Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\nj=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n})(window,document,'script','dataLayer','GTM-KGWHQP6');</script>\n<!-- End Google Tag Manager -->\n<script>\n    function copyToBuffer(elID) {\n        var copyText = document.getElementById(elID);\n\n        var $temp = $(\"<input>\");\n        $(\"body\").append($temp);\n        $temp.val($(copyText).text()).select();\n        document.execCommand(\"copy\");\n        $temp.remove();\n    }\n    function showFAIconInfo(code){\n        window.open(\"https://fontawesome.com/icons?d=gallery&q=\"+code,\"_blank\")\n    }\n</script > <!--HEAD_INJECT-->\n</head>\n<body class=\"screen\" style=\"background:#646464\">\n\n            <!-- Google Tag Manager (noscript) -->\n            <noscript><iframe src=\"https://www.googletagmanager.com/ns.html?id=GTM-KGWHQP6\"\n            height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>\n            <!-- End Google Tag Manager (noscript) --><div class=\"containerSVG\"> <svg class=\"svgIcon\">     <symbol id=\"icMenu\" viewBox=\"0 0 24 24\">         <path d=\"M4,14 C2.8954305,14 2,13.1045695 2,12 C2,10.8954305 2.8954305,10 4,10 C5.1045695,10 6,10.8954305 6,12 C6,13.1045695 5.1045695,14 4,14 Z M12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 Z M20,14 C18.8954305,14 18,13.1045695 18,12 C18,10.8954305 18.8954305,10 20,10 C21.1045695,10 22,10.8954305 22,12 C22,13.1045695 21.1045695,14 20,14 Z\" />    </symbol>     <symbol id=\"icArrwLeft\" viewBox=\"0 0 24 24\">         <path d=\"M14.7071068,16.2928932 C15.0976311,16.6834175 15.0976311,17.3165825 14.7071068,17.7071068 C14.3165825,18.0976311 13.6834175,18.0976311 13.2928932,17.7071068 L8.29289322,12.7071068 C7.90236893,12.3165825 7.90236893,11.6834175 8.29289322,11.2928932 L13.2928932,6.29289322 C13.6834175,5.90236893 14.3165825,5.90236893 14.7071068,6.29289322 C15.0976311,6.68341751 15.0976311,7.31658249 14.7071068,7.70710678 L10.4142136,12 L14.7071068,16.2928932 Z\" />    </symbol>     <symbol id=\"icArrwRight\" viewBox=\"0 0 24 24\">         <path d=\"M15.7071068,16.2928932 C16.0976311,16.6834175 16.0976311,17.3165825 15.7071068,17.7071068 C15.3165825,18.0976311 14.6834175,18.0976311 14.2928932,17.7071068 L9.29289322,12.7071068 C8.90236893,12.3165825 8.90236893,11.6834175 9.29289322,11.2928932 L14.2928932,6.29289322 C14.6834175,5.90236893 15.3165825,5.90236893 15.7071068,6.29289322 C16.0976311,6.68341751 16.0976311,7.31658249 15.7071068,7.70710678 L11.4142136,12 L15.7071068,16.2928932 Z\" transform=\"matrix(-1 0 0 1 25 0)\" />    </symbol>     <symbol id=\"icHeart\" viewBox=\"0 0 24 24\">        <path fill=\"none\" stroke=\"#404B58\" stroke-width=\"2\" d=\"M12,18.8536369 C17.3943819,16.1015046 20,12.9784118 20,9.5 C20,7.01471863 17.9852814,5 15.5,5 C14.4391705,5 13.4374107,5.36699819 12.6367778,6.02820949 L12,6.55409926 L11.3632222,6.02820949 C10.5625893,5.36699819 9.56082953,5 8.5,5 C6.01471863,5 4,7.01471863 4,9.5 C4,12.9784118 6.60561807,16.1015046 12,18.8536369 Z\"/>    </symbol>     <symbol id=\"icPointer\" viewBox=\"0 0 24 24\">        <path d=\"M7.16743376,4.34579076 C7.66363057,3.87908025 8.39151976,3.755899 9.01365224,4.03335379 L9.01365224,4.03335379 L9.10700534,4.08777143 C9.63104823,4.47472439 10.0217699,5.01508408 10.2127512,5.60062907 C10.4917675,6.2824399 10.6779761,6.99862675 10.7637203,7.71100475 L10.7637203,7.71100475 L10.817,8.033 L10.870648,7.99008307 C10.9508584,7.93108653 11.0375587,7.87866583 11.130137,7.83371233 L11.130137,7.83371233 L11.2733366,7.7719939 C11.645598,7.65777539 12.0393934,7.63209308 12.4423174,7.7005808 C12.8744158,7.79091478 13.2571639,8.03945252 13.515467,8.3974312 L13.515467,8.3974312 L13.525,8.415 L13.5580532,8.38060854 C13.7375777,8.20417748 13.9573664,8.06689595 14.2126677,7.97661745 L14.2126677,7.97661745 L14.3700719,7.92815354 C14.7601243,7.85683728 15.1598757,7.85683728 15.6185326,7.94579813 C15.9857599,8.06856757 16.3070009,8.30005011 16.5396674,8.609557 L16.5396674,8.609557 L16.6043493,8.72458234 C16.6428414,8.82097976 16.6788016,8.91827539 16.7122125,9.01653061 L16.7122125,9.01653061 L16.726,9.06 L16.8208864,8.97721759 C16.9681747,8.85926223 17.1379522,8.76879133 17.3224355,8.71266304 L17.3224355,8.71266304 L17.4634337,8.67711323 C17.9896455,8.5711603 18.5324104,8.75398638 18.8872742,9.15672267 C19.242138,9.55945895 19.3551885,10.1209202 19.1838405,10.6296094 L19.2093224,10.5357799 L19.2090334,12.7610745 C19.1862391,13.1271355 19.1470105,13.4918634 19.0880685,13.8664949 L19.0224339,14.2448438 L18.9429284,14.6322922 C18.7541803,15.1823144 18.4914657,15.7040948 18.1217893,16.2343243 C17.6699093,16.7368429 17.2965379,17.3047439 17.0143136,17.9188042 L17.0363224,17.8727799 L17.0047955,18.0384429 C16.9929046,18.1111666 16.9833046,18.1844595 16.9760091,18.2584435 L16.9610493,18.4825906 L16.9599974,18.7116152 C16.9591372,18.9778721 16.9937268,19.2430586 17.0628545,19.5001867 C17.1426598,19.7970308 16.9382454,20.0949145 16.6325641,20.1272293 C16.1887579,20.1741459 15.7412421,20.1741459 15.2739714,20.1241858 C14.8192803,20.0542334 14.4188874,19.6281882 14.0808095,19.0818271 L14.0808095,19.0818271 L14.007,18.959 C13.6458971,19.5469161 13.2283691,20.0164429 12.7971825,20.133474 L12.7971825,20.133474 L12.6792804,20.1564734 C12.40804,20.1888603 12.1178623,20.1996559 11.5976025,20.1951344 L11.5976025,20.1951344 L10.2584286,20.169736 L9.48,20.16 C9.16840558,20.16 8.93270845,19.8780895 8.98790834,19.5714235 L8.98790834,19.5714235 L9.0027436,19.4616806 C9.02119919,19.2959265 9.02585489,19.1230993 9.00917907,18.9708257 C8.99125835,18.8071844 8.95160807,18.7033862 8.92009941,18.6757201 L8.92009941,18.6757201 L8.68126124,18.4610804 L7.73371165,17.5692523 L6.87532389,16.5869703 C6.7408966,16.4141352 6.12289613,15.3607699 5.76460288,14.7906072 L5.76460288,14.7906072 L5.62038404,14.5667312 C5.59696768,14.5332792 5.57867016,14.5070634 5.55981671,14.4808928 L5.55981671,14.4808928 L4.6386181,13.3268316 C4.38383819,13.0013876 4.22702059,12.7785585 4.13684781,12.6063498 L4.13684781,12.6063498 L4.05648317,12.4645383 C3.86064293,12.0784209 3.81297141,11.6326253 3.92414472,11.2219126 C4.14055922,10.3315035 4.98520633,9.74084121 5.93847381,9.84979299 C6.54529028,9.97169156 7.1029491,10.2691096 7.53281665,10.6960265 L7.53281665,10.6960265 L7.723,10.883 L7.6912417,10.723928 L7.64251772,10.5011802 C7.57779705,10.2174049 7.53759574,10.0512485 7.49682897,9.89744992 L7.49682897,9.89744992 L7.32963781,9.29184575 L7.04456938,8.13982119 C6.92154004,7.6413934 6.82477772,7.13684703 6.75976794,6.65834902 C6.61137215,5.91865308 6.7193326,5.15047299 7.06586723,4.48033484 L7.06586723,4.48033484 Z M8.50934031,4.91178804 C8.27952269,4.84813061 8.02983182,4.9074776 7.85256624,5.07420924 L7.91432244,5.02377994 L7.87117294,5.11837118 C7.74604513,5.42118721 7.68689523,5.74791709 7.69799852,6.08431903 L7.71312508,6.28720691 L7.81882079,6.96398474 C7.87322009,7.27747387 7.93828923,7.58776101 8.01328078,7.89178265 L8.01328078,7.89178265 L8.29601637,9.03471155 L8.44911047,9.58764732 L8.58001699,10.1158847 L8.67625156,10.548595 C8.75149962,10.9085229 8.81587907,11.2760953 8.87882633,11.6904775 L8.87882633,11.6904775 L8.97335261,12.3520537 C9.01060121,12.4927706 9.0255715,12.5703226 9.01950838,12.6680061 C9.00562698,12.8916511 9.00162999,12.9288159 8.65656558,13.0620472 L8.65656558,13.0620472 L8.43241435,13.1459443 C8.23545672,13.065262 8.18621731,13.0450914 8.15393215,13.0140564 L8.15393215,13.0140564 L8.08883861,12.9370646 L7.80233685,12.5590928 L7.5578657,12.2238297 L7.29668723,11.8970782 C7.15496439,11.7304554 7.00269692,11.5703689 6.83776679,11.4148687 C6.53749664,11.1168285 6.15631213,10.9135301 5.78418862,10.8368753 C5.37391601,10.7907916 4.99336072,11.0569141 4.892676,11.4704756 C4.83729564,11.6753474 4.87010001,11.8940432 5.00167451,12.1056421 L5.00167451,12.1056421 L5.03690334,12.1679323 C5.11596703,12.3004962 5.24732182,12.482014 5.44275702,12.7314353 L5.44275702,12.7314353 L5.92017745,13.3249945 L6.29953254,13.8026386 L6.44532166,14.0015976 C6.74304028,14.4457352 7.37243772,15.5180187 7.58358848,15.8552149 L7.58358848,15.8552149 L7.64628835,15.9507477 L8.44514313,16.8689487 L9.35653146,17.7235091 L9.57990059,17.9242799 C9.83717242,18.1501771 9.96070027,18.4735533 10.003236,18.8619643 C10.0118351,18.9404865 10.0168432,19.0197358 10.0187392,19.0991214 L10.0187392,19.0991214 L10.017,19.165 L11.2459156,19.1902367 C11.83561,19.2002217 12.1681116,19.1973958 12.4192519,19.177468 L12.4192519,19.177468 L12.5615794,19.1634247 C12.6170658,19.1568969 13.0000528,18.7092498 13.2226316,18.3267031 L13.2226316,18.3267031 L13.2778839,18.2314035 C13.4396915,17.9886975 13.7135208,17.8397056 14.01,17.8397056 C14.3435391,17.8397056 14.648412,18.0282734 14.789646,18.3118584 L14.789646,18.3118584 L14.8465822,18.411971 C15.047575,18.7505088 15.3490101,19.1240166 15.4025641,19.1327707 C15.5895219,19.1525347 15.7772609,19.1624167 15.965,19.1624167 L15.965,19.1624167 L15.985,19.161 L15.9680577,18.9804543 L15.9601727,18.7231423 C15.9502275,18.3449039 15.9880257,17.9669214 16.0726727,17.5981423 L16.0726727,17.5981423 L16.1056864,17.5011958 C16.429971,16.7956214 16.8589864,16.1430854 17.3379853,15.6167264 C17.61604,15.2123026 17.8377695,14.7719231 17.981188,14.3648199 C18.099272,13.8160386 18.1760798,13.2591823 18.21,12.73 L18.21,12.73 L18.21,10.47 L18.2361595,10.3103906 C18.2934615,10.1402752 18.2556553,9.95251178 18.136982,9.81782907 C18.0183088,9.68314635 17.8367978,9.62200582 17.6608226,9.6574385 C17.4848473,9.69287119 17.3411425,9.81949403 17.2838405,9.98960943 L17.2838405,9.98960943 L17.2333183,10.0960858 C17.2060315,10.1394966 17.1784846,10.1946376 17.1518918,10.2591212 C17.1098746,10.3610069 17.0769839,10.4664928 17.0409555,10.6029732 L17.0409555,10.6029732 L16.996796,10.7581274 L16.9595346,10.8379862 C16.9006729,10.9958359 16.8849765,11.0379292 16.5932412,10.9992882 L16.5932412,10.9992882 L16.1501329,10.9267917 C16.0348274,10.6676813 16.009204,10.6101012 16.0212304,10.6050553 C15.9848193,10.0869965 15.8682391,9.57772504 15.6756507,9.09541766 L15.7,9.162 L15.6694688,9.12668516 C15.5949633,9.04790444 15.511666,8.98482947 15.4387784,8.94442971 L15.4387784,8.94442971 L15.3700719,8.91184646 C15.098938,8.86227297 14.821062,8.86227297 14.5895196,8.90293341 C14.4230985,8.94756646 14.2774688,9.04887406 14.1777454,9.18938502 L14.2223224,9.13377994 L14.1675121,9.33868789 C14.1182734,9.53586776 14.0787072,9.73525859 14.0489695,9.93588797 L14.011754,10.2376394 L13.9893984,10.5405193 C13.9584597,11.1706655 13.0294935,11.1762842 12.9909347,10.5465584 L12.9909347,10.5465584 L12.9730784,10.2516986 C12.930789,9.77937294 12.8204225,9.3155109 12.6453027,8.87454375 L12.669,8.94 L12.6310282,8.89504189 C12.5518534,8.81362497 12.456908,8.75113321 12.3573482,8.71292346 L12.3573482,8.71292346 L12.2566634,8.6830061 C12.0262976,8.64406561 11.7900203,8.659475 11.5981215,8.71719666 C11.4132489,8.78768046 11.2781507,8.94899175 11.2402903,9.14805807 C11.2189407,9.25480644 11.2137839,9.47449295 11.224132,9.79681583 L11.224132,9.79681583 L11.25,10.55 C11.25,11.2056512 10.2721205,11.2219446 10.2502775,10.5666574 C10.2493885,10.539987 10.2483981,10.5176836 10.244244,10.482217 L10.244244,10.482217 L9.90052097,8.58822404 L9.77361364,7.85000483 C9.69594584,7.20750256 9.53236284,6.57833719 9.27496435,5.94601645 L9.27496435,5.94601645 L9.20787946,5.76867193 C9.05849627,5.42113224 8.81912344,5.11827389 8.51299466,4.89222857 L8.51732244,4.89577994 L8.557,4.928 Z M15.375,13 C15.5562184,13 15.707414,13.1282379 15.7423813,13.2987132 L15.75,13.3741092 L15.75,16.8258906 C15.75,17.0325054 15.5821068,17.1999998 15.375,17.1999998 C15.1937816,17.1999998 15.042586,17.0717619 15.0076187,16.9012866 L15,16.8258906 L15,13.3741092 C15,13.1674944 15.1678932,13 15.375,13 Z M13.3728388,13 C13.5540542,12.998966 13.7059881,13.1260313 13.7419398,13.2958993 L13.7499939,13.3710717 L13.7699939,16.8246259 C13.7711876,17.0307477 13.6042648,17.1988055 13.3971615,17.1999998 C13.2159461,17.2010338 13.0640121,17.0739685 13.0280605,16.9041005 L13.0200064,16.8289281 L13,13.3753739 C12.9988127,13.1692522 13.1657354,13.0011944 13.3728388,13 Z M11.3728136,13 C11.5540289,12.998944 11.7059714,13.127215 11.7419346,13.2987061 L11.7499938,13.3745973 L11.7699938,16.8210084 C11.7712014,17.0291026 11.6042899,17.1987799 11.3971867,17.1999998 C11.2159713,17.2010558 11.0640288,17.0727848 11.0280657,16.9012937 L11.0200065,16.8254025 L11,13.3789914 C10.9987989,13.1708972 11.1657103,13.0012199 11.3728136,13 Z\" />    </symbol>    <symbol id=\"icAnnotation\" viewBox=\"0 0 24 24\">        <path d=\"M5,16 L13,16 C13.5522847,16 14,16.4477153 14,17 C14,17.5522847 13.5522847,18 13,18 L5,18 C4.44771525,18 4,17.5522847 4,17 C4,16.4477153 4.44771525,16 5,16 Z M5,6 L19,6 C19.5522847,6 20,6.44771525 20,7 C20,7.55228475 19.5522847,8 19,8 L5,8 C4.44771525,8 4,7.55228475 4,7 C4,6.44771525 4.44771525,6 5,6 Z M5,11 L19,11 C19.5522847,11 20,11.4477153 20,12 C20,12.5522847 19.5522847,13 19,13 L5,13 C4.44771525,13 4,12.5522847 4,12 C4,11.4477153 4.44771525,11 5,11 Z\" />    </symbol>    <symbol id=\"icEmbed\" viewBox=\"0 0 24 24\">        <path d=\"M6.7080808,14.2938686 C7.09806642,14.6849308 7.09719364,15.3180952 6.70613141,15.7080808 C6.31506919,16.0980664 5.68190481,16.0971936 5.2919192,15.7061314 L2.2919192,12.6978494 C1.90193253,12.3067862 1.90280651,11.6736197 2.29387128,11.2836345 L5.29387128,8.29191651 C5.684935,7.90193238 6.31809937,7.90280757 6.70808349,8.29387128 C7.09806762,8.684935 7.09719243,9.31809937 6.70612872,9.70808349 L4.41421491,11.9936701 L6.7080808,14.2938686 Z M17.2938713,9.70808349 C16.9028076,9.31809937 16.9019324,8.684935 17.2919165,8.29387128 C17.6819006,7.90280757 18.315065,7.90193238 18.7061287,8.29191651 L21.7061287,11.2836345 C22.0971935,11.6736197 22.0980675,12.3067862 21.7080808,12.6978494 L18.7080808,15.7061314 C18.3180952,16.0971936 17.6849308,16.0980664 17.2938686,15.7080808 C16.9028064,15.3180952 16.9019336,14.6849308 17.2919192,14.2938686 L19.5857851,11.9936701 L17.2938713,9.70808349 Z M13.0513167,5.68377223 C13.2259645,5.15982892 13.7922844,4.87666893 14.3162278,5.0513167 C14.8401711,5.22596447 15.1233311,5.79228445 14.9486833,6.31622777 L10.9486833,18.3162278 C10.7740355,18.8401711 10.2077156,19.1233311 9.68377223,18.9486833 C9.15982892,18.7740355 8.87666893,18.2077156 9.0513167,17.6837722 L13.0513167,5.68377223 Z\" />    </symbol>    <symbol id=\"icGrid\" viewBox=\"0 0 24 24\">         <path d=\"M12,17 C13.1045695,17 14,17.8954305 14,19 C14,20.1045695 13.1045695,21 12,21 C10.8954305,21 10,20.1045695 10,19 C10,17.8954305 10.8954305,17 12,17 Z M18.5,17 C19.3284271,17 20,17.6715729 20,18.5 C20,19.3284271 19.3284271,20 18.5,20 C17.6715729,20 17,19.3284271 17,18.5 C17,17.6715729 17.6715729,17 18.5,17 Z M5.5,17 C6.32842712,17 7,17.6715729 7,18.5 C7,19.3284271 6.32842712,20 5.5,20 C4.67157288,20 4,19.3284271 4,18.5 C4,17.6715729 4.67157288,17 5.5,17 Z M12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 Z M19,10 C20.1045695,10 21,10.8954305 21,12 C21,13.1045695 20.1045695,14 19,14 C17.8954305,14 17,13.1045695 17,12 C17,10.8954305 17.8954305,10 19,10 Z M5,10 C6.1045695,10 7,10.8954305 7,12 C7,13.1045695 6.1045695,14 5,14 C3.8954305,14 3,13.1045695 3,12 C3,10.8954305 3.8954305,10 5,10 Z M12,3 C13.1045695,3 14,3.8954305 14,5 C14,6.1045695 13.1045695,7 12,7 C10.8954305,7 10,6.1045695 10,5 C10,3.8954305 10.8954305,3 12,3 Z M18.5,4 C19.3284271,4 20,4.67157288 20,5.5 C20,6.32842712 19.3284271,7 18.5,7 C17.6715729,7 17,6.32842712 17,5.5 C17,4.67157288 17.6715729,4 18.5,4 Z M5.5,4 C6.32842712,4 7,4.67157288 7,5.5 C7,6.32842712 6.32842712,7 5.5,7 C4.67157288,7 4,6.32842712 4,5.5 C4,4.67157288 4.67157288,4 5.5,4 Z\" />    </symbol>    <symbol id=\"icClose\" viewBox=\"0 0 24 24\">      <path d=\"M10.5857864,12 L7.29289322,8.70710678 C6.90236893,8.31658249 6.90236893,7.68341751 7.29289322,7.29289322 C7.68341751,6.90236893 8.31658249,6.90236893 8.70710678,7.29289322 L12,10.5857864 L15.2928932,7.29289322 C15.6834175,6.90236893 16.3165825,6.90236893 16.7071068,7.29289322 C17.0976311,7.68341751 17.0976311,8.31658249 16.7071068,8.70710678 L13.4142136,12 L16.7071068,15.2928932 C17.0976311,15.6834175 17.0976311,16.3165825 16.7071068,16.7071068 C16.3165825,17.0976311 15.6834175,17.0976311 15.2928932,16.7071068 L12,13.4142136 L8.70710678,16.7071068 C8.31658249,17.0976311 7.68341751,17.0976311 7.29289322,16.7071068 C6.90236893,16.3165825 6.90236893,15.6834175 7.29289322,15.2928932 L10.5857864,12 Z\" transform=\"rotate(-90 12 12)\" />    </symbol>    <symbol id=\"icBack\" viewBox=\"0 0 24 24\">      <path d=\"M8.36500685,12.7725895 C8.14212731,12.5891835 8,12.3112069 8,12.0000346 C8,11.7624738 8.08283717,11.5442607 8.22120202,11.3727048 L8.22132741,11.3368041 L12.2213274,6.37260414 C12.5678477,5.94255515 13.1973815,5.87484175 13.6274305,6.22136203 C14.0574795,6.56788232 14.1251929,7.1974161 13.7786726,7.6274651 L11.0611595,11.0000346 L19,11.0000346 C19.5522847,11.0000346 20,11.4477499 20,12.0000346 C20,12.5523194 19.5522847,13.0000346 19,13.0000346 L11.0998289,13.0000346 L13.7830365,16.3780588 C14.1265442,16.8105179 14.0544349,17.4395633 13.6219758,17.7830711 C13.1895167,18.1265788 12.5604713,18.0544695 12.2169635,17.6220104 L8.36500685,12.7725895 Z M5,6.00003462 C5.55228475,6.00003462 6,6.44774987 6,7.00003462 L6,17.0000346 C6,17.5523194 5.55228475,18.0000346 5,18.0000346 C4.44771525,18.0000346 4,17.5523194 4,17.0000346 L4,7.00003462 C4,6.44774987 4.44771525,6.00003462 5,6.00003462 Z\" />    </symbol>    <symbol id=\"icResize\" viewBox=\"0 0 24 24\">      <path d=\"M15.5857864,7 L13,7 C12.4477153,7 12,6.55228475 12,6 C12,5.44771525 12.4477153,5 13,5 L18,5 C18.5522847,5 19,5.44771525 19,6 L19,11 C19,11.5522847 18.5522847,12 18,12 C17.4477153,12 17,11.5522847 17,11 L17,8.41421356 L14.7071068,10.7071068 C14.3165825,11.0976311 13.6834175,11.0976311 13.2928932,10.7071068 C12.9023689,10.3165825 12.9023689,9.68341751 13.2928932,9.29289322 L15.5857864,7 Z M7,15.5857864 L9.29289322,13.2928932 C9.68341751,12.9023689 10.3165825,12.9023689 10.7071068,13.2928932 C11.0976311,13.6834175 11.0976311,14.3165825 10.7071068,14.7071068 L8.41421356,17 L11,17 C11.5522847,17 12,17.4477153 12,18 C12,18.5522847 11.5522847,19 11,19 L6,19 C5.44771525,19 5,18.5522847 5,18 L5,13 C5,12.4477153 5.44771525,12 6,12 C6.55228475,12 7,12.4477153 7,13 L7,15.5857864 Z\" />    </symbol>    <symbol id=\"icGridLayout\" viewBox=\"0 0 24 24\">      <path d=\"M6.07692308,5 C5.48215488,5 5,5.44771525 5,6 L5,17 C5,17.5522847 5.48215488,18 6.07692308,18 L17.9230769,18 C18.5178451,18 19,17.5522847 19,17 L19,6 C19,5.44771525 18.5178451,5 17.9230769,5 L6.07692308,5 Z M6.17647059,3 L17.8235294,3 C19.5778457,3 21,4.34314575 21,6 L21,17 C21,18.6568542 19.5778457,20 17.8235294,20 L6.17647059,20 C4.42215432,20 3,18.6568542 3,17 L3,6 C3,4.34314575 4.42215432,3 6.17647059,3 Z M14,5 L16,5 L16,18 L14,18 L14,5 Z M8,5 L10,5 L10,18 L8,18 L8,5 Z\" />    </symbol>    <symbol id=\"icElementInspector\" viewBox=\"0 0 24 24\">      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />    </symbol>    <symbol id=\"icVersionInspector\" viewBox=\"0 0 24 24\">      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />    </symbol>    <symbol id=\"icIncreaseVersion\" viewBox=\"0 0 24 24\">      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M11.3086198,9.29124083 C11.7000598,8.90162823 12.333229,8.90311306 12.7228373,9.29455727 L12.7228373,9.29455727 L15.7087669,12.2945573 C16.0983722,12.6859984 16.0968839,13.3191617 15.7054427,13.7087669 C15.3140016,14.0983722 14.6808383,14.0968839 14.2912331,13.7054427 L14.2912331,13.7054427 L12.0107539,11.4142175 L9.70545052,13.7087592 C9.31401364,14.0983687 8.6808504,14.0968874 8.29124083,13.7054505 C7.90163127,13.3140136 7.9031126,12.6808504 8.29454948,12.2912408 L8.29454948,12.2912408 Z\" />    </symbol>    <symbol id=\"icDecreaseVersion\" viewBox=\"0 0 24 24\">      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M8.29124083,10.2945495 C8.6808504,9.9031126 9.31401364,9.90163127 9.70545052,10.2912408 L9.70545052,10.2912408 L12.0107539,12.5857825 L14.2912331,10.2945573 C14.6808383,9.90311611 15.3140016,9.90162781 15.7054427,10.2912331 C16.0968839,10.6808383 16.0983722,11.3140016 15.7087669,11.7054427 L15.7087669,11.7054427 L12.7228373,14.7054427 C12.333229,15.0968869 11.7000598,15.0983718 11.3086198,14.7087592 L11.3086198,14.7087592 L8.29454948,11.7087592 C7.9031126,11.3191496 7.90163127,10.6859864 8.29124083,10.2945495 Z\" />    </symbol>    <symbol id=\"icCloseBtn\" viewBox=\"0 0 24 24\">      <path fill=\"#FFFFFF\" d=\"M4.29289322,4.29289322 C4.68341751,3.90236893 5.31658249,3.90236893 5.70710678,4.29289322 L5.70710678,4.29289322 L12,10.585 L18.2928932,4.29289322 C18.6533772,3.93240926 19.2206082,3.90467972 19.6128994,4.20970461 L19.7071068,4.29289322 C20.0976311,4.68341751 20.0976311,5.31658249 19.7071068,5.70710678 L19.7071068,5.70710678 L13.415,12 L19.7071068,18.2928932 C20.0675907,18.6533772 20.0953203,19.2206082 19.7902954,19.6128994 L19.7071068,19.7071068 C19.3165825,20.0976311 18.6834175,20.0976311 18.2928932,19.7071068 L18.2928932,19.7071068 L12,13.415 L5.70710678,19.7071068 C5.34662282,20.0675907 4.77939176,20.0953203 4.38710056,19.7902954 L4.29289322,19.7071068 C3.90236893,19.3165825 3.90236893,18.6834175 4.29289322,18.2928932 L4.29289322,18.2928932 L10.585,12 L4.29289322,5.70710678 C3.93240926,5.34662282 3.90467972,4.77939176 4.20970461,4.38710056 Z\" />    </symbol>    <symbol id=\"icAddComment\" viewBox=\"0 0 24 24\">    <path d=\"M19,3 L5,3 C3.34314575,3 2,4.34314575 2,6 L2,16 L2.00509269,16.1762728 C2.09633912,17.75108 3.40231912,19 5,19 L15.65,19 L18.7506099,21.4811128 C19.105236,21.7648136 19.545857,21.9193752 20,21.9193752 C21.1045695,21.9193752 22,21.0239447 22,19.9193752 L22,6 C22,4.34314575 20.6568542,3 19,3 Z M5,5 L19,5 C19.5522847,5 20,5.44771525 20,6 L20,19.9193752 L16.3507811,17 L5,17 C4.44771525,17 4,16.5522847 4,16 L4,6 C4,5.44771525 4.44771525,5 5,5 Z M12,7 C12.5522847,7 13,7.44771525 13,8 L13,14 C13,14.5522847 12.5522847,15 12,15 C11.4477153,15 11,14.5522847 11,14 L11,8 C11,7.44771525 11.4477153,7 12,7 Z M9,10 L15,10 C15.5522847,10 16,10.4477153 16,11 C16,11.5522847 15.5522847,12 15,12 L9,12 C8.44771525,12 8,11.5522847 8,11 C8,10.4477153 8.44771525,10 9,10 Z\"/>    </symbol >    <symbol id=\"icComments\" viewBox=\"0 0 24 24\">    <path d=\"M19,3 C20.6568542,3 22,4.34314575 22,6 L22,6 L22,19.9193752 C22,21.0239447 21.1045695,21.9193752 20,21.9193752 C19.545857,21.9193752 19.105236,21.7648136 18.7506099,21.4811128 L18.7506099,21.4811128 L15.65,19 L5,19 C3.40231912,19 2.09633912,17.75108 2.00509269,16.1762728 L2.00509269,16.1762728 L2,16 L2,6 C2,4.34314575 3.34314575,3 5,3 L5,3 Z M19,5 L5,5 C4.44771525,5 4,5.44771525 4,6 L4,6 L4,16 C4,16.5522847 4.44771525,17 5,17 L5,17 L16.3507811,17 L20,19.9193752 L20,6 C20,5.44771525 19.5522847,5 19,5 L19,5 Z M12,12 C12.5522847,12 13,12.4477153 13,13 C13,13.5522847 12.5522847,14 12,14 L8,14 C7.44771525,14 7,13.5522847 7,13 C7,12.4477153 7.44771525,12 8,12 L12,12 Z M16,8 C16.5522847,8 17,8.44771525 17,9 C17,9.55228475 16.5522847,10 16,10 L8,10 C7.44771525,10 7,9.55228475 7,9 C7,8.44771525 7.44771525,8 8,8 L16,8 Z\"/>    </symbol >    </svg ></div >\n    <div class=\"shaft1\"></div><div class=\"shaft2\"></div><div class=\"shaft3\"></div>    <div class=\"shaft4\"></div><div class=\"shaft5\"></div><div class=\"shaft6\"></div><div class=\"shaft7\"></div>  </div>      <!--/load indicator-->     <div id=\"container\">        <div id=\"marker\"></div>        <div id=\"content\" onclick=\"viewer.onContentClick()\"></div>        <div id=\"sidebar\" class=\"hidden\">            <div id=\"symbol_viewer\" class=\"hidden\">                <div class=\"title\">                  <div style=\"width:100%;\">Element Inspector</div>                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.symbolViewer.toggle();  return false;\">                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                  </div>                </div>                <div class=\"checkbox-container\" style=\"margin-top:62px;\">                  <input type=\"checkbox\" id=\"symbol_viewer_symbols\" />                  <label for=\"symbol_viewer_symbols\"></label>                  <span class=\"checkbox-label\">Show symbols&nbsp;&nbsp;</span>                  <select id=\"lib_selector\" style=\"width:200px;display:none;\"></select>                </div>                <div id=\"empty\" style=\"padding: 16px 20px 0 20px;margin-top:20px;\">Click any element to inspect</div>                <div id=\"symbol_viewer_content\" style=\"margin-top:20px;\">                </div>            </div>            <div id=\"comments_viewer\" class=\"hidden\">                <div class=\"title\">                    <div style=\"width:100%;\">Comments</div>                    <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.commentsViewer.toggle();  return false;\">                        <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                    </div>                </div>                <div id=\"comments_viewer_content\">                </div>            </div >            <div id=\"version_viewer\" class=\"hidden\">                <div class=\"title\">                  <div style=\"width:100%;\">Version Inspector</div>                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.versionViewer.toggle();  return false;\">                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                  </div>                </div>    <div style=\"padding: 72px 20px 0 20px\">                   Mode:<br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_diff\" value=\"diff\" checked onclick=\"viewer.versionViewer.pageChanged()\" disabled /><label for=\"version_viewer_mode_diff\">Differences</label><br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_prev\" value=\"prev\" onclick=\"viewer.versionViewer.pageChanged()\" disabled><label for=\"version_viewer_mode_prev\">Prev version</label><br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_new\" value=\"new\" onclick=\"viewer.versionViewer.pageChanged()\" disabled><label for=\"version_viewer_mode_new\">New version</label><br />                </div>                <div id=\"version_viewer_content\" style=\"padding: 72px 20px 0 20px\"></div>            </div>        </div>    <div id=\"content-shadow\" class=\"hidden\" onclick=\"viewer.onContentClick()\"></div>    <div id=\"content-modal\" class=\"contentModal hidden\" onclick=\"viewer.onModalClick()\"></div>            <div id=\"gallery-modal\" class=\"hidden\">\n          <div id=\"gallery-header\">\n            <div id=\"gallery-header-container\">\n              <div id=\"title\"><div>Favicon</div><div id=\"screensamount\"></div></div>\n              <div id=\"search\"><input type=\"text\" placeholder=\"Search screen...\" id=\"searchInput\" onkeyup=\"searchScreen()\"></div>\n              <div id=\"right\">\n                <div class=\"checkbox-container\">\n                  <input type=\"checkbox\" id=\"galleryShowMap\" onclick=\"viewer.galleryViewer.enableMapMode(this.checked)\"/>\n                  <label for=\"galleryShowMap\"></label>\n                  <span class=\"checkbox-label\">Show map (M)</span>\n                </div>\n                <div id=\"closebtn\" onclick=\"viewer.galleryViewer.hide(); return false;\"><svg class=\"svgIcon\"><use xlink:href=\"#icCloseBtn\"></use></svg></div>\n              </div>\n            </div>\n          </div>\n          <div id=\"gallery\"><div id=\"grid\"></div></div>\n          <div id=\"map-controls\">\n            <div id=\"map-controls-container\">\n              <div class=\"checkbox-container\">\n                <input type=\"checkbox\" id=\"galleryShowMapLinks\" onclick=\"viewer.galleryViewer.showMapLinks(this.checked)\"/>\n                <label for=\"galleryShowMapLinks\"></label>\n                <span class=\"checkbox-label\">Show all links (L)</span>\n              </div>\n              <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" class=\"mapZoom\" onclick=\"viewer.galleryViewer.mapZoomChanged(this.value)\">\n              <span onclick=\"viewer.galleryViewer.resetMapZoom();return false;\" class=\"mapResetZoom\">Reset zoom</span>\n            </div>\n          </div>\n        </div>\n    <div id=\"nav\" class=\"nav\">            <div class=\"navLeft\">                <div id=\"menu\" class=\"menu\">                            <div class=\"groupe\">                                <div id=\"menu_comments_viewer\" class=\"hidden item\" onclick=\"viewer.commentsViewer.toggle(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icAnnotation\"></use></svg>                                    <span>Comments</span>                                    <div class=\"tips\">C</div>                                </div>                                <div id=\"links\" class=\"item\" onclick=\"viewer.toggleLinks(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icPointer\"></use></svg>                                    <span>Hot Spots</span>                                    <div class=\"tips\">⇧</div>                                </div>                                <div  id=\"zoom\" class=\"item\" onclick=\"viewer.toggleZoom(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icResize\"></use></svg>                                    <span>Toogle Auto-Scale</span>                                    <div class=\"tips\">Z</div>                                </div>                                <div  id=\"embed\" class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.share();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icEmbed\"></use></svg>                                    <span>Show Embed Code</span>                                    <div class=\"tips\">E</div>                                </div>                                <div  id=\"grid\" class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.toogleLayout();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icGridLayout\"></use></svg>                                    <span>Toogle Grid Layout</span>                                    <div class=\"tips\">L</div>                                </div>                            <div  id=\"symbols\"  class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.symbolViewer.toggle();  return false;\">                                <svg class='svgIcon'><use xlink:href=\"#icElementInspector\"></use></svg>                                <span>Elements Inspector</span>                                <div class=\"tips\">M</div>                            </div>                                <div id=\"menu_version_viewer\" class=\"hidden item\" onclick=\"addRemoveClass('class','menu','active'); viewer.versionViewer.toggle();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icVersionInspector\"></use></svg>                                    <span>Version Inspector</span>                                    <div class=\"tips\">V</div>                                </div>                            </div>                            <hr>                            <div class=\"groupe\">                                <div class=\"item\" onclick=\"viewer.increaseVersion(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icIncreaseVersion\"></use></svg>                                    <span>Version Up</span>                                    <div class=\"tips\">⇧ ↑</div>                                </div>                                <div class=\"item\" onclick=\"viewer.decreaseVersion(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icDecreaseVersion\"></use></svg>                                    <span>Version Down</span>                                    <div class=\"tips\">⇧ ↓</div>                                </div>                            </div>                            <hr>                            <div  id=\"viewall\" class=\"groupe\">                                <div class=\"item\" onclick=\"viewer.galleryViewer.show(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icGrid\"></use></svg>                                    <span>View All Screens</span>                                    <div class=\"tips\">G</div>                                </div>                                <div  id=\"start\"  class=\"item\" onclick=\"viewer.goToPage(0); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icBack\"></use></svg>                                    <span>Go To Start</span>                                    <div class=\"tips\">S</div>                                </div>                            </div>                </div>                <div id=\"btnMenu\" class=\"btnMenu\" onclick=\"addRemoveClass('class', 'menu', 'active')\">                    <svg class='svgIcon'><use xlink:href=\"#icMenu\"></use></svg>                </div>                <div id=\"btnOpenNew\" style='display:none' class=\"btnMenu\" onclick=\"viewer.openNewWindow();return false;\">                    <svg class='svgIcon'><use xlink:href=\"#icResize\"></use></svg>                </div>                <div class=\"navPreviewNext\">                    <div id=\"nav-left-prev\" class=\"btnPreview\" onclick=\"viewer.previous(); return false;\" title=\"Previous screen\">                        <svg class='svgIcon'><use xlink:href=\"#icArrwLeft\"></use></svg>                    </div>                    <div id=\"nav-left-next\" class=\"btnNext\" onclick=\"viewer.next(); return false;\" title=\"Next screen\"><svg class='svgIcon'><use xlink:href=\"#icArrwRight\"></use></svg></div>                </div>            </div>            <div class=\"navCenter\"><div class=\"pageName title\">Default button</div></div>            <div class=\"navRight\">                        <div id=\"loading\" class=\"hidden\">                            <div class=\"lds-ring\"><div></div><div></div><div></div><div></div></div>                        </div>                        <div id=\"pageComments\" onclick=\"commentsViewer.toggle(); return false;\" class=\"hidden\">                            <svg class=\"svgIcon\"><use xlink:href=\"#icAddComment\"></use></svg>                            <div id = \"counter\">3</div>                        </div>                    </div >           </div>        </div> </div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/Favicon/resources/animations.css",
    "content": "@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/Favicon/resources/cb-modern-ui.css",
    "content": "@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/Favicon/resources/jquery.hotkeys.js",
    "content": "/*jslint browser: true*/\n/*jslint jquery: true*/\n\n/*\n * jQuery Hotkeys Plugin\n * Copyright 2010, John Resig\n * Dual licensed under the MIT or GPL Version 2 licenses.\n *\n * Based upon the plugin by Tzury Bar Yochay:\n * https://github.com/tzuryby/jquery.hotkeys\n *\n * Original idea by:\n * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/\n */\n\n/*\n * One small change is: now keys are passed by object { keys: '...' }\n * Might be useful, when you want to pass some other data to your handler\n */\n\n(function(jQuery) {\n\n  jQuery.hotkeys = {\n    version: \"0.2.0\",\n\n    specialKeys: {\n      8: \"backspace\",\n      9: \"tab\",\n      10: \"return\",\n      13: \"return\",\n      16: \"shift\",\n      17: \"ctrl\",\n      18: \"alt\",\n      19: \"pause\",\n      20: \"capslock\",\n      27: \"esc\",\n      32: \"space\",\n      33: \"pageup\",\n      34: \"pagedown\",\n      35: \"end\",\n      36: \"home\",\n      37: \"left\",\n      38: \"up\",\n      39: \"right\",\n      40: \"down\",\n      45: \"insert\",\n      46: \"del\",\n      59: \";\",\n      61: \"=\",\n      96: \"0\",\n      97: \"1\",\n      98: \"2\",\n      99: \"3\",\n      100: \"4\",\n      101: \"5\",\n      102: \"6\",\n      103: \"7\",\n      104: \"8\",\n      105: \"9\",\n      106: \"*\",\n      107: \"+\",\n      109: \"-\",\n      110: \".\",\n      111: \"/\",\n      112: \"f1\",\n      113: \"f2\",\n      114: \"f3\",\n      115: \"f4\",\n      116: \"f5\",\n      117: \"f6\",\n      118: \"f7\",\n      119: \"f8\",\n      120: \"f9\",\n      121: \"f10\",\n      122: \"f11\",\n      123: \"f12\",\n      144: \"numlock\",\n      145: \"scroll\",\n      173: \"-\",\n      186: \";\",\n      187: \"=\",\n      188: \",\",\n      189: \"-\",\n      190: \".\",\n      191: \"/\",\n      192: \"`\",\n      219: \"[\",\n      220: \"\\\\\",\n      221: \"]\",\n      222: \"'\"\n    },\n\n    shiftNums: {\n      \"`\": \"~\",\n      \"1\": \"!\",\n      \"2\": \"@\",\n      \"3\": \"#\",\n      \"4\": \"$\",\n      \"5\": \"%\",\n      \"6\": \"^\",\n      \"7\": \"&\",\n      \"8\": \"*\",\n      \"9\": \"(\",\n      \"0\": \")\",\n      \"-\": \"_\",\n      \"=\": \"+\",\n      \";\": \": \",\n      \"'\": \"\\\"\",\n      \",\": \"<\",\n      \".\": \">\",\n      \"/\": \"?\",\n      \"\\\\\": \"|\"\n    },\n\n    // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url\n    textAcceptingInputTypes: [\n      \"text\", \"password\", \"number\", \"email\", \"url\", \"range\", \"date\", \"month\", \"week\", \"time\", \"datetime\",\n      \"datetime-local\", \"search\", \"color\", \"tel\"],\n\n    // default input types not to bind to unless bound directly\n    textInputTypes: /textarea|input|select/i,\n\n    options: {\n      filterInputAcceptingElements: true,\n      filterTextInputs: true,\n      filterContentEditable: true\n    }\n  };\n\n  function keyHandler(handleObj) {\n    if (typeof handleObj.data === \"string\") {\n      handleObj.data = {\n        keys: handleObj.data\n      };\n    }\n\n    // Only care when a possible input has been specified\n    if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== \"string\") {\n      return;\n    }\n\n    var origHandler = handleObj.handler,\n      keys = handleObj.data.keys.toLowerCase().split(\" \");\n\n    handleObj.handler = function(event) {\n      //      Don't fire in text-accepting inputs that we didn't directly bind to\n      if (this !== event.target &&\n        (jQuery.hotkeys.options.filterInputAcceptingElements &&\n          jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||\n          (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||\n          (jQuery.hotkeys.options.filterTextInputs &&\n            jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {\n        return;\n      }\n\n      var special = event.type !== \"keypress\" && jQuery.hotkeys.specialKeys[event.which],\n        character = String.fromCharCode(event.which).toLowerCase(),\n        modif = \"\",\n        possible = {};\n\n      jQuery.each([\"alt\", \"ctrl\", \"shift\"], function(index, specialKey) {\n\n        if (event[specialKey + 'Key'] && special !== specialKey) {\n          modif += specialKey + '+';\n        }\n      });\n\n      // metaKey is triggered off ctrlKey erronously\n      if (event.metaKey && !event.ctrlKey && special !== \"meta\") {\n        modif += \"meta+\";\n      }\n\n      if (event.metaKey && special !== \"meta\" && modif.indexOf(\"alt+ctrl+shift+\") > -1) {\n        modif = modif.replace(\"alt+ctrl+shift+\", \"hyper+\");\n      }\n\n      if (special) {\n        possible[modif + special] = true;\n      }\n      else {\n        possible[modif + character] = true;\n        possible[modif + jQuery.hotkeys.shiftNums[character]] = true;\n\n        // \"$\" can be triggered as \"Shift+4\" or \"Shift+$\" or just \"$\"\n        if (modif === \"shift+\") {\n          possible[jQuery.hotkeys.shiftNums[character]] = true;\n        }\n      }\n\n      for (var i = 0, l = keys.length; i < l; i++) {\n        if (possible[keys[i]]) {\n          return origHandler.apply(this, arguments);\n        }\n      }\n    };\n  }\n\n  jQuery.each([\"keydown\", \"keyup\", \"keypress\"], function() {\n    jQuery.event.special[this] = {\n      add: keyHandler\n    };\n  });\n\n})(jQuery || this.jQuery || window.jQuery);\n"
  },
  {
    "path": "docs/Favicon/resources/ux1-ui.css",
    "content": "/* MOD FOR VARIANT B WAS @color-background-primary*/\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: all 0.2s easy;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/Favicon/resources/viewer-center.css",
    "content": "#container, #content {\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "docs/Favicon/resources/viewer-fonts.css",
    "content": "@font-face {\n    font-family: 'Font Awesome';\n    font-weight:400;\n    src: url('fonts/fa-regular-400.woff2') format(\"woff2\"),\n    url('fonts/fa-regular-400.woff') format('woff'), \n    url('fonts/fa-regular-400.ttf') format('truetype');\n}\n@font-face {\n    font-family: 'Font Awesome';\n    font-weight:900;\n    src: url('fonts/fa-solid-900.woff2') format(\"woff2\"),\n    url('fonts/fa-solid-900.woff') format('woff'), \n    url('fonts/fa-solid-900.ttf') format('truetype');\n}\n"
  },
  {
    "path": "docs/Favicon/resources/viewer-top.css",
    "content": "#container{\n\ttext-align: center;\n    margin: 0 auto;\t\n    overflow: auto;\n}\n\n#content,#map{\n\ttext-align: center;\n    margin: 0 auto;\t   \n    /*width:100%;*/\n    /*position:fixed; */\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: 0 auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "docs/Favicon/resources/viewer.css",
    "content": "/*@import \"https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css\";*/\n\n/* Tooltip container */\n.tooltip {\n  position: relative;\n  display: inline-block;\n  /*border-bottom: 1px dotted black; */\n  cursor: pointer;\n}\n\n/* Tooltip text */\n.tooltip .tooltiptext {\n  visibility: hidden;\n  background-color:  var(--color-primary);\n  color: var(--color-background);\n  text-align: center;\n  padding: 10px 10px;\n  border-radius: 4px;\n \n  /* Position the tooltip text - see examples below! */\n  position: absolute;\n  z-index: 1;\n}\n\n/* Show the tooltip text when you mouse over the tooltip container */\n.tooltip:hover .tooltiptext {\n  visibility: visible;\n}\n\n#content .image_div {\n  margin: 0 auto;\n  position: absolute;\n}\n#contentModall .image_div {\n    margin: 0 auto;\n    /* Commented to fix long modals\n    position: absolute;\n    */\n  }\n\n.image_div img {\n  /*-webkit-transform:translate3d(0,0,0);*/\n}\n\n/*** MAPS ***/\n.linksDiv {\n  width: 100%;\n  position: absolute;\n  z-index: 2;\n}\n\n.linkDiv {\n  position: absolute;\n  cursor: pointer;\n  opacity: 0;\n  transition: opacity 0.5s;\n  background-color: #FFC400;\n  pointer-events: auto;\n}\n\n.linkHoverDiv {\n  position: absolute;\n  opacity: 0;\n  cursor: pointer;\n  transition: opacity 0.5s;\n  background-color: #FFC400;\n  pointer-events: auto;\n}\n\n.linkDivHighlight:hover {\n  opacity: 0.2;\n  transition: opacity 0.5s;\n}\n\n.contentLinksVisible .linkDiv {\n  opacity: 0.4;\n  transition: opacity 0.5s;\n}\n\n/************ SYMBOLS ******/\n.symbolLink {}\n\n.modalSymbolLink {}\n\n.symbolDiv {\n  position: absolute;\n  display: none;\n  z-index: 30;\n}\n\n.contentSymbolsVisible .symbolDiv:hover {\n  opacity: 1;\n  outline: 1px dashed red;\n  outline-offset: -1px;\n  background-color: rgba(255, 0, 0, .05);\n  transition: all 0.3s;\n}\n\n.contentSymbolsVisible .symbolDiv {\n  opacity: .3;\n  display: block;\n  cursor: pointer;\n  /*border: 1px dashed red;   */\n  outline: 1px solid red;\n  outline-offset: -1px;\n  transition: all 0.3s;\n}\n\n.svBorderLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 30;\n}\n.contentSymbolsVisible .svBorderLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px dashed red;\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n\n.svMarginLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 31;\n}\n.contentSymbolsVisible .svMarginLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px solid var(--color-accent);\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n.svMarginValueDiv {\n    position: absolute;\n    display: none;\n    z-index: 32;\n}\n.contentSymbolsVisible .svMarginValueDiv {\n    display: block;\n    cursor: pointer;\n    border-radius: 4px;\n    height: 16px;\n    width:30px;\n    line-height: 16px;\n    font-weight: 500;\n    background-color: var(--color-accent);\n    transition: all 0.3s;\n    font-size:10px;\n    color:white;\n}\n\n#comments_viewer,\n#symbol_viewer,\n#version_viewer {\n  width: 100%;\n  overflow-y: auto;\n  padding-bottom: 20px;\n}\n\nselect{\n  height: 32px;\n  width: 100%;\n  font-size: var(--font-small);\n  color: var(--color-primary);\n  border: none;\n  border-radius: 5px;\n  background: var(--color-hover);\n  cursor: pointer;\n  text-indent: 4px;\n  transition: all .12s;\n\n  /* reset */\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n\n  padding: 0px 8px;\n}\n\nselect:hover{\n  background: var(--color-hover-bright);\n  /* color: white; */\n  transition: all .2s;\n}\n\nselect:focus{\n  outline: none;\n}\n\n.checkbox-container{\n  display: flex;\n  align-items: center;\n  justify-content:flex-start;\n\tposition: relative;\n  height: 36px;\n  font-size: 14px;\n}\n\n.checkbox-container label{\n  background-color: var(--color-active);\n\tborder-radius: 20px;\n\tdisplay: inline-block;\n\tposition: relative;\n\ttransition: all 0.2s ease-out;\n\twidth: 28px;\n\theight: 16px;\n\tz-index: 2;\n  cursor: pointer;\n}\n\n#gallery-modal .checkbox-container label {\n  background-color: rgba(255,255,255,.12);\n}\n\n.checkbox-container label::after {\n\tcontent: ' ';\n  background-color: rgb(255, 255, 255);\n\tborder-radius: 50%;\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\ttransform: translateX(0);\n\ttransition: transform 0.12s linear;\n\twidth: 12px;\n\theight: 12px;\n\tz-index: 3;\n  /* box-shadow: var(--shadow1); */\n  transition: all .2s;\n}\n\n.checkbox-container label:hover::after{\n  transition: all .12s;\n}\n\n.checkbox-container input {\n\tvisibility: hidden;\n\tposition: absolute;\n\tz-index: 2;\n}\n\n.checkbox-container input:checked + label::after {\n\ttransform: translateX(calc(100% + 0.5px));\n  background-color: #fff;\n}\n\n.checkbox-container input:checked + label {\n\tbackground-color: var(--color-accent);\n}\n\n#gallery-modal .checkbox-container input:checked + label {\n\tbackground-color: var(--color-accent);\n}\n\n.checkbox-container .checkbox-label{\n  margin-left: 12px;\n}\n\n#symbol_viewer .checkbox-container {\n  padding: 16px 20px 0px 20px;\n}\n\n#gallery-modal .checkbox-container {\n  margin-right: 40px;\n}\n\n#comments_viewer .title, \n#symbol_viewer .title, \n#version_viewer .title {\n  height: 56px;\n  display: flex;\n  align-items: center;\n  position: fixed;\n  box-sizing:border-box;\n  width: 400px;\n  padding: 0 20px;\n  background: var(--color-background);\n  font-weight: var(--font-weight-bold);\n  border-bottom: solid 1px var(--color-border);\n  z-index: 10;\n}\n\n#comments_viewer_content{\n  padding: 72px 20px 0 20px;\n}\n\n#symbol_viewer_content .block{\n  padding: 16px 20px 0 20px;\n}\n\n#symbol_viewer_content .block.twoColumn{\n  display: flex;\n}\n\n.tokenName{\n  /* color: aquamarine; */\n  color: var(--color-accent);\n  white-space: nowrap;\n}\n\n.tokenValue{\n  color: var(--color-secondary);\n  margin-left: 10px;\n}\n\n.twoColumn div{\n  width: 50%;\n}\n\n#symbol_viewer_content .label{\n  color: var(--color-secondary);\n}\n\n#symbol_viewer_content .value{\n  padding-top: 8px;\n}\n\n#symbol_viewer_content .value.code{\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n  line-height: 18px;\n}\n\n.svlink{\n    color: var(--color-accent);\n}\n\n#symbol_viewer_content .icon{\n    font-family: \"Font Awesome\";\n    font-size: 60px;\n    line-height: 60px;\n    font-weight:400;\n}\n#symbol_viewer_content .icon.solid{\n    font-weight:900;\n}\n\n#sv_content{\n    font-size: 20px;\n    line-height: 18px;\n  }\n\n/* #symbol_viewer_content .head {\n  color: var(--color-secondary);\n  border-top: solid 1px var(--color-border);\n}\n\n#version_viewer .head {\n  font-weight: 900;\n} */\n\n/******************/\n/************ LAYOUT ******/\n.layouLineDiv {\n  position: absolute;\n  opacity: 0;\n  transition: opacity 0.5s;\n  z-index: 30;\n  pointer-events: none;\n}\n\n.layoutColDiv {\n  background-color: #FF0000;\n}\n\n.layoutRowDiv {\n  background-color: #000000;\n}\n\n.contentLayoutVisible .layouLineDiv {\n  opacity: 0.05;\n  transition: opacity 0.5s;\n  pointer-events: none;\n}\n\n/******************/\n.map {\n  z-index: 9;\n  position: absolute;\n}\n\n.contentModal {\n  z-index: 50;\n  position: fixed;\n  margin: auto;\n  max-height: 100%;\n  align-items: center;\n}\n\n.contentModal>div {\n  pointer-events: auto;\n}\n\n.contentModal .image_div {\n  z-index: 50;\n}\n\n#content-shadow.shadow {\n  background-color: rgba(5, 4, 4, 0.7);\n}\n\n#content-shadow.no-shadow {\n  background-color: transparent;\n}\n\n/*\n#content-modal>div {\n\tmargin-top: 50px auto 50px;\n\tmargin-bottom: 50px auto 50px;\n}\n*/\n/* GALLERY */\n#gallery-modal {\n  display: flex;\n  justify-content: center;\n  position: fixed;\n  overflow: auto;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100vw;\n  height: 100vh;\n  background-color: var(--color-background-gallery);\n  z-index: 100;\n}\n\n#gallery {\n  /*width: 100%;*/\n  margin: 80px 0px;\n}\n\n.gallery-grid {\n    width: calc(100% - 80px);\n    max-width: 1200px;\n    margin: 80px 40px;\n}\n\n#gallery-header {\n  display: flex;\n  position: fixed;\n  justify-content: center;\n  top: 0;\n  height: 80px;\n  width: 100%;\n  color: white;\n  font-size: 24px;\n  line-height: 32px;\n  background-color: var(--color-background-gallery-90);\n  z-index: 102;\n}\n\n#gallery-header-container {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  /* max-width: 1200px; */\n  width: 100%;\n  margin: 0 40px;\n}\n\n#gallery-header-container #title {\n  text-align: left;\n}\n\n#gallery-header-container #title div{\nwhite-space: nowrap;\noverflow-x: hidden;\ntext-overflow: ellipsis;\n-webkit-line-clamp: 2;\n-webkit-box-orient: vertical;\n}\n\n#map-controls {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 56px;\n  display: flex;\n  justify-content: center;\n  color: white;\n  background-color: var(--color-background-gallery-90);\n  z-index: 103;\n}\n\n#map-controls-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  /* max-width: 1200px; */\n  width: 100%;\n  margin: 0 40px;\n}\n\n#gallery-header-container #screensamount {\n  font-size: 14px;\n  line-height: 20px;\n  font-weight: 300;\n  opacity: .7;\n}\n\n#search {\n  max-width: 280px;\n  flex-grow: 1;\n  display: flex;\n  height: 32px;\n  padding: 0 12px;\n  border-radius: 5px;\n  background: rgba(255, 255, 255, 0.12);\n}\n\n#searchInput {\n  width: 200px;\n  height: 30px;\n  font-size: 14px;\n  font-weight: 300;\n  letter-spacing: 0.2px;\n  color: white;\n  border: none;\n  background: none;\n}\n\n#searchInput:focus {\n  outline: none;\n}\n\n#searchInput::placeholder {\n  color: white;\n  opacity: .5;\n}\n\n#right {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n}\n\n#title, #search, #right {\n  width: calc(100% / 3);\n}\n\n#gallery-header-container #closebtn {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  min-width: 48px;\n  height: 48px;\n  margin-right: -16px;\n  cursor: pointer;\n  opacity: .7;\n  border-radius: 100%;\n  transition: all .2s;\n  background: rgba(0, 0, 0, 0);\n}\n\n#gallery-header-container #closebtn:hover {\n  opacity: 1;\n  background: rgba(0, 0, 0, .18);\n  transition: all .24s;\n}\n\n.gallery-grid{\n    justify-content: center;\n    margin: 0 -32px 80px -32px;\n}\n\n#gallery #grid {\n  /* display: -webkit-box; */\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  /* -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal; */\n  flex-direction: row;\n  margin: 0 -16px 80px -16px;\n  padding-bottom: 40px;\n}\n\n.galleryArtboardAbs{\n    position: absolute;\n    border-radius: 5px;\n    background: none;\n    box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n    cursor: pointer;\n    transition: transform .24s, box-shadow .24s;\n}\n\n.galleryArtboardAbs image{\n}\n\n#gallery #grid svg{\n    z-index:61;\n    pointer-events: none;\n    position:absolute;\n    left:40px;\n    top: 80px;\n}\n\n#gallery #grid svg circle{\n    z-index:62;\n    stroke: #F89000;\n    stroke-width:1;\n    fill:white;\n}\n\n#gallery #grid svg path{\n    stroke: #F89000;\n    stroke-width:1;\n    fill:none;\n    z-index:61;\n}\n\n/* #gallery-header-container .checkbox-container{\n    width:100%;\n} */\n\n#map-controls-container .mapZoom{\n    width:100px;\n}\n\n#map-controls-container .mapResetZoom{\n    color:white;\n    font-size:14px;\n    cursor: pointer;\n    margin-left: 16px;\n}\n\n#commenting {\n  z-index: 100;\n  position: fixed;\n  overflow: auto;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100vw;\n  height: 100vh;\n  display: grid;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: auto;\n  margin-bottom: auto;\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.9);\n}\n\n.grid-cell {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: calc((1200px - 16px*6)/4);\n  height: 212px;\n  overflow: hidden;\n  margin: 16px;\n  border-radius: 5px;\n  background: #fff;\n  box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n  cursor: pointer;\n  transition: transform .24s, box-shadow .24s;\n}\n\n.grid-cell:hover,\n.galleryArtboardAbs:hover {\n  transition: all .2s;\n  transform: translateY(-2px);\n  box-shadow: 0 0.3125rem 2rem 0 rgba(0, 0, 0, 0.3);\n}\n\n/*.grid-cell-wrapper{\n\twidth: 100%;\n\tdisplay: inline-block;\n\tposition: relative;\n}*/\n/*.grid-cell-main{\n\tposition: absolute;\n\ttop:0;\n\tbottom:0;\n\tleft:0;\n\tright:0;\n}*/\n.grid-cell img {\n  width: 100%;\n  /*top:0;*/\n  /*position: absolute;*/\n}\n\n\n#gallery #grid .groupTitle{\n    position: absolute;\n    width: 100%;\n    height:30px;\n    color: gray;\n    font-size: 20px;\n    text-align: left;\n  }\n\n.grid-cell .div-page-title{\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n}\n\n.grid-cell span {\n  padding: 0 16px;\n  height: 56px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  align-self: flex-end;\n  font-size: 14px;\n  color: white;\n  font-weight: 400;\n  background-color: var(--color-background-gallery-90);\n}\n\na,\na:focus {\n  outline: none;\n  color: var(--color-link);\n  text-decoration: none;\n  font-weight: bold;\n}\n\n.transparent {\n  visibility: hidden;\n  opacity: 0;\n}\n\n.hidden {\n  display: none !important;\n}\n\n.hotspots-off {\n  pointer-events: none;\n}\n\n/* VIEWER UI */\n:root {\n\n  /* color */\n  --color-primary: #404B58;\n  --color-secondary: #989EA5;\n  --color-accent: #007AFF;\n  --color-hover: #F4F5F6;\n  --color-hover-bright: var(--color-active);\n  --color-active: #EAEDF0;\n  --color-border: var(--color-active);\n  --color-link:    #568AF2;\n  --color-background: #FFF;\n  --color-background-gallery: var(--color-primary);\n  --color-background-gallery-90: rgba(64,75,88,0.90);\n\n  /* font size */\n  --font-regular: 14px;\n  --font-small: 12px;\n\n  /* font weight  */\n  --font-weight-regular: 300;\n  --font-weight-bold: 700;\n\n  /* line height */\n  --line-height: 20px;\n\n  /* shadow */\n  --shadow1: 0 1px 3px 0 rgba(0,0,0,0.20);\n  --shadow2: 0 0 1px 0 rgba(0,0,0,0.10), 0 2px 8px 0 rgba(0,0,0,0.10);\n\n  /* border */\n  --border: 1px solid rgba(73, 84, 96, 0.06);\n\n  /* border radius */\n  --border-radius: 3px;\n  --border-radius-circle: 100%;\n\n}\n\n/* NAVIGATION */\nbody {\n  font-size: var(--font-regular);\n  line-height: var(--line-height);\n  color: var(--color-primary);\n  margin: 0px;\n  padding: 0px;\n  font-family: -apple-system, BlinkMacSystemFont,\n    \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif,\n    \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n}\n\n.containerSVG {\n  display: none;\n}\n\n.svgIcon {\n  width: 24px;\n  height: 24px;\n  fill: var(--color-primary);\n}\n\n#sidebar {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  display: flex;\n  text-align: left;\n  z-index: 60;\n  background: var(--color-background);\n  border-left: var(--border);\n  box-shadow: var(--shadow2);\n}\n\n.nav {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  width: calc(100% - 24px);\n  display: flex;\n  justify-content: space-between;\n  margin: 12px;\n  user-select: none;\n  pointer-events: none;\n  z-index: 60;\n}\n\n.navLeft,\n.navRight {\n  display: flex;\n}\n\n.navLeft,\n.nav #pageComments {\n  pointer-events: all;\n}\n.navCenter {\n  display: flex;\n  justify-content: center;\n}\n\n.nav .btnMenu,\n.nav .navPreviewNext,\n.nav #pageComments,\n.nav .pageName {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 36px;\n  background: var(--color-background);\n  border-radius: 36px;\n  box-shadow: var(--shadow1);\n  align-self: center;\n  /* opacity: .9; */\n}\n\n.nav #pageComments #counter{\n    margin-left:5px;\n}\n\n.btnMenu,\n.navPreviewNext {\n  margin-right: 12px;\n}\n\n.btnMenu,\n.nav #pageComments,\n.btnPreview,\n.btnNext {\n  cursor: pointer;\n}\n\n.navPreviewNext svg,\n.btnMenu svg,\n.navPreviewNext svg {\n  padding: 6px;\n}\n\n.navPreviewNext {\n  overflow: hidden;\n}\n\n.navPreviewNext div {\n  height: 36px;\n}\n\n.nav .pageName {\n  padding: 0 12px;\n}\n.nav #pageComments {\n    padding: 0 12px;\n}\n\n.btnMenu:hover,\n.nav #pageComments:hover,\n.btnPreview:hover,\n.btnNext:hover {\n  background: var(--color-hover);\n  transition: background .2s;\n  opacity: 1;\n}\n\n.btnMenu:active,\n.nav #pageComments:active,\n.btnPreview:active,\n.btnNext:active {\n  background: var(--color-active);\n  transform: scale(.9);\n  transition: transform .24s;\n  opacity: 1;\n}\n\n.btnPreview:active,\n.btnNext:active {\n  border-radius: 50%;\n}\n\n.nav .disabled {\n  opacity: .5;\n}\n\n/*** MENU ***/\n.menu {\n  position: absolute;\n  bottom: 48px;\n  display: flex;\n  flex-direction: column;\n  width: 240px;\n  padding: 16px 0;\n  background: var(--color-background);\n  border: var(--border);\n  box-shadow: var(--shadow2);\n  border-radius: var(--border-radius);\n  visibility: hidden;\n  opacity: 0;\n  transform: translateY(-8px);\n  transition: all .12s;\n}\n\n.menu.active {\n  opacity: 1;\n  visibility: visible;\n  transform: translateY(0px);\n  transition: all .12s;\n}\n\n.item {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 0 20px;\n  height: 44px;\n  cursor: pointer;\n  background: var(--color-background);\n}\n\n.item:hover {\n  background: var(--color-hover);\n}\n\n.item:active {\n  background: var(--color-active);\n  transition: background .12s;\n}\n\n.item svg {\n  margin-right: 16px;\n}\n\n.item span {\n  flex-grow: 1;\n  text-align: start;\n}\n\n.item .tips {\n  font-size: var(--font-small);\n  color: var(--color-secondary);\n}\n\nhr {\n  display: block;\n  height: 1px;\n  border: 0;\n  border-top: 1px solid var(--color-border);\n  margin: 16px 0 16px 20px;\n  padding: 0;\n}\n\n#sidebar hr {\n  margin: 16px 0 0 20px;\n}\n\narea {\n  cursor: pointer;\n}\n\n/*** OVERLAYS ***/\n.divPanel {\n  position: absolute;\n  z-index: 3;\n}\n\n/*** FIXED PANELS ***/\n.fixedPanelTop {\n    z-index: 14;\n  }\n\n.fixedPanel1 {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    margin: 0 auto;\n    pointer-events: none;\n    user-select: none;\n}\n\n.fixedPanel {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixedPanelFloat {\n    position: fixed;\n    z-index: 13;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixed_back {\n  z-index: 6;\n  background: rgba(100, 100, 100, 255);\n  color: #f1f1f1;\n  text-align: center;\n  margin: 0 auto;\n  position: absolute;\n}\n\n/*\n\n#fixed_left{\n\tposition: fixed;\n\tz-index: 10;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\toverflow: hidden;\n\ttext-align: center;\n\tmargin: 0 auto;\n}\n\n#fixed_left_back{\n\tz-index: 5;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\ttext-align: center;\n\tmargin: 0 auto;\n\tposition: absolute;\n}\n**/\n/*** LOADING INDICATOR ***/\n#nav #loading {\n    width: 40px;\n  }\n\n.lds-ring {\n    display: inline-block;\n    position: relative;\n    width: 36px;\n    height: 28px;\n    margin-top: 4px;\n}\n.lds-ring div {\n    box-sizing: border-box;\n    display: block;\n    position: absolute;\n    width: 24px;\n    height: 24px;\n    border: 2px solid var(--color-secondary) ;\n    border-radius: 50%;\n    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    border-color: var(--color-secondary) transparent transparent transparent;        \n}\n.lds-ring div:nth-child(1) {\n    animation-delay: -0.45s;\n}\n.lds-ring div:nth-child(2) {\n    animation-delay: -0.3s;\n}\n.lds-ring div:nth-child(3) {\n    animation-delay: -0.15s;\n}\n@keyframes lds-ring {\n    0% {\n        transform: rotate(0deg);\n    }\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n.version-screen-div {\n  cursor: pointer;\n}\n\n/*@media only screen and (max-width: 1024px) and (min-width: 768px) {\n\n.grid-cell{ width: calc(100%/2 - 48px);}\n\n}\n\n@media only screen and (max-width: 768px) and (min-width: 320px) {\n\t.grid-cell{ width: 100%; }\n}*/\n\n@media (prefers-color-scheme: dark) {\n\n  /* Dark mode styles go here! */\n  :root {\n    --color-primary: #E0E1E1;\n    --color-secondary: #989EA5;\n    --color-accent: #568AF2;\n    --color-hover: #444549;\n    --color-hover-bright: rgba(255,255,255,.12);\n    --color-active: #282828;\n    --color-border: var(--color-active);\n    --color-link:    #989EA5;\n    --color-background: #35363A;\n    --color-background-gallery: var(--color-background);\n    --color-background-gallery-90: rgba(53,54,58,0.90);\n  }\n\n}\n\n@media (prefers-color-scheme: light) {\n  /* Light styles go here! */\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/AbstractViewer.js",
    "content": "\nclass AbstractViewer {\n    constructor() {\n        // common viewer settings, can be changed in child constructors\n        this.isSidebarChild = true\n        this.blockMainNavigation = false\n        this.enableTopNavigation = false\n        this.alwaysHandlePageChanged = false\n\n        // internal viewer props, can be read by child \n        this.inited = false\n        this.visible = false\n\n        //\n        viewer.allChilds.push(this)\n    }\n\n\n    // called by Viewer\n    pageChanged() {\n\n    }\n\n    // called by viewer\n    viewerResized() {\n\n    }\n\n    hide() {\n        viewer.hideChild()\n    }\n\n    show() {\n        viewer.showChild(this)\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    handleKeyDown(jevent) {\n        return false\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        return false\n    }\n    onContentClick() {\n        return false\n    }\n\n\n\n    isVisible() {\n        return this.visible\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    _showSelf() {\n        this.visible = true\n    }\n\n    _hideSelf() {\n        this.visible = false\n    }\n\n\n\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/CommentsViewer.js",
    "content": "\nlet commentsViewer = null;\n\nclass CommentsViewer extends AbstractViewer {\n    constructor() {\n        super()\n\n        this.alwaysHandlePageChanged = true\n\n        this.comments = null\n        this.inputFocused = false\n        commentsViewer = this\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        this._showLoadingMessage()\n        this._showComments();\n\n        this.inited = true\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        $('#comments_viewer').addClass(\"hidden\")\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").show()\n        this.comments.hideViewer()\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (67 == event.which) { // c\n            // Key \"C\" activates self\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    pageChanged() {\n        this._showCommentCounter()\n        if (!this.visible) {\n            return\n        }\n        if (!this.inited) return this.initialize();\n        comments.reloadComments()\n    }\n\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!comments.inputFocused && 67 == event.which) { // key \"g\"\n            // Key \"G\" deactivates Symbol Viewer\n            this.toggle()\n        } else if (comments.inputFocused) {\n            return true\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n    /////////////////////////////////////////////////\n\n    _showCommentCounter() {\n        var formData = new FormData();\n\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=getInfo\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                commentsViewer.updateCommentCounter(result.data.commentsTotal)\n            } else {\n                console.log(result.message);\n            }\n            return\n\n        };\n        xhr.send(formData);\n    }\n\n    updateCommentCounter(total) {\n        var div = $('#nav #pageComments #counter')\n        if (total > 0) {\n            div.html(total);\n            div.show()\n        } else {\n            div.hide()\n        }\n    }\n\n    _showComments() {\n        var formData = new FormData();\n        //\n        var uid = window.localStorage.getItem(\"comments-uid\")\n        var sid = window.localStorage.getItem(\"comments-sid\")\n        if (null != uid && null != sid) {\n            formData.append(\"uid\", uid);\n            formData.append(\"sid\", sid);\n        }\n        //\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=buildFullHTML\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                $('#comments_viewer_content').html(result.data);\n            } else {\n                $('#comments_viewer_content').html(result.message);\n            }\n            return\n\n        };\n        xhr.send(formData);\n    }\n\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#comments_viewer').removeClass(\"hidden\")\n        super._showSelf()\n        //\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").hide()\n        //\n        if (this.comments) this.comments.showViewer()\n    }\n\n    _showLoadingMessage() {\n        $(\"#comments_viewer_content\").html(\"Loading...\")\n    }\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/GalleryViewer.js",
    "content": "const GALLERY_TOP_MARGIN = 80\nconst GALLERY_LEFTRIGH_MARGIN = 40\n\n\nclass GalleryViewerMapLink {\n    constructor(index, link, spage, dpage) {\n        this.index = index\n        this.link = link\n        this.spage = spage\n        this.dpage = dpage\n        //\n        if (undefined == spage.slinks) spage.slinks = []\n        if (undefined == dpage.dlinks) dpage.dlinks = []\n        spage.slinks.push(this)\n        dpage.dlinks.push(this)\n    }\n\n    buildCode(zoom, visible) {\n        const page = this.spage\n        const dpage = this.dpage\n        const l = this.link\n        let svg = \"\"\n        //\n        var lsx = l.rect.x + l.rect.width / 2 + page.finalLeft\n        var lsy = l.rect.y + l.rect.height / 2 + page.finalTop\n        //\n        var ldx0 = dpage.finalLeft\n        var ldx1 = dpage.finalLeft + dpage.width\n        var ldy0 = dpage.finalTop\n        var ldx = 0, ldy = 0, dc = 0\n        // find the best target edge to connect with\n        if (ldx0 > lsx) {\n            ldx = ldx0\n            ldy = ldy0 + dpage.height / 2\n            lsx += l.rect.width / 2 // place start to hotspot right edge\n            dc = 50\n        } else if (lsx > ldx1) {\n            ldx = ldx1\n            ldy = ldy0 + dpage.height / 2\n            lsx -= l.rect.width / 2 // place start to hotspot left edge\n            dc = 50\n        } else if (ldy0 > lsy) {\n            lsy += l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0\n            dc = 0\n        } else {\n            lsy -= l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0 + dpage.height\n            dc = 0\n        }\n        //\n        //\n        const styleCode = visible ? \"\" : \" style='display:none' \"\n        svg += \"<path \" + styleCode + \"marker-end='url(#arrow)' id='l\" + this.index + \"' d='M \"\n            + Math.round(lsx * zoom) + \" \"\n            + Math.round(lsy * zoom) + \" \"\n            + \"q \"\n            + Math.round((ldx - lsx) / 2 * zoom) + \" \"\n            + dc + \" \"\n            + Math.round((ldx - lsx) * zoom) + \" \"\n            + Math.round((ldy - lsy) * zoom) + \" \"\n            + \"'/>\"\n        //\n        svg += \"<circle \" + styleCode + \"' id='s\" + this.index + \"' cx='\" + Math.round(lsx * zoom) + \"' cy='\" + Math.round(lsy * zoom) + \"' r='3'/>\"\n        //\n        return svg\n    }\n}\n\nclass GalleryViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.isSidebarChild = false\n        this.blockMainNavigation = true\n        this.enableTopNavigation = true\n        this.mapLinks = null\n        this.mapFocusedPage = null\n        this.isMapMode = false\n        //\n        const restoredMode = window.localStorage.getItem(\"galleryIsModeAbs\") == \"true\"\n        if (null != restoredMode) this.isMapMode = restoredMode\n        $(\"#gallery-header-container #right #galleryShowMap\").prop('checked', this.isMapMode);\n        //\n        this.isMapLinksVisible = false\n        if (window.localStorage.getItem(\"galleryIsLinkVisible\") == \"true\") this.isMapLinksVisible = true\n        $(\"#map-controls #map-controls-container #galleryShowMapLinks\").prop('checked', this.isMapLinksVisible);\n        //\n        this.mapZoom = 0.2\n        this.isCustomMapZoom = false\n        this.currentFullWidth = null\n\n        this.searchInputFocused = false\n    }\n\n    initialize(force = false, skipZoomUpdate = false) {\n        if (!force && this.inited) return\n\n        // adjust main container for current mode\n        if (this.isMapMode) {\n            $(\"#gallery\").removeClass(\"gallery-grid\")\n        } else {\n            $(\"#gallery\").addClass(\"gallery-grid\")\n        }\n\n        $('#gallery #grid').empty()\n        this.loadPages();\n\n        //load amount of pages to gallery title\n        document.getElementById(\"screensamount\").innerHTML = viewer.userStoryPages.length + \" screens\";\n\n        // Adjust map zoom\n        const zoomContainter = $(\"#map-controls\")\n        if (this.isMapMode) {\n            if (!skipZoomUpdate) {\n                const zoomControl = $(\".mapZoom\")\n                zoomControl.val(this.mapZoom * 100)\n            }\n            zoomContainter.show();\n        } else {\n            zoomContainter.hide()\n        }\n\n        this.inited = true\n    }\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!this.searchInputFocused && 71 == event.which) { // key \"g\"\n            // Key \"G\" deactivates Symbol Viewer\n            this.toggle()\n        } else if (this.searchInputFocused) {\n            return true\n        } else if (76 == event.which) { // key \"l\"\n            $(\"#galleryShowMapLinks\").click()\n        } else if (77 == event.which) { // key \"m\"\n            $(\"#galleryShowMap\").click()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    mapZoomChanged(zoomValue) {\n        this.mapZoom = zoomValue / 200\n        this.isCustomMapZoom = true\n        this.initialize(true, true)\n    }\n\n    viewerResized() {\n        if (!this.isMapMode) return\n        this.initialize(true)\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (71 == event.which) { // g\n            // Key \"G\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    /// calling from parent\n    handleURLParam(paramValue) {\n        const enableMap = \"m\" == paramValue\n        if (enableMap != this.isMapMode) {\n            this.enableMapMode(enableMap)\n            $(\"#galleryShowMap\").prop('checked', enableMap);\n        }\n    }\n\n    // Calling from UI\n    enableMapMode(enabled) {\n        window.localStorage.setItem(\"galleryIsModeAbs\", enabled)\n        this.isMapMode = enabled\n        this.initialize(true)\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    // Calling from UI\n    showMapLinks(visible) {\n        window.localStorage.setItem(\"galleryIsLinkVisible\", visible)\n        this.isMapLinksVisible = visible\n        this._showHideMapLinks(visible ? null : false)\n    }\n\n    // Calling from UI\n    resetMapZoom() {\n        this.isCustomMapZoom = false\n        this.initialize(true)\n    }\n\n    _showSelf() {\n        if (!this.inited || this.currentFullWidth != viewer.fullWidth) this.initialize(true)\n\n        $('#gallery-modal').removeClass('hidden');\n\n        $('#searchInput').focusin(function () {\n            viewer.galleryViewer.searchInputFocused = true\n        })\n        $('#searchInput').focusout(function () {\n            viewer.galleryViewer.searchInputFocused = false\n        })\n        $('#searchInput').focus()\n\n        super._showSelf()\n\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    _hideSelf() {\n        $('#gallery-modal').addClass('hidden');\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n\n\n    loadPages() {\n        if (this.isMapMode) return this.loadPagesAbs()\n        viewer.userStoryPages.forEach(function (page) {\n            this.loadOnePage(page);\n        }, this);\n    }\n\n    loadPagesAbs() {\n        let groupSpace = 80\n\n        // find maximum width of Sketch page with artoards\n        let maxGroupWidth = null\n\n        story.groups.forEach(function (group) {\n            // find group pages\n            const pages = viewer.userStoryPages.filter(s => s.groupID == group.id)\n            group.pages = pages // save for below\n            if (pages.length == 0) return\n            ///\n            let left = null, right = null, top = null, bottom = null\n            pages.forEach(function (page) {\n                page.group = group\n                page.slinks = []\n                page.dlinks = []\n                //\n                if (null == top || page.y < top) top = page.y\n                if (null == left || page.x < left) left = page.x\n                if (null == right || (page.x + page.width) > right) right = page.x + page.width\n                if (null == bottom || (page.y + page.height) > bottom) bottom = page.y + page.height\n            }, this);\n            const groupWidth = right - left\n            if (null == maxGroupWidth || groupWidth > maxGroupWidth) maxGroupWidth = groupWidth\n            // // save for below\n            group.top = top\n            group.bottom = bottom\n            group.left = left\n            group.right = right\n            group.height = bottom - top\n        }, this);\n\n        // Calculate zoom to fit max width\n        if (!this.isCustomMapZoom) {\n            this.mapZoom = (viewer.fullWidth - GALLERY_LEFTRIGH_MARGIN * 2) / maxGroupWidth\n            if (this.mapZoom > 0.6) this.mapZoom = 0.6\n        }\n        this.currentFullWidth = viewer.fullWidth\n\n        // show pages using their coordinates and current zoom\n        let deltaY = 0\n        let fullHeight = 0\n        const groupTitleHeight = 40 / this.mapZoom\n        story.groups.forEach(function (group) {\n            if (group.pages.length == 0) return\n            ///\n            let top = deltaY - group.top\n            const left = group.left\n            group.finalTop = deltaY\n            top += groupTitleHeight\n            //// show group title\n            this.addMapPageGroupTitle(group)\n            //// show pages\n            group.pages.forEach(function (page) {                //\n                this.loadOnePageAbs(page, left, top);\n            }, this);\n            //\n            fullHeight += group.height\n            //\n            deltaY += group.height + groupSpace + groupTitleHeight + 30\n        }, this);\n        fullHeight = deltaY //+= groupSpace * (story.groups.length - 1)\n\n        //\n        this._buildMapLinks(maxGroupWidth, fullHeight)\n    }\n\n\n    addMapPageGroupTitle(group) {\n        let style = this._valueToStyle(\"left\", 0, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", group.finalTop, GALLERY_TOP_MARGIN)\n\n        var div = $('<div/>', {\n            id: \"g\" + group.id,\n            class: \"groupTitle\",\n            style: style,\n        });\n        div.html(group.name)\n        div.appendTo($('#gallery #grid'));\n\n    }\n\n\n    selectPage(index) {\n        this.hide()\n        viewer.goToPage(index)\n    }\n\n    mouseEnterPage(index) {\n        if (this.isMapLinksVisible) return\n        //\n        if (this.mapFocusedPage) this.mapFocusedPage.showHideGalleryLinks(false)\n        const page = story.pages[index]\n        page.showHideGalleryLinks(true)\n        this.mapFocusedPage = page\n    }\n\n\n    loadOnePage(page) {\n        var imageURI = page.image\n\n        var div = $('<div/>', {\n            id: page.index,\n            class: \"grid-cell\",\n        });\n\n        var divWrapper = $('<div/>', {\n            class: \"grid-cell-wrapper\"\n        });\n        var divMain = $('<div/>', {\n            class: \"grid-cell-main\"\n        });\n        div.click(function (e) {\n            viewer.galleryViewer.selectPage(parseInt(this.id));\n        });\n        div.appendTo($('#gallery #grid'));\n\n        var img = $('<img/>', {\n            class: \"gallery-image\",\n            alt: page.title,\n            src: encodeURIComponent(viewer.files) + '/previews/' + encodeURIComponent(imageURI),\n        });\n\n        img.appendTo(divMain);\n        divMain.appendTo(divWrapper);\n        divWrapper.appendTo(div);\n\n        var divTitle = $('<div/>', {\n            class: \"div-page-title\"\n        });\n\n        var title = $('<span/>', {\n            id: \"page-title\",\n            alt: page.title,\n            text: page.title,\n        });\n\n        title.appendTo(divTitle);\n        divTitle.appendTo(divMain);\n    }\n\n    loadOnePageAbs(page, pageLeft, pageTop) {\n        page.finalTop = pageTop + page.y\n        page.finalLeft = page.x - pageLeft\n\n        let style = this._valueToStyle(\"left\", page.finalLeft, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", page.finalTop, GALLERY_TOP_MARGIN)\n            + this._valueToStyle(\"width\", page.width) + this._valueToStyle(\"height\", page.height)\n        if (undefined != story.backColor) {\n            style += \"background-color:\" + story.backColor + \";\"\n        }\n\n        var div = $('<div/>', {\n            id: page.index,\n            class: \"galleryArtboardAbs\",\n            style: style,\n        });\n\n        div.click(function (e) {\n            viewer.galleryViewer.selectPage(parseInt(this.id));\n        });\n        div.mouseenter(function () {\n            viewer.galleryViewer.mouseEnterPage(this.id)\n        })\n        div.appendTo($('#gallery #grid'));\n\n        const width = Math.round(this.mapZoom * page.width)\n        // Show large image for large width\n        const previewWidth = 522\n        let src = encodeURIComponent(viewer.files)\n        if (width < previewWidth) {\n            src += '/previews/' + encodeURIComponent(page.image)\n        } else {\n            src += '/' + encodeURIComponent(story.hasRetina ? page['image2x'] : page.image)\n        }\n\n        var img = $('<img/>', {\n            class: \"gallery-map-image\",\n            alt: page.title,\n            width: width,\n            height: Math.round(this.mapZoom * page.height) + \"px\",\n            src: src\n        });\n        img.appendTo(div);\n    }\n\n    _valueToStyle(styleName, v, absDelta = 0) {\n        return styleName + \": \" + Math.round(v * this.mapZoom + absDelta) + \"px;\"\n    }\n\n    _showHideMapLinks(show) {\n        viewer.userStoryPages.forEach(function (page) {\n            page.showHideGalleryLinks(show)\n        });\n    }\n\n\n    _buildMapLinks(finalWidth, finalHeight) {\n\n        // build scene\n        let svg = \"<svg\"\n            + \" height='\" + Math.abs(Math.round(finalHeight * this.mapZoom)) + \"'\"\n            + \" width='\" + Math.abs(Math.round(finalWidth * this.mapZoom)) + \"'\"\n            + \" >\"\n        svg += `\n            <defs>\n             <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\"\n                markerWidth=\"6\" markerHeight=\"6\" fill=\"#F89000\"\n                orient=\"auto\">\n                 <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#F89000\"/>\n             </marker>\n            </defs>\n        `\n        //\n        let indexCounter = 0\n        this.mapLinks = []\n        //\n        viewer.userStoryPages.forEach(function (page) {\n            /// Show links to other pages\n            page.links.forEach(function (l) {\n                // valide destination page\n                if (l.page == page.index) return\n                const dpage = story.pages[l.page]\n                if (!dpage || \"external\" == dpage.type || \"overlay\" == dpage.type) return\n                // build SVG coode for the link\n                const link = new GalleryViewerMapLink(indexCounter++, l, page, dpage)\n                svg += link.buildCode(this.mapZoom, this.isMapLinksVisible)\n                this.mapLinks.push(link)\n            }, this)\n        }, this)\n\n        svg += \"</svg>\"\n        $('#gallery #grid').append(svg)\n    }\n\n}\n\n//Search page in gallery by page name\nfunction searchScreen() {\n    var keyword = $(\"#searchInput\").val().toLowerCase();\n    var foundScreenAmount = 0;\n\n    viewer.userStoryPages.forEach(function (page) {\n        const title = page.title.toLowerCase()\n        const div = $(\"#gallery #grid #\" + page.index)\n        const visible = title.includes(keyword) || page.image.includes(keyword)\n        if (visible) foundScreenAmount++\n        page.visibleInGallery = visible\n        //\n        //if (div.is(':visible') == visible) return\n        //\n        if (visible) {\n            div.show()\n        } else {\n            div.hide()\n        }\n    });\n\n    viewer.galleryViewer._showHideMapLinks()\n\n    //load amount of pages to gallery title\n    $(\"#screensamount\").html(foundScreenAmount + \" screens\")\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/LayersData.js",
    "content": "const SYMBOLS_DICT = {};\nconst TOKENS_DICT = {};var layersData = [{\"ii\":0,\"nextLinkIndex\":0,\"artboardType\":0,\"isModal\":false,\"showShadow\":true,\"overlayOverFixed\":false,\"overlayAlsoFixed\":true,\"overlayClosePrevOverlay\":false,\"transAnimType\":0,\"overlayByEvent\":0,\"oldOverlayAlign\":0,\"overlayPin\":0,\"overlayPinHotspot\":0,\"overlayPinPage\":0,\"index\":0,\"x\":163,\"y\":85,\"w\":579,\"h\":301,\"c\":[{\"ii\":1,\"x\":0,\"y\":1,\"w\":579,\"h\":301,\"s\":\"_Stage\",\"c\":[{\"ii\":2,\"x\":494,\"y\":21,\"w\":60,\"h\":60,\"c\":[{\"ii\":3,\"x\":494,\"y\":21,\"w\":60,\"h\":60,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #4A90E2;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":4,\"x\":501.50390625,\"y\":28.70703125,\"w\":43.83179262311364,\"h\":40.117687017624945,\"tp\":\"ShapePath\",\"n\":\"Path\",\"pr\":\"border-color: #FFFFFF;\\nborder-width: 1px;\\n\"}],\"tp\":\"Group\",\"n\":\"@SiteIcon@\"},{\"ii\":5,\"x\":0,\"y\":1,\"w\":579,\"h\":301,\"tp\":\"ShapePath\",\"n\":\"Background\",\"pr\":\"background-color: #FFFFFF;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"}],\"tp\":\"SI\"},{\"ii\":6,\"x\":257,\"y\":138,\"w\":26,\"h\":13,\"tp\":\"Text\",\"n\":\"Test\",\"tx\":\"Test\",\"pr\":\"font-family: Graphik;\\nfont-size: 13.3px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ncolor: #47485A;\\n\"}],\"tp\":\"Artboard\",\"n\":\"Artboard\"}]"
  },
  {
    "path": "docs/Favicon/viewer/SymbolViewer.js",
    "content": "const ELEMENTINSPECTOR_LINUX_FONT_SIZES = {\n    \"8px\": \"6px\",\n    \"10px\": \"7px\",\n    \"11px\": \"8px\",\n    \"12px\": \"9px\",\n    \"13px\": \"10px\",\n    \"15px\": \"11px\",\n    \"16px\": \"12px\",\n    \"17px\": \"13px\",\n    \"19px\": \"14px\",\n    \"20px\": \"15px\",\n    \"21px\": \"16px\",\n    \"23px\": \"17px\",\n    \"24px\": \"18px\",\n    \"25px\": \"19px\",\n    \"26px\": \"20px\"\n}\n\nconst SUPPORT_TYPES = [\"Text\", \"ShapePath\", \"Image\"]\n\nclass SymbolViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.createdPages = {}\n        //this.symbolIDs = {} // layer indexes ( in pages[].layers ) of symbols\n        this.currentLib = \"\"\n        this.selected = null\n        this.showSymbols = false\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        // populate library select\n        const libSelect = $('#symbol_viewer #lib_selector')\n        libSelect.append($('<option>', {\n            value: \"\",\n            text: 'Library autoselection'\n        }));\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            libSelect.append($('<option>', {\n                value: libName,\n                text: libName\n            }));\n        }\n        libSelect.change(function () {\n            var libName = $(this).children(\"option:selected\").val();\n            viewer.symbolViewer._selectLib(libName)\n\n        })\n        //\n        const symCheck = $('#symbol_viewer_symbols')\n        symCheck.click(function () {\n            viewer.symbolViewer._setSymCheck($(this).is(':checked'))\n\n        })\n\n        this.inited = true\n    }\n\n    _setSymCheck(showSymbols) {\n        this.showSymbols = showSymbols\n        $('#lib_selector').toggle()\n        this._reShowContent()\n\n    }\n\n    // called by Viewer\n    pageChanged() {\n        this._buildSymbolLinks()\n    }\n\n    _selectLib(libName) {\n        this.currentLib = libName\n        this._reShowContent()\n    }\n\n    _reShowContent() {\n        delete this.createdPages[viewer.currentPage.index]\n\n        // remove existing symbol links\n        this.page.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        for (const panel of this.page.fixedPanels) {\n            panel.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        }\n\n        // redraw inspector\n        this._showEmptyContent()\n\n        // rebuild links\n        this._buildSymbolLinks()\n    }\n\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n\n\n    _hideSelf() {\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        if (isModal) {\n            $(\".modalSymbolLink\").remove()\n            delete this.createdPages[viewer.currentPage.index]\n        }\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.removeClass(\"contentSymbolsVisible\")\n\n        viewer.linksDisabled = false\n        $('#symbol_viewer').addClass(\"hidden\")\n\n        this.setSelected(null, null, null)\n\n        super._hideSelf()\n    }\n\n    onContentClick() {\n        this.setSelected(null)\n        return true\n    }\n\n    handleKeyDown(jevent) {\n\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" eactivates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n\n        viewer.toggleLinks(false)\n        viewer.toogleLayout(false)\n        viewer.linksDisabled = true\n\n        this._buildSymbolLinks()\n\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.addClass(\"contentSymbolsVisible\")\n\n        this._showEmptyContent()\n\n        $('#symbol_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n\n    }\n\n    _showEmptyContent() {\n        $(\"#symbol_viewer_content\").html(\"\")\n        $('#symbol_viewer #empty').removeClass(\"hidden\")\n    }\n\n    _buildSymbolLinks() {\n        this._showPage(viewer.currentPage)\n        for (let overlay of viewer.currentPage.currentOverlays) {\n            this._showPage(overlay)\n        }\n    }\n\n\n    _showPage(page) {\n        var pageIndex = page.index\n        this.pageIndex = pageIndex\n        this.page = page\n        if (!(pageIndex in this.createdPages)) {\n            const newPageInfo = {\n                layerArray: [],\n                siLayerIndexes: {}\n            }\n            // cache only standalone pages\n            this.createdPages[pageIndex] = newPageInfo\n\n            this.pageInfo = newPageInfo\n            this._create()\n        } else {\n            this.pageInfo = this.createdPages[pageIndex]\n        }\n    }\n\n\n\n    _create() {\n        const layers = layersData[this.pageIndex].c\n        if (undefined == layers) return\n        if (this.showSymbols)\n            this._processSymbolList(layers)\n        else\n            this._processLayerList(layers)\n    }\n\n    _processSymbolList(layers, isParentSymbol = false) {\n        for (var l of layers.slice().reverse()) {\n            if (this.currentLib != \"\") {\n                if (this.showSymbols && l.b) {\n                    if (l.b == this.currentLib) {\n                        this._showElement(l)\n                        continue\n                    }\n                }\n                if (!this.showSymbols && undefined != l.l) {\n                    const styleInfo = this._findStyleAndLibByStyleName(l.l)\n                    if (styleInfo && styleInfo.libName == this.currentLib) {\n                        this._showElement(l)\n                        continue\n                    }\n                }\n            } else {\n                if ((this.showSymbols && l.s != undefined) ||\n                    (!this.showSymbols && !isParentSymbol && l.l != undefined)) {\n                    this._showElement(l)\n                }\n            }\n            if (undefined != l.c)\n                this._processSymbolList(l.c, this.showSymbols && l.s != undefined)\n        }\n    }\n\n    _processLayerList(layers, sSI = null) {\n        for (var l of layers.slice().reverse()) {\n            if (SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd) {\n                this._showElement(l, sSI)\n            }\n            if (undefined != l.c)\n                this._processLayerList(l.c, \"SI\" == l.tp ? l : sSI)\n        }\n    }\n\n    _showElement(l, siLayer = null) {\n        if (l.hd) return\n\n        var currentPanel = this.page\n        l.finalX = l.x\n        l.finalY = l.y\n\n        for (const panel of this.page.fixedPanels) {\n            if (l.x >= panel.x && l.y >= panel.y &&\n                ((l.x + l.w) <= (panel.x + panel.width)) && ((l.y + l.h) <= (panel.y + panel.height))\n            ) {\n                l.finalX = l.x - panel.x\n                l.finalY = l.y - panel.y\n                currentPanel = panel\n                break\n            }\n        }\n        l.parentPanel = currentPanel\n\n\n        // Check if some layer on top of current\n        for (const pl of this.pageInfo.layerArray) {\n            if (pl.finalX <= l.finalX && pl.finalY <= l.finalY && (pl.finalX + pl.w) >= (l.finalX + l.w) && (pl.finalY + pl.h) >= (l.finalY + l.h)) return\n        }\n\n        // Check if layer is empty\n        if (\"Text\" == l.tp) {\n            if (\"\" == l.tx.trim()) return\n        }\n\n        // also push symbol instance to a list of layers (if was not aded before)\n        let indexOfSO = -1\n        if (siLayer) {\n            if (siLayer.s in this.pageInfo.siLayerIndexes) {\n                indexOfSO = this.pageInfo.siLayerIndexes[siLayer.s]\n            } else {\n                indexOfSO = this.pageInfo.layerArray.length\n                this.pageInfo.layerArray.push(siLayer)\n            }\n        }\n        //\n\n        l.infoIndex = this.pageInfo.layerArray.length\n        this.pageInfo.layerArray.push(l)\n\n        var a = $(\"<a>\", {\n            class: viewer.currentPage.isModal ? \"modalSymbolLink\" : \"symbolLink\",\n            pi: this.pageIndex,\n            li: l.infoIndex,\n            si: indexOfSO\n        })\n\n        a.click(function (event) {\n            const sv = viewer.symbolViewer\n            const pageIndex = $(this).attr(\"pi\")\n            const layerIndex = $(this).attr(\"li\")\n            const siLayerIndex = $(this).attr(\"si\")\n            const pageInfo = sv.createdPages[pageIndex]\n            let topLayer = pageInfo.layerArray[layerIndex]\n            const siLayer = siLayerIndex >= 0 ? pageInfo.layerArray[siLayerIndex] : null\n\n            sv.setSelected(event, topLayer, $(this))\n            if (!sv.selected) {\n                return false\n            }\n            const layer = sv.selected.layer // selection can be changed inside setSelected\n\n            var symName = sv.showSymbols ? layer.s : (siLayer ? siLayer.s : null)\n            var styleName = layer.l\n            var comment = layer.comment\n            var frameX = layer.finalX\n            var frameY = layer.finalY\n            var frameWidth = layer.w\n            var frameHeight = layer.h\n\n            const styleInfo = styleName != undefined ? viewer.symbolViewer._findStyleAndLibByStyleName(styleName) : undefined\n            const symInfo = symName != undefined ? viewer.symbolViewer._findSymbolAndLibBySymbolName(symName) : undefined\n\n            var info = \"\"\n            // layer.b : shared library name, owner of style or symbol\n            // layer.s : symbol name\n            // layer.l : style name\n            // layer.tp : layer type: SI, Text, ShapePath or Image\n            // siLayer : symbol master, owner of the layer            \n\n            if (symName != undefined) {\n                info = \"<hr>\" +\n                    \"<div class='block'>\" +\n                    \"<div class='label'>\" + \"Symbol\" + \"</div>\" +\n                    \"<div class='value'>\" + symName + \"</div>\"\n                const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n                    (siLayer && siLayer.b ? siLayer.b + \" (external)\" : \"Document\")\n                info += \"<div style='font-size:12px; color:var(--color-secondary)'>\" + libName + \"</div></div>\"\n\n            }\n            if (styleName != undefined) {\n                info = \"<hr>\" +\n                    \"<div class='block'>\" +\n                    \"<div class='label'>\" + \"Style\" + \"</div>\" +\n                    \"<div class='value'>\" + styleName + \"</div>\"\n                const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n                    (siLayer ? siLayer.b + \" (external)\" : \"Document\")\n                info += \"<div style='font-size:12px; color:var(--color-secondary)'>\" + libName + \"</div></div>\"\n            }\n\n\n            if (comment != undefined) info +=\n                \"<hr>\" +\n                \"<div class='block'>\" +\n                \"<div class='label'>\" + \"Comment\" + \"</div>\" +\n                \"<div style='value'>\" + comment + \"</div>\" + 2\n            \"</div>\"\n\n            info += \"<hr>\" +\n                \"<div class='block twoColumn'>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"X: </span>\" + Math.round(frameX) + \"px\" +\n                \"</div>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Y: </span>\" + Math.round(frameY) + \"px\" +\n                \"</div>\" +\n                \"</div>\"\n\n            info += \"<div class='block twoColumn'>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Width: </span>\" + Math.round(frameWidth) + \"px\" +\n                \"</div>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Height: </span>\" + Math.round(frameHeight) + \"px\" +\n                \"</div>\" +\n                \"</div>\"\n\n\n            if (layer.pr != undefined) {\n                let tokens = null\n                if (styleInfo)\n                    tokens = styleInfo.style.tokens\n                else if (symInfo) {\n                    const foundLayer = symInfo.symbol.layers[layer.n]\n                    if (foundLayer) tokens = foundLayer.tokens\n                }\n                const decRes = sv._decorateCSS(layer, tokens, layer.b ? layer : siLayer)\n                info += decRes.css\n                if (\"Text\" == layer.tp) {\n                    if (layer.tx != undefined && layer.tx != \"\") {\n                        info += `\n                            <hr>\n                            <div class='block'>\n                            <div class='label'>Content&nbsp;<button onclick = \"copyToBuffer('sv_content')\">Copy</button>`\n                        let afterContent = \"\"\n                        let cssClass = \"\"\n                        if (decRes.styles[\"font-family\"].startsWith(\"Font Awesome 5\")) {\n                            cssClass += \"icon \"\n                            if (decRes.styles[\"font-weight\"] != \"400\") cssClass += \"solid \"\n                            const codeText = layer.tx.codePointAt(0).toString(16)\n                            afterContent = \"Unicode: \" + codeText\n                            info += `<button onclick = \"showFAIconInfo('` + codeText + `')\">Info</button>`\n                        } else {\n                            cssClass += \"code value\"\n                        }\n                        info += `</div ><div id='sv_content' class=\"` + cssClass + `\">` + layer.tx + \"</div>\"\n                        if (afterContent != \"\") {\n                            info += \"<div  class='code value'>\" + afterContent + \"</div>\"\n                        }\n                        info += \"</div>\"\n                    }\n                }\n            }\n\n            if (\"Image\" == layer.tp) {\n                const url = layer.iu\n                info += `\n                        <hr>\n                        <div class='block'>\n                        <div class='label'>Content&nbsp;<a class=\"svlink\" href=\"`+ url + `\">Download</a>`\n                let cssClass = \"code value\"\n                const width = \"100%\" //viewer.defSidebarWidth - 40\n                info += `</div ><div id='sv_content' class=\"` + cssClass + `\"><img ` + `width=\"` + width + `\" src=\"` + url + `\"/></div>`\n            }\n\n            $('#symbol_viewer #empty').addClass(\"hidden\")\n            $(\"#symbol_viewer_content\").html(info)\n            $(\"#symbol_viewer_content\").removeClass(\"hidden\")\n\n            //alert(info)\n            return false\n        })\n\n        a.prependTo(currentPanel.linksDiv)\n\n        var style = \"left: \" + l.finalX + \"px; top:\" + l.finalY + \"px; \"\n        style += \"width: \" + l.w + \"px; height:\" + l.h + \"px; \"\n        var symbolDiv = $(\"<div>\", {\n            class: \"symbolDiv\",\n        }).attr('style', style)\n        symbolDiv.mouseenter(function () {\n            viewer.symbolViewer.mouseEnterLayerDiv($(this))\n        })\n\n        symbolDiv.appendTo(a)\n    }\n\n    setSelected(event = null, layer = null, a = null, force = false) {\n        const prevClickedLayer = this.lastClickedLayer\n        this.lastClickedLayer = layer\n        //\n        const click = event && event.offsetX ? {\n            x: event.offsetX * viewer.currentZoom + layer.finalX,\n            y: event.offsetY * viewer.currentZoom + layer.finalY\n        } : {}\n        let foundLayers = []\n        this.findOtherSelection(click, null, foundLayers)\n        // reset previous selection                \n        if (this.selected) {\n            if (!force && event && layer) {\n                if (foundLayers.length > 1) {\n                    let newIndex = undefined\n                    if (undefined != prevClickedLayer && layer.ii != prevClickedLayer.ii) {\n                        // clicked on an other layer, find its index\n                        newIndex = foundLayers.indexOf(layer)\n                    } else if (undefined != this.selectedLayerIndex) {\n                        // clicked on the some layer, but \n                        // we have several overlaped objects under a cursor, so switch to the next \n                        newIndex = (this.selectedLayerIndex + 1) >= foundLayers.length ? 0 : this.selectedLayerIndex + 1\n                    } else {\n                        newIndex = foundLayers.indexOf(layer)\n                    }\n                    layer = foundLayers[newIndex]\n                    this.selectedLayerIndex = newIndex\n                }\n            }\n            this.selected.marginDivs.forEach(d => d.remove())\n            this.selected.borderDivs.forEach(d => d.remove())\n        } else {\n            this.selectedLayerIndex = foundLayers.indexOf(layer)\n        }\n\n        if (!layer) {\n            this.selected = null\n            this.lastClickedLayer = undefined\n            this.selectedLayerIndex = undefined\n            ////\n            $('#symbol_viewer #empty').removeClass(\"hidden\")\n            $(\"#symbol_viewer_content\").addClass(\"hidden\")\n            ////\n            return\n        }\n        // select new\n        this.selected = {\n            layer: layer,\n            a: $(this),\n            marginDivs: [],\n            borderDivs: [],\n        }\n        // draw left vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw right vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX + layer.w, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw top horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n        // draw bottom horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY + layer.h, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n    }\n\n    findOtherSelection(click, layers, foundLayers) {\n        if (null == layers) layers = layersData[this.pageIndex].c\n        if (undefined == layers) return\n        for (var l of layers.slice().reverse()) {\n\n            if (SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd) {\n                if (click.x >= l.finalX && click.x <= (l.finalX + l.w) && click.y >= l.finalY && click.y <= (l.finalY + l.h)) {\n                    foundLayers.push(l)\n                }\n            }\n            if (undefined != l.c)\n                this.findOtherSelection(click, l.c, foundLayers)\n        }\n    }\n\n\n    mouseEnterLayerDiv(div) {\n        // get a layer under mouse \n        const a = div.parent()\n        const sv = viewer.symbolViewer\n        const pageIndex = a.attr(\"pi\")\n        const layerIndex = a.attr(\"li\")\n        const layer = sv.createdPages[pageIndex].layerArray[layerIndex]\n        if (!layer) return\n        // get a currently selected layer\n        if (!sv.selected) return\n        const slayer = sv.selected.layer\n        //\n        if (!slayer || !layer) return\n        // check if layers are in the same panel\n        if (slayer.parentPanel != layer.parentPanel) return\n        // remove previous margins\n        this.selected.marginDivs.forEach(d => d.remove())\n        this.selected.marginDivs = []\n        // show margins\n        this._drawTopVMargin(slayer.parentPanel, layer, slayer)\n        this._drawBottomVMargin(slayer.parentPanel, layer, slayer)\n        this._drawLeftHMargin(slayer.parentPanel, layer, slayer)\n        this._drawRightHMargin(slayer.parentPanel, layer, slayer)\n    }\n\n    _drawLeftHMargin(currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n        if (layer.finalX == slayer.finalX) {\n        } else if ((slayer.finalX + slayer.w) < layer.finalX) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalX + layer.w) < slayer.finalX) {\n            // layer bottom over slayer.top\n            x = layer.finalX + layer.w\n            hmargin = slayer.finalX - x\n        } else if (layer.finalX < slayer.finalX) {\n            // layer top over slayer.top\n            x = layer.finalX\n            hmargin = slayer.finalX - x\n        } else {\n            // layer top over slayer.top\n            x = slayer.finalX\n            hmargin = layer.finalX - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawRightHMargin(currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n\n        const layerRight = layer.finalX + layer.w\n        const slayerRight = slayer.finalX + slayer.w\n\n        if (layerRight == slayerRight) {\n        } else if (layerRight < slayer.finalX) {\n            // if layer bottom over slayer bottom => don't show bottom margin                \n        } else if (slayerRight < layer.finalX) {\n            // slayer bottom over layer.top\n            x = slayerRight\n            hmargin = layer.finalX - x\n        } else if (slayerRight < layerRight) {\n            // slayer bottom over layer.bottom\n            x = slayerRight\n            hmargin = layerRight - x\n        } else {\n            // slayer bottom over layer.bottom\n            x = layerRight\n            hmargin = slayerRight - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawTopVMargin(currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n        if (layer.finalY == slayer.finalY) {\n        } else if ((slayer.finalY + slayer.h) < layer.finalY) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalY + layer.h) < slayer.finalY) {\n            // layer bottom over slayer.top\n            y = layer.finalY + layer.h\n            vmargin = slayer.finalY - y\n        } else if (layer.finalY < slayer.finalY) {\n            // layer top over slayer.top\n            y = layer.finalY\n            vmargin = slayer.finalY - y\n        } else {\n            // layer top over slayer.top\n            y = slayer.finalY\n            vmargin = layer.finalY - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n    _drawBottomVMargin(currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n\n        const layerBottom = layer.finalY + layer.h\n        const slayerBottom = slayer.finalY + slayer.h\n\n        if (layerBottom == slayerBottom) {\n        } else if (layerBottom < slayer.finalY) {\n            // if layer bottom over slayer bottom => don't show bottom margin        \n        } else if (slayerBottom < layer.finalY) {\n            // slayer bottom over layer.top\n            y = slayerBottom\n            vmargin = layer.finalY - y\n        } else if (slayerBottom < layerBottom) {\n            // slayer bottom over layer.bottom\n            y = slayerBottom\n            vmargin = layerBottom - y\n        } else {\n            // slayer bottom over layer.bottom\n            y = layerBottom\n            vmargin = slayerBottom - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _findLayersCenterX(l, sl) {\n        let c = l.finalX + l.w / 2\n        let sc = sl.finalX + sl.w / 2\n        return sl.finalX > l.finalX && ((sl.finalX + sl.w) < (l.finalX + l.w)) ? sc : c\n    }\n\n    _findLayersCenterY(l, sl) {\n        let c = l.finalY + l.h / 2\n        let sc = sl.finalY + sl.h / 2\n        return sl.finalY > l.finalY && ((sl.finalY + sl.h) < (l.finalY + l.h)) ? sc : c\n    }\n\n    _drawMarginLine(currentPanel, x, y, width, height, className) {\n        var style = \"left: \" + x + \"px; top:\" + y + \"px; \"\n        style += \"width: \" + width + \"px; height:\" + height + \"px; \"\n        var div = $(\"<div>\", { class: className }).attr('style', style)\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n    _drawMarginValue(currentPanel, x, y, value) {\n        const valueHeight = 20\n        const valueWidth = 30\n        var style = \"left: \" + (x - valueWidth / 2) + \"px; top:\" + (y - valueHeight / 2) + \"px; \"\n        //style += \"width: \" + valueWidth + \"px; height:\" + valueHeight + \"px; \"\n        var div = $(\"<div>\", {\n            class: \"svMarginValueDiv\",\n        }).attr('style', style)\n        //\n        div.html(\" \" + Number.parseFloat(value).toFixed(0) + \" \")\n        //\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n\n    _decorateCSS(layer, tokens, siLayer) {\n        let css = layer.pr\n        let result = \"\"\n        let styles = {}\n\n        result += \"<hr>\" +\n            \"<div class='block'>\" +\n            \"<div class='label'>Styles\" +\n            (1 == story.fontSizeFormat ? \" (font size adjusted for Linux)\" : \"\") +\n            \"</div > \" +\n            \"<div class='value code'>\"\n\n        css.split(\"\\n\").forEach(line => {\n            if (\"\" == line) return\n            const props = line.split(\": \", 2)\n            if (!props.length) return\n            const styleName = props[0]\n            const styleValue = props[1].slice(0, -1)\n            result += \"\" + styleName + \": \"\n            result += \"<span class='tokenName'>\"\n            //\n            styles[styleName] = styleValue\n            //\n            if (layer.cv && \"color\" == styleName) {\n                // get token for color variable\n                const tokens = this._findSwatchTokens(layer.cv)\n                if (tokens) {\n                    const tokenStr = this._decorateSwatchToken(tokens, styleValue)\n                    result += tokenStr != \"\" ? tokenStr : (styleValue + \";\")\n                }\n            } else {\n                const tokenStr = tokens != null ? this._decorateStyleToken(styleName, tokens, siLayer, styleValue) : \"\"\n                result += tokenStr != \"\" ? tokenStr : (this._formatStyleValue(styleName, styleValue) + \";\")\n            }\n            //\n            result += \"</span>\"\n            result += \"<br/>\"\n        }, this);\n\n        result += \"</div></div>\"\n        return { \"css\": result, \"styles\": styles }\n    }\n\n    _decorateSwatchToken(tokens, styleValue) {\n        const tokenName = tokens[0][1]\n        //\n        return tokenName + \";</span><span class='tokenValue'>//\" + styleValue\n    }\n\n    _decorateStyleToken(style, tokens, siLayer, styleValue) {\n        // search tokan name by style name \n        const foundTokens = tokens.filter(t => t[0] == style)\n        if (!foundTokens.length) return \"\"\n        const tokenName = foundTokens[foundTokens.length - 1][1]\n        //\n        const libName = undefined != siLayer.b ? siLayer.b : story.docName\n        const finalTokenInfo = this._findTokenValueByName(tokenName, libName, styleValue)\n        //\n        if (finalTokenInfo)\n            return finalTokenInfo[0] + \";</span><span class='tokenValue'>//\" + this._formatStyleValue(style, finalTokenInfo[1])\n        else\n            return \"\"\n    }\n\n    _formatStyleValue(style = \"font-size\", styleValue = \"13px\") {\n        if (\"font-size\" == style && 1 == story.fontSizeFormat) {\n            if (styleValue in ELEMENTINSPECTOR_LINUX_FONT_SIZES) {\n                styleValue = ELEMENTINSPECTOR_LINUX_FONT_SIZES[styleValue]\n            } else {\n                styleValue = Math.round(Number(styleValue.replace(\"px\", \"\")) / 1.333) + \"px\"\n            }\n        }\n        return styleValue\n    }\n\n\n    _showTextPropery(propName, propValue, postfix = \"\") {\n        let text = propName + \": \" + propValue + postfix + \";\"\n        return text + \"<br/>\"\n    }\n\n    // result: undefined or [tokenName,tokenValue]\n    _findTokenValueByName(tokenName, libName, styleValue = null) {\n        const lib = TOKENS_DICT[libName]\n        if (undefined == lib) return undefined\n        let value = lib[tokenName]\n        if (value != undefined || null == styleValue) return [tokenName, lib[tokenName]]\n\n        ///// try to find a token with a similar name\n        // cut magic postfix to get a string for search\n        const pos = tokenName.indexOf(\"--token\")\n        if (pos < 0) return undefined\n        styleValue = styleValue.toLowerCase()\n        const newName = tokenName.slice(0, pos)\n        // filter lib tokens by name and value\n        const similarTokens = Object.keys(lib).filter(function (n) {\n            return n.startsWith(newName) && lib[n].toLowerCase() == styleValue\n        }, this)\n        if (!similarTokens.length) return undefined\n        //\n        return [\n            similarTokens[0],\n            lib[similarTokens[0]]\n        ]\n    }\n\n    _findSymbolAndLibBySymbolName(symName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(symName in lib)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                symbol: lib[symName]\n            }\n        }\n        return undefined\n    }\n\n    _findStyleAndLibByStyleName(styleName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(\"styles\" in lib) || !(styleName in lib.styles)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                style: lib.styles[styleName]\n            }\n        }\n        return undefined\n    }\n\n    // cv:{\n    //   sn: swatch name\n    //   ln:  lib name\n    // }\n    _findSwatchTokens(cv) {\n        const lib = SYMBOLS_DICT[cv.ln]\n        if (!lib) {\n            console.log(\"Can not find lib \" + cv.ln)\n            return null\n        }\n        //\n        const swatch = lib.colors__[cv.sn]\n        if (!swatch) {\n            console.log(\"Can not find color name \" + cv.sn)\n            return null\n        }\n\n        return swatch\n    }\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/VersionViewer.js",
    "content": "function getVersionInfoRequest() {\n    var resp = this\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['time']) {\n                viewer.versionViewer._loadData(data);\n                return true\n            }\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return false\n}\n\n\nclass VersionViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.screenDiffs = []\n        this.mode = 'diff'\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        // init document common data here\n        this._showLoadingMessage()\n        this._askServerTools();\n\n        this.inited = true\n    }\n\n\n    goTo(pageIndex) {\n        viewer.goToPage(pageIndex)\n    }\n\n    // delta = -1 or +1\n    switchMode(delta) {\n        const modes = ['diff', 'prev', 'new']\n        var posMode = modes.indexOf(this.mode)\n        if (undefined == posMode) return\n\n        posMode += delta\n        if (posMode < 0) posMode = modes.length - 1\n        if (posMode >= modes.length) posMode = 0\n\n        modes.forEach(function (mode, pos) {\n            var radio = $(\"#version_viewer_mode_\" + mode)\n            radio.prop('checked', pos == posMode)\n        }, this)\n\n        this.pageChanged()\n\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        this._restoreNewImages()\n        $('#version_viewer').addClass(\"hidden\")\n        if (document.location.search.includes('v')) {\n            document.location.search = \"\" // remove ?v\n        }\n        super._hideSelf()\n    }\n\n    pageChanged() {\n\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        $(\"#version_viewer_mode_diff\").prop('disabled', disabled);\n        $(\"#version_viewer_mode_new\").prop('disabled', disabled);\n        $(\"#version_viewer_mode_prev\").prop('disabled', disabled);\n        if (disabled) return\n\n        this._showCurrentPageDiffs()\n    }\n\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (38 == event.which && event.shiftKey) {   // shift + up\n            viewer.increaseVersion()\n        } else if (40 == event.which && event.shiftKey) {   // shift + down\n            viewer.decreaseVersion()\n        } else if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else if (!disabled && 37 == event.which && event.shiftKey) {   // left + shift\n            this.switchMode(-1)\n        } else if (!disabled && 39 == event.which && event.shiftKey) {   // right + shift\n            this.switchMode(1)\n        } else if (event.shiftKey) {  //shift\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    /////////////////////////////////////////////////\n\n    _restoreNewImages() {\n        story.pages.forEach(function (page) {\n            if (page.srcImageObjSrc) page.imageObj.attr(\"src\", page.srcImageObjSrc)\n        })\n\n    }\n\n    _showScreens(data, showNew) {\n        var info = \"\";\n        for (const screen of data['screens_changed']) {\n            if (screen['is_new'] != showNew) continue;\n            const pageIndex = viewer.getPageIndex(screen['screen_name'], -1)\n            const page = pageIndex >= 0 ? story.pages[pageIndex] : undefined\n\n            // We don't need to show external artboards here\n            if (page && (\"external\" == page.type)) continue\n\n            var pageName = page ? page.title : screen['screen_name'];\n\n            if (page && screen['is_diff']) {\n                this.screenDiffs[screen['screen_name']] = screen\n            }\n\n            info += \"<div class='version-screen-div' onclick='viewer.versionViewer.goTo(\" + pageIndex + \")'>\";\n            info += \"<div>\";\n            info += pageName;\n            info += \"</div><div>\";\n            info += \"<img src='\" + screen['image_url'] + \"' border='0'/>\";\n            info += \"</div>\";\n            info += \"</div>\";\n        }\n        return info;\n    }\n\n\n    _showCurrentPageDiffs() {\n        const data = this.data\n        const page = viewer.currentPage\n        if (!page || !data) return false\n\n        const screen = this.screenDiffs[page.getHash()]\n        if (!screen) return false\n\n        this.mode = $(\"#version_viewer_mode_diff\").prop('checked') ? 'diff' : ($(\"#version_viewer_mode_prev\").prop('checked') ? 'prev' : 'new')\n        var newSrc = ''\n\n        // save original image srcs\n        if (!page.srcImageObjSrc) page.srcImageObjSrc = page.imageObj.attr(\"src\")\n\n        if ('diff' == this.mode) {\n            newSrc = data['journals_path'] + '/' + data['dir'] + \"/diffs/\" + screen['screen_name'] + (story.hasRetina && viewer.isHighDensityDisplay() ? \"@2x\" : \"\") + \".\" + story.fileType\n        } else if ('new' == this.mode) {\n            if (page.imageObj.attr(\"src\") != page.srcImageObjSrc) {\n                newSrc = page.srcImageObjSrc\n            }\n        } else {\n            newSrc = \"../\" + data['down_ver'] + \"/\" + page.srcImageObjSrc\n        }\n\n\n        page.imageObj.attr(\"src\", newSrc)\n        return true\n    }\n\n    _loadData(data) {\n        var info = \"\"\n        this.data = data\n        this.screenDiffs = {}\n\n        if (data['screens_total_new']) {\n            info += \"<p class='head'>Added screens (\" + data['screens_total_new'] + \"):</p>\";\n            info += this._showScreens(data, true);\n        }\n        if (data['screens_total_changed']) {\n            info += \"<p class='head'>Changed screens (\" + data['screens_total_changed'] + \")</p>\";\n            info += this._showScreens(data, false);\n        }\n        if (!data['screens_total_new'] && !data['screens_total_changed']) {\n            info += \"No new or changed screens\"\n        }\n\n        this.pageChanged()\n\n        $(\"#version_viewer_content\").html(info)\n    }\n\n    _askServerTools() {\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"GET\", story.serverToolsPath + \"version_info.php?ver=\" + story.docVersion, true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onreadystatechange = getVersionInfoRequest;\n        xhr.send(null);\n    }\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#version_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n    }\n\n    _showLoadingMessage() {\n        $(\"#version_viewer_content\").html(\"Loading...\")\n        $('#version_viewer #empty').removeClass(\"hidden\")\n    }\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/story.js",
    "content": "var story = {\n \"docName\": \"favicon\",\n \"docPath\": \"P_P_P\",\n \"docVersion\": \"V_V_V\",\n \"authorName\": \"V_V_N\",\n \"authorEmail\": \"V_V_E\",\n \"commentsURL\": \"V_V_C\",\n \"hasRetina\": true,\n \"serverToolsPath\": \"/_tools/\",\n \"fontSizeFormat\": -1,\n \"fileType\": \"png\",\n \"disableHotspots\": false,\n \"zoomEnabled\": true,\n \"title\": \"Favicon\",\n \"layersExist\": true,\n \"centerContent\": true,\n \"highlightLinks\": false,\n \"pages\": [\n  {\n   \"id\": \"DCF3FBD9-83E1-477F-B9E6-7F9856CC9BEA\",\n   \"groupID\": \"B148EE9B-1E60-4408-BFD3-8138A60488C2\",\n   \"index\": 0,\n   \"image\": \"artboard.png\",\n   \"image2x\": \"artboard@2x.png\",\n   \"width\": 579,\n   \"height\": 301,\n   \"x\": 163,\n   \"y\": 85,\n   \"title\": \"Artboard\",\n   \"transAnimType\": 0,\n   \"layout\": {\n    \"offset\": 90,\n    \"totalWidth\": 1170,\n    \"numberOfColumns\": 12,\n    \"columnWidth\": 77.5,\n    \"gutterWidth\": 21.81818181818182\n   },\n   \"type\": \"regular\",\n   \"fixedPanels\": [],\n   \"links\": []\n  }\n ],\n \"groups\": [\n  {\n   \"id\": \"B148EE9B-1E60-4408-BFD3-8138A60488C2\",\n   \"name\": \"Page 1\"\n  }\n ],\n \"startPageIndex\": 0,\n \"totalImages\": 1\n}"
  },
  {
    "path": "docs/Favicon/viewer/viewer-page.js",
    "content": "\n///\nconst Constants = {\n    ARTBOARD_OVERLAY_PIN_HOTSPOT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE: 1,\n    //\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT: 6,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER: 7,\n    //\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_PAGE_CENTER: 6,\n}\n\nconst EVENT_HOVER = 1\nconst TRANS_ANIM_NONE = 0\nlet TRANS_ANIMATIONS = [\n    {},\n    { in_str_classes: \".transit .slideInUp\", out_str_classes: \".transit .slideOutUp\", in_token: \"transition-slidein-up\", out_token: \"transition-slideout-up\" },\n    { in_str_classes: \".transit .slideInLeft\", out_str_classes: \".transit .slideOutLeft\", in_token: \"transition-slidein-left\", out_token: \"transition-slideout-left\" },\n    { in_str_classes: \".transit .fadeIn\", out_str_classes: \".transit .fadeOut\", in_token: \"transition-fadein_str_classes:\", out_token: \"transition-fadeout\" },\n    { in_str_classes: \".transit .slideInRight\", out_str_classes: \".transit .slideOutRight\", in_token: \"transition-slidein-right\", out_token: \"transition-slideout-right\" },\n    { in_str_classes: \".transit .slideInDown\", out_str_classes: \".transit .slideOutDown\", in_token: \"transition-slidein-down\", out_token: \"transition-slideout-down\" },\n]\n\n\n\nfunction inViewport($el) {\n    var elH = $el.outerHeight(),\n        H = $(window).height(),\n        r = $el[0].getBoundingClientRect(), t = r.top, b = r.bottom;\n    return Math.max(0, t > 0 ? Math.min(elH, H - t) : Math.min(b, H));\n}\n\nfunction handleAnimationEndOnHide(el) {\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnHide)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tch\")]\n    t.out_classes.forEach(function (className) {\n        el.target.classList.remove(className)\n    })\n    el.target.classList.add(\"hidden\")\n}\n\nfunction handleAnimationEndOnShow(el) {\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnShow)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tcs\")]\n    t.in_classes.forEach(function (className) {\n        el.target.classList.remove(className)\n    })\n\n}\n\n\n\nclass ViewerPage {\n\n    constructor() {\n        this.currentOverlays = []\n        this.parentPage = undefined\n\n        this.visible = false\n        this.image = undefined\n        this.imageDiv = undefined\n        this.imageObj = undefined\n\n        this.currentLeft = undefined\n        this.currentTop = undefined\n\n        this.currentX = undefined\n        this.currentY = undefined\n\n        this.overlayByEvent = undefined\n        this.tmpSrcOverlayByEvent = undefined\n\n        this.visibleInGallery = true\n    }\n\n    showHideGalleryLinks(show = null) {\n\n        if (this.slinks) this._showHideGalleryLinkSet(this.slinks, show)\n        if (this.dlinks) this._showHideGalleryLinkSet(this.dlinks, show)\n    }\n\n    _showHideGalleryLinkSet(links, forceShow = null) {\n        links.forEach(function (link) {\n            let show = forceShow != null ? forceShow : this.visibleInGallery && link.dpage.visibleInGallery && link.spage.visibleInGallery\n            // hide link\n            const o = $(\"#gallery #grid svg #l\" + link.index)\n            if (show) o.show(); else o.hide()\n            // hide start point\n            const sp = $(\"#gallery #grid svg #s\" + link.index)\n            if (show) sp.show(); else sp.hide()\n        }, this)\n    }\n\n    getHash() {\n        var image = this.image;\n        return image.substring(0, image.length - 4); // strip .png suffix\n    }\n\n    hide(hideChilds = false, disableAnim = false) {\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal) {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tch\", this.transAnimType)\n            transInfo.out_classes.forEach(function (className) {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnHide)\n        } else {\n            this.imageDiv.addClass(\"hidden\")\n        }\n\n        if (undefined != this.parentPage) { // current page is overlay      \n\n            if (hideChilds) this.hideChildOverlays()\n\n            const parent = this.parentPage\n            viewer.refresh_url(parent)\n            // remove this from parent overlay\n            const index = parent.currentOverlays.indexOf(this)\n            parent.currentOverlays.splice(index, 1)\n            this.parentPage = undefined\n        } else {\n            this.hideCurrentOverlays()\n        }\n\n        if (undefined != this.tmpSrcOverlayByEvent) {\n            this.overlayByEvent = this.tmpSrcOverlayByEvent\n            this.tmpSrcOverlayByEvent = undefined\n        }\n        // Cleanup\n        this.visible = false\n        this.currentLink = null\n    }\n\n    hideCurrentOverlays() {\n        const overlays = this.currentOverlays.slice()\n        for (let overlay of overlays) {\n            overlay.hide()\n        }\n        return overlays.length > 0\n    }\n\n    hideChildOverlays() {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays) {\n            if (overlay.currentLink.orgPage != this) continue\n            overlay.hide()\n        }\n    }\n\n    hideOtherParentOverlays() {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays) {\n            if (overlay == this) continue\n            overlay.hide()\n        }\n    }\n\n\n    show(disableAnim = false) {\n        if (!this.imageObj) this.loadImages(true)\n\n        this.updatePosition()\n\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal) {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tcs\", this.transAnimType)\n            transInfo.in_classes.forEach(function (className, index) {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnShow)\n        } else {\n        }\n        this.imageDiv.removeClass(\"hidden\")\n        this.visible = true\n    }\n\n    updatePosition() {\n        this.currentLeft = viewer.currentMarginLeft\n        this.currentTop = viewer.currentMarginTop\n\n        if (this.isModal) {\n            var regPage = viewer.lastRegularPage\n\n            this.currentLeft += Math.round(regPage.width / 2) - Math.round(this.width / 2)\n            const visibleHeight = inViewport(regPage.imageDiv)\n            this.currentTop += Math.round(visibleHeight / 2) - Math.round(this.height / 2 * viewer.currentZoom)\n            if (this.currentTop < 0) this.currentTop = 0\n            if (this.currentLeft < 0) this.currentLeft = 0\n\n            var contentModal = $('#content-modal');\n            contentModal.css(\"margin-left\", this.currentLeft + \"px\")\n            contentModal.css(\"margin-top\", this.currentTop + \"px\")\n            if (this.height >= visibleHeight)\n                contentModal.css(\"overflow-y\", \"scroll\")\n            else\n                contentModal.css(\"overflow-y\", \"\")\n\n\n        } else if (\"overlay\" == this.type) {\n            this.currentLeft = viewer.currentPage ? viewer.currentPage.currentLeft : 0\n            this.currentTop = viewer.currentPage ? viewer.currentPage.currentTop : 0\n        }\n    }\n\n    showOverlayByLinkIndex(linkIndex) {\n        linkIndex = parseInt(linkIndex, 10)\n\n        var link = this._getLinkByIndex(linkIndex)\n        if (!link) {\n            console.log('Error: can not find link to overlay by index=\"' + linkIndex + '\"')\n            return false\n        }\n\n        // can handle only page-to-page transition\n        if ((link[\"page\"] == undefined)) return false\n\n        var destPage = story.pages[link.page]\n        // for mouseover overlay we need to show it on click, but only one time)\n        if (\"overlay\" == destPage.type && 1 == destPage.overlayByEvent) {\n            destPage.tmpSrcOverlayByEvent = destPage.overlayByEvent\n            destPage.overlayByEvent = 0\n            viewer.customEvent = {\n                x: link.rect.x,\n                y: link.rect.y,\n                pageIndex: this.index,\n                linkIndex: link.index\n            }\n            handleLinkEvent({})\n            viewer.customEvent = undefined\n        } else {\n            link.a.click()\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMove(x, y) {\n        for (let overlay of this.currentOverlays) {\n            // Commented to hide mouseover-overlay inside onclick-overlay  (ver 12.4.3)\n            //if (overlay.currentLink.orgPage != this) continue \n            overlay.onMouseMoveOverlay(x, y)\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMoveOverlay(x, y) {\n        if (this.imageDiv.hasClass(\"hidden\") || this.overlayByEvent != 1) return false\n        if (viewer.linksDisabled) return false\n\n        // handle mouse hover if this page is overlay\n        var _hideSelf = false\n        while (1 == this.overlayByEvent) {\n            var localX = Math.round(x / viewer.currentZoom) - this.currentLeft\n            var localY = Math.round(y / viewer.currentZoom) - this.currentTop\n            //alert(\" localX:\"+localX+\" localY:\"+localY+\" linkX:\"+this.currentLink.x+\" linkY:\"+this.currentLink.y);\n\n\n            if ( // check if we inside in overlay\n                localX >= this.currentX\n                && localY >= this.currentY\n                && localX < (this.currentX + this.width)\n                && localY < (this.currentY + this.height)\n            ) {\n                break\n            }\n\n            if ( // check if we out of current hotspot\n                localX < this.currentLink.x\n                || localY < this.currentLink.y\n                || localX >= (this.currentLink.x + this.currentLink.width)\n                || localY >= (this.currentLink.y + this.currentLink.height)\n            ) {\n                _hideSelf = true\n                break\n            }\n            break\n        }\n\n        // allow childs to handle mouse move\n        var visibleTotal = 0\n        var total = 0\n\n        for (let overlay of this.parentPage.currentOverlays) {\n            if (overlay.currentLink.orgPage != this) continue\n            total++\n            if (overlay.onMouseMoveOverlay(x, y)) visibleTotal++\n        }\n\n        if (_hideSelf)\n            if (!total || (total && !visibleTotal)) {\n                this.hide(false)\n                return false\n            }\n\n        return true\n    }\n\n\n    showAsOverlayInCurrentPage(orgPage, link, posX, posY, linkParentFixed, disableAnim) {\n        const newParentPage = viewer.currentPage\n\n        if (!this.imageDiv) {\n            this.loadImages(true)\n        }\n\n        // check if we need to hide any other already visible overlay\n        var positionCloned = false\n        const currentOverlays = newParentPage.currentOverlays\n\n        let overlayIndex = currentOverlays.indexOf(this.index)\n        if (overlayIndex < 0) {\n            if ('overlay' !== link.orgPage.type || this.overlayClosePrevOverlay) {\n                // if we show new overlay by clicking inside other overlay then we close the original overlay\n                if ('overlay' == orgPage.type && this.overlayClosePrevOverlay) {\n                    orgPage.hide()\n                } else {\n                    for (let overlay of currentOverlays) {\n                        if (overlay == this) continue\n                        overlay.hide()\n                    }\n                }\n            }\n        }\n\n        // Show overlay on the new position\n        const div = this.imageDiv\n\n        this.inFixedPanel = linkParentFixed && this.overlayAlsoFixed\n        if (!this.parentPage || this.parentPage.id != newParentPage.id || div.hasClass('hidden')) {\n\n            if (this.inFixedPanel) {\n                div.removeClass('divPanel')\n                div.addClass('fixedPanelFloat')\n            } else if (newParentPage.isModal) {\n                //div.removeClass('divPanel')\n                //div.removeClass('fixedPanelFloat')        \n            } else {\n                div.removeClass('fixedPanelFloat') // clear after inFixedPanel\n                div.addClass('divPanel')\n            }\n\n            // click on overlay outside of any hotspots should not close it\n            div.click(function () {\n                const index = parseInt(this.id.substring(this.id.indexOf(\"_\") + 1))\n                if (index >= 0) {\n                    const page = story.pages[index]\n                    const indexOverlay = page.parentPage.currentOverlays.indexOf(page)\n                    if (indexOverlay == 0) page.hideOtherParentOverlays()\n                }\n                return false\n            })\n\n            if (link.fixedBottomPanel) {\n                // show new overlay aligned to bottom\n                const panel = link.fixedBottomPanel;\n                const panelLink = panel.links[link.index]\n                posX = panel.x + panelLink.rect.x\n                posY = orgPage.height - panel.y - panelLink.rect.y// + panelLink.rect.height)\n\n                // check page right border\n                if ((posX + this.width) > orgPage.width) posX = orgPage.width - this.width\n\n                newParentPage.imageDiv.append(div)\n                div.css('top', \"\")\n                div.css('bottom', posY)\n                div.css('margin-left', posX + \"px\")\n            } else {\n                // \n                if (!this.overlayClosePrevOverlay && !positionCloned && undefined != this.overlayShadowX &&\n                    (\n                        (0 == this.overlayPin) // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                        && (3 == this.overlayPinHotspot) //ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                    )\n                ) {// OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_LEFT\n                    posX -= this.overlayShadowX\n                }\n\n                this.currentX = posX\n                this.currentY = posY\n\n                if (\"modal\" == orgPage.type) newParentPage.imageDiv.append(div)\n                div.css('top', posY + \"px\")\n                div.css('margin-left', posX + \"px\")\n            }\n\n            this.show(disableAnim)\n            div.css('z-index', 50 + newParentPage.currentOverlays.length)\n            newParentPage.currentOverlays.push(this)\n            this.parentPage = newParentPage\n            this.currentLink = link\n\n            // Change URL\n            if (undefined != this.overlayRedirectTargetPage) {\n                viewer.refresh_url(this)\n            } else {\n                var extURL = 'o=' + link.index\n                viewer.refresh_url(newParentPage, extURL)\n            }\n\n\n        } else if (1 == this.overlayByEvent && posX == this.currentX && posY == this.currentY) {//handle only mouse hover\n            // cursor returned back from overlay to hotspot -> nothing to do\n        } else {\n            this.hide()\n            viewer.refresh_url(newParentPage)\n        }\n    }\n\n    loadImages(force = false) {\n        /// check if already loaded images for this page\n        if (!force && this.imageObj != undefined) {\n            return pagerMarkImageAsLoaded()\n        }\n\n        const enableLinks = true\n        var isModal = this.type === \"modal\";\n\n        var content = $('#content')\n        var cssStyle = \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n        if (this.overlayShadow != undefined)\n            cssStyle += \"box-shadow:\" + this.overlayShadow + \";\"\n        if ('overlay' == this.type && this.overlayOverFixed)\n            cssStyle += \"z-index: 50;\"\n        var imageDiv = $('<div>', {\n            class: ('overlay' == this.type) ? \"divPanel\" : \"image_div\",\n            id: \"div_\" + this.index,\n            style: cssStyle\n        });\n        this.imageDiv = imageDiv\n\n\n        // create fixed panel images        \n        for (var panel of this.fixedPanels) {\n            let style = \"height: \" + panel.height + \"px; width: \" + panel.width + \"px; \"\n            if (panel.constrains.top || panel.isFixedDiv || (!panel.constrains.top && !panel.constrains.bottom)) {\n                style += \"top:\" + panel.y + \"px;\"\n            } else if (panel.constrains.bottom) {\n                style += \"bottom:\" + (this.height - panel.y - panel.height) + \"px;\"\n            }\n            if (panel.constrains.left || panel.isFixedDiv || (!panel.constrains.left && !panel.constrains.right)) {\n                style += \"margin-left:\" + panel.x + \"px;\"\n            } else if (panel.constrains.right) {\n                style += \"margin-left:\" + panel.x + \"px;\"\n            }\n            //\n\n            if (panel.shadow != undefined)\n                style += \"box-shadow:\" + panel.shadow + \";\"\n\n            // create Div for fixed panel\n            var cssClass = \"\"\n            if (panel.isFloat) {\n                cssClass = 'fixedPanelFloat'\n            } else if (panel.isFixedDiv) {\n                cssClass = 'divPanel'\n            } else if (\"top\" == panel.type) {\n                cssClass = 'fixedPanel fixedPanelTop'\n            } else if (\"left\" == panel.type) {\n                cssClass = 'fixedPanel'\n            }\n\n            var divID = panel.divID != '' ? panel.divID : (\"fixed_\" + this.index + \"_\" + panel.index)\n\n            var panelDiv = $(\"<div>\", {\n                id: divID,\n                class: cssClass,\n                style: style\n            });\n            //panelDiv.css(\"box-shadow\",panel.shadow!=undefined?panel.shadow:\"none\")     \n            panelDiv.appendTo(imageDiv);\n            panel.imageDiv = panelDiv\n\n            // create link div\n            panel.linksDiv = $(\"<div>\", {\n                class: \"linksDiv\",\n                style: \"height: \" + panel.height + \"px; width: \" + panel.width + \"px;\"\n            })\n            panel.linksDiv.appendTo(panel.imageDiv)\n            this._createLinks(panel)\n\n            // add image itself\n            panel.imageObj = this._loadSingleImage(panel.isFloat || panel.isFixedDiv ? panel : this, 'img_' + panel.index + \"_\")\n            panel.imageObj.appendTo(panelDiv);\n            if (!this.isDefault) panel.imageObj.css(\"webkit-transform\", \"translate3d(0,0,0)\")\n        }\n\n        // create main content image      \n        {\n            var isModal = this.type === \"modal\";\n            var contentModal = $('#content-modal');\n            imageDiv.appendTo(isModal ? contentModal : content);\n\n            // create link div\n            if (enableLinks) {\n                var linksDiv = $(\"<div>\", {\n                    id: \"div_links_\" + this.index,\n                    class: \"linksDiv\",\n                    style: \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n                })\n                linksDiv.appendTo(imageDiv)\n                this.linksDiv = linksDiv\n\n                this._createLinks(this)\n            }\n        }\n        var img = this._loadSingleImage(this, 'img_')\n        this.imageObj = img\n        img.appendTo(imageDiv)\n    }\n\n    showLayout() {\n        if (undefined == this.layoutCreated) {\n            this.layoutCreated = true\n            this._addLayoutLines(this.imageDiv)\n        }\n    }\n\n    _addLayoutLines(imageDiv) {\n        if (this.type != \"regular\" || undefined == this.layout) return\n\n        var x = this.layout.offset\n        var colWidth = this.layout.columnWidth\n        var colWidthInt = Math.round(this.layout.columnWidth)\n        var gutterWidth = this.layout.gutterWidth\n        for (var i = 0; i < this.layout.numberOfColumns; i++) {\n            var style = \"left: \" + Math.trunc(x) + \"px; top:\" + 0 + \"px; width: \" + colWidthInt + \"px; height:\" + this.height + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutColDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n            x += colWidth + gutterWidth\n        }\n\n        for (var y = 0; y < this.height; y += 5) {\n            var style = \"left: \" + 0 + \"px; top:\" + y + \"px; width: \" + this.width + \"px; height:\" + 1 + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutRowDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n        }\n    }\n\n\n    /*------------------------------- INTERNAL METHODS -----------------------------*/\n\n    // Try to find a first page and link which has a link to this page\n    _getSrcPageAndLink() {\n        let res = null\n        for (var page of story.pages) {\n            res = this._getLinkByTargetPage(page, page.links, this.index)\n            if (res) return res\n            for (var panel of page.fixedPanels) {\n                res = this._getLinkByTargetPage(page, panel.links, this.index)\n                if (res) return res\n            }\n        }\n        return null\n    }\n\n    _getLinkByTargetPage(page, links, targetPageIndex) {\n        const link = links.find(link => link.page == targetPageIndex)\n        if (!link) return null\n        return {\n            link: link,\n            page: page\n        }\n    }\n\n\n\n    _getLinkByIndex(index) {\n        var link = this._getLinkByIndexInLinks(index, this.links)\n        if (link != null) return link\n        for (var panel of this.fixedPanels) {\n            link = this._getLinkByIndexInLinks(index, panel.links)\n            if (link != null) return link\n        }\n        return null\n    }\n\n    _getLinkByIndexInLinks(index, links) {\n        var found = links.find(function (el) {\n            return el.index == index\n        })\n        return found != undefined ? found : null\n    }\n\n\n    _loadSingleImage(sizeSrc, idPrefix) {\n        var hasRetinaImages = story.hasRetina\n        var imageURI = hasRetinaImages && viewer.isHighDensityDisplay() ? sizeSrc.image2x : sizeSrc.image;\n        var unCachePostfix = \"V_V_V\" == story.docVersion ? \"\" : (\"?\" + story.docVersion)\n\n        var img = $('<img/>', {\n            id: idPrefix + this.index,\n            class: \"pageImage\",\n            src: encodeURIComponent(viewer.files) + '/' + encodeURIComponent(imageURI) + unCachePostfix,\n        }).attr('width', sizeSrc.width).attr('height', sizeSrc.height);\n\n        img.preload(function (perc, done) {\n            //console.log(perc, done);\n        });\n        return img;\n    }\n\n    // panel: ref to panel or this\n    _createLinks(panel) {\n        var linksDiv = panel.linksDiv\n\n        for (var link of panel.links) {\n            let x = link.rect.x + (link.isParentFixed ? panel.x : 0)\n            let y = link.rect.y + (link.isParentFixed ? panel.y : 0)\n\n            var a = $(\"<a>\", {\n                lpi: this.index,\n                li: link.index,\n                lppi: \"fixedPanels\" in panel ? -1 : panel.index,\n                lpx: x,\n                lpy: y\n            })\n\n            var eventType = 0 // click\n\n            if ('page' in link) {\n                var destPageIndex = viewer.getPageIndex(parseInt(link.page))\n                var destPage = story.pages[destPageIndex];\n                if ('overlay' == destPage.type) {\n                    eventType = destPage.overlayByEvent\n                }\n            }\n\n\n            if (EVENT_HOVER == eventType) { // for Mouse over event\n                a.mouseenter(handleLinkEvent)\n                if (\n                    0 == destPage.overlayPin // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                    && 3 == destPage.overlayPinHotspot // ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                ) {\n                } else {\n                    // need to pass click event to overlayed layers\n                    a.click(function (e) {\n                        if (undefined == e.originalEvent) return\n                        var nextObjects = document.elementsFromPoint(e.originalEvent.x, e.originalEvent.y);\n                        for (var i = 0; i < nextObjects.length; i++) {\n                            var obj = nextObjects[i].parentElement\n                            if (!obj || obj.nodeName != 'A' || obj == this) continue\n                            $(obj).trigger('click', e);\n                            return\n                        }\n                    })\n                }\n            } else { // for On click event\n                a.click(handleLinkEvent)\n            }\n\n            a.appendTo(linksDiv)\n\n            link.a = a\n\n            var style = \"left: \" + link.rect.x + \"px; top:\" + link.rect.y + \"px; width: \" + link.rect.width + \"px; height:\" + link.rect.height + \"px; \"\n            var linkDiv = $(\"<div>\", {\n                class: (EVENT_HOVER == eventType ? \"linkHoverDiv\" : \"linkDiv\") + (story.disableHotspots ? \"\" : \" linkDivHighlight\"),\n            }).attr('style', style)\n            linkDiv.appendTo(a)\n\n            link.div = linkDiv\n\n        }\n    }\n}\n\n//\n// customData:\n//  x,y,pageIndex\nfunction handleLinkEvent(event) {\n    var customData = viewer[\"customEvent\"]\n\n    if (viewer.linksDisabled) return false\n\n    let currentPage = viewer.currentPage\n    let orgPage = customData ? story.pages[customData.pageIndex] : story.pages[$(this).attr(\"lpi\")]\n\n    const linkIndex = customData ? customData.linkIndex : $(this).attr(\"li\")\n    const link = orgPage._getLinkByIndex(linkIndex)\n\n    if (link.page != undefined) {\n        var destPageIndex = parseInt(link.page)\n        var linkParentFixed = \"overlay\" != orgPage.type ? link.isParentFixed : orgPage.inFixedPanel\n\n\n        // title = story.pages[link.page].title;                   \n        var destPage = story.pages[destPageIndex]\n        if (!destPage) return\n\n\n        if (('overlay' == destPage.type || 'modal' == destPage.type) && destPage.overlayRedirectTargetPage != undefined) {\n\n            // Change base page\n            viewer.goTo(destPage.overlayRedirectTargetPage, false)\n            currentPage = viewer.currentPage\n            orgPage = viewer.currentPage\n        }\n\n        if ('overlay' == destPage.type) {\n\n            var orgLink = {\n                orgPage: orgPage,\n                index: linkIndex,\n                fixedPanelIndex: parseInt($(this).attr(\"lppi\")),\n                this: $(this),\n                x: customData ? customData.x : parseInt($(this).attr(\"lpx\")),\n                y: customData ? customData.y : parseInt($(this).attr(\"lpy\")),\n                width: link.rect.width,\n                height: link.rect.height\n            }\n\n            // check if link in fixed panel aligned to bottom\n            if (linkParentFixed && destPage.overlayAlsoFixed && orgLink.fixedPanelIndex >= 0 && currentPage.fixedPanels[orgLink.fixedPanelIndex].constrains.bottom) {\n                orgLink.fixedBottomPanel = currentPage.fixedPanels[orgLink.fixedPanelIndex]\n            } else { // clicked not from fixed panel           \n                const pinHotspot = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT == destPage.overlayPin\n                const pinPage = Constants.ARTBOARD_OVERLAY_PIN_PAGE == destPage.overlayPin\n\n                var pageX = 0\n                var pageY = 0\n\n                if (pinHotspot) {\n                    //////////////////////////////// PIN TO HOTSPOT ////////////////////////////////\n                    // clicked from some other overlay\n                    if ('overlay' == orgPage.type) {\n                        orgLink.x += orgPage.currentX\n                        orgLink.y += orgPage.currentY\n                    }\n                    pageX = orgLink.x\n                    pageY = orgLink.y\n\n                    var offsetX = pinHotspot\n                        && (\n                            Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot\n                        )\n                        ? 5 : 0\n\n                    if (destPage.overlayClosePrevOverlay && ('overlay' == orgPage.type)) {\n                        pageX = orgPage.currentX\n                        pageY = orgPage.currentY\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot) {\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width - destPage.width\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT == destPage.overlayPinHotspot) {\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        //pageY -= destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width - destPage.width\n                        //pageY = pageY - destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY -= destPage.height\n                    }\n\n                    // check page right side\n                    if (!pinHotspot || (Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT != destPage.overlayPinHotspot)) {\n                        const fullWidth = destPage.width + offsetX // + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        if ((pageX + fullWidth) > currentPage.width)\n                            pageX = currentPage.width - fullWidth\n\n                        /*if(linkPosX < (offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0))){  \n                            linkPosX = offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        }*/\n                    }\n                } else {\n                    //////////////////////////////// PIN TO PAGE ////////////////////////////////\n\n                    if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT == destPage.overlayPinPage) {\n                        pageX = 0\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT == destPage.overlayPinPage) {\n                        pageX = currentPage.width - destPage.width\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = parseInt(currentPage.height / 2) - parseInt(destPage.height / 2)\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT == destPage.overlayPinPage) {\n                        pageX = 0\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT == destPage.overlayPinPage) {\n                        pageX = currentPage.width - destPage.width\n                        pageY = currentPage.height - destPage.height\n                    }\n\n                }\n\n                if (pageX < 0) pageX = 0\n                if (pageY < 0) pageY = 0\n            }\n\n            if (destPage.visible) {\n                const sameLink = destPage.currentLink.index == orgLink.index\n                if (sameLink) {\n                    destPage.hide()\n                } else {\n                    destPage.hide(false, true) // hide without transition animation\n                    if (orgPage != destPage)\n                        destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed, true)\n                }\n                return false\n            }\n            destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed)\n            return false\n        } else {\n            // close modal if some link inside a modal opens the same modal\n            if (destPageIndex == currentPage.index && currentPage.isModal) {\n                viewer.goBack()\n                return false\n            }\n\n            // check if we need to close current overlay\n            currentPage.hideCurrentOverlays()\n\n            viewer.goTo(parseInt(destPageIndex))\n            return false\n        }\n    } else if (link.action != null && link.action == 'back') {\n        //title = \"Go Back\";\n        viewer.currentPage.hideCurrentOverlays()\n        viewer.goBack()\n        return false\n    } else if (link.url != null) {\n        //title = link.url;\n        viewer.currentPage.hideCurrentOverlays()\n        var target = link.target\n        window.open(link.url, target != undefined ? target : \"_self\")\n        return false\n        //document.location = link_url\n        //target = link.target!=null?link.target:null;\t\t\n    }\n\n    // close last current overlay if it still has parent\n    if ('overlay' == orgPage.type && undefined != orgPage.parentPage) {\n        orgPage.hide()\n    }\n\n    return false\n}\n"
  },
  {
    "path": "docs/Favicon/viewer/viewer.js",
    "content": "\n// =============================== PRELOAD IMAGES =========================\nvar pagerLoadingTotal = 0\n\nfunction getQuery(uri, q) {\n    return (uri.match(new RegExp('[?&]' + q + '=([^&]+)')) || [, null])[1];\n}\n\nfunction showError(error) {\n    alert(error)\n}\n\nfunction showMessage(message) {\n    alert(message)\n}\n\nfunction checkFolderInfoRequest(resp) {\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['project_url'] && '' != data['project_url']) return data\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return undefined\n}\n\nfunction handleDecreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    if ('' == data.link_down) return showMessage('This is the oldest version.')\n    window.open(data.link_down + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction handleIncreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    let link = data.link_up\n    if ('' == link) {\n        if (!window.confirm('This is the newest version. Go to live version?')) return\n        link = data.link_live\n    }\n    window.open(link + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction doTransNext() {\n    // get oldest transition\n    const trans = viewer.transQueue[0]\n    // if it still active then run it\n    if (trans.active) {\n        viewer.next()\n        console.log(\"RUN transition\")\n    } else {\n        console.log(\"skip transition\")\n    }\n\n    // remove this transtion from stack\n    viewer.transQueue.shift()\n}\n\n$.fn.preload = function (callback) {\n    var length = this.length;\n    var iterator = 0;\n\n    return this.each(function () {\n        var self = this;\n        var tmp = new Image();\n\n        if (callback) tmp.onload = function () {\n            callback.call(self, 100 * ++iterator / length, iterator === length);\n            pagerMarkImageAsLoaded()\n        };\n        tmp.src = this.src;\n    });\n};\n\nfunction pagerMarkImageAsLoaded() {\n    console.log(pagerLoadingTotal);\n    if (--pagerLoadingTotal == 0) {\n        $(\"#nav #loading\").addClass(\"hidden\")\n    }\n}\n\nasync function preloadAllPageImages() {\n    $(\"#nav #loading\").removeClass(\"hidden\")\n    pagerLoadingTotal = story.totalImages\n    var pages = story.pages;\n    for (var page of story.pages) {\n        if (page.imageObj == undefined) {\n            page.loadImages()\n            page.imageDiv.addClass(\"hidden\")\n        }\n    }\n}\n\nfunction reloadAllPageImages() {\n    for (var page of story.pages) {\n        page.imageObj.parent().remove();\n        page.imageObj = undefined\n        for (var p of page.fixedPanels) {\n            p.imageObj.parent().remove();\n            p.imageObj = undefined\n        }\n    }\n    preloadAllPageImages()\n}\n\nfunction doBlinkHotspots() {\n    viewer.toggleLinks()\n}\n\n\n// str: .transit .slideInDown\"\nfunction splitStylesStr(str) {\n    return str.split(\" \").map(s => s.replace(\".\", \"\"))\n}\n\n// ============================ VIEWER ====================================\n\nfunction createViewer(story, files) {\n    return {\n        highlightLinks: story.highlightLinks,\n        showLayout: false,\n        isEmbed: false,\n\n        fullBaseURL: \"\",\n        fullCurrentPageURL: \"\",\n\n        prevPage: undefined,\n        currentPage: undefined,\n        lastRegularPage: undefined,\n\n        currentMarginLeft: undefined,\n        currentMarginTop: undefined,\n\n        backStack: [],\n        urlLastIndex: -1,\n        urlLocked: false,\n        files: files,\n        userStoryPages: [],\n        zoomEnabled: story.zoomEnabled,\n\n        sidebarVisible: false,\n        child: null, // some instance of Viewer\n        allChilds: [], // list of all inited instances of Viewer\n        symbolViewer: null,\n        versionViewer: null,\n        commentsViewer: null,\n\n        defSidebarWidth: 400,\n\n        transQueue: [],\n\n        initialize: function () {\n            this.initParseGetParams()\n            this.buildUserStory();\n            this.initializeHighDensitySupport();\n            this.initAnimations()\n\n            /// Init Viewers\n            this.galleryViewer = new GalleryViewer()\n\n            if (story.layersExist) {\n                this.symbolViewer = new SymbolViewer()\n\n            }\n            // Create Version Viewer for published mockups with some version specified\n            if (story.docVersion != 'V_V_V') {\n                this.versionViewer = new VersionViewer()\n                $(\"#menu_version_viewer\").removeClass(\"hidden\");\n            }\n            if (story.commentsURL != 'V_V_C' && story.commentsURL != \"\") {\n                this.commentsViewer = new CommentsViewer()\n                $(\"#nav #pageComments\").removeClass(\"hidden\")\n            }\n\n        },\n\n        initAnimations: function () {\n            if (story.layersExist) {\n                // TODO\n            }\n            // transform \".transit .slideInDown\" strings into class name arrays\n            TRANS_ANIMATIONS.forEach(function (t, index) {\n                if (0 == index) return\n                t.in_classes = splitStylesStr(t.in_str_classes)\n                t.out_classes = splitStylesStr(t.out_str_classes)\n            }, this)\n        },\n\n        initializeLast: function () {\n\n            $(\"body\").keydown(function (event) {\n                viewer.handleKeyDown(event)\n            })\n            window.addEventListener('mousemove', function (e) {\n                viewer.onMouseMove(e.pageX, e.pageY)\n            });\n            jQuery(window).resize(function () { viewer.zoomContent() });\n\n            if (this.urlParams.get('v') != null && this.versionViewer) {\n                this.versionViewer.toggle()\n            }\n            if (this.urlParams.get('c') != null && this.commentsViewer) {\n                this.commentsViewer.toggle()\n            }\n            const gParam = this.urlParams.get('g')\n            if (gParam != null && this.galleryViewer) {\n                this.galleryViewer.handleURLParam(gParam)\n                this.galleryViewer.show()\n            }\n        },\n\n        initParseGetParams: function () {\n            const loc = document.location\n            this.fullBaseURL = loc.protocol + \"//\" + loc.hostname + loc.pathname\n            this.urlParams = new URLSearchParams(loc.search.substring(1));\n            this.urlSearch = loc.search\n\n            if (this.urlParams.get('e') != null) {\n                this.isEmbed = true\n                // hide image preload indicator\n                $('#nav loading').hide()\n                // hide Navigation                \n                $('.navCenter').hide()\n                $('.navPreviewNext').hide()\n                $('#btnMenu').hide()\n                $('#btnOpenNew').show()\n            }\n        },\n        initializeHighDensitySupport: function () {\n            if (window.matchMedia) {\n                this.hdMediaQuery = window\n                    .matchMedia(\"only screen and (min--moz-device-pixel-ratio: 1.1), only screen and (-o-min-device-pixel-ratio: 2.2/2), only screen and (-webkit-min-device-pixel-ratio: 1.1), only screen and (min-device-pixel-ratio: 1.1), only screen and (min-resolution: 1.1dppx)\");\n                var v = this;\n                this.hdMediaQuery.addListener(function (e) {\n                    v.refresh();\n                });\n            }\n        },\n        isHighDensityDisplay: function () {\n            return (this.hdMediaQuery && this.hdMediaQuery.matches || (window.devicePixelRatio && window.devicePixelRatio > 1));\n        },\n        buildUserStory: function () {\n            // \n            let opages = []\n            story.pages.forEach(function (page) {\n                opages.push($.extend(new ViewerPage(), page))\n            })\n            story.pages = opages\n            //\n            this.userStoryPages = []\n            for (var page of story.pages) {\n                if ('regular' == page.type || 'modal' == page.type) {\n                    page.userIndex = this.userStoryPages.length\n                    this.userStoryPages.push(page)\n                } else {\n                    page.userIndex = -1\n                }\n            }\n        },\n\n        handleKeyDown: function (jevent) {\n            const v = viewer\n            const event = jevent.originalEvent\n\n            const allowNavigation = !this.child || !this.child.blockMainNavigation\n            const enableTopNavigation = !this.child || this.child.enableTopNavigation\n\n            // allow all childs to handle global keys\n            if (!this.child) {\n                for (const child of this.allChilds) {\n                    if (child.handleKeyDownWhileInactive(jevent)) return true\n                }\n            }\n\n            // allow currently active childs to handle global keys\n            if (this.child && this.child.handleKeyDown(jevent)) return true\n\n            //console.log(jevent.metaKey)\n            //console.log(jevent.which)\n            if (allowNavigation && (13 == event.which || 39 == event.which)) { // enter OR right\n                v.next()\n            } else if (allowNavigation && (8 == event.which || 37 == event.which)) { // backspace OR left\n                v.previous()\n            } else if (allowNavigation && (16 == event.which)) { // shift\n                if (!jevent.metaKey) {  // no cmd to allow user to make a screenshot on macOS\n                    v.toggleLinks()\n                }\n            } else if (allowNavigation && 91 == event.which) { // cmd\n                if (this.highlightLinks) v.toggleLinks(false) // hide hightlights to allow user to make a screenshot on macOS\n            } else if (event.metaKey || event.altKey || event.ctrlKey) { // skip any modificator active to allow a browser to handle its own shortkeys\n                return false\n            } else if (allowNavigation && 90 == event.which) { // z\n                v.toggleZoom()\n            } else if (allowNavigation && 69 == event.which) { // e\n                v.share()\n            } else if (allowNavigation && 76 == event.which) { // l\n                v.toogleLayout();\n            } else if (enableTopNavigation && 83 == event.which) { // s\n                var first = v.getFirstUserPage()\n                if (first && (first.index != v.currentPage.index || this.child)) {\n                    this.hideChild()\n                    v.goToPage(first.index)\n                }\n            } else if (allowNavigation && 27 == event.which) { // esc\t\n                v.onKeyEscape()\n            } else {\n                return false\n            }\n            jevent.preventDefault()\n            return true\n        },\n\n\n        blinkHotspots: function () {\n            if (this.symbolViewer && this.symbolViewer.visible) return\n            this.toggleLinks()\n            setTimeout(doBlinkHotspots, 500)\n        },\n\n        setMouseMoveHandler: function (obj) {\n            this.mouseMoveHandler = obj\n        },\n\n        onMouseMove: function (x, y) {\n            if (this.mouseMoveHandler && this.mouseMoveHandler.onMouseMove(x, y)) return\n            if (this.currentPage) this.currentPage.onMouseMove(x, y)\n        },\n\n        onContentClick: function () {\n            // allow currently active child to handle click\n            if (this.child && this.child.onContentClick()) return true\n\n            if (this.linksDisabled) return false\n            if (this.onKeyEscape()) return\n            this.blinkHotspots()\n        },\n        onModalClick: function () {\n            this.blinkHotspots()\n        },\n\n        _setupFolderinfoRequest: function (func) {\n            var xhr = new XMLHttpRequest();\n            xhr.open(\"GET\", story.serverToolsPath + \"folder_info.php\", true);\n            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n            xhr.onreadystatechange = func;\n            xhr.send(null);\n        },\n\n        decreaseVersion: function () {\n            this._setupFolderinfoRequest(handleDecreaseVersion)\n        },\n\n        increaseVersion: function () {\n            this._setupFolderinfoRequest(handleIncreaseVersion)\n        },\n\n\n        showChild: function (child) {\n            // Hide currently visible child\n            if (this.child) {\n                this.hideChild(this.child)\n            }\n\n            // Show new child\n            this.child = child;\n            if (child.isSidebarChild) {\n                this._showSidebar()\n            }\n            child._showSelf()\n        },\n\n\n        _showSidebar: function () {\n            this.sidebarVisible = true\n            $('#sidebar').removeClass(\"hidden\")\n            viewer.zoomContent()\n        },\n\n        _hideSidebar: function () {\n            this.sidebarVisible = false\n            $('#sidebar').addClass(\"hidden\")\n            this.zoomContent()\n        },\n\n        hideChild: function () {\n            if (!this.child) return;\n            if (this.child.isSidebarChild) {\n                this._hideSidebar()\n            }\n            this.child._hideSelf();\n            this.child = null;\n\n        },\n\n        share: function () {\n            var page = undefined == this.lastRegularPage ? this.currentPage : this.lastRegularPage\n\n            let url = this.fullCurrentPageURL\n            url += '&e=1'\n\n            var iframe = '<iframe src=\"' + url + '\" style=\"border: none;\" noborder=\"0\"'\n            iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n            iframe += ' height=\"' + (story.iFrameSizeHeight ? story.iFrameSizeHeight : page.height) + '\"'\n            iframe += ' scrolling=\"auto\" seamless id=\"iFrame1\"></iframe>'\n\n            iframe += '\\n\\n'\n\n            var ihref = url.substring(0, url.lastIndexOf(\"/\"))\n\n            ihref = ihref + \"/images/\" + page['image2x']\n            iframe += \"<a target='_blank' href='\" + url + \"'>\" + \"<img border='0' \"\n            iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n            //iframe += ' height=\"'+(story.iFrameSizeHeight?story.iFrameSizeHeight:page.height) + '\"'\n            iframe += \"src='\" + ihref + \"'\"\n            iframe += \"/></a>\"\n\n            alert(iframe)\n        },\n\n        toggleZoom: function () {\n            this.zoomEnabled = !this.zoomEnabled\n            this.zoomContent()\n        },\n\n        openNewWindow: function () {\n            let url = this.fullCurrentPageURL\n            // ok, now open it in the new browse window\n            window.open(url, \"_blank\")\n        },\n\n        zoomContent: function () {\n            var page = this.lastRegularPage\n            if (undefined == page) return\n\n\n            if (undefined == this.marker) {\n                this.marker = $('#marker')\n            }\n            var marker = this.marker\n\n            var content = $('#content')\n            //var contentShadow = $('#content-shadow')\n            var contentModal = $('#content-modal')\n            var elems = [content, contentModal] //,contentShadow\n\n            var fullWidth = marker.innerWidth()\n            var availableWidth = fullWidth\n            var zoom = \"\"\n            var scale = \"\"\n\n            // check sidebar\n            var sidebarWidth = 0\n            if (this.sidebarVisible) {\n                var sidebar = $(\"#sidebar\")\n\n                sidebarWidth = this.defSidebarWidth\n\n                /* commented because it works in bad way with small artboards and large screen\n                sidebarWidth = Math.round((fullWidth - page.width) / 2)\n                if (sidebarWidth < defSidebarWidth) {\n                    sidebarWidth = defSidebarWidth\n                    availableWidth = fullWidth - sidebarWidth\n                }*/\n                if (((fullWidth - page.width) / 2) < sidebarWidth) {\n                    availableWidth = fullWidth - sidebarWidth\n                }\n\n                sidebar.css(\"margin-left\", (fullWidth - sidebarWidth) + \"px\")\n                sidebar.css(\"margin-top\", (0) + \"px\")\n                sidebar.css(\"width\", sidebarWidth + \"px\")\n                sidebar.css(\"height\", \"100%\")\n            }\n\n\n            if (this.zoomEnabled && ((availableWidth < page.width) || screen.width <= 800)) {\n                zoom = availableWidth / page.width\n                scale = \"scale(\" + zoom + \")\"\n            }\n\n            var newZoom = zoom != '' ? (zoom + 0) : 1\n\n            if (undefined == this.currentZoom || this.currentZoom != newZoom) {\n                for (var el of elems) {\n                    el.css(\"zoom\", zoom)\n                    el.css(\"-moz-transform\", scale)\n                }\n                content.css(\"-moz-transform-origin\", \"left top\")\n                contentModal.css(\"-moz-transform-origin\", \"center top\")\n\n            }\n\n            this.currentZoom = newZoom\n            this.fullWidth = fullWidth\n\n            // Calculate margins\n            this.currentMarginLeft = Math.round(availableWidth / 2) - Math.round(page.width / 2 * this.currentZoom)\n            this.currentMarginTop = 0\n\n            if (this.currentMarginLeft < 0) this.currentMarginLeft = 0\n\n            // Set content to new left positions\n            content.css(\"margin-left\", this.currentMarginLeft + \"px\")\n            content.css(\"margin-top\", this.currentMarginTop + \"px\")\n            this.currentPage.updatePosition()\n\n            // \n            if (this.child) {\n                this.child.viewerResized()\n            }\n        },\n\n        getPageHashes: function () {\n            if (this.pageHashes == null) {\n                var hashes = {};\n                for (var page of story.pages) {\n                    hashes[page.getHash()] = page.index;\n                }\n                this.pageHashes = hashes;\n            }\n            return this.pageHashes;\n        },\n        getModalFirstParentPageIndex: function (modalIndex) {\n            var foundPageIndex = null\n            // scan all regular pages\n            story.pages.filter(page => \"regular\" == page.type).some(function (page) {\n                const foundLinks = page.links.filter(link => link.page != null && link.page == modalIndex)\n                if (foundLinks.length != 0) {\n                    // return the page index which has link to modal                    \n                    foundPageIndex = page.index\n                    return true\n                }\n                // save a first regular page as a \"found\" for case if we will not \n                // find any page with a link to a specified modal\n                if (null == foundPageIndex) foundPageIndex = page.index\n                return false\n            }, this)\n\n            // ok, we found some regular page which has a link to specified modal ( or it was a fist regular page)\n            return foundPageIndex\n        },\n        getPageIndex: function (page, defIndex = 0) {\n            var index;\n\n            if (typeof page === \"number\") {\n                index = page;\n            } else if (page === \"\") {\n                index = defIndex;\n            } else {\n                index = this.getPageHashes()[page];\n                if (index == undefined) {\n                    index = defIndex;\n                }\n            }\n            return index;\n        },\n        goBack: function () {\n            if (this.backStack.length > 0) {\n                this.goTo(this.backStack[this.backStack.length - 1]);\n                this.backStack.shift();\n            } else if (this.currentPage.isModal && this.lastRegularPage) {\n                this.goTo(this.lastRegularPage.index);\n            } else {\n                window.history.back();\n            }\n        },\n        goToPage: function (page) {\n            this.clear_context();\n            this.goTo(page);\n        },\n        goTo: function (page, refreshURL = true) {\n            // We don't need any waiting page transitions anymore\n            this._resetTransQueue()\n\n            //if(this.symbolViewer) this.symbolViewer.hide()\n\n            var index = this.getPageIndex(page);\n            var currentPage = this.currentPage\n\n            if (currentPage && !currentPage.isModal) {\n                this.backStack.push(currentPage.index);\n            }\n\n            var oldcurrentPageModal = currentPage && currentPage.isModal\n\n            if (index < 0 || (currentPage && index == currentPage.index) || index >= story.pages.length) return;\n\n\n            var newPage = story.pages[index];\n            if (newPage.type === \"modal\") {\n                // hide parent page links hightlighting\n                this._updateLinksState(false, $('#content'))\n\n                // no any page visible now, need to find something\n                if (undefined == currentPage) {\n                    var parentIndex = this.getModalFirstParentPageIndex(index);\n                    this.goTo(parentIndex, false);\n                    this.zoomContent()\n                }\n\n                // redraw modal links hightlighting\n                this._updateLinksState()\n            } else {\n                if (oldcurrentPageModal) {\n                    // hide modal page links hightlighting\n                    this._updateLinksState(false, $('#content-modal'))\n                    this._updateLinksState(undefined, $('#content'))\n                }\n            }\n            this.prevPage = currentPage\n            var prevRegularPage = this.lastRegularPage\n\n            newPage.show()\n\n            this.refresh_adjust_content_layer(newPage);\n            this.refresh_hide_last_image(newPage)\n            this.refresh_switch_modal_layer(newPage);\n            if (refreshURL) {\n                this.refresh_url(newPage)\n            } else {\n                this._calcCurrentPageURL(newPage)\n            }\n            this.refresh_update_navbar(newPage);\n\n            this.currentPage = newPage;\n            if (!newPage.isModal) {\n                this.lastRegularPage = newPage\n            }\n\n            // zoom content if the new page dimensions differ from the previous\n            if (!newPage.isModal) {\n                if (!prevRegularPage || newPage.width != prevRegularPage.width || newPage.height != prevRegularPage.height) {\n                    this.zoomContent()\n                }\n            }\n\n\n            if (newPage.transNextMsecs != undefined) {\n                this._setupTransNext(newPage.transNextMsecs)\n            }\n\n            if (!newPage.disableAutoScroll) {\n                window.scrollTo(0, 0)\n            }\n\n            if (this.child) this.child.pageChanged()\n            this.allChilds.filter(c => c.alwaysHandlePageChanged).forEach(function (c) {\n                c.pageChanged()\n            })\n\n        },\n        _setupTransNext: function (msecs) {\n            // deactivate all waiting transitions\n            for (var trans of this.transQueue) {\n                trans.active = false\n            }\n            // place new active transition over the top of stack\n            this.transQueue.push({\n                page: this.currentPage,\n                active: true\n            })\n            // set timer in milisecs\n            setTimeout(doTransNext, msecs)\n        },\n        // Deactivate all waiting transitions\n        _resetTransQueue: function () {\n            for (var trans of this.transQueue) {\n                trans.active = false\n            }\n        },\n        refresh_update_navbar: function (page) {\n            var VERSION_INJECT = story.docVersion != 'V_V_V' ? (\" (v\" + story.docVersion + \")\") : \"\";\n\n            var prevPage = this.getPreviousUserPage(page)\n            var nextPage = this.getNextUserPage(page)\n\n            $('#nav .title').html((page.userIndex + 1) + '/' + this.userStoryPages.length + ' - ' + page.title + VERSION_INJECT);\n            $('#nav-left-prev').toggleClass('disabled', !prevPage)\n            $('#nav-left-next').toggleClass('disabled', !nextPage)\n\n            if (prevPage) {\n                $('#nav-left-prev a').attr('title', prevPage.title);\n            } else {\n                $('#nav-left-prev a').removeAttr('title');\n            }\n\n            if (nextPage) {\n                $('#nav-left-next a').attr('title', nextPage.title);\n            } else {\n                $('#nav-left-next a').removeAttr('title');\n            }\n\n            $('#nav-right-hints').toggleClass('disabled', page.annotations == undefined);\n\n            this.refresh_update_links_toggler(page);\n        },\n        refresh_update_links_toggler: function (page) {\n            $('#nav-right-links').toggleClass('active', this.highlightLinks);\n            $('#nav-right-links').toggleClass('disabled', page.links.length == 0);\n        },\n        refresh_hide_last_image: function (page) {\n            var content = $('#content');\n            var contentModal = $('#content-modal');\n            var isModal = page.isModal\n\n            // hide last regular page to show a new regular after modal\n            if (!isModal && this.lastRegularPage && this.lastRegularPage.index != page.index) {\n                var lastPageImg = $('#img_' + this.lastRegularPage.index);\n                if (lastPageImg.length) {\n                    this.lastRegularPage.hide()\n                }\n            }\n\n            // hide last modal \n            var prevPageWasModal = this.prevPage != null && this.prevPage.type === \"modal\"\n            if (prevPageWasModal) {\n                var prevImg = $('#img_' + this.prevPage.index);\n                if (prevImg.length) {\n                    this.prevPage.hide()\n                    //pagerHideImg(prevImg)\n                }\n            }\n\n        },\n        refresh_adjust_content_layer: function (page) {\n            if (page.isModal) return;\n\n            var contentShadow = $('#content-shadow');\n            var contentModal = $('#content-modal');\n            var content = $('#content');\n\n            var prevPageWasModal = this.prevPage && this.prevPage.isModal\n            if (prevPageWasModal) {\n                contentShadow.addClass('hidden');\n                contentModal.addClass('hidden');\n            }\n\n        },\n\n        refresh_switch_modal_layer: function (page) {\n            if (!page.isModal) return;\n\n            var showShadow = page.showShadow == 1;\n            var contentModal = $('#content-modal');\n            var contentShadow = $('#content-shadow');\n\n            if (showShadow) {\n                contentShadow.removeClass('no-shadow');\n                contentShadow.addClass('shadow');\n                contentShadow.removeClass('hidden');\n            } else {\n                contentModal.addClass('hidden');\n            }\n            contentModal.removeClass('hidden');\n        },\n\n\n        _getSearchPath(page = null, extURL = null) {\n            if (!page) page = this.currentPage\n            let search = '?' + encodeURIComponent(page.getHash())\n            if (extURL != null && extURL != \"\") search += \"&\" + extURL\n            return search\n        },\n\n        _calcCurrentPageURL: function (page = null, extURL = null) {\n            if (!page) page = this.currentPage\n            this.urlLastIndex = page.index\n            $(document).attr('title', story.title + ': ' + page.title)\n\n            let newPath = this.fullBaseURL + this._getSearchPath(page, extURL)\n            this.fullCurrentPageURL = newPath\n        },\n\n        refresh_url: function (page, extURL = \"\", pushHistory = true) {\n            if (this.urlLocked) return\n\n            this._calcCurrentPageURL(page, extURL)\n            let newPath = this.fullCurrentPageURL\n            this.fullCurrentPageURL = newPath\n\n            if (this.isEmbed) {\n                newPath += \"&e=1\"\n            }\n            if (this.galleryViewer && this.galleryViewer.isVisible()) {\n                newPath += \"&g=\" + (this.galleryViewer.isMapMode ? \"m\" : \"g\")\n            }\n            if (this.commentsViewer && this.commentsViewer.isVisible()) {\n                newPath += \"&c=1\"\n            }\n\n            if (pushHistory) {\n                window.history.pushState(newPath, page.title, newPath);\n            } else {\n                window.history.replaceState({}, page.title, newPath);\n            }\n        },\n\n        /*\n        _parseLocationHash: function () {\n            var result = {\n                reset_url: false,\n                overlayLinkIndex: undefined,\n                redirectOverlayLinkIndex: undefined,\n            }\n            var hash = location.hash;\n     \n            if (hash == null || hash.length == 0) {\n                hash = '#'\n                result.reset_url = true\n     \n            } else if (hash.indexOf('/') > 0) {\n                // read additonal parameters\n                var args = hash.split('/')\n                // check for link to click\n                if (args[1] == 'o') {\n                    result.overlayLinkIndex = args[2]\n                }\n                hash = hash.substring(0, hash.indexOf('/'))\n                hash = '#' + hash.replace(/^[^#]*#?(.*)$/, '$1');\n            }\n     \n            result.page_name = hash\n            return result\n        },*/\n\n        _parseLocationSearch: function () {\n            //if (document.location.hash != null && document.location.hash != \"\")\n            //  return this._parseLocationHash()\n\n            var result = {\n                page_name: \"\",\n                reset_url: false,\n                overlayLinkIndex: undefined,\n                redirectOverlayLinkIndex: undefined,\n            }\n            this.urlParams.forEach(function (value, key) {\n                if (\"\" == value) result.page_name = key\n            }, this);\n\n            if (null == result.page_name || \"\" == result.page_name || this.urlParams.get(result.page_name) != \"\") {\n                result.page_name = \"\"\n                result.reset_url = true\n            } else {\n                result.overlayLinkIndex = this.urlParams.get(\"o\")\n            }\n            return result\n        },\n\n        handleNewLocation: function (initial) {\n            var locInfo = this._parseLocationSearch()\n            var pageIndex = locInfo.page_name != null ? this.getPageIndex(locInfo.page_name, null) : null\n            if (null == pageIndex) {\n                // get the default page\n                pageIndex = story.startPageIndex\n                locInfo.reset_url = true\n            }\n\n            if (!initial && this.urlLastIndex == pageIndex) {\n                return\n            }\n\n            var page = story.pages[pageIndex];\n\n            // check if this redirect overlay\n            let overlayRedirectInfo = null\n            if (undefined != page.overlayRedirectTargetPage) {\n                overlayRedirectInfo = page._getSrcPageAndLink()\n                if (overlayRedirectInfo) {\n                    pageIndex = overlayRedirectInfo.page.index\n                    page = overlayRedirectInfo.page\n                }\n\n            }\n\n            if (initial)\n                page.isDefault = true\n            else\n                this.clear_context();\n\n            // check if this page overlay\n            // check if this redirect overlay\n            this.goTo(pageIndex, locInfo.reset_url);\n\n            if (locInfo.overlayLinkIndex != null) {\n                page.showOverlayByLinkIndex(locInfo.overlayLinkIndex)\n            }\n\n            if (!initial) this.urlLastIndex = pageIndex\n\n            // Open redirect overlay over the overlay source page\n            if (overlayRedirectInfo) {\n                overlayRedirectInfo.link.a.click()\n            }\n        },\n\n        clear_context_hide_all_images: function () {\n            var page = this.currentPage;\n            var content = $('#content');\n            var contentModal = $('#content-modal');\n            var contentShadow = $('#content-shadow');\n            var isModal = page && page.type === \"modal\";\n\n            contentShadow.addClass('hidden');\n            contentModal.addClass('hidden');\n\n            // hide last regular page\n            if (this.lastRegularPage) {\n                var lastPageImg = $('#img_' + this.lastRegularPage.index);\n                if (lastPageImg.length) {\n                    this.lastRegularPage.hide()\n                }\n            }\n\n            // hide current modal \n            if (isModal) {\n                var modalImg = $('#img_' + this.currentPage.index);\n                if (modalImg.length) {\n                    this.currentPage.hide()\n                }\n            }\n\n        },\n\n        clear_context: function () {\n            this.clear_context_hide_all_images()\n\n            this.prevPage = undefined\n            this.currentPage = undefined\n            this.lastRegularPage = undefined\n\n            this.backStack = []\n        },\n\n        refresh: function () {\n            reloadAllPageImages()\n            this.currentPage.show()\n        },\n        onKeyEscape: function () {\n            // If the current page has some overlay open then close it\n            const page = this.currentPage\n            if (page.hideCurrentOverlays()) {\n                return true\n            }\n            // If the current page is modal then close it and go to the last non-modal page\n            if (this.currentPage.isModal) {\n                viewer.goBack()\n                return true\n            }\n            return false\n        },\n        next: function () {\n            var page = this.getNextUserPage(this.currentPage)\n            if (!page) return\n            this.goToPage(page.index);\n        },\n        previous: function () {\n            var page = this.getPreviousUserPage(this.currentPage)\n            if (!page) return\n            this.goToPage(page.index);\n        },\n        getFirstUserPage: function () {\n            var first = this.userStoryPages[0]\n            return first ? first : null\n        },\n        getNextUserPage: function (page) {\n            var nextUserIndex = page ? page.userIndex + 1 : 0\n            if (nextUserIndex >= this.userStoryPages.length) return null\n            return this.userStoryPages[nextUserIndex]\n        },\n        getPreviousUserPage: function (page) {\n            var prevUserIndex = page ? page.userIndex - 1 : -1\n            if (prevUserIndex < 0) return null\n            return this.userStoryPages[prevUserIndex]\n        },\n        toggleLinks: function (newState = undefined) {\n            this.highlightLinks = newState != undefined ? newState : !this.highlightLinks\n            this.refresh_update_links_toggler(this.currentPage)\n            this._updateLinksState()\n        },\n\n        toogleLayout: function (newState = undefined) {\n            this.showLayout = newState != undefined ? newState : !this.showLayout\n            div = $('#content')\n\n            if (this.showLayout) {\n                this.currentPage.showLayout()\n                div.addClass(\"contentLayoutVisible\")\n            } else\n                div.removeClass(\"contentLayoutVisible\")\n        },\n\n\n\n        _updateLinksState: function (showLinks = undefined, div = undefined) {\n            if (undefined == div) {\n                if (this.currentPage.isModal) {\n                    div = $('#content-modal')\n                } else {\n                    div = $('#content')\n                }\n            }\n            if (undefined == showLinks) showLinks = this.highlightLinks\n            if (showLinks)\n                div.addClass(\"contentLinksVisible\")\n            else\n                div.removeClass(\"contentLinksVisible\")\n        },\n\n        showHints: function () {\n            var text = this.currentPage.annotations;\n            if (text == undefined) return;\n            alert(text);\n        },\n        hideNavbar: function () {\n            $('#nav').slideToggle('fast', function () {\n                $('#nav-hide').slideToggle('fast').removeClass('hidden');\n            });\n        },\n        showNavbar: function () {\n            $('#nav-hide').slideToggle('fast', function () {\n                $('#nav').slideToggle('fast');\n            }).addClass('hidden');\n        },\n\n\n        handleStateChanges: function (e) {\n            viewer.urlLocked = true\n            viewer.currentPage.hide(true, true)\n            viewer.currentPage = null\n\n            viewer.initParseGetParams()\n            viewer.handleNewLocation(true)\n            viewer.urlLocked = false\n        },\n    };\n}\n\n// ADD | REMOVE CLASS\n// mode ID - getELementByID\n// mode CLASS - getELementByClassName\n\nfunction addRemoveClass(mode, el, cls) {\n\n    var el;\n\n    switch (mode) {\n        case 'class':\n            el = document.getElementsByClassName(el)[0];\n            break;\n\n        case 'id':\n            el = document.getElementById(el);\n            break;\n    }\n\n    if (el.classList.contains(cls)) {\n        el.classList.remove(cls)\n    } else {\n        el.classList.add(cls);\n    }\n}\n\n// Redirect from legacy format URL\n//    https://site.com/dd/index.html#home/o/10?shared  \n// to the new\n//    https://site.com/dd/index.html?home&o=10&shared=true\n\nfunction redirectFromHashToSearch() {\n    const loc = document.location\n    if (loc.hash == null || loc.hash.length == \"\") return false\n\n    let url = loc.protocol + \"//\" + loc.host + loc.pathname\n\n    if (loc.hash.indexOf('/') > 0) {\n        let hash = loc.hash\n        // read additonal parameters\n        var args = hash.split('/')\n        // check for link to click\n        let search = hash.substring(0, hash.indexOf('/'))\n        search = '?' + search.replace(/^[^#]*#?(.*)$/, '$1');\n        if (args[1] == 'o') {\n            search += \"&o=\" + args[2]\n        }\n        url += search\n    } else {\n        url += \"?\" + loc.hash.substring(1)\n    }\n    if (null != loc.search && \"\" != loc.search) {\n        let search = loc.search.substring(1)\n        if (\"embed\" == search) {\n            url += \"&e=1\"\n        }\n    }\n    //\n    document.location = url\n    return true\n}\nfunction handleStateChanges(e) {\n    viewer.handleStateChanges(e)\n}\n\n$(document).ready(function () {\n    if (redirectFromHashToSearch()) return\n\n    viewer.initialize();\n    if (!!('ontouchstart' in window) || !!('onmsgesturechange' in window)) {\n        $('body').removeClass('screen');\n    }\n\n    viewer.handleNewLocation(true)\n    if (!viewer.isEmbed) preloadAllPageImages();\n\n    window.addEventListener('popstate', handleStateChanges);\n    $(window).hashchange(handleStateChanges);\n\n    viewer.zoomContent()\n    viewer.initializeLast()\n});\n"
  },
  {
    "path": "docs/FixedLayers/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta name=\"generator\" content=\"Generated using Puzzle Publisher 16.1.2 plugin for Sketch.app - https://github.com/ingrammicro/puzzle-publisher\">\n<title>FixedLayers</title>\n<link rel=\"shortcut icon\"  type=\"image/png?\" href=\"resources/icon.png?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer-center.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/cb-modern-ui.css?V_V_V\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/ux1-ui.css?V_V_V\">\n<script type=\"text/javascript\" src=\"resources/jquery-3.3.1.min.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"resources/jquery.hotkeys.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"resources/jquery.ba-hashchange.min.js\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/viewer-page.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/story.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/viewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/AbstractViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/CommentsViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/GalleryViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/LayersData.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\" src=\"viewer/SymbolViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"resources/viewer-fonts.css?V_V_V\">\n<script type=\"text/javascript\" src=\"viewer/VersionViewer.js?V_V_V\" charset=\"UTF-8\"></script>\n<script type=\"text/javascript\">\n  var viewer = createViewer(story, \"images\");\n</script>\n\n<!-- Google Tag Manager -->\n<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\nnew Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\nj=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n})(window,document,'script','dataLayer','GTM-KGWHQP6');</script>\n<!-- End Google Tag Manager -->\n<script>\n    function copyToBuffer(elID) {\n        var copyText = document.getElementById(elID);\n\n        var $temp = $(\"<input>\");\n        $(\"body\").append($temp);\n        $temp.val($(copyText).text()).select();\n        document.execCommand(\"copy\");\n        $temp.remove();\n    }\n    function showFAIconInfo(code){\n        window.open(\"https://fontawesome.com/icons?d=gallery&q=\"+code,\"_blank\")\n    }\n</script > <!--HEAD_INJECT-->\n</head>\n<body class=\"screen\" style=\"background:#646464\">\n\n            <!-- Google Tag Manager (noscript) -->\n            <noscript><iframe src=\"https://www.googletagmanager.com/ns.html?id=GTM-KGWHQP6\"\n            height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe></noscript>\n            <!-- End Google Tag Manager (noscript) --><div class=\"containerSVG\"> <svg class=\"svgIcon\">     <symbol id=\"icMenu\" viewBox=\"0 0 24 24\">         <path d=\"M4,14 C2.8954305,14 2,13.1045695 2,12 C2,10.8954305 2.8954305,10 4,10 C5.1045695,10 6,10.8954305 6,12 C6,13.1045695 5.1045695,14 4,14 Z M12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 Z M20,14 C18.8954305,14 18,13.1045695 18,12 C18,10.8954305 18.8954305,10 20,10 C21.1045695,10 22,10.8954305 22,12 C22,13.1045695 21.1045695,14 20,14 Z\" />    </symbol>     <symbol id=\"icArrwLeft\" viewBox=\"0 0 24 24\">         <path d=\"M14.7071068,16.2928932 C15.0976311,16.6834175 15.0976311,17.3165825 14.7071068,17.7071068 C14.3165825,18.0976311 13.6834175,18.0976311 13.2928932,17.7071068 L8.29289322,12.7071068 C7.90236893,12.3165825 7.90236893,11.6834175 8.29289322,11.2928932 L13.2928932,6.29289322 C13.6834175,5.90236893 14.3165825,5.90236893 14.7071068,6.29289322 C15.0976311,6.68341751 15.0976311,7.31658249 14.7071068,7.70710678 L10.4142136,12 L14.7071068,16.2928932 Z\" />    </symbol>     <symbol id=\"icArrwRight\" viewBox=\"0 0 24 24\">         <path d=\"M15.7071068,16.2928932 C16.0976311,16.6834175 16.0976311,17.3165825 15.7071068,17.7071068 C15.3165825,18.0976311 14.6834175,18.0976311 14.2928932,17.7071068 L9.29289322,12.7071068 C8.90236893,12.3165825 8.90236893,11.6834175 9.29289322,11.2928932 L14.2928932,6.29289322 C14.6834175,5.90236893 15.3165825,5.90236893 15.7071068,6.29289322 C16.0976311,6.68341751 16.0976311,7.31658249 15.7071068,7.70710678 L11.4142136,12 L15.7071068,16.2928932 Z\" transform=\"matrix(-1 0 0 1 25 0)\" />    </symbol>     <symbol id=\"icHeart\" viewBox=\"0 0 24 24\">        <path fill=\"none\" stroke=\"#404B58\" stroke-width=\"2\" d=\"M12,18.8536369 C17.3943819,16.1015046 20,12.9784118 20,9.5 C20,7.01471863 17.9852814,5 15.5,5 C14.4391705,5 13.4374107,5.36699819 12.6367778,6.02820949 L12,6.55409926 L11.3632222,6.02820949 C10.5625893,5.36699819 9.56082953,5 8.5,5 C6.01471863,5 4,7.01471863 4,9.5 C4,12.9784118 6.60561807,16.1015046 12,18.8536369 Z\"/>    </symbol>     <symbol id=\"icPointer\" viewBox=\"0 0 24 24\">        <path d=\"M7.16743376,4.34579076 C7.66363057,3.87908025 8.39151976,3.755899 9.01365224,4.03335379 L9.01365224,4.03335379 L9.10700534,4.08777143 C9.63104823,4.47472439 10.0217699,5.01508408 10.2127512,5.60062907 C10.4917675,6.2824399 10.6779761,6.99862675 10.7637203,7.71100475 L10.7637203,7.71100475 L10.817,8.033 L10.870648,7.99008307 C10.9508584,7.93108653 11.0375587,7.87866583 11.130137,7.83371233 L11.130137,7.83371233 L11.2733366,7.7719939 C11.645598,7.65777539 12.0393934,7.63209308 12.4423174,7.7005808 C12.8744158,7.79091478 13.2571639,8.03945252 13.515467,8.3974312 L13.515467,8.3974312 L13.525,8.415 L13.5580532,8.38060854 C13.7375777,8.20417748 13.9573664,8.06689595 14.2126677,7.97661745 L14.2126677,7.97661745 L14.3700719,7.92815354 C14.7601243,7.85683728 15.1598757,7.85683728 15.6185326,7.94579813 C15.9857599,8.06856757 16.3070009,8.30005011 16.5396674,8.609557 L16.5396674,8.609557 L16.6043493,8.72458234 C16.6428414,8.82097976 16.6788016,8.91827539 16.7122125,9.01653061 L16.7122125,9.01653061 L16.726,9.06 L16.8208864,8.97721759 C16.9681747,8.85926223 17.1379522,8.76879133 17.3224355,8.71266304 L17.3224355,8.71266304 L17.4634337,8.67711323 C17.9896455,8.5711603 18.5324104,8.75398638 18.8872742,9.15672267 C19.242138,9.55945895 19.3551885,10.1209202 19.1838405,10.6296094 L19.2093224,10.5357799 L19.2090334,12.7610745 C19.1862391,13.1271355 19.1470105,13.4918634 19.0880685,13.8664949 L19.0224339,14.2448438 L18.9429284,14.6322922 C18.7541803,15.1823144 18.4914657,15.7040948 18.1217893,16.2343243 C17.6699093,16.7368429 17.2965379,17.3047439 17.0143136,17.9188042 L17.0363224,17.8727799 L17.0047955,18.0384429 C16.9929046,18.1111666 16.9833046,18.1844595 16.9760091,18.2584435 L16.9610493,18.4825906 L16.9599974,18.7116152 C16.9591372,18.9778721 16.9937268,19.2430586 17.0628545,19.5001867 C17.1426598,19.7970308 16.9382454,20.0949145 16.6325641,20.1272293 C16.1887579,20.1741459 15.7412421,20.1741459 15.2739714,20.1241858 C14.8192803,20.0542334 14.4188874,19.6281882 14.0808095,19.0818271 L14.0808095,19.0818271 L14.007,18.959 C13.6458971,19.5469161 13.2283691,20.0164429 12.7971825,20.133474 L12.7971825,20.133474 L12.6792804,20.1564734 C12.40804,20.1888603 12.1178623,20.1996559 11.5976025,20.1951344 L11.5976025,20.1951344 L10.2584286,20.169736 L9.48,20.16 C9.16840558,20.16 8.93270845,19.8780895 8.98790834,19.5714235 L8.98790834,19.5714235 L9.0027436,19.4616806 C9.02119919,19.2959265 9.02585489,19.1230993 9.00917907,18.9708257 C8.99125835,18.8071844 8.95160807,18.7033862 8.92009941,18.6757201 L8.92009941,18.6757201 L8.68126124,18.4610804 L7.73371165,17.5692523 L6.87532389,16.5869703 C6.7408966,16.4141352 6.12289613,15.3607699 5.76460288,14.7906072 L5.76460288,14.7906072 L5.62038404,14.5667312 C5.59696768,14.5332792 5.57867016,14.5070634 5.55981671,14.4808928 L5.55981671,14.4808928 L4.6386181,13.3268316 C4.38383819,13.0013876 4.22702059,12.7785585 4.13684781,12.6063498 L4.13684781,12.6063498 L4.05648317,12.4645383 C3.86064293,12.0784209 3.81297141,11.6326253 3.92414472,11.2219126 C4.14055922,10.3315035 4.98520633,9.74084121 5.93847381,9.84979299 C6.54529028,9.97169156 7.1029491,10.2691096 7.53281665,10.6960265 L7.53281665,10.6960265 L7.723,10.883 L7.6912417,10.723928 L7.64251772,10.5011802 C7.57779705,10.2174049 7.53759574,10.0512485 7.49682897,9.89744992 L7.49682897,9.89744992 L7.32963781,9.29184575 L7.04456938,8.13982119 C6.92154004,7.6413934 6.82477772,7.13684703 6.75976794,6.65834902 C6.61137215,5.91865308 6.7193326,5.15047299 7.06586723,4.48033484 L7.06586723,4.48033484 Z M8.50934031,4.91178804 C8.27952269,4.84813061 8.02983182,4.9074776 7.85256624,5.07420924 L7.91432244,5.02377994 L7.87117294,5.11837118 C7.74604513,5.42118721 7.68689523,5.74791709 7.69799852,6.08431903 L7.71312508,6.28720691 L7.81882079,6.96398474 C7.87322009,7.27747387 7.93828923,7.58776101 8.01328078,7.89178265 L8.01328078,7.89178265 L8.29601637,9.03471155 L8.44911047,9.58764732 L8.58001699,10.1158847 L8.67625156,10.548595 C8.75149962,10.9085229 8.81587907,11.2760953 8.87882633,11.6904775 L8.87882633,11.6904775 L8.97335261,12.3520537 C9.01060121,12.4927706 9.0255715,12.5703226 9.01950838,12.6680061 C9.00562698,12.8916511 9.00162999,12.9288159 8.65656558,13.0620472 L8.65656558,13.0620472 L8.43241435,13.1459443 C8.23545672,13.065262 8.18621731,13.0450914 8.15393215,13.0140564 L8.15393215,13.0140564 L8.08883861,12.9370646 L7.80233685,12.5590928 L7.5578657,12.2238297 L7.29668723,11.8970782 C7.15496439,11.7304554 7.00269692,11.5703689 6.83776679,11.4148687 C6.53749664,11.1168285 6.15631213,10.9135301 5.78418862,10.8368753 C5.37391601,10.7907916 4.99336072,11.0569141 4.892676,11.4704756 C4.83729564,11.6753474 4.87010001,11.8940432 5.00167451,12.1056421 L5.00167451,12.1056421 L5.03690334,12.1679323 C5.11596703,12.3004962 5.24732182,12.482014 5.44275702,12.7314353 L5.44275702,12.7314353 L5.92017745,13.3249945 L6.29953254,13.8026386 L6.44532166,14.0015976 C6.74304028,14.4457352 7.37243772,15.5180187 7.58358848,15.8552149 L7.58358848,15.8552149 L7.64628835,15.9507477 L8.44514313,16.8689487 L9.35653146,17.7235091 L9.57990059,17.9242799 C9.83717242,18.1501771 9.96070027,18.4735533 10.003236,18.8619643 C10.0118351,18.9404865 10.0168432,19.0197358 10.0187392,19.0991214 L10.0187392,19.0991214 L10.017,19.165 L11.2459156,19.1902367 C11.83561,19.2002217 12.1681116,19.1973958 12.4192519,19.177468 L12.4192519,19.177468 L12.5615794,19.1634247 C12.6170658,19.1568969 13.0000528,18.7092498 13.2226316,18.3267031 L13.2226316,18.3267031 L13.2778839,18.2314035 C13.4396915,17.9886975 13.7135208,17.8397056 14.01,17.8397056 C14.3435391,17.8397056 14.648412,18.0282734 14.789646,18.3118584 L14.789646,18.3118584 L14.8465822,18.411971 C15.047575,18.7505088 15.3490101,19.1240166 15.4025641,19.1327707 C15.5895219,19.1525347 15.7772609,19.1624167 15.965,19.1624167 L15.965,19.1624167 L15.985,19.161 L15.9680577,18.9804543 L15.9601727,18.7231423 C15.9502275,18.3449039 15.9880257,17.9669214 16.0726727,17.5981423 L16.0726727,17.5981423 L16.1056864,17.5011958 C16.429971,16.7956214 16.8589864,16.1430854 17.3379853,15.6167264 C17.61604,15.2123026 17.8377695,14.7719231 17.981188,14.3648199 C18.099272,13.8160386 18.1760798,13.2591823 18.21,12.73 L18.21,12.73 L18.21,10.47 L18.2361595,10.3103906 C18.2934615,10.1402752 18.2556553,9.95251178 18.136982,9.81782907 C18.0183088,9.68314635 17.8367978,9.62200582 17.6608226,9.6574385 C17.4848473,9.69287119 17.3411425,9.81949403 17.2838405,9.98960943 L17.2838405,9.98960943 L17.2333183,10.0960858 C17.2060315,10.1394966 17.1784846,10.1946376 17.1518918,10.2591212 C17.1098746,10.3610069 17.0769839,10.4664928 17.0409555,10.6029732 L17.0409555,10.6029732 L16.996796,10.7581274 L16.9595346,10.8379862 C16.9006729,10.9958359 16.8849765,11.0379292 16.5932412,10.9992882 L16.5932412,10.9992882 L16.1501329,10.9267917 C16.0348274,10.6676813 16.009204,10.6101012 16.0212304,10.6050553 C15.9848193,10.0869965 15.8682391,9.57772504 15.6756507,9.09541766 L15.7,9.162 L15.6694688,9.12668516 C15.5949633,9.04790444 15.511666,8.98482947 15.4387784,8.94442971 L15.4387784,8.94442971 L15.3700719,8.91184646 C15.098938,8.86227297 14.821062,8.86227297 14.5895196,8.90293341 C14.4230985,8.94756646 14.2774688,9.04887406 14.1777454,9.18938502 L14.2223224,9.13377994 L14.1675121,9.33868789 C14.1182734,9.53586776 14.0787072,9.73525859 14.0489695,9.93588797 L14.011754,10.2376394 L13.9893984,10.5405193 C13.9584597,11.1706655 13.0294935,11.1762842 12.9909347,10.5465584 L12.9909347,10.5465584 L12.9730784,10.2516986 C12.930789,9.77937294 12.8204225,9.3155109 12.6453027,8.87454375 L12.669,8.94 L12.6310282,8.89504189 C12.5518534,8.81362497 12.456908,8.75113321 12.3573482,8.71292346 L12.3573482,8.71292346 L12.2566634,8.6830061 C12.0262976,8.64406561 11.7900203,8.659475 11.5981215,8.71719666 C11.4132489,8.78768046 11.2781507,8.94899175 11.2402903,9.14805807 C11.2189407,9.25480644 11.2137839,9.47449295 11.224132,9.79681583 L11.224132,9.79681583 L11.25,10.55 C11.25,11.2056512 10.2721205,11.2219446 10.2502775,10.5666574 C10.2493885,10.539987 10.2483981,10.5176836 10.244244,10.482217 L10.244244,10.482217 L9.90052097,8.58822404 L9.77361364,7.85000483 C9.69594584,7.20750256 9.53236284,6.57833719 9.27496435,5.94601645 L9.27496435,5.94601645 L9.20787946,5.76867193 C9.05849627,5.42113224 8.81912344,5.11827389 8.51299466,4.89222857 L8.51732244,4.89577994 L8.557,4.928 Z M15.375,13 C15.5562184,13 15.707414,13.1282379 15.7423813,13.2987132 L15.75,13.3741092 L15.75,16.8258906 C15.75,17.0325054 15.5821068,17.1999998 15.375,17.1999998 C15.1937816,17.1999998 15.042586,17.0717619 15.0076187,16.9012866 L15,16.8258906 L15,13.3741092 C15,13.1674944 15.1678932,13 15.375,13 Z M13.3728388,13 C13.5540542,12.998966 13.7059881,13.1260313 13.7419398,13.2958993 L13.7499939,13.3710717 L13.7699939,16.8246259 C13.7711876,17.0307477 13.6042648,17.1988055 13.3971615,17.1999998 C13.2159461,17.2010338 13.0640121,17.0739685 13.0280605,16.9041005 L13.0200064,16.8289281 L13,13.3753739 C12.9988127,13.1692522 13.1657354,13.0011944 13.3728388,13 Z M11.3728136,13 C11.5540289,12.998944 11.7059714,13.127215 11.7419346,13.2987061 L11.7499938,13.3745973 L11.7699938,16.8210084 C11.7712014,17.0291026 11.6042899,17.1987799 11.3971867,17.1999998 C11.2159713,17.2010558 11.0640288,17.0727848 11.0280657,16.9012937 L11.0200065,16.8254025 L11,13.3789914 C10.9987989,13.1708972 11.1657103,13.0012199 11.3728136,13 Z\" />    </symbol>    <symbol id=\"icAnnotation\" viewBox=\"0 0 24 24\">        <path d=\"M5,16 L13,16 C13.5522847,16 14,16.4477153 14,17 C14,17.5522847 13.5522847,18 13,18 L5,18 C4.44771525,18 4,17.5522847 4,17 C4,16.4477153 4.44771525,16 5,16 Z M5,6 L19,6 C19.5522847,6 20,6.44771525 20,7 C20,7.55228475 19.5522847,8 19,8 L5,8 C4.44771525,8 4,7.55228475 4,7 C4,6.44771525 4.44771525,6 5,6 Z M5,11 L19,11 C19.5522847,11 20,11.4477153 20,12 C20,12.5522847 19.5522847,13 19,13 L5,13 C4.44771525,13 4,12.5522847 4,12 C4,11.4477153 4.44771525,11 5,11 Z\" />    </symbol>    <symbol id=\"icEmbed\" viewBox=\"0 0 24 24\">        <path d=\"M6.7080808,14.2938686 C7.09806642,14.6849308 7.09719364,15.3180952 6.70613141,15.7080808 C6.31506919,16.0980664 5.68190481,16.0971936 5.2919192,15.7061314 L2.2919192,12.6978494 C1.90193253,12.3067862 1.90280651,11.6736197 2.29387128,11.2836345 L5.29387128,8.29191651 C5.684935,7.90193238 6.31809937,7.90280757 6.70808349,8.29387128 C7.09806762,8.684935 7.09719243,9.31809937 6.70612872,9.70808349 L4.41421491,11.9936701 L6.7080808,14.2938686 Z M17.2938713,9.70808349 C16.9028076,9.31809937 16.9019324,8.684935 17.2919165,8.29387128 C17.6819006,7.90280757 18.315065,7.90193238 18.7061287,8.29191651 L21.7061287,11.2836345 C22.0971935,11.6736197 22.0980675,12.3067862 21.7080808,12.6978494 L18.7080808,15.7061314 C18.3180952,16.0971936 17.6849308,16.0980664 17.2938686,15.7080808 C16.9028064,15.3180952 16.9019336,14.6849308 17.2919192,14.2938686 L19.5857851,11.9936701 L17.2938713,9.70808349 Z M13.0513167,5.68377223 C13.2259645,5.15982892 13.7922844,4.87666893 14.3162278,5.0513167 C14.8401711,5.22596447 15.1233311,5.79228445 14.9486833,6.31622777 L10.9486833,18.3162278 C10.7740355,18.8401711 10.2077156,19.1233311 9.68377223,18.9486833 C9.15982892,18.7740355 8.87666893,18.2077156 9.0513167,17.6837722 L13.0513167,5.68377223 Z\" />    </symbol>    <symbol id=\"icGrid\" viewBox=\"0 0 24 24\">         <path d=\"M12,17 C13.1045695,17 14,17.8954305 14,19 C14,20.1045695 13.1045695,21 12,21 C10.8954305,21 10,20.1045695 10,19 C10,17.8954305 10.8954305,17 12,17 Z M18.5,17 C19.3284271,17 20,17.6715729 20,18.5 C20,19.3284271 19.3284271,20 18.5,20 C17.6715729,20 17,19.3284271 17,18.5 C17,17.6715729 17.6715729,17 18.5,17 Z M5.5,17 C6.32842712,17 7,17.6715729 7,18.5 C7,19.3284271 6.32842712,20 5.5,20 C4.67157288,20 4,19.3284271 4,18.5 C4,17.6715729 4.67157288,17 5.5,17 Z M12,10 C13.1045695,10 14,10.8954305 14,12 C14,13.1045695 13.1045695,14 12,14 C10.8954305,14 10,13.1045695 10,12 C10,10.8954305 10.8954305,10 12,10 Z M19,10 C20.1045695,10 21,10.8954305 21,12 C21,13.1045695 20.1045695,14 19,14 C17.8954305,14 17,13.1045695 17,12 C17,10.8954305 17.8954305,10 19,10 Z M5,10 C6.1045695,10 7,10.8954305 7,12 C7,13.1045695 6.1045695,14 5,14 C3.8954305,14 3,13.1045695 3,12 C3,10.8954305 3.8954305,10 5,10 Z M12,3 C13.1045695,3 14,3.8954305 14,5 C14,6.1045695 13.1045695,7 12,7 C10.8954305,7 10,6.1045695 10,5 C10,3.8954305 10.8954305,3 12,3 Z M18.5,4 C19.3284271,4 20,4.67157288 20,5.5 C20,6.32842712 19.3284271,7 18.5,7 C17.6715729,7 17,6.32842712 17,5.5 C17,4.67157288 17.6715729,4 18.5,4 Z M5.5,4 C6.32842712,4 7,4.67157288 7,5.5 C7,6.32842712 6.32842712,7 5.5,7 C4.67157288,7 4,6.32842712 4,5.5 C4,4.67157288 4.67157288,4 5.5,4 Z\" />    </symbol>    <symbol id=\"icClose\" viewBox=\"0 0 24 24\">      <path d=\"M10.5857864,12 L7.29289322,8.70710678 C6.90236893,8.31658249 6.90236893,7.68341751 7.29289322,7.29289322 C7.68341751,6.90236893 8.31658249,6.90236893 8.70710678,7.29289322 L12,10.5857864 L15.2928932,7.29289322 C15.6834175,6.90236893 16.3165825,6.90236893 16.7071068,7.29289322 C17.0976311,7.68341751 17.0976311,8.31658249 16.7071068,8.70710678 L13.4142136,12 L16.7071068,15.2928932 C17.0976311,15.6834175 17.0976311,16.3165825 16.7071068,16.7071068 C16.3165825,17.0976311 15.6834175,17.0976311 15.2928932,16.7071068 L12,13.4142136 L8.70710678,16.7071068 C8.31658249,17.0976311 7.68341751,17.0976311 7.29289322,16.7071068 C6.90236893,16.3165825 6.90236893,15.6834175 7.29289322,15.2928932 L10.5857864,12 Z\" transform=\"rotate(-90 12 12)\" />    </symbol>    <symbol id=\"icBack\" viewBox=\"0 0 24 24\">      <path d=\"M8.36500685,12.7725895 C8.14212731,12.5891835 8,12.3112069 8,12.0000346 C8,11.7624738 8.08283717,11.5442607 8.22120202,11.3727048 L8.22132741,11.3368041 L12.2213274,6.37260414 C12.5678477,5.94255515 13.1973815,5.87484175 13.6274305,6.22136203 C14.0574795,6.56788232 14.1251929,7.1974161 13.7786726,7.6274651 L11.0611595,11.0000346 L19,11.0000346 C19.5522847,11.0000346 20,11.4477499 20,12.0000346 C20,12.5523194 19.5522847,13.0000346 19,13.0000346 L11.0998289,13.0000346 L13.7830365,16.3780588 C14.1265442,16.8105179 14.0544349,17.4395633 13.6219758,17.7830711 C13.1895167,18.1265788 12.5604713,18.0544695 12.2169635,17.6220104 L8.36500685,12.7725895 Z M5,6.00003462 C5.55228475,6.00003462 6,6.44774987 6,7.00003462 L6,17.0000346 C6,17.5523194 5.55228475,18.0000346 5,18.0000346 C4.44771525,18.0000346 4,17.5523194 4,17.0000346 L4,7.00003462 C4,6.44774987 4.44771525,6.00003462 5,6.00003462 Z\" />    </symbol>    <symbol id=\"icResize\" viewBox=\"0 0 24 24\">      <path d=\"M15.5857864,7 L13,7 C12.4477153,7 12,6.55228475 12,6 C12,5.44771525 12.4477153,5 13,5 L18,5 C18.5522847,5 19,5.44771525 19,6 L19,11 C19,11.5522847 18.5522847,12 18,12 C17.4477153,12 17,11.5522847 17,11 L17,8.41421356 L14.7071068,10.7071068 C14.3165825,11.0976311 13.6834175,11.0976311 13.2928932,10.7071068 C12.9023689,10.3165825 12.9023689,9.68341751 13.2928932,9.29289322 L15.5857864,7 Z M7,15.5857864 L9.29289322,13.2928932 C9.68341751,12.9023689 10.3165825,12.9023689 10.7071068,13.2928932 C11.0976311,13.6834175 11.0976311,14.3165825 10.7071068,14.7071068 L8.41421356,17 L11,17 C11.5522847,17 12,17.4477153 12,18 C12,18.5522847 11.5522847,19 11,19 L6,19 C5.44771525,19 5,18.5522847 5,18 L5,13 C5,12.4477153 5.44771525,12 6,12 C6.55228475,12 7,12.4477153 7,13 L7,15.5857864 Z\" />    </symbol>    <symbol id=\"icGridLayout\" viewBox=\"0 0 24 24\">      <path d=\"M6.07692308,5 C5.48215488,5 5,5.44771525 5,6 L5,17 C5,17.5522847 5.48215488,18 6.07692308,18 L17.9230769,18 C18.5178451,18 19,17.5522847 19,17 L19,6 C19,5.44771525 18.5178451,5 17.9230769,5 L6.07692308,5 Z M6.17647059,3 L17.8235294,3 C19.5778457,3 21,4.34314575 21,6 L21,17 C21,18.6568542 19.5778457,20 17.8235294,20 L6.17647059,20 C4.42215432,20 3,18.6568542 3,17 L3,6 C3,4.34314575 4.42215432,3 6.17647059,3 Z M14,5 L16,5 L16,18 L14,18 L14,5 Z M8,5 L10,5 L10,18 L8,18 L8,5 Z\" />    </symbol>    <symbol id=\"icElementInspector\" viewBox=\"0 0 24 24\">      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />    </symbol>    <symbol id=\"icVersionInspector\" viewBox=\"0 0 24 24\">      <path d=\"M6,5 C5.40294373,5 5,5.41327562 5,6 L5,18 C5,18.5867244 5.40294373,19 6,19 L9,19 L9,5 L6,5 Z M6,3 L9,3 L9,21 L6,21 C4.22104159,21 3,19.7358628 3,18 L3,6 C3,4.26413718 4.22104159,3 6,3 Z M12,1 C12.5522847,1 13,1.44771525 13,2 L13,22 C13,22.5522847 12.5522847,23 12,23 C11.4477153,23 11,22.5522847 11,22 L11,2 C11,1.44771525 11.4477153,1 12,1 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M19,7 L21,7 L21,9 L19,9 L19,7 Z M19,11 L21,11 L21,13 L19,13 L19,11 Z M19,15 L21,15 L21,17 L19,17 L19,15 Z M15,19 L17,19 L17,21 L15,21 L15,19 Z M19,19 L21,19 C21,20.1045695 20.1045695,21 19,21 L19,19 Z M19,3 C20.1045695,3 21,3.8954305 21,5 L19,5 L19,3 Z\" />    </symbol>    <symbol id=\"icIncreaseVersion\" viewBox=\"0 0 24 24\">      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M11.3086198,9.29124083 C11.7000598,8.90162823 12.333229,8.90311306 12.7228373,9.29455727 L12.7228373,9.29455727 L15.7087669,12.2945573 C16.0983722,12.6859984 16.0968839,13.3191617 15.7054427,13.7087669 C15.3140016,14.0983722 14.6808383,14.0968839 14.2912331,13.7054427 L14.2912331,13.7054427 L12.0107539,11.4142175 L9.70545052,13.7087592 C9.31401364,14.0983687 8.6808504,14.0968874 8.29124083,13.7054505 C7.90163127,13.3140136 7.9031126,12.6808504 8.29454948,12.2912408 L8.29454948,12.2912408 Z\" />    </symbol>    <symbol id=\"icDecreaseVersion\" viewBox=\"0 0 24 24\">      <path d=\"M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M8.29124083,10.2945495 C8.6808504,9.9031126 9.31401364,9.90163127 9.70545052,10.2912408 L9.70545052,10.2912408 L12.0107539,12.5857825 L14.2912331,10.2945573 C14.6808383,9.90311611 15.3140016,9.90162781 15.7054427,10.2912331 C16.0968839,10.6808383 16.0983722,11.3140016 15.7087669,11.7054427 L15.7087669,11.7054427 L12.7228373,14.7054427 C12.333229,15.0968869 11.7000598,15.0983718 11.3086198,14.7087592 L11.3086198,14.7087592 L8.29454948,11.7087592 C7.9031126,11.3191496 7.90163127,10.6859864 8.29124083,10.2945495 Z\" />    </symbol>    <symbol id=\"icCloseBtn\" viewBox=\"0 0 24 24\">      <path fill=\"#FFFFFF\" d=\"M4.29289322,4.29289322 C4.68341751,3.90236893 5.31658249,3.90236893 5.70710678,4.29289322 L5.70710678,4.29289322 L12,10.585 L18.2928932,4.29289322 C18.6533772,3.93240926 19.2206082,3.90467972 19.6128994,4.20970461 L19.7071068,4.29289322 C20.0976311,4.68341751 20.0976311,5.31658249 19.7071068,5.70710678 L19.7071068,5.70710678 L13.415,12 L19.7071068,18.2928932 C20.0675907,18.6533772 20.0953203,19.2206082 19.7902954,19.6128994 L19.7071068,19.7071068 C19.3165825,20.0976311 18.6834175,20.0976311 18.2928932,19.7071068 L18.2928932,19.7071068 L12,13.415 L5.70710678,19.7071068 C5.34662282,20.0675907 4.77939176,20.0953203 4.38710056,19.7902954 L4.29289322,19.7071068 C3.90236893,19.3165825 3.90236893,18.6834175 4.29289322,18.2928932 L4.29289322,18.2928932 L10.585,12 L4.29289322,5.70710678 C3.93240926,5.34662282 3.90467972,4.77939176 4.20970461,4.38710056 Z\" />    </symbol>    <symbol id=\"icAddComment\" viewBox=\"0 0 24 24\">    <path d=\"M19,3 L5,3 C3.34314575,3 2,4.34314575 2,6 L2,16 L2.00509269,16.1762728 C2.09633912,17.75108 3.40231912,19 5,19 L15.65,19 L18.7506099,21.4811128 C19.105236,21.7648136 19.545857,21.9193752 20,21.9193752 C21.1045695,21.9193752 22,21.0239447 22,19.9193752 L22,6 C22,4.34314575 20.6568542,3 19,3 Z M5,5 L19,5 C19.5522847,5 20,5.44771525 20,6 L20,19.9193752 L16.3507811,17 L5,17 C4.44771525,17 4,16.5522847 4,16 L4,6 C4,5.44771525 4.44771525,5 5,5 Z M12,7 C12.5522847,7 13,7.44771525 13,8 L13,14 C13,14.5522847 12.5522847,15 12,15 C11.4477153,15 11,14.5522847 11,14 L11,8 C11,7.44771525 11.4477153,7 12,7 Z M9,10 L15,10 C15.5522847,10 16,10.4477153 16,11 C16,11.5522847 15.5522847,12 15,12 L9,12 C8.44771525,12 8,11.5522847 8,11 C8,10.4477153 8.44771525,10 9,10 Z\"/>    </symbol >    <symbol id=\"icComments\" viewBox=\"0 0 24 24\">    <path d=\"M19,3 C20.6568542,3 22,4.34314575 22,6 L22,6 L22,19.9193752 C22,21.0239447 21.1045695,21.9193752 20,21.9193752 C19.545857,21.9193752 19.105236,21.7648136 18.7506099,21.4811128 L18.7506099,21.4811128 L15.65,19 L5,19 C3.40231912,19 2.09633912,17.75108 2.00509269,16.1762728 L2.00509269,16.1762728 L2,16 L2,6 C2,4.34314575 3.34314575,3 5,3 L5,3 Z M19,5 L5,5 C4.44771525,5 4,5.44771525 4,6 L4,6 L4,16 C4,16.5522847 4.44771525,17 5,17 L5,17 L16.3507811,17 L20,19.9193752 L20,6 C20,5.44771525 19.5522847,5 19,5 L19,5 Z M12,12 C12.5522847,12 13,12.4477153 13,13 C13,13.5522847 12.5522847,14 12,14 L8,14 C7.44771525,14 7,13.5522847 7,13 C7,12.4477153 7.44771525,12 8,12 L12,12 Z M16,8 C16.5522847,8 17,8.44771525 17,9 C17,9.55228475 16.5522847,10 16,10 L8,10 C7.44771525,10 7,9.55228475 7,9 C7,8.44771525 7.44771525,8 8,8 L16,8 Z\"/>    </symbol >    </svg ></div >\n    <div class=\"shaft1\"></div><div class=\"shaft2\"></div><div class=\"shaft3\"></div>    <div class=\"shaft4\"></div><div class=\"shaft5\"></div><div class=\"shaft6\"></div><div class=\"shaft7\"></div>  </div>      <!--/load indicator-->     <div id=\"container\">        <div id=\"marker\"></div>        <div id=\"content\" onclick=\"viewer.onContentClick()\"></div>        <div id=\"sidebar\" class=\"hidden\">            <div id=\"symbol_viewer\" class=\"hidden\">                <div class=\"title\">                  <div style=\"width:100%;\">Element Inspector</div>                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.symbolViewer.toggle();  return false;\">                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                  </div>                </div>                <div class=\"checkbox-container\" style=\"margin-top:62px;\">                  <input type=\"checkbox\" id=\"symbol_viewer_symbols\" />                  <label for=\"symbol_viewer_symbols\"></label>                  <span class=\"checkbox-label\">Show symbols&nbsp;&nbsp;</span>                  <select id=\"lib_selector\" style=\"width:200px;display:none;\"></select>                </div>                <div id=\"empty\" style=\"padding: 16px 20px 0 20px;margin-top:20px;\">Click any element to inspect</div>                <div id=\"symbol_viewer_content\" style=\"margin-top:20px;\">                </div>            </div>            <div id=\"comments_viewer\" class=\"hidden\">                <div class=\"title\">                    <div style=\"width:100%;\">Comments</div>                    <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.commentsViewer.toggle();  return false;\">                        <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                    </div>                </div>                <div id=\"comments_viewer_content\">                </div>            </div >            <div id=\"version_viewer\" class=\"hidden\">                <div class=\"title\">                  <div style=\"width:100%;\">Version Inspector</div>                  <div style=\"width:24px; height:24px; cursor: pointer;\" onclick=\"viewer.versionViewer.toggle();  return false;\">                    <svg class=\"svgIcon\"><use xlink:href=\"#icClose\"></use></svg>                  </div>                </div>    <div style=\"padding: 72px 20px 0 20px\">                   Mode:<br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_diff\" value=\"diff\" checked onclick=\"viewer.versionViewer.pageChanged()\" disabled /><label for=\"version_viewer_mode_diff\">Differences</label><br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_prev\" value=\"prev\" onclick=\"viewer.versionViewer.pageChanged()\" disabled><label for=\"version_viewer_mode_prev\">Prev version</label><br />                  <input type=\"radio\" name=\"version_viewer_mode\" id=\"version_viewer_mode_new\" value=\"new\" onclick=\"viewer.versionViewer.pageChanged()\" disabled><label for=\"version_viewer_mode_new\">New version</label><br />                </div>                <div id=\"version_viewer_content\" style=\"padding: 72px 20px 0 20px\"></div>            </div>        </div>    <div id=\"content-shadow\" class=\"hidden\" onclick=\"viewer.onContentClick()\"></div>    <div id=\"content-modal\" class=\"contentModal hidden\" onclick=\"viewer.onModalClick()\"></div>            <div id=\"gallery-modal\" class=\"hidden\">\n          <div id=\"gallery-header\">\n            <div id=\"gallery-header-container\">\n              <div id=\"title\"><div>FixedLayers</div><div id=\"screensamount\"></div></div>\n              <div id=\"search\"><input type=\"text\" placeholder=\"Search screen...\" id=\"searchInput\" onkeyup=\"searchScreen()\"></div>\n              <div id=\"right\">\n                <div class=\"checkbox-container\">\n                  <input type=\"checkbox\" id=\"galleryShowMap\" onclick=\"viewer.galleryViewer.enableMapMode(this.checked)\"/>\n                  <label for=\"galleryShowMap\"></label>\n                  <span class=\"checkbox-label\">Show map (M)</span>\n                </div>\n                <div id=\"closebtn\" onclick=\"viewer.galleryViewer.hide(); return false;\"><svg class=\"svgIcon\"><use xlink:href=\"#icCloseBtn\"></use></svg></div>\n              </div>\n            </div>\n          </div>\n          <div id=\"gallery\"><div id=\"grid\"></div></div>\n          <div id=\"map-controls\">\n            <div id=\"map-controls-container\">\n              <div class=\"checkbox-container\">\n                <input type=\"checkbox\" id=\"galleryShowMapLinks\" onclick=\"viewer.galleryViewer.showMapLinks(this.checked)\"/>\n                <label for=\"galleryShowMapLinks\"></label>\n                <span class=\"checkbox-label\">Show all links (L)</span>\n              </div>\n              <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" class=\"mapZoom\" onclick=\"viewer.galleryViewer.mapZoomChanged(this.value)\">\n              <span onclick=\"viewer.galleryViewer.resetMapZoom();return false;\" class=\"mapResetZoom\">Reset zoom</span>\n            </div>\n          </div>\n        </div>\n    <div id=\"nav\" class=\"nav\">            <div class=\"navLeft\">                <div id=\"menu\" class=\"menu\">                            <div class=\"groupe\">                                <div id=\"menu_comments_viewer\" class=\"hidden item\" onclick=\"viewer.commentsViewer.toggle(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icAnnotation\"></use></svg>                                    <span>Comments</span>                                    <div class=\"tips\">C</div>                                </div>                                <div id=\"links\" class=\"item\" onclick=\"viewer.toggleLinks(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icPointer\"></use></svg>                                    <span>Hot Spots</span>                                    <div class=\"tips\">⇧</div>                                </div>                                <div  id=\"zoom\" class=\"item\" onclick=\"viewer.toggleZoom(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icResize\"></use></svg>                                    <span>Toogle Auto-Scale</span>                                    <div class=\"tips\">Z</div>                                </div>                                <div  id=\"embed\" class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.share();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icEmbed\"></use></svg>                                    <span>Show Embed Code</span>                                    <div class=\"tips\">E</div>                                </div>                                <div  id=\"grid\" class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.toogleLayout();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icGridLayout\"></use></svg>                                    <span>Toogle Grid Layout</span>                                    <div class=\"tips\">L</div>                                </div>                            <div  id=\"symbols\"  class=\"item\" onclick=\"addRemoveClass('class','menu','active'); viewer.symbolViewer.toggle();  return false;\">                                <svg class='svgIcon'><use xlink:href=\"#icElementInspector\"></use></svg>                                <span>Elements Inspector</span>                                <div class=\"tips\">M</div>                            </div>                                <div id=\"menu_version_viewer\" class=\"hidden item\" onclick=\"addRemoveClass('class','menu','active'); viewer.versionViewer.toggle();  return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icVersionInspector\"></use></svg>                                    <span>Version Inspector</span>                                    <div class=\"tips\">V</div>                                </div>                            </div>                            <hr>                            <div class=\"groupe\">                                <div class=\"item\" onclick=\"viewer.increaseVersion(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icIncreaseVersion\"></use></svg>                                    <span>Version Up</span>                                    <div class=\"tips\">⇧ ↑</div>                                </div>                                <div class=\"item\" onclick=\"viewer.decreaseVersion(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icDecreaseVersion\"></use></svg>                                    <span>Version Down</span>                                    <div class=\"tips\">⇧ ↓</div>                                </div>                            </div>                            <hr>                            <div  id=\"viewall\" class=\"groupe\">                                <div class=\"item\" onclick=\"viewer.galleryViewer.show(); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icGrid\"></use></svg>                                    <span>View All Screens</span>                                    <div class=\"tips\">G</div>                                </div>                                <div  id=\"start\"  class=\"item\" onclick=\"viewer.goToPage(0); addRemoveClass('class','menu','active'); return false;\">                                    <svg class='svgIcon'><use xlink:href=\"#icBack\"></use></svg>                                    <span>Go To Start</span>                                    <div class=\"tips\">S</div>                                </div>                            </div>                </div>                <div id=\"btnMenu\" class=\"btnMenu\" onclick=\"addRemoveClass('class', 'menu', 'active')\">                    <svg class='svgIcon'><use xlink:href=\"#icMenu\"></use></svg>                </div>                <div id=\"btnOpenNew\" style='display:none' class=\"btnMenu\" onclick=\"viewer.openNewWindow();return false;\">                    <svg class='svgIcon'><use xlink:href=\"#icResize\"></use></svg>                </div>                <div class=\"navPreviewNext\">                    <div id=\"nav-left-prev\" class=\"btnPreview\" onclick=\"viewer.previous(); return false;\" title=\"Previous screen\">                        <svg class='svgIcon'><use xlink:href=\"#icArrwLeft\"></use></svg>                    </div>                    <div id=\"nav-left-next\" class=\"btnNext\" onclick=\"viewer.next(); return false;\" title=\"Next screen\"><svg class='svgIcon'><use xlink:href=\"#icArrwRight\"></use></svg></div>                </div>            </div>            <div class=\"navCenter\"><div class=\"pageName title\">Default button</div></div>            <div class=\"navRight\">                        <div id=\"loading\" class=\"hidden\">                            <div class=\"lds-ring\"><div></div><div></div><div></div><div></div></div>                        </div>                        <div id=\"pageComments\" onclick=\"commentsViewer.toggle(); return false;\" class=\"hidden\">                            <svg class=\"svgIcon\"><use xlink:href=\"#icAddComment\"></use></svg>                            <div id = \"counter\">3</div>                        </div>                    </div >           </div>        </div> </div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/FixedLayers/resources/animations.css",
    "content": "@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/cb-modern-ui.css",
    "content": "@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/jquery.hotkeys.js",
    "content": "/*jslint browser: true*/\n/*jslint jquery: true*/\n\n/*\n * jQuery Hotkeys Plugin\n * Copyright 2010, John Resig\n * Dual licensed under the MIT or GPL Version 2 licenses.\n *\n * Based upon the plugin by Tzury Bar Yochay:\n * https://github.com/tzuryby/jquery.hotkeys\n *\n * Original idea by:\n * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/\n */\n\n/*\n * One small change is: now keys are passed by object { keys: '...' }\n * Might be useful, when you want to pass some other data to your handler\n */\n\n(function(jQuery) {\n\n  jQuery.hotkeys = {\n    version: \"0.2.0\",\n\n    specialKeys: {\n      8: \"backspace\",\n      9: \"tab\",\n      10: \"return\",\n      13: \"return\",\n      16: \"shift\",\n      17: \"ctrl\",\n      18: \"alt\",\n      19: \"pause\",\n      20: \"capslock\",\n      27: \"esc\",\n      32: \"space\",\n      33: \"pageup\",\n      34: \"pagedown\",\n      35: \"end\",\n      36: \"home\",\n      37: \"left\",\n      38: \"up\",\n      39: \"right\",\n      40: \"down\",\n      45: \"insert\",\n      46: \"del\",\n      59: \";\",\n      61: \"=\",\n      96: \"0\",\n      97: \"1\",\n      98: \"2\",\n      99: \"3\",\n      100: \"4\",\n      101: \"5\",\n      102: \"6\",\n      103: \"7\",\n      104: \"8\",\n      105: \"9\",\n      106: \"*\",\n      107: \"+\",\n      109: \"-\",\n      110: \".\",\n      111: \"/\",\n      112: \"f1\",\n      113: \"f2\",\n      114: \"f3\",\n      115: \"f4\",\n      116: \"f5\",\n      117: \"f6\",\n      118: \"f7\",\n      119: \"f8\",\n      120: \"f9\",\n      121: \"f10\",\n      122: \"f11\",\n      123: \"f12\",\n      144: \"numlock\",\n      145: \"scroll\",\n      173: \"-\",\n      186: \";\",\n      187: \"=\",\n      188: \",\",\n      189: \"-\",\n      190: \".\",\n      191: \"/\",\n      192: \"`\",\n      219: \"[\",\n      220: \"\\\\\",\n      221: \"]\",\n      222: \"'\"\n    },\n\n    shiftNums: {\n      \"`\": \"~\",\n      \"1\": \"!\",\n      \"2\": \"@\",\n      \"3\": \"#\",\n      \"4\": \"$\",\n      \"5\": \"%\",\n      \"6\": \"^\",\n      \"7\": \"&\",\n      \"8\": \"*\",\n      \"9\": \"(\",\n      \"0\": \")\",\n      \"-\": \"_\",\n      \"=\": \"+\",\n      \";\": \": \",\n      \"'\": \"\\\"\",\n      \",\": \"<\",\n      \".\": \">\",\n      \"/\": \"?\",\n      \"\\\\\": \"|\"\n    },\n\n    // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url\n    textAcceptingInputTypes: [\n      \"text\", \"password\", \"number\", \"email\", \"url\", \"range\", \"date\", \"month\", \"week\", \"time\", \"datetime\",\n      \"datetime-local\", \"search\", \"color\", \"tel\"],\n\n    // default input types not to bind to unless bound directly\n    textInputTypes: /textarea|input|select/i,\n\n    options: {\n      filterInputAcceptingElements: true,\n      filterTextInputs: true,\n      filterContentEditable: true\n    }\n  };\n\n  function keyHandler(handleObj) {\n    if (typeof handleObj.data === \"string\") {\n      handleObj.data = {\n        keys: handleObj.data\n      };\n    }\n\n    // Only care when a possible input has been specified\n    if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== \"string\") {\n      return;\n    }\n\n    var origHandler = handleObj.handler,\n      keys = handleObj.data.keys.toLowerCase().split(\" \");\n\n    handleObj.handler = function(event) {\n      //      Don't fire in text-accepting inputs that we didn't directly bind to\n      if (this !== event.target &&\n        (jQuery.hotkeys.options.filterInputAcceptingElements &&\n          jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||\n          (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||\n          (jQuery.hotkeys.options.filterTextInputs &&\n            jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {\n        return;\n      }\n\n      var special = event.type !== \"keypress\" && jQuery.hotkeys.specialKeys[event.which],\n        character = String.fromCharCode(event.which).toLowerCase(),\n        modif = \"\",\n        possible = {};\n\n      jQuery.each([\"alt\", \"ctrl\", \"shift\"], function(index, specialKey) {\n\n        if (event[specialKey + 'Key'] && special !== specialKey) {\n          modif += specialKey + '+';\n        }\n      });\n\n      // metaKey is triggered off ctrlKey erronously\n      if (event.metaKey && !event.ctrlKey && special !== \"meta\") {\n        modif += \"meta+\";\n      }\n\n      if (event.metaKey && special !== \"meta\" && modif.indexOf(\"alt+ctrl+shift+\") > -1) {\n        modif = modif.replace(\"alt+ctrl+shift+\", \"hyper+\");\n      }\n\n      if (special) {\n        possible[modif + special] = true;\n      }\n      else {\n        possible[modif + character] = true;\n        possible[modif + jQuery.hotkeys.shiftNums[character]] = true;\n\n        // \"$\" can be triggered as \"Shift+4\" or \"Shift+$\" or just \"$\"\n        if (modif === \"shift+\") {\n          possible[jQuery.hotkeys.shiftNums[character]] = true;\n        }\n      }\n\n      for (var i = 0, l = keys.length; i < l; i++) {\n        if (possible[keys[i]]) {\n          return origHandler.apply(this, arguments);\n        }\n      }\n    };\n  }\n\n  jQuery.each([\"keydown\", \"keyup\", \"keypress\"], function() {\n    jQuery.event.special[this] = {\n      add: keyHandler\n    };\n  });\n\n})(jQuery || this.jQuery || window.jQuery);\n"
  },
  {
    "path": "docs/FixedLayers/resources/ux1-ui.css",
    "content": "/* MOD FOR VARIANT B WAS @color-background-primary*/\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: all 0.2s easy;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/viewer-center.css",
    "content": "#container, #content {\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/viewer-fonts.css",
    "content": "@font-face {\n    font-family: 'Font Awesome';\n    font-weight:400;\n    src: url('fonts/fa-regular-400.woff2') format(\"woff2\"),\n    url('fonts/fa-regular-400.woff') format('woff'), \n    url('fonts/fa-regular-400.ttf') format('truetype');\n}\n@font-face {\n    font-family: 'Font Awesome';\n    font-weight:900;\n    src: url('fonts/fa-solid-900.woff2') format(\"woff2\"),\n    url('fonts/fa-solid-900.woff') format('woff'), \n    url('fonts/fa-solid-900.ttf') format('truetype');\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/viewer-top.css",
    "content": "#container{\n\ttext-align: center;\n    margin: 0 auto;\t\n    overflow: auto;\n}\n\n#content,#map{\n\ttext-align: center;\n    margin: 0 auto;\t   \n    /*width:100%;*/\n    /*position:fixed; */\n}\n\n#content-shadow {\n\tz-index: 40;\n\tposition: fixed;\n\toverflow: auto;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\twidth: 100vw;\n\theight: 100vh;\n\tdisplay: grid;\n\tmargin-left: auto;\n\tmargin-right: auto; \n\tmargin-top: 0 auto; \n\tmargin-bottom: auto;\n\talign-items: center;\t\n}\n"
  },
  {
    "path": "docs/FixedLayers/resources/viewer.css",
    "content": "/*@import \"https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css\";*/\n\n/* Tooltip container */\n.tooltip {\n  position: relative;\n  display: inline-block;\n  /*border-bottom: 1px dotted black; */\n  cursor: pointer;\n}\n\n/* Tooltip text */\n.tooltip .tooltiptext {\n  visibility: hidden;\n  background-color:  var(--color-primary);\n  color: var(--color-background);\n  text-align: center;\n  padding: 10px 10px;\n  border-radius: 4px;\n \n  /* Position the tooltip text - see examples below! */\n  position: absolute;\n  z-index: 1;\n}\n\n/* Show the tooltip text when you mouse over the tooltip container */\n.tooltip:hover .tooltiptext {\n  visibility: visible;\n}\n\n#content .image_div {\n  margin: 0 auto;\n  position: absolute;\n}\n#contentModall .image_div {\n    margin: 0 auto;\n    /* Commented to fix long modals\n    position: absolute;\n    */\n  }\n\n.image_div img {\n  /*-webkit-transform:translate3d(0,0,0);*/\n}\n\n/*** MAPS ***/\n.linksDiv {\n  width: 100%;\n  position: absolute;\n  z-index: 2;\n}\n\n.linkDiv {\n  position: absolute;\n  cursor: pointer;\n  opacity: 0;\n  transition: opacity 0.5s;\n  background-color: #FFC400;\n  pointer-events: auto;\n}\n\n.linkHoverDiv {\n  position: absolute;\n  opacity: 0;\n  cursor: pointer;\n  transition: opacity 0.5s;\n  background-color: #FFC400;\n  pointer-events: auto;\n}\n\n.linkDivHighlight:hover {\n  opacity: 0.2;\n  transition: opacity 0.5s;\n}\n\n.contentLinksVisible .linkDiv {\n  opacity: 0.4;\n  transition: opacity 0.5s;\n}\n\n/************ SYMBOLS ******/\n.symbolLink {}\n\n.modalSymbolLink {}\n\n.symbolDiv {\n  position: absolute;\n  display: none;\n  z-index: 30;\n}\n\n.contentSymbolsVisible .symbolDiv:hover {\n  opacity: 1;\n  outline: 1px dashed red;\n  outline-offset: -1px;\n  background-color: rgba(255, 0, 0, .05);\n  transition: all 0.3s;\n}\n\n.contentSymbolsVisible .symbolDiv {\n  opacity: .3;\n  display: block;\n  cursor: pointer;\n  /*border: 1px dashed red;   */\n  outline: 1px solid red;\n  outline-offset: -1px;\n  transition: all 0.3s;\n}\n\n.svBorderLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 30;\n}\n.contentSymbolsVisible .svBorderLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px dashed red;\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n\n.svMarginLineDiv {\n    position: absolute;\n    display: none;\n    z-index: 31;\n}\n.contentSymbolsVisible .svMarginLineDiv {\n    display: block;\n    cursor: pointer;\n    outline: 1px solid var(--color-accent);\n    outline-offset: -1px;\n    transition: all 0.3s;\n}\n\n.svMarginValueDiv {\n    position: absolute;\n    display: none;\n    z-index: 32;\n}\n.contentSymbolsVisible .svMarginValueDiv {\n    display: block;\n    cursor: pointer;\n    border-radius: 4px;\n    height: 16px;\n    width:30px;\n    line-height: 16px;\n    font-weight: 500;\n    background-color: var(--color-accent);\n    transition: all 0.3s;\n    font-size:10px;\n    color:white;\n}\n\n#comments_viewer,\n#symbol_viewer,\n#version_viewer {\n  width: 100%;\n  overflow-y: auto;\n  padding-bottom: 20px;\n}\n\nselect{\n  height: 32px;\n  width: 100%;\n  font-size: var(--font-small);\n  color: var(--color-primary);\n  border: none;\n  border-radius: 5px;\n  background: var(--color-hover);\n  cursor: pointer;\n  text-indent: 4px;\n  transition: all .12s;\n\n  /* reset */\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n\n  padding: 0px 8px;\n}\n\nselect:hover{\n  background: var(--color-hover-bright);\n  /* color: white; */\n  transition: all .2s;\n}\n\nselect:focus{\n  outline: none;\n}\n\n.checkbox-container{\n  display: flex;\n  align-items: center;\n  justify-content:flex-start;\n\tposition: relative;\n  height: 36px;\n  font-size: 14px;\n}\n\n.checkbox-container label{\n  background-color: var(--color-active);\n\tborder-radius: 20px;\n\tdisplay: inline-block;\n\tposition: relative;\n\ttransition: all 0.2s ease-out;\n\twidth: 28px;\n\theight: 16px;\n\tz-index: 2;\n  cursor: pointer;\n}\n\n#gallery-modal .checkbox-container label {\n  background-color: rgba(255,255,255,.12);\n}\n\n.checkbox-container label::after {\n\tcontent: ' ';\n  background-color: rgb(255, 255, 255);\n\tborder-radius: 50%;\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\ttransform: translateX(0);\n\ttransition: transform 0.12s linear;\n\twidth: 12px;\n\theight: 12px;\n\tz-index: 3;\n  /* box-shadow: var(--shadow1); */\n  transition: all .2s;\n}\n\n.checkbox-container label:hover::after{\n  transition: all .12s;\n}\n\n.checkbox-container input {\n\tvisibility: hidden;\n\tposition: absolute;\n\tz-index: 2;\n}\n\n.checkbox-container input:checked + label::after {\n\ttransform: translateX(calc(100% + 0.5px));\n  background-color: #fff;\n}\n\n.checkbox-container input:checked + label {\n\tbackground-color: var(--color-accent);\n}\n\n#gallery-modal .checkbox-container input:checked + label {\n\tbackground-color: var(--color-accent);\n}\n\n.checkbox-container .checkbox-label{\n  margin-left: 12px;\n}\n\n#symbol_viewer .checkbox-container {\n  padding: 16px 20px 0px 20px;\n}\n\n#gallery-modal .checkbox-container {\n  margin-right: 40px;\n}\n\n#comments_viewer .title, \n#symbol_viewer .title, \n#version_viewer .title {\n  height: 56px;\n  display: flex;\n  align-items: center;\n  position: fixed;\n  box-sizing:border-box;\n  width: 400px;\n  padding: 0 20px;\n  background: var(--color-background);\n  font-weight: var(--font-weight-bold);\n  border-bottom: solid 1px var(--color-border);\n  z-index: 10;\n}\n\n#comments_viewer_content{\n  padding: 72px 20px 0 20px;\n}\n\n#symbol_viewer_content .block{\n  padding: 16px 20px 0 20px;\n}\n\n#symbol_viewer_content .block.twoColumn{\n  display: flex;\n}\n\n.tokenName{\n  /* color: aquamarine; */\n  color: var(--color-accent);\n  white-space: nowrap;\n}\n\n.tokenValue{\n  color: var(--color-secondary);\n  margin-left: 10px;\n}\n\n.twoColumn div{\n  width: 50%;\n}\n\n#symbol_viewer_content .label{\n  color: var(--color-secondary);\n}\n\n#symbol_viewer_content .value{\n  padding-top: 8px;\n}\n\n#symbol_viewer_content .value.code{\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n  line-height: 18px;\n}\n\n.svlink{\n    color: var(--color-accent);\n}\n\n#symbol_viewer_content .icon{\n    font-family: \"Font Awesome\";\n    font-size: 60px;\n    line-height: 60px;\n    font-weight:400;\n}\n#symbol_viewer_content .icon.solid{\n    font-weight:900;\n}\n\n#sv_content{\n    font-size: 20px;\n    line-height: 18px;\n  }\n\n/* #symbol_viewer_content .head {\n  color: var(--color-secondary);\n  border-top: solid 1px var(--color-border);\n}\n\n#version_viewer .head {\n  font-weight: 900;\n} */\n\n/******************/\n/************ LAYOUT ******/\n.layouLineDiv {\n  position: absolute;\n  opacity: 0;\n  transition: opacity 0.5s;\n  z-index: 30;\n  pointer-events: none;\n}\n\n.layoutColDiv {\n  background-color: #FF0000;\n}\n\n.layoutRowDiv {\n  background-color: #000000;\n}\n\n.contentLayoutVisible .layouLineDiv {\n  opacity: 0.05;\n  transition: opacity 0.5s;\n  pointer-events: none;\n}\n\n/******************/\n.map {\n  z-index: 9;\n  position: absolute;\n}\n\n.contentModal {\n  z-index: 50;\n  position: fixed;\n  margin: auto;\n  max-height: 100%;\n  align-items: center;\n}\n\n.contentModal>div {\n  pointer-events: auto;\n}\n\n.contentModal .image_div {\n  z-index: 50;\n}\n\n#content-shadow.shadow {\n  background-color: rgba(5, 4, 4, 0.7);\n}\n\n#content-shadow.no-shadow {\n  background-color: transparent;\n}\n\n/*\n#content-modal>div {\n\tmargin-top: 50px auto 50px;\n\tmargin-bottom: 50px auto 50px;\n}\n*/\n/* GALLERY */\n#gallery-modal {\n  display: flex;\n  justify-content: center;\n  position: fixed;\n  overflow: auto;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100vw;\n  height: 100vh;\n  background-color: var(--color-background-gallery);\n  z-index: 100;\n}\n\n#gallery {\n  /*width: 100%;*/\n  margin: 80px 0px;\n}\n\n.gallery-grid {\n    width: calc(100% - 80px);\n    max-width: 1200px;\n    margin: 80px 40px;\n}\n\n#gallery-header {\n  display: flex;\n  position: fixed;\n  justify-content: center;\n  top: 0;\n  height: 80px;\n  width: 100%;\n  color: white;\n  font-size: 24px;\n  line-height: 32px;\n  background-color: var(--color-background-gallery-90);\n  z-index: 102;\n}\n\n#gallery-header-container {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  /* max-width: 1200px; */\n  width: 100%;\n  margin: 0 40px;\n}\n\n#gallery-header-container #title {\n  text-align: left;\n}\n\n#gallery-header-container #title div{\nwhite-space: nowrap;\noverflow-x: hidden;\ntext-overflow: ellipsis;\n-webkit-line-clamp: 2;\n-webkit-box-orient: vertical;\n}\n\n#map-controls {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 56px;\n  display: flex;\n  justify-content: center;\n  color: white;\n  background-color: var(--color-background-gallery-90);\n  z-index: 103;\n}\n\n#map-controls-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  /* max-width: 1200px; */\n  width: 100%;\n  margin: 0 40px;\n}\n\n#gallery-header-container #screensamount {\n  font-size: 14px;\n  line-height: 20px;\n  font-weight: 300;\n  opacity: .7;\n}\n\n#search {\n  max-width: 280px;\n  flex-grow: 1;\n  display: flex;\n  height: 32px;\n  padding: 0 12px;\n  border-radius: 5px;\n  background: rgba(255, 255, 255, 0.12);\n}\n\n#searchInput {\n  width: 200px;\n  height: 30px;\n  font-size: 14px;\n  font-weight: 300;\n  letter-spacing: 0.2px;\n  color: white;\n  border: none;\n  background: none;\n}\n\n#searchInput:focus {\n  outline: none;\n}\n\n#searchInput::placeholder {\n  color: white;\n  opacity: .5;\n}\n\n#right {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n}\n\n#title, #search, #right {\n  width: calc(100% / 3);\n}\n\n#gallery-header-container #closebtn {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  min-width: 48px;\n  height: 48px;\n  margin-right: -16px;\n  cursor: pointer;\n  opacity: .7;\n  border-radius: 100%;\n  transition: all .2s;\n  background: rgba(0, 0, 0, 0);\n}\n\n#gallery-header-container #closebtn:hover {\n  opacity: 1;\n  background: rgba(0, 0, 0, .18);\n  transition: all .24s;\n}\n\n.gallery-grid{\n    justify-content: center;\n    margin: 0 -32px 80px -32px;\n}\n\n#gallery #grid {\n  /* display: -webkit-box; */\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  /* -webkit-box-orient: horizontal;\n  -webkit-box-direction: normal; */\n  flex-direction: row;\n  margin: 0 -16px 80px -16px;\n  padding-bottom: 40px;\n}\n\n.galleryArtboardAbs{\n    position: absolute;\n    border-radius: 5px;\n    background: none;\n    box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n    cursor: pointer;\n    transition: transform .24s, box-shadow .24s;\n}\n\n.galleryArtboardAbs image{\n}\n\n#gallery #grid svg{\n    z-index:61;\n    pointer-events: none;\n    position:absolute;\n    left:40px;\n    top: 80px;\n}\n\n#gallery #grid svg circle{\n    z-index:62;\n    stroke: #F89000;\n    stroke-width:1;\n    fill:white;\n}\n\n#gallery #grid svg path{\n    stroke: #F89000;\n    stroke-width:1;\n    fill:none;\n    z-index:61;\n}\n\n/* #gallery-header-container .checkbox-container{\n    width:100%;\n} */\n\n#map-controls-container .mapZoom{\n    width:100px;\n}\n\n#map-controls-container .mapResetZoom{\n    color:white;\n    font-size:14px;\n    cursor: pointer;\n    margin-left: 16px;\n}\n\n#commenting {\n  z-index: 100;\n  position: fixed;\n  overflow: auto;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100vw;\n  height: 100vh;\n  display: grid;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: auto;\n  margin-bottom: auto;\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.9);\n}\n\n.grid-cell {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: calc((1200px - 16px*6)/4);\n  height: 212px;\n  overflow: hidden;\n  margin: 16px;\n  border-radius: 5px;\n  background: #fff;\n  box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.08);\n  cursor: pointer;\n  transition: transform .24s, box-shadow .24s;\n}\n\n.grid-cell:hover,\n.galleryArtboardAbs:hover {\n  transition: all .2s;\n  transform: translateY(-2px);\n  box-shadow: 0 0.3125rem 2rem 0 rgba(0, 0, 0, 0.3);\n}\n\n/*.grid-cell-wrapper{\n\twidth: 100%;\n\tdisplay: inline-block;\n\tposition: relative;\n}*/\n/*.grid-cell-main{\n\tposition: absolute;\n\ttop:0;\n\tbottom:0;\n\tleft:0;\n\tright:0;\n}*/\n.grid-cell img {\n  width: 100%;\n  /*top:0;*/\n  /*position: absolute;*/\n}\n\n\n#gallery #grid .groupTitle{\n    position: absolute;\n    width: 100%;\n    height:30px;\n    color: gray;\n    font-size: 20px;\n    text-align: left;\n  }\n\n.grid-cell .div-page-title{\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n}\n\n.grid-cell span {\n  padding: 0 16px;\n  height: 56px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  align-self: flex-end;\n  font-size: 14px;\n  color: white;\n  font-weight: 400;\n  background-color: var(--color-background-gallery-90);\n}\n\na,\na:focus {\n  outline: none;\n  color: var(--color-link);\n  text-decoration: none;\n  font-weight: bold;\n}\n\n.transparent {\n  visibility: hidden;\n  opacity: 0;\n}\n\n.hidden {\n  display: none !important;\n}\n\n.hotspots-off {\n  pointer-events: none;\n}\n\n/* VIEWER UI */\n:root {\n\n  /* color */\n  --color-primary: #404B58;\n  --color-secondary: #989EA5;\n  --color-accent: #007AFF;\n  --color-hover: #F4F5F6;\n  --color-hover-bright: var(--color-active);\n  --color-active: #EAEDF0;\n  --color-border: var(--color-active);\n  --color-link:    #568AF2;\n  --color-background: #FFF;\n  --color-background-gallery: var(--color-primary);\n  --color-background-gallery-90: rgba(64,75,88,0.90);\n\n  /* font size */\n  --font-regular: 14px;\n  --font-small: 12px;\n\n  /* font weight  */\n  --font-weight-regular: 300;\n  --font-weight-bold: 700;\n\n  /* line height */\n  --line-height: 20px;\n\n  /* shadow */\n  --shadow1: 0 1px 3px 0 rgba(0,0,0,0.20);\n  --shadow2: 0 0 1px 0 rgba(0,0,0,0.10), 0 2px 8px 0 rgba(0,0,0,0.10);\n\n  /* border */\n  --border: 1px solid rgba(73, 84, 96, 0.06);\n\n  /* border radius */\n  --border-radius: 3px;\n  --border-radius-circle: 100%;\n\n}\n\n/* NAVIGATION */\nbody {\n  font-size: var(--font-regular);\n  line-height: var(--line-height);\n  color: var(--color-primary);\n  margin: 0px;\n  padding: 0px;\n  font-family: -apple-system, BlinkMacSystemFont,\n    \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif,\n    \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n}\n\n.containerSVG {\n  display: none;\n}\n\n.svgIcon {\n  width: 24px;\n  height: 24px;\n  fill: var(--color-primary);\n}\n\n#sidebar {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  display: flex;\n  text-align: left;\n  z-index: 60;\n  background: var(--color-background);\n  border-left: var(--border);\n  box-shadow: var(--shadow2);\n}\n\n.nav {\n  position: fixed;\n  left: 0;\n  bottom: 0;\n  width: calc(100% - 24px);\n  display: flex;\n  justify-content: space-between;\n  margin: 12px;\n  user-select: none;\n  pointer-events: none;\n  z-index: 60;\n}\n\n.navLeft,\n.navRight {\n  display: flex;\n}\n\n.navLeft,\n.nav #pageComments {\n  pointer-events: all;\n}\n.navCenter {\n  display: flex;\n  justify-content: center;\n}\n\n.nav .btnMenu,\n.nav .navPreviewNext,\n.nav #pageComments,\n.nav .pageName {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 36px;\n  background: var(--color-background);\n  border-radius: 36px;\n  box-shadow: var(--shadow1);\n  align-self: center;\n  /* opacity: .9; */\n}\n\n.nav #pageComments #counter{\n    margin-left:5px;\n}\n\n.btnMenu,\n.navPreviewNext {\n  margin-right: 12px;\n}\n\n.btnMenu,\n.nav #pageComments,\n.btnPreview,\n.btnNext {\n  cursor: pointer;\n}\n\n.navPreviewNext svg,\n.btnMenu svg,\n.navPreviewNext svg {\n  padding: 6px;\n}\n\n.navPreviewNext {\n  overflow: hidden;\n}\n\n.navPreviewNext div {\n  height: 36px;\n}\n\n.nav .pageName {\n  padding: 0 12px;\n}\n.nav #pageComments {\n    padding: 0 12px;\n}\n\n.btnMenu:hover,\n.nav #pageComments:hover,\n.btnPreview:hover,\n.btnNext:hover {\n  background: var(--color-hover);\n  transition: background .2s;\n  opacity: 1;\n}\n\n.btnMenu:active,\n.nav #pageComments:active,\n.btnPreview:active,\n.btnNext:active {\n  background: var(--color-active);\n  transform: scale(.9);\n  transition: transform .24s;\n  opacity: 1;\n}\n\n.btnPreview:active,\n.btnNext:active {\n  border-radius: 50%;\n}\n\n.nav .disabled {\n  opacity: .5;\n}\n\n/*** MENU ***/\n.menu {\n  position: absolute;\n  bottom: 48px;\n  display: flex;\n  flex-direction: column;\n  width: 240px;\n  padding: 16px 0;\n  background: var(--color-background);\n  border: var(--border);\n  box-shadow: var(--shadow2);\n  border-radius: var(--border-radius);\n  visibility: hidden;\n  opacity: 0;\n  transform: translateY(-8px);\n  transition: all .12s;\n}\n\n.menu.active {\n  opacity: 1;\n  visibility: visible;\n  transform: translateY(0px);\n  transition: all .12s;\n}\n\n.item {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 0 20px;\n  height: 44px;\n  cursor: pointer;\n  background: var(--color-background);\n}\n\n.item:hover {\n  background: var(--color-hover);\n}\n\n.item:active {\n  background: var(--color-active);\n  transition: background .12s;\n}\n\n.item svg {\n  margin-right: 16px;\n}\n\n.item span {\n  flex-grow: 1;\n  text-align: start;\n}\n\n.item .tips {\n  font-size: var(--font-small);\n  color: var(--color-secondary);\n}\n\nhr {\n  display: block;\n  height: 1px;\n  border: 0;\n  border-top: 1px solid var(--color-border);\n  margin: 16px 0 16px 20px;\n  padding: 0;\n}\n\n#sidebar hr {\n  margin: 16px 0 0 20px;\n}\n\narea {\n  cursor: pointer;\n}\n\n/*** OVERLAYS ***/\n.divPanel {\n  position: absolute;\n  z-index: 3;\n}\n\n/*** FIXED PANELS ***/\n.fixedPanelTop {\n    z-index: 14;\n  }\n\n.fixedPanel1 {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    margin: 0 auto;\n    pointer-events: none;\n    user-select: none;\n}\n\n.fixedPanel {\n    position: fixed;\n    z-index: 12;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixedPanelFloat {\n    position: fixed;\n    z-index: 13;\n    overflow: hidden;\n    text-align: center;\n}\n\n.fixed_back {\n  z-index: 6;\n  background: rgba(100, 100, 100, 255);\n  color: #f1f1f1;\n  text-align: center;\n  margin: 0 auto;\n  position: absolute;\n}\n\n/*\n\n#fixed_left{\n\tposition: fixed;\n\tz-index: 10;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\toverflow: hidden;\n\ttext-align: center;\n\tmargin: 0 auto;\n}\n\n#fixed_left_back{\n\tz-index: 5;\n\tbackground: rgba(100,100,100,255);\n\tcolor: #f1f1f1;\n\ttext-align: center;\n\tmargin: 0 auto;\n\tposition: absolute;\n}\n**/\n/*** LOADING INDICATOR ***/\n#nav #loading {\n    width: 40px;\n  }\n\n.lds-ring {\n    display: inline-block;\n    position: relative;\n    width: 36px;\n    height: 28px;\n    margin-top: 4px;\n}\n.lds-ring div {\n    box-sizing: border-box;\n    display: block;\n    position: absolute;\n    width: 24px;\n    height: 24px;\n    border: 2px solid var(--color-secondary) ;\n    border-radius: 50%;\n    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    border-color: var(--color-secondary) transparent transparent transparent;        \n}\n.lds-ring div:nth-child(1) {\n    animation-delay: -0.45s;\n}\n.lds-ring div:nth-child(2) {\n    animation-delay: -0.3s;\n}\n.lds-ring div:nth-child(3) {\n    animation-delay: -0.15s;\n}\n@keyframes lds-ring {\n    0% {\n        transform: rotate(0deg);\n    }\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n.version-screen-div {\n  cursor: pointer;\n}\n\n/*@media only screen and (max-width: 1024px) and (min-width: 768px) {\n\n.grid-cell{ width: calc(100%/2 - 48px);}\n\n}\n\n@media only screen and (max-width: 768px) and (min-width: 320px) {\n\t.grid-cell{ width: 100%; }\n}*/\n\n@media (prefers-color-scheme: dark) {\n\n  /* Dark mode styles go here! */\n  :root {\n    --color-primary: #E0E1E1;\n    --color-secondary: #989EA5;\n    --color-accent: #568AF2;\n    --color-hover: #444549;\n    --color-hover-bright: rgba(255,255,255,.12);\n    --color-active: #282828;\n    --color-border: var(--color-active);\n    --color-link:    #989EA5;\n    --color-background: #35363A;\n    --color-background-gallery: var(--color-background);\n    --color-background-gallery-90: rgba(53,54,58,0.90);\n  }\n\n}\n\n@media (prefers-color-scheme: light) {\n  /* Light styles go here! */\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/AbstractViewer.js",
    "content": "\nclass AbstractViewer {\n    constructor() {\n        // common viewer settings, can be changed in child constructors\n        this.isSidebarChild = true\n        this.blockMainNavigation = false\n        this.enableTopNavigation = false\n        this.alwaysHandlePageChanged = false\n\n        // internal viewer props, can be read by child \n        this.inited = false\n        this.visible = false\n\n        //\n        viewer.allChilds.push(this)\n    }\n\n\n    // called by Viewer\n    pageChanged() {\n\n    }\n\n    // called by viewer\n    viewerResized() {\n\n    }\n\n    hide() {\n        viewer.hideChild()\n    }\n\n    show() {\n        viewer.showChild(this)\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    handleKeyDown(jevent) {\n        return false\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        return false\n    }\n    onContentClick() {\n        return false\n    }\n\n\n\n    isVisible() {\n        return this.visible\n    }\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n    _showSelf() {\n        this.visible = true\n    }\n\n    _hideSelf() {\n        this.visible = false\n    }\n\n\n\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/CommentsViewer.js",
    "content": "\nlet commentsViewer = null;\n\nclass CommentsViewer extends AbstractViewer {\n    constructor() {\n        super()\n\n        this.alwaysHandlePageChanged = true\n\n        this.comments = null\n        this.inputFocused = false\n        commentsViewer = this\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        this._showLoadingMessage()\n        this._showComments();\n\n        this.inited = true\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        $('#comments_viewer').addClass(\"hidden\")\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").show()\n        this.comments.hideViewer()\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (67 == event.which) { // c\n            // Key \"C\" activates self\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    pageChanged() {\n        this._showCommentCounter()\n        if (!this.visible) {\n            return\n        }\n        if (!this.inited) return this.initialize();\n        comments.reloadComments()\n    }\n\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!comments.inputFocused && 67 == event.which) { // key \"g\"\n            // Key \"G\" deactivates Symbol Viewer\n            this.toggle()\n        } else if (comments.inputFocused) {\n            return true\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n    /////////////////////////////////////////////////\n\n    _showCommentCounter() {\n        var formData = new FormData();\n\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=getInfo\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                commentsViewer.updateCommentCounter(result.data.commentsTotal)\n            } else {\n                console.log(result.message);\n            }\n            return\n\n        };\n        xhr.send(formData);\n    }\n\n    updateCommentCounter(total) {\n        var div = $('#nav #pageComments #counter')\n        if (total > 0) {\n            div.html(total);\n            div.show()\n        } else {\n            div.hide()\n        }\n    }\n\n    _showComments() {\n        var formData = new FormData();\n        //\n        var uid = window.localStorage.getItem(\"comments-uid\")\n        var sid = window.localStorage.getItem(\"comments-sid\")\n        if (null != uid && null != sid) {\n            formData.append(\"uid\", uid);\n            formData.append(\"sid\", sid);\n        }\n        //\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"POST\", story.commentsURL + \"&cmd=buildFullHTML\", true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onload = function () {\n            var result = JSON.parse(this.responseText);\n            //\n            if (\"ok\" == result.status) {\n                $('#comments_viewer_content').html(result.data);\n            } else {\n                $('#comments_viewer_content').html(result.message);\n            }\n            return\n\n        };\n        xhr.send(formData);\n    }\n\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#comments_viewer').removeClass(\"hidden\")\n        super._showSelf()\n        //\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n        viewer.currentPage.linksDiv.children(\"a\").hide()\n        //\n        if (this.comments) this.comments.showViewer()\n    }\n\n    _showLoadingMessage() {\n        $(\"#comments_viewer_content\").html(\"Loading...\")\n    }\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/GalleryViewer.js",
    "content": "const GALLERY_TOP_MARGIN = 80\nconst GALLERY_LEFTRIGH_MARGIN = 40\n\n\nclass GalleryViewerMapLink {\n    constructor(index, link, spage, dpage) {\n        this.index = index\n        this.link = link\n        this.spage = spage\n        this.dpage = dpage\n        //\n        if (undefined == spage.slinks) spage.slinks = []\n        if (undefined == dpage.dlinks) dpage.dlinks = []\n        spage.slinks.push(this)\n        dpage.dlinks.push(this)\n    }\n\n    buildCode(zoom, visible) {\n        const page = this.spage\n        const dpage = this.dpage\n        const l = this.link\n        let svg = \"\"\n        //\n        var lsx = l.rect.x + l.rect.width / 2 + page.finalLeft\n        var lsy = l.rect.y + l.rect.height / 2 + page.finalTop\n        //\n        var ldx0 = dpage.finalLeft\n        var ldx1 = dpage.finalLeft + dpage.width\n        var ldy0 = dpage.finalTop\n        var ldx = 0, ldy = 0, dc = 0\n        // find the best target edge to connect with\n        if (ldx0 > lsx) {\n            ldx = ldx0\n            ldy = ldy0 + dpage.height / 2\n            lsx += l.rect.width / 2 // place start to hotspot right edge\n            dc = 50\n        } else if (lsx > ldx1) {\n            ldx = ldx1\n            ldy = ldy0 + dpage.height / 2\n            lsx -= l.rect.width / 2 // place start to hotspot left edge\n            dc = 50\n        } else if (ldy0 > lsy) {\n            lsy += l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0\n            dc = 0\n        } else {\n            lsy -= l.rect.height / 2\n            ldx = ldx0 + dpage.width / 2\n            ldy = ldy0 + dpage.height\n            dc = 0\n        }\n        //\n        //\n        const styleCode = visible ? \"\" : \" style='display:none' \"\n        svg += \"<path \" + styleCode + \"marker-end='url(#arrow)' id='l\" + this.index + \"' d='M \"\n            + Math.round(lsx * zoom) + \" \"\n            + Math.round(lsy * zoom) + \" \"\n            + \"q \"\n            + Math.round((ldx - lsx) / 2 * zoom) + \" \"\n            + dc + \" \"\n            + Math.round((ldx - lsx) * zoom) + \" \"\n            + Math.round((ldy - lsy) * zoom) + \" \"\n            + \"'/>\"\n        //\n        svg += \"<circle \" + styleCode + \"' id='s\" + this.index + \"' cx='\" + Math.round(lsx * zoom) + \"' cy='\" + Math.round(lsy * zoom) + \"' r='3'/>\"\n        //\n        return svg\n    }\n}\n\nclass GalleryViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.isSidebarChild = false\n        this.blockMainNavigation = true\n        this.enableTopNavigation = true\n        this.mapLinks = null\n        this.mapFocusedPage = null\n        this.isMapMode = false\n        //\n        const restoredMode = window.localStorage.getItem(\"galleryIsModeAbs\") == \"true\"\n        if (null != restoredMode) this.isMapMode = restoredMode\n        $(\"#gallery-header-container #right #galleryShowMap\").prop('checked', this.isMapMode);\n        //\n        this.isMapLinksVisible = false\n        if (window.localStorage.getItem(\"galleryIsLinkVisible\") == \"true\") this.isMapLinksVisible = true\n        $(\"#map-controls #map-controls-container #galleryShowMapLinks\").prop('checked', this.isMapLinksVisible);\n        //\n        this.mapZoom = 0.2\n        this.isCustomMapZoom = false\n        this.currentFullWidth = null\n\n        this.searchInputFocused = false\n    }\n\n    initialize(force = false, skipZoomUpdate = false) {\n        if (!force && this.inited) return\n\n        // adjust main container for current mode\n        if (this.isMapMode) {\n            $(\"#gallery\").removeClass(\"gallery-grid\")\n        } else {\n            $(\"#gallery\").addClass(\"gallery-grid\")\n        }\n\n        $('#gallery #grid').empty()\n        this.loadPages();\n\n        //load amount of pages to gallery title\n        document.getElementById(\"screensamount\").innerHTML = viewer.userStoryPages.length + \" screens\";\n\n        // Adjust map zoom\n        const zoomContainter = $(\"#map-controls\")\n        if (this.isMapMode) {\n            if (!skipZoomUpdate) {\n                const zoomControl = $(\".mapZoom\")\n                zoomControl.val(this.mapZoom * 100)\n            }\n            zoomContainter.show();\n        } else {\n            zoomContainter.hide()\n        }\n\n        this.inited = true\n    }\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n\n        if (27 == event.which) { // esc\n            this.toggle()\n        } else if (!this.searchInputFocused && 71 == event.which) { // key \"g\"\n            // Key \"G\" deactivates Symbol Viewer\n            this.toggle()\n        } else if (this.searchInputFocused) {\n            return true\n        } else if (76 == event.which) { // key \"l\"\n            $(\"#galleryShowMapLinks\").click()\n        } else if (77 == event.which) { // key \"m\"\n            $(\"#galleryShowMap\").click()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    mapZoomChanged(zoomValue) {\n        this.mapZoom = zoomValue / 200\n        this.isCustomMapZoom = true\n        this.initialize(true, true)\n    }\n\n    viewerResized() {\n        if (!this.isMapMode) return\n        this.initialize(true)\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (71 == event.which) { // g\n            // Key \"G\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    /// calling from parent\n    handleURLParam(paramValue) {\n        const enableMap = \"m\" == paramValue\n        if (enableMap != this.isMapMode) {\n            this.enableMapMode(enableMap)\n            $(\"#galleryShowMap\").prop('checked', enableMap);\n        }\n    }\n\n    // Calling from UI\n    enableMapMode(enabled) {\n        window.localStorage.setItem(\"galleryIsModeAbs\", enabled)\n        this.isMapMode = enabled\n        this.initialize(true)\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    // Calling from UI\n    showMapLinks(visible) {\n        window.localStorage.setItem(\"galleryIsLinkVisible\", visible)\n        this.isMapLinksVisible = visible\n        this._showHideMapLinks(visible ? null : false)\n    }\n\n    // Calling from UI\n    resetMapZoom() {\n        this.isCustomMapZoom = false\n        this.initialize(true)\n    }\n\n    _showSelf() {\n        if (!this.inited || this.currentFullWidth != viewer.fullWidth) this.initialize(true)\n\n        $('#gallery-modal').removeClass('hidden');\n\n        $('#searchInput').focusin(function () {\n            viewer.galleryViewer.searchInputFocused = true\n        })\n        $('#searchInput').focusout(function () {\n            viewer.galleryViewer.searchInputFocused = false\n        })\n        $('#searchInput').focus()\n\n        super._showSelf()\n\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n    _hideSelf() {\n        $('#gallery-modal').addClass('hidden');\n        super._hideSelf()\n        viewer.refresh_url(viewer.currentPage, \"\", false)\n    }\n\n\n\n    loadPages() {\n        if (this.isMapMode) return this.loadPagesAbs()\n        viewer.userStoryPages.forEach(function (page) {\n            this.loadOnePage(page);\n        }, this);\n    }\n\n    loadPagesAbs() {\n        let groupSpace = 80\n\n        // find maximum width of Sketch page with artoards\n        let maxGroupWidth = null\n\n        story.groups.forEach(function (group) {\n            // find group pages\n            const pages = viewer.userStoryPages.filter(s => s.groupID == group.id)\n            group.pages = pages // save for below\n            if (pages.length == 0) return\n            ///\n            let left = null, right = null, top = null, bottom = null\n            pages.forEach(function (page) {\n                page.group = group\n                page.slinks = []\n                page.dlinks = []\n                //\n                if (null == top || page.y < top) top = page.y\n                if (null == left || page.x < left) left = page.x\n                if (null == right || (page.x + page.width) > right) right = page.x + page.width\n                if (null == bottom || (page.y + page.height) > bottom) bottom = page.y + page.height\n            }, this);\n            const groupWidth = right - left\n            if (null == maxGroupWidth || groupWidth > maxGroupWidth) maxGroupWidth = groupWidth\n            // // save for below\n            group.top = top\n            group.bottom = bottom\n            group.left = left\n            group.right = right\n            group.height = bottom - top\n        }, this);\n\n        // Calculate zoom to fit max width\n        if (!this.isCustomMapZoom) {\n            this.mapZoom = (viewer.fullWidth - GALLERY_LEFTRIGH_MARGIN * 2) / maxGroupWidth\n            if (this.mapZoom > 0.6) this.mapZoom = 0.6\n        }\n        this.currentFullWidth = viewer.fullWidth\n\n        // show pages using their coordinates and current zoom\n        let deltaY = 0\n        let fullHeight = 0\n        const groupTitleHeight = 40 / this.mapZoom\n        story.groups.forEach(function (group) {\n            if (group.pages.length == 0) return\n            ///\n            let top = deltaY - group.top\n            const left = group.left\n            group.finalTop = deltaY\n            top += groupTitleHeight\n            //// show group title\n            this.addMapPageGroupTitle(group)\n            //// show pages\n            group.pages.forEach(function (page) {                //\n                this.loadOnePageAbs(page, left, top);\n            }, this);\n            //\n            fullHeight += group.height\n            //\n            deltaY += group.height + groupSpace + groupTitleHeight + 30\n        }, this);\n        fullHeight = deltaY //+= groupSpace * (story.groups.length - 1)\n\n        //\n        this._buildMapLinks(maxGroupWidth, fullHeight)\n    }\n\n\n    addMapPageGroupTitle(group) {\n        let style = this._valueToStyle(\"left\", 0, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", group.finalTop, GALLERY_TOP_MARGIN)\n\n        var div = $('<div/>', {\n            id: \"g\" + group.id,\n            class: \"groupTitle\",\n            style: style,\n        });\n        div.html(group.name)\n        div.appendTo($('#gallery #grid'));\n\n    }\n\n\n    selectPage(index) {\n        this.hide()\n        viewer.goToPage(index)\n    }\n\n    mouseEnterPage(index) {\n        if (this.isMapLinksVisible) return\n        //\n        if (this.mapFocusedPage) this.mapFocusedPage.showHideGalleryLinks(false)\n        const page = story.pages[index]\n        page.showHideGalleryLinks(true)\n        this.mapFocusedPage = page\n    }\n\n\n    loadOnePage(page) {\n        var imageURI = page.image\n\n        var div = $('<div/>', {\n            id: page.index,\n            class: \"grid-cell\",\n        });\n\n        var divWrapper = $('<div/>', {\n            class: \"grid-cell-wrapper\"\n        });\n        var divMain = $('<div/>', {\n            class: \"grid-cell-main\"\n        });\n        div.click(function (e) {\n            viewer.galleryViewer.selectPage(parseInt(this.id));\n        });\n        div.appendTo($('#gallery #grid'));\n\n        var img = $('<img/>', {\n            class: \"gallery-image\",\n            alt: page.title,\n            src: encodeURIComponent(viewer.files) + '/previews/' + encodeURIComponent(imageURI),\n        });\n\n        img.appendTo(divMain);\n        divMain.appendTo(divWrapper);\n        divWrapper.appendTo(div);\n\n        var divTitle = $('<div/>', {\n            class: \"div-page-title\"\n        });\n\n        var title = $('<span/>', {\n            id: \"page-title\",\n            alt: page.title,\n            text: page.title,\n        });\n\n        title.appendTo(divTitle);\n        divTitle.appendTo(divMain);\n    }\n\n    loadOnePageAbs(page, pageLeft, pageTop) {\n        page.finalTop = pageTop + page.y\n        page.finalLeft = page.x - pageLeft\n\n        let style = this._valueToStyle(\"left\", page.finalLeft, GALLERY_LEFTRIGH_MARGIN) + this._valueToStyle(\"top\", page.finalTop, GALLERY_TOP_MARGIN)\n            + this._valueToStyle(\"width\", page.width) + this._valueToStyle(\"height\", page.height)\n        if (undefined != story.backColor) {\n            style += \"background-color:\" + story.backColor + \";\"\n        }\n\n        var div = $('<div/>', {\n            id: page.index,\n            class: \"galleryArtboardAbs\",\n            style: style,\n        });\n\n        div.click(function (e) {\n            viewer.galleryViewer.selectPage(parseInt(this.id));\n        });\n        div.mouseenter(function () {\n            viewer.galleryViewer.mouseEnterPage(this.id)\n        })\n        div.appendTo($('#gallery #grid'));\n\n        const width = Math.round(this.mapZoom * page.width)\n        // Show large image for large width\n        const previewWidth = 522\n        let src = encodeURIComponent(viewer.files)\n        if (width < previewWidth) {\n            src += '/previews/' + encodeURIComponent(page.image)\n        } else {\n            src += '/' + encodeURIComponent(story.hasRetina ? page['image2x'] : page.image)\n        }\n\n        var img = $('<img/>', {\n            class: \"gallery-map-image\",\n            alt: page.title,\n            width: width,\n            height: Math.round(this.mapZoom * page.height) + \"px\",\n            src: src\n        });\n        img.appendTo(div);\n    }\n\n    _valueToStyle(styleName, v, absDelta = 0) {\n        return styleName + \": \" + Math.round(v * this.mapZoom + absDelta) + \"px;\"\n    }\n\n    _showHideMapLinks(show) {\n        viewer.userStoryPages.forEach(function (page) {\n            page.showHideGalleryLinks(show)\n        });\n    }\n\n\n    _buildMapLinks(finalWidth, finalHeight) {\n\n        // build scene\n        let svg = \"<svg\"\n            + \" height='\" + Math.abs(Math.round(finalHeight * this.mapZoom)) + \"'\"\n            + \" width='\" + Math.abs(Math.round(finalWidth * this.mapZoom)) + \"'\"\n            + \" >\"\n        svg += `\n            <defs>\n             <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\"\n                markerWidth=\"6\" markerHeight=\"6\" fill=\"#F89000\"\n                orient=\"auto\">\n                 <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#F89000\"/>\n             </marker>\n            </defs>\n        `\n        //\n        let indexCounter = 0\n        this.mapLinks = []\n        //\n        viewer.userStoryPages.forEach(function (page) {\n            /// Show links to other pages\n            page.links.forEach(function (l) {\n                // valide destination page\n                if (l.page == page.index) return\n                const dpage = story.pages[l.page]\n                if (!dpage || \"external\" == dpage.type || \"overlay\" == dpage.type) return\n                // build SVG coode for the link\n                const link = new GalleryViewerMapLink(indexCounter++, l, page, dpage)\n                svg += link.buildCode(this.mapZoom, this.isMapLinksVisible)\n                this.mapLinks.push(link)\n            }, this)\n        }, this)\n\n        svg += \"</svg>\"\n        $('#gallery #grid').append(svg)\n    }\n\n}\n\n//Search page in gallery by page name\nfunction searchScreen() {\n    var keyword = $(\"#searchInput\").val().toLowerCase();\n    var foundScreenAmount = 0;\n\n    viewer.userStoryPages.forEach(function (page) {\n        const title = page.title.toLowerCase()\n        const div = $(\"#gallery #grid #\" + page.index)\n        const visible = title.includes(keyword) || page.image.includes(keyword)\n        if (visible) foundScreenAmount++\n        page.visibleInGallery = visible\n        //\n        //if (div.is(':visible') == visible) return\n        //\n        if (visible) {\n            div.show()\n        } else {\n            div.hide()\n        }\n    });\n\n    viewer.galleryViewer._showHideMapLinks()\n\n    //load amount of pages to gallery title\n    $(\"#screensamount\").html(foundScreenAmount + \" screens\")\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/LayersData.js",
    "content": "const SYMBOLS_DICT = {\"ux1-ui\":{\"styles\":{\"Controls/Buttons/BorderButton/Primary/Text\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Primary/Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Primary/IconDanger\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/BorderButton/Primary/IconSuccess\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-success\"]]},\"Controls/Buttons/BorderButton/Primary/IconWarning\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/BorderButton/Primary/Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Controls/Buttons/BorderButton/Danger/Text\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/BorderButton/Danger/Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/BorderButton/Danger/IconDanger\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Danger/IconSuccess\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Danger/IconWarning\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Danger/Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Controls/Buttons/BorderButton/Warning/Text\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/BorderButton/Warning/Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/BorderButton/Warning/IconDanger\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Warning/IconSuccess\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Warning/IconWarning\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/BorderButton/Warning/Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Controls/Buttons/Raised/Primary/Text\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/Raised/Primary/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Controls/Buttons/Raised/Default/Text\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/Raised/Default/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]},\"Controls/Buttons/Raised/Warning/Text\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/Raised/Warning/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-warning\"]]},\"Controls/Buttons/Raised/Disabled/Text\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled-contrast\"]]},\"Controls/Buttons/Raised/Disabled/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-disabled\"]]},\"Controls/Buttons/Raised/Danger/Text\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/Raised/Danger/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-danger\"]]},\"Controls/Buttons/Raised/Primary/Symbol\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Controls/Buttons/RaisedAlfa/Delete\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/RaisedAlfa/Default\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/RaisedAlfa/Warning\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Controls/Buttons/FlatCompact/Primary/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/FlatCompact/Default/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/FlatCompact/Warning/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/FlatCompact/Disabled/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/FlatCompact/Danger/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/FlatCompact/Primary/Symbol\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Buttons/FlatCompact/Default/Symbol\":{\"tokens\":[[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/FlatCompact/Warning/Symbol\":{\"tokens\":[[\"color\",\"@color-warning\"]]},\"Controls/Buttons/FlatCompact/Danger/Symbol\":{\"tokens\":[[\"color\",\"@color-danger\"]]},\"Controls/Buttons/FlatCompact/Disabled/Symbol\":{\"tokens\":[[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/Flat/Primary/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/Flat/Primary/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/Flat/Default/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/Flat/Default/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/Flat/Warning/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/Flat/Warning/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/Flat/Disabled/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/Flat/Disabled/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/Flat/Danger/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/Flat/Danger/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/TileButton/Primary/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/TileButton/Primary/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-primary\"]]},\"Controls/Buttons/TileButton/Default/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/TileButton/Default/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-secondary\"]]},\"Controls/Buttons/TileButton/Warning/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/TileButton/Warning/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-warning\"]]},\"Controls/Buttons/TileButton/Disabled/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/TileButton/Disabled/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text-disabled\"]]},\"Controls/Buttons/TileButton/Danger/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/TileButton/Danger/Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-danger\"]]},\"Controls/Buttons/Submit/Ok/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Controls/Buttons/Submit/Ok/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-primary\"]]},\"Controls/Buttons/Submit/Cancel/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Controls/Buttons/Submit/Cancel/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]},\"Controls/Buttons/Submit/Danger/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Controls/Buttons/Submit/Danger/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-danger\"]]},\"Controls/Buttons/Submit/OkDisabled/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-text-disabled-contrast\"]]},\"Controls/Buttons/Submit/OkDisabled/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-disabled\"]]},\"Controls/Links/Primary\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Controls/Links/Annotation\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Controls/Links/Danger\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-danger\"]]},\"Controls/Links/Warning\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-warning\"]]},\"Controls/ActionsMenu/Action/Default\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-family\",\"@font-face-regular\"]]},\"Controls/ActionsMenu/Action/Warning\":{\"tokens\":[[\"color\",\"@color-text-warning\"],[\"font-family\",\"@font-face-regular\"]]},\"Controls/ActionsMenu/Action/Danger\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-family\",\"@font-face-regular\"]]},\"Controls/ActionsMenu/Action/Disabled\":{\"tokens\":[[\"color\",\"@color-text-disabled\"],[\"font-family\",\"@font-face-regular\"]]},\"Controls/ActionsMenu/Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Controls/ActionsMenu/Separator\":{\"tokens\":[[\"background-color\",\"@color-border-separator\"]]},\"Controls/Special/IconEdit\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Controls/Special/IconInlineEdit\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-x-large\"],[\"color\",\"@color-secondary\"]]},\"Controls/Special/IconInlineEditHover\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-x-large\"],[\"color\",\"@color-primary\"]]},\"Views/Table/Item/Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextRight\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextCentered\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextDanger\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextDangerRight\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextDangerBoldRight\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextDangerSmallBoldRight\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextLinked\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextBold\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextBoldRight\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextSuccess\":{\"tokens\":[[\"color\",\"@color-text-success\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/TextSuccessRight\":{\"tokens\":[[\"color\",\"@color-text-success\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Table/Item/BottomLine\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Views/Table/Item/BackSelected\":{\"tokens\":[[\"background-color\",\"@color-background-table-row-selected\"]]},\"Views/Table/Item/DnDIcon\":{\"tokens\":[[\"color\",\"@color-neutral-light\"]]},\"Views/Table/ShowMore/Back\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"]]},\"Views/Table/Header/ColumnLabel\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Table/Header/ColumnLabelRight\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Table/Header/SortIcon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Views/Table/Header/FilterIcon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"]]},\"Views/Table/Header/FilterActiveIcon\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-medium\"]]},\"Views/Table/Header/BottomLine\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Views/Table/Header/ConfigureIcon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-large\"]]},\"Views/Table/Search/Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Views/Table/Search/Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Views/Table/Filter/Selected/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"color\",\"@color-text-primary-contrast\"]]},\"Views/Table/Filter/Unselected/Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"color\",\"@color-text-active\"]]},\"Views/Table/Filter/Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"background-color\",\"@color-background-ghost\"]]},\"Views/Table/Tab/Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Views/Table/Tab/Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Table/Tab/Selected/Top\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Views/Table/Tab/Selected/VerticalLine\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Views/Table/Tab/Selected/Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-small\"]]},\"Views/Table/Tab/Counter/Regular\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]},\"Views/Table/Tab/Counter/Warning\":{\"tokens\":[[\"color\",\"@color-text-warning\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]},\"Views/Table/Tab/Counter/Success\":{\"tokens\":[[\"color\",\"@color-text-success\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]},\"Views/Table/Tab/Counter/Danger\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]},\"Views/Table/Switcher/Selected/Icon\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text-active\"]]},\"Views/Table/Switcher/Regular/Icon\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text\"]]},\"Views/Table/Switcher/Selected/Back\":{\"tokens\":[[\"border-color\",\"@color-border-active\"]]},\"Views/Table/Switcher/Regular/Back\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"]]},\"Views/LargeNumber/Number\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"],[\"font-family\",\"@font-face-heading\"]]},\"Views/LargeNumber/Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Views/LargeNumber/Label\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]},\"Views/Home/MarketplaceTile/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"]]},\"Views/Home/MarketplaceTile/BackGroup\":{\"tokens\":[[\"box-shadow\",\"@shadow-large\"]]},\"Views/Home/Tile/Top/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Views/Home/Tile/Top/Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Views/Home/Tile/NumberH0\":{\"tokens\":[[\"font-size\",\"@font-xxxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-primary\"]]},\"Views/Home/Tile/NumberH0Disabled\":{\"tokens\":[[\"font-size\",\"@font-xxxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled\"]]},\"Views/Home/Tile/NumberH0Danger\":{\"tokens\":[[\"font-size\",\"@font-xxxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-danger\"]]},\"Views/Home/Tile/NumberH0Warning\":{\"tokens\":[[\"font-size\",\"@font-xxxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-warning\"]]},\"Views/Home/Tile/NumberH0Success\":{\"tokens\":[[\"font-size\",\"@font-xxxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-success\"]]},\"Views/Home/Tile/NumberPrefix\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Views/Home/Tile/NumberPostfix\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Views/Home/Tile/NumberH2\":{\"tokens\":[[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-primary\"]]},\"Views/Home/Tile/NumberH2Warning\":{\"tokens\":[[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-warning\"]]},\"Views/Home/Tile/NumberH2Success\":{\"tokens\":[[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-success\"]]},\"Views/Home/Tile/NumberH2Danger\":{\"tokens\":[[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-danger\"]]},\"Views/Home/Tile/NumberH2Hint\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Views/Tiles/Selectable/Regular/Text\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Views/Tiles/Selectable/Regular/Description\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-large\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Views/Tiles/Selectable/Regular/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Views/Tiles/Selectable/Selected/Text\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-active\"]]},\"Views/Tiles/Selectable/Selected/Description\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-large\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Views/Tiles/Selectable/Selected/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-primary\"]]},\"Views/Tiles/Selectable/Regular/Icon\":{\"tokens\":[[\"color\",\"@color-text\"]]},\"Views/Tiles/Selectable/Selected/Icon\":{\"tokens\":[[\"color\",\"@color-text-active\"]]},\"Views/Tiles/Unclickable/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"border-color\",\"@color-border\"],[\"box-shadow\",\"@shadow-small\"],[\"background-color\",\"@color-background-container\"]]},\"Views/Tiles/Clickable/Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Views/Text/ScreenLabel\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Views/Text/SectionLabel\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Views/Text/TileLabel\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Views/Text/TileLabelCenter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Views/Text/TileLabelH4\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Views/Text/FieldLabel\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"color\",\"@color-text-secondary\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Text/Regular\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Views/Text/Secondary\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]},\"Views/Text/Success\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-success\"]]},\"Views/Text/Warning\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-warning\"]]},\"Views/Text/Danger\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-danger\"]]},\"Views/Text/Linked\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Views/Text/RegularCenter\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Views/Text/Disabled\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-disabled\"]]},\"Views/Text/White\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Views/Text/DangerSymbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Views/Text/ScreenDescription\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"]]},\"Views/Text/TileDescription\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Views/Text/FieldDescription\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Tooltip/Default/Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Tooltip/Default/Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-neutral\"],[\"border-width\",\"@border\"]]},\"Views/Tooltip/Danger/Text\":{\"tokens\":[[\"color\",\"@color-text-danger-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Views/Tooltip/Danger/Back\":{\"tokens\":[[\"background-color\",\"@color-danger\"]]},\"Views/Status/Regular Status/Icon/Success\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-success\"]]},\"Views/Status/Regular Status/Icon/Danger\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"]]},\"Views/Status/Regular Status/Icon/Warning\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-warning\"]]},\"Views/Status/Regular Status/Icon/In Progress\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-progress\"]]},\"Views/Status/Regular Status/Icon/Disabled\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-disabled\"]]},\"Views/Status/Regular Status/Icon/Error\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Views/Status/Regular Status/Text\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]},\"Views/Status/Tile Status/Icon/Success\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-success\"]]},\"Views/Status/Tile Status/Icon/Error\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-danger\"]]},\"Views/Status/Tile Status/Icon/Warning\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-warning\"]]},\"Views/Status/Tile Status/Icon/In Progress\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-progress\"]]},\"Views/Status/Tile Status/Icon/Disabled\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-disabled\"]]},\"Views/Status/Tile Status/Text\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Status/Icon Status/Circle/Success\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-success\"]]},\"Views/Status/Icon Status/Circle/Error\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-danger\"]]},\"Views/Status/Icon Status/Circle/Warning\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Views/Status/Icon Status/Circle/In Progress\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-progress\"]]},\"Views/Status/Icon Status/Circle/Disabled\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-secondary\"]]},\"Views/Status/Icon Status/Circle/Update Availble\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Views/Status/Icon Status/Text\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Views/Status/Icon Status/Symbol\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"]]},\"Views/Gauge/Filled/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Views/Gauge/Empty/Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"]]},\"Views/GaugeCompact/Filled/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Views/GaugeCompact/Empty/Back\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Views/Dividers TileDivider\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Layout/Breadcrumb/Text\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Layout/Breadcrumb/BackSymbol\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"]]},\"Layout/Tabbar/Selected/Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-active\"]]},\"Layout/Tabbar/Regular/Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Layout/Tabbar/Selected/Line\":{\"tokens\":[[\"background-color\",\"@color-text-active\"]]},\"Layout/Tabbar/Background\":{\"tokens\":[[\"background-color\",\"@color-neutral-light\"]]},\"Layout/Content/Back\":{\"tokens\":[[\"background-color\",\"@color-background-stage\"]]},\"Layout/Modal/Back\":{\"tokens\":[[\"background-color\",\"@color-background-stage\"]]},\"Layout/Modal/Close Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"opacity\",\"@opacity-disabled\"],[\"font-size\",\"@font-xx-large\"],[\"font-family\",\"@font-face-icons\"]]},\"Layout/Bottom/Footer/Back\":{\"tokens\":[[\"background-color\",\"@footer-background\"]]},\"Layout/Bottom/Footer/Line\":{\"tokens\":[[\"background-color\",\"@footer-line-color\"]]},\"Layout/Top/Back\":{\"tokens\":[[\"background-color\",\"@top-bar-background\"],[\"box-shadow\",\"@top-bar-background-shadow\"]]},\"Layout/Top/Title\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Layout/Top/Item/Text\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Layout/Top/Item/Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Layout/Top/Item/DownIcon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@font-xx-large\"]]},\"Layout/Top/ItemCompact/Text\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@top-bar-item-text-secondary\"]]},\"Layout/Top/ItemCompactSelected/Text\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@top-bar-item-text-active\"]]},\"Layout/Top/ItemSelected/Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text-active\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Layout/Top/SecondaryText\":{\"tokens\":[[\"color\",\"@top-bar-item-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"]]},\"Layout/LeftMenu/Item/Regular-Text\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-font\"],[\"letter-spacing\",\"@side-bar-item-font-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Layout/LeftMenu/Item/Regular-Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-icon-font\"]]},\"Layout/LeftMenu/Item/Selected-Text\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-font\"],[\"letter-spacing\",\"@side-bar-item-font-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Layout/LeftMenu/Item/Selected-Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-icon-font\"]]},\"Layout/LeftMenu/Back\":{\"tokens\":[[\"background-color\",\"@side-bar-background\"]]},\"Layout/LeftMenuCollapsed/Back\":{\"tokens\":[[\"background-color\",\"@side-bar-collapsed-background\"]]},\"Layout/LeftMenuCollapsed/Item/Selected-Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-icon-font\"]]},\"Layout/LeftMenuCollapsed/Item/Regular-Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-icon-font\"]]},\"Layout/Submenu/Item/Regular-Text\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Layout/Submenu/Item/Selected-Text\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@side-bar-sub-item-text-active\"]]},\"Layout/Submenu/Item/Regular-Icon\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-family\",\"@font-face-icons\"]]},\"Layout/Submenu/Item/Selected-Icon\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@side-bar-sub-item-text-active\"]]},\"Layout/Submenu/Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"]]},\"Layout/Submenu/Back\":{\"tokens\":[[\"background-color\",\"@side-bar-sub-background\"]]},\"Layout/Wizard/Back\":{\"tokens\":[[\"background-color\",\"@side-bar-sub-background\"]]},\"Layout/Wizard/Step/Completed/Index\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Layout/Wizard/Step/Completed/Text\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"]]},\"Layout/Wizard/Step/Completed/Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"],[\"border-radius\",\"@radius-circle\"]]},\"Layout/Wizard/Step/Current/Text\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text-active\"],[\"font-weight\",\"@font-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Layout/Wizard/Step/Current/PreLine\":{\"tokens\":[[\"background-color\",\"@color-background-wizard-preline\"]]},\"Layout/Wizard/Step/Current/PostLine\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Layout/Wizard/Step/Current/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius-circle\"]]},\"Layout/Wizard/Step/Current/Index\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-weight\",\"@font-weight-bold\"],[\"font-family\",\"@font-face-regular\"]]},\"Layout/Wizard/Step/Future/Future/Text\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Layout/Wizard/Step/Future/Back\":{\"tokens\":[[\"border-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius-circle\"]]},\"Layout/Wizard/Step/Future/Index\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text-active\"],[\"font-weight\",\"@font-weight-bold\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Layout/Wizard/Step/Future/Line\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Feedback/Notification/Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Feedback/Notification/Message\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Feedback/Notification/Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Feedback/Notification/Color Panel/Success\":{\"tokens\":[[\"background-color\",\"@color-success\"]]},\"Feedback/Notification/Color Panel/Error\":{\"tokens\":[[\"background-color\",\"@color-danger\"]]},\"Feedback/Notification/Color Panel/Warning\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]},\"Feedback/Notification/Color Panel/Disabled\":{\"tokens\":[[\"background-color\",\"@color-secondary\"]]},\"Feedback/Notification/Color Panel/In Progress\":{\"tokens\":[[\"background-color\",\"@color-progress\"]]},\"Feedback/Notification/Color Panel/Update Availble\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Inputs/TextArea/Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Inputs/TextArea/TextDisabled\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text-disabled\"]]},\"Inputs/TextArea/Text-RightAlign\":{\"tokens\":[[\"color\",\"@color-text\"]]},\"Inputs/TextInput/Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Inputs/TextInput/Focused/Back\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Inputs/TextInput/FocusedError/Back\":{\"tokens\":[[\"border-color\",\"@color-border-danger\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Inputs/TextInput/Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Inputs/TextInput/Text-RightAlign\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Inputs/TextInput/TextDisabled\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text-disabled\"]]},\"Inputs/TextInput/Unit\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Inputs/TextInput/ControlSymbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Inputs/TextInput/ControlSymbol-Regular\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Inputs/TextInput/Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]},\"Inputs/TextInput/Placeholder-RightAlign\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]},\"Inputs/Checkbox/Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Inputs/Checkbox/SelectedSymbolBack\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-color\",\"@color-background-primary\"]]},\"Inputs/Checkbox/UnselectedSymbolBack\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-secondary\"]]},\"Inputs/Switch/Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Inputs/Switch/On/Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Inputs/Switch/Off/Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]},\"Inputs/Spinner/EnabledButton/Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]},\"Inputs/Spinner/DisabledButton/Back\":{\"tokens\":[[\"background-color\",\"@color-background-disabled\"]]},\"Inputs/Upload/Back\":{\"tokens\":[[\"background-color\",\"@color-background-ghost\"],[\"border-color\",\"@color-border\"]]},\"Inputs/Select/Menu/Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"box-shadow\",\"@shadow-medium\"]]},\"Other/Loading/Indicator\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Other/Loading/Message\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"]]},\"Marketplace/Views/Breadcrumb/Divider\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Marketplace/ProductPage/PriceTile/Label/Trial\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-success\"],[\"font-family\",\"@font-face-regular\"]]},\"Marketplace/ProductPage/PriceTile/Label/Promo\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-warning\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Marketplace/Regular\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Marketplace/RegularWhite\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Marketplace/RegularBold\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Marketplace/RegularBoldWhite\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Marketplace/Header\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"]]},\"Marketplace/TileLabelSmall\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"]]},\"Marketplace/TileLabelWhite\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"]]},\"Views/Badge/Dot/Primary\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]},\"Views/Badge/Dot/Warning\":{\"tokens\":[[\"background-color\",\"@color-background-warning\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]},\"Views/Badge/Dot/Danger\":{\"tokens\":[[\"background-color\",\"@color-background-danger\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]},\"Views/Badge/Dot/Secondary\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]},\"Views/Badge/Dot/Success\":{\"tokens\":[[\"background-color\",\"@color-background-success\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]}},\"colors__\":{},\"Controls/Border Right Button/Primary/Text/Regular\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Free Button/Primary/Text\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Left Button/Primary/Text\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Primary/Text + Actions/Regular\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Primary/Text + Icon/Regular\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Primary/Text + Icon + Actions/Regular\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-primary\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Primary/Icon + Actions/Regular\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Primary/Icon/Regular\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]}}},\"Controls/Border Right Button/Danger/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Border Right Button/Warning/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Border Free Button/Danger/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Border Free Button/Warning/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Border Left Button/Danger/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Border Left Button/Warning/Text\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-primary\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Border Button/Primary Dots\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Combined Shape\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval 1\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval 2\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval 3\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"_Outdated / Controls / Border Buttons / Primary\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]}}},\"_Outdated / Controls / Border Buttons / Primary Dots\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"],[\"border-radius\",\"@radius\"]]},\"Oval 1\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval 2\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval 3\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Raised Button/Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Raised Free Button/Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Raised Free Button/Primary Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"Controls/Raised Button/Primary Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"_Outdated / Controls / Raised Buttons / Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Inputs / File Uploader / Single / Uploading\":{\"layers\":{\"Rectangle 21\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"],[\"background-color\",\"@color-background-primary\"]]},\"filename\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"],[\"color\",\"@color-text\"]]}}},\"Views / Table / Filter Button - Selected\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"color\",\"@color-text-primary-contrast\"]]}}},\"Controls/Flat Compact Button/Primary Dots\":{\"layers\":{\"Oval\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy 2\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Flat Button/Primary Dots\":{\"layers\":{\"Oval\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy 2\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Alfa Button/Primary\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"_Outdated / Views / Grid2 / More Button\":{\"layers\":{\"Oval\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]},\"Oval Copy 2\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"_Outdated / Vws / Tbl / _Mr / Fltr Bttn / Slctd\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-primary\"]]}}},\"Controls/Raised Button/Default\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]}}},\"Controls/Raised Free Button/Default\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]}}},\"Controls/Raised Free Button/Default Actions\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"Controls/Raised Button/Default Actions\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"_Outdated / Controls / Raised Buttons / Default\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]}}},\"Controls/Alfa Button/Default\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]},\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"_Outdated / Controls / Alfa Buttons / Second\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]}}},\"Controls/Raised Button/Warning\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-warning\"]]},\"Warning Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"Controls/Alfa Button/Warning\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-warning\"]]},\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"_Outdated / Controls / Raised Buttons / Warning\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-warning\"]]},\"Warning Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"_Outdated / Controls / Raised Buttons / Disabled\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled-contrast\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-disabled\"]]}}},\"_Outdated / Ctrlss / Rsd Bttns / Dsbld\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-disabled-contrast\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-disabled\"]]}}},\"Controls/Raised Free Button/Delete\":{\"layers\":{\"Delete Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-danger\"]]}}},\"Controls/Raised Button/Delete\":{\"layers\":{\"Delete Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-danger\"]]}}},\"_Outdated / Controls / Raised Buttons / Delete\":{\"layers\":{\"Delete Button Label\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-danger\"]]}}},\"Controls/Alfa Button/Danger\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-danger\"]]},\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-semibold\"]]}}},\"_Outdated / Controls / Raised Buttons / Primary + Actions\":{\"layers\":{\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"Controls/Flat Compact Button/Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Flat Compact Button/Primary Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Symbol\":{\"tokens\":[[\"color\",\"@color-primary\"]]}}},\"_Outdated / Other / Notifications / Sccss- 2 Lines\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-success\"]]},\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / Sccss\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-success\"]]},\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / Failed\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / In Progress\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / Warning\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / In Progress-Lns\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Other / Notifications / Warning-Lns\":{\"layers\":{\"Action\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"Inputs / Text Input / Default + Lang\":{\"layers\":{\"Lang\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Controls/Flat Compact Button/Default\":{\"layers\":{\"Default Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]}}},\"Controls/Flat Compact Button/Default Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Symbol\":{\"tokens\":[[\"color\",\"@color-secondary\"]]}}},\"_Outdated / Inputs / Text Input / Default + Lang\":{\"layers\":{\"Lang\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Controls/Flat Compact Button/Warning Actions\":{\"layers\":{\"Warning Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Menu Icon\":{\"tokens\":[[\"color\",\"@color-warning\"]]}}},\"Controls/Flat Compact Button/Warning\":{\"layers\":{\"Warning Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]}}},\"_Outdated/Controls / Buttons / Flat / Warning\":{\"layers\":{\"Warning Button\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]}}},\"_Outdated/Controls / Buttons / Flat / Warning + Actions\":{\"layers\":{\"Primary Button\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Symbol\":{\"tokens\":[[\"color\",\"@color-warning\"]]}}},\"Controls/Flat Compact Button/Delete\":{\"layers\":{\"Error Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Flat Compact Button/Delete Actions\":{\"layers\":{\"Error Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Symbol \\\"Down\\\"\":{\"tokens\":[[\"color\",\"@color-danger\"]]}}},\"_Outdated/Controls / Buttons / Flat / Delete\":{\"layers\":{\"Delete Button\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]}}},\"Views / Table / Tabs / Defaut Selected + Menu\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Left Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Right Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-small\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Multiline / Defaut Selected + Menu\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Left Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Right Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-small\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Controls/Flat Button/Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Flat Button/Primary Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Flat Button/Default Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-secondary\"]]}}},\"Controls/Flat Button/Default\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]}}},\"Controls/Flat Button/Warning Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Flat Button/Warning\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Flat Button/Danger Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Flat Button/Danger\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Tile Button/Primary\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Tile Button/Primary Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-primary\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Tile Button/Default Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-secondary\"]]}}},\"Controls/Tile Button/Default\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-secondary\"]]}}},\"Controls/Tile Button/Warning Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Tile Button/Warning\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Tile Button/Danger Actions\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]},\"Symbol\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Tile Button/Danger\":{\"layers\":{\"Primary Button Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-danger\"]]}}},\"Controls/Submit Button/Ok\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-primary\"]]}}},\"Controls/Submit Button/Cancel\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-secondary\"]]}}},\"Controls/Submit Button/Danger\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-primary-contrast\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-weight\",\"@font-weight-bold\"],[\"line-height\",\"@line-height-large\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-danger\"]]}}},\"Inputs / File Uploader / Single / Uploading Error\":{\"layers\":{\"Upload another file.\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"filename\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Inputs / File Uploader / Single + DragNDrop / Default\":{\"layers\":{\"browse.\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Drag and drop file o\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-ghost\"],[\"border-color\",\"@color-border\"]]}}},\"Inputs / File Uploader / Multiple + DragNDrop / Default\":{\"layers\":{\"browse.\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Drag and drop file o\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-ghost\"],[\"border-color\",\"@color-border\"]]}}},\"Views / Banner / Information\":{\"layers\":{\"Banner text\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]}}},\"Controls/Link/Primary\":{\"layers\":{\"Link Label\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]}}},\"Marketplace /Order Draft Details\":{\"layers\":{\"49820\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Your order is saved\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Your order number is\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Marketplace / Order Details\":{\"layers\":{\"49820\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Thanks for your orde\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]},\"Your order is being\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Controls / Links / Primary Link\":{\"layers\":{\"Link\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]}}},\"_Outdated/Inputs / File Uploader / Single + DragNDrop / Default\":{\"layers\":{\"browse.\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Drag and drop file o\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-ghost\"],[\"border-color\",\"@color-border\"]]}}},\"_Outdated / Vws / Tbl / Fltr Bttn\":{\"layers\":{\"Text\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-primary\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"background-color\",\"@color-background-ghost\"]]}}},\"Controls/Link/Danger\":{\"layers\":{\"Link\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-danger\"]]}}},\"Views / Banner / Warning\":{\"layers\":{\"Banner text\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-warning\"]]}}},\"Controls/Link/Warning\":{\"layers\":{\"Link\":{\"tokens\":[[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"],[\"color\",\"@color-warning\"]]}}},\"Layout / Actions Menu / Action Default\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Outdated / Cntrls / Spcl /  Actns Mn / Actn / Dflt\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Outdated / Cntrls / Spcl /  Actns Mn / Actn / Wrnng\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-warning\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Outdated / Cntrls / Spcl /  Actns Mn / Actn / Dngr\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-danger\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Outdated / Cntrls / Spcl /  Actns Mn / Actn / Dsbld\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-disabled\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views / Table / _More / Filter Popup\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Views / Table / _More / Column Selector\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Layout / Actions Menu  / Backstage\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Layout / Actions Menu / Divider\":{\"layers\":{\"Separator\":{\"tokens\":[[\"background-color\",\"@color-border-separator\"]]}}},\"Outdated / Cntrls / Spcl /  Actns Mn / Dvdr\":{\"layers\":{\"Separator\":{\"tokens\":[[\"background-color\",\"@color-border-separator\"]]}}},\"Controls / Special / Icon Edit Button\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-primary\"]]}}},\"Controls / Special / Inline Edit\":{\"layers\":{\"Icon\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-x-large\"],[\"color\",\"@color-secondary\"]]}}},\"Controls / Special / Inline Edit Hover\":{\"layers\":{\"Icon\":{\"tokens\":[[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-x-large\"],[\"color\",\"@color-primary\"]]}}},\"Views / Other / Counter\":{\"layers\":{\"count\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"color\",\"@color-text-secondary\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"Marketplace / Tile / Product page / Default + link\":{\"layers\":{\"$18.80/month\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Office 365 Business\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Work anywhere, anyti\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"from\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Marketplace / Tile / Product page / Default\":{\"layers\":{\"$18.80/month\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Office 365 Business\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Work anywhere, anyti\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"from\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Marketplace / Tile / Product page / Top offer\":{\"layers\":{\"$18.80/month\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"Office 365 Business\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Work anywhere, anyti\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"from\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Views / Table / Item\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]}}},\"Views / Table / _More / Item Expandable / Parent Expanded\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]}}},\"Views / Table / _More / Item Dragable\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"DnD Icon\":{\"tokens\":[[\"color\",\"@color-neutral-light\"]]}}},\"Views / Table / _More / Item Expandable / Parent Collapsed\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]}}},\"Views / Table / _More / Item Expandable / Child\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]}}},\"_Outdated / Views / Grid / Item\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]}}},\"Views / Table / _More / Show More\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-neutral\"]]}}},\"Views / Table / _More / Header / Column Sort\":{\"layers\":{\"Sort Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]}}},\"Views / Table / _More / Header / BI Column Filter\":{\"layers\":{\"Filter Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"]]}}},\"Controls / Special / Icon Info\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"]]}}},\"Views / Table / _More / Header / BI Column Filter Active\":{\"layers\":{\"Filter Icon\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-medium\"]]}}},\"Views / Table / Header\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Select Columns - Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-large\"]]}}},\"_Outdated / Views / Grid / Header\":{\"layers\":{\"Bottom Line\":{\"tokens\":[[\"background-color\",\"@color-border-neutral\"]]},\"Select Columns - Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-large\"]]}}},\"Views / Table / Search\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Views / Table / Search\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Views / Table /  _More / Search / Active\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Views / Table /  _More / Search / With text\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Views / Table / _More / Search / Request\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Path\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"],[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"Views / Table / Filter Button\":{\"layers\":{\"Text\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"color\",\"@color-text-active\"]]},\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"background-color\",\"@color-background-ghost\"]]}}},\"Views / Table / Tabs / Default Selected\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Left Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Right Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-small\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Multiline / Default Selected\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-primary\"]]},\"Left Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Right Border\":{\"tokens\":[[\"background-color\",\"@color-border\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-small\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Default Unselected\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Multiline / Default Unselected\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Default Unselected First\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / Tabs / Multiline / Default Unselected First\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-bold\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Counter\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Views / Table / _More /  Switcher / Tiles Selected\":{\"layers\":{\"Icon\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text-active\"],[\"color\",\"@color-text\"]]},\"bg\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"border-color\",\"@color-border-neutral\"]]}}},\"Views / Table / _More / Switcher / Grid Selected\":{\"layers\":{\"Icon\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"font-size\",\"@font-medium\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@color-text-active\"],[\"color\",\"@color-text\"]]},\"bg\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"border-color\",\"@color-border-neutral\"]]}}},\"Views / Other / Large Number\":{\"layers\":{\"Number\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"],[\"font-family\",\"@font-face-heading\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"]]},\"Label\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Tiles / Home / Marketplace\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"]]},\"Group\":{\"tokens\":[[\"box-shadow\",\"@shadow-large\"]]}}},\"Marketplace / Header / Back\":{\"layers\":{\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"]]},\"Marketplace\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"]]}}},\"Marketplace / Header / Back with dropdown\":{\"layers\":{\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"]]},\"Marketplace\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"]]}}},\" Marketplace / Header / Cart counter\":{\"layers\":{\"bg red\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"]]}}},\"Views / Tiles  / UX1 / Home / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"Layout / Top / Submenu / Selected\":{\"layers\":{\"bg\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Customers list\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]}}},\"Views / Tiles / Selectable / Unselected\":{\"layers\":{\"Title\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Description\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-large\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"]]}}},\"Views / Tiles / Selectable / Disabled\":{\"layers\":{\"Title\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Description\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-large\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text\"]]}}},\"Marketplace / Tile / Category / Small\":{\"layers\":{\"Backup and Disaster\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]}}},\"Marketplace / Tile / Category / Small without icons\":{\"layers\":{\"Backup and Disaster\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]}}},\"Marketplace / Tile / Category / Big\":{\"layers\":{\"Section Label\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]},\"Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Views / Tiles / Selectable / Selected\":{\"layers\":{\"Title\":{\"tokens\":[[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-active\"]]},\"Description\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-large\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-primary\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@color-text-active\"]]}}},\"_Outdated / Experimental / Tile quick filter / Selected\":{\"layers\":{\"bg\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-primary\"]]}}},\"Views / Tiles / Unclickable / Unlabeled Tile\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"border-color\",\"@color-border\"],[\"box-shadow\",\"@shadow-small\"],[\"background-color\",\"@color-background-container\"]]}}},\"Views / Tiles / Unclickable / Labeled Tile\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"border-color\",\"@color-border\"],[\"box-shadow\",\"@shadow-small\"],[\"background-color\",\"@color-background-container\"]]}}},\"Views / Tiles / Unclickable / Labeled Tile H4\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"border-color\",\"@color-border\"],[\"box-shadow\",\"@shadow-small\"],[\"background-color\",\"@color-background-container\"]]},\"Labeled Tile\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Tiles / Home / Regular\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Views / Tiles / Home / Regular without Icon\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Views / Tiles / Clickable / Labeled Tile\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Views / Tiles / Clickable / Labeled Tile H4\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Labeled Tile\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Tiles / Clickable / Unlabeled Tile\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]}}},\"Other/Notification on Home/Warning\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Color Panel\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]}}},\"Other/Notification on Home/Warning - 2 Lines\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-radius\",\"@radius\"],[\"background-color\",\"@color-background-container\"],[\"box-shadow\",\"@shadow-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Color Panel\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]}}},\"Views / Labels / Screen Label\":{\"layers\":{\"Screen Title\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Labels / Screen Label + Breadcrumb\":{\"layers\":{\"Screen Title\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"_Outdated/ Views / Labels / Screen Label\":{\"layers\":{\"Screen Title\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xxx-large\"],[\"letter-spacing\",\"@font-xxx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Labels / Section Label\":{\"layers\":{\"Section Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"_Outdated / Views / Labels / Section Label\":{\"layers\":{\"Section Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Labels / Tile Label\":{\"layers\":{\"Tile Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Other / Modal / Tiny modal\":{\"layers\":{\"Save your cart\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Labels / Tile Label H4\":{\"layers\":{\"Tile Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-weight\",\"@font-weight-medium\"]]}}},\"Views / Labels / Field Label\":{\"layers\":{\"Field Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"color\",\"@color-text-secondary\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Experimental / Views / Large Margin\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"color\",\"@color-text-secondary\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated / Experimental / Views / Large Price\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"color\",\"@color-text-secondary\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"Inputs / File Uploader / Single / Default\":{\"layers\":{\"No file chosen.\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Inputs / File Uploader / Single / Uploaded\":{\"layers\":{\"filename\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Inputs / Select / Menu\":{\"layers\":{\"Item 1\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Item 2\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Item 3\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"box-shadow\",\"@shadow-medium\"]]}}},\"Views / Text / Default\":{\"layers\":{\"Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Views / Gauge / 0%\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Empty\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"]]}}},\"Views / Gauge / 10%\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Empty\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"]]}}},\"Views / Gauge / 100%\":{\"layers\":{\"Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]},\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]}}},\"Views / Tiles / Home / Marketplace Compact\":{\"layers\":{\"Buy a broad range of\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Controls/Link/Action link\":{\"layers\":{\"Link Label\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Layout / Top / Submenu / Default\":{\"layers\":{\"Resellers list\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Lyt / Mdl / Dlt Cnfrm\":{\"layers\":{\"Type something\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text\"]]}}},\"Views / Table / APS / Counter\":{\"layers\":{\"13 item(s) total\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Table / APS / Counter - No Search\":{\"layers\":{\"13 item(s) total\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Table / APS / Counter + Paging\":{\"layers\":{\"1-10 of 13\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Table / APS / Empty Message\":{\"layers\":{\"Message\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Table / Paging\":{\"layers\":{\"1-10 of 234\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Other / Confirm Order Modal\":{\"layers\":{\"Close Icon\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-secondary\"]]}}},\"Views / Text / Danger\":{\"layers\":{\"Text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-danger\"]]}}},\"Views / Banner / Error\":{\"layers\":{\"Banner text\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@color-text-danger\"]]}}},\"Inputs / Text Input / Error - Focused\":{\"layers\":{\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-danger\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"Inputs / Select / Error\":{\"layers\":{\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-danger\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Inputs / Text Input / Focused Error\":{\"layers\":{\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-danger\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Inputs / Select / Default / Error\":{\"layers\":{\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-danger\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"Views / Descriptions / Screen description\":{\"layers\":{\"Screen description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-large\"],[\"letter-spacing\",\"@font-large-spacing\"],[\"font-family\",\"@font-face-heading\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Views / Descriptions / Tile Description\":{\"layers\":{\"Tile description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Views / Descriptions / Field Description\":{\"layers\":{\"Field description\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Layout / Bottom / Footer\":{\"layers\":{\"Copyright © 2019 Ing\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]},\"Group\":{\"tokens\":[[\"background-color\",\"@footer-background\"]]},\"Line\":{\"tokens\":[[\"background-color\",\"@footer-line-color\"]]}}},\"Views / Tooltip / Regular\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-neutral\"],[\"border-width\",\"@border\"]]}}},\"Views / Tooltip / Error\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@color-text-danger-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-danger\"]]}}},\"Views/Status/Regular Status/Success\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-success\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]}}},\"Views/Status/Regular Status/Warning\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-warning\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]}}},\"Views/Status/Regular Status/In Progress\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-progress\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]}}},\"Views/Status/Regular Status/Disabled\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-icons\"],[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"color\",\"@color-text-disabled\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]}}},\"Views/Status/Regular Status/Error\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"color\",\"@color-text-danger\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"margin-left\",\"@icon-width\"]]}}},\"Views/Status/Tile Status/Success\":{\"layers\":{\"Oval\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-success\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Tile Status/Error\":{\"layers\":{\"Oval\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-danger\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Tile Status/Warning\":{\"layers\":{\"Oval\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-warning\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Tile Status/In Progress\":{\"layers\":{\"Oval\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-progress\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Tile Status/Disabled\":{\"layers\":{\"Oval\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"border-color\",\"@color-text-disabled\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Icon Status/Success\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-success\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/App Status/Installed\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-success\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Icon Status/Error\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-danger\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Icon Status/Warning\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated/_Outdated/thr/Ntfctn/Wrnng\":{\"layers\":{\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"_Outdated/thr/Ntfctn/Wrnng-Lns\":{\"layers\":{\"Color Panel\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-warning\"]]},\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]}}},\"Views/Status/Icon Status/In Progress\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-progress\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/Icon Status/Disabled\":{\"layers\":{\"Circle\":{\"tokens\":[[\"height\",\"@icon-width\"],[\"width\",\"@icon-width\"],[\"background-color\",\"@color-success\"],[\"background-color\",\"@color-secondary\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/App Status/Update Available\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views/Status/App Status/In Progress\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text-primary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Views / Gauge Compact / 10%\":{\"layers\":{\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Empty\":{\"tokens\":[[\"background-color\",\"@color-border\"]]}}},\"Views / Gauge Compact / 30%\":{\"layers\":{\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Empty\":{\"tokens\":[[\"background-color\",\"@color-border\"]]}}},\"Views / Gauge Compact / 50%\":{\"layers\":{\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Empty\":{\"tokens\":[[\"background-color\",\"@color-border\"]]}}},\"Views / Gauge Compact / 70%\":{\"layers\":{\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]},\"Empty\":{\"tokens\":[[\"background-color\",\"@color-border\"]]}}},\"Views / Gauge Compact / 100%\":{\"layers\":{\"Filled\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]}}},\"Views / Gauge Compact / 0%\":{\"layers\":{\"Empty\":{\"tokens\":[[\"background-color\",\"@color-border\"]]}}},\"Views / Labels / Breadcrumb\":{\"layers\":{\"Breadcrumb\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Back Symbol\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"]]}}},\"Marketplace / Views / Breadcrumb / Breadcrumb\":{\"layers\":{\"Home\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Views / Breadcrumb\":{\"layers\":{\"Breadcrumb\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Back Symbol\":{\"tokens\":[[\"color\",\"@color-text-active\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"]]}}},\"Layout / Tab Bar / Tabs / Selected \":{\"layers\":{\"Selected Tab\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text-active\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-text-active\"]]}}},\"Layout / Tab Bar / Tabs / Unselected \":{\"layers\":{\"Overview\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-semibold\"],[\"color\",\"@color-text\"]]}}},\"Layout / Tab Bar / Background\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-neutral-light\"]]}}},\"Layout / Content / Stage\":{\"layers\":{\"Background @MainBackground@\":{\"tokens\":[[\"background-color\",\"@color-background-stage\"]]}}},\"Other / Mobile / Layout / Back\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-stage\"]]}}},\"_Outdated / Lyt / Mdl / CB Mdal\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-stage\"]]},\"Close Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"opacity\",\"@opacity-disabled\"],[\"font-size\",\"@font-xx-large\"],[\"font-family\",\"@font-face-icons\"]]}}},\"Layout / Top / Details / UX1 Back Empty\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@top-bar-background\"],[\"box-shadow\",\"@top-bar-background-shadow\"]]}}},\"Layout / Top / Details / CB Back Empty\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@top-bar-background\"],[\"box-shadow\",\"@top-bar-background-shadow\"]]}}},\"Layout / Top / Submenu / Back\":{\"layers\":{\"bg\":{\"tokens\":[[\"background-color\",\"@top-bar-background\"],[\"box-shadow\",\"@top-bar-background-shadow\"]]}}},\"Other / Mobile / Layout / Top\":{\"layers\":{\"Back\":{\"tokens\":[[\"background-color\",\"@top-bar-background\"],[\"box-shadow\",\"@top-bar-background-shadow\"]]},\"Page Title\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Symbol - Notifications\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Symbol - Navigation\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Symbol - Back\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]}}},\"Layout / Top / Reseller Top\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Account ID\":{\"tokens\":[[\"color\",\"@top-bar-item-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"]]}}},\"Layout / Top / Customer Top\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Account ID\":{\"tokens\":[[\"color\",\"@top-bar-item-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"]]}}},\"Layout / Top / Item\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]}}},\"Layout / Top / Item + Descr\":{\"layers\":{\"Text\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Subtext\":{\"tokens\":[[\"color\",\"@top-bar-item-text-secondary\"],[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"]]}}},\"Layout / Top / Item + Menu\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-font\"],[\"letter-spacing\",\"@top-bar-item-font-spacing\"],[\"font-weight\",\"@top-bar-item-weight\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Symb - Down\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@font-xx-large\"]]}}},\"Layout / Top / Compact Item\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text\"],[\"font-size\",\"@top-bar-item-icon-font\"]]},\"Text\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@top-bar-item-text-secondary\"]]}}},\"Layout / Top / Compact Item Selected\":{\"layers\":{\"Text\":{\"tokens\":[[\"font-size\",\"@font-x-small\"],[\"letter-spacing\",\"@font-x-small-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@top-bar-item-text-active\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@top-bar-item-text-active\"],[\"font-size\",\"@top-bar-item-icon-font\"]]}}},\"Layout / Menu Expanded / Item Regular\":{\"layers\":{\"Item Label\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-font\"],[\"letter-spacing\",\"@side-bar-item-font-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-icon-font\"]]}}},\"Layout / Menu Expanded / Item Selected\":{\"layers\":{\"Item Label\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-font\"],[\"letter-spacing\",\"@side-bar-item-font-spacing\"],[\"font-weight\",\"@font-weight-medium\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-icon-font\"]]}}},\"Layout / Expanded Menu / Background\":{\"layers\":{\"Rectangle\":{\"tokens\":[[\"background-color\",\"@side-bar-background\"]]}}},\"Layout / Menu Collapsed / Background\":{\"layers\":{\"Rectangle\":{\"tokens\":[[\"background-color\",\"@side-bar-collapsed-background\"]]}}},\"Layout / Menu Collapsed / Item Selected\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text-active\"],[\"font-size\",\"@side-bar-item-icon-font\"]]}}},\"Layout / Menu Collapsed / Item Regular\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-item-text\"],[\"font-size\",\"@side-bar-item-icon-font\"]]}}},\"Layout / Submenu / Item Regular\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-family\",\"@font-face-icons\"]]}}},\"Layout / Submenu / Item Text\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Layout / Submenu / Item Selected\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"color\",\"@side-bar-sub-item-text-active\"]]},\"Icon\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text\"],[\"font-family\",\"@font-face-icons\"],[\"color\",\"@side-bar-sub-item-text-active\"]]}}},\"Layout / Submenu / Back\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-text\"],[\"font-size\",\"@font-x-large\"],[\"letter-spacing\",\"@font-x-large-spacing\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@side-bar-sub-background\"]]}}},\"Layout / Wizard / Back\":{\"layers\":{\"Rectangle\":{\"tokens\":[[\"background-color\",\"@side-bar-sub-background\"]]}}},\"Layout / Wizard / Steps / Completed\":{\"layers\":{\"Ok Symbol\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"]]},\"Circle\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"],[\"border-radius\",\"@radius-circle\"]]}}},\"Layout / Wizard / Steps / Current\":{\"layers\":{\"Step Label\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text-active\"],[\"font-weight\",\"@font-medium\"],[\"font-weight\",\"@font-weight-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Circle\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius-circle\"]]},\"Step Index\":{\"tokens\":[[\"color\",\"@color-text-primary-contrast\"],[\"font-weight\",\"@font-weight-bold\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Layout / Wizard / Lines / Pre-Current\":{\"layers\":{\"Rectangle 4\":{\"tokens\":[[\"background-color\",\"@color-background-wizard-preline\"]]}}},\"Layout / Wizard / Lines / Post-Current\":{\"layers\":{\"Rectangle 4 Copy\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]}}},\"Layout / Wizard / Steps / Future\":{\"layers\":{\"Circle\":{\"tokens\":[[\"border-color\",\"@color-background-primary\"],[\"border-radius\",\"@radius-circle\"]]},\"Step Index\":{\"tokens\":[[\"color\",\"@side-bar-sub-item-text-active\"],[\"font-weight\",\"@font-weight-bold\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Layout / Wizard / Lines / Future\":{\"layers\":{\"Rectangle 4 Copy 4\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]}}},\"Other / Notification / Warning\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Color Panel\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]}}},\"Other / Notification / With Action / Warning\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Color Panel\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]}}},\"Other / Notification / With Action / Warning 2\":{\"layers\":{\"Background\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-radius\",\"@radius\"],[\"box-shadow\",\"@shadow-xx-large\"]]},\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-weight\",\"@font-weight-semibold\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Description\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-weight\",\"@font-weight-normal\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-large\"]]},\"Color Panel\":{\"tokens\":[[\"background-color\",\"@color-warning\"]]}}},\"Inputs / Text Area / Default\":{\"layers\":{\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]}}},\"Inputs / Text Area / Default + Tools\":{\"layers\":{\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]}}},\"Inputs / Text Input / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Text Input / Default Right\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Text Input / Default + Unit\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Unit\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Combo / Default\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Select / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Dropdown Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Password / Filled\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Symbol - Show/Hide\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Symbol - Generate\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"_Outdated / Inputs / Text Input / Default + Unit\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Unit\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Inputs / Text Input / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Inputs / Text Input / Default Right\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Inputs / Combo / Default\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Search Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"_Outdated / Inputs / Select / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Dropdown Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"_Outdated / Inputs / Date / Single\":{\"layers\":{\"Back\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Placeholder\":{\"tokens\":[[\"color\",\"@color-text-disabled-contrast\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"]]}}},\"_Outdated / Inputs / Text Area / Default\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Inputs / Password / Filled\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]},\"Symbol - Show/Hide\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Symbol - Generate\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Inputs / Text Input / Default - Focused\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"Inputs / Text Input / Default - Focused Empty\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]}}},\"_Outdated / Inputs / Text Input / Default / Focused\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]},\"Value\":{\"tokens\":[[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-regular\"],[\"font-weight\",\"@font-weight-normal\"],[\"color\",\"@color-text\"]]}}},\"_Outdated / Inputs / Text Input / Default / Focused Empty\":{\"layers\":{\"Background\":{\"tokens\":[[\"border-color\",\"@color-border-active\"],[\"background-color\",\"@color-background-container\"],[\"border-width\",\"@border-medium\"],[\"border-radius\",\"@radius\"]]}}},\"Inputs / Spinner / Enabled + Unit\":{\"layers\":{\"Unit\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Inputs / Widget List  / Last\":{\"layers\":{\"Add Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]},\"Delete Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Inputs / Widget List  / None-last\":{\"layers\":{\"Delete Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Controls / Icon\":{\"layers\":{\"Icon\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Marketplace / Inputs / Search / Default\":{\"layers\":{\"\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Marketplace / Inputs / Search / Categories filter\":{\"layers\":{\"\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Inputs / Date / Single\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"color\",\"@color-text-secondary\"]]}}},\"Inputs / Checkbox / Selected\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Checkbox / Selected Disabled\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Checkbox / Indeterminate\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Radio / Selected\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Radio / Unselected\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Radio / Disabled\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]},\"Oval\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-secondary\"]]}}},\"Inputs / Checkbox / Unselected\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Checkbox / Disabled\":{\"layers\":{\"Label\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"font-family\",\"@font-face-regular\"],[\"line-height\",\"@line-height-medium\"]]}}},\"Inputs / Checkbox / Unlabeled / Selected\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-color\",\"@color-background-primary\"]]}}},\"Inputs / Checkbox / Unlabeled / Indeterminate\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"border-color\",\"@color-background-primary\"]]}}},\"Inputs / Checkbox / Unlabeled / Unselected\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-secondary\"]]}}},\"Inputs / Checkbox / Unlabeled / Unselected Disabled\":{\"layers\":{\"Symbol\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border-secondary\"]]}}},\"Inputs / Switch / On\":{\"layers\":{\"ON\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"OFF\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"]]}}},\"Inputs / Switch / Off\":{\"layers\":{\"ON\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"OFF\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]},\"Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]}}},\"Inputs / Spinner / Enabled\":{\"layers\":{\"Right-plus\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]},\"Minus - Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]}}},\"_Outdated / Inputs / Spinner / Enabled\":{\"layers\":{\"Right-plus\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]},\"Minus - Back\":{\"tokens\":[[\"background-color\",\"@color-background-secondary\"]]}}},\"_Outdated / Inputs / Select / Menu\":{\"layers\":{\"Rectangle\":{\"tokens\":[[\"background-color\",\"@color-background-container\"],[\"border-color\",\"@color-border\"],[\"border-width\",\"@border-medium\"],[\"box-shadow\",\"@shadow-medium\"]]}}},\"Other / Loading\":{\"layers\":{\"Indicator\":{\"tokens\":[[\"color\",\"@color-primary\"]]},\"Message\":{\"tokens\":[[\"color\",\"@color-text\"],[\"font-size\",\"@font-xx-large\"],[\"letter-spacing\",\"@font-xx-large-spacing\"],[\"line-height\",\"@line-height-medium\"],[\"font-family\",\"@font-face-heading\"]]}}},\"Marketplace / Views / Breadcrumb / Divider\":{\"layers\":{\"Divider\":{\"tokens\":[[\"color\",\"@color-text-secondary\"],[\"font-size\",\"@font-small\"],[\"letter-spacing\",\"@font-small-spacing\"],[\"font-family\",\"@font-face-regular\"]]}}},\"Marketplace / Tile / Product page / Promo \":{\"layers\":{\"Simplify the process\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"from\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]},\"$5.00/month\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]}}},\"Marketplace / Inputs / Search / Categories menu\":{\"layers\":{\"Shop by сategory\":{\"tokens\":[[\"font-family\",\"@font-face-regular\"],[\"font-size\",\"@font-medium\"],[\"letter-spacing\",\"@font-medium-spacing\"],[\"line-height\",\"@line-height-large\"]]}}},\"Views/Badge/Dot/primary\":{\"layers\":{\"circle\":{\"tokens\":[[\"background-color\",\"@color-background-primary\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]}}},\"Views/Badge/Dot/warning\":{\"layers\":{\"circle\":{\"tokens\":[[\"background-color\",\"@color-background-warning\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]}}},\"Views/Badge/Dot/danger\":{\"layers\":{\"circle\":{\"tokens\":[[\"background-color\",\"@color-background-danger\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]}}},\"Views/Badge/Dot/success\":{\"layers\":{\"circle\":{\"tokens\":[[\"background-color\",\"@color-background-success\"],[\"width\",\"@badge-dot-size\"],[\"height\",\"@badge-dot-size\"]]}}}}};\nconst TOKENS_DICT = {'ux1-ui':{\"@space-xxx-large\":\"60px\",\"@space-xx-large\":\"50px\",\"@space-x-large\":\"30px\",\"@space-large\":\"25px\",\"@space-medium\":\"20px\",\"@space-small\":\"15px\",\"@space-x-small\":\"10px\",\"@space-xx-small\":\"5px\",\"@font-xxxx-large\":\"48px\",\"@font-xxxx-large-spacing\":\"0.96px\",\"@font-xxx-large\":\"32px\",\"@font-xxx-large-spacing\":\"0.64px\",\"@font-xx-large\":\"24px\",\"@font-xx-large-spacing\":\"0.48px\",\"@font-x-large\":\"18.7px\",\"@font-x-large-spacing\":\"0.374px\",\"@font-large\":\"16px\",\"@font-large-spacing\":\"0.64px\",\"@font-medium\":\"13.3px\",\"@font-medium-spacing\":\"0.532px\",\"@font-small\":\"12px\",\"@font-small-spacing\":\"0.48px\",\"@font-x-small\":\"10.7px\",\"@font-x-small-spacing\":\"0.428px\",\"@font-xx-small\":\"9px\",\"@font-xx-small-spacing\":\"0.36px\",\"@font-weight-bold\":\"700\",\"@font-weight-semibold\":\"500\",\"@font-weight-medium\":\"500\",\"@font-weight-normal\":\"400\",\"@line-height-large\":\"1.5\",\"@line-height-medium\":\"1.25\",\"@line-height-small\":\"1.1\",\"@radius\":\"2px\",\"@radius-circle\":\"100%\",\"@radius-large\":\"10px\",\"@radius-medium\":\"3px\",\"@radius-small\":\"2px\",\"@radius-none\":\"0px\",\"@border\":\"1px\",\"@border-large\":\"2px\",\"@border-medium\":\"1px\",\"@border-none\":\"0px\",\"@font-face-heading\":\"\\\"Neue Haas Grotesk Display Std\\\", -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, \\\"Helvetica Neue\\\", Arial, sans-serif\",\"@font-face-regular\":\"\\\"Neue Haas Grotesk Display Std\\\", -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, \\\"Helvetica Neue\\\", Arial, sans-serif\",\"@font-face-mono\":\"SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\"@font-face-icons\":\"\\\"Font Awesome 5 Free\\\"\",\"@widget-large\":\"40px\",\"@widget-medium\":\"36px\",\"@widget-small\":\"32px\",\"@widget-mini\":\"28px\",\"@button-large\":\"50px\",\"@button-medium\":\"35px\",\"@button-small\":\"20px\",\"@shadow\":\"0 2px 5px rgba(0, 0, 0, 0.1)\",\"@shadow-xx-large\":\"0 0 55px rgba(0, 0, 0, 0.1)\",\"@shadow-x-large\":\"0 0 40px rgba(0, 0, 0, 0.1)\",\"@shadow-large\":\"0px 5px 10px rgba(0, 0, 0, 0.1)\",\"@shadow-medium\":\"0 2px 5px rgba(0, 0, 0, 0.1)\",\"@shadow-small\":\"none\",\"@shadow-hovered\":\"0 15px 25px 0 rgba(0, 0, 0, 0.2)\",\"@shadow-none\":\"none\",\"@opacity-disabled\":\"0.5\",\"@viewport-mobile\":\"480px\",\"@viewport-tablet\":\"768px\",\"@viewport-laptop\":\"1440px\",\"@viewport-desktop\":\"1920px\",\"@color-brand\":\"#408CF0\",\"@color-primary\":\"#408CF0\",\"@color-secondary\":\"#8C8C8C\",\"@color-black\":\"#32272F\",\"@color-gray\":\"#8C8C8C\",\"@color-white\":\"#ffffff\",\"@color-danger\":\"#d83a3a\",\"@color-warning\":\"#ff8e2a\",\"@color-success\":\"#60C060\",\"@color-progress\":\"#8c8c8c\",\"@color-highlight\":\"yellow\",\"@color-neutral-darkest\":\"#7f7f7f\",\"@color-neutral-darker\":\"#8c8c8c\",\"@color-neutral-dark\":\"#999999\",\"@color-neutral\":\"#a6a6a6\",\"@color-neutral-light\":\"#bfbfbf\",\"@color-neutral-lighter\":\"#d9d9d9\",\"@color-neutral-lightest\":\"#e5e5e5\",\"@color-background-brand\":\"#408CF0\",\"@color-background-primary\":\"#408CF0\",\"@color-background-primary-active\":\"#1270eb\",\"@color-background-primary-hovered\":\"#1270eb\",\"@color-background-secondary\":\"#8C8C8C\",\"@color-background-secondary-active\":\"#737373\",\"@color-background-secondary-hovered\":\"#737373\",\"@color-background-danger\":\"#d83a3a\",\"@color-background-warning\":\"#ff8e2a\",\"@color-background-success\":\"#60C060\",\"@color-background-progress\":\"#8c8c8c\",\"@color-background-highlight\":\"yellow\",\"@color-background-ghost\":\"transparent\",\"@color-background-ghost-active\":\"#bfbfbf\",\"@color-background-ghost-hovered\":\"#bfbfbf\",\"@color-background-disabled\":\"#F5F7FA\",\"@color-background-table-head\":\"#ffffff\",\"@color-background-table-row\":\"#ffffff\",\"@color-background-table-row-alt-1\":\"#ffffff\",\"@color-background-table-row-alt-2\":\"#e5e5e5\",\"@color-background-table-row-hovered\":\"#ffffff\",\"@color-background-table-row-selected\":\"#e5e5e5\",\"@color-background-table-column\":\"#ffffff\",\"@color-background-table-column-alt-1\":\"#E9EDF0\",\"@color-background-table-column-alt-2\":\"#ffffff\",\"@color-background-table-column-hovered\":\"#e5e5e5\",\"@color-background-table-column-selected\":\"#e5e5e5\",\"@color-background-stage\":\"#F8F9FB\",\"@color-background-container\":\"white\",\"@color-background-overlay\":\"rgba(0, 0, 0, 0.5)\",\"@color-background-wizard-preline\":\"linear-gradient(#8C8C8C, #408CF0)\",\"@color-text\":\"#32272F\",\"@color-text-brand\":\"#408CF0\",\"@color-text-primary\":\"#32272F\",\"@color-text-secondary\":\"#8c8c8c\",\"@color-text-active\":\"#408CF0\",\"@color-text-highlight\":\"yellow\",\"@color-text-disabled\":\"#a6a6a6\",\"@color-text-danger\":\"#d83a3a\",\"@color-text-warning\":\"#ff8e2a\",\"@color-text-success\":\"#60C060\",\"@color-text-progress\":\"#8c8c8c\",\"@color-border\":\"#d9d9d9\",\"@color-border-brand\":\"#408CF0\",\"@color-border-primary\":\"#408CF0\",\"@color-border-secondary\":\"#8C8C8C\",\"@color-border-neutral\":\"#d9d9d9\",\"@color-border-separator\":\"#d9d9d9\",\"@color-border-disabled\":\"#bfbfbf\",\"@color-border-active\":\"#408CF0\",\"@color-border-danger\":\"#d83a3a\",\"@color-border-warning\":\"#ff8e2a\",\"@color-border-success\":\"#60C060\",\"@color-border-progress\":\"#8c8c8c\",\"@color-text-brand-contrast\":\"#ffffff\",\"@color-text-primary-contrast\":\"#ffffff\",\"@color-text-secondary-contrast\":\"rgba(255, 255, 255, 0.7)\",\"@color-text-active-contrast\":\"#ffffff\",\"@color-text-highlight-contrast\":\"#000000\",\"@color-text-disabled-contrast\":\"rgba(0, 0, 0, 0.2)\",\"@color-text-danger-contrast\":\"#ffffff\",\"@color-text-warning-contrast\":\"#ffffff\",\"@color-text-success-contrast\":\"#ffffff\",\"@color-text-progress-contrast\":\"#ffffff\",\"@color-text-fade\":\"rgba(50, 39, 47, 0.4)\",\"@color-text-brand-fade\":\"rgba(64, 140, 240, 0.4)\",\"@color-text-primary-fade\":\"rgba(50, 39, 47, 0.4)\",\"@color-text-secondary-fade\":\"rgba(140, 140, 140, 0.4)\",\"@color-text-active-fade\":\"rgba(64, 140, 240, 0.4)\",\"@color-text-highlight-fade\":\"rgba(255, 255, 0, 0.4)\",\"@color-text-disabled-fade\":\"rgba(166, 166, 166, 0.4)\",\"@color-text-danger-fade\":\"rgba(216, 58, 58, 0.4)\",\"@color-text-warning-fade\":\"rgba(255, 142, 42, 0.4)\",\"@color-text-success-fade\":\"rgba(96, 192, 96, 0.4)\",\"@color-text-progress-fade\":\"rgba(140, 140, 140, 0.4)\",\"@top-bar-background\":\"#ffffff\",\"@top-bar-background-shadow\":\"0 0 30px rgba(71, 72, 90, 0.15)\",\"@top-bar-background-image\":\"transparent\",\"@top-bar-logo\":\"../images/panel-logo.png\",\"@cb-top-bar-logo\":\"../images/cb-panel-logo.png\",\"@top-bar-item-text\":\"#32272F\",\"@top-bar-item-text-active\":\"#408CF0\",\"@top-bar-item-text-hovered\":\"#408CF0\",\"@top-bar-item-text-secondary\":\"rgba(50, 39, 47, 0.9)\",\"@top-bar-item-text-secondary-hovered\":\"#8c8c8c\",\"@top-bar-item-background\":\"transparent\",\"@top-bar-item-background-active\":\"#ffffff\",\"@top-bar-item-background-hovered\":\"#ffffff\",\"@top-bar-item-font\":\"13.3px\",\"@top-bar-item-font-spacing\":\"0.532px\",\"@top-bar-item-weight\":\"500\",\"@top-bar-item-icon-font\":\"24px\",\"@top-bar-sub-background\":\"#ffffff\",\"@top-bar-sub-background-active\":\"#ffffff\",\"@top-bar-sub-background-hovered\":\"#408CF0\",\"@top-bar-sub-text\":\"#32272F\",\"@top-bar-sub-text-active\":\"#408CF0\",\"@top-bar-sub-text-hovered\":\"#ffffff\",\"@top-bar-sub-text-secondary\":\"#8c8c8c\",\"@top-bar-sub-text-secondary-hovered\":\"#8c8c8c\",\"@top-bar-sub-font\":\"13.3px\",\"@top-bar-sub-font-spacing\":\"0.532px\",\"@top-bar-sub-icon-font\":\"13.3px\",\"@side-bar-background\":\"#F8F9FB\",\"@side-bar-collapsed-background\":\"#ffffff\",\"@side-bar-item-text\":\"#32272F\",\"@side-bar-item-text-active\":\"#408CF0\",\"@side-bar-item-text-hovered\":\"#408CF0\",\"@side-bar-item-text-secondary\":\"#8c8c8c\",\"@side-bar-item-text-secondary-hovered\":\"#8c8c8c\",\"@side-bar-item-font\":\"13.3px\",\"@side-bar-item-icon-font\":\"24px\",\"@side-bar-item-font-spacing\":\"0.532px\",\"@side-bar-item-background\":\"transparent\",\"@side-bar-item-background-active\":\"transparent\",\"@side-bar-item-background-hovered\":\"transparent\",\"@side-bar-sub-background\":\"transparent\",\"@side-bar-sub-text\":\"#32272F\",\"@side-bar-sub-item-background\":\"transparent\",\"@side-bar-sub-item-background-active\":\"transparent\",\"@side-bar-sub-item-background-hovered\":\"transparent\",\"@side-bar-sub-item-text\":\"#32272F\",\"@side-bar-sub-item-text-active\":\"#408CF0\",\"@side-bar-sub-item-text-hovered\":\"#408CF0\",\"@side-bar-sub-item-text-secondary\":\"#8c8c8c\",\"@side-bar-sub-item-text-secondary-hovered\":\"#8c8c8c\",\"@side-bar-sub-item-font\":\"13.3px\",\"@side-bar-sub-item-font-spacing\":\"0.532px\",\"@side-bar-sub-item-icon-font\":\"13.3px\",\"@footer-background\":\"#F2F2F2\",\"@footer-line-color\":\"#CCCCCC\",\"@footer-logo\":\"../images/poweredby-logo.png\",\"@login-head-background\":\"\\\"none\\\"\",\"@login-page-background\":\"../images/login_page_bckgr.jpg\",\"@speed-slow\":\"all 0.9s ease\",\"@speed-medium\":\"all 0.2s ease\",\"@speed-fast\":\"all 0.1s ease\",\"@duration-long\":\"0.9s\",\"@duration-medium\":\"0.2s\",\"@duration-short\":\"0.1s\",\"@transition-fadein\":\"'.transit .fadeIn'\",\"@transition-slidein-right\":\"'.transit .slideInRight'\",\"@transition-slidein-left\":\"'.transit .slideInLeft'\",\"@transition-slidein-up\":\"'.transit .slideInUp'\",\"@transition-slidein-down\":\"'.transit .slideInDown'\",\"@transition-fadeout\":\"'.transit .fadeOut'\",\"@transition-slideout-right\":\"'.transit .slideOutRight'\",\"@transition-slideout-left\":\"'.transit .slideOutLeft'\",\"@transition-slideout-up\":\"'.transit .slideOutUp'\",\"@transition-slideout-down\":\"'.transit .slideOutDown'\",\"@badge-dot-size\":\"4px\"}};var layersData = [{\"ii\":0,\"nextLinkIndex\":0,\"artboardType\":0,\"isModal\":false,\"showShadow\":true,\"overlayOverFixed\":false,\"overlayAlsoFixed\":true,\"overlayClosePrevOverlay\":false,\"transAnimType\":0,\"overlayByEvent\":0,\"oldOverlayAlign\":0,\"overlayPin\":0,\"overlayPinHotspot\":0,\"overlayPinPage\":0,\"index\":0,\"x\":833,\"y\":536,\"w\":1026,\"h\":1114,\"c\":[{\"ii\":1,\"x\":128,\"y\":123,\"w\":756,\"h\":962,\"c\":[{\"ii\":2,\"x\":128,\"y\":123,\"w\":97,\"h\":41,\"tp\":\"Text\",\"n\":\"Page 1\",\"tx\":\"Page 1\",\"pr\":\"font-family: Open Sans;\\nfont-size: 30px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 700;\\ntext-transform: none;\\nletter-spacing: 0px;\\ncolor: #4A4A4A;\\n\"},{\"ii\":3,\"x\":128,\"y\":193,\"w\":756,\"h\":208,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #D8D8D8;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":4,\"x\":128,\"y\":877,\"w\":756,\"h\":208,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #D8D8D8;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":5,\"x\":310,\"y\":421,\"w\":246,\"h\":208,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #D8D8D8;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":6,\"x\":365,\"y\":649,\"w\":519,\"h\":208,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #D8D8D8;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":7,\"x\":128,\"y\":649,\"w\":217,\"h\":208,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #D8D8D8;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"}],\"tp\":\"Group\",\"n\":\"Content\"},{\"ii\":8,\"x\":305,\"y\":1085,\"w\":100,\"h\":20,\"s\":\"Controls / Flat Buttons / Primary + Actions\",\"b\":\"ux1-ui\",\"c\":[{\"ii\":9,\"x\":305,\"y\":1085,\"w\":100,\"h\":20,\"tp\":\"HotSpot\",\"n\":\"Link - Primary Button\"},{\"ii\":10,\"x\":396,\"y\":1085,\"w\":9,\"h\":20,\"l\":\"Controls/Buttons/Flat/Primary/Symbol\",\"b\":\"ux1-ui\",\"tp\":\"Text\",\"n\":\"Symbol\",\"tx\":\"\",\"pr\":\"font-family: Font Awesome 5 Free;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nline-height: 20px;\\ntext-transform: uppercase;\\ncolor: #1894FC;\\n\"},{\"ii\":11,\"x\":305,\"y\":1085,\"w\":86,\"h\":20,\"l\":\"Controls/Buttons/Flat/Primary/Text\",\"b\":\"ux1-ui\",\"tp\":\"Text\",\"n\":\"Primary Button Label\",\"tx\":\"Show Menu\",\"pr\":\"font-family: Graphik;\\nfont-size: 13.3px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 600;\\nline-height: 20px;\\ntext-transform: uppercase;\\ncolor: #1894FC;\\n\"}],\"tp\":\"SI\"},{\"ii\":12,\"x\":0,\"y\":0,\"w\":109,\"h\":1116,\"c\":[{\"ii\":13,\"isFixed\":true,\"overlayType\":2,\"fixedIndex\":0,\"fixedType\":\"left\",\"isFloat\":false,\"isFixedDiv\":false,\"fixedShadows\":[],\"x\":0,\"y\":0,\"w\":109,\"h\":1114,\"tp\":\"ShapePath\",\"n\":\"Fixed Left Transparent Shape\",\"pr\":\"background-color: #D8D8D8;\\nopacity: 0;\\n\"},{\"ii\":14,\"x\":0,\"y\":93,\"w\":109,\"h\":1023,\"tp\":\"ShapePath\",\"n\":\"Back\",\"pr\":\"background-color: linear-gradient(182.020deg,#FDF9F9 ,#FDFDFF ,#D8D8D8);\\n\"},{\"ii\":15,\"x\":26,\"y\":117,\"w\":68,\"h\":284,\"c\":[{\"ii\":16,\"x\":26,\"y\":117,\"w\":43,\"h\":18,\"tp\":\"Text\",\"n\":\"Page 1\",\"tx\":\"Page 1\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 700;\\ntext-transform: none;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":17,\"x\":26,\"y\":155,\"w\":42,\"h\":18,\"tp\":\"Text\",\"n\":\"Page 2\",\"tx\":\"Page 2\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":18,\"x\":26,\"y\":193,\"w\":42,\"h\":18,\"tp\":\"Text\",\"n\":\"Page 3\",\"tx\":\"Page 3\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":19,\"x\":26,\"y\":231,\"w\":68,\"h\":18,\"tp\":\"Text\",\"n\":\"Menu Item\",\"tx\":\"Menu Item\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":20,\"x\":26,\"y\":269,\"w\":68,\"h\":18,\"tp\":\"Text\",\"n\":\"Menu Item\",\"tx\":\"Menu Item\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":21,\"x\":26,\"y\":307,\"w\":68,\"h\":18,\"tp\":\"Text\",\"n\":\"Menu Item\",\"tx\":\"Menu Item\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":22,\"x\":26,\"y\":345,\"w\":68,\"h\":18,\"tp\":\"Text\",\"n\":\"Menu Item\",\"tx\":\"Menu Item\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"},{\"ii\":23,\"x\":26,\"y\":383,\"w\":68,\"h\":18,\"tp\":\"Text\",\"n\":\"Menu Item\",\"tx\":\"Menu Item\",\"pr\":\"font-family: Open Sans;\\nfont-size: 13.30000019073486px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 400;\\ntext-transform: none;\\ntext-decoration: underline;\\nletter-spacing: 0px;\\ncolor: #000000;\\n\"}],\"tp\":\"Group\",\"n\":\"Menu Items\"}],\"tp\":\"Group\",\"n\":\"Left Menu\"},{\"ii\":24,\"isFixed\":true,\"overlayType\":1,\"fixedIndex\":1,\"fixedType\":\"top\",\"isFloat\":false,\"isFixedDiv\":false,\"fixedShadows\":[{\"blur\":18,\"x\":0,\"y\":15,\"spread\":0,\"color\":\"#00000038\",\"enabled\":true}],\"x\":0,\"y\":0,\"w\":1026,\"h\":93,\"c\":[{\"ii\":25,\"x\":0,\"y\":0,\"w\":1026,\"h\":93,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #4A90E2;\\n\"},{\"ii\":26,\"x\":443,\"y\":23,\"w\":97,\"h\":41,\"tp\":\"Text\",\"n\":\"Page 2\",\"tx\":\"Page 2\",\"pr\":\"font-family: Open Sans;\\nfont-size: 30px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 700;\\ntext-transform: none;\\nletter-spacing: 0px;\\ncolor: #FFFFFF;\\n\"}],\"tp\":\"Group\",\"n\":\"Top\"},{\"ii\":27,\"isFixed\":true,\"overlayType\":0,\"fixedIndex\":2,\"fixedType\":\"float\",\"isFloat\":true,\"isFixedDiv\":false,\"fixedShadows\":[{\"blur\":20,\"x\":0,\"y\":20,\"spread\":0,\"color\":\"#00000080\",\"enabled\":true}],\"x\":810,\"y\":137,\"w\":178,\"h\":112,\"c\":[{\"ii\":28,\"x\":810,\"y\":137,\"w\":178,\"h\":112,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #F5A623;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":29,\"x\":854,\"y\":177,\"w\":93,\"h\":27,\"tp\":\"Text\",\"n\":\"Float Top\",\"tx\":\"Float Top\",\"pr\":\"font-family: Open Sans;\\nfont-size: 20px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 700;\\ntext-transform: none;\\ncolor: #FFFFFF;\\n\"}],\"tp\":\"Group\",\"n\":\"Float Top\"},{\"ii\":30,\"isFixed\":true,\"overlayType\":0,\"fixedIndex\":3,\"fixedType\":\"float\",\"isFloat\":true,\"isFixedDiv\":false,\"fixedShadows\":[{\"blur\":20,\"x\":0,\"y\":20,\"spread\":0,\"color\":\"#00000080\",\"enabled\":false}],\"x\":836,\"y\":983,\"w\":178,\"h\":112,\"c\":[{\"ii\":31,\"x\":836,\"y\":983,\"w\":178,\"h\":112,\"tp\":\"ShapePath\",\"n\":\"Rectangle\",\"pr\":\"background-color: #F5A623;\\nborder-color: #979797;\\nborder-position: inside;\\nborder-width: 1px;\\n\"},{\"ii\":32,\"x\":860,\"y\":1023,\"w\":131,\"h\":27,\"tp\":\"Text\",\"n\":\"Float Bottom\",\"tx\":\"Float Bottom\",\"pr\":\"font-family: Open Sans;\\nfont-size: 20px;\\ntext-align: left;\\nvertical-align: top;\\nfont-weight: 700;\\ntext-transform: none;\\ncolor: #FFFFFF;\\n\"}],\"tp\":\"Group\",\"n\":\"Float Bottom\"}],\"tp\":\"Artboard\",\"n\":\"Page\"}]"
  },
  {
    "path": "docs/FixedLayers/viewer/SymbolViewer.js",
    "content": "const ELEMENTINSPECTOR_LINUX_FONT_SIZES = {\n    \"8px\": \"6px\",\n    \"10px\": \"7px\",\n    \"11px\": \"8px\",\n    \"12px\": \"9px\",\n    \"13px\": \"10px\",\n    \"15px\": \"11px\",\n    \"16px\": \"12px\",\n    \"17px\": \"13px\",\n    \"19px\": \"14px\",\n    \"20px\": \"15px\",\n    \"21px\": \"16px\",\n    \"23px\": \"17px\",\n    \"24px\": \"18px\",\n    \"25px\": \"19px\",\n    \"26px\": \"20px\"\n}\n\nconst SUPPORT_TYPES = [\"Text\", \"ShapePath\", \"Image\"]\n\nclass SymbolViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.createdPages = {}\n        //this.symbolIDs = {} // layer indexes ( in pages[].layers ) of symbols\n        this.currentLib = \"\"\n        this.selected = null\n        this.showSymbols = false\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        // populate library select\n        const libSelect = $('#symbol_viewer #lib_selector')\n        libSelect.append($('<option>', {\n            value: \"\",\n            text: 'Library autoselection'\n        }));\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            libSelect.append($('<option>', {\n                value: libName,\n                text: libName\n            }));\n        }\n        libSelect.change(function () {\n            var libName = $(this).children(\"option:selected\").val();\n            viewer.symbolViewer._selectLib(libName)\n\n        })\n        //\n        const symCheck = $('#symbol_viewer_symbols')\n        symCheck.click(function () {\n            viewer.symbolViewer._setSymCheck($(this).is(':checked'))\n\n        })\n\n        this.inited = true\n    }\n\n    _setSymCheck(showSymbols) {\n        this.showSymbols = showSymbols\n        $('#lib_selector').toggle()\n        this._reShowContent()\n\n    }\n\n    // called by Viewer\n    pageChanged() {\n        this._buildSymbolLinks()\n    }\n\n    _selectLib(libName) {\n        this.currentLib = libName\n        this._reShowContent()\n    }\n\n    _reShowContent() {\n        delete this.createdPages[viewer.currentPage.index]\n\n        // remove existing symbol links\n        this.page.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        for (const panel of this.page.fixedPanels) {\n            panel.linksDiv.children(\".modalSymbolLink,.symbolLink\").remove()\n        }\n\n        // redraw inspector\n        this._showEmptyContent()\n\n        // rebuild links\n        this._buildSymbolLinks()\n    }\n\n\n    toggle() {\n        return this.visible ? this.hide() : this.show()\n    }\n\n\n\n    _hideSelf() {\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        if (isModal) {\n            $(\".modalSymbolLink\").remove()\n            delete this.createdPages[viewer.currentPage.index]\n        }\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.removeClass(\"contentSymbolsVisible\")\n\n        viewer.linksDisabled = false\n        $('#symbol_viewer').addClass(\"hidden\")\n\n        this.setSelected(null, null, null)\n\n        super._hideSelf()\n    }\n\n    onContentClick() {\n        this.setSelected(null)\n        return true\n    }\n\n    handleKeyDown(jevent) {\n\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" eactivates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (77 == event.which) { // m\n            // Key \"M\" activates Symbol Viewer\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n\n        viewer.toggleLinks(false)\n        viewer.toogleLayout(false)\n        viewer.linksDisabled = true\n\n        this._buildSymbolLinks()\n\n        var isModal = viewer.currentPage && viewer.currentPage.isModal\n        const contentDiv = isModal ? $('#content-modal') : $('#content')\n        contentDiv.addClass(\"contentSymbolsVisible\")\n\n        this._showEmptyContent()\n\n        $('#symbol_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n\n    }\n\n    _showEmptyContent() {\n        $(\"#symbol_viewer_content\").html(\"\")\n        $('#symbol_viewer #empty').removeClass(\"hidden\")\n    }\n\n    _buildSymbolLinks() {\n        this._showPage(viewer.currentPage)\n        for (let overlay of viewer.currentPage.currentOverlays) {\n            this._showPage(overlay)\n        }\n    }\n\n\n    _showPage(page) {\n        var pageIndex = page.index\n        this.pageIndex = pageIndex\n        this.page = page\n        if (!(pageIndex in this.createdPages)) {\n            const newPageInfo = {\n                layerArray: [],\n                siLayerIndexes: {}\n            }\n            // cache only standalone pages\n            this.createdPages[pageIndex] = newPageInfo\n\n            this.pageInfo = newPageInfo\n            this._create()\n        } else {\n            this.pageInfo = this.createdPages[pageIndex]\n        }\n    }\n\n\n\n    _create() {\n        const layers = layersData[this.pageIndex].c\n        if (undefined == layers) return\n        if (this.showSymbols)\n            this._processSymbolList(layers)\n        else\n            this._processLayerList(layers)\n    }\n\n    _processSymbolList(layers, isParentSymbol = false) {\n        for (var l of layers.slice().reverse()) {\n            if (this.currentLib != \"\") {\n                if (this.showSymbols && l.b) {\n                    if (l.b == this.currentLib) {\n                        this._showElement(l)\n                        continue\n                    }\n                }\n                if (!this.showSymbols && undefined != l.l) {\n                    const styleInfo = this._findStyleAndLibByStyleName(l.l)\n                    if (styleInfo && styleInfo.libName == this.currentLib) {\n                        this._showElement(l)\n                        continue\n                    }\n                }\n            } else {\n                if ((this.showSymbols && l.s != undefined) ||\n                    (!this.showSymbols && !isParentSymbol && l.l != undefined)) {\n                    this._showElement(l)\n                }\n            }\n            if (undefined != l.c)\n                this._processSymbolList(l.c, this.showSymbols && l.s != undefined)\n        }\n    }\n\n    _processLayerList(layers, sSI = null) {\n        for (var l of layers.slice().reverse()) {\n            if (SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd) {\n                this._showElement(l, sSI)\n            }\n            if (undefined != l.c)\n                this._processLayerList(l.c, \"SI\" == l.tp ? l : sSI)\n        }\n    }\n\n    _showElement(l, siLayer = null) {\n        if (l.hd) return\n\n        var currentPanel = this.page\n        l.finalX = l.x\n        l.finalY = l.y\n\n        for (const panel of this.page.fixedPanels) {\n            if (l.x >= panel.x && l.y >= panel.y &&\n                ((l.x + l.w) <= (panel.x + panel.width)) && ((l.y + l.h) <= (panel.y + panel.height))\n            ) {\n                l.finalX = l.x - panel.x\n                l.finalY = l.y - panel.y\n                currentPanel = panel\n                break\n            }\n        }\n        l.parentPanel = currentPanel\n\n\n        // Check if some layer on top of current\n        for (const pl of this.pageInfo.layerArray) {\n            if (pl.finalX <= l.finalX && pl.finalY <= l.finalY && (pl.finalX + pl.w) >= (l.finalX + l.w) && (pl.finalY + pl.h) >= (l.finalY + l.h)) return\n        }\n\n        // Check if layer is empty\n        if (\"Text\" == l.tp) {\n            if (\"\" == l.tx.trim()) return\n        }\n\n        // also push symbol instance to a list of layers (if was not aded before)\n        let indexOfSO = -1\n        if (siLayer) {\n            if (siLayer.s in this.pageInfo.siLayerIndexes) {\n                indexOfSO = this.pageInfo.siLayerIndexes[siLayer.s]\n            } else {\n                indexOfSO = this.pageInfo.layerArray.length\n                this.pageInfo.layerArray.push(siLayer)\n            }\n        }\n        //\n\n        l.infoIndex = this.pageInfo.layerArray.length\n        this.pageInfo.layerArray.push(l)\n\n        var a = $(\"<a>\", {\n            class: viewer.currentPage.isModal ? \"modalSymbolLink\" : \"symbolLink\",\n            pi: this.pageIndex,\n            li: l.infoIndex,\n            si: indexOfSO\n        })\n\n        a.click(function (event) {\n            const sv = viewer.symbolViewer\n            const pageIndex = $(this).attr(\"pi\")\n            const layerIndex = $(this).attr(\"li\")\n            const siLayerIndex = $(this).attr(\"si\")\n            const pageInfo = sv.createdPages[pageIndex]\n            let topLayer = pageInfo.layerArray[layerIndex]\n            const siLayer = siLayerIndex >= 0 ? pageInfo.layerArray[siLayerIndex] : null\n\n            sv.setSelected(event, topLayer, $(this))\n            if (!sv.selected) {\n                return false\n            }\n            const layer = sv.selected.layer // selection can be changed inside setSelected\n\n            var symName = sv.showSymbols ? layer.s : (siLayer ? siLayer.s : null)\n            var styleName = layer.l\n            var comment = layer.comment\n            var frameX = layer.finalX\n            var frameY = layer.finalY\n            var frameWidth = layer.w\n            var frameHeight = layer.h\n\n            const styleInfo = styleName != undefined ? viewer.symbolViewer._findStyleAndLibByStyleName(styleName) : undefined\n            const symInfo = symName != undefined ? viewer.symbolViewer._findSymbolAndLibBySymbolName(symName) : undefined\n\n            var info = \"\"\n            // layer.b : shared library name, owner of style or symbol\n            // layer.s : symbol name\n            // layer.l : style name\n            // layer.tp : layer type: SI, Text, ShapePath or Image\n            // siLayer : symbol master, owner of the layer            \n\n            if (symName != undefined) {\n                info = \"<hr>\" +\n                    \"<div class='block'>\" +\n                    \"<div class='label'>\" + \"Symbol\" + \"</div>\" +\n                    \"<div class='value'>\" + symName + \"</div>\"\n                const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n                    (siLayer && siLayer.b ? siLayer.b + \" (external)\" : \"Document\")\n                info += \"<div style='font-size:12px; color:var(--color-secondary)'>\" + libName + \"</div></div>\"\n\n            }\n            if (styleName != undefined) {\n                info = \"<hr>\" +\n                    \"<div class='block'>\" +\n                    \"<div class='label'>\" + \"Style\" + \"</div>\" +\n                    \"<div class='value'>\" + styleName + \"</div>\"\n                const libName = layer.b != undefined ? (layer.b + \" (external)\") :\n                    (siLayer ? siLayer.b + \" (external)\" : \"Document\")\n                info += \"<div style='font-size:12px; color:var(--color-secondary)'>\" + libName + \"</div></div>\"\n            }\n\n\n            if (comment != undefined) info +=\n                \"<hr>\" +\n                \"<div class='block'>\" +\n                \"<div class='label'>\" + \"Comment\" + \"</div>\" +\n                \"<div style='value'>\" + comment + \"</div>\" + 2\n            \"</div>\"\n\n            info += \"<hr>\" +\n                \"<div class='block twoColumn'>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"X: </span>\" + Math.round(frameX) + \"px\" +\n                \"</div>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Y: </span>\" + Math.round(frameY) + \"px\" +\n                \"</div>\" +\n                \"</div>\"\n\n            info += \"<div class='block twoColumn'>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Width: </span>\" + Math.round(frameWidth) + \"px\" +\n                \"</div>\" +\n                \"<div>\" +\n                \"<span class='label'>\" + \"Height: </span>\" + Math.round(frameHeight) + \"px\" +\n                \"</div>\" +\n                \"</div>\"\n\n\n            if (layer.pr != undefined) {\n                let tokens = null\n                if (styleInfo)\n                    tokens = styleInfo.style.tokens\n                else if (symInfo) {\n                    const foundLayer = symInfo.symbol.layers[layer.n]\n                    if (foundLayer) tokens = foundLayer.tokens\n                }\n                const decRes = sv._decorateCSS(layer, tokens, layer.b ? layer : siLayer)\n                info += decRes.css\n                if (\"Text\" == layer.tp) {\n                    if (layer.tx != undefined && layer.tx != \"\") {\n                        info += `\n                            <hr>\n                            <div class='block'>\n                            <div class='label'>Content&nbsp;<button onclick = \"copyToBuffer('sv_content')\">Copy</button>`\n                        let afterContent = \"\"\n                        let cssClass = \"\"\n                        if (decRes.styles[\"font-family\"].startsWith(\"Font Awesome 5\")) {\n                            cssClass += \"icon \"\n                            if (decRes.styles[\"font-weight\"] != \"400\") cssClass += \"solid \"\n                            const codeText = layer.tx.codePointAt(0).toString(16)\n                            afterContent = \"Unicode: \" + codeText\n                            info += `<button onclick = \"showFAIconInfo('` + codeText + `')\">Info</button>`\n                        } else {\n                            cssClass += \"code value\"\n                        }\n                        info += `</div ><div id='sv_content' class=\"` + cssClass + `\">` + layer.tx + \"</div>\"\n                        if (afterContent != \"\") {\n                            info += \"<div  class='code value'>\" + afterContent + \"</div>\"\n                        }\n                        info += \"</div>\"\n                    }\n                }\n            }\n\n            if (\"Image\" == layer.tp) {\n                const url = layer.iu\n                info += `\n                        <hr>\n                        <div class='block'>\n                        <div class='label'>Content&nbsp;<a class=\"svlink\" href=\"`+ url + `\">Download</a>`\n                let cssClass = \"code value\"\n                const width = \"100%\" //viewer.defSidebarWidth - 40\n                info += `</div ><div id='sv_content' class=\"` + cssClass + `\"><img ` + `width=\"` + width + `\" src=\"` + url + `\"/></div>`\n            }\n\n            $('#symbol_viewer #empty').addClass(\"hidden\")\n            $(\"#symbol_viewer_content\").html(info)\n            $(\"#symbol_viewer_content\").removeClass(\"hidden\")\n\n            //alert(info)\n            return false\n        })\n\n        a.prependTo(currentPanel.linksDiv)\n\n        var style = \"left: \" + l.finalX + \"px; top:\" + l.finalY + \"px; \"\n        style += \"width: \" + l.w + \"px; height:\" + l.h + \"px; \"\n        var symbolDiv = $(\"<div>\", {\n            class: \"symbolDiv\",\n        }).attr('style', style)\n        symbolDiv.mouseenter(function () {\n            viewer.symbolViewer.mouseEnterLayerDiv($(this))\n        })\n\n        symbolDiv.appendTo(a)\n    }\n\n    setSelected(event = null, layer = null, a = null, force = false) {\n        const prevClickedLayer = this.lastClickedLayer\n        this.lastClickedLayer = layer\n        //\n        const click = event && event.offsetX ? {\n            x: event.offsetX * viewer.currentZoom + layer.finalX,\n            y: event.offsetY * viewer.currentZoom + layer.finalY\n        } : {}\n        let foundLayers = []\n        this.findOtherSelection(click, null, foundLayers)\n        // reset previous selection                \n        if (this.selected) {\n            if (!force && event && layer) {\n                if (foundLayers.length > 1) {\n                    let newIndex = undefined\n                    if (undefined != prevClickedLayer && layer.ii != prevClickedLayer.ii) {\n                        // clicked on an other layer, find its index\n                        newIndex = foundLayers.indexOf(layer)\n                    } else if (undefined != this.selectedLayerIndex) {\n                        // clicked on the some layer, but \n                        // we have several overlaped objects under a cursor, so switch to the next \n                        newIndex = (this.selectedLayerIndex + 1) >= foundLayers.length ? 0 : this.selectedLayerIndex + 1\n                    } else {\n                        newIndex = foundLayers.indexOf(layer)\n                    }\n                    layer = foundLayers[newIndex]\n                    this.selectedLayerIndex = newIndex\n                }\n            }\n            this.selected.marginDivs.forEach(d => d.remove())\n            this.selected.borderDivs.forEach(d => d.remove())\n        } else {\n            this.selectedLayerIndex = foundLayers.indexOf(layer)\n        }\n\n        if (!layer) {\n            this.selected = null\n            this.lastClickedLayer = undefined\n            this.selectedLayerIndex = undefined\n            ////\n            $('#symbol_viewer #empty').removeClass(\"hidden\")\n            $(\"#symbol_viewer_content\").addClass(\"hidden\")\n            ////\n            return\n        }\n        // select new\n        this.selected = {\n            layer: layer,\n            a: $(this),\n            marginDivs: [],\n            borderDivs: [],\n        }\n        // draw left vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw right vertical border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, layer.finalX + layer.w, 0, 1, layer.parentPanel.height, \"svBorderLineDiv\")\n        )\n        // draw top horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n        // draw bottom horizonal border\n        this.selected.borderDivs.push(\n            this._drawMarginLine(layer.parentPanel, 0, layer.finalY + layer.h, layer.parentPanel.width, 1, \"svBorderLineDiv\")\n        )\n    }\n\n    findOtherSelection(click, layers, foundLayers) {\n        if (null == layers) layers = layersData[this.pageIndex].c\n        if (undefined == layers) return\n        for (var l of layers.slice().reverse()) {\n\n            if (SUPPORT_TYPES.indexOf(l.tp) >= 0 && !l.hd) {\n                if (click.x >= l.finalX && click.x <= (l.finalX + l.w) && click.y >= l.finalY && click.y <= (l.finalY + l.h)) {\n                    foundLayers.push(l)\n                }\n            }\n            if (undefined != l.c)\n                this.findOtherSelection(click, l.c, foundLayers)\n        }\n    }\n\n\n    mouseEnterLayerDiv(div) {\n        // get a layer under mouse \n        const a = div.parent()\n        const sv = viewer.symbolViewer\n        const pageIndex = a.attr(\"pi\")\n        const layerIndex = a.attr(\"li\")\n        const layer = sv.createdPages[pageIndex].layerArray[layerIndex]\n        if (!layer) return\n        // get a currently selected layer\n        if (!sv.selected) return\n        const slayer = sv.selected.layer\n        //\n        if (!slayer || !layer) return\n        // check if layers are in the same panel\n        if (slayer.parentPanel != layer.parentPanel) return\n        // remove previous margins\n        this.selected.marginDivs.forEach(d => d.remove())\n        this.selected.marginDivs = []\n        // show margins\n        this._drawTopVMargin(slayer.parentPanel, layer, slayer)\n        this._drawBottomVMargin(slayer.parentPanel, layer, slayer)\n        this._drawLeftHMargin(slayer.parentPanel, layer, slayer)\n        this._drawRightHMargin(slayer.parentPanel, layer, slayer)\n    }\n\n    _drawLeftHMargin(currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n        if (layer.finalX == slayer.finalX) {\n        } else if ((slayer.finalX + slayer.w) < layer.finalX) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalX + layer.w) < slayer.finalX) {\n            // layer bottom over slayer.top\n            x = layer.finalX + layer.w\n            hmargin = slayer.finalX - x\n        } else if (layer.finalX < slayer.finalX) {\n            // layer top over slayer.top\n            x = layer.finalX\n            hmargin = slayer.finalX - x\n        } else {\n            // layer top over slayer.top\n            x = slayer.finalX\n            hmargin = layer.finalX - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawRightHMargin(currentPanel, layer, slayer) {\n        let hmargin = 0\n        let x = null\n\n        const layerRight = layer.finalX + layer.w\n        const slayerRight = slayer.finalX + slayer.w\n\n        if (layerRight == slayerRight) {\n        } else if (layerRight < slayer.finalX) {\n            // if layer bottom over slayer bottom => don't show bottom margin                \n        } else if (slayerRight < layer.finalX) {\n            // slayer bottom over layer.top\n            x = slayerRight\n            hmargin = layer.finalX - x\n        } else if (slayerRight < layerRight) {\n            // slayer bottom over layer.bottom\n            x = slayerRight\n            hmargin = layerRight - x\n        } else {\n            // slayer bottom over layer.bottom\n            x = layerRight\n            hmargin = slayerRight - x\n        }\n\n        if (hmargin > 0) {\n            let y = this._findLayersCenterY(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, hmargin, 1, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x + hmargin / 2, y, hmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _drawTopVMargin(currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n        if (layer.finalY == slayer.finalY) {\n        } else if ((slayer.finalY + slayer.h) < layer.finalY) {\n            // if layer bottom over slayer top => don't show top margin\n        } else if ((layer.finalY + layer.h) < slayer.finalY) {\n            // layer bottom over slayer.top\n            y = layer.finalY + layer.h\n            vmargin = slayer.finalY - y\n        } else if (layer.finalY < slayer.finalY) {\n            // layer top over slayer.top\n            y = layer.finalY\n            vmargin = slayer.finalY - y\n        } else {\n            // layer top over slayer.top\n            y = slayer.finalY\n            vmargin = layer.finalY - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n    _drawBottomVMargin(currentPanel, layer, slayer) {\n        let vmargin = 0\n        let y = null\n\n        const layerBottom = layer.finalY + layer.h\n        const slayerBottom = slayer.finalY + slayer.h\n\n        if (layerBottom == slayerBottom) {\n        } else if (layerBottom < slayer.finalY) {\n            // if layer bottom over slayer bottom => don't show bottom margin        \n        } else if (slayerBottom < layer.finalY) {\n            // slayer bottom over layer.top\n            y = slayerBottom\n            vmargin = layer.finalY - y\n        } else if (slayerBottom < layerBottom) {\n            // slayer bottom over layer.bottom\n            y = slayerBottom\n            vmargin = layerBottom - y\n        } else {\n            // slayer bottom over layer.bottom\n            y = layerBottom\n            vmargin = slayerBottom - y\n        }\n\n        if (vmargin > 0) {\n            let x = this._findLayersCenterX(layer, slayer)\n            this.selected.marginDivs.push(this._drawMarginLine(currentPanel, x, y, 1, vmargin, \"svMarginLineDiv\"))\n            this.selected.marginDivs.push(this._drawMarginValue(currentPanel, x, y + vmargin / 2, vmargin, \"svMarginLineDiv\"))\n        }\n    }\n\n\n    _findLayersCenterX(l, sl) {\n        let c = l.finalX + l.w / 2\n        let sc = sl.finalX + sl.w / 2\n        return sl.finalX > l.finalX && ((sl.finalX + sl.w) < (l.finalX + l.w)) ? sc : c\n    }\n\n    _findLayersCenterY(l, sl) {\n        let c = l.finalY + l.h / 2\n        let sc = sl.finalY + sl.h / 2\n        return sl.finalY > l.finalY && ((sl.finalY + sl.h) < (l.finalY + l.h)) ? sc : c\n    }\n\n    _drawMarginLine(currentPanel, x, y, width, height, className) {\n        var style = \"left: \" + x + \"px; top:\" + y + \"px; \"\n        style += \"width: \" + width + \"px; height:\" + height + \"px; \"\n        var div = $(\"<div>\", { class: className }).attr('style', style)\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n    _drawMarginValue(currentPanel, x, y, value) {\n        const valueHeight = 20\n        const valueWidth = 30\n        var style = \"left: \" + (x - valueWidth / 2) + \"px; top:\" + (y - valueHeight / 2) + \"px; \"\n        //style += \"width: \" + valueWidth + \"px; height:\" + valueHeight + \"px; \"\n        var div = $(\"<div>\", {\n            class: \"svMarginValueDiv\",\n        }).attr('style', style)\n        //\n        div.html(\" \" + Number.parseFloat(value).toFixed(0) + \" \")\n        //\n        div.appendTo(currentPanel.linksDiv)\n        return div\n    }\n\n    _decorateCSS(layer, tokens, siLayer) {\n        let css = layer.pr\n        let result = \"\"\n        let styles = {}\n\n        result += \"<hr>\" +\n            \"<div class='block'>\" +\n            \"<div class='label'>Styles\" +\n            (1 == story.fontSizeFormat ? \" (font size adjusted for Linux)\" : \"\") +\n            \"</div > \" +\n            \"<div class='value code'>\"\n\n        css.split(\"\\n\").forEach(line => {\n            if (\"\" == line) return\n            const props = line.split(\": \", 2)\n            if (!props.length) return\n            const styleName = props[0]\n            const styleValue = props[1].slice(0, -1)\n            result += \"\" + styleName + \": \"\n            result += \"<span class='tokenName'>\"\n            //\n            styles[styleName] = styleValue\n            //\n            if (layer.cv && \"color\" == styleName) {\n                // get token for color variable\n                const tokens = this._findSwatchTokens(layer.cv)\n                if (tokens) {\n                    const tokenStr = this._decorateSwatchToken(tokens, styleValue)\n                    result += tokenStr != \"\" ? tokenStr : (styleValue + \";\")\n                }\n            } else {\n                const tokenStr = tokens != null ? this._decorateStyleToken(styleName, tokens, siLayer, styleValue) : \"\"\n                result += tokenStr != \"\" ? tokenStr : (this._formatStyleValue(styleName, styleValue) + \";\")\n            }\n            //\n            result += \"</span>\"\n            result += \"<br/>\"\n        }, this);\n\n        result += \"</div></div>\"\n        return { \"css\": result, \"styles\": styles }\n    }\n\n    _decorateSwatchToken(tokens, styleValue) {\n        const tokenName = tokens[0][1]\n        //\n        return tokenName + \";</span><span class='tokenValue'>//\" + styleValue\n    }\n\n    _decorateStyleToken(style, tokens, siLayer, styleValue) {\n        // search tokan name by style name \n        const foundTokens = tokens.filter(t => t[0] == style)\n        if (!foundTokens.length) return \"\"\n        const tokenName = foundTokens[foundTokens.length - 1][1]\n        //\n        const libName = undefined != siLayer.b ? siLayer.b : story.docName\n        const finalTokenInfo = this._findTokenValueByName(tokenName, libName, styleValue)\n        //\n        if (finalTokenInfo)\n            return finalTokenInfo[0] + \";</span><span class='tokenValue'>//\" + this._formatStyleValue(style, finalTokenInfo[1])\n        else\n            return \"\"\n    }\n\n    _formatStyleValue(style = \"font-size\", styleValue = \"13px\") {\n        if (\"font-size\" == style && 1 == story.fontSizeFormat) {\n            if (styleValue in ELEMENTINSPECTOR_LINUX_FONT_SIZES) {\n                styleValue = ELEMENTINSPECTOR_LINUX_FONT_SIZES[styleValue]\n            } else {\n                styleValue = Math.round(Number(styleValue.replace(\"px\", \"\")) / 1.333) + \"px\"\n            }\n        }\n        return styleValue\n    }\n\n\n    _showTextPropery(propName, propValue, postfix = \"\") {\n        let text = propName + \": \" + propValue + postfix + \";\"\n        return text + \"<br/>\"\n    }\n\n    // result: undefined or [tokenName,tokenValue]\n    _findTokenValueByName(tokenName, libName, styleValue = null) {\n        const lib = TOKENS_DICT[libName]\n        if (undefined == lib) return undefined\n        let value = lib[tokenName]\n        if (value != undefined || null == styleValue) return [tokenName, lib[tokenName]]\n\n        ///// try to find a token with a similar name\n        // cut magic postfix to get a string for search\n        const pos = tokenName.indexOf(\"--token\")\n        if (pos < 0) return undefined\n        styleValue = styleValue.toLowerCase()\n        const newName = tokenName.slice(0, pos)\n        // filter lib tokens by name and value\n        const similarTokens = Object.keys(lib).filter(function (n) {\n            return n.startsWith(newName) && lib[n].toLowerCase() == styleValue\n        }, this)\n        if (!similarTokens.length) return undefined\n        //\n        return [\n            similarTokens[0],\n            lib[similarTokens[0]]\n        ]\n    }\n\n    _findSymbolAndLibBySymbolName(symName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(symName in lib)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                symbol: lib[symName]\n            }\n        }\n        return undefined\n    }\n\n    _findStyleAndLibByStyleName(styleName) {\n        for (const libName of Object.keys(SYMBOLS_DICT)) {\n            const lib = SYMBOLS_DICT[libName]\n            if (!(\"styles\" in lib) || !(styleName in lib.styles)) continue\n            return {\n                lib: lib,\n                libName: libName,\n                style: lib.styles[styleName]\n            }\n        }\n        return undefined\n    }\n\n    // cv:{\n    //   sn: swatch name\n    //   ln:  lib name\n    // }\n    _findSwatchTokens(cv) {\n        const lib = SYMBOLS_DICT[cv.ln]\n        if (!lib) {\n            console.log(\"Can not find lib \" + cv.ln)\n            return null\n        }\n        //\n        const swatch = lib.colors__[cv.sn]\n        if (!swatch) {\n            console.log(\"Can not find color name \" + cv.sn)\n            return null\n        }\n\n        return swatch\n    }\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/VersionViewer.js",
    "content": "function getVersionInfoRequest() {\n    var resp = this\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['time']) {\n                viewer.versionViewer._loadData(data);\n                return true\n            }\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return false\n}\n\n\nclass VersionViewer extends AbstractViewer {\n    constructor() {\n        super()\n        this.screenDiffs = []\n        this.mode = 'diff'\n    }\n\n    initialize(force = false) {\n        if (!force && this.inited) return\n\n        // init document common data here\n        this._showLoadingMessage()\n        this._askServerTools();\n\n        this.inited = true\n    }\n\n\n    goTo(pageIndex) {\n        viewer.goToPage(pageIndex)\n    }\n\n    // delta = -1 or +1\n    switchMode(delta) {\n        const modes = ['diff', 'prev', 'new']\n        var posMode = modes.indexOf(this.mode)\n        if (undefined == posMode) return\n\n        posMode += delta\n        if (posMode < 0) posMode = modes.length - 1\n        if (posMode >= modes.length) posMode = 0\n\n        modes.forEach(function (mode, pos) {\n            var radio = $(\"#version_viewer_mode_\" + mode)\n            radio.prop('checked', pos == posMode)\n        }, this)\n\n        this.pageChanged()\n\n    }\n\n    ///////////////////////////////////////////////// called by Viewer\n\n\n    _hideSelf() {\n        this._restoreNewImages()\n        $('#version_viewer').addClass(\"hidden\")\n        if (document.location.search.includes('v')) {\n            document.location.search = \"\" // remove ?v\n        }\n        super._hideSelf()\n    }\n\n    pageChanged() {\n\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        $(\"#version_viewer_mode_diff\").prop('disabled', disabled);\n        $(\"#version_viewer_mode_new\").prop('disabled', disabled);\n        $(\"#version_viewer_mode_prev\").prop('disabled', disabled);\n        if (disabled) return\n\n        this._showCurrentPageDiffs()\n    }\n\n\n    handleKeyDownWhileInactive(jevent) {\n        const event = jevent.originalEvent\n\n        if (38 == event.which && event.shiftKey) {   // shift + up\n            viewer.increaseVersion()\n        } else if (40 == event.which && event.shiftKey) {   // shift + down\n            viewer.decreaseVersion()\n        } else if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else {\n            return super.handleKeyDownWhileInactive(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n\n    handleKeyDown(jevent) {\n        const event = jevent.originalEvent\n        var disabled = !this.screenDiffs[viewer.currentPage.getHash()]\n\n        if (86 == event.which) { // \"v\" key\n            this.toggle()\n        } else if (!disabled && 37 == event.which && event.shiftKey) {   // left + shift\n            this.switchMode(-1)\n        } else if (!disabled && 39 == event.which && event.shiftKey) {   // right + shift\n            this.switchMode(1)\n        } else if (event.shiftKey) {  //shift\n        } else {\n            return super.handleKeyDown(jevent)\n        }\n\n        jevent.preventDefault()\n        return true\n    }\n\n    /////////////////////////////////////////////////\n\n    _restoreNewImages() {\n        story.pages.forEach(function (page) {\n            if (page.srcImageObjSrc) page.imageObj.attr(\"src\", page.srcImageObjSrc)\n        })\n\n    }\n\n    _showScreens(data, showNew) {\n        var info = \"\";\n        for (const screen of data['screens_changed']) {\n            if (screen['is_new'] != showNew) continue;\n            const pageIndex = viewer.getPageIndex(screen['screen_name'], -1)\n            const page = pageIndex >= 0 ? story.pages[pageIndex] : undefined\n\n            // We don't need to show external artboards here\n            if (page && (\"external\" == page.type)) continue\n\n            var pageName = page ? page.title : screen['screen_name'];\n\n            if (page && screen['is_diff']) {\n                this.screenDiffs[screen['screen_name']] = screen\n            }\n\n            info += \"<div class='version-screen-div' onclick='viewer.versionViewer.goTo(\" + pageIndex + \")'>\";\n            info += \"<div>\";\n            info += pageName;\n            info += \"</div><div>\";\n            info += \"<img src='\" + screen['image_url'] + \"' border='0'/>\";\n            info += \"</div>\";\n            info += \"</div>\";\n        }\n        return info;\n    }\n\n\n    _showCurrentPageDiffs() {\n        const data = this.data\n        const page = viewer.currentPage\n        if (!page || !data) return false\n\n        const screen = this.screenDiffs[page.getHash()]\n        if (!screen) return false\n\n        this.mode = $(\"#version_viewer_mode_diff\").prop('checked') ? 'diff' : ($(\"#version_viewer_mode_prev\").prop('checked') ? 'prev' : 'new')\n        var newSrc = ''\n\n        // save original image srcs\n        if (!page.srcImageObjSrc) page.srcImageObjSrc = page.imageObj.attr(\"src\")\n\n        if ('diff' == this.mode) {\n            newSrc = data['journals_path'] + '/' + data['dir'] + \"/diffs/\" + screen['screen_name'] + (story.hasRetina && viewer.isHighDensityDisplay() ? \"@2x\" : \"\") + \".\" + story.fileType\n        } else if ('new' == this.mode) {\n            if (page.imageObj.attr(\"src\") != page.srcImageObjSrc) {\n                newSrc = page.srcImageObjSrc\n            }\n        } else {\n            newSrc = \"../\" + data['down_ver'] + \"/\" + page.srcImageObjSrc\n        }\n\n\n        page.imageObj.attr(\"src\", newSrc)\n        return true\n    }\n\n    _loadData(data) {\n        var info = \"\"\n        this.data = data\n        this.screenDiffs = {}\n\n        if (data['screens_total_new']) {\n            info += \"<p class='head'>Added screens (\" + data['screens_total_new'] + \"):</p>\";\n            info += this._showScreens(data, true);\n        }\n        if (data['screens_total_changed']) {\n            info += \"<p class='head'>Changed screens (\" + data['screens_total_changed'] + \")</p>\";\n            info += this._showScreens(data, false);\n        }\n        if (!data['screens_total_new'] && !data['screens_total_changed']) {\n            info += \"No new or changed screens\"\n        }\n\n        this.pageChanged()\n\n        $(\"#version_viewer_content\").html(info)\n    }\n\n    _askServerTools() {\n        var xhr = new XMLHttpRequest();\n        xhr.open(\"GET\", story.serverToolsPath + \"version_info.php?ver=\" + story.docVersion, true);\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n        xhr.onreadystatechange = getVersionInfoRequest;\n        xhr.send(null);\n    }\n\n    _showSelf() {\n        if (!this.inited) this.initialize()\n        $('#version_viewer').removeClass(\"hidden\")\n\n        super._showSelf()\n    }\n\n    _showLoadingMessage() {\n        $(\"#version_viewer_content\").html(\"Loading...\")\n        $('#version_viewer #empty').removeClass(\"hidden\")\n    }\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/story.js",
    "content": "var story = {\n \"docName\": \"fixedlayers\",\n \"docPath\": \"P_P_P\",\n \"docVersion\": \"V_V_V\",\n \"authorName\": \"V_V_N\",\n \"authorEmail\": \"V_V_E\",\n \"commentsURL\": \"V_V_C\",\n \"hasRetina\": true,\n \"serverToolsPath\": \"/_tools/\",\n \"fontSizeFormat\": -1,\n \"fileType\": \"png\",\n \"disableHotspots\": false,\n \"zoomEnabled\": true,\n \"title\": \"FixedLayers\",\n \"layersExist\": true,\n \"centerContent\": true,\n \"highlightLinks\": false,\n \"pages\": [\n  {\n   \"id\": \"097F784C-F265-482B-82C2-10E9EA1D77B7\",\n   \"groupID\": \"AFBBAF5B-B3EA-4EEC-BF53-930806F595AF\",\n   \"index\": 0,\n   \"image\": \"page.png\",\n   \"image2x\": \"page@2x.png\",\n   \"width\": 1026,\n   \"height\": 1114,\n   \"x\": 833,\n   \"y\": 536,\n   \"title\": \"Page\",\n   \"transAnimType\": 0,\n   \"layout\": {\n    \"offset\": 90,\n    \"totalWidth\": 1170,\n    \"numberOfColumns\": 12,\n    \"columnWidth\": 77.5,\n    \"gutterWidth\": 21.81818181818182\n   },\n   \"type\": \"regular\",\n   \"fixedPanels\": [\n    {\n     \"constrains\": {\n      \"top\": false,\n      \"bottom\": false,\n      \"left\": false,\n      \"right\": false,\n      \"height\": false,\n      \"width\": false\n     },\n     \"x\": 0,\n     \"y\": 0,\n     \"width\": 109,\n     \"height\": 1114,\n     \"type\": \"left\",\n     \"index\": 0,\n     \"isFloat\": false,\n     \"isFixedDiv\": false,\n     \"divID\": \"\",\n     \"links\": [],\n     \"image\": \"page.png\",\n     \"image2x\": \"page@2x.png\"\n    },\n    {\n     \"constrains\": {\n      \"top\": true,\n      \"bottom\": false,\n      \"left\": true,\n      \"right\": true,\n      \"height\": true,\n      \"width\": false\n     },\n     \"x\": 0,\n     \"y\": 0,\n     \"width\": 1026,\n     \"height\": 93,\n     \"type\": \"top\",\n     \"index\": 1,\n     \"isFloat\": false,\n     \"isFixedDiv\": false,\n     \"divID\": \"\",\n     \"links\": [],\n     \"image\": \"page.png\",\n     \"image2x\": \"page@2x.png\",\n     \"shadow\": \"0px 15px 18px 0 #00000038 \",\n     \"shadowX\": 18\n    },\n    {\n     \"constrains\": {\n      \"top\": true,\n      \"bottom\": false,\n      \"left\": false,\n      \"right\": true,\n      \"height\": true,\n      \"width\": true\n     },\n     \"x\": 810,\n     \"y\": 137,\n     \"width\": 178,\n     \"height\": 112,\n     \"type\": \"float\",\n     \"index\": 2,\n     \"isFloat\": true,\n     \"isFixedDiv\": false,\n     \"divID\": \"\",\n     \"links\": [],\n     \"image\": \"page-2.png\",\n     \"image2x\": \"page-2@2x.png\",\n     \"shadow\": \"0px 20px 20px 0 #00000080 \",\n     \"shadowX\": 20\n    },\n    {\n     \"constrains\": {\n      \"top\": false,\n      \"bottom\": true,\n      \"left\": false,\n      \"right\": true,\n      \"height\": false,\n      \"width\": false\n     },\n     \"x\": 836,\n     \"y\": 983,\n     \"width\": 178,\n     \"height\": 112,\n     \"type\": \"float\",\n     \"index\": 3,\n     \"isFloat\": true,\n     \"isFixedDiv\": false,\n     \"divID\": \"\",\n     \"links\": [],\n     \"image\": \"page-3.png\",\n     \"image2x\": \"page-3@2x.png\"\n    }\n   ],\n   \"links\": []\n  }\n ],\n \"groups\": [\n  {\n   \"id\": \"AFBBAF5B-B3EA-4EEC-BF53-930806F595AF\",\n   \"name\": \"Page 1\"\n  }\n ],\n \"startPageIndex\": 0,\n \"totalImages\": 5\n}"
  },
  {
    "path": "docs/FixedLayers/viewer/viewer-page.js",
    "content": "\n///\nconst Constants = {\n    ARTBOARD_OVERLAY_PIN_HOTSPOT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE: 1,\n    //\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT: 6,\n    ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER: 7,\n    //\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT: 0,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER: 1,\n    ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT: 2,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT: 3,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER: 4,\n    ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT: 5,\n    ARTBOARD_OVERLAY_PIN_PAGE_CENTER: 6,\n}\n\nconst EVENT_HOVER = 1\nconst TRANS_ANIM_NONE = 0\nlet TRANS_ANIMATIONS = [\n    {},\n    { in_str_classes: \".transit .slideInUp\", out_str_classes: \".transit .slideOutUp\", in_token: \"transition-slidein-up\", out_token: \"transition-slideout-up\" },\n    { in_str_classes: \".transit .slideInLeft\", out_str_classes: \".transit .slideOutLeft\", in_token: \"transition-slidein-left\", out_token: \"transition-slideout-left\" },\n    { in_str_classes: \".transit .fadeIn\", out_str_classes: \".transit .fadeOut\", in_token: \"transition-fadein_str_classes:\", out_token: \"transition-fadeout\" },\n    { in_str_classes: \".transit .slideInRight\", out_str_classes: \".transit .slideOutRight\", in_token: \"transition-slidein-right\", out_token: \"transition-slideout-right\" },\n    { in_str_classes: \".transit .slideInDown\", out_str_classes: \".transit .slideOutDown\", in_token: \"transition-slidein-down\", out_token: \"transition-slideout-down\" },\n]\n\n\n\nfunction inViewport($el) {\n    var elH = $el.outerHeight(),\n        H = $(window).height(),\n        r = $el[0].getBoundingClientRect(), t = r.top, b = r.bottom;\n    return Math.max(0, t > 0 ? Math.min(elH, H - t) : Math.min(b, H));\n}\n\nfunction handleAnimationEndOnHide(el) {\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnHide)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tch\")]\n    t.out_classes.forEach(function (className) {\n        el.target.classList.remove(className)\n    })\n    el.target.classList.add(\"hidden\")\n}\n\nfunction handleAnimationEndOnShow(el) {\n    el.target.removeEventListener(\"animationend\", handleAnimationEndOnShow)\n    const t = TRANS_ANIMATIONS[el.target.getAttribute(\"_tcs\")]\n    t.in_classes.forEach(function (className) {\n        el.target.classList.remove(className)\n    })\n\n}\n\n\n\nclass ViewerPage {\n\n    constructor() {\n        this.currentOverlays = []\n        this.parentPage = undefined\n\n        this.visible = false\n        this.image = undefined\n        this.imageDiv = undefined\n        this.imageObj = undefined\n\n        this.currentLeft = undefined\n        this.currentTop = undefined\n\n        this.currentX = undefined\n        this.currentY = undefined\n\n        this.overlayByEvent = undefined\n        this.tmpSrcOverlayByEvent = undefined\n\n        this.visibleInGallery = true\n    }\n\n    showHideGalleryLinks(show = null) {\n\n        if (this.slinks) this._showHideGalleryLinkSet(this.slinks, show)\n        if (this.dlinks) this._showHideGalleryLinkSet(this.dlinks, show)\n    }\n\n    _showHideGalleryLinkSet(links, forceShow = null) {\n        links.forEach(function (link) {\n            let show = forceShow != null ? forceShow : this.visibleInGallery && link.dpage.visibleInGallery && link.spage.visibleInGallery\n            // hide link\n            const o = $(\"#gallery #grid svg #l\" + link.index)\n            if (show) o.show(); else o.hide()\n            // hide start point\n            const sp = $(\"#gallery #grid svg #s\" + link.index)\n            if (show) sp.show(); else sp.hide()\n        }, this)\n    }\n\n    getHash() {\n        var image = this.image;\n        return image.substring(0, image.length - 4); // strip .png suffix\n    }\n\n    hide(hideChilds = false, disableAnim = false) {\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal) {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tch\", this.transAnimType)\n            transInfo.out_classes.forEach(function (className) {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnHide)\n        } else {\n            this.imageDiv.addClass(\"hidden\")\n        }\n\n        if (undefined != this.parentPage) { // current page is overlay      \n\n            if (hideChilds) this.hideChildOverlays()\n\n            const parent = this.parentPage\n            viewer.refresh_url(parent)\n            // remove this from parent overlay\n            const index = parent.currentOverlays.indexOf(this)\n            parent.currentOverlays.splice(index, 1)\n            this.parentPage = undefined\n        } else {\n            this.hideCurrentOverlays()\n        }\n\n        if (undefined != this.tmpSrcOverlayByEvent) {\n            this.overlayByEvent = this.tmpSrcOverlayByEvent\n            this.tmpSrcOverlayByEvent = undefined\n        }\n        // Cleanup\n        this.visible = false\n        this.currentLink = null\n    }\n\n    hideCurrentOverlays() {\n        const overlays = this.currentOverlays.slice()\n        for (let overlay of overlays) {\n            overlay.hide()\n        }\n        return overlays.length > 0\n    }\n\n    hideChildOverlays() {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays) {\n            if (overlay.currentLink.orgPage != this) continue\n            overlay.hide()\n        }\n    }\n\n    hideOtherParentOverlays() {\n        const overlays = this.parentPage.currentOverlays.slice()\n        for (let overlay of overlays) {\n            if (overlay == this) continue\n            overlay.hide()\n        }\n    }\n\n\n    show(disableAnim = false) {\n        if (!this.imageObj) this.loadImages(true)\n\n        this.updatePosition()\n\n        if (!disableAnim && TRANS_ANIM_NONE != this.transAnimType && !this.isModal) {\n            const transInfo = TRANS_ANIMATIONS[this.transAnimType]\n            const el = this.imageDiv.get(0)\n            el.setAttribute(\"_tcs\", this.transAnimType)\n            transInfo.in_classes.forEach(function (className, index) {\n                this.imageDiv.addClass(className)\n            }, this)\n            el.addEventListener(\"animationend\", handleAnimationEndOnShow)\n        } else {\n        }\n        this.imageDiv.removeClass(\"hidden\")\n        this.visible = true\n    }\n\n    updatePosition() {\n        this.currentLeft = viewer.currentMarginLeft\n        this.currentTop = viewer.currentMarginTop\n\n        if (this.isModal) {\n            var regPage = viewer.lastRegularPage\n\n            this.currentLeft += Math.round(regPage.width / 2) - Math.round(this.width / 2)\n            const visibleHeight = inViewport(regPage.imageDiv)\n            this.currentTop += Math.round(visibleHeight / 2) - Math.round(this.height / 2 * viewer.currentZoom)\n            if (this.currentTop < 0) this.currentTop = 0\n            if (this.currentLeft < 0) this.currentLeft = 0\n\n            var contentModal = $('#content-modal');\n            contentModal.css(\"margin-left\", this.currentLeft + \"px\")\n            contentModal.css(\"margin-top\", this.currentTop + \"px\")\n            if (this.height >= visibleHeight)\n                contentModal.css(\"overflow-y\", \"scroll\")\n            else\n                contentModal.css(\"overflow-y\", \"\")\n\n\n        } else if (\"overlay\" == this.type) {\n            this.currentLeft = viewer.currentPage ? viewer.currentPage.currentLeft : 0\n            this.currentTop = viewer.currentPage ? viewer.currentPage.currentTop : 0\n        }\n    }\n\n    showOverlayByLinkIndex(linkIndex) {\n        linkIndex = parseInt(linkIndex, 10)\n\n        var link = this._getLinkByIndex(linkIndex)\n        if (!link) {\n            console.log('Error: can not find link to overlay by index=\"' + linkIndex + '\"')\n            return false\n        }\n\n        // can handle only page-to-page transition\n        if ((link[\"page\"] == undefined)) return false\n\n        var destPage = story.pages[link.page]\n        // for mouseover overlay we need to show it on click, but only one time)\n        if (\"overlay\" == destPage.type && 1 == destPage.overlayByEvent) {\n            destPage.tmpSrcOverlayByEvent = destPage.overlayByEvent\n            destPage.overlayByEvent = 0\n            viewer.customEvent = {\n                x: link.rect.x,\n                y: link.rect.y,\n                pageIndex: this.index,\n                linkIndex: link.index\n            }\n            handleLinkEvent({})\n            viewer.customEvent = undefined\n        } else {\n            link.a.click()\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMove(x, y) {\n        for (let overlay of this.currentOverlays) {\n            // Commented to hide mouseover-overlay inside onclick-overlay  (ver 12.4.3)\n            //if (overlay.currentLink.orgPage != this) continue \n            overlay.onMouseMoveOverlay(x, y)\n        }\n    }\n\n    // return true (overlay is hidden) or false (overlay is visible)\n    onMouseMoveOverlay(x, y) {\n        if (this.imageDiv.hasClass(\"hidden\") || this.overlayByEvent != 1) return false\n        if (viewer.linksDisabled) return false\n\n        // handle mouse hover if this page is overlay\n        var _hideSelf = false\n        while (1 == this.overlayByEvent) {\n            var localX = Math.round(x / viewer.currentZoom) - this.currentLeft\n            var localY = Math.round(y / viewer.currentZoom) - this.currentTop\n            //alert(\" localX:\"+localX+\" localY:\"+localY+\" linkX:\"+this.currentLink.x+\" linkY:\"+this.currentLink.y);\n\n\n            if ( // check if we inside in overlay\n                localX >= this.currentX\n                && localY >= this.currentY\n                && localX < (this.currentX + this.width)\n                && localY < (this.currentY + this.height)\n            ) {\n                break\n            }\n\n            if ( // check if we out of current hotspot\n                localX < this.currentLink.x\n                || localY < this.currentLink.y\n                || localX >= (this.currentLink.x + this.currentLink.width)\n                || localY >= (this.currentLink.y + this.currentLink.height)\n            ) {\n                _hideSelf = true\n                break\n            }\n            break\n        }\n\n        // allow childs to handle mouse move\n        var visibleTotal = 0\n        var total = 0\n\n        for (let overlay of this.parentPage.currentOverlays) {\n            if (overlay.currentLink.orgPage != this) continue\n            total++\n            if (overlay.onMouseMoveOverlay(x, y)) visibleTotal++\n        }\n\n        if (_hideSelf)\n            if (!total || (total && !visibleTotal)) {\n                this.hide(false)\n                return false\n            }\n\n        return true\n    }\n\n\n    showAsOverlayInCurrentPage(orgPage, link, posX, posY, linkParentFixed, disableAnim) {\n        const newParentPage = viewer.currentPage\n\n        if (!this.imageDiv) {\n            this.loadImages(true)\n        }\n\n        // check if we need to hide any other already visible overlay\n        var positionCloned = false\n        const currentOverlays = newParentPage.currentOverlays\n\n        let overlayIndex = currentOverlays.indexOf(this.index)\n        if (overlayIndex < 0) {\n            if ('overlay' !== link.orgPage.type || this.overlayClosePrevOverlay) {\n                // if we show new overlay by clicking inside other overlay then we close the original overlay\n                if ('overlay' == orgPage.type && this.overlayClosePrevOverlay) {\n                    orgPage.hide()\n                } else {\n                    for (let overlay of currentOverlays) {\n                        if (overlay == this) continue\n                        overlay.hide()\n                    }\n                }\n            }\n        }\n\n        // Show overlay on the new position\n        const div = this.imageDiv\n\n        this.inFixedPanel = linkParentFixed && this.overlayAlsoFixed\n        if (!this.parentPage || this.parentPage.id != newParentPage.id || div.hasClass('hidden')) {\n\n            if (this.inFixedPanel) {\n                div.removeClass('divPanel')\n                div.addClass('fixedPanelFloat')\n            } else if (newParentPage.isModal) {\n                //div.removeClass('divPanel')\n                //div.removeClass('fixedPanelFloat')        \n            } else {\n                div.removeClass('fixedPanelFloat') // clear after inFixedPanel\n                div.addClass('divPanel')\n            }\n\n            // click on overlay outside of any hotspots should not close it\n            div.click(function () {\n                const index = parseInt(this.id.substring(this.id.indexOf(\"_\") + 1))\n                if (index >= 0) {\n                    const page = story.pages[index]\n                    const indexOverlay = page.parentPage.currentOverlays.indexOf(page)\n                    if (indexOverlay == 0) page.hideOtherParentOverlays()\n                }\n                return false\n            })\n\n            if (link.fixedBottomPanel) {\n                // show new overlay aligned to bottom\n                const panel = link.fixedBottomPanel;\n                const panelLink = panel.links[link.index]\n                posX = panel.x + panelLink.rect.x\n                posY = orgPage.height - panel.y - panelLink.rect.y// + panelLink.rect.height)\n\n                // check page right border\n                if ((posX + this.width) > orgPage.width) posX = orgPage.width - this.width\n\n                newParentPage.imageDiv.append(div)\n                div.css('top', \"\")\n                div.css('bottom', posY)\n                div.css('margin-left', posX + \"px\")\n            } else {\n                // \n                if (!this.overlayClosePrevOverlay && !positionCloned && undefined != this.overlayShadowX &&\n                    (\n                        (0 == this.overlayPin) // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                        && (3 == this.overlayPinHotspot) //ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                    )\n                ) {// OLD_ARTBOARD_OVERLAY_ALIGN_HOTSPOT_TOP_LEFT\n                    posX -= this.overlayShadowX\n                }\n\n                this.currentX = posX\n                this.currentY = posY\n\n                if (\"modal\" == orgPage.type) newParentPage.imageDiv.append(div)\n                div.css('top', posY + \"px\")\n                div.css('margin-left', posX + \"px\")\n            }\n\n            this.show(disableAnim)\n            div.css('z-index', 50 + newParentPage.currentOverlays.length)\n            newParentPage.currentOverlays.push(this)\n            this.parentPage = newParentPage\n            this.currentLink = link\n\n            // Change URL\n            if (undefined != this.overlayRedirectTargetPage) {\n                viewer.refresh_url(this)\n            } else {\n                var extURL = 'o=' + link.index\n                viewer.refresh_url(newParentPage, extURL)\n            }\n\n\n        } else if (1 == this.overlayByEvent && posX == this.currentX && posY == this.currentY) {//handle only mouse hover\n            // cursor returned back from overlay to hotspot -> nothing to do\n        } else {\n            this.hide()\n            viewer.refresh_url(newParentPage)\n        }\n    }\n\n    loadImages(force = false) {\n        /// check if already loaded images for this page\n        if (!force && this.imageObj != undefined) {\n            return pagerMarkImageAsLoaded()\n        }\n\n        const enableLinks = true\n        var isModal = this.type === \"modal\";\n\n        var content = $('#content')\n        var cssStyle = \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n        if (this.overlayShadow != undefined)\n            cssStyle += \"box-shadow:\" + this.overlayShadow + \";\"\n        if ('overlay' == this.type && this.overlayOverFixed)\n            cssStyle += \"z-index: 50;\"\n        var imageDiv = $('<div>', {\n            class: ('overlay' == this.type) ? \"divPanel\" : \"image_div\",\n            id: \"div_\" + this.index,\n            style: cssStyle\n        });\n        this.imageDiv = imageDiv\n\n\n        // create fixed panel images        \n        for (var panel of this.fixedPanels) {\n            let style = \"height: \" + panel.height + \"px; width: \" + panel.width + \"px; \"\n            if (panel.constrains.top || panel.isFixedDiv || (!panel.constrains.top && !panel.constrains.bottom)) {\n                style += \"top:\" + panel.y + \"px;\"\n            } else if (panel.constrains.bottom) {\n                style += \"bottom:\" + (this.height - panel.y - panel.height) + \"px;\"\n            }\n            if (panel.constrains.left || panel.isFixedDiv || (!panel.constrains.left && !panel.constrains.right)) {\n                style += \"margin-left:\" + panel.x + \"px;\"\n            } else if (panel.constrains.right) {\n                style += \"margin-left:\" + panel.x + \"px;\"\n            }\n            //\n\n            if (panel.shadow != undefined)\n                style += \"box-shadow:\" + panel.shadow + \";\"\n\n            // create Div for fixed panel\n            var cssClass = \"\"\n            if (panel.isFloat) {\n                cssClass = 'fixedPanelFloat'\n            } else if (panel.isFixedDiv) {\n                cssClass = 'divPanel'\n            } else if (\"top\" == panel.type) {\n                cssClass = 'fixedPanel fixedPanelTop'\n            } else if (\"left\" == panel.type) {\n                cssClass = 'fixedPanel'\n            }\n\n            var divID = panel.divID != '' ? panel.divID : (\"fixed_\" + this.index + \"_\" + panel.index)\n\n            var panelDiv = $(\"<div>\", {\n                id: divID,\n                class: cssClass,\n                style: style\n            });\n            //panelDiv.css(\"box-shadow\",panel.shadow!=undefined?panel.shadow:\"none\")     \n            panelDiv.appendTo(imageDiv);\n            panel.imageDiv = panelDiv\n\n            // create link div\n            panel.linksDiv = $(\"<div>\", {\n                class: \"linksDiv\",\n                style: \"height: \" + panel.height + \"px; width: \" + panel.width + \"px;\"\n            })\n            panel.linksDiv.appendTo(panel.imageDiv)\n            this._createLinks(panel)\n\n            // add image itself\n            panel.imageObj = this._loadSingleImage(panel.isFloat || panel.isFixedDiv ? panel : this, 'img_' + panel.index + \"_\")\n            panel.imageObj.appendTo(panelDiv);\n            if (!this.isDefault) panel.imageObj.css(\"webkit-transform\", \"translate3d(0,0,0)\")\n        }\n\n        // create main content image      \n        {\n            var isModal = this.type === \"modal\";\n            var contentModal = $('#content-modal');\n            imageDiv.appendTo(isModal ? contentModal : content);\n\n            // create link div\n            if (enableLinks) {\n                var linksDiv = $(\"<div>\", {\n                    id: \"div_links_\" + this.index,\n                    class: \"linksDiv\",\n                    style: \"height: \" + this.height + \"px; width: \" + this.width + \"px;\"\n                })\n                linksDiv.appendTo(imageDiv)\n                this.linksDiv = linksDiv\n\n                this._createLinks(this)\n            }\n        }\n        var img = this._loadSingleImage(this, 'img_')\n        this.imageObj = img\n        img.appendTo(imageDiv)\n    }\n\n    showLayout() {\n        if (undefined == this.layoutCreated) {\n            this.layoutCreated = true\n            this._addLayoutLines(this.imageDiv)\n        }\n    }\n\n    _addLayoutLines(imageDiv) {\n        if (this.type != \"regular\" || undefined == this.layout) return\n\n        var x = this.layout.offset\n        var colWidth = this.layout.columnWidth\n        var colWidthInt = Math.round(this.layout.columnWidth)\n        var gutterWidth = this.layout.gutterWidth\n        for (var i = 0; i < this.layout.numberOfColumns; i++) {\n            var style = \"left: \" + Math.trunc(x) + \"px; top:\" + 0 + \"px; width: \" + colWidthInt + \"px; height:\" + this.height + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutColDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n            x += colWidth + gutterWidth\n        }\n\n        for (var y = 0; y < this.height; y += 5) {\n            var style = \"left: \" + 0 + \"px; top:\" + y + \"px; width: \" + this.width + \"px; height:\" + 1 + \"px; \"\n            var colDiv = $(\"<div>\", {\n                class: \"layoutRowDiv layouLineDiv\",\n            }).attr('style', style)\n            colDiv.appendTo(this.linksDiv)\n        }\n    }\n\n\n    /*------------------------------- INTERNAL METHODS -----------------------------*/\n\n    // Try to find a first page and link which has a link to this page\n    _getSrcPageAndLink() {\n        let res = null\n        for (var page of story.pages) {\n            res = this._getLinkByTargetPage(page, page.links, this.index)\n            if (res) return res\n            for (var panel of page.fixedPanels) {\n                res = this._getLinkByTargetPage(page, panel.links, this.index)\n                if (res) return res\n            }\n        }\n        return null\n    }\n\n    _getLinkByTargetPage(page, links, targetPageIndex) {\n        const link = links.find(link => link.page == targetPageIndex)\n        if (!link) return null\n        return {\n            link: link,\n            page: page\n        }\n    }\n\n\n\n    _getLinkByIndex(index) {\n        var link = this._getLinkByIndexInLinks(index, this.links)\n        if (link != null) return link\n        for (var panel of this.fixedPanels) {\n            link = this._getLinkByIndexInLinks(index, panel.links)\n            if (link != null) return link\n        }\n        return null\n    }\n\n    _getLinkByIndexInLinks(index, links) {\n        var found = links.find(function (el) {\n            return el.index == index\n        })\n        return found != undefined ? found : null\n    }\n\n\n    _loadSingleImage(sizeSrc, idPrefix) {\n        var hasRetinaImages = story.hasRetina\n        var imageURI = hasRetinaImages && viewer.isHighDensityDisplay() ? sizeSrc.image2x : sizeSrc.image;\n        var unCachePostfix = \"V_V_V\" == story.docVersion ? \"\" : (\"?\" + story.docVersion)\n\n        var img = $('<img/>', {\n            id: idPrefix + this.index,\n            class: \"pageImage\",\n            src: encodeURIComponent(viewer.files) + '/' + encodeURIComponent(imageURI) + unCachePostfix,\n        }).attr('width', sizeSrc.width).attr('height', sizeSrc.height);\n\n        img.preload(function (perc, done) {\n            //console.log(perc, done);\n        });\n        return img;\n    }\n\n    // panel: ref to panel or this\n    _createLinks(panel) {\n        var linksDiv = panel.linksDiv\n\n        for (var link of panel.links) {\n            let x = link.rect.x + (link.isParentFixed ? panel.x : 0)\n            let y = link.rect.y + (link.isParentFixed ? panel.y : 0)\n\n            var a = $(\"<a>\", {\n                lpi: this.index,\n                li: link.index,\n                lppi: \"fixedPanels\" in panel ? -1 : panel.index,\n                lpx: x,\n                lpy: y\n            })\n\n            var eventType = 0 // click\n\n            if ('page' in link) {\n                var destPageIndex = viewer.getPageIndex(parseInt(link.page))\n                var destPage = story.pages[destPageIndex];\n                if ('overlay' == destPage.type) {\n                    eventType = destPage.overlayByEvent\n                }\n            }\n\n\n            if (EVENT_HOVER == eventType) { // for Mouse over event\n                a.mouseenter(handleLinkEvent)\n                if (\n                    0 == destPage.overlayPin // ARTBOARD_OVERLAY_PIN_HOTSPOT\n                    && 3 == destPage.overlayPinHotspot // ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT\n                ) {\n                } else {\n                    // need to pass click event to overlayed layers\n                    a.click(function (e) {\n                        if (undefined == e.originalEvent) return\n                        var nextObjects = document.elementsFromPoint(e.originalEvent.x, e.originalEvent.y);\n                        for (var i = 0; i < nextObjects.length; i++) {\n                            var obj = nextObjects[i].parentElement\n                            if (!obj || obj.nodeName != 'A' || obj == this) continue\n                            $(obj).trigger('click', e);\n                            return\n                        }\n                    })\n                }\n            } else { // for On click event\n                a.click(handleLinkEvent)\n            }\n\n            a.appendTo(linksDiv)\n\n            link.a = a\n\n            var style = \"left: \" + link.rect.x + \"px; top:\" + link.rect.y + \"px; width: \" + link.rect.width + \"px; height:\" + link.rect.height + \"px; \"\n            var linkDiv = $(\"<div>\", {\n                class: (EVENT_HOVER == eventType ? \"linkHoverDiv\" : \"linkDiv\") + (story.disableHotspots ? \"\" : \" linkDivHighlight\"),\n            }).attr('style', style)\n            linkDiv.appendTo(a)\n\n            link.div = linkDiv\n\n        }\n    }\n}\n\n//\n// customData:\n//  x,y,pageIndex\nfunction handleLinkEvent(event) {\n    var customData = viewer[\"customEvent\"]\n\n    if (viewer.linksDisabled) return false\n\n    let currentPage = viewer.currentPage\n    let orgPage = customData ? story.pages[customData.pageIndex] : story.pages[$(this).attr(\"lpi\")]\n\n    const linkIndex = customData ? customData.linkIndex : $(this).attr(\"li\")\n    const link = orgPage._getLinkByIndex(linkIndex)\n\n    if (link.page != undefined) {\n        var destPageIndex = parseInt(link.page)\n        var linkParentFixed = \"overlay\" != orgPage.type ? link.isParentFixed : orgPage.inFixedPanel\n\n\n        // title = story.pages[link.page].title;                   \n        var destPage = story.pages[destPageIndex]\n        if (!destPage) return\n\n\n        if (('overlay' == destPage.type || 'modal' == destPage.type) && destPage.overlayRedirectTargetPage != undefined) {\n\n            // Change base page\n            viewer.goTo(destPage.overlayRedirectTargetPage, false)\n            currentPage = viewer.currentPage\n            orgPage = viewer.currentPage\n        }\n\n        if ('overlay' == destPage.type) {\n\n            var orgLink = {\n                orgPage: orgPage,\n                index: linkIndex,\n                fixedPanelIndex: parseInt($(this).attr(\"lppi\")),\n                this: $(this),\n                x: customData ? customData.x : parseInt($(this).attr(\"lpx\")),\n                y: customData ? customData.y : parseInt($(this).attr(\"lpy\")),\n                width: link.rect.width,\n                height: link.rect.height\n            }\n\n            // check if link in fixed panel aligned to bottom\n            if (linkParentFixed && destPage.overlayAlsoFixed && orgLink.fixedPanelIndex >= 0 && currentPage.fixedPanels[orgLink.fixedPanelIndex].constrains.bottom) {\n                orgLink.fixedBottomPanel = currentPage.fixedPanels[orgLink.fixedPanelIndex]\n            } else { // clicked not from fixed panel           \n                const pinHotspot = Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT == destPage.overlayPin\n                const pinPage = Constants.ARTBOARD_OVERLAY_PIN_PAGE == destPage.overlayPin\n\n                var pageX = 0\n                var pageY = 0\n\n                if (pinHotspot) {\n                    //////////////////////////////// PIN TO HOTSPOT ////////////////////////////////\n                    // clicked from some other overlay\n                    if ('overlay' == orgPage.type) {\n                        orgLink.x += orgPage.currentX\n                        orgLink.y += orgPage.currentY\n                    }\n                    pageX = orgLink.x\n                    pageY = orgLink.y\n\n                    var offsetX = pinHotspot\n                        && (\n                            Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot\n                            || Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot\n                        )\n                        ? 5 : 0\n\n                    if (destPage.overlayClosePrevOverlay && ('overlay' == orgPage.type)) {\n                        pageX = orgPage.currentX\n                        pageY = orgPage.currentY\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_LEFT == destPage.overlayPinHotspot) {\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UNDER_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width - destPage.width\n                        pageY += link.rect.height\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT == destPage.overlayPinHotspot) {\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        //pageY -= destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width - destPage.width\n                        //pageY = pageY - destPage.height                            \n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_BOTTOM_RIGHT == destPage.overlayPinHotspot) {\n                        pageX += orgLink.width\n                    } else if (pinHotspot && Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_UP_CENTER == destPage.overlayPinHotspot) {\n                        pageX += parseInt(orgLink.width / 2) - parseInt(destPage.width / 2)\n                        pageY -= destPage.height\n                    }\n\n                    // check page right side\n                    if (!pinHotspot || (Constants.ARTBOARD_OVERLAY_PIN_HOTSPOT_TOP_LEFT != destPage.overlayPinHotspot)) {\n                        const fullWidth = destPage.width + offsetX // + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        if ((pageX + fullWidth) > currentPage.width)\n                            pageX = currentPage.width - fullWidth\n\n                        /*if(linkPosX < (offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0))){  \n                            linkPosX = offsetX + (('overlayShadowX' in destPage)?destPage.overlayShadowX:0)\n                        }*/\n                    }\n                } else {\n                    //////////////////////////////// PIN TO PAGE ////////////////////////////////\n\n                    if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_LEFT == destPage.overlayPinPage) {\n                        pageX = 0\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_TOP_RIGHT == destPage.overlayPinPage) {\n                        pageX = currentPage.width - destPage.width\n                        pageY = 0\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = parseInt(currentPage.height / 2) - parseInt(destPage.height / 2)\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_LEFT == destPage.overlayPinPage) {\n                        pageX = 0\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_CENTER == destPage.overlayPinPage) {\n                        pageX = parseInt(currentPage.width / 2) - parseInt(destPage.width / 2)\n                        pageY = currentPage.height - destPage.height\n                    } else if (pinPage && Constants.ARTBOARD_OVERLAY_PIN_PAGE_BOTTOM_RIGHT == destPage.overlayPinPage) {\n                        pageX = currentPage.width - destPage.width\n                        pageY = currentPage.height - destPage.height\n                    }\n\n                }\n\n                if (pageX < 0) pageX = 0\n                if (pageY < 0) pageY = 0\n            }\n\n            if (destPage.visible) {\n                const sameLink = destPage.currentLink.index == orgLink.index\n                if (sameLink) {\n                    destPage.hide()\n                } else {\n                    destPage.hide(false, true) // hide without transition animation\n                    if (orgPage != destPage)\n                        destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed, true)\n                }\n                return false\n            }\n            destPage.showAsOverlayInCurrentPage(orgPage, orgLink, pageX, pageY, linkParentFixed)\n            return false\n        } else {\n            // close modal if some link inside a modal opens the same modal\n            if (destPageIndex == currentPage.index && currentPage.isModal) {\n                viewer.goBack()\n                return false\n            }\n\n            // check if we need to close current overlay\n            currentPage.hideCurrentOverlays()\n\n            viewer.goTo(parseInt(destPageIndex))\n            return false\n        }\n    } else if (link.action != null && link.action == 'back') {\n        //title = \"Go Back\";\n        viewer.currentPage.hideCurrentOverlays()\n        viewer.goBack()\n        return false\n    } else if (link.url != null) {\n        //title = link.url;\n        viewer.currentPage.hideCurrentOverlays()\n        var target = link.target\n        window.open(link.url, target != undefined ? target : \"_self\")\n        return false\n        //document.location = link_url\n        //target = link.target!=null?link.target:null;\t\t\n    }\n\n    // close last current overlay if it still has parent\n    if ('overlay' == orgPage.type && undefined != orgPage.parentPage) {\n        orgPage.hide()\n    }\n\n    return false\n}\n"
  },
  {
    "path": "docs/FixedLayers/viewer/viewer.js",
    "content": "\n// =============================== PRELOAD IMAGES =========================\nvar pagerLoadingTotal = 0\n\nfunction getQuery(uri, q) {\n    return (uri.match(new RegExp('[?&]' + q + '=([^&]+)')) || [, null])[1];\n}\n\nfunction showError(error) {\n    alert(error)\n}\n\nfunction showMessage(message) {\n    alert(message)\n}\n\nfunction checkFolderInfoRequest(resp) {\n    if (resp.readyState == resp.DONE) {\n        if (resp.status == 200 && resp.responseText != null) {\n            const data = JSON.parse(resp.responseText)\n            if (undefined != data['project_url'] && '' != data['project_url']) return data\n        }\n        showError(\"Can't get information about the versions.\")\n    }\n    return undefined\n}\n\nfunction handleDecreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    if ('' == data.link_down) return showMessage('This is the oldest version.')\n    window.open(data.link_down + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction handleIncreaseVersion() {\n    var data = checkFolderInfoRequest(this)\n    if (undefined == data) return\n    let link = data.link_up\n    if ('' == link) {\n        if (!window.confirm('This is the newest version. Go to live version?')) return\n        link = data.link_live\n    }\n    window.open(link + '?' + encodeURIComponent(viewer.currentPage.getHash()), \"_self\");\n}\n\nfunction doTransNext() {\n    // get oldest transition\n    const trans = viewer.transQueue[0]\n    // if it still active then run it\n    if (trans.active) {\n        viewer.next()\n        console.log(\"RUN transition\")\n    } else {\n        console.log(\"skip transition\")\n    }\n\n    // remove this transtion from stack\n    viewer.transQueue.shift()\n}\n\n$.fn.preload = function (callback) {\n    var length = this.length;\n    var iterator = 0;\n\n    return this.each(function () {\n        var self = this;\n        var tmp = new Image();\n\n        if (callback) tmp.onload = function () {\n            callback.call(self, 100 * ++iterator / length, iterator === length);\n            pagerMarkImageAsLoaded()\n        };\n        tmp.src = this.src;\n    });\n};\n\nfunction pagerMarkImageAsLoaded() {\n    console.log(pagerLoadingTotal);\n    if (--pagerLoadingTotal == 0) {\n        $(\"#nav #loading\").addClass(\"hidden\")\n    }\n}\n\nasync function preloadAllPageImages() {\n    $(\"#nav #loading\").removeClass(\"hidden\")\n    pagerLoadingTotal = story.totalImages\n    var pages = story.pages;\n    for (var page of story.pages) {\n        if (page.imageObj == undefined) {\n            page.loadImages()\n            page.imageDiv.addClass(\"hidden\")\n        }\n    }\n}\n\nfunction reloadAllPageImages() {\n    for (var page of story.pages) {\n        page.imageObj.parent().remove();\n        page.imageObj = undefined\n        for (var p of page.fixedPanels) {\n            p.imageObj.parent().remove();\n            p.imageObj = undefined\n        }\n    }\n    preloadAllPageImages()\n}\n\nfunction doBlinkHotspots() {\n    viewer.toggleLinks()\n}\n\n\n// str: .transit .slideInDown\"\nfunction splitStylesStr(str) {\n    return str.split(\" \").map(s => s.replace(\".\", \"\"))\n}\n\n// ============================ VIEWER ====================================\n\nfunction createViewer(story, files) {\n    return {\n        highlightLinks: story.highlightLinks,\n        showLayout: false,\n        isEmbed: false,\n\n        fullBaseURL: \"\",\n        fullCurrentPageURL: \"\",\n\n        prevPage: undefined,\n        currentPage: undefined,\n        lastRegularPage: undefined,\n\n        currentMarginLeft: undefined,\n        currentMarginTop: undefined,\n\n        backStack: [],\n        urlLastIndex: -1,\n        urlLocked: false,\n        files: files,\n        userStoryPages: [],\n        zoomEnabled: story.zoomEnabled,\n\n        sidebarVisible: false,\n        child: null, // some instance of Viewer\n        allChilds: [], // list of all inited instances of Viewer\n        symbolViewer: null,\n        versionViewer: null,\n        commentsViewer: null,\n\n        defSidebarWidth: 400,\n\n        transQueue: [],\n\n        initialize: function () {\n            this.initParseGetParams()\n            this.buildUserStory();\n            this.initializeHighDensitySupport();\n            this.initAnimations()\n\n            /// Init Viewers\n            this.galleryViewer = new GalleryViewer()\n\n            if (story.layersExist) {\n                this.symbolViewer = new SymbolViewer()\n\n            }\n            // Create Version Viewer for published mockups with some version specified\n            if (story.docVersion != 'V_V_V') {\n                this.versionViewer = new VersionViewer()\n                $(\"#menu_version_viewer\").removeClass(\"hidden\");\n            }\n            if (story.commentsURL != 'V_V_C' && story.commentsURL != \"\") {\n                this.commentsViewer = new CommentsViewer()\n                $(\"#nav #pageComments\").removeClass(\"hidden\")\n            }\n\n        },\n\n        initAnimations: function () {\n            if (story.layersExist) {\n                // TODO\n            }\n            // transform \".transit .slideInDown\" strings into class name arrays\n            TRANS_ANIMATIONS.forEach(function (t, index) {\n                if (0 == index) return\n                t.in_classes = splitStylesStr(t.in_str_classes)\n                t.out_classes = splitStylesStr(t.out_str_classes)\n            }, this)\n        },\n\n        initializeLast: function () {\n\n            $(\"body\").keydown(function (event) {\n                viewer.handleKeyDown(event)\n            })\n            window.addEventListener('mousemove', function (e) {\n                viewer.onMouseMove(e.pageX, e.pageY)\n            });\n            jQuery(window).resize(function () { viewer.zoomContent() });\n\n            if (this.urlParams.get('v') != null && this.versionViewer) {\n                this.versionViewer.toggle()\n            }\n            if (this.urlParams.get('c') != null && this.commentsViewer) {\n                this.commentsViewer.toggle()\n            }\n            const gParam = this.urlParams.get('g')\n            if (gParam != null && this.galleryViewer) {\n                this.galleryViewer.handleURLParam(gParam)\n                this.galleryViewer.show()\n            }\n        },\n\n        initParseGetParams: function () {\n            const loc = document.location\n            this.fullBaseURL = loc.protocol + \"//\" + loc.hostname + loc.pathname\n            this.urlParams = new URLSearchParams(loc.search.substring(1));\n            this.urlSearch = loc.search\n\n            if (this.urlParams.get('e') != null) {\n                this.isEmbed = true\n                // hide image preload indicator\n                $('#nav loading').hide()\n                // hide Navigation                \n                $('.navCenter').hide()\n                $('.navPreviewNext').hide()\n                $('#btnMenu').hide()\n                $('#btnOpenNew').show()\n            }\n        },\n        initializeHighDensitySupport: function () {\n            if (window.matchMedia) {\n                this.hdMediaQuery = window\n                    .matchMedia(\"only screen and (min--moz-device-pixel-ratio: 1.1), only screen and (-o-min-device-pixel-ratio: 2.2/2), only screen and (-webkit-min-device-pixel-ratio: 1.1), only screen and (min-device-pixel-ratio: 1.1), only screen and (min-resolution: 1.1dppx)\");\n                var v = this;\n                this.hdMediaQuery.addListener(function (e) {\n                    v.refresh();\n                });\n            }\n        },\n        isHighDensityDisplay: function () {\n            return (this.hdMediaQuery && this.hdMediaQuery.matches || (window.devicePixelRatio && window.devicePixelRatio > 1));\n        },\n        buildUserStory: function () {\n            // \n            let opages = []\n            story.pages.forEach(function (page) {\n                opages.push($.extend(new ViewerPage(), page))\n            })\n            story.pages = opages\n            //\n            this.userStoryPages = []\n            for (var page of story.pages) {\n                if ('regular' == page.type || 'modal' == page.type) {\n                    page.userIndex = this.userStoryPages.length\n                    this.userStoryPages.push(page)\n                } else {\n                    page.userIndex = -1\n                }\n            }\n        },\n\n        handleKeyDown: function (jevent) {\n            const v = viewer\n            const event = jevent.originalEvent\n\n            const allowNavigation = !this.child || !this.child.blockMainNavigation\n            const enableTopNavigation = !this.child || this.child.enableTopNavigation\n\n            // allow all childs to handle global keys\n            if (!this.child) {\n                for (const child of this.allChilds) {\n                    if (child.handleKeyDownWhileInactive(jevent)) return true\n                }\n            }\n\n            // allow currently active childs to handle global keys\n            if (this.child && this.child.handleKeyDown(jevent)) return true\n\n            //console.log(jevent.metaKey)\n            //console.log(jevent.which)\n            if (allowNavigation && (13 == event.which || 39 == event.which)) { // enter OR right\n                v.next()\n            } else if (allowNavigation && (8 == event.which || 37 == event.which)) { // backspace OR left\n                v.previous()\n            } else if (allowNavigation && (16 == event.which)) { // shift\n                if (!jevent.metaKey) {  // no cmd to allow user to make a screenshot on macOS\n                    v.toggleLinks()\n                }\n            } else if (allowNavigation && 91 == event.which) { // cmd\n                if (this.highlightLinks) v.toggleLinks(false) // hide hightlights to allow user to make a screenshot on macOS\n            } else if (event.metaKey || event.altKey || event.ctrlKey) { // skip any modificator active to allow a browser to handle its own shortkeys\n                return false\n            } else if (allowNavigation && 90 == event.which) { // z\n                v.toggleZoom()\n            } else if (allowNavigation && 69 == event.which) { // e\n                v.share()\n            } else if (allowNavigation && 76 == event.which) { // l\n                v.toogleLayout();\n            } else if (enableTopNavigation && 83 == event.which) { // s\n                var first = v.getFirstUserPage()\n                if (first && (first.index != v.currentPage.index || this.child)) {\n                    this.hideChild()\n                    v.goToPage(first.index)\n                }\n            } else if (allowNavigation && 27 == event.which) { // esc\t\n                v.onKeyEscape()\n            } else {\n                return false\n            }\n            jevent.preventDefault()\n            return true\n        },\n\n\n        blinkHotspots: function () {\n            if (this.symbolViewer && this.symbolViewer.visible) return\n            this.toggleLinks()\n            setTimeout(doBlinkHotspots, 500)\n        },\n\n        setMouseMoveHandler: function (obj) {\n            this.mouseMoveHandler = obj\n        },\n\n        onMouseMove: function (x, y) {\n            if (this.mouseMoveHandler && this.mouseMoveHandler.onMouseMove(x, y)) return\n            if (this.currentPage) this.currentPage.onMouseMove(x, y)\n        },\n\n        onContentClick: function () {\n            // allow currently active child to handle click\n            if (this.child && this.child.onContentClick()) return true\n\n            if (this.linksDisabled) return false\n            if (this.onKeyEscape()) return\n            this.blinkHotspots()\n        },\n        onModalClick: function () {\n            this.blinkHotspots()\n        },\n\n        _setupFolderinfoRequest: function (func) {\n            var xhr = new XMLHttpRequest();\n            xhr.open(\"GET\", story.serverToolsPath + \"folder_info.php\", true);\n            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n            xhr.onreadystatechange = func;\n            xhr.send(null);\n        },\n\n        decreaseVersion: function () {\n            this._setupFolderinfoRequest(handleDecreaseVersion)\n        },\n\n        increaseVersion: function () {\n            this._setupFolderinfoRequest(handleIncreaseVersion)\n        },\n\n\n        showChild: function (child) {\n            // Hide currently visible child\n            if (this.child) {\n                this.hideChild(this.child)\n            }\n\n            // Show new child\n            this.child = child;\n            if (child.isSidebarChild) {\n                this._showSidebar()\n            }\n            child._showSelf()\n        },\n\n\n        _showSidebar: function () {\n            this.sidebarVisible = true\n            $('#sidebar').removeClass(\"hidden\")\n            viewer.zoomContent()\n        },\n\n        _hideSidebar: function () {\n            this.sidebarVisible = false\n            $('#sidebar').addClass(\"hidden\")\n            this.zoomContent()\n        },\n\n        hideChild: function () {\n            if (!this.child) return;\n            if (this.child.isSidebarChild) {\n                this._hideSidebar()\n            }\n            this.child._hideSelf();\n            this.child = null;\n\n        },\n\n        share: function () {\n            var page = undefined == this.lastRegularPage ? this.currentPage : this.lastRegularPage\n\n            let url = this.fullCurrentPageURL\n            url += '&e=1'\n\n            var iframe = '<iframe src=\"' + url + '\" style=\"border: none;\" noborder=\"0\"'\n            iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n            iframe += ' height=\"' + (story.iFrameSizeHeight ? story.iFrameSizeHeight : page.height) + '\"'\n            iframe += ' scrolling=\"auto\" seamless id=\"iFrame1\"></iframe>'\n\n            iframe += '\\n\\n'\n\n            var ihref = url.substring(0, url.lastIndexOf(\"/\"))\n\n            ihref = ihref + \"/images/\" + page['image2x']\n            iframe += \"<a target='_blank' href='\" + url + \"'>\" + \"<img border='0' \"\n            iframe += ' width=\"' + (story.iFrameSizeWidth ? story.iFrameSizeWidth : page.width) + '\"'\n            //iframe += ' height=\"'+(story.iFrameSizeHeight?story.iFrameSizeHeight:page.height) + '\"'\n            iframe += \"src='\" + ihref + \"'\"\n            iframe += \"/></a>\"\n\n            alert(iframe)\n        },\n\n        toggleZoom: function () {\n            this.zoomEnabled = !this.zoomEnabled\n            this.zoomContent()\n        },\n\n        openNewWindow: function () {\n            let url = this.fullCurrentPageURL\n            // ok, now open it in the new browse window\n            window.open(url, \"_blank\")\n        },\n\n        zoomContent: function () {\n            var page = this.lastRegularPage\n            if (undefined == page) return\n\n\n            if (undefined == this.marker) {\n                this.marker = $('#marker')\n            }\n            var marker = this.marker\n\n            var content = $('#content')\n            //var contentShadow = $('#content-shadow')\n            var contentModal = $('#content-modal')\n            var elems = [content, contentModal] //,contentShadow\n\n            var fullWidth = marker.innerWidth()\n            var availableWidth = fullWidth\n            var zoom = \"\"\n            var scale = \"\"\n\n            // check sidebar\n            var sidebarWidth = 0\n            if (this.sidebarVisible) {\n                var sidebar = $(\"#sidebar\")\n\n                sidebarWidth = this.defSidebarWidth\n\n                /* commented because it works in bad way with small artboards and large screen\n                sidebarWidth = Math.round((fullWidth - page.width) / 2)\n                if (sidebarWidth < defSidebarWidth) {\n                    sidebarWidth = defSidebarWidth\n                    availableWidth = fullWidth - sidebarWidth\n                }*/\n                if (((fullWidth - page.width) / 2) < sidebarWidth) {\n                    availableWidth = fullWidth - sidebarWidth\n                }\n\n                sidebar.css(\"margin-left\", (fullWidth - sidebarWidth) + \"px\")\n                sidebar.css(\"margin-top\", (0) + \"px\")\n                sidebar.css(\"width\", sidebarWidth + \"px\")\n                sidebar.css(\"height\", \"100%\")\n            }\n\n\n            if (this.zoomEnabled && ((availableWidth < page.width) || screen.width <= 800)) {\n                zoom = availableWidth / page.width\n                scale = \"scale(\" + zoom + \")\"\n            }\n\n            var newZoom = zoom != '' ? (zoom + 0) : 1\n\n            if (undefined == this.currentZoom || this.currentZoom != newZoom) {\n                for (var el of elems) {\n                    el.css(\"zoom\", zoom)\n                    el.css(\"-moz-transform\", scale)\n                }\n                content.css(\"-moz-transform-origin\", \"left top\")\n                contentModal.css(\"-moz-transform-origin\", \"center top\")\n\n            }\n\n            this.currentZoom = newZoom\n            this.fullWidth = fullWidth\n\n            // Calculate margins\n            this.currentMarginLeft = Math.round(availableWidth / 2) - Math.round(page.width / 2 * this.currentZoom)\n            this.currentMarginTop = 0\n\n            if (this.currentMarginLeft < 0) this.currentMarginLeft = 0\n\n            // Set content to new left positions\n            content.css(\"margin-left\", this.currentMarginLeft + \"px\")\n            content.css(\"margin-top\", this.currentMarginTop + \"px\")\n            this.currentPage.updatePosition()\n\n            // \n            if (this.child) {\n                this.child.viewerResized()\n            }\n        },\n\n        getPageHashes: function () {\n            if (this.pageHashes == null) {\n                var hashes = {};\n                for (var page of story.pages) {\n                    hashes[page.getHash()] = page.index;\n                }\n                this.pageHashes = hashes;\n            }\n            return this.pageHashes;\n        },\n        getModalFirstParentPageIndex: function (modalIndex) {\n            var foundPageIndex = null\n            // scan all regular pages\n            story.pages.filter(page => \"regular\" == page.type).some(function (page) {\n                const foundLinks = page.links.filter(link => link.page != null && link.page == modalIndex)\n                if (foundLinks.length != 0) {\n                    // return the page index which has link to modal                    \n                    foundPageIndex = page.index\n                    return true\n                }\n                // save a first regular page as a \"found\" for case if we will not \n                // find any page with a link to a specified modal\n                if (null == foundPageIndex) foundPageIndex = page.index\n                return false\n            }, this)\n\n            // ok, we found some regular page which has a link to specified modal ( or it was a fist regular page)\n            return foundPageIndex\n        },\n        getPageIndex: function (page, defIndex = 0) {\n            var index;\n\n            if (typeof page === \"number\") {\n                index = page;\n            } else if (page === \"\") {\n                index = defIndex;\n            } else {\n                index = this.getPageHashes()[page];\n                if (index == undefined) {\n                    index = defIndex;\n                }\n            }\n            return index;\n        },\n        goBack: function () {\n            if (this.backStack.length > 0) {\n                this.goTo(this.backStack[this.backStack.length - 1]);\n                this.backStack.shift();\n            } else if (this.currentPage.isModal && this.lastRegularPage) {\n                this.goTo(this.lastRegularPage.index);\n            } else {\n                window.history.back();\n            }\n        },\n        goToPage: function (page) {\n            this.clear_context();\n            this.goTo(page);\n        },\n        goTo: function (page, refreshURL = true) {\n            // We don't need any waiting page transitions anymore\n            this._resetTransQueue()\n\n            //if(this.symbolViewer) this.symbolViewer.hide()\n\n            var index = this.getPageIndex(page);\n            var currentPage = this.currentPage\n\n            if (currentPage && !currentPage.isModal) {\n                this.backStack.push(currentPage.index);\n            }\n\n            var oldcurrentPageModal = currentPage && currentPage.isModal\n\n            if (index < 0 || (currentPage && index == currentPage.index) || index >= story.pages.length) return;\n\n\n            var newPage = story.pages[index];\n            if (newPage.type === \"modal\") {\n                // hide parent page links hightlighting\n                this._updateLinksState(false, $('#content'))\n\n                // no any page visible now, need to find something\n                if (undefined == currentPage) {\n                    var parentIndex = this.getModalFirstParentPageIndex(index);\n                    this.goTo(parentIndex, false);\n                    this.zoomContent()\n                }\n\n                // redraw modal links hightlighting\n                this._updateLinksState()\n            } else {\n                if (oldcurrentPageModal) {\n                    // hide modal page links hightlighting\n                    this._updateLinksState(false, $('#content-modal'))\n                    this._updateLinksState(undefined, $('#content'))\n                }\n            }\n            this.prevPage = currentPage\n            var prevRegularPage = this.lastRegularPage\n\n            newPage.show()\n\n            this.refresh_adjust_content_layer(newPage);\n            this.refresh_hide_last_image(newPage)\n            this.refresh_switch_modal_layer(newPage);\n            if (refreshURL) {\n                this.refresh_url(newPage)\n            } else {\n                this._calcCurrentPageURL(newPage)\n            }\n            this.refresh_update_navbar(newPage);\n\n            this.currentPage = newPage;\n            if (!newPage.isModal) {\n                this.lastRegularPage = newPage\n            }\n\n            // zoom content if the new page dimensions differ from the previous\n            if (!newPage.isModal) {\n                if (!prevRegularPage || newPage.width != prevRegularPage.width || newPage.height != prevRegularPage.height) {\n                    this.zoomContent()\n                }\n            }\n\n\n            if (newPage.transNextMsecs != undefined) {\n                this._setupTransNext(newPage.transNextMsecs)\n            }\n\n            if (!newPage.disableAutoScroll) {\n                window.scrollTo(0, 0)\n            }\n\n            if (this.child) this.child.pageChanged()\n            this.allChilds.filter(c => c.alwaysHandlePageChanged).forEach(function (c) {\n                c.pageChanged()\n            })\n\n        },\n        _setupTransNext: function (msecs) {\n            // deactivate all waiting transitions\n            for (var trans of this.transQueue) {\n                trans.active = false\n            }\n            // place new active transition over the top of stack\n            this.transQueue.push({\n                page: this.currentPage,\n                active: true\n            })\n            // set timer in milisecs\n            setTimeout(doTransNext, msecs)\n        },\n        // Deactivate all waiting transitions\n        _resetTransQueue: function () {\n            for (var trans of this.transQueue) {\n                trans.active = false\n            }\n        },\n        refresh_update_navbar: function (page) {\n            var VERSION_INJECT = story.docVersion != 'V_V_V' ? (\" (v\" + story.docVersion + \")\") : \"\";\n\n            var prevPage = this.getPreviousUserPage(page)\n            var nextPage = this.getNextUserPage(page)\n\n            $('#nav .title').html((page.userIndex + 1) + '/' + this.userStoryPages.length + ' - ' + page.title + VERSION_INJECT);\n            $('#nav-left-prev').toggleClass('disabled', !prevPage)\n            $('#nav-left-next').toggleClass('disabled', !nextPage)\n\n            if (prevPage) {\n                $('#nav-left-prev a').attr('title', prevPage.title);\n            } else {\n                $('#nav-left-prev a').removeAttr('title');\n            }\n\n            if (nextPage) {\n                $('#nav-left-next a').attr('title', nextPage.title);\n            } else {\n                $('#nav-left-next a').removeAttr('title');\n            }\n\n            $('#nav-right-hints').toggleClass('disabled', page.annotations == undefined);\n\n            this.refresh_update_links_toggler(page);\n        },\n        refresh_update_links_toggler: function (page) {\n            $('#nav-right-links').toggleClass('active', this.highlightLinks);\n            $('#nav-right-links').toggleClass('disabled', page.links.length == 0);\n        },\n        refresh_hide_last_image: function (page) {\n            var content = $('#content');\n            var contentModal = $('#content-modal');\n            var isModal = page.isModal\n\n            // hide last regular page to show a new regular after modal\n            if (!isModal && this.lastRegularPage && this.lastRegularPage.index != page.index) {\n                var lastPageImg = $('#img_' + this.lastRegularPage.index);\n                if (lastPageImg.length) {\n                    this.lastRegularPage.hide()\n                }\n            }\n\n            // hide last modal \n            var prevPageWasModal = this.prevPage != null && this.prevPage.type === \"modal\"\n            if (prevPageWasModal) {\n                var prevImg = $('#img_' + this.prevPage.index);\n                if (prevImg.length) {\n                    this.prevPage.hide()\n                    //pagerHideImg(prevImg)\n                }\n            }\n\n        },\n        refresh_adjust_content_layer: function (page) {\n            if (page.isModal) return;\n\n            var contentShadow = $('#content-shadow');\n            var contentModal = $('#content-modal');\n            var content = $('#content');\n\n            var prevPageWasModal = this.prevPage && this.prevPage.isModal\n            if (prevPageWasModal) {\n                contentShadow.addClass('hidden');\n                contentModal.addClass('hidden');\n            }\n\n        },\n\n        refresh_switch_modal_layer: function (page) {\n            if (!page.isModal) return;\n\n            var showShadow = page.showShadow == 1;\n            var contentModal = $('#content-modal');\n            var contentShadow = $('#content-shadow');\n\n            if (showShadow) {\n                contentShadow.removeClass('no-shadow');\n                contentShadow.addClass('shadow');\n                contentShadow.removeClass('hidden');\n            } else {\n                contentModal.addClass('hidden');\n            }\n            contentModal.removeClass('hidden');\n        },\n\n\n        _getSearchPath(page = null, extURL = null) {\n            if (!page) page = this.currentPage\n            let search = '?' + encodeURIComponent(page.getHash())\n            if (extURL != null && extURL != \"\") search += \"&\" + extURL\n            return search\n        },\n\n        _calcCurrentPageURL: function (page = null, extURL = null) {\n            if (!page) page = this.currentPage\n            this.urlLastIndex = page.index\n            $(document).attr('title', story.title + ': ' + page.title)\n\n            let newPath = this.fullBaseURL + this._getSearchPath(page, extURL)\n            this.fullCurrentPageURL = newPath\n        },\n\n        refresh_url: function (page, extURL = \"\", pushHistory = true) {\n            if (this.urlLocked) return\n\n            this._calcCurrentPageURL(page, extURL)\n            let newPath = this.fullCurrentPageURL\n            this.fullCurrentPageURL = newPath\n\n            if (this.isEmbed) {\n                newPath += \"&e=1\"\n            }\n            if (this.galleryViewer && this.galleryViewer.isVisible()) {\n                newPath += \"&g=\" + (this.galleryViewer.isMapMode ? \"m\" : \"g\")\n            }\n            if (this.commentsViewer && this.commentsViewer.isVisible()) {\n                newPath += \"&c=1\"\n            }\n\n            if (pushHistory) {\n                window.history.pushState(newPath, page.title, newPath);\n            } else {\n                window.history.replaceState({}, page.title, newPath);\n            }\n        },\n\n        /*\n        _parseLocationHash: function () {\n            var result = {\n                reset_url: false,\n                overlayLinkIndex: undefined,\n                redirectOverlayLinkIndex: undefined,\n            }\n            var hash = location.hash;\n     \n            if (hash == null || hash.length == 0) {\n                hash = '#'\n                result.reset_url = true\n     \n            } else if (hash.indexOf('/') > 0) {\n                // read additonal parameters\n                var args = hash.split('/')\n                // check for link to click\n                if (args[1] == 'o') {\n                    result.overlayLinkIndex = args[2]\n                }\n                hash = hash.substring(0, hash.indexOf('/'))\n                hash = '#' + hash.replace(/^[^#]*#?(.*)$/, '$1');\n            }\n     \n            result.page_name = hash\n            return result\n        },*/\n\n        _parseLocationSearch: function () {\n            //if (document.location.hash != null && document.location.hash != \"\")\n            //  return this._parseLocationHash()\n\n            var result = {\n                page_name: \"\",\n                reset_url: false,\n                overlayLinkIndex: undefined,\n                redirectOverlayLinkIndex: undefined,\n            }\n            this.urlParams.forEach(function (value, key) {\n                if (\"\" == value) result.page_name = key\n            }, this);\n\n            if (null == result.page_name || \"\" == result.page_name || this.urlParams.get(result.page_name) != \"\") {\n                result.page_name = \"\"\n                result.reset_url = true\n            } else {\n                result.overlayLinkIndex = this.urlParams.get(\"o\")\n            }\n            return result\n        },\n\n        handleNewLocation: function (initial) {\n            var locInfo = this._parseLocationSearch()\n            var pageIndex = locInfo.page_name != null ? this.getPageIndex(locInfo.page_name, null) : null\n            if (null == pageIndex) {\n                // get the default page\n                pageIndex = story.startPageIndex\n                locInfo.reset_url = true\n            }\n\n            if (!initial && this.urlLastIndex == pageIndex) {\n                return\n            }\n\n            var page = story.pages[pageIndex];\n\n            // check if this redirect overlay\n            let overlayRedirectInfo = null\n            if (undefined != page.overlayRedirectTargetPage) {\n                overlayRedirectInfo = page._getSrcPageAndLink()\n                if (overlayRedirectInfo) {\n                    pageIndex = overlayRedirectInfo.page.index\n                    page = overlayRedirectInfo.page\n                }\n\n            }\n\n            if (initial)\n                page.isDefault = true\n            else\n                this.clear_context();\n\n            // check if this page overlay\n            // check if this redirect overlay\n            this.goTo(pageIndex, locInfo.reset_url);\n\n            if (locInfo.overlayLinkIndex != null) {\n                page.showOverlayByLinkIndex(locInfo.overlayLinkIndex)\n            }\n\n            if (!initial) this.urlLastIndex = pageIndex\n\n            // Open redirect overlay over the overlay source page\n            if (overlayRedirectInfo) {\n                overlayRedirectInfo.link.a.click()\n            }\n        },\n\n        clear_context_hide_all_images: function () {\n            var page = this.currentPage;\n            var content = $('#content');\n            var contentModal = $('#content-modal');\n            var contentShadow = $('#content-shadow');\n            var isModal = page && page.type === \"modal\";\n\n            contentShadow.addClass('hidden');\n            contentModal.addClass('hidden');\n\n            // hide last regular page\n            if (this.lastRegularPage) {\n                var lastPageImg = $('#img_' + this.lastRegularPage.index);\n                if (lastPageImg.length) {\n                    this.lastRegularPage.hide()\n                }\n            }\n\n            // hide current modal \n            if (isModal) {\n                var modalImg = $('#img_' + this.currentPage.index);\n                if (modalImg.length) {\n                    this.currentPage.hide()\n                }\n            }\n\n        },\n\n        clear_context: function () {\n            this.clear_context_hide_all_images()\n\n            this.prevPage = undefined\n            this.currentPage = undefined\n            this.lastRegularPage = undefined\n\n            this.backStack = []\n        },\n\n        refresh: function () {\n            reloadAllPageImages()\n            this.currentPage.show()\n        },\n        onKeyEscape: function () {\n            // If the current page has some overlay open then close it\n            const page = this.currentPage\n            if (page.hideCurrentOverlays()) {\n                return true\n            }\n            // If the current page is modal then close it and go to the last non-modal page\n            if (this.currentPage.isModal) {\n                viewer.goBack()\n                return true\n            }\n            return false\n        },\n        next: function () {\n            var page = this.getNextUserPage(this.currentPage)\n            if (!page) return\n            this.goToPage(page.index);\n        },\n        previous: function () {\n            var page = this.getPreviousUserPage(this.currentPage)\n            if (!page) return\n            this.goToPage(page.index);\n        },\n        getFirstUserPage: function () {\n            var first = this.userStoryPages[0]\n            return first ? first : null\n        },\n        getNextUserPage: function (page) {\n            var nextUserIndex = page ? page.userIndex + 1 : 0\n            if (nextUserIndex >= this.userStoryPages.length) return null\n            return this.userStoryPages[nextUserIndex]\n        },\n        getPreviousUserPage: function (page) {\n            var prevUserIndex = page ? page.userIndex - 1 : -1\n            if (prevUserIndex < 0) return null\n            return this.userStoryPages[prevUserIndex]\n        },\n        toggleLinks: function (newState = undefined) {\n            this.highlightLinks = newState != undefined ? newState : !this.highlightLinks\n            this.refresh_update_links_toggler(this.currentPage)\n            this._updateLinksState()\n        },\n\n        toogleLayout: function (newState = undefined) {\n            this.showLayout = newState != undefined ? newState : !this.showLayout\n            div = $('#content')\n\n            if (this.showLayout) {\n                this.currentPage.showLayout()\n                div.addClass(\"contentLayoutVisible\")\n            } else\n                div.removeClass(\"contentLayoutVisible\")\n        },\n\n\n\n        _updateLinksState: function (showLinks = undefined, div = undefined) {\n            if (undefined == div) {\n                if (this.currentPage.isModal) {\n                    div = $('#content-modal')\n                } else {\n                    div = $('#content')\n                }\n            }\n            if (undefined == showLinks) showLinks = this.highlightLinks\n            if (showLinks)\n                div.addClass(\"contentLinksVisible\")\n            else\n                div.removeClass(\"contentLinksVisible\")\n        },\n\n        showHints: function () {\n            var text = this.currentPage.annotations;\n            if (text == undefined) return;\n            alert(text);\n        },\n        hideNavbar: function () {\n            $('#nav').slideToggle('fast', function () {\n                $('#nav-hide').slideToggle('fast').removeClass('hidden');\n            });\n        },\n        showNavbar: function () {\n            $('#nav-hide').slideToggle('fast', function () {\n                $('#nav').slideToggle('fast');\n            }).addClass('hidden');\n        },\n\n\n        handleStateChanges: function (e) {\n            viewer.urlLocked = true\n            viewer.currentPage.hide(true, true)\n            viewer.currentPage = null\n\n            viewer.initParseGetParams()\n            viewer.handleNewLocation(true)\n            viewer.urlLocked = false\n        },\n    };\n}\n\n// ADD | REMOVE CLASS\n// mode ID - getELementByID\n// mode CLASS - getELementByClassName\n\nfunction addRemoveClass(mode, el, cls) {\n\n    var el;\n\n    switch (mode) {\n        case 'class':\n            el = document.getElementsByClassName(el)[0];\n            break;\n\n        case 'id':\n            el = document.getElementById(el);\n            break;\n    }\n\n    if (el.classList.contains(cls)) {\n        el.classList.remove(cls)\n    } else {\n        el.classList.add(cls);\n    }\n}\n\n// Redirect from legacy format URL\n//    https://site.com/dd/index.html#home/o/10?shared  \n// to the new\n//    https://site.com/dd/index.html?home&o=10&shared=true\n\nfunction redirectFromHashToSearch() {\n    const loc = document.location\n    if (loc.hash == null || loc.hash.length == \"\") return false\n\n    let url = loc.protocol + \"//\" + loc.host + loc.pathname\n\n    if (loc.hash.indexOf('/') > 0) {\n        let hash = loc.hash\n        // read additonal parameters\n        var args = hash.split('/')\n        // check for link to click\n        let search = hash.substring(0, hash.indexOf('/'))\n        search = '?' + search.replace(/^[^#]*#?(.*)$/, '$1');\n        if (args[1] == 'o') {\n            search += \"&o=\" + args[2]\n        }\n        url += search\n    } else {\n        url += \"?\" + loc.hash.substring(1)\n    }\n    if (null != loc.search && \"\" != loc.search) {\n        let search = loc.search.substring(1)\n        if (\"embed\" == search) {\n            url += \"&e=1\"\n        }\n    }\n    //\n    document.location = url\n    return true\n}\nfunction handleStateChanges(e) {\n    viewer.handleStateChanges(e)\n}\n\n$(document).ready(function () {\n    if (redirectFromHashToSearch()) return\n\n    viewer.initialize();\n    if (!!('ontouchstart' in window) || !!('onmsgesturechange' in window)) {\n        $('body').removeClass('screen');\n    }\n\n    viewer.handleNewLocation(true)\n    if (!viewer.isEmbed) preloadAllPageImages();\n\n    window.addEventListener('popstate', handleStateChanges);\n    $(window).hashchange(handleStateChanges);\n\n    viewer.zoomContent()\n    viewer.initializeLast()\n});\n"
  },
  {
    "path": "docs/Tokens/Lib/_pt-assets/UIKit/inspector.json",
    "content": "{\"styles\":{\"Controls/Buttons/Primary/Background\":{\"tokens\":[[\"background-color\",\"@btn-background-color\"],[\"border-color\",\"@btn-border-color\"],[\"border-width\",\"@btn-border-width\"],[\"border-radius\",\"@btn-border-radius\"]]},\"Controls/Buttons/Primary/Label\":{\"tokens\":[[\"color\",\"@btn-label-color\"],[\"font-size\",\"@btn-label-font-size\"]]}},\"colors__\":{},\"attrs\":{},\"Controls/Buttons\":{\"layers\":{\"Rectangle\":{\"tokens\":[[\"background-color\",\"@btn-background-color\"],[\"border-color\",\"@btn-border-color\"],[\"border-width\",\"@btn-border-width\"],[\"border-radius\",\"@btn-border-radius\"]]},\"Button\":{\"tokens\":[[\"color\",\"@btn-label-color\"],[\"font-size\",\"@btn-label-font-size\"]]}}}}"
  },
  {
    "path": "docs/Tokens/Lib/_pt-assets/UIKit/vars.json",
    "content": "{\"@brand-color\":\"blue\",\"@btn-background-color\":\"#6666ff\",\"@btn-border-color\":\"#000099\",\"@btn-border-width\":\"2px\",\"@btn-border-radius\":\"4px\",\"@btn-label-color\":\"white\",\"@btn-label-font-size\":\"14px\"}"
  },
  {
    "path": "docs/Tokens/Lib/_pt-assets/UIKit/vars.scss",
    "content": "$brand-color: blue;\n$btn-background-color: #6666ff;\n$btn-border-color: #000099;\n$btn-border-radius: 4px;\n$btn-border-width: 2px;\n$btn-label-color: white;\n$btn-label-font-size: 14px;\n"
  },
  {
    "path": "docs/Tokens/Lib/design-tokens.less",
    "content": "@brand-color: blue;\n\n@btn-background-color: lighten(@brand-color, 20%);\n@btn-border-color: darken(@brand-color, 20%);\n@btn-border-width: 2px;\n@btn-border-radius: 4px;\n@btn-label-color: white;\n@btn-label-font-size: 14px;"
  },
  {
    "path": "docs/Tokens/Lib/sketch-styles.less",
    "content": "@import \"design-tokens.less\";\n\n.Controls {\n    .Buttons {\n        .Primary {\n            .Background {\n                background-color: @btn-background-color;\n                border-color: @btn-border-color;\n                border-width: @btn-border-width;\n                border-radius: @btn-border-radius;\n            }\n            .Label {\n                color: @btn-label-color;\n                font-size: @btn-label-font-size;\n            }\n        }\n    }\n}"
  },
  {
    "path": "examples/Link-ModalArtboard/Link-ModalArtboard-vars.json",
    "content": "{\n    \"space-xxx-large\": \"60px\",\n    \"space-xx-large\": \"50px\",\n    \"space-x-large\": \"30px\",\n    \"space-large\": \"25px\",\n    \"space-medium\": \"20px\",\n    \"space-small\": \"15px\",\n    \"space-x-small\": \"10px\",\n    \"space-xx-small\": \"5px\",\n    \"font-xxxx-large\": \"48px\",\n    \"font-xxxx-large-spacing\": \"0.96px\",\n    \"font-xxx-large\": \"32px\",\n    \"font-xxx-large-spacing\": \"0.64px\",\n    \"font-xx-large\": \"24px\",\n    \"font-xx-large-spacing\": \"0.48px\",\n    \"font-x-large\": \"18.7px\",\n    \"font-x-large-spacing\": \"0.374px\",\n    \"font-large\": \"16px\",\n    \"font-large-spacing\": \"0.64px\",\n    \"font-medium\": \"13.3px\",\n    \"font-medium-spacing\": \"0.532px\",\n    \"font-small\": \"12px\",\n    \"font-small-spacing\": \"0.48px\",\n    \"font-x-small\": \"10.7px\",\n    \"font-x-small-spacing\": \"0.428px\",\n    \"font-xx-small\": \"9px\",\n    \"font-xx-small-spacing\": \"0.36px\",\n    \"font-weight-bold\": \"700\",\n    \"font-weight-semibold\": \"500\",\n    \"font-weight-medium\": \"500\",\n    \"font-weight-normal\": \"400\",\n    \"line-height-large\": \"1.5\",\n    \"line-height-medium\": \"1.25\",\n    \"line-height-small\": \"1.1\",\n    \"radius\": \"2px\",\n    \"radius-circle\": \"100%\",\n    \"radius-large\": \"10px\",\n    \"radius-medium\": \"3px\",\n    \"radius-small\": \"2px\",\n    \"radius-none\": \"0px\",\n    \"border\": \"1px\",\n    \"border-large\": \"2px\",\n    \"border-medium\": \"1px\",\n    \"border-none\": \"0px\",\n    \"font-face-heading\": \"\\\"Neue Haas Grotesk Display Std\\\", -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, \\\"Helvetica Neue\\\", Arial, sans-serif\",\n    \"font-face-regular\": \"\\\"Neue Haas Grotesk Display Std\\\", -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, \\\"Helvetica Neue\\\", Arial, sans-serif\",\n    \"font-face-mono\": \"SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n    \"button-large\": \"50px\",\n    \"button-medium\": \"35px\",\n    \"button-small\": \"20px\",\n    \"shadow\": \"0 2px 5px rgba(0, 0, 0, 0.1)\",\n    \"shadow-xx-large\": \"0 0 55px rgba(0, 0, 0, 0.1)\",\n    \"shadow-x-large\": \"0 0 40px rgba(0, 0, 0, 0.1)\",\n    \"shadow-large\": \"0px 5px 10px rgba(0, 0, 0, 0.1)\",\n    \"shadow-medium\": \"0 2px 5px rgba(0, 0, 0, 0.1)\",\n    \"shadow-small\": \"none\",\n    \"shadow-hovered\": \"0 15px 25px 0 rgba(0, 0, 0, 0.2)\",\n    \"shadow-none\": \"none\",\n    \"opacity-disabed\": \"0.5\",\n    \"viewport-mobile\": \"480px\",\n    \"viewport-tablet\": \"768px\",\n    \"viewport-laptop\": \"1440px\",\n    \"viewport-desktop\": \"1920px\",\n    \"color-brand\": \"#408CF0\",\n    \"color-primary\": \"#408CF0\",\n    \"color-secondary\": \"#8C8C8C\",\n    \"color-black\": \"#32272F\",\n    \"color-gray\": \"#8C8C8C\",\n    \"color-white\": \"#ffffff\",\n    \"color-danger\": \"#d83a3a\",\n    \"color-warning\": \"#ff8e2a\",\n    \"color-success\": \"#60C060\",\n    \"color-progress\": \"#8c8c8c\",\n    \"color-highlight\": \"yellow\",\n    \"color-neutral-darkest\": \"#7f7f7f\",\n    \"color-neutral-darker\": \"#8c8c8c\",\n    \"color-neutral-dark\": \"#999999\",\n    \"color-neutral\": \"#a6a6a6\",\n    \"color-neutral-light\": \"#bfbfbf\",\n    \"color-neutral-lighter\": \"#d9d9d9\",\n    \"color-neutral-lightest\": \"#e5e5e5\",\n    \"color-background-brand\": \"#408CF0\",\n    \"color-background-primary\": \"#408CF0\",\n    \"color-background-primary-active\": \"#1270eb\",\n    \"color-background-primary-hovered\": \"#1270eb\",\n    \"color-background-secondary\": \"#8C8C8C\",\n    \"color-background-secondary-active\": \"#737373\",\n    \"color-background-secondary-hovered\": \"#737373\",\n    \"color-background-danger\": \"#d83a3a\",\n    \"color-background-warning\": \"#ff8e2a\",\n    \"color-background-success\": \"#60C060\",\n    \"color-background-progress\": \"#8c8c8c\",\n    \"color-background-highlight\": \"yellow\",\n    \"color-background-ghost\": \"transparent\",\n    \"color-background-disabled\": \"#d9d9d9\",\n    \"color-background-table-head\": \"#ffffff\",\n    \"color-background-table-row\": \"#ffffff\",\n    \"color-background-table-row-alt-1\": \"#ffffff\",\n    \"color-background-table-row-alt-2\": \"#e5e5e5\",\n    \"color-background-table-row-hovered\": \"#ffffff\",\n    \"color-background-table-row-selected\": \"#e5e5e5\",\n    \"color-background-table-column\": \"#ffffff\",\n    \"color-background-table-column-alt-1\": \"#E9EDF0\",\n    \"color-background-table-column-alt-2\": \"#ffffff\",\n    \"color-background-table-column-hovered\": \"#e5e5e5\",\n    \"color-background-table-column-selected\": \"#e5e5e5\",\n    \"color-background-stage\": \"#F8F9FB\",\n    \"color-background-container\": \"white\",\n    \"color-background-overlay\": \"rgba(0, 0, 0, 0.5)\",\n    \"color-background-wizard-preline\": \"linear-gradient(#8C8C8C, #408CF0)\",\n    \"color-text\": \"#32272F\",\n    \"color-text-brand\": \"#408CF0\",\n    \"color-text-primary\": \"#32272F\",\n    \"color-text-secondary\": \"#8c8c8c\",\n    \"color-text-active\": \"#408CF0\",\n    \"color-text-highlight\": \"yellow\",\n    \"color-text-disabled\": \"#a6a6a6\",\n    \"color-text-danger\": \"#d83a3a\",\n    \"color-text-warning\": \"#ff8e2a\",\n    \"color-text-success\": \"#60C060\",\n    \"color-text-progress\": \"#8c8c8c\",\n    \"color-border\": \"#d9d9d9\",\n    \"color-border-brand\": \"#408CF0\",\n    \"color-border-primary\": \"#408CF0\",\n    \"color-border-secondary\": \"#8C8C8C\",\n    \"color-border-neutral\": \"#d9d9d9\",\n    \"color-border-separator\": \"#d9d9d9\",\n    \"color-border-disabled\": \"#bfbfbf\",\n    \"color-border-active\": \"#408CF0\",\n    \"color-border-danger\": \"#d83a3a\",\n    \"color-border-warning\": \"#ff8e2a\",\n    \"color-border-success\": \"#60C060\",\n    \"color-border-progress\": \"#8c8c8c\",\n    \"color-text-brand-contrast\": \"#ffffff\",\n    \"color-text-primary-contrast\": \"#ffffff\",\n    \"color-text-secondary-contrast\": \"rgba(255, 255, 255, 0.7)\",\n    \"color-text-active-contrast\": \"#ffffff\",\n    \"color-text-highlight-contrast\": \"#000000\",\n    \"color-text-disabled-contrast\": \"rgba(0, 0, 0, 0.2)\",\n    \"color-text-danger-contrast\": \"#ffffff\",\n    \"color-text-warning-contrast\": \"#ffffff\",\n    \"color-text-success-contrast\": \"#ffffff\",\n    \"color-text-progress-contrast\": \"#ffffff\",\n    \"top-bar-background\": \"#ffffff\",\n    \"top-bar-background-shadow\": \"0 0 30px rgba(71, 72, 90, 0.15)\",\n    \"top-bar-background-image\": \"transparent\",\n    \"top-bar-logo\": \"../images/panel-logo.png\",\n    \"cb-top-bar-logo\": \"../images/cb-panel-logo.png\",\n    \"top-bar-item-text\": \"#32272F\",\n    \"top-bar-item-text-active\": \"#408CF0\",\n    \"top-bar-item-text-hovered\": \"#408CF0\",\n    \"top-bar-item-text-secondary\": \"rgba(50, 39, 47, 0.9)\",\n    \"top-bar-item-text-secondary-hovered\": \"#8c8c8c\",\n    \"top-bar-item-background\": \"transparent\",\n    \"top-bar-item-background-active\": \"#ffffff\",\n    \"top-bar-item-background-hovered\": \"#ffffff\",\n    \"top-bar-item-font\": \"13.3px\",\n    \"top-bar-item-font-spacing\": \"0.532px\",\n    \"top-bar-item-weight\": \"500\",\n    \"top-bar-item-icon-font\": \"24px\",\n    \"top-bar-sub-background\": \"#ffffff\",\n    \"top-bar-sub-background-active\": \"#ffffff\",\n    \"top-bar-sub-background-hovered\": \"#408CF0\",\n    \"top-bar-sub-text\": \"#32272F\",\n    \"top-bar-sub-text-active\": \"#408CF0\",\n    \"top-bar-sub-text-hovered\": \"#ffffff\",\n    \"top-bar-sub-text-secondary\": \"#8c8c8c\",\n    \"top-bar-sub-text-secondary-hovered\": \"#8c8c8c\",\n    \"top-bar-sub-font\": \"13.3px\",\n    \"top-bar-sub-font-spacing\": \"0.532px\",\n    \"top-bar-sub-icon-font\": \"13.3px\",\n    \"side-bar-background\": \"#F8F9FB\",\n    \"side-bar-collapsed-background\": \"#ffffff\",\n    \"side-bar-item-text\": \"#32272F\",\n    \"side-bar-item-text-active\": \"#408CF0\",\n    \"side-bar-item-text-hovered\": \"#408CF0\",\n    \"side-bar-item-text-secondary\": \"#8c8c8c\",\n    \"side-bar-item-text-secondary-hovered\": \"#8c8c8c\",\n    \"side-bar-item-font\": \"13.3px\",\n    \"side-bar-item-icon-font\": \"24px\",\n    \"side-bar-item-font-spacing\": \"0.532px\",\n    \"side-bar-item-background\": \"transparent\",\n    \"side-bar-item-background-active\": \"transparent\",\n    \"side-bar-item-background-hovered\": \"transparent\",\n    \"side-bar-sub-background\": \"transparent\",\n    \"side-bar-sub-text\": \"#32272F\",\n    \"side-bar-sub-item-background\": \"transparent\",\n    \"side-bar-sub-item-background-active\": \"transparent\",\n    \"side-bar-sub-item-background-hovered\": \"transparent\",\n    \"side-bar-sub-item-text\": \"#32272F\",\n    \"side-bar-sub-item-text-active\": \"#408CF0\",\n    \"side-bar-sub-item-text-hovered\": \"#408CF0\",\n    \"side-bar-sub-item-text-secondary\": \"#8c8c8c\",\n    \"side-bar-sub-item-text-secondary-hovered\": \"#8c8c8c\",\n    \"side-bar-sub-item-font\": \"13.3px\",\n    \"side-bar-sub-item-font-spacing\": \"0.532px\",\n    \"side-bar-sub-item-icon-font\": \"13.3px\",\n    \"login-head-background\": \"\\\"none\\\"\",\n    \"login-page-background\": \"../images/login_page_bckgr.jpg\",\n    \"speed-slow\": \"all 0.9s ease\",\n    \"speed-medium\": \"all 0.2s ease\",\n    \"speed-fast\": \"all 0.1s ease\",\n    \"duration-long\": \"0.9s\",\n    \"duration-medium\": \"0.2s\",\n    \"duration-short\": \"0.1s\",\n    \"transition-fadein\": \"'.transit .fadeIn'\",\n    \"transition-slidein-right\": \"'.transit .slideInRight'\",\n    \"transition-slidein-left\": \"'.transit .slideInLeft'\",\n    \"transition-slidein-up\": \"'.transit .slideInUp'\",\n    \"transition-slidein-down\": \"'.transit .slideInDown'\",\n    \"transition-fadeout\": \"'.transit .fadeOut'\",\n    \"transition-slideout-right\": \"'.transit .slideOutRight'\",\n    \"transition-slideout-left\": \"'.transit .slideOutLeft'\",\n    \"transition-slideout-up\": \"'.transit .slideOutUp'\",\n    \"transition-slideout-down\": \"'.transit .slideOutDown'\"\n}"
  },
  {
    "path": "examples/Link-ModalArtboard/Link-ModalArtboard-viewer.css",
    "content": "/* MOD FOR VARIANT B WAS @color-background-primary*/\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  from {\n    opacity: 1;\n  }\n  to {\n    opacity: 0;\n  }\n}\n@keyframes slideInDown {\n  from {\n    transform: translate3d(0, -100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInLeft {\n  from {\n    transform: translate3d(-100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInRight {\n  from {\n    transform: translate3d(100%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideInUp {\n  from {\n    transform: translate3d(0, 100%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 100%, 0);\n  }\n}\n@keyframes slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-100%, 0, 0);\n  }\n}\n@keyframes slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(100%, 0, 0);\n  }\n}\n@keyframes slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -100%, 0);\n  }\n}\n.fadeIn {\n  animation-name: fadeIn;\n}\n.fadeOut {\n  animation-name: fadeOut;\n}\n.slideInDown {\n  animation-name: slideInDown;\n}\n.slideInLeft {\n  animation-name: slideInLeft;\n}\n.slideInRight {\n  animation-name: slideInRight;\n}\n.slideInUp {\n  animation-name: slideInUp;\n}\n.slideOutDown {\n  animation-name: slideOutDown;\n}\n.slideOutLeft {\n  animation-name: slideOutLeft;\n}\n.slideOutRight {\n  animation-name: slideOutRight;\n}\n.slideOutUp {\n  animation-name: slideOutUp;\n}\n.transit {\n  animation-duration: 0.2s;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "server_tools/announce.php",
    "content": "<?php\n\n/*\n    To announce published mockups in Telegram channel you need:\n    1. Create a Telegram public channel\n    2. Create a Telegram BOT via BotFather\n    3. Set the bot as administrator in your channel    \n    See details here: https://medium.com/@xabaras/sending-a-message-to-a-telegram-channel-the-easy-way-eb0a0b32968\n    4. Specify parameters in config.js file\n    5. Be sure that your web server can write/read data.raw and config.json files. \n    I used the following commangs:\n    chcon -R -t httpd_sys_content_t .\n    chcon -R -t httpd_sys_rw_content_t  .\n    chown -R apache:apache config.json\n    chown -R apache:apache data.raw \n*/\n\ninclude_once \"lib/FolderInfo.php\";\n\nfunction getParam($key){\n    if(!array_key_exists($key,$_GET)) return \"\";\n    return $_GET[$key];\n}\n\nfunction printError($error){\n    error_log($error);\n    print($error.\"<br/>\");\n}\n\n\nclass Worker{\n    public $ref = \"\";\n    public $base_url = \"\";\n    public $data = [];\n\n    public function __construct() \n    {\n        $this->ref = getenv(\"HTTP_REFERER\");\n\n        // base_url = https://myserver.com\n        $this->base_url = (getenv(\"HTTPS\")==\"on\"?\"https://\":\"http://\").getenv(\"SERVER_NAME\");\n\n        // local_url = /_tools/\n        $local_url = $_SERVER['REQUEST_URI'];\n        $this->local_url = substr($local_url,0,strrpos($local_url,\"/announce.php\")+1);\n\n        // local_path = /var/www/html/_tools\n        $local_path = $_SERVER['SCRIPT_FILENAME'];\n        $this->local_path = substr($local_path,0,strrpos($local_path,\"/\"));\n\n        // READ CONFIG\n        $config_text = file_get_contents(\"config.json\");\n        if(FALSE===$config_text){\n            $this->config = [];     \n            printError(\"Error: can not open config.json\");                    \n        }else{\n            $this->config = json_decode($config_text,TRUE);\n        }\n    }\n\n    private function _readParams(){\n        // read params\n        $this->skip_tele = getParam(\"NOTELE\");\n        $this->skip_save = getParam(\"NOSAVE\");\n        \n\n        $secret = $_GET[\"sec\"].\"\";\n        if($secret!=$this->config['secret-key']){\n            printError(\"Error: secrets are not the same \");\n            return FALSE;\n        }\n\n        $data = array(\n            'time' => time(),\n            'dir' => $_GET[\"dir\"].\"\",       //  myproject/77\n            'author' => $_GET[\"author\"].\"\",\n            'email' => $_GET[\"email\"].\"\",\n            'message' => $_GET[\"msg\"].\"\",\n            'ver' => $_GET[\"ver\"].\"\",       // 77\n            'down_ver' => '',               // will be specified later\n            'screens_changed' => []        //[  support_1@2x.png  ]\n        );\n        \n        // check params\n        if(''==$data['dir']){\n            printError(\"Error: 'dir' is empty \");\n            return FALSE;\n        }\n        if(''==$data['author']){\n            printError(\"Error: 'author' is empty\");\n            return FALSE;\n        }\n        if(''==$data['message']){\n            printError(\"Error: 'msg' is empty\");\n            return FALSE;\n        }\n\n        // post-process\n        $data['url'] = $this->base_url.\"/\".$data['dir'];\n\n        return $data;\n    }\n\n    private function _findPrevVersion(){        \n        // get more info about published version\n\n        $folder_info = new FolderInfo();\n        if(!$folder_info->collectInfo($this->data['dir'])){\n            printError(\"Error: can not get version folder information\");\n            return FALSE; \n        }\n\n        $this->data['down_ver'] = $folder_info->down_ver;\n        $this->local_dir = $folder_info->local_dir;\n        \n        return TRUE;\n    }\n\n    private function _compareVers(){\n        if( ''==$this->data['down_ver'] ) return TRUE;\n        $cmd = \"diff -rq\";\n        $cmd = $cmd.\" \".$this->local_path.\"/\".$this->local_dir.\"/\".$this->data['down_ver'];\n        $cmd = $cmd.\" \".$this->local_path.\"/\".$this->local_dir.\"/\".$this->data['ver'];\n        $cmd = $cmd.\" | grep /images/full\";\n\n        $res = shell_exec($cmd);\n        if(NULL==$res) return TRUE;\n\n        $this->data['screens_changed'] = [];\n        $this->data['journals_path'] = str_replace(\"//\",\"/\",$this->local_url).\"journals\";\n       \n        \n        $lines = explode(\"\\n\",$res);\n        foreach($lines as $line){           \n            $cmd_diff = \"\";\n            //\n            $info = $lines = explode(\" \",$line);   \n            $image_base_url = $this->base_url.\"/\".$this->data['dir'].\"/images/\";\n            $screen_base_url = $this->base_url.\"/\".$this->data['dir'].\"/?\";\n            \n            $is_new = FALSE;\n            $file_info = NULL;\n            $path_diff = '';\n\n            if('Files'==$info[0]){\n                // Checking this format:             \n                // Files /var/www/html/test/101/support_1@2x.png and /var/www/html/test/102/support_1@2x.png differ\n                $file_info =  pathinfo($info[3]);\n                // compare images               \n                {\n                    $path_new = $info[3];\n                    $path_prev = str_replace(\"/\".$this->data['ver'].\"/\",\"/\".$this->data['down_ver'].\"/\",$info[3]);\n                    $dir_diff = 'journals/'.$this->data['dir'].\"/diffs\";                    \n\n                    if(!file_exists($dir_diff) && !mkdir($dir_diff,0777,TRUE)){\n                        printError(\"Error: can not create folder \".$dir_diff);\n                        return FALSE;\n                    }                                    \n                    $path_diff = $dir_diff.\"/\".$file_info['basename'];\n\n                    $cmd_diff .=  ($cmd_diff!=''?'; ':'').\"convert $path_prev $path_new  \\( -clone 0 -clone 1 -compose difference -composite \\) \\( -clone 1 -fill red -colorize 10% \\) -delete 1 +swap -compose over -composite  $path_diff 2>/dev/null >/dev/null\";\n                }\n            }else if('Only'==$info[0]){\n                // Checking this format:             \n                // Only in /var/www/html/test/101/images: support_1@2x.png\n                $file_info =  pathinfo($info[3]);\n                $is_new = TRUE;\n            }\n\n            if(NULL==$file_info) continue;\n            // Skip internal system artboards\n\t\t   if(strpos($file_info['filename'],\"__\")===0) continue;\n\n            $image_name = $file_info['basename'];\n            $screen_name = $file_info['filename'];\n\n            // skip Retina duplicate\n            if(strpos($image_name,'@2x.png')) continue;\n\n            $this->data['screens_changed'][] = [\n                'is_new'      => $is_new,\n                'is_diff'     => !$is_new,\n                'screen_url' =>  $screen_base_url.$screen_name,\n                'image_url' =>  $image_base_url.$image_name\n            ];\n            //\n            if($cmd_diff!=''){\n                shell_exec($cmd_diff);\n            }\n        }\n\n \n    }\n\n    private function _saveData(){\n        if($this->skip_save!='') return TRUE;\n\n        // Save data to project subfolder folder\n        $dir_path =  str_replace(\"../\",\"journals/\",$this->local_dir);\n        if(!file_exists($dir_path) && !mkdir($dir_path,0777,TRUE)){\n            printError(\"Error: can not create folder \".$dir_path);\n            return FALSE;\n        }\n\n        $path = $dir_path.\"/journal.txt\";\n        $text = json_encode($this->data,JSON_UNESCAPED_SLASHES).\",\\n\";\n        \n        if(FALSE==file_put_contents($path,$text,FILE_APPEND)){\n            printError(\"Error: can not open file \".$path);\n            return FALSE;\n        }       \n        \n        $summary_rec = [\n            'time' => $this->data['time'],\n            'dir' => $dir_path,\n        ];\n        file_put_contents(\"journals/journals.txt\", json_encode($summary_rec,JSON_UNESCAPED_SLASHES).\",\\n\",FILE_APPEND);\n\n        return TRUE;\n    }\n\n    private function _saveSplitData($local_dir,$data){\n\n        // Save data to project subfolder folder\n        $dir_path =  str_replace(\"../\",\"journals/\",$local_dir);\n        if(!file_exists($dir_path) && !mkdir($dir_path,0777,TRUE)){\n            printError(\"Error: can not create folder \".$dir_path);\n            return FALSE;\n        }\n\n        $path = $dir_path.\"/journal.txt\";\n        $text = json_encode($data,JSON_UNESCAPED_SLASHES);\n        $text = preg_replace(\"/^\\[{1}/\",\"\",$text);\n        $text = preg_replace(\"/\\]{1}$/\",\",\\n\",$text);\n        \n        if(FALSE==file_put_contents($path,$text,$options)){\n            printError(\"Error: can not open file \".$path);\n            return FALSE;\n        }       \n        \n\n        return TRUE;\n    }\n\n\n    private function _postToTelegram(){\n        if($this->skip_tele!='') return TRUE;\n\n        $apiToken =  $this->config['telegram-bot-token'];\n        $channelID =  $this->config['telegram-channel-id'];\n\n        if(''==$apiToken) return TRUE;\n    \n        // Take the fist new/updated page\n        $pageURL = $this->data['url'];\n        if(count($this->data['screens_changed'])){\n            $pageURL = $this->data['screens_changed'][0]['screen_url'];\n        }\n\n        $data = [\n            'chat_id' =>  $channelID,\n            'text' => $this->data['author'].' published '. $pageURL. \" \\n- \". $this->data['message']\n        ];\n\n        $response = file_get_contents(\"https://api.telegram.org/bot$apiToken/sendMessage?\" . http_build_query($data) );\n\n        return TRUE;\n    }\n\n\n    private function  _splitData(){\n        $file_data = file_get_contents(\"data.raw\");\n        if(FALSE===$file_data) $file_data=\"\";\n        $text_data = \"[\".$file_data.\"[]]\";        \n        $data = json_decode($text_data,TRUE);\n\n        // group all records per project\n        $new_projects = [];\n        $new_data = [];\n        foreach($data as $old_rec){\n            if(\"\"==$old_rec['dir']) continue;\n            // cut /234\n            $ver_pos = strrpos($old_rec['dir'], \"/\" );\n            if(FALSE===$ver_pos)\n                $dir = $old_rec['dir'];\n            else \n                $dir = substr( $old_rec['dir'] ,0, $ver_pos);\n\n            print(\"found '\".$old_rec['dir'].\"'<br/>\"); \n\n            if( !array_key_exists($dir,$new_projects) ){\n                $new_projects[$dir] = [];\n            }\n            $new_projects[$dir][] = $old_rec;\n            \n            // \n            $new_data[] = [\n                'time' => $old_rec['time'],\n                'dir' => $dir \n            ];\n\n            // \n        }\n        \n        // save every project\n        foreach(array_keys($new_projects) as $dir){\n            $this->_saveSplitData(\"../\".$dir,$new_projects[$dir]);\n            print(\"saved \".$dir.\"<br/>\");\n        }\n\n        // \n        $new_data_text = json_encode($new_data,JSON_UNESCAPED_SLASHES);\n        $new_data_text = preg_replace(\"/^\\[{1}/\",\"\",$new_data_text);\n        $new_data_text = preg_replace(\"/\\]{1}$/\",\",\\n\",$new_data_text);\n\n        file_put_contents(\"journals/journals.txt\", $new_data_text);\n    }\n\n    public function run()\n    {\n        //$this->_splitData();\n        //return TRUE;\n        \n        // COLLECT DATA\n        $this->data = $this->_readParams();\n        if(FALSE===$this->data) return FALSE;\n\n        $this->_findPrevVersion();\n        $this->_compareVers();\n        \n        // SAVE DATA TO DISK\n        if(!$this->_saveData()) return FALSE;\n        \n        // INFORM SUBSCRIBERS\n        $this->_postToTelegram();\n\n        return TRUE;\n    }\n    \n\n  \n}\n\n\n$worker = new Worker();\n$worker->run();\n"
  },
  {
    "path": "server_tools/config.json",
    "content": "{\n    \"telegram-bot-token\": \"XXXXXX:XXXXXXXXXXXXXXXXXXXXX\",\n    \"telegram-channel-id\": \"@my_channel\",\n    \"secret-key\": \"\"\n}"
  },
  {
    "path": "server_tools/folder_info.php",
    "content": "<?php\n\ninclude_once \"lib/FolderInfo.php\";\n\n$parser = new FolderInfo();\nif($parser->collectInfo()){\n    $parser->dump_json();\n}else{\n    print(\"[]\");\n}\n?>"
  },
  {
    "path": "server_tools/journal.php",
    "content": "<?php\n\nclass Worker{\n    public $ref = \"\";\n    public $baseurl = \"\";\n    public $data = [];\n\n    public function __construct() \n    {\n        $this->baseurl = (getenv(\"HTTPS\")==\"on\"?\"https://\":\"http://\").getenv(\"SERVER_NAME\").\"/\";\n        $this->ref = getenv(\"HTTP_REFERER\");        \n    }\n\n   \n    public function _print_data() \n    {\n        $file_data = file_get_contents(\"data.raw\");\n        if(FALSE===$file_data) $file_data=\"\";\n        $text_data = \"[\".$file_data.\"[]]\";\n\n        print($text_data); \n        return TRUE;\n    }\n\n\n    public function run()\n    {\n\n        if(!$this->_print_data()) return FALSE;\n        return TRUE;\n    }\n\n  \n}\n\n\n$worker = new Worker();\n$worker->run();"
  },
  {
    "path": "server_tools/lib/FolderInfo.php",
    "content": "<?php\n\nfunction folderInfoFilter($var){\n    return (int)$var>0;\n}\n\n// result [string(starting from \"/\"),pos,integer]\nfunction find_int_in_string($text) {\n    preg_match('/\\/(\\d+)/', $text, $m, PREG_OFFSET_CAPTURE);    \n    if (sizeof($m))\n        return [$m[0][0],$m[0][1],(int)$m[1][0]];\n\n    return FALSE;\n}\n\nclass FolderInfo{\n    public $ref = \"\";\n\n    public $project_url = \"\";\n    public $localurl = \"\";\n    public $local_dir = \"\";\n    public $is_live = FALSE;\n    public $version = -1;\n    public $versions = [];\n\n    public $link_live = '';\n    public $link_ver = '';\n    public $link_up = '';\n    public $link_down = '';\n\n    public function __construct() \n    {\n        $this->baseurl = (getenv(\"HTTPS\")==\"on\"?\"https://\":\"http://\").getenv(\"SERVER_NAME\").\"/\";\n    }\n\n    // get requested parameters from HTTP_REFERER\n    // or from LOCAL_URL get parameter\n    private function _parse_context($local_url=\"\")\n    {           \n        if($local_url!=\"\"){\n            // accept local URL in GET parameters\n            $this->local_url = $local_url;\n        }else{\n            $ref = getenv(\"HTTP_REFERER\");\n            // check referrer is not empty and it contains base url\n            if($ref=='' || strpos($ref,($this->baseurl))!=0){\n                print(\"Error: Wrong context\");\n                return FALSE;\n            }\n            // extract local URL to mockups from referrer\n            $this->local_url = substr($ref,strlen($this->baseurl));\n        }\n\n        $localurl = $this->local_url;\n\n        // extract version information from local url\n        $vers_pos = strpos($localurl,\"/live/\");\n        if($vers_pos===FALSE)\n        {\n            // we not in live\n            $integer_info = find_int_in_string($localurl);\n           \n            // check if we not in /<NUMBER>\n            if($integer_info===FALSE) return FALSE;\n\n            $vers_pos = $integer_info[1];\n            $this->version = $integer_info[2];\n        }else{\n            $this->is_live = TRUE;\n        }\n\n        // drop version from local URL\n        $this->project_url = substr($localurl,0,$vers_pos);\n        $this->local_dir = '../'.$this->project_url;\n        \n        return TRUE;\n    }\n\n    private function _read_dir()\n    {\n        if(!is_dir( $this->local_dir )){\n            print(\"Error: Can't read folder: \".$this->local_dir);\n            return FALSE;\n        }\n        // get folder content\n        $raw_files = scandir( $this->local_dir );\n        // drop non-integers\n        $raw_files = array_filter($raw_files,\"folderInfoFilter\");\n        // sort \n        sort($raw_files, SORT_NUMERIC);\n        // sort reverse \n        $raw_files = array_reverse($raw_files);\n        // add \"live\" to begining\n        array_unshift($raw_files,\"live\");\n\n        $this->versions = $raw_files;\n        return TRUE;\n    }\n\n    private function _build_url($version){\n        return $this->baseurl.$this->project_url.\"/\".$version.\"/\";        \n    }\n    \n    private function _calc_links(){\n        $this->link_up = \"\";\n        $this->up_ver = \"\";\n        $this->down_ver = \"\";\n        $this->link_downr = \"\";\n\n\n        if($this->is_live){\n            $this->link_live = \"\";\n            $this->link_ver = $this->_build_url( $this->versions[1] );\n            $this->down_ver = count($this->versions)>2 ? $this->versions[2] : \"\";\n            $this->link_down = $this->down_ver!=''?$this->_build_url(  $this->down_ver ) : \"\";\n        }else{\n            $this->link_live  = $this->_build_url(  $this->versions[0] );\n            $this->link_ver = \"\";            \n\n            $ver_pos = array_search($this->version,$this->versions);\n\n            if($ver_pos==1){\n                // the actual version\n            }else{                \n                $this->up_ver = $this->versions[$ver_pos-1];\n                $this->link_up = $this->_build_url( $this->up_ver );\n            }\n\n            if($ver_pos==(count( $this->versions)-1)){\n                // the first version                \n            }else{                \n                $this->down_ver = $this->versions[$ver_pos+1];\n                $this->link_down = $this->_build_url( $this->down_ver );\n            }\n        }\n    }\n\n    public function collectInfo($local_url=\"\")\n    {\n        if(!$this->_parse_context($local_url)) return FALSE;\n        if(!$this->_read_dir()) return FALSE;       \n        $this->_calc_links();\n\n        return TRUE;\n    }\n\n    public function dump_json(){\n        print(json_encode($this,JSON_UNESCAPED_SLASHES));\n    }\n\n    public function debug(){\n        foreach($this->versions as $file)\n        {\n            echo \"$file<br/>\";\n        }\n    }\n}\n\n?>"
  },
  {
    "path": "server_tools/version_info.php",
    "content": "<?php\n\nfunction folderInfoFilter($var){\n    return (int)$var>0;\n}\n\n// result [string(starting from \"/\"),pos,integer]\nfunction find_int_in_string($text) {\n    preg_match('/\\/(\\d+)/', $text, $m, PREG_OFFSET_CAPTURE);    \n    if (sizeof($m))\n        return [$m[0][0],$m[0][1],(int)$m[1][0]];\n\n    return FALSE;\n}\n\nclass Worker{\n    public $ref = \"\";           // https://myserver.com/project/82/index.html#support_1\n    public $local_url = \"\";     // https://myserver.com/project/82/index.html#support_1\n    public $project_url = \"\";   // https://myserver.com/project\n    public $base_url = \"\";      // https://myserver.com/\n    public $local_dir= \"\";      // project/82\n    public $ver = \"\";           // 82\n    public $current_ver = \"\";   // 82 or live\n    public $result = [];\n\n    public $is_live = FALSE;\n\n    public function __construct() \n    {\n        $this->base_url = (getenv(\"HTTPS\")==\"on\"?\"https://\":\"http://\").getenv(\"SERVER_NAME\").\"/\";\n        $this->ver = $_GET[\"ver\"].\"\";\n    }\n\n    // get requested parameters from HTTP_REFERER\n    // or from LOCAL_URL get parameter\n    private function _parse_context($local_url=\"\")\n    {           \n        if($local_url!=\"\"){\n            // accept local URL in GET parameters\n            $this->local_url = $local_url;\n        }else{\n            $ref = getenv(\"HTTP_REFERER\");\n            // check referrer is not empty and it contains base url\n            if($ref=='' || strpos($ref,($this->base_url))!=0){\n                print(\"Error: Wrong context\");\n                return FALSE;\n            }\n            // extract local URL to mockups from referrer\n            $this->local_url = $ref;           \n        }\n\n        $localurl = $this->local_url;\n\n        // extract version information from local url\n        $vers_pos = strpos($localurl,\"/live/\");\n        if($vers_pos===FALSE)\n        {\n            // we not in live\n            $integer_info = find_int_in_string($localurl);\n           \n            // check if we not in /<NUMBER>\n            if($integer_info===FALSE) return FALSE;\n\n            $vers_pos = $integer_info[1];\n            $this->current_ver = $integer_info[2];\n        }else{\n            $this->is_live = TRUE;\n            $this->current_ver = \"live\";\n        }\n\n        // drop version from local URL\n        $this->project_url = substr($localurl,0,$vers_pos);\n        $this->local_dir = substr($this->project_url,strlen($this->base_url));\n        //print( $this->local_url);\n        //print( $this->base_url);\n        \n        return TRUE;\n    }\n\n    private function _tranformImageURLbyPreview($img){\n        $ver = \"/\".$this->ver.\"/images/\";\n        return str_replace($ver,($ver.\"previews/\"),$img);\n    }\n\n    private function _find_info(){\n        // read change journal into normalised $data\n        $file_data = file_get_contents(\"journals/\".$this->local_dir.\"/journal.txt\");        \n        if(FALSE===$file_data) $file_data=\"\";\n        $text_data = \"[\".$file_data.\"[]]\";        \n        $data = json_decode($text_data,TRUE);\n        \n        // resort changes from new to old\n        $data = array_filter($data,function($a){return array_key_exists('ver',$a); });\n        usort($data,function ($a, $b) { return intval($b['ver']) - intval($a['ver']) ;});\n\n        // precalculate some data\n        foreach($data as &$rec){\n            $this->_extend_rec_info($rec);\n        }\n        \n        $this->result['recs'] = $data;\n        return TRUE;\n    }\n\n    private function _extend_rec_info(& $rec){\n    \n        // replace image URL by preview image URL;\n        $changed = 0;\n        $new = 0;\n        foreach($rec['screens_changed'] as $index => $screen){\n            $rec['screens_changed'][$index]['image_url'] = $this->_tranformImageURLbyPreview ($screen['image_url']);\n            $screen_url = $rec['screens_changed'][$index]['screen_url'];\n\n            // exctract page name after #\n            $screen_name_pos = strpos($screen_url,'#');\n            $screen_name = FALSE===$screen_name_pos?\"\":substr($screen_url,$screen_name_pos+1);\n            $rec['screens_changed'][$index]['screen_name']  =  $screen_name;\n\n            if($screen['is_new']) $new++; else $changed++;\n        }\n        $rec['screens_total_new']=$new;\n        $rec['screens_total_changed']=$changed;\n    }\n\n    private function _build_url($version){\n        return $this->base_url.$this->project_url.\"/\".$version.\"/\";        \n    }\n  \n    public function collectInfo($local_url=\"\")\n    {\n        if(!$this->_parse_context($local_url)) return FALSE;\n        if(!$this->_find_info()) return FALSE;       \n\n        return TRUE;\n    }\n\n    public function dump_json(){\n        print(json_encode($this->result,JSON_UNESCAPED_SLASHES));\n    }\n\n}\n\n$parser = new Worker();\nif($parser->collectInfo()){\n    $parser->dump_json();\n}else{\n    print(\"[]\"); \n}\n?>\n"
  }
]