[
  {
    "path": ".editorconfig",
    "content": "[*]\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n\n*.md text\n\n*.eps binary\n*.gif binary\n*.ico binary\n*.jpeg binary\n*.jpg binary\n*.png binary\n*.svg binary\n*.tif binary\n*.tiff binary\n*.ttf binary\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n*.sql\n*.sqlite\n\n# OS files #\n############\n$RECYCLE.BIN/\n*.cab\n*.lnk\n*.msi\n*.msix\n*.msm\n*.msp\n*.stackdump\n.DS_Store\n.DS_Store?\n.Spotlight-V100\n.Trashes\n._*\nThumbs.db\n[Dd]esktop.ini\nehthumbs.db\nehthumbs_vista.db\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 arkenfox\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"
  },
  {
    "path": "README.md",
    "content": "# TorZillaPrint\n\nTorZillaPrint (TZP) aims to provide a comprehensive, all-in-one, fingerprinting test suite, nicely broken into suitable sections with relevant information together. Long term, the goal is to collect Gecko only fingerprint data (no PII) for analysis to see how many classifications each metric or section provides.\n\n#### 🟥 Fingerprints are ALWAYS loose\n\nA fingerprint is just a snapshot of data at any given time, and collected metrics can change for a number or reasons: such as zooming, resizing windows, moving windows, per site settings, etc. Snapshots of fingerprints can still be linked after the fact. Unless you know what is being collected and it's stability, then don't make assumptions. Always treat fingerprints as loose/fuzzy.\n\nTZP aims to make sure Tor Browser and RFP are protecting metrics where known, and to dig into more areas of interest to determine equivalency or possible entropy. Non-stable metrics are collected to provide as much information as possible for analysis.\n\n#### 🟪 What we do care about:\n- Gecko\n- Comparing Tor Browser with Firefox\n- First party only (for now)\n- Lowering entropy (or poison pills where appropriate)\n- Help. We'll take all the help we can get.\n\n#### 🟩 What we might care about:\n- Collecting data via submissions\n- Expanding to include tests that require third parties\n\n#### 🟧 What we don't care about:\n- non-Gecko\n- Extensions (except those used in Tor Browser if they affect tests)\n- Providing entropy figures which requires real world tests with one result per profile\n\n#### 🟦 Acknowledgments\n\nYou know who you are. We don't need to list everyone. You're doing this to make the world a better place - that's your reward. And that's about it, for now. If you want to contribute with your amazing skills - come in and say hello.\n\n<br>\nversion: draftv1.2<br>date: 10-Feb-2022\n"
  },
  {
    "path": "css/index.css",
    "content": "\n:root{\n\t/* backgrounds */\n\t--bg0: #fbfaf9; /* body */\n\t--bg1: #f09b9b;\n\t--bg2: #f0b89b;\n\t--bg3: #f0d49b;\n\t--bg4: #f0f09b;\n\t--bg5: #d4f09b;\n\t--bg6: #b8f09b;\n\t--bg7: #9bf09b;\n\t--bg8: #9bf0b8;\n\t--bg9: #9bf0d4;\n\t--bg10: #9bf0f0;\n\t--bg11: #9bd4f0;\n\t--bg12: #9bb8f0;\n\t--bg13: #9b9bf0;\n\t--bg14: #b89bf0;\n\t--bg15: #d49bf0;\n\t--bg16: #f09bf0;\n\t--bg17: #f09bd4;\n\t--bg18: #f09bb8;\n\t--bg67: #686868a6;\n\t--bg98: #ededed; /* overlaytop */\n\t--bg99: #a4a4a4; /* fingerprint and perf */\n\t--bggood: #bae6aa40;\n\t--bgbad: #fb80802e;\n\t/* text */\n\t--testh: #000; /* h2 title */\n\t--test0: #0b0b0b;  /* body */\n\t--test1: #be0f0f;  /* 190,  15,  15 | s218 */\n\t--test2: #c85a1e;  /* 200,  90,  30 | s188 */\n\t--test3: #c8870f;  /* 200, 135,  15 | s219 */\n\t--test4: #a0a00a;  /* 160. 160,  10 | s225 */\n\t--test5: #82b919;  /* 130, 185,  25 | s194 */\n\t--test6: #46be0f;  /*  70, 190,  15 | s214 */\n\t--test7: #0ab90a;  /*  10, 185,  10 | s229 */\n\t--test8: #0aa537;  /*  10, 165,  55 | s226 */\n\t--test9: #0a9164;  /*  10, 145, 100 | s222 */\n\t--test10: #14aaaa; /*  20, 170, 170 | s201 */\n\t--test11: #1482b4; /*  30, 130, 180 | s204 */\n\t--test12: #3c73e1; /*  60, 115, 225 | s187 */\n\t--test13: #5f5fd7; /*  95,  95, 215 | s152 */\n\t--test14: #8c60e6; /* 140,  95, 230 | s186 */\n\t--test15: #9137be; /* 145,  55, 190 | s141 */\n\t--test16: #d750d7; /* 215,  80, 215 | s160 */\n\t--test17: #b43d8c; /* 180,  60, 140 | s128 */\n\t--test18: #c81f5a; /* 200,  30,  90 | s188 */\n\t--test99: #808080; /* fp + perf */\n\t--testbad: #be0f0f;\n\t--testred: #be0f0f; /* not affected by basic mode */\n\t--testweight: bold;\n\t/* other */\n\t--txtbasic: #69004f;\n\t--txtindicate: #0aa537; /* color change of copy button */\n\t--txtlink: #5079cb;\n  --txtSize: 11px;\n  --txtSizeBigger: 18px;\n\t/* json colors */\n\t--jboolean: #058b00;\n\t--jkey: #0074e8;\n\t--jnull: #5c5c5f;\n\t--jstring: #dd00a9;\n}\n\n@media (prefers-color-scheme: light) {\n\t/* DARK mode: we only apply it with prefers-light to encourage dark reader extensions to trigger */\n\t:root{\n\t/* backgrounds */\n\t--bg0: #161b22;\n\t--bg67: #a2a2a2;\n\t--bg98: #161b22;\n\t--bg99: #808080;\n\t--bggood: none;\n\t--bgbad: none;\n\t/* text */\n\t--testh: #ffffff;\n\t--test0: #b3b3b3;\n\t--test1: #dc9d9d;\n\t--test2: #dcb29d;\n\t--test3: #dcc79d;\n\t--test4: #dcdc9d;\n\t--test5: #c7dc9d;\n\t--test6: #b2dc9d;\n\t--test7: #9ddc9d;\n\t--test8: #9ddcb2;\n\t--test9: #9ddcc7;\n\t--test10: #9ddcdc;\n\t--test11: #9dc7dc;\n\t--test12: #9db2dc;\n\t--test13: #9d9ddc;\n\t--test14: #b29ddc;\n\t--test15: #c79ddc;\n\t--test16: #dc9ddc;\n\t--test17: #dc9dc7;\n\t--test18: #dc9db2;\n\t--testbad: #ff8787;\n\t--testred: #ff8787;\n\t--testweight: normal;\n\t/* other */\n\t--txtbasic: #d4c1b3;\n\t--txtindicate: white;\n\t--txtlink: #9db2dc;\n\t/* json colors */\n\t--jstring: #ff7de9;\n\t--jboolean: #86de74;\n\t--jnull: #939395;\n\t--jkey: #75bfff;\n\t}\n}\n\n\n/* so all window measurements are the same: redundant since we contain everything in tzpBody\n\tbut it can't hurt: note we still measure scrollbars in elements */\nhtml {scrollbar-width: none;}\nbody {background-color: var(--bg0); color: var(--test0);}\n#tzpBody {\n\tposition: fixed;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\toverflow-y: auto;\n}\nh2 {color: var(--testh); font-size: 14px; text-align: center; margin-top: inherit;}\ncode {\n\tbackground: rgba(142, 142, 145, 0.25) !important;\n\tpadding: 2px 6px; /* top+bottom | left+ right */\n}\n.s67 {\n\tfonmt-weight: bold;\n\ttext-decoration: underline;\n}\n\na {color: black; text-decoration: none;}\na.blue {color: var(--txtlink); text-decoration: none;}\na.return {color: var(--txtlink); text-decoration: none;  font-size: 14px; line-height: 1.2em}\n.no_color {color: var(--test0);}\n.good {color: var(--test7); background-color: var(--bggood);}\n.bad {color: var(--testbad); background-color: var(--bgbad);}\n.red {color: var(--testred); background-color: var(--bgbad);} /* use in basic mode to enforce showing an issue e.g. mismatched metric counts */\n.faint {color: var(--test99);}\n.indicate {color: var(--txtindicate);}\n.hidden {display: none;}\n.health, .healthsilent {font-size: 10px;}\n.smaller {font-size: 11px;}\n.bigger {font-size: var(--txtSizeBigger);}\n.offscreen {\n\tposition: absolute !important;\n\ttop: -2000% !important;\n\tleft: 0px !important;\n}\n.bold {font-weight: bold;}\n.normal {font-weight: normal;}\n.mono {font-family: monospace, \"Courier New\"; font-size: var(--txtSize);}\n.strike {text-decoration: line-through;}\n.spaces {white-space: pre-wrap;}\n.nospaces {white-space: normal;}\n.perf {font-family: monospace, \"Courier New\"; font-size: 12px; white-space: pre-wrap;}\n.lies {color: var(--test99); text-decoration: underline;}\n.revert {all: revert;}\n\n/* JSON */\n.string {color: var(--jstring);}\n.boolean, .number {color: var(--jboolean);}\n.null {color: var(--jnull);}\n.key {color: var(--jkey);}\n\n/* buttons */\n.btn {\n\tdisplay: inline-block;\n\tfont-size: 12px;\n\tfont-family: monospace, \"Courier New\";\n\tpadding-left: 4px;\n\tpadding-right: 6px;\n\tcursor: pointer;\n}\n.btnright {\n\tpadding-right: 0px;\n}\n.btnb {cursor: pointer;}\n/* item metrics/counts: dotted, no padding, normal */\n.btnc {\n\tfont-weight: normal;\n\ttext-decoration: underline;\n\ttext-decoration-style: dotted;\n\tcursor: pointer;\n}\n.btns {\n\ttext-decoration: underline;\n\tpadding-left: 8px;\n\tpadding-right: 8px;\n\tcursor: pointer;\n}\n.btn0, .s0 {color: var(--test0);}\n.btn1, .s1 {color: var(--test1); cursor: pointer;}\n.btn2, .s2 {color: var(--test2);}\n.btn3, .s3 {color: var(--test3);}\n.btn4, .s4 {color: var(--test4);}\n.btn5, .s5 {color: var(--test5);}\n.btn6, .s6 {color: var(--test6);}\n.btn7, .s7 {color: var(--test7);}\n.btn8, .s8 {color: var(--test8);}\n.btn9, .s9 {color: var(--test9);}\n.btn10, .s10 {color: var(--test10);}\n.btn11, .s11 {color: var(--test11);}\n.btn12, .s12 {color: var(--test12);}\n.btn13, .s13 {color: var(--test13);}\n.btn14, .s14 {color: var(--test14);}\n.btn15, .s15 {color: var(--test15);}\n.btn16, .s16 {color: var(--test16);}\n.btn17, .s17 {color: var(--test17);}\n.btn18, .s18 {color: var(--test18);}\n.btn99, .s99 {color: var(--test99);}\n.btngood, .sgood {color: var(--test7); background-color: var(--bggood);}\n.btnbad, .sbad {color: var(--testbad); background-color: var(--bgbad);}\n.btnred, .sred {color: var(--testred); background-color: var(--bgbad);}\n.btn-left {float: left; position: relative; left: -15px; top: 0px;}\n.btn-right {float: right; position: relative; top: 0px; text-align: right;}\n.btn-right-inset {float: right; position: relative; top: 0px; right: 25%; width: 50%; direction: rtl; }\n\n/* tooltips */\n.icon {font-size: 10px; font-weight: bold; color: var(--test0); cursor: default;}\n.ttip {position: relative; display: inline-block; font-weight: normal;}\n.ttip .ttxt {\n\tvisibility: hidden; width: 150px; background-color: var(--test0); color: var(--bg0); text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip .ttxtb {\n\tvisibility: hidden; width: 210px; background-color: var(--test0); color: var(--bg0); text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip .ttxtx {\n\tvisibility: hidden; width: 250px; background-color: var(--test0); color: var(--bg0); text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip:hover .ttxt, .ttip:hover .ttxtb, .ttip:hover .ttxtx {visibility: visible;}\n.ttip .ttxt::after, .ttip .ttxtb::after, .ttip .ttxtx::after {\n\tcontent: \" \"; position: absolute; top: 50%; right: 100%; margin-top: -5px; border-width: 5px;\n\tborder-style: solid; border-color: transparent var(--test0) transparent transparent;}\n\n/* overlay */\n#modaloverlay {\n\tposition: fixed;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 900;\n\tdisplay: none;\n}\n#overlay {\n\tposition: fixed;\n\ttop: 50%;\n\tleft: 50%;\n\tright: 0;\n\tbottom: 0;\n\ttransform: translate(-50%, -50%);\n\tdisplay: none;\n\twidth: 95%;\n\tmax-width: 725px;\n\tmin-width: 225px;\n\theight: 85%;\n\tborder: 2px solid var(--test0);\n\tbackground-color: var(--bg0);\n\tbox-shadow: 3px 3px 7px black;\n\tz-index: 1000;\n\toverflow-y: scroll;\n}\n#overlaykit {\n\tbackground-image: url('../images/kit-happy.svg');\n\tbackground-repeat: no-repeat;\n\tbackground-attachment: fixed;\n\theight: 40px;\n\ttransform: scale(-1, 1);\n\tfilter: sepia(1) hue-rotate(65deg) brightness(0.9) saturate(1.3);\n\tz-index: 900;\n\t/* works except it moves with scroll\n\tposition: fixed;\n\tbottom: 25px;\n\tright: 25px;\n\t*/\n\t/* works but it's position isn't precise */\n\tposition: sticky;\n\ttop: 92%;\n\twidth: 97%;\n}\n#overlaytop {\n\tposition: sticky;\n\ttop: 0;\n\tbackground-color: var(--bg98);\n\tborder-bottom: 1px solid var(--test0);\n\tpadding: 18px 25px;\n\tz-index: 1000;\n}\n#overlaycontent {\n\tposition: absolute;\n\tmargin: 15px 25px 10px;\n\tpadding-bottom: 15px;\n}\n#overlaybuttons {\n\tfloat: right;\n\twidth: fit-content;\n\ttext-align: right;\n}\n\n/* table nav */\ndiv.nav-title {position: relative; font-weight: bold;}\ndiv.nav-down {position: absolute; right: 5px; top: 0px; width: 250px; text-align: right;}\ndiv.nav-up {position: absolute; left: 5px; top: 0px; width: 250px; text-align: left;}\ndiv.nav-right {position: absolute; right: 5px; top: -2px; width: 0px;}\n\n/* tables */\ntable {\n\twidth: 98%;\n\tmin-width: 475px;\n\tmax-width: 775px;\n\tborder-collapse: collapse;\n\tmargin: 0 auto 10px auto;\n\tfont-size: 12px;\n}\ntbody:before {content: \"-\"; display: block; line-height: 1em; color: transparent;}\ntd {padding-top: 2px; padding-bottom: 3px; padding-left: 10px; vertical-align: top;}\nth {color: black; font-size: 14px; padding: 3px 0;}\n\ntable td:first-child { text-align: right; vertical-align: top;}\ntable td.blurb {text-align: center; line-height: 1.5em;}\ntable td.center {text-align: center;}\ntable td.intro {text-align: left; line-height: 1.5em; padding-bottom: 10px; padding-left: 0px;}\ntable td.secthash {\n\ttext-align: left;\n\tvertical-align: top;\n\tline-height: 1.2em;\n\tpadding-bottom: 8px;\n\tfont-family: monospace, \"Courier New\";\n\tfont-size: 12px;\n}\ntable td.showhide {text-align: center; padding-top: 7px; padding-bottom: 7px;}\ntr td.border-bottom {\n\tborder-bottom: 1px solid var(--test99);\n\tborder-bottom-style: dashed;\n}\ntr td.border-top, div.border-top {\n\tborder-top: 1px solid var(--test99);\n\tborder-top-style: dashed;\n}\n\n#tb1 th {background-color: var(--bg1);}\n#tb2 th {background-color: var(--bg2);}\n#tb3 th {background-color: var(--bg3);}\n#tb4 th {background-color: var(--bg4);}\n#tb5 th {background-color: var(--bg5);}\n#tb6 th {background-color: var(--bg6);}\n#tb7 th {background-color: var(--bg7);}\n#tb8 th {background-color: var(--bg8);}\n#tb9 th {background-color: var(--bg9);}\n#tb10 th {background-color: var(--bg10);}\n#tb11 th {background-color: var(--bg11);}\n#tb12 th {background-color: var(--bg12);}\n#tb13 th {background-color: var(--bg13);}\n#tb14 th {background-color: var(--bg14);}\n#tb15 th {background-color: var(--bg15);}\n#tb16 th {background-color: var(--bg16);}\n#tb17 th {background-color: var(--bg17);}\n#tb18 th {background-color: var(--bg18);}\n#tb99 th {background-color: var(--bg99);}\n#tbfp th {background-color: var(--bg99);}\n#tbperf th {background-color: var(--bg99);}\n#tbblock th {background-color: var(--bg99);}\n\n.togA, .togP, #btnFS, /* android, perf, fullscreen element */\n.togUA, .togUAD, .togAI, .togAW, .togMM,\n.togCS, .togFS, .togFG, .togL, .togS, .togTP, .togTA, .togTL, .togTT, .togTO {display: none;}\n\n#tb1 td:first-child {color: var(--test1); font-weight: var(--testweight)}\n\t#tb1 .togS td:first-child {color: var(--test99); font-weight: normal;} /* screen/window/viewport */\n#tb2 td:first-child {color: var(--test2); font-weight: var(--testweight)}\n\t#tb2 .togUA td:first-child {color: var(--test99); font-weight: normal;} /* useragent */\n\t#tb2 .togUAD td:first-child {color: var(--test99);} /* useragentdata */\n\t#tb2 .togAI td:first-child {color: var(--test99); font-weight: normal;} /* agent iframes */\n\t#tb2 .togAW td:first-child {color: var(--test99); font-weight: normal;} /* agent workers */\n#tb3 td:first-child {color: var(--test3); font-weight: var(--testweight)}\n#tb4 td:first-child {color: var(--test4); font-weight: var(--testweight)}\n\t#tb4 .togTT td:first-child {color: var(--test99); font-weight: normal;} /* timezone timezone */\n\t#tb4 .togTL td:first-child {color: var(--test99); font-weight: normal;} /* timezone lastmodified */\n\t#tb4 .togTO td:first-child {color: var(--test99); font-weight: normal;} /* timezone offsets */\n#tb5 td:first-child {color: var(--test5); font-weight: var(--testweight)}\n#tb6 td:first-child {color: var(--test6); font-weight: var(--testweight)}\n#tb7 td:first-child {color: var(--test7); font-weight: var(--testweight)}\n#tb8 td:first-child {color: var(--test8); font-weight: var(--testweight)}\n#tb9 td:first-child {color: var(--test9); font-weight: var(--testweight)}\n#tb10 td:first-child {color: var(--test10); font-weight: var(--testweight)}\n#tb11 td:first-child {color: var(--test11); font-weight: var(--testweight)}\n#tb12 td:first-child {color: var(--test12); font-weight: var(--testweight)}\n\t#tb12 .togFS td:first-child {color: var(--test99); font-weight: normal;} /* font sizes */\n\t#tb12 .togFG td:first-child {color: var(--test99); font-weight: normal;} /* font glyphs */\n#tb13 td:first-child {color: var(--test13); font-weight: var(--testweight)}\n#tb14 td:first-child {color: var(--test14); font-weight: var(--testweight)}\n\t#tb14 .togCS td:first-child {color: var(--test99); font-weight: normal;} /* computed styles */\n\t#tb14 .togMM td:first-child {color: var(--test99); font-weight: normal;} /* matchmedia + css */\n#tb15 td:first-child {color: var(--test15); font-weight: var(--testweight)}\n#tb16 td:first-child {color: var(--test16); font-weight: var(--testweight)}\n#tb17 td:first-child {color: var(--test17); font-weight: var(--testweight)}\n\t#tb17 .togTA td:first-child {color: var(--test99); font-weight: normal;} /* timing audio */\n\t#tb17 .togTP td:first-child {color: var(--test99); font-weight: normal;} /* timing precision */\n#tb18 td:first-child {color: var(--test18); font-weight: var(--testweight)}\n#tb99 td:first-child {color: var(--test99);} /* index page */\n#tbfp td:first-child {color: var(--test99); font-weight: var(--testweight);}\n#tbperf td:first-child {color: var(--test99);}\n\n/* main test */\n\nbody.tzpBody::after {\n\tcontent: \"\";\n\tbackground: url('chrome://global/skin/onion-pattern.svg');\n\tbackground-repeat: repeat;\n\topacity: 0.15;\n\ttop: 0;\n\tleft: 0;\n\tbottom: 0;\n\tright: 0;\n\tposition: fixed;\n\tz-index: -1;\n}\n#tzpLV {\n\theight: 100lvh;\n\twidth: 100lvw;\n\tposition: fixed;\n\tleft: 0;\n\tz-index: -6000;\n}\n#tzpSV {\n\theight: 100svh;\n\twidth: 100svw;\n\tposition: fixed;\n\tleft: 0;\n\tz-index: -6000;\n}\n#tzpResource {\n\tbackground-image: url(\"about:logo\"), url(\"\");\n\tbackground-size: auto 100%; background-repeat: no-repeat; background-position: 10px 0px;\n}\n#tzpWordmark {\n\tbackground-image: url(\"chrome://branding/content/about-wordmark.svg\"), url(\"\");\n\tbackground-size: auto 100%; background-repeat: no-repeat; background-position: 10px 0px;\n}\n#tzpScroll {width: 100px; overflow-y: scroll;}\n#tzpRect {\n\tposition: fixed;\n\ttop: 0; left: 0;\n\twidth:100px; height:100px;\n\ttransform: rotate(45deg);\n\tpadding: 0px;\n\tz-index: -1; \n}\n#tzpFS::backdrop {\n\tbackground-color: var(--bg0);\n\t/*opacity: 0;*/\n}\n\n#tzpCalc {width: fit-content;}\n.tzpCalcContainer {container: calccontainer / inline-size;}\n.tzpCalcA {\n\twidth: calc(1px *\n\t\t((e * 0.06314882636070251 - 0.06699182000011206 / (327510.10546596383 * 101099.74005273856 ))\n\t\t\t+ 0.9363944577053189 / sin( sin( 86911.80023335948 *  tan(122224.59393033749) / tan(250486.18265094055)\n\t\t\t+ (169617.27745474092) / pi * 19.00493122072233 - 0.22360279853455722 ) / 50590.01594434995 + tan(( \n\t\t\t110958.53977223029) + 109345.15143883083 * 99223.79864865377 + 0.05425928323529661)  / 94812.65262083427\n\t\t\t* pi) - 0.8964629967231303 * -341499.34226304095\n\t\t)\n\t);\n}\n\n/*** FP POCs ***/\n.cursive {font-family: cursive;}\n.emoji {font-family: emoji;}\n.fangsong {font-family: fangsong;}\n.fantasy {font-family: fantasy;}\n.math {font-family: math;}\n.monospace {font-family: monospace;}\n.none {font-family: none;}\n.sans-serif {font-family: sans-serif;}\n.serif {font-family: serif;}\n.system-ui {font-family: system-ui;}\n.ui-monospace {font-family: ui-monospace;}\n.ui-rounded {font-family: ui-rounded;}\n.ui-sans-serif {font-family: ui-sans-serif;}\n.ui-serif {font-family: ui-serif;}\n\n.normalized {\n\tfont-family: none !important;\n\tfont-size: initial !important;\n\tfont-style: normal !important;\n\tfont-variant: normal !important;\n\tfont-weight: normal !important;\n\tline-height: normal !important;\n\ttext-transform: none !important;\n\ttext-align: left !important;\n\ttext-decoration: none !important;\n\ttext-shadow: none !important;\n\twhite-space: normal !important;\n\tword-break: normal !important;\n\tword-spacing: normal !important;\n}\n#element-fp {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tfont-family: none;\n\tfont-size: initial;\n\tfont-style: normal;\n\tfont-variant: normal;\n\tfont-weight: normal;\n\tline-height: normal;\n\ttext-transform: none;\n\ttext-align: left;\n\ttext-decoration: none;\n\ttext-shadow: none;\n\twhite-space: nowrap;\n\ttransform: skew(1.787542deg, 3.263901deg); /* domrect */\n}\n#element-fp .unstyled {\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tappearance: none;\n}\n#element-fp tbody:before {\n\tcontent: none;\n\tline-height: normal;\n}\n#element-fp .revert {\n\t/* https://developer.mozilla.org/en-US/docs/Web/CSS/all */ \n\tall: revert;\n}\n.skew {transform: skew(1.787542deg,3.263901deg);}\n\n/*** TZP MAIN ***/\n@font-face {font-family: \"ABR\"; src: url(\"../fonts/AdobeBlankRegular.ttf\");}\n@font-face {\n\tfont-family: \"graphite\";\n\t/* src: url(\"../fonts/GraphiteWidthTest.ttf\"); */\n\tsrc: url(data:font/truetype;base64,AAEAAAAUAQAABABARFNJRwAAAAEAAAFMAAAACEZlYXSAA4EXAAALNAAAABxHbGF0A8sFdwAACjQAAAA2R2xvYwCvAJUAAApsAAAAHk9TLzJRF1vMAAABVAAAAGBTaWxmHoAfnQAACowAAACmU2lsbICBgJQAAAtQAAAAFGNtYXABZABDAAABtAAAAExjdnQgAAAAAAAAAgAAAAGeZnBnbeLCUEIAAAOgAAAAE2dhc3AABwAbAAADtAAAAAxnbHlmnTyrAQAAA8AAAAGoaGVhZCVhj4AAAAVoAAAANmhoZWEG1QJ0AAAFoAAAACRobXR4CSIAvQAABcQAAAAYbG9jYQGKASIAAAXcAAAADm1heHAAcQB4AAAF7AAAACBuYW1lpHI7RgAABgwAAAMPcG9zdFJPeoAAAAkcAAAARXByZXC8yrV/AAAJZAAAAM8AAAABAAAAAAAEAYUBkAAFAAACigJYAAAASwKKAlgAAAFeADIBAwAAAAAEAAAAAAAAAAAAAAMAAAAAAAAAAAAAAABNQUNSAMAAIAAtA6//HwAAA68A4QAAAAEAAAAAAAAAAAAAACAAAAAAAAIAAAADAAAAFAADAAEAAAAUAAQAOAAAAAoACAACAAIAIAArAC0AoP//AAAAIAArAC0AoP///+H/2P/X/2EAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAALHZFILADJUUjYWgYI2hgRC0AAAEAAgAHAAr//wAPAAIAPwAAAbYC+AADAAcAVEAgAQgIQAkCBwQEAQAGBQQDAgUEBwAHBgcBAgEDAAEBAEZ2LzcYAD88LzwQ/TwQ/TwBLzz9PC88/TwAMTABSWixAAhJaGGwQFJYOBE3uQAI/8A4WTMRIRElMxEjPwF3/sf6+gL4/Qg/AnsAAAADACoAZgEwAWwAAwAHAAsAQUAVAQUFQAYAAwAFAgEDAgcABAEAAQRGdi83GAAvPC8Q/TwBLzz9PAAxMAFJaLEEBUloYbBAUlg4ETe5AAX/wDhZEzMRIychFSE1IRUhh01NXQEG/voBBv76AWz++qlNTU0AAAEAKgDCATABDwADAEFAFQEFBUAGAAMABQIBAwIHAAQBAAEERnYvNxgALzwvEP08AS88/TwAMTABSWixBAVJaGGwQFJYOBE3uQAF/8A4WSUhNSEBMP76AQbCTQAAAQAqAMIDJAEPAAMAQUAVAQUFQAYAAwAFAgEDAgcABAEAAQRGdi83GAAvPC8Q/TwBLzz9PAAxMAFJaLEEBUloYbBAUlg4ETe5AAX/wDhZJSE1IQMk/QYC+sJNAAABAAAAAQAAga0g2F8PPPUADwPoAAAAAOF4FG8AAAAA4Xk3NQAqAAADJAL4AAAABwACAAAAAAAAAAEAAAOv/x8AAANOAAAAAAMkAAEAAAAAAAAAAAAAAAAAAAAGAfQAPwEsAAAAAAAAAVoAKgFaACoDTgAqAAAAPgA+AD4AeACmANQAAAABAAAABgAMAAMAAAAAAAIAAgAWAAEAAABkAFQAAAAAAAAAEQDSAAEAAAAAAAEAEwAAAAEAAAAAAAIABwATAAEAAAAAAAQAEwAaAAEAAAAAAAUADQAtAAEAAAAAAAYAEwA6AAEAAAAAAQAABgIrAAMAAQQJAAAAzABNAAMAAQQJAAEAJgEZAAMAAQQJAAIADgE/AAMAAQQJAAMAPAFNAAMAAQQJAAQAJgGJAAMAAQQJAAUAGgGvAAMAAQQJAAYAJgHJAAMAAQQJAAcACAHvAAMAAQQJABAAJgH3AAMAAQQJABEADgIdAAMAAQQJAQAADAIxR3JhcGhpdGUgV2lkdGggVGVzdFJlZ3VsYXJHcmFwaGl0ZSBXaWR0aCBUZXN0VmVyc2lvbiAxLjAwMEdyYXBoaXRlIFdpZHRoIFRlc3QAKABjACkAIABDAG8AcAB5AHIAaQBnAGgAdAAgADIAMAAyADMAIABTAEkATAAgAEkAbgB0AGUAcgBuAGEAdABpAG8AbgBhAGwALAAgADcANQAwADAAIABXAC4AIABDAGEAbQBwACAAVwBpAHMAZABvAG0AIABSAGQALgAsACAARABhAGwAbABhAHMALAAgAFQAWAAgADcANQAyADMANgAgAFUAUwBBACAAKAA5ADcAMgApACAANwAwADgALQA3ADQAOQA1ACAAUgBXAEUARwByAGEAcABoAGkAdABlACAAVwBpAGQAdABoACAAVABlAHMAdABSAGUAZwB1AGwAYQByADEALgAwADAAMAA7AE0AQQBDAFIAOwBHAHIAYQBwAGgAaQB0AGUAIABXAGkAZAB0AGgAIABUAGUAcwB0AEcAcgBhAHAAaABpAHQAZQAgAFcAaQBkAHQAaAAgAFQAZQBzAHQAVgBlAHIAcwBpAG8AbgAgADEALgAwADAAMABHAHIAYQBwAGgAaQB0AGUAIABXAGkAZAB0AGgAIABUAGUAcwB0AE4AbwBuAGUARwByAGEAcABoAGkAdABlACAAVwBpAGQAdABoACAAVABlAHMAdABSAGUAZwB1AGwAYQByTm9OYW1lAE4AbwBOAGEAbQBlAAACAAAAAAAA/5wAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAADAQIADgAQAQMLbm9OYW1lMDAwMDEKaHlwaGVud2lkZQAAAEBHJiYlJSQkIyMiIiEhICAfHx4eHR0cHBsbGhoZGRgYFxcWFhUVFBQTExISEREQEA8PDg4NDQwMCwsKCgkJCAgDAwICAQEAAACNuAH/hUVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoREVoRLMFBEYAK7MHBkYAK7EEBEVoRLEGBkVoRAAAAQAAAQEAHgECAA8ACQEBAB4BAgAeAAUBAgAeAAUBAQAeAQEAHgACAAEAHgACAAEAHgEBAB4AAAABAAAAAAAFAAQACAAOABIAGAAeACIAJgAsADIANgAAAAIAAAABAAAAAAAMAAkAAAAAAQABAf8AAAAAAQIDAAAAAAAAAQAAAAAAAAAABgAAAEwAAACaAAIAAgABAAAAAAAgAAcAAACgAAgAAgACAAoADAAOAAUABAAFAQAAAQAAAAAAlQAAAJUAAACWAAAAAAACAAEAAQABAAEAAQAAAAAABAAEAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAQAAQAAHAAZMQAAAAEAAAABAAAAAAAAAAEAAAAAABiAAAEAAAB//wABAAAAAAAAAAAAAICAgIAAAAAU) format(\"truetype\");\n}\n"
  },
  {
    "path": "css/media.css",
    "content": "/* @supports: https://drafts.csswg.org/css-conditional-5/#at-supports-ext */\n\n/* font-tech */\n#cssCOLRv0:after{content:\"n/a\";}\n@supports font-tech(color-COLRv0){#cssCOLRv0:after{content:\"supported\";}}\n#cssCOLRv1:after{content:\"n/a\";}\n@supports font-tech(color-COLRv1){#cssCOLRv1:after{content:\"supported\";}}\n#cssOpenType:after{content:\"n/a\";}\n@supports font-tech(features-opentype){#cssOpenType:after{content:\"supported\";}}\n#cssTrueType:after{content:\"n/a\";}\n@supports font-format(TrueType){#cssTrueType:after{content:\"supported\";}}\n\n/* font-format */\n#cssWoff2:after{content:\"undefined\";}\n@supports font-format(woff2){#cssWoff2:after{content:\"supported\";}}\n\n/* @media */\n#cssOm:after{content:\"undefined\";}\n@media (-moz-device-orientation:portrait){#cssOm:after{content:\"portrait\";}}\n@media (-moz-device-orientation:landscape){#cssOm:after{content:\"landscape\";}}\n#cssO:after{content:\"undefined\";}\n@media (orientation:portrait){#cssO:after{content:\"portrait\";}}\n@media (orientation:landscape){#cssO:after{content:\"landscape\";}}\n#cssAR:after{content:\"undefined\";}\n@media (aspect-ratio:1/1){#cssAR:after{content:\"square\";}}\n@media (min-aspect-ratio:10000/9999){#cssAR:after{content:\"landscape\";}}\n@media (max-aspect-ratio:9999/10000){#cssAR:after{content:\"portrait\";}}\n#cssDAR:after{content:\"undefined\";}\n@media (device-aspect-ratio:1/1){#cssDAR:after{content:\"square\";}}\n@media (min-device-aspect-ratio:10000/9999){#cssDAR:after{content:\"landscape\";}}\n@media (max-device-aspect-ratio:9999/10000){#cssDAR:after{content:\"portrait\";}}\n#cssDM:after{content:\"undefined\";}\n@media (display-mode:fullscreen){#cssDM:after{content:\"fullscreen\";}}\n@media (display-mode:browser){#cssDM:after{content:\"browser\";}}\n@media (display-mode:minimal-ui){#cssDM:after{content:\"minimal-ui\";}}\n@media (display-mode:standalone){#cssDM:after{content:\"standalone\";}}\n@media (display-mode:picture-in-picture){#cssDM:after{content:\"picture-in-picture\";}}\n@media (display-mode:window-controls-overlay){#cssDM:after{content:\"window-controls-overlay\";}}\n\n#cssC:after{content:\"n/a\";}\n@media (color:0){#cssC:after{content:\"\";}}\n@media (color:1){#cssC:after{content:\"1\";}}\n@media (color:2){#cssC:after{content:\"2\";}}\n@media (color:3){#cssC:after{content:\"3\";}}\n@media (color:4){#cssC:after{content:\"4\";}}\n@media (color:5){#cssC:after{content:\"5\";}}\n@media (color:6){#cssC:after{content:\"6\";}}\n@media (color:7){#cssC:after{content:\"7\";}}\n@media (color:8){#cssC:after{content:\"8\";}}\n@media (color:9){#cssC:after{content:\"9\";}}\n@media (color:10){#cssC:after{content:\"10\";}}\n@media (color:11){#cssC:after{content:\"11\";}}\n@media (color:12){#cssC:after{content:\"12\";}}\n@media (color:13){#cssC:after{content:\"13\";}}\n@media (color:14){#cssC:after{content:\"14\";}}\n@media (color:15){#cssC:after{content:\"15\";}}\n@media (color:16){#cssC:after{content:\"16\";}}\n@media (color:17){#cssC:after{content:\"17\";}}\n@media (color:18){#cssC:after{content:\"18\";}}\n@media (color:19){#cssC:after{content:\"19\";}}\n@media (color:20){#cssC:after{content:\"20\";}}\n@media (color:21){#cssC:after{content:\"21\";}}\n@media (color:22){#cssC:after{content:\"22\";}}\n@media (color:23){#cssC:after{content:\"23\";}}\n@media (color:24){#cssC:after{content:\"24\";}}\n@media (color:25){#cssC:after{content:\"\";}}\n\n/*\n\talways return none with a traling space so we can differentiate between this\n\tand default none: https://www.w3.org/TR/CSS21/generate.html#content\n*/\n#cssH:after{content:\"n/a\";}\n@media (hover:hover){#cssH:after{content:\"hover\";}}\n@media (hover:none){#cssH:after{content:\"none \";}}\n#cssAH:after{content:\"n/a\";}\n@media (any-hover:hover){#cssAH:after{content:\"hover\";}}\n@media (any-hover:none){#cssAH:after{content:\"none \";}}\n#cssPRM:after{content:\"n/a\";}\n@media (prefers-reduced-motion:no-preference){#cssPRM:after{content:\"no-preference\";}}\n@media (prefers-reduced-motion:reduce){#cssPRM:after{content:\"reduce\";}}\n#cssP:after{content:\"n/a\";}\n@media (pointer:fine){#cssP:after{content:\"fine\";}}\n@media (pointer:coarse){#cssP:after{content:\"coarse\";}}\n@media (pointer:none){#cssP:after{content:\"none \";}}\n/* any-pointer order matters: https://www.w3.org/TR/mediaqueries-4/#any-input */\n#cssAP:before{content:\"n/a\";}\n@media (any-pointer:coarse){#cssAP:before{content:\"coarse\";}}\n@media (any-pointer:fine){#cssAP:before{content:\"fine\";}} /* fine over coarse */\n@media (any-pointer:none){#cssAP:before{content:\"none \";}}\n#cssAP:after{content:\" + n/a\";}\n@media (any-pointer:fine){#cssAP:after{content:\" + fine\";}}\n@media (any-pointer:coarse){#cssAP:after{content:\" + coarse\";}} /* coarse over fine */\n@media (any-pointer:none){#cssAP:after{content:\" + none \";}}\n#cssPC:after{content:\"n/a\";}\n@media (prefers-contrast:no-preference){#cssPC:after{content:\"no-preference\";}}\n@media (prefers-contrast:less){#cssPC:after{content:\"less\";}}\n@media (prefers-contrast:more){#cssPC:after{content:\"more\";}}\n@media (prefers-contrast:custom){#cssPC:after{content:\"custom\";}}\n#cssPCS:after{content:\"n/a\";} /*note: no-preference: obsolete FF79+: 1643656 */\n@media (prefers-color-scheme:light){#cssPCS:after{content:\"light\";}}\n@media (prefers-color-scheme:dark){#cssPCS:after{content:\"dark\";}}\n#cssFC:after{content:\"n/a\";}\n@media (forced-colors:none){#cssFC:after{content:\"none \";}}\n@media (forced-colors:active){#cssFC:after{content:\"active\";}}\n#cssDR:after{content:\"n/a\";}\n@media (dynamic-range:standard){#cssDR:after{content:\"standard\";}}\n@media (dynamic-range:high){#cssDR:after{content:\"high\";}}\n#cssVDR:after{content:\"n/a\";}\n@media (dynamic-range:standard){#cssVDR:after{content:\"standard\";}}\n@media (dynamic-range:high){#cssVDR:after{content:\"high\";}}\n#cssCG:after{content:\"n/a\";} /* \"ascending order\" see https://drafts.csswg.org/mediaqueries/#color-gamut */\n@media (color-gamut:srgb){#cssCG:after{content:\"srgb\";}} /* narrow */\n@media (color-gamut:p3){#cssCG:after{content:\"p3\";}} /* wider: p3 includes srgb */\n@media (color-gamut:rec2020){#cssCG:after{content:\"rec2020\";}} /* wider: rec2020 includes p3 */\n#cssPRT:after{content:\"n/a\";}\n@media (prefers-reduced-transparency:no-preference){#cssPRT:after{content:\"no-preference\";}}\n@media (prefers-reduced-transparency:reduce){#cssPRT:after{content:\"reduce\";}}\n#cssIC:after{content:\"n/a\";}\n@media (inverted-colors:none){#cssIC:after{content:\"none \";}}\n@media (inverted-colors:inverted){#cssIC:after{content:\"inverted\";}}\n#cssPRD:after{content:\"n/a\";}\n@media (prefers-reduced-data:no-preference){#cssPRD:after{content:\"no-preference\";}}\n@media (prefers-reduced-data:reduce){#cssPRD:after{content:\"reduce\";}}\n#cssDP:after{content:\"undefined\";} /* use undefined to match navigator */\n@media (device-posture:continuous){#cssDP:after{content:\"continuous\";}}\n@media (device-posture:folded){#cssDP:after{content:\"folded\";}}\n#cssUD:after{content:\"n/a\";}\n@media (update:none){#cssUD:after{content:\"none\";}} /* FF102+: 1422312 || FYI: gecko currently only reports none or fast */\n@media (update:slow){#cssUD:after{content:\"slow\";}}\n@media (update:fast){#cssUD:after{content:\"fast\";}}\n\n/* https://drafts.csswg.org/mediaqueries-5/#mf-horizontal-viewport-segments */\n#cssVS:before{content:\"n/a\";}\n@media (horizontal-viewport-segments:1){#cssVS:before{content:\"1\";}}\n@media (horizontal-viewport-segments:2){#cssVS:before{content:\"2\";}}\n@media (horizontal-viewport-segments:3){#cssVS:before{content:\"3\";}}\n@media (horizontal-viewport-segments:4){#cssVS:before{content:\"4\";}}\n@media (horizontal-viewport-segments:5){#cssVS:before{content:\"5\";}}\n#cssVS:after{content:\" x n/a\";}\n@media (vertical-viewport-segments:1){#cssVS:after{content:\" x 1\";}}\n@media (vertical-viewport-segments:2){#cssVS:after{content:\" x 2\";}}\n@media (vertical-viewport-segments:3){#cssVS:after{content:\" x 3\";}}\n@media (vertical-viewport-segments:4){#cssVS:after{content:\" x 4\";}}\n@media (vertical-viewport-segments:5){#cssVS:after{content:\" x 5\";}}\n\n/* dpi */\n@media (min-resolution:39dpi){#P:before{content:\"\";}}\n@media (min-resolution:40dpi){#P:before{content:\"40\";}}\n@media (min-resolution:41dpi){#P:before{content:\"41\";}}\n@media (min-resolution:42dpi){#P:before{content:\"42\";}}\n@media (min-resolution:43dpi){#P:before{content:\"43\";}}\n@media (min-resolution:44dpi){#P:before{content:\"44\";}}\n@media (min-resolution:45dpi){#P:before{content:\"45\";}}\n@media (min-resolution:46dpi){#P:before{content:\"46\";}}\n@media (min-resolution:47dpi){#P:before{content:\"47\";}}\n@media (min-resolution:48dpi){#P:before{content:\"48\";}}\n@media (min-resolution:49dpi){#P:before{content:\"49\";}}\n@media (min-resolution:50dpi){#P:before{content:\"50\";}}\n@media (min-resolution:51dpi){#P:before{content:\"51\";}}\n@media (min-resolution:52dpi){#P:before{content:\"52\";}}\n@media (min-resolution:53dpi){#P:before{content:\"53\";}}\n@media (min-resolution:54dpi){#P:before{content:\"54\";}}\n@media (min-resolution:55dpi){#P:before{content:\"55\";}}\n@media (min-resolution:56dpi){#P:before{content:\"56\";}}\n@media (min-resolution:57dpi){#P:before{content:\"57\";}}\n@media (min-resolution:58dpi){#P:before{content:\"58\";}}\n@media (min-resolution:59dpi){#P:before{content:\"59\";}}\n@media (min-resolution:60dpi){#P:before{content:\"60\";}}\n@media (min-resolution:61dpi){#P:before{content:\"61\";}}\n@media (min-resolution:62dpi){#P:before{content:\"62\";}}\n@media (min-resolution:63dpi){#P:before{content:\"63\";}}\n@media (min-resolution:64dpi){#P:before{content:\"64\";}}\n@media (min-resolution:65dpi){#P:before{content:\"65\";}}\n@media (min-resolution:66dpi){#P:before{content:\"66\";}}\n@media (min-resolution:67dpi){#P:before{content:\"67\";}}\n@media (min-resolution:68dpi){#P:before{content:\"68\";}}\n@media (min-resolution:69dpi){#P:before{content:\"69\";}}\n@media (min-resolution:70dpi){#P:before{content:\"70\";}}\n@media (min-resolution:71dpi){#P:before{content:\"71\";}}\n@media (min-resolution:72dpi){#P:before{content:\"72\";}}\n@media (min-resolution:73dpi){#P:before{content:\"73\";}}\n@media (min-resolution:74dpi){#P:before{content:\"74\";}}\n@media (min-resolution:75dpi){#P:before{content:\"75\";}}\n@media (min-resolution:76dpi){#P:before{content:\"76\";}}\n@media (min-resolution:77dpi){#P:before{content:\"77\";}}\n@media (min-resolution:78dpi){#P:before{content:\"78\";}}\n@media (min-resolution:79dpi){#P:before{content:\"79\";}}\n@media (min-resolution:80dpi){#P:before{content:\"80\";}}\n@media (min-resolution:81dpi){#P:before{content:\"81\";}}\n@media (min-resolution:82dpi){#P:before{content:\"82\";}}\n@media (min-resolution:83dpi){#P:before{content:\"83\";}}\n@media (min-resolution:84dpi){#P:before{content:\"84\";}}\n@media (min-resolution:85dpi){#P:before{content:\"85\";}}\n@media (min-resolution:86dpi){#P:before{content:\"86\";}}\n@media (min-resolution:87dpi){#P:before{content:\"87\";}}\n@media (min-resolution:88dpi){#P:before{content:\"88\";}}\n@media (min-resolution:89dpi){#P:before{content:\"89\";}}\n@media (min-resolution:90dpi){#P:before{content:\"90\";}}\n@media (min-resolution:91dpi){#P:before{content:\"91\";}}\n@media (min-resolution:92dpi){#P:before{content:\"92\";}}\n@media (min-resolution:93dpi){#P:before{content:\"93\";}}\n@media (min-resolution:94dpi){#P:before{content:\"94\";}}\n@media (min-resolution:95dpi){#P:before{content:\"95\";}}\n@media (min-resolution:96dpi){#P:before{content:\"96\";}}\n@media (min-resolution:97dpi){#P:before{content:\"97\";}}\n@media (min-resolution:98dpi){#P:before{content:\"98\";}}\n@media (min-resolution:99dpi){#P:before{content:\"99\";}}\n@media (min-resolution:100dpi){#P:before{content:\"100\";}}\n@media (min-resolution:101dpi){#P:before{content:\"101\";}}\n@media (min-resolution:102dpi){#P:before{content:\"102\";}}\n@media (min-resolution:103dpi){#P:before{content:\"103\";}}\n@media (min-resolution:104dpi){#P:before{content:\"104\";}}\n@media (min-resolution:105dpi){#P:before{content:\"105\";}}\n@media (min-resolution:106dpi){#P:before{content:\"106\";}}\n@media (min-resolution:107dpi){#P:before{content:\"107\";}}\n@media (min-resolution:108dpi){#P:before{content:\"108\";}}\n@media (min-resolution:109dpi){#P:before{content:\"109\";}}\n@media (min-resolution:110dpi){#P:before{content:\"110\";}}\n@media (min-resolution:111dpi){#P:before{content:\"111\";}}\n@media (min-resolution:112dpi){#P:before{content:\"112\";}}\n@media (min-resolution:113dpi){#P:before{content:\"113\";}}\n@media (min-resolution:114dpi){#P:before{content:\"114\";}}\n@media (min-resolution:115dpi){#P:before{content:\"115\";}}\n@media (min-resolution:116dpi){#P:before{content:\"116\";}}\n@media (min-resolution:117dpi){#P:before{content:\"117\";}}\n@media (min-resolution:118dpi){#P:before{content:\"118\";}}\n@media (min-resolution:119dpi){#P:before{content:\"119\";}}\n@media (min-resolution:120dpi){#P:before{content:\"120\";}}\n@media (min-resolution:121dpi){#P:before{content:\"121\";}}\n@media (min-resolution:122dpi){#P:before{content:\"122\";}}\n@media (min-resolution:123dpi){#P:before{content:\"123\";}}\n@media (min-resolution:124dpi){#P:before{content:\"124\";}}\n@media (min-resolution:125dpi){#P:before{content:\"125\";}}\n@media (min-resolution:126dpi){#P:before{content:\"126\";}}\n@media (min-resolution:127dpi){#P:before{content:\"127\";}}\n@media (min-resolution:128dpi){#P:before{content:\"128\";}}\n@media (min-resolution:129dpi){#P:before{content:\"129\";}}\n@media (min-resolution:130dpi){#P:before{content:\"130\";}}\n@media (min-resolution:131dpi){#P:before{content:\"131\";}}\n@media (min-resolution:132dpi){#P:before{content:\"132\";}}\n@media (min-resolution:133dpi){#P:before{content:\"133\";}}\n@media (min-resolution:134dpi){#P:before{content:\"134\";}}\n@media (min-resolution:135dpi){#P:before{content:\"135\";}}\n@media (min-resolution:136dpi){#P:before{content:\"136\";}}\n@media (min-resolution:137dpi){#P:before{content:\"137\";}}\n@media (min-resolution:138dpi){#P:before{content:\"138\";}}\n@media (min-resolution:139dpi){#P:before{content:\"139\";}}\n@media (min-resolution:140dpi){#P:before{content:\"140\";}}\n@media (min-resolution:141dpi){#P:before{content:\"141\";}}\n@media (min-resolution:142dpi){#P:before{content:\"142\";}}\n@media (min-resolution:143dpi){#P:before{content:\"143\";}}\n@media (min-resolution:144dpi){#P:before{content:\"144\";}}\n@media (min-resolution:145dpi){#P:before{content:\"145\";}}\n@media (min-resolution:146dpi){#P:before{content:\"146\";}}\n@media (min-resolution:147dpi){#P:before{content:\"147\";}}\n@media (min-resolution:148dpi){#P:before{content:\"148\";}}\n@media (min-resolution:149dpi){#P:before{content:\"149\";}}\n@media (min-resolution:150dpi){#P:before{content:\"150\";}}\n@media (min-resolution:151dpi){#P:before{content:\"151\";}}\n@media (min-resolution:152dpi){#P:before{content:\"152\";}}\n@media (min-resolution:153dpi){#P:before{content:\"153\";}}\n@media (min-resolution:154dpi){#P:before{content:\"154\";}}\n@media (min-resolution:155dpi){#P:before{content:\"155\";}}\n@media (min-resolution:156dpi){#P:before{content:\"156\";}}\n@media (min-resolution:157dpi){#P:before{content:\"157\";}}\n@media (min-resolution:158dpi){#P:before{content:\"158\";}}\n@media (min-resolution:159dpi){#P:before{content:\"159\";}}\n@media (min-resolution:160dpi){#P:before{content:\"160\";}}\n@media (min-resolution:161dpi){#P:before{content:\"161\";}}\n@media (min-resolution:162dpi){#P:before{content:\"162\";}}\n@media (min-resolution:163dpi){#P:before{content:\"163\";}}\n@media (min-resolution:164dpi){#P:before{content:\"164\";}}\n@media (min-resolution:165dpi){#P:before{content:\"165\";}}\n@media (min-resolution:166dpi){#P:before{content:\"166\";}}\n@media (min-resolution:167dpi){#P:before{content:\"167\";}}\n@media (min-resolution:168dpi){#P:before{content:\"168\";}}\n@media (min-resolution:169dpi){#P:before{content:\"169\";}}\n@media (min-resolution:170dpi){#P:before{content:\"170\";}}\n@media (min-resolution:171dpi){#P:before{content:\"171\";}}\n@media (min-resolution:172dpi){#P:before{content:\"172\";}}\n@media (min-resolution:173dpi){#P:before{content:\"173\";}}\n@media (min-resolution:174dpi){#P:before{content:\"174\";}}\n@media (min-resolution:175dpi){#P:before{content:\"175\";}}\n@media (min-resolution:176dpi){#P:before{content:\"176\";}}\n@media (min-resolution:177dpi){#P:before{content:\"177\";}}\n@media (min-resolution:178dpi){#P:before{content:\"178\";}}\n@media (min-resolution:179dpi){#P:before{content:\"179\";}}\n@media (min-resolution:180dpi){#P:before{content:\"180\";}}\n@media (min-resolution:181dpi){#P:before{content:\"181\";}}\n@media (min-resolution:182dpi){#P:before{content:\"182\";}}\n@media (min-resolution:183dpi){#P:before{content:\"183\";}}\n@media (min-resolution:184dpi){#P:before{content:\"184\";}}\n@media (min-resolution:185dpi){#P:before{content:\"185\";}}\n@media (min-resolution:186dpi){#P:before{content:\"186\";}}\n@media (min-resolution:187dpi){#P:before{content:\"187\";}}\n@media (min-resolution:188dpi){#P:before{content:\"188\";}}\n@media (min-resolution:189dpi){#P:before{content:\"189\";}}\n@media (min-resolution:190dpi){#P:before{content:\"190\";}}\n@media (min-resolution:191dpi){#P:before{content:\"191\";}}\n@media (min-resolution:192dpi){#P:before{content:\"192\";}}\n@media (min-resolution:193dpi){#P:before{content:\"193\";}}\n@media (min-resolution:194dpi){#P:before{content:\"194\";}}\n@media (min-resolution:195dpi){#P:before{content:\"195\";}}\n@media (min-resolution:196dpi){#P:before{content:\"196\";}}\n@media (min-resolution:197dpi){#P:before{content:\"197\";}}\n@media (min-resolution:198dpi){#P:before{content:\"198\";}}\n@media (min-resolution:199dpi){#P:before{content:\"199\";}}\n@media (min-resolution:200dpi){#P:before{content:\"200\";}}\n@media (min-resolution:201dpi){#P:before{content:\"201\";}}\n@media (min-resolution:202dpi){#P:before{content:\"202\";}}\n@media (min-resolution:203dpi){#P:before{content:\"203\";}}\n@media (min-resolution:204dpi){#P:before{content:\"204\";}}\n@media (min-resolution:205dpi){#P:before{content:\"205\";}}\n@media (min-resolution:206dpi){#P:before{content:\"206\";}}\n@media (min-resolution:207dpi){#P:before{content:\"207\";}}\n@media (min-resolution:208dpi){#P:before{content:\"208\";}}\n@media (min-resolution:209dpi){#P:before{content:\"209\";}}\n@media (min-resolution:210dpi){#P:before{content:\"210\";}}\n@media (min-resolution:211dpi){#P:before{content:\"211\";}}\n@media (min-resolution:212dpi){#P:before{content:\"212\";}}\n@media (min-resolution:213dpi){#P:before{content:\"213\";}}\n@media (min-resolution:214dpi){#P:before{content:\"214\";}}\n@media (min-resolution:215dpi){#P:before{content:\"215\";}}\n@media (min-resolution:216dpi){#P:before{content:\"216\";}}\n@media (min-resolution:217dpi){#P:before{content:\"217\";}}\n@media (min-resolution:218dpi){#P:before{content:\"218\";}}\n@media (min-resolution:219dpi){#P:before{content:\"219\";}}\n@media (min-resolution:220dpi){#P:before{content:\"220\";}}\n@media (min-resolution:221dpi){#P:before{content:\"221\";}}\n@media (min-resolution:222dpi){#P:before{content:\"222\";}}\n@media (min-resolution:223dpi){#P:before{content:\"223\";}}\n@media (min-resolution:224dpi){#P:before{content:\"224\";}}\n@media (min-resolution:225dpi){#P:before{content:\"225\";}}\n@media (min-resolution:226dpi){#P:before{content:\"226\";}}\n@media (min-resolution:227dpi){#P:before{content:\"227\";}}\n@media (min-resolution:228dpi){#P:before{content:\"228\";}}\n@media (min-resolution:229dpi){#P:before{content:\"229\";}}\n@media (min-resolution:230dpi){#P:before{content:\"230\";}}\n@media (min-resolution:231dpi){#P:before{content:\"231\";}}\n@media (min-resolution:232dpi){#P:before{content:\"232\";}}\n@media (min-resolution:233dpi){#P:before{content:\"233\";}}\n@media (min-resolution:234dpi){#P:before{content:\"234\";}}\n@media (min-resolution:235dpi){#P:before{content:\"235\";}}\n@media (min-resolution:236dpi){#P:before{content:\"236\";}}\n@media (min-resolution:237dpi){#P:before{content:\"237\";}}\n@media (min-resolution:238dpi){#P:before{content:\"238\";}}\n@media (min-resolution:239dpi){#P:before{content:\"239\";}}\n@media (min-resolution:240dpi){#P:before{content:\"240\";}}\n@media (min-resolution:241dpi){#P:before{content:\"241\";}}\n@media (min-resolution:242dpi){#P:before{content:\"242\";}}\n@media (min-resolution:243dpi){#P:before{content:\"243\";}}\n@media (min-resolution:244dpi){#P:before{content:\"244\";}}\n@media (min-resolution:245dpi){#P:before{content:\"245\";}}\n@media (min-resolution:246dpi){#P:before{content:\"246\";}}\n@media (min-resolution:247dpi){#P:before{content:\"247\";}}\n@media (min-resolution:248dpi){#P:before{content:\"248\";}}\n@media (min-resolution:249dpi){#P:before{content:\"249\";}}\n@media (min-resolution:250dpi){#P:before{content:\"250\";}}\n@media (min-resolution:251dpi){#P:before{content:\"251\";}}\n@media (min-resolution:252dpi){#P:before{content:\"252\";}}\n@media (min-resolution:253dpi){#P:before{content:\"253\";}}\n@media (min-resolution:254dpi){#P:before{content:\"254\";}}\n@media (min-resolution:255dpi){#P:before{content:\"255\";}}\n@media (min-resolution:256dpi){#P:before{content:\"256\";}}\n@media (min-resolution:257dpi){#P:before{content:\"257\";}}\n@media (min-resolution:258dpi){#P:before{content:\"258\";}}\n@media (min-resolution:259dpi){#P:before{content:\"259\";}}\n@media (min-resolution:260dpi){#P:before{content:\"260\";}}\n@media (min-resolution:261dpi){#P:before{content:\"261\";}}\n@media (min-resolution:262dpi){#P:before{content:\"262\";}}\n@media (min-resolution:263dpi){#P:before{content:\"263\";}}\n@media (min-resolution:264dpi){#P:before{content:\"264\";}}\n@media (min-resolution:265dpi){#P:before{content:\"265\";}}\n@media (min-resolution:266dpi){#P:before{content:\"266\";}}\n@media (min-resolution:267dpi){#P:before{content:\"267\";}}\n@media (min-resolution:268dpi){#P:before{content:\"268\";}}\n@media (min-resolution:269dpi){#P:before{content:\"269\";}}\n@media (min-resolution:270dpi){#P:before{content:\"270\";}}\n@media (min-resolution:271dpi){#P:before{content:\"271\";}}\n@media (min-resolution:272dpi){#P:before{content:\"272\";}}\n@media (min-resolution:273dpi){#P:before{content:\"273\";}}\n@media (min-resolution:274dpi){#P:before{content:\"274\";}}\n@media (min-resolution:275dpi){#P:before{content:\"275\";}}\n@media (min-resolution:276dpi){#P:before{content:\"276\";}}\n@media (min-resolution:277dpi){#P:before{content:\"277\";}}\n@media (min-resolution:278dpi){#P:before{content:\"278\";}}\n@media (min-resolution:279dpi){#P:before{content:\"279\";}}\n@media (min-resolution:280dpi){#P:before{content:\"280\";}}\n@media (min-resolution:281dpi){#P:before{content:\"281\";}}\n@media (min-resolution:282dpi){#P:before{content:\"282\";}}\n@media (min-resolution:283dpi){#P:before{content:\"283\";}}\n@media (min-resolution:284dpi){#P:before{content:\"284\";}}\n@media (min-resolution:285dpi){#P:before{content:\"285\";}}\n@media (min-resolution:286dpi){#P:before{content:\"286\";}}\n@media (min-resolution:287dpi){#P:before{content:\"287\";}}\n@media (min-resolution:288dpi){#P:before{content:\"288\";}}\n@media (min-resolution:289dpi){#P:before{content:\"289\";}}\n@media (min-resolution:290dpi){#P:before{content:\"290\";}}\n@media (min-resolution:291dpi){#P:before{content:\"291\";}}\n@media (min-resolution:292dpi){#P:before{content:\"292\";}}\n@media (min-resolution:293dpi){#P:before{content:\"293\";}}\n@media (min-resolution:294dpi){#P:before{content:\"294\";}}\n@media (min-resolution:295dpi){#P:before{content:\"295\";}}\n@media (min-resolution:296dpi){#P:before{content:\"296\";}}\n@media (min-resolution:297dpi){#P:before{content:\"297\";}}\n@media (min-resolution:298dpi){#P:before{content:\"298\";}}\n@media (min-resolution:299dpi){#P:before{content:\"299\";}}\n@media (min-resolution:300dpi){#P:before{content:\"300\";}}\n@media (min-resolution:301dpi){#P:before{content:\"301\";}}\n@media (min-resolution:302dpi){#P:before{content:\"302\";}}\n@media (min-resolution:303dpi){#P:before{content:\"303\";}}\n@media (min-resolution:304dpi){#P:before{content:\"304\";}}\n@media (min-resolution:305dpi){#P:before{content:\"305\";}}\n@media (min-resolution:306dpi){#P:before{content:\"306\";}}\n@media (min-resolution:307dpi){#P:before{content:\"307\";}}\n@media (min-resolution:308dpi){#P:before{content:\"308\";}}\n@media (min-resolution:309dpi){#P:before{content:\"309\";}}\n@media (min-resolution:310dpi){#P:before{content:\"310\";}}\n@media (min-resolution:311dpi){#P:before{content:\"311\";}}\n@media (min-resolution:312dpi){#P:before{content:\"312\";}}\n@media (min-resolution:313dpi){#P:before{content:\"313\";}}\n@media (min-resolution:314dpi){#P:before{content:\"314\";}}\n@media (min-resolution:315dpi){#P:before{content:\"315\";}}\n@media (min-resolution:316dpi){#P:before{content:\"316\";}}\n@media (min-resolution:317dpi){#P:before{content:\"317\";}}\n@media (min-resolution:318dpi){#P:before{content:\"318\";}}\n@media (min-resolution:319dpi){#P:before{content:\"319\";}}\n@media (min-resolution:320dpi){#P:before{content:\"320\";}}\n@media (min-resolution:321dpi){#P:before{content:\"321\";}}\n@media (min-resolution:322dpi){#P:before{content:\"322\";}}\n@media (min-resolution:323dpi){#P:before{content:\"323\";}}\n@media (min-resolution:324dpi){#P:before{content:\"324\";}}\n@media (min-resolution:325dpi){#P:before{content:\"325\";}}\n@media (min-resolution:326dpi){#P:before{content:\"326\";}}\n@media (min-resolution:327dpi){#P:before{content:\"327\";}}\n@media (min-resolution:328dpi){#P:before{content:\"328\";}}\n@media (min-resolution:329dpi){#P:before{content:\"329\";}}\n@media (min-resolution:330dpi){#P:before{content:\"330\";}}\n@media (min-resolution:331dpi){#P:before{content:\"331\";}}\n@media (min-resolution:332dpi){#P:before{content:\"332\";}}\n@media (min-resolution:333dpi){#P:before{content:\"333\";}}\n@media (min-resolution:334dpi){#P:before{content:\"334\";}}\n@media (min-resolution:335dpi){#P:before{content:\"335\";}}\n@media (min-resolution:336dpi){#P:before{content:\"336\";}}\n@media (min-resolution:337dpi){#P:before{content:\"337\";}}\n@media (min-resolution:338dpi){#P:before{content:\"338\";}}\n@media (min-resolution:339dpi){#P:before{content:\"339\";}}\n@media (min-resolution:340dpi){#P:before{content:\"340\";}}\n@media (min-resolution:341dpi){#P:before{content:\"341\";}}\n@media (min-resolution:342dpi){#P:before{content:\"342\";}}\n@media (min-resolution:343dpi){#P:before{content:\"343\";}}\n@media (min-resolution:344dpi){#P:before{content:\"344\";}}\n@media (min-resolution:345dpi){#P:before{content:\"345\";}}\n@media (min-resolution:346dpi){#P:before{content:\"346\";}}\n@media (min-resolution:347dpi){#P:before{content:\"347\";}}\n@media (min-resolution:348dpi){#P:before{content:\"348\";}}\n@media (min-resolution:349dpi){#P:before{content:\"349\";}}\n@media (min-resolution:350dpi){#P:before{content:\"350\";}}\n@media (min-resolution:351dpi){#P:before{content:\"351\";}}\n@media (min-resolution:352dpi){#P:before{content:\"352\";}}\n@media (min-resolution:353dpi){#P:before{content:\"353\";}}\n@media (min-resolution:354dpi){#P:before{content:\"354\";}}\n@media (min-resolution:355dpi){#P:before{content:\"355\";}}\n@media (min-resolution:356dpi){#P:before{content:\"356\";}}\n@media (min-resolution:357dpi){#P:before{content:\"357\";}}\n@media (min-resolution:358dpi){#P:before{content:\"358\";}}\n@media (min-resolution:359dpi){#P:before{content:\"359\";}}\n@media (min-resolution:360dpi){#P:before{content:\"360\";}}\n@media (min-resolution:361dpi){#P:before{content:\"361\";}}\n@media (min-resolution:362dpi){#P:before{content:\"362\";}}\n@media (min-resolution:363dpi){#P:before{content:\"363\";}}\n@media (min-resolution:364dpi){#P:before{content:\"364\";}}\n@media (min-resolution:365dpi){#P:before{content:\"365\";}}\n@media (min-resolution:366dpi){#P:before{content:\"366\";}}\n@media (min-resolution:367dpi){#P:before{content:\"367\";}}\n@media (min-resolution:368dpi){#P:before{content:\"368\";}}\n@media (min-resolution:369dpi){#P:before{content:\"369\";}}\n@media (min-resolution:370dpi){#P:before{content:\"370\";}}\n@media (min-resolution:371dpi){#P:before{content:\"371\";}}\n@media (min-resolution:372dpi){#P:before{content:\"372\";}}\n@media (min-resolution:373dpi){#P:before{content:\"373\";}}\n@media (min-resolution:374dpi){#P:before{content:\"374\";}}\n@media (min-resolution:375dpi){#P:before{content:\"375\";}}\n@media (min-resolution:376dpi){#P:before{content:\"376\";}}\n@media (min-resolution:377dpi){#P:before{content:\"377\";}}\n@media (min-resolution:378dpi){#P:before{content:\"378\";}}\n@media (min-resolution:379dpi){#P:before{content:\"379\";}}\n@media (min-resolution:380dpi){#P:before{content:\"380\";}}\n@media (min-resolution:381dpi){#P:before{content:\"381\";}}\n@media (min-resolution:382dpi){#P:before{content:\"382\";}}\n@media (min-resolution:383dpi){#P:before{content:\"383\";}}\n@media (min-resolution:384dpi){#P:before{content:\"384\";}}\n@media (min-resolution:385dpi){#P:before{content:\"385\";}}\n@media (min-resolution:386dpi){#P:before{content:\"386\";}}\n@media (min-resolution:387dpi){#P:before{content:\"387\";}}\n@media (min-resolution:388dpi){#P:before{content:\"388\";}}\n@media (min-resolution:389dpi){#P:before{content:\"389\";}}\n@media (min-resolution:390dpi){#P:before{content:\"390\";}}\n@media (min-resolution:391dpi){#P:before{content:\"391\";}}\n@media (min-resolution:392dpi){#P:before{content:\"392\";}}\n@media (min-resolution:393dpi){#P:before{content:\"393\";}}\n@media (min-resolution:394dpi){#P:before{content:\"394\";}}\n@media (min-resolution:395dpi){#P:before{content:\"395\";}}\n@media (min-resolution:396dpi){#P:before{content:\"396\";}}\n@media (min-resolution:397dpi){#P:before{content:\"397\";}}\n@media (min-resolution:398dpi){#P:before{content:\"398\";}}\n@media (min-resolution:399dpi){#P:before{content:\"399\";}}\n@media (min-resolution:400dpi){#P:before{content:\"400\";}}\n@media (min-resolution:401dpi){#P:before{content:\"\";}}\n\n"
  },
  {
    "path": "css/screen_size.css",
    "content": "@media (min-device-width:399px){#S:before{content:\"\";}}\n@media (min-device-width:400px){#S:before{content:\"400\";}}\n@media (min-device-width:401px){#S:before{content:\"401\";}}\n@media (min-device-width:402px){#S:before{content:\"402\";}}\n@media (min-device-width:403px){#S:before{content:\"403\";}}\n@media (min-device-width:404px){#S:before{content:\"404\";}}\n@media (min-device-width:405px){#S:before{content:\"405\";}}\n@media (min-device-width:406px){#S:before{content:\"406\";}}\n@media (min-device-width:407px){#S:before{content:\"407\";}}\n@media (min-device-width:408px){#S:before{content:\"408\";}}\n@media (min-device-width:409px){#S:before{content:\"409\";}}\n@media (min-device-width:410px){#S:before{content:\"410\";}}\n@media (min-device-width:411px){#S:before{content:\"411\";}}\n@media (min-device-width:412px){#S:before{content:\"412\";}}\n@media (min-device-width:413px){#S:before{content:\"413\";}}\n@media (min-device-width:414px){#S:before{content:\"414\";}}\n@media (min-device-width:415px){#S:before{content:\"415\";}}\n@media (min-device-width:416px){#S:before{content:\"416\";}}\n@media (min-device-width:417px){#S:before{content:\"417\";}}\n@media (min-device-width:418px){#S:before{content:\"418\";}}\n@media (min-device-width:419px){#S:before{content:\"419\";}}\n@media (min-device-width:420px){#S:before{content:\"420\";}}\n@media (min-device-width:421px){#S:before{content:\"421\";}}\n@media (min-device-width:422px){#S:before{content:\"422\";}}\n@media (min-device-width:423px){#S:before{content:\"423\";}}\n@media (min-device-width:424px){#S:before{content:\"424\";}}\n@media (min-device-width:425px){#S:before{content:\"425\";}}\n@media (min-device-width:426px){#S:before{content:\"426\";}}\n@media (min-device-width:427px){#S:before{content:\"427\";}}\n@media (min-device-width:428px){#S:before{content:\"428\";}}\n@media (min-device-width:429px){#S:before{content:\"429\";}}\n@media (min-device-width:430px){#S:before{content:\"430\";}}\n@media (min-device-width:431px){#S:before{content:\"431\";}}\n@media (min-device-width:432px){#S:before{content:\"432\";}}\n@media (min-device-width:433px){#S:before{content:\"433\";}}\n@media (min-device-width:434px){#S:before{content:\"434\";}}\n@media (min-device-width:435px){#S:before{content:\"435\";}}\n@media (min-device-width:436px){#S:before{content:\"436\";}}\n@media (min-device-width:437px){#S:before{content:\"437\";}}\n@media (min-device-width:438px){#S:before{content:\"438\";}}\n@media (min-device-width:439px){#S:before{content:\"439\";}}\n@media (min-device-width:440px){#S:before{content:\"440\";}}\n@media (min-device-width:441px){#S:before{content:\"441\";}}\n@media (min-device-width:442px){#S:before{content:\"442\";}}\n@media (min-device-width:443px){#S:before{content:\"443\";}}\n@media (min-device-width:444px){#S:before{content:\"444\";}}\n@media (min-device-width:445px){#S:before{content:\"445\";}}\n@media (min-device-width:446px){#S:before{content:\"446\";}}\n@media (min-device-width:447px){#S:before{content:\"447\";}}\n@media (min-device-width:448px){#S:before{content:\"448\";}}\n@media (min-device-width:449px){#S:before{content:\"449\";}}\n@media (min-device-width:450px){#S:before{content:\"450\";}}\n@media (min-device-width:451px){#S:before{content:\"451\";}}\n@media (min-device-width:452px){#S:before{content:\"452\";}}\n@media (min-device-width:453px){#S:before{content:\"453\";}}\n@media (min-device-width:454px){#S:before{content:\"454\";}}\n@media (min-device-width:455px){#S:before{content:\"455\";}}\n@media (min-device-width:456px){#S:before{content:\"456\";}}\n@media (min-device-width:457px){#S:before{content:\"457\";}}\n@media (min-device-width:458px){#S:before{content:\"458\";}}\n@media (min-device-width:459px){#S:before{content:\"459\";}}\n@media (min-device-width:460px){#S:before{content:\"460\";}}\n@media (min-device-width:461px){#S:before{content:\"461\";}}\n@media (min-device-width:462px){#S:before{content:\"462\";}}\n@media (min-device-width:463px){#S:before{content:\"463\";}}\n@media (min-device-width:464px){#S:before{content:\"464\";}}\n@media (min-device-width:465px){#S:before{content:\"465\";}}\n@media (min-device-width:466px){#S:before{content:\"466\";}}\n@media (min-device-width:467px){#S:before{content:\"467\";}}\n@media (min-device-width:468px){#S:before{content:\"468\";}}\n@media (min-device-width:469px){#S:before{content:\"469\";}}\n@media (min-device-width:470px){#S:before{content:\"470\";}}\n@media (min-device-width:471px){#S:before{content:\"471\";}}\n@media (min-device-width:472px){#S:before{content:\"472\";}}\n@media (min-device-width:473px){#S:before{content:\"473\";}}\n@media (min-device-width:474px){#S:before{content:\"474\";}}\n@media (min-device-width:475px){#S:before{content:\"475\";}}\n@media (min-device-width:476px){#S:before{content:\"476\";}}\n@media (min-device-width:477px){#S:before{content:\"477\";}}\n@media (min-device-width:478px){#S:before{content:\"478\";}}\n@media (min-device-width:479px){#S:before{content:\"479\";}}\n@media (min-device-width:480px){#S:before{content:\"480\";}}\n@media (min-device-width:481px){#S:before{content:\"481\";}}\n@media (min-device-width:482px){#S:before{content:\"482\";}}\n@media (min-device-width:483px){#S:before{content:\"483\";}}\n@media (min-device-width:484px){#S:before{content:\"484\";}}\n@media (min-device-width:485px){#S:before{content:\"485\";}}\n@media (min-device-width:486px){#S:before{content:\"486\";}}\n@media (min-device-width:487px){#S:before{content:\"487\";}}\n@media (min-device-width:488px){#S:before{content:\"488\";}}\n@media (min-device-width:489px){#S:before{content:\"489\";}}\n@media (min-device-width:490px){#S:before{content:\"490\";}}\n@media (min-device-width:491px){#S:before{content:\"491\";}}\n@media (min-device-width:492px){#S:before{content:\"492\";}}\n@media (min-device-width:493px){#S:before{content:\"493\";}}\n@media (min-device-width:494px){#S:before{content:\"494\";}}\n@media (min-device-width:495px){#S:before{content:\"495\";}}\n@media (min-device-width:496px){#S:before{content:\"496\";}}\n@media (min-device-width:497px){#S:before{content:\"497\";}}\n@media (min-device-width:498px){#S:before{content:\"498\";}}\n@media (min-device-width:499px){#S:before{content:\"499\";}}\n@media (min-device-width:500px){#S:before{content:\"500\";}}\n@media (min-device-width:501px){#S:before{content:\"501\";}}\n@media (min-device-width:502px){#S:before{content:\"502\";}}\n@media (min-device-width:503px){#S:before{content:\"503\";}}\n@media (min-device-width:504px){#S:before{content:\"504\";}}\n@media (min-device-width:505px){#S:before{content:\"505\";}}\n@media (min-device-width:506px){#S:before{content:\"506\";}}\n@media (min-device-width:507px){#S:before{content:\"507\";}}\n@media (min-device-width:508px){#S:before{content:\"508\";}}\n@media (min-device-width:509px){#S:before{content:\"509\";}}\n@media (min-device-width:510px){#S:before{content:\"510\";}}\n@media (min-device-width:511px){#S:before{content:\"511\";}}\n@media (min-device-width:512px){#S:before{content:\"512\";}}\n@media (min-device-width:513px){#S:before{content:\"513\";}}\n@media (min-device-width:514px){#S:before{content:\"514\";}}\n@media (min-device-width:515px){#S:before{content:\"515\";}}\n@media (min-device-width:516px){#S:before{content:\"516\";}}\n@media (min-device-width:517px){#S:before{content:\"517\";}}\n@media (min-device-width:518px){#S:before{content:\"518\";}}\n@media (min-device-width:519px){#S:before{content:\"519\";}}\n@media (min-device-width:520px){#S:before{content:\"520\";}}\n@media (min-device-width:521px){#S:before{content:\"521\";}}\n@media (min-device-width:522px){#S:before{content:\"522\";}}\n@media (min-device-width:523px){#S:before{content:\"523\";}}\n@media (min-device-width:524px){#S:before{content:\"524\";}}\n@media (min-device-width:525px){#S:before{content:\"525\";}}\n@media (min-device-width:526px){#S:before{content:\"526\";}}\n@media (min-device-width:527px){#S:before{content:\"527\";}}\n@media (min-device-width:528px){#S:before{content:\"528\";}}\n@media (min-device-width:529px){#S:before{content:\"529\";}}\n@media (min-device-width:530px){#S:before{content:\"530\";}}\n@media (min-device-width:531px){#S:before{content:\"531\";}}\n@media (min-device-width:532px){#S:before{content:\"532\";}}\n@media (min-device-width:533px){#S:before{content:\"533\";}}\n@media (min-device-width:534px){#S:before{content:\"534\";}}\n@media (min-device-width:535px){#S:before{content:\"535\";}}\n@media (min-device-width:536px){#S:before{content:\"536\";}}\n@media (min-device-width:537px){#S:before{content:\"537\";}}\n@media (min-device-width:538px){#S:before{content:\"538\";}}\n@media (min-device-width:539px){#S:before{content:\"539\";}}\n@media (min-device-width:540px){#S:before{content:\"540\";}}\n@media (min-device-width:541px){#S:before{content:\"541\";}}\n@media (min-device-width:542px){#S:before{content:\"542\";}}\n@media (min-device-width:543px){#S:before{content:\"543\";}}\n@media (min-device-width:544px){#S:before{content:\"544\";}}\n@media (min-device-width:545px){#S:before{content:\"545\";}}\n@media (min-device-width:546px){#S:before{content:\"546\";}}\n@media (min-device-width:547px){#S:before{content:\"547\";}}\n@media (min-device-width:548px){#S:before{content:\"548\";}}\n@media (min-device-width:549px){#S:before{content:\"549\";}}\n@media (min-device-width:550px){#S:before{content:\"550\";}}\n@media (min-device-width:551px){#S:before{content:\"551\";}}\n@media (min-device-width:552px){#S:before{content:\"552\";}}\n@media (min-device-width:553px){#S:before{content:\"553\";}}\n@media (min-device-width:554px){#S:before{content:\"554\";}}\n@media (min-device-width:555px){#S:before{content:\"555\";}}\n@media (min-device-width:556px){#S:before{content:\"556\";}}\n@media (min-device-width:557px){#S:before{content:\"557\";}}\n@media (min-device-width:558px){#S:before{content:\"558\";}}\n@media (min-device-width:559px){#S:before{content:\"559\";}}\n@media (min-device-width:560px){#S:before{content:\"560\";}}\n@media (min-device-width:561px){#S:before{content:\"561\";}}\n@media (min-device-width:562px){#S:before{content:\"562\";}}\n@media (min-device-width:563px){#S:before{content:\"563\";}}\n@media (min-device-width:564px){#S:before{content:\"564\";}}\n@media (min-device-width:565px){#S:before{content:\"565\";}}\n@media (min-device-width:566px){#S:before{content:\"566\";}}\n@media (min-device-width:567px){#S:before{content:\"567\";}}\n@media (min-device-width:568px){#S:before{content:\"568\";}}\n@media (min-device-width:569px){#S:before{content:\"569\";}}\n@media (min-device-width:570px){#S:before{content:\"570\";}}\n@media (min-device-width:571px){#S:before{content:\"571\";}}\n@media (min-device-width:572px){#S:before{content:\"572\";}}\n@media (min-device-width:573px){#S:before{content:\"573\";}}\n@media (min-device-width:574px){#S:before{content:\"574\";}}\n@media (min-device-width:575px){#S:before{content:\"575\";}}\n@media (min-device-width:576px){#S:before{content:\"576\";}}\n@media (min-device-width:577px){#S:before{content:\"577\";}}\n@media (min-device-width:578px){#S:before{content:\"578\";}}\n@media (min-device-width:579px){#S:before{content:\"579\";}}\n@media (min-device-width:580px){#S:before{content:\"580\";}}\n@media (min-device-width:581px){#S:before{content:\"581\";}}\n@media (min-device-width:582px){#S:before{content:\"582\";}}\n@media (min-device-width:583px){#S:before{content:\"583\";}}\n@media (min-device-width:584px){#S:before{content:\"584\";}}\n@media (min-device-width:585px){#S:before{content:\"585\";}}\n@media (min-device-width:586px){#S:before{content:\"586\";}}\n@media (min-device-width:587px){#S:before{content:\"587\";}}\n@media (min-device-width:588px){#S:before{content:\"588\";}}\n@media (min-device-width:589px){#S:before{content:\"589\";}}\n@media (min-device-width:590px){#S:before{content:\"590\";}}\n@media (min-device-width:591px){#S:before{content:\"591\";}}\n@media (min-device-width:592px){#S:before{content:\"592\";}}\n@media (min-device-width:593px){#S:before{content:\"593\";}}\n@media (min-device-width:594px){#S:before{content:\"594\";}}\n@media (min-device-width:595px){#S:before{content:\"595\";}}\n@media (min-device-width:596px){#S:before{content:\"596\";}}\n@media (min-device-width:597px){#S:before{content:\"597\";}}\n@media (min-device-width:598px){#S:before{content:\"598\";}}\n@media (min-device-width:599px){#S:before{content:\"599\";}}\n@media (min-device-width:600px){#S:before{content:\"600\";}}\n@media (min-device-width:601px){#S:before{content:\"601\";}}\n@media (min-device-width:602px){#S:before{content:\"602\";}}\n@media (min-device-width:603px){#S:before{content:\"603\";}}\n@media (min-device-width:604px){#S:before{content:\"604\";}}\n@media (min-device-width:605px){#S:before{content:\"605\";}}\n@media (min-device-width:606px){#S:before{content:\"606\";}}\n@media (min-device-width:607px){#S:before{content:\"607\";}}\n@media (min-device-width:608px){#S:before{content:\"608\";}}\n@media (min-device-width:609px){#S:before{content:\"609\";}}\n@media (min-device-width:610px){#S:before{content:\"610\";}}\n@media (min-device-width:611px){#S:before{content:\"611\";}}\n@media (min-device-width:612px){#S:before{content:\"612\";}}\n@media (min-device-width:613px){#S:before{content:\"613\";}}\n@media (min-device-width:614px){#S:before{content:\"614\";}}\n@media (min-device-width:615px){#S:before{content:\"615\";}}\n@media (min-device-width:616px){#S:before{content:\"616\";}}\n@media (min-device-width:617px){#S:before{content:\"617\";}}\n@media (min-device-width:618px){#S:before{content:\"618\";}}\n@media (min-device-width:619px){#S:before{content:\"619\";}}\n@media (min-device-width:620px){#S:before{content:\"620\";}}\n@media (min-device-width:621px){#S:before{content:\"621\";}}\n@media (min-device-width:622px){#S:before{content:\"622\";}}\n@media (min-device-width:623px){#S:before{content:\"623\";}}\n@media (min-device-width:624px){#S:before{content:\"624\";}}\n@media (min-device-width:625px){#S:before{content:\"625\";}}\n@media (min-device-width:626px){#S:before{content:\"626\";}}\n@media (min-device-width:627px){#S:before{content:\"627\";}}\n@media (min-device-width:628px){#S:before{content:\"628\";}}\n@media (min-device-width:629px){#S:before{content:\"629\";}}\n@media (min-device-width:630px){#S:before{content:\"630\";}}\n@media (min-device-width:631px){#S:before{content:\"631\";}}\n@media (min-device-width:632px){#S:before{content:\"632\";}}\n@media (min-device-width:633px){#S:before{content:\"633\";}}\n@media (min-device-width:634px){#S:before{content:\"634\";}}\n@media (min-device-width:635px){#S:before{content:\"635\";}}\n@media (min-device-width:636px){#S:before{content:\"636\";}}\n@media (min-device-width:637px){#S:before{content:\"637\";}}\n@media (min-device-width:638px){#S:before{content:\"638\";}}\n@media (min-device-width:639px){#S:before{content:\"639\";}}\n@media (min-device-width:640px){#S:before{content:\"640\";}}\n@media (min-device-width:641px){#S:before{content:\"641\";}}\n@media (min-device-width:642px){#S:before{content:\"642\";}}\n@media (min-device-width:643px){#S:before{content:\"643\";}}\n@media (min-device-width:644px){#S:before{content:\"644\";}}\n@media (min-device-width:645px){#S:before{content:\"645\";}}\n@media (min-device-width:646px){#S:before{content:\"646\";}}\n@media (min-device-width:647px){#S:before{content:\"647\";}}\n@media (min-device-width:648px){#S:before{content:\"648\";}}\n@media (min-device-width:649px){#S:before{content:\"649\";}}\n@media (min-device-width:650px){#S:before{content:\"650\";}}\n@media (min-device-width:651px){#S:before{content:\"651\";}}\n@media (min-device-width:652px){#S:before{content:\"652\";}}\n@media (min-device-width:653px){#S:before{content:\"653\";}}\n@media (min-device-width:654px){#S:before{content:\"654\";}}\n@media (min-device-width:655px){#S:before{content:\"655\";}}\n@media (min-device-width:656px){#S:before{content:\"656\";}}\n@media (min-device-width:657px){#S:before{content:\"657\";}}\n@media (min-device-width:658px){#S:before{content:\"658\";}}\n@media (min-device-width:659px){#S:before{content:\"659\";}}\n@media (min-device-width:660px){#S:before{content:\"660\";}}\n@media (min-device-width:661px){#S:before{content:\"661\";}}\n@media (min-device-width:662px){#S:before{content:\"662\";}}\n@media (min-device-width:663px){#S:before{content:\"663\";}}\n@media (min-device-width:664px){#S:before{content:\"664\";}}\n@media (min-device-width:665px){#S:before{content:\"665\";}}\n@media (min-device-width:666px){#S:before{content:\"666\";}}\n@media (min-device-width:667px){#S:before{content:\"667\";}}\n@media (min-device-width:668px){#S:before{content:\"668\";}}\n@media (min-device-width:669px){#S:before{content:\"669\";}}\n@media (min-device-width:670px){#S:before{content:\"670\";}}\n@media (min-device-width:671px){#S:before{content:\"671\";}}\n@media (min-device-width:672px){#S:before{content:\"672\";}}\n@media (min-device-width:673px){#S:before{content:\"673\";}}\n@media (min-device-width:674px){#S:before{content:\"674\";}}\n@media (min-device-width:675px){#S:before{content:\"675\";}}\n@media (min-device-width:676px){#S:before{content:\"676\";}}\n@media (min-device-width:677px){#S:before{content:\"677\";}}\n@media (min-device-width:678px){#S:before{content:\"678\";}}\n@media (min-device-width:679px){#S:before{content:\"679\";}}\n@media (min-device-width:680px){#S:before{content:\"680\";}}\n@media (min-device-width:681px){#S:before{content:\"681\";}}\n@media (min-device-width:682px){#S:before{content:\"682\";}}\n@media (min-device-width:683px){#S:before{content:\"683\";}}\n@media (min-device-width:684px){#S:before{content:\"684\";}}\n@media (min-device-width:685px){#S:before{content:\"685\";}}\n@media (min-device-width:686px){#S:before{content:\"686\";}}\n@media (min-device-width:687px){#S:before{content:\"687\";}}\n@media (min-device-width:688px){#S:before{content:\"688\";}}\n@media (min-device-width:689px){#S:before{content:\"689\";}}\n@media (min-device-width:690px){#S:before{content:\"690\";}}\n@media (min-device-width:691px){#S:before{content:\"691\";}}\n@media (min-device-width:692px){#S:before{content:\"692\";}}\n@media (min-device-width:693px){#S:before{content:\"693\";}}\n@media (min-device-width:694px){#S:before{content:\"694\";}}\n@media (min-device-width:695px){#S:before{content:\"695\";}}\n@media (min-device-width:696px){#S:before{content:\"696\";}}\n@media (min-device-width:697px){#S:before{content:\"697\";}}\n@media (min-device-width:698px){#S:before{content:\"698\";}}\n@media (min-device-width:699px){#S:before{content:\"699\";}}\n@media (min-device-width:700px){#S:before{content:\"700\";}}\n@media (min-device-width:701px){#S:before{content:\"701\";}}\n@media (min-device-width:702px){#S:before{content:\"702\";}}\n@media (min-device-width:703px){#S:before{content:\"703\";}}\n@media (min-device-width:704px){#S:before{content:\"704\";}}\n@media (min-device-width:705px){#S:before{content:\"705\";}}\n@media (min-device-width:706px){#S:before{content:\"706\";}}\n@media (min-device-width:707px){#S:before{content:\"707\";}}\n@media (min-device-width:708px){#S:before{content:\"708\";}}\n@media (min-device-width:709px){#S:before{content:\"709\";}}\n@media (min-device-width:710px){#S:before{content:\"710\";}}\n@media (min-device-width:711px){#S:before{content:\"711\";}}\n@media (min-device-width:712px){#S:before{content:\"712\";}}\n@media (min-device-width:713px){#S:before{content:\"713\";}}\n@media (min-device-width:714px){#S:before{content:\"714\";}}\n@media (min-device-width:715px){#S:before{content:\"715\";}}\n@media (min-device-width:716px){#S:before{content:\"716\";}}\n@media (min-device-width:717px){#S:before{content:\"717\";}}\n@media (min-device-width:718px){#S:before{content:\"718\";}}\n@media (min-device-width:719px){#S:before{content:\"719\";}}\n@media (min-device-width:720px){#S:before{content:\"720\";}}\n@media (min-device-width:721px){#S:before{content:\"721\";}}\n@media (min-device-width:722px){#S:before{content:\"722\";}}\n@media (min-device-width:723px){#S:before{content:\"723\";}}\n@media (min-device-width:724px){#S:before{content:\"724\";}}\n@media (min-device-width:725px){#S:before{content:\"725\";}}\n@media (min-device-width:726px){#S:before{content:\"726\";}}\n@media (min-device-width:727px){#S:before{content:\"727\";}}\n@media (min-device-width:728px){#S:before{content:\"728\";}}\n@media (min-device-width:729px){#S:before{content:\"729\";}}\n@media (min-device-width:730px){#S:before{content:\"730\";}}\n@media (min-device-width:731px){#S:before{content:\"731\";}}\n@media (min-device-width:732px){#S:before{content:\"732\";}}\n@media (min-device-width:733px){#S:before{content:\"733\";}}\n@media (min-device-width:734px){#S:before{content:\"734\";}}\n@media (min-device-width:735px){#S:before{content:\"735\";}}\n@media (min-device-width:736px){#S:before{content:\"736\";}}\n@media (min-device-width:737px){#S:before{content:\"737\";}}\n@media (min-device-width:738px){#S:before{content:\"738\";}}\n@media (min-device-width:739px){#S:before{content:\"739\";}}\n@media (min-device-width:740px){#S:before{content:\"740\";}}\n@media (min-device-width:741px){#S:before{content:\"741\";}}\n@media (min-device-width:742px){#S:before{content:\"742\";}}\n@media (min-device-width:743px){#S:before{content:\"743\";}}\n@media (min-device-width:744px){#S:before{content:\"744\";}}\n@media (min-device-width:745px){#S:before{content:\"745\";}}\n@media (min-device-width:746px){#S:before{content:\"746\";}}\n@media (min-device-width:747px){#S:before{content:\"747\";}}\n@media (min-device-width:748px){#S:before{content:\"748\";}}\n@media (min-device-width:749px){#S:before{content:\"749\";}}\n@media (min-device-width:750px){#S:before{content:\"750\";}}\n@media (min-device-width:751px){#S:before{content:\"751\";}}\n@media (min-device-width:752px){#S:before{content:\"752\";}}\n@media (min-device-width:753px){#S:before{content:\"753\";}}\n@media (min-device-width:754px){#S:before{content:\"754\";}}\n@media (min-device-width:755px){#S:before{content:\"755\";}}\n@media (min-device-width:756px){#S:before{content:\"756\";}}\n@media (min-device-width:757px){#S:before{content:\"757\";}}\n@media (min-device-width:758px){#S:before{content:\"758\";}}\n@media (min-device-width:759px){#S:before{content:\"759\";}}\n@media (min-device-width:760px){#S:before{content:\"760\";}}\n@media (min-device-width:761px){#S:before{content:\"761\";}}\n@media (min-device-width:762px){#S:before{content:\"762\";}}\n@media (min-device-width:763px){#S:before{content:\"763\";}}\n@media (min-device-width:764px){#S:before{content:\"764\";}}\n@media (min-device-width:765px){#S:before{content:\"765\";}}\n@media (min-device-width:766px){#S:before{content:\"766\";}}\n@media (min-device-width:767px){#S:before{content:\"767\";}}\n@media (min-device-width:768px){#S:before{content:\"768\";}}\n@media (min-device-width:769px){#S:before{content:\"769\";}}\n@media (min-device-width:770px){#S:before{content:\"770\";}}\n@media (min-device-width:771px){#S:before{content:\"771\";}}\n@media (min-device-width:772px){#S:before{content:\"772\";}}\n@media (min-device-width:773px){#S:before{content:\"773\";}}\n@media (min-device-width:774px){#S:before{content:\"774\";}}\n@media (min-device-width:775px){#S:before{content:\"775\";}}\n@media (min-device-width:776px){#S:before{content:\"776\";}}\n@media (min-device-width:777px){#S:before{content:\"777\";}}\n@media (min-device-width:778px){#S:before{content:\"778\";}}\n@media (min-device-width:779px){#S:before{content:\"779\";}}\n@media (min-device-width:780px){#S:before{content:\"780\";}}\n@media (min-device-width:781px){#S:before{content:\"781\";}}\n@media (min-device-width:782px){#S:before{content:\"782\";}}\n@media (min-device-width:783px){#S:before{content:\"783\";}}\n@media (min-device-width:784px){#S:before{content:\"784\";}}\n@media (min-device-width:785px){#S:before{content:\"785\";}}\n@media (min-device-width:786px){#S:before{content:\"786\";}}\n@media (min-device-width:787px){#S:before{content:\"787\";}}\n@media (min-device-width:788px){#S:before{content:\"788\";}}\n@media (min-device-width:789px){#S:before{content:\"789\";}}\n@media (min-device-width:790px){#S:before{content:\"790\";}}\n@media (min-device-width:791px){#S:before{content:\"791\";}}\n@media (min-device-width:792px){#S:before{content:\"792\";}}\n@media (min-device-width:793px){#S:before{content:\"793\";}}\n@media (min-device-width:794px){#S:before{content:\"794\";}}\n@media (min-device-width:795px){#S:before{content:\"795\";}}\n@media (min-device-width:796px){#S:before{content:\"796\";}}\n@media (min-device-width:797px){#S:before{content:\"797\";}}\n@media (min-device-width:798px){#S:before{content:\"798\";}}\n@media (min-device-width:799px){#S:before{content:\"799\";}}\n@media (min-device-width:800px){#S:before{content:\"800\";}}\n@media (min-device-width:801px){#S:before{content:\"801\";}}\n@media (min-device-width:802px){#S:before{content:\"802\";}}\n@media (min-device-width:803px){#S:before{content:\"803\";}}\n@media (min-device-width:804px){#S:before{content:\"804\";}}\n@media (min-device-width:805px){#S:before{content:\"805\";}}\n@media (min-device-width:806px){#S:before{content:\"806\";}}\n@media (min-device-width:807px){#S:before{content:\"807\";}}\n@media (min-device-width:808px){#S:before{content:\"808\";}}\n@media (min-device-width:809px){#S:before{content:\"809\";}}\n@media (min-device-width:810px){#S:before{content:\"810\";}}\n@media (min-device-width:811px){#S:before{content:\"811\";}}\n@media (min-device-width:812px){#S:before{content:\"812\";}}\n@media (min-device-width:813px){#S:before{content:\"813\";}}\n@media (min-device-width:814px){#S:before{content:\"814\";}}\n@media (min-device-width:815px){#S:before{content:\"815\";}}\n@media (min-device-width:816px){#S:before{content:\"816\";}}\n@media (min-device-width:817px){#S:before{content:\"817\";}}\n@media (min-device-width:818px){#S:before{content:\"818\";}}\n@media (min-device-width:819px){#S:before{content:\"819\";}}\n@media (min-device-width:820px){#S:before{content:\"820\";}}\n@media (min-device-width:821px){#S:before{content:\"821\";}}\n@media (min-device-width:822px){#S:before{content:\"822\";}}\n@media (min-device-width:823px){#S:before{content:\"823\";}}\n@media (min-device-width:824px){#S:before{content:\"824\";}}\n@media (min-device-width:825px){#S:before{content:\"825\";}}\n@media (min-device-width:826px){#S:before{content:\"826\";}}\n@media (min-device-width:827px){#S:before{content:\"827\";}}\n@media (min-device-width:828px){#S:before{content:\"828\";}}\n@media (min-device-width:829px){#S:before{content:\"829\";}}\n@media (min-device-width:830px){#S:before{content:\"830\";}}\n@media (min-device-width:831px){#S:before{content:\"831\";}}\n@media (min-device-width:832px){#S:before{content:\"832\";}}\n@media (min-device-width:833px){#S:before{content:\"833\";}}\n@media (min-device-width:834px){#S:before{content:\"834\";}}\n@media (min-device-width:835px){#S:before{content:\"835\";}}\n@media (min-device-width:836px){#S:before{content:\"836\";}}\n@media (min-device-width:837px){#S:before{content:\"837\";}}\n@media (min-device-width:838px){#S:before{content:\"838\";}}\n@media (min-device-width:839px){#S:before{content:\"839\";}}\n@media (min-device-width:840px){#S:before{content:\"840\";}}\n@media (min-device-width:841px){#S:before{content:\"841\";}}\n@media (min-device-width:842px){#S:before{content:\"842\";}}\n@media (min-device-width:843px){#S:before{content:\"843\";}}\n@media (min-device-width:844px){#S:before{content:\"844\";}}\n@media (min-device-width:845px){#S:before{content:\"845\";}}\n@media (min-device-width:846px){#S:before{content:\"846\";}}\n@media (min-device-width:847px){#S:before{content:\"847\";}}\n@media (min-device-width:848px){#S:before{content:\"848\";}}\n@media (min-device-width:849px){#S:before{content:\"849\";}}\n@media (min-device-width:850px){#S:before{content:\"850\";}}\n@media (min-device-width:851px){#S:before{content:\"851\";}}\n@media (min-device-width:852px){#S:before{content:\"852\";}}\n@media (min-device-width:853px){#S:before{content:\"853\";}}\n@media (min-device-width:854px){#S:before{content:\"854\";}}\n@media (min-device-width:855px){#S:before{content:\"855\";}}\n@media (min-device-width:856px){#S:before{content:\"856\";}}\n@media (min-device-width:857px){#S:before{content:\"857\";}}\n@media (min-device-width:858px){#S:before{content:\"858\";}}\n@media (min-device-width:859px){#S:before{content:\"859\";}}\n@media (min-device-width:860px){#S:before{content:\"860\";}}\n@media (min-device-width:861px){#S:before{content:\"861\";}}\n@media (min-device-width:862px){#S:before{content:\"862\";}}\n@media (min-device-width:863px){#S:before{content:\"863\";}}\n@media (min-device-width:864px){#S:before{content:\"864\";}}\n@media (min-device-width:865px){#S:before{content:\"865\";}}\n@media (min-device-width:866px){#S:before{content:\"866\";}}\n@media (min-device-width:867px){#S:before{content:\"867\";}}\n@media (min-device-width:868px){#S:before{content:\"868\";}}\n@media (min-device-width:869px){#S:before{content:\"869\";}}\n@media (min-device-width:870px){#S:before{content:\"870\";}}\n@media (min-device-width:871px){#S:before{content:\"871\";}}\n@media (min-device-width:872px){#S:before{content:\"872\";}}\n@media (min-device-width:873px){#S:before{content:\"873\";}}\n@media (min-device-width:874px){#S:before{content:\"874\";}}\n@media (min-device-width:875px){#S:before{content:\"875\";}}\n@media (min-device-width:876px){#S:before{content:\"876\";}}\n@media (min-device-width:877px){#S:before{content:\"877\";}}\n@media (min-device-width:878px){#S:before{content:\"878\";}}\n@media (min-device-width:879px){#S:before{content:\"879\";}}\n@media (min-device-width:880px){#S:before{content:\"880\";}}\n@media (min-device-width:881px){#S:before{content:\"881\";}}\n@media (min-device-width:882px){#S:before{content:\"882\";}}\n@media (min-device-width:883px){#S:before{content:\"883\";}}\n@media (min-device-width:884px){#S:before{content:\"884\";}}\n@media (min-device-width:885px){#S:before{content:\"885\";}}\n@media (min-device-width:886px){#S:before{content:\"886\";}}\n@media (min-device-width:887px){#S:before{content:\"887\";}}\n@media (min-device-width:888px){#S:before{content:\"888\";}}\n@media (min-device-width:889px){#S:before{content:\"889\";}}\n@media (min-device-width:890px){#S:before{content:\"890\";}}\n@media (min-device-width:891px){#S:before{content:\"891\";}}\n@media (min-device-width:892px){#S:before{content:\"892\";}}\n@media (min-device-width:893px){#S:before{content:\"893\";}}\n@media (min-device-width:894px){#S:before{content:\"894\";}}\n@media (min-device-width:895px){#S:before{content:\"895\";}}\n@media (min-device-width:896px){#S:before{content:\"896\";}}\n@media (min-device-width:897px){#S:before{content:\"897\";}}\n@media (min-device-width:898px){#S:before{content:\"898\";}}\n@media (min-device-width:899px){#S:before{content:\"899\";}}\n@media (min-device-width:900px){#S:before{content:\"900\";}}\n@media (min-device-width:901px){#S:before{content:\"901\";}}\n@media (min-device-width:902px){#S:before{content:\"902\";}}\n@media (min-device-width:903px){#S:before{content:\"903\";}}\n@media (min-device-width:904px){#S:before{content:\"904\";}}\n@media (min-device-width:905px){#S:before{content:\"905\";}}\n@media (min-device-width:906px){#S:before{content:\"906\";}}\n@media (min-device-width:907px){#S:before{content:\"907\";}}\n@media (min-device-width:908px){#S:before{content:\"908\";}}\n@media (min-device-width:909px){#S:before{content:\"909\";}}\n@media (min-device-width:910px){#S:before{content:\"910\";}}\n@media (min-device-width:911px){#S:before{content:\"911\";}}\n@media (min-device-width:912px){#S:before{content:\"912\";}}\n@media (min-device-width:913px){#S:before{content:\"913\";}}\n@media (min-device-width:914px){#S:before{content:\"914\";}}\n@media (min-device-width:915px){#S:before{content:\"915\";}}\n@media (min-device-width:916px){#S:before{content:\"916\";}}\n@media (min-device-width:917px){#S:before{content:\"917\";}}\n@media (min-device-width:918px){#S:before{content:\"918\";}}\n@media (min-device-width:919px){#S:before{content:\"919\";}}\n@media (min-device-width:920px){#S:before{content:\"920\";}}\n@media (min-device-width:921px){#S:before{content:\"921\";}}\n@media (min-device-width:922px){#S:before{content:\"922\";}}\n@media (min-device-width:923px){#S:before{content:\"923\";}}\n@media (min-device-width:924px){#S:before{content:\"924\";}}\n@media (min-device-width:925px){#S:before{content:\"925\";}}\n@media (min-device-width:926px){#S:before{content:\"926\";}}\n@media (min-device-width:927px){#S:before{content:\"927\";}}\n@media (min-device-width:928px){#S:before{content:\"928\";}}\n@media (min-device-width:929px){#S:before{content:\"929\";}}\n@media (min-device-width:930px){#S:before{content:\"930\";}}\n@media (min-device-width:931px){#S:before{content:\"931\";}}\n@media (min-device-width:932px){#S:before{content:\"932\";}}\n@media (min-device-width:933px){#S:before{content:\"933\";}}\n@media (min-device-width:934px){#S:before{content:\"934\";}}\n@media (min-device-width:935px){#S:before{content:\"935\";}}\n@media (min-device-width:936px){#S:before{content:\"936\";}}\n@media (min-device-width:937px){#S:before{content:\"937\";}}\n@media (min-device-width:938px){#S:before{content:\"938\";}}\n@media (min-device-width:939px){#S:before{content:\"939\";}}\n@media (min-device-width:940px){#S:before{content:\"940\";}}\n@media (min-device-width:941px){#S:before{content:\"941\";}}\n@media (min-device-width:942px){#S:before{content:\"942\";}}\n@media (min-device-width:943px){#S:before{content:\"943\";}}\n@media (min-device-width:944px){#S:before{content:\"944\";}}\n@media (min-device-width:945px){#S:before{content:\"945\";}}\n@media (min-device-width:946px){#S:before{content:\"946\";}}\n@media (min-device-width:947px){#S:before{content:\"947\";}}\n@media (min-device-width:948px){#S:before{content:\"948\";}}\n@media (min-device-width:949px){#S:before{content:\"949\";}}\n@media (min-device-width:950px){#S:before{content:\"950\";}}\n@media (min-device-width:951px){#S:before{content:\"951\";}}\n@media (min-device-width:952px){#S:before{content:\"952\";}}\n@media (min-device-width:953px){#S:before{content:\"953\";}}\n@media (min-device-width:954px){#S:before{content:\"954\";}}\n@media (min-device-width:955px){#S:before{content:\"955\";}}\n@media (min-device-width:956px){#S:before{content:\"956\";}}\n@media (min-device-width:957px){#S:before{content:\"957\";}}\n@media (min-device-width:958px){#S:before{content:\"958\";}}\n@media (min-device-width:959px){#S:before{content:\"959\";}}\n@media (min-device-width:960px){#S:before{content:\"960\";}}\n@media (min-device-width:961px){#S:before{content:\"961\";}}\n@media (min-device-width:962px){#S:before{content:\"962\";}}\n@media (min-device-width:963px){#S:before{content:\"963\";}}\n@media (min-device-width:964px){#S:before{content:\"964\";}}\n@media (min-device-width:965px){#S:before{content:\"965\";}}\n@media (min-device-width:966px){#S:before{content:\"966\";}}\n@media (min-device-width:967px){#S:before{content:\"967\";}}\n@media (min-device-width:968px){#S:before{content:\"968\";}}\n@media (min-device-width:969px){#S:before{content:\"969\";}}\n@media (min-device-width:970px){#S:before{content:\"970\";}}\n@media (min-device-width:971px){#S:before{content:\"971\";}}\n@media (min-device-width:972px){#S:before{content:\"972\";}}\n@media (min-device-width:973px){#S:before{content:\"973\";}}\n@media (min-device-width:974px){#S:before{content:\"974\";}}\n@media (min-device-width:975px){#S:before{content:\"975\";}}\n@media (min-device-width:976px){#S:before{content:\"976\";}}\n@media (min-device-width:977px){#S:before{content:\"977\";}}\n@media (min-device-width:978px){#S:before{content:\"978\";}}\n@media (min-device-width:979px){#S:before{content:\"979\";}}\n@media (min-device-width:980px){#S:before{content:\"980\";}}\n@media (min-device-width:981px){#S:before{content:\"981\";}}\n@media (min-device-width:982px){#S:before{content:\"982\";}}\n@media (min-device-width:983px){#S:before{content:\"983\";}}\n@media (min-device-width:984px){#S:before{content:\"984\";}}\n@media (min-device-width:985px){#S:before{content:\"985\";}}\n@media (min-device-width:986px){#S:before{content:\"986\";}}\n@media (min-device-width:987px){#S:before{content:\"987\";}}\n@media (min-device-width:988px){#S:before{content:\"988\";}}\n@media (min-device-width:989px){#S:before{content:\"989\";}}\n@media (min-device-width:990px){#S:before{content:\"990\";}}\n@media (min-device-width:991px){#S:before{content:\"991\";}}\n@media (min-device-width:992px){#S:before{content:\"992\";}}\n@media (min-device-width:993px){#S:before{content:\"993\";}}\n@media (min-device-width:994px){#S:before{content:\"994\";}}\n@media (min-device-width:995px){#S:before{content:\"995\";}}\n@media (min-device-width:996px){#S:before{content:\"996\";}}\n@media (min-device-width:997px){#S:before{content:\"997\";}}\n@media (min-device-width:998px){#S:before{content:\"998\";}}\n@media (min-device-width:999px){#S:before{content:\"999\";}}\n@media (min-device-width:1000px){#S:before{content:\"1000\";}}\n@media (min-device-width:1001px){#S:before{content:\"1001\";}}\n@media (min-device-width:1002px){#S:before{content:\"1002\";}}\n@media (min-device-width:1003px){#S:before{content:\"1003\";}}\n@media (min-device-width:1004px){#S:before{content:\"1004\";}}\n@media (min-device-width:1005px){#S:before{content:\"1005\";}}\n@media (min-device-width:1006px){#S:before{content:\"1006\";}}\n@media (min-device-width:1007px){#S:before{content:\"1007\";}}\n@media (min-device-width:1008px){#S:before{content:\"1008\";}}\n@media (min-device-width:1009px){#S:before{content:\"1009\";}}\n@media (min-device-width:1010px){#S:before{content:\"1010\";}}\n@media (min-device-width:1011px){#S:before{content:\"1011\";}}\n@media (min-device-width:1012px){#S:before{content:\"1012\";}}\n@media (min-device-width:1013px){#S:before{content:\"1013\";}}\n@media (min-device-width:1014px){#S:before{content:\"1014\";}}\n@media (min-device-width:1015px){#S:before{content:\"1015\";}}\n@media (min-device-width:1016px){#S:before{content:\"1016\";}}\n@media (min-device-width:1017px){#S:before{content:\"1017\";}}\n@media (min-device-width:1018px){#S:before{content:\"1018\";}}\n@media (min-device-width:1019px){#S:before{content:\"1019\";}}\n@media (min-device-width:1020px){#S:before{content:\"1020\";}}\n@media (min-device-width:1021px){#S:before{content:\"1021\";}}\n@media (min-device-width:1022px){#S:before{content:\"1022\";}}\n@media (min-device-width:1023px){#S:before{content:\"1023\";}}\n@media (min-device-width:1024px){#S:before{content:\"1024\";}}\n@media (min-device-width:1025px){#S:before{content:\"1025\";}}\n@media (min-device-width:1026px){#S:before{content:\"1026\";}}\n@media (min-device-width:1027px){#S:before{content:\"1027\";}}\n@media (min-device-width:1028px){#S:before{content:\"1028\";}}\n@media (min-device-width:1029px){#S:before{content:\"1029\";}}\n@media (min-device-width:1030px){#S:before{content:\"1030\";}}\n@media (min-device-width:1031px){#S:before{content:\"1031\";}}\n@media (min-device-width:1032px){#S:before{content:\"1032\";}}\n@media (min-device-width:1033px){#S:before{content:\"1033\";}}\n@media (min-device-width:1034px){#S:before{content:\"1034\";}}\n@media (min-device-width:1035px){#S:before{content:\"1035\";}}\n@media (min-device-width:1036px){#S:before{content:\"1036\";}}\n@media (min-device-width:1037px){#S:before{content:\"1037\";}}\n@media (min-device-width:1038px){#S:before{content:\"1038\";}}\n@media (min-device-width:1039px){#S:before{content:\"1039\";}}\n@media (min-device-width:1040px){#S:before{content:\"1040\";}}\n@media (min-device-width:1041px){#S:before{content:\"1041\";}}\n@media (min-device-width:1042px){#S:before{content:\"1042\";}}\n@media (min-device-width:1043px){#S:before{content:\"1043\";}}\n@media (min-device-width:1044px){#S:before{content:\"1044\";}}\n@media (min-device-width:1045px){#S:before{content:\"1045\";}}\n@media (min-device-width:1046px){#S:before{content:\"1046\";}}\n@media (min-device-width:1047px){#S:before{content:\"1047\";}}\n@media (min-device-width:1048px){#S:before{content:\"1048\";}}\n@media (min-device-width:1049px){#S:before{content:\"1049\";}}\n@media (min-device-width:1050px){#S:before{content:\"1050\";}}\n@media (min-device-width:1051px){#S:before{content:\"1051\";}}\n@media (min-device-width:1052px){#S:before{content:\"1052\";}}\n@media (min-device-width:1053px){#S:before{content:\"1053\";}}\n@media (min-device-width:1054px){#S:before{content:\"1054\";}}\n@media (min-device-width:1055px){#S:before{content:\"1055\";}}\n@media (min-device-width:1056px){#S:before{content:\"1056\";}}\n@media (min-device-width:1057px){#S:before{content:\"1057\";}}\n@media (min-device-width:1058px){#S:before{content:\"1058\";}}\n@media (min-device-width:1059px){#S:before{content:\"1059\";}}\n@media (min-device-width:1060px){#S:before{content:\"1060\";}}\n@media (min-device-width:1061px){#S:before{content:\"1061\";}}\n@media (min-device-width:1062px){#S:before{content:\"1062\";}}\n@media (min-device-width:1063px){#S:before{content:\"1063\";}}\n@media (min-device-width:1064px){#S:before{content:\"1064\";}}\n@media (min-device-width:1065px){#S:before{content:\"1065\";}}\n@media (min-device-width:1066px){#S:before{content:\"1066\";}}\n@media (min-device-width:1067px){#S:before{content:\"1067\";}}\n@media (min-device-width:1068px){#S:before{content:\"1068\";}}\n@media (min-device-width:1069px){#S:before{content:\"1069\";}}\n@media (min-device-width:1070px){#S:before{content:\"1070\";}}\n@media (min-device-width:1071px){#S:before{content:\"1071\";}}\n@media (min-device-width:1072px){#S:before{content:\"1072\";}}\n@media (min-device-width:1073px){#S:before{content:\"1073\";}}\n@media (min-device-width:1074px){#S:before{content:\"1074\";}}\n@media (min-device-width:1075px){#S:before{content:\"1075\";}}\n@media (min-device-width:1076px){#S:before{content:\"1076\";}}\n@media (min-device-width:1077px){#S:before{content:\"1077\";}}\n@media (min-device-width:1078px){#S:before{content:\"1078\";}}\n@media (min-device-width:1079px){#S:before{content:\"1079\";}}\n@media (min-device-width:1080px){#S:before{content:\"1080\";}}\n@media (min-device-width:1081px){#S:before{content:\"1081\";}}\n@media (min-device-width:1082px){#S:before{content:\"1082\";}}\n@media (min-device-width:1083px){#S:before{content:\"1083\";}}\n@media (min-device-width:1084px){#S:before{content:\"1084\";}}\n@media (min-device-width:1085px){#S:before{content:\"1085\";}}\n@media (min-device-width:1086px){#S:before{content:\"1086\";}}\n@media (min-device-width:1087px){#S:before{content:\"1087\";}}\n@media (min-device-width:1088px){#S:before{content:\"1088\";}}\n@media (min-device-width:1089px){#S:before{content:\"1089\";}}\n@media (min-device-width:1090px){#S:before{content:\"1090\";}}\n@media (min-device-width:1091px){#S:before{content:\"1091\";}}\n@media (min-device-width:1092px){#S:before{content:\"1092\";}}\n@media (min-device-width:1093px){#S:before{content:\"1093\";}}\n@media (min-device-width:1094px){#S:before{content:\"1094\";}}\n@media (min-device-width:1095px){#S:before{content:\"1095\";}}\n@media (min-device-width:1096px){#S:before{content:\"1096\";}}\n@media (min-device-width:1097px){#S:before{content:\"1097\";}}\n@media (min-device-width:1098px){#S:before{content:\"1098\";}}\n@media (min-device-width:1099px){#S:before{content:\"1099\";}}\n@media (min-device-width:1100px){#S:before{content:\"1100\";}}\n@media (min-device-width:1101px){#S:before{content:\"1101\";}}\n@media (min-device-width:1102px){#S:before{content:\"1102\";}}\n@media (min-device-width:1103px){#S:before{content:\"1103\";}}\n@media (min-device-width:1104px){#S:before{content:\"1104\";}}\n@media (min-device-width:1105px){#S:before{content:\"1105\";}}\n@media (min-device-width:1106px){#S:before{content:\"1106\";}}\n@media (min-device-width:1107px){#S:before{content:\"1107\";}}\n@media (min-device-width:1108px){#S:before{content:\"1108\";}}\n@media (min-device-width:1109px){#S:before{content:\"1109\";}}\n@media (min-device-width:1110px){#S:before{content:\"1110\";}}\n@media (min-device-width:1111px){#S:before{content:\"1111\";}}\n@media (min-device-width:1112px){#S:before{content:\"1112\";}}\n@media (min-device-width:1113px){#S:before{content:\"1113\";}}\n@media (min-device-width:1114px){#S:before{content:\"1114\";}}\n@media (min-device-width:1115px){#S:before{content:\"1115\";}}\n@media (min-device-width:1116px){#S:before{content:\"1116\";}}\n@media (min-device-width:1117px){#S:before{content:\"1117\";}}\n@media (min-device-width:1118px){#S:before{content:\"1118\";}}\n@media (min-device-width:1119px){#S:before{content:\"1119\";}}\n@media (min-device-width:1120px){#S:before{content:\"1120\";}}\n@media (min-device-width:1121px){#S:before{content:\"1121\";}}\n@media (min-device-width:1122px){#S:before{content:\"1122\";}}\n@media (min-device-width:1123px){#S:before{content:\"1123\";}}\n@media (min-device-width:1124px){#S:before{content:\"1124\";}}\n@media (min-device-width:1125px){#S:before{content:\"1125\";}}\n@media (min-device-width:1126px){#S:before{content:\"1126\";}}\n@media (min-device-width:1127px){#S:before{content:\"1127\";}}\n@media (min-device-width:1128px){#S:before{content:\"1128\";}}\n@media (min-device-width:1129px){#S:before{content:\"1129\";}}\n@media (min-device-width:1130px){#S:before{content:\"1130\";}}\n@media (min-device-width:1131px){#S:before{content:\"1131\";}}\n@media (min-device-width:1132px){#S:before{content:\"1132\";}}\n@media (min-device-width:1133px){#S:before{content:\"1133\";}}\n@media (min-device-width:1134px){#S:before{content:\"1134\";}}\n@media (min-device-width:1135px){#S:before{content:\"1135\";}}\n@media (min-device-width:1136px){#S:before{content:\"1136\";}}\n@media (min-device-width:1137px){#S:before{content:\"1137\";}}\n@media (min-device-width:1138px){#S:before{content:\"1138\";}}\n@media (min-device-width:1139px){#S:before{content:\"1139\";}}\n@media (min-device-width:1140px){#S:before{content:\"1140\";}}\n@media (min-device-width:1141px){#S:before{content:\"1141\";}}\n@media (min-device-width:1142px){#S:before{content:\"1142\";}}\n@media (min-device-width:1143px){#S:before{content:\"1143\";}}\n@media (min-device-width:1144px){#S:before{content:\"1144\";}}\n@media (min-device-width:1145px){#S:before{content:\"1145\";}}\n@media (min-device-width:1146px){#S:before{content:\"1146\";}}\n@media (min-device-width:1147px){#S:before{content:\"1147\";}}\n@media (min-device-width:1148px){#S:before{content:\"1148\";}}\n@media (min-device-width:1149px){#S:before{content:\"1149\";}}\n@media (min-device-width:1150px){#S:before{content:\"1150\";}}\n@media (min-device-width:1151px){#S:before{content:\"1151\";}}\n@media (min-device-width:1152px){#S:before{content:\"1152\";}}\n@media (min-device-width:1153px){#S:before{content:\"1153\";}}\n@media (min-device-width:1154px){#S:before{content:\"1154\";}}\n@media (min-device-width:1155px){#S:before{content:\"1155\";}}\n@media (min-device-width:1156px){#S:before{content:\"1156\";}}\n@media (min-device-width:1157px){#S:before{content:\"1157\";}}\n@media (min-device-width:1158px){#S:before{content:\"1158\";}}\n@media (min-device-width:1159px){#S:before{content:\"1159\";}}\n@media (min-device-width:1160px){#S:before{content:\"1160\";}}\n@media (min-device-width:1161px){#S:before{content:\"1161\";}}\n@media (min-device-width:1162px){#S:before{content:\"1162\";}}\n@media (min-device-width:1163px){#S:before{content:\"1163\";}}\n@media (min-device-width:1164px){#S:before{content:\"1164\";}}\n@media (min-device-width:1165px){#S:before{content:\"1165\";}}\n@media (min-device-width:1166px){#S:before{content:\"1166\";}}\n@media (min-device-width:1167px){#S:before{content:\"1167\";}}\n@media (min-device-width:1168px){#S:before{content:\"1168\";}}\n@media (min-device-width:1169px){#S:before{content:\"1169\";}}\n@media (min-device-width:1170px){#S:before{content:\"1170\";}}\n@media (min-device-width:1171px){#S:before{content:\"1171\";}}\n@media (min-device-width:1172px){#S:before{content:\"1172\";}}\n@media (min-device-width:1173px){#S:before{content:\"1173\";}}\n@media (min-device-width:1174px){#S:before{content:\"1174\";}}\n@media (min-device-width:1175px){#S:before{content:\"1175\";}}\n@media (min-device-width:1176px){#S:before{content:\"1176\";}}\n@media (min-device-width:1177px){#S:before{content:\"1177\";}}\n@media (min-device-width:1178px){#S:before{content:\"1178\";}}\n@media (min-device-width:1179px){#S:before{content:\"1179\";}}\n@media (min-device-width:1180px){#S:before{content:\"1180\";}}\n@media (min-device-width:1181px){#S:before{content:\"1181\";}}\n@media (min-device-width:1182px){#S:before{content:\"1182\";}}\n@media (min-device-width:1183px){#S:before{content:\"1183\";}}\n@media (min-device-width:1184px){#S:before{content:\"1184\";}}\n@media (min-device-width:1185px){#S:before{content:\"1185\";}}\n@media (min-device-width:1186px){#S:before{content:\"1186\";}}\n@media (min-device-width:1187px){#S:before{content:\"1187\";}}\n@media (min-device-width:1188px){#S:before{content:\"1188\";}}\n@media (min-device-width:1189px){#S:before{content:\"1189\";}}\n@media (min-device-width:1190px){#S:before{content:\"1190\";}}\n@media (min-device-width:1191px){#S:before{content:\"1191\";}}\n@media (min-device-width:1192px){#S:before{content:\"1192\";}}\n@media (min-device-width:1193px){#S:before{content:\"1193\";}}\n@media (min-device-width:1194px){#S:before{content:\"1194\";}}\n@media (min-device-width:1195px){#S:before{content:\"1195\";}}\n@media (min-device-width:1196px){#S:before{content:\"1196\";}}\n@media (min-device-width:1197px){#S:before{content:\"1197\";}}\n@media (min-device-width:1198px){#S:before{content:\"1198\";}}\n@media (min-device-width:1199px){#S:before{content:\"1199\";}}\n@media (min-device-width:1200px){#S:before{content:\"1200\";}}\n@media (min-device-width:1201px){#S:before{content:\"1201\";}}\n@media (min-device-width:1202px){#S:before{content:\"1202\";}}\n@media (min-device-width:1203px){#S:before{content:\"1203\";}}\n@media (min-device-width:1204px){#S:before{content:\"1204\";}}\n@media (min-device-width:1205px){#S:before{content:\"1205\";}}\n@media (min-device-width:1206px){#S:before{content:\"1206\";}}\n@media (min-device-width:1207px){#S:before{content:\"1207\";}}\n@media (min-device-width:1208px){#S:before{content:\"1208\";}}\n@media (min-device-width:1209px){#S:before{content:\"1209\";}}\n@media (min-device-width:1210px){#S:before{content:\"1210\";}}\n@media (min-device-width:1211px){#S:before{content:\"1211\";}}\n@media (min-device-width:1212px){#S:before{content:\"1212\";}}\n@media (min-device-width:1213px){#S:before{content:\"1213\";}}\n@media (min-device-width:1214px){#S:before{content:\"1214\";}}\n@media (min-device-width:1215px){#S:before{content:\"1215\";}}\n@media (min-device-width:1216px){#S:before{content:\"1216\";}}\n@media (min-device-width:1217px){#S:before{content:\"1217\";}}\n@media (min-device-width:1218px){#S:before{content:\"1218\";}}\n@media (min-device-width:1219px){#S:before{content:\"1219\";}}\n@media (min-device-width:1220px){#S:before{content:\"1220\";}}\n@media (min-device-width:1221px){#S:before{content:\"1221\";}}\n@media (min-device-width:1222px){#S:before{content:\"1222\";}}\n@media (min-device-width:1223px){#S:before{content:\"1223\";}}\n@media (min-device-width:1224px){#S:before{content:\"1224\";}}\n@media (min-device-width:1225px){#S:before{content:\"1225\";}}\n@media (min-device-width:1226px){#S:before{content:\"1226\";}}\n@media (min-device-width:1227px){#S:before{content:\"1227\";}}\n@media (min-device-width:1228px){#S:before{content:\"1228\";}}\n@media (min-device-width:1229px){#S:before{content:\"1229\";}}\n@media (min-device-width:1230px){#S:before{content:\"1230\";}}\n@media (min-device-width:1231px){#S:before{content:\"1231\";}}\n@media (min-device-width:1232px){#S:before{content:\"1232\";}}\n@media (min-device-width:1233px){#S:before{content:\"1233\";}}\n@media (min-device-width:1234px){#S:before{content:\"1234\";}}\n@media (min-device-width:1235px){#S:before{content:\"1235\";}}\n@media (min-device-width:1236px){#S:before{content:\"1236\";}}\n@media (min-device-width:1237px){#S:before{content:\"1237\";}}\n@media (min-device-width:1238px){#S:before{content:\"1238\";}}\n@media (min-device-width:1239px){#S:before{content:\"1239\";}}\n@media (min-device-width:1240px){#S:before{content:\"1240\";}}\n@media (min-device-width:1241px){#S:before{content:\"1241\";}}\n@media (min-device-width:1242px){#S:before{content:\"1242\";}}\n@media (min-device-width:1243px){#S:before{content:\"1243\";}}\n@media (min-device-width:1244px){#S:before{content:\"1244\";}}\n@media (min-device-width:1245px){#S:before{content:\"1245\";}}\n@media (min-device-width:1246px){#S:before{content:\"1246\";}}\n@media (min-device-width:1247px){#S:before{content:\"1247\";}}\n@media (min-device-width:1248px){#S:before{content:\"1248\";}}\n@media (min-device-width:1249px){#S:before{content:\"1249\";}}\n@media (min-device-width:1250px){#S:before{content:\"1250\";}}\n@media (min-device-width:1251px){#S:before{content:\"1251\";}}\n@media (min-device-width:1252px){#S:before{content:\"1252\";}}\n@media (min-device-width:1253px){#S:before{content:\"1253\";}}\n@media (min-device-width:1254px){#S:before{content:\"1254\";}}\n@media (min-device-width:1255px){#S:before{content:\"1255\";}}\n@media (min-device-width:1256px){#S:before{content:\"1256\";}}\n@media (min-device-width:1257px){#S:before{content:\"1257\";}}\n@media (min-device-width:1258px){#S:before{content:\"1258\";}}\n@media (min-device-width:1259px){#S:before{content:\"1259\";}}\n@media (min-device-width:1260px){#S:before{content:\"1260\";}}\n@media (min-device-width:1261px){#S:before{content:\"1261\";}}\n@media (min-device-width:1262px){#S:before{content:\"1262\";}}\n@media (min-device-width:1263px){#S:before{content:\"1263\";}}\n@media (min-device-width:1264px){#S:before{content:\"1264\";}}\n@media (min-device-width:1265px){#S:before{content:\"1265\";}}\n@media (min-device-width:1266px){#S:before{content:\"1266\";}}\n@media (min-device-width:1267px){#S:before{content:\"1267\";}}\n@media (min-device-width:1268px){#S:before{content:\"1268\";}}\n@media (min-device-width:1269px){#S:before{content:\"1269\";}}\n@media (min-device-width:1270px){#S:before{content:\"1270\";}}\n@media (min-device-width:1271px){#S:before{content:\"1271\";}}\n@media (min-device-width:1272px){#S:before{content:\"1272\";}}\n@media (min-device-width:1273px){#S:before{content:\"1273\";}}\n@media (min-device-width:1274px){#S:before{content:\"1274\";}}\n@media (min-device-width:1275px){#S:before{content:\"1275\";}}\n@media (min-device-width:1276px){#S:before{content:\"1276\";}}\n@media (min-device-width:1277px){#S:before{content:\"1277\";}}\n@media (min-device-width:1278px){#S:before{content:\"1278\";}}\n@media (min-device-width:1279px){#S:before{content:\"1279\";}}\n@media (min-device-width:1280px){#S:before{content:\"1280\";}}\n@media (min-device-width:1281px){#S:before{content:\"1281\";}}\n@media (min-device-width:1282px){#S:before{content:\"1282\";}}\n@media (min-device-width:1283px){#S:before{content:\"1283\";}}\n@media (min-device-width:1284px){#S:before{content:\"1284\";}}\n@media (min-device-width:1285px){#S:before{content:\"1285\";}}\n@media (min-device-width:1286px){#S:before{content:\"1286\";}}\n@media (min-device-width:1287px){#S:before{content:\"1287\";}}\n@media (min-device-width:1288px){#S:before{content:\"1288\";}}\n@media (min-device-width:1289px){#S:before{content:\"1289\";}}\n@media (min-device-width:1290px){#S:before{content:\"1290\";}}\n@media (min-device-width:1291px){#S:before{content:\"1291\";}}\n@media (min-device-width:1292px){#S:before{content:\"1292\";}}\n@media (min-device-width:1293px){#S:before{content:\"1293\";}}\n@media (min-device-width:1294px){#S:before{content:\"1294\";}}\n@media (min-device-width:1295px){#S:before{content:\"1295\";}}\n@media (min-device-width:1296px){#S:before{content:\"1296\";}}\n@media (min-device-width:1297px){#S:before{content:\"1297\";}}\n@media (min-device-width:1298px){#S:before{content:\"1298\";}}\n@media (min-device-width:1299px){#S:before{content:\"1299\";}}\n@media (min-device-width:1300px){#S:before{content:\"1300\";}}\n@media (min-device-width:1301px){#S:before{content:\"1301\";}}\n@media (min-device-width:1302px){#S:before{content:\"1302\";}}\n@media (min-device-width:1303px){#S:before{content:\"1303\";}}\n@media (min-device-width:1304px){#S:before{content:\"1304\";}}\n@media (min-device-width:1305px){#S:before{content:\"1305\";}}\n@media (min-device-width:1306px){#S:before{content:\"1306\";}}\n@media (min-device-width:1307px){#S:before{content:\"1307\";}}\n@media (min-device-width:1308px){#S:before{content:\"1308\";}}\n@media (min-device-width:1309px){#S:before{content:\"1309\";}}\n@media (min-device-width:1310px){#S:before{content:\"1310\";}}\n@media (min-device-width:1311px){#S:before{content:\"1311\";}}\n@media (min-device-width:1312px){#S:before{content:\"1312\";}}\n@media (min-device-width:1313px){#S:before{content:\"1313\";}}\n@media (min-device-width:1314px){#S:before{content:\"1314\";}}\n@media (min-device-width:1315px){#S:before{content:\"1315\";}}\n@media (min-device-width:1316px){#S:before{content:\"1316\";}}\n@media (min-device-width:1317px){#S:before{content:\"1317\";}}\n@media (min-device-width:1318px){#S:before{content:\"1318\";}}\n@media (min-device-width:1319px){#S:before{content:\"1319\";}}\n@media (min-device-width:1320px){#S:before{content:\"1320\";}}\n@media (min-device-width:1321px){#S:before{content:\"1321\";}}\n@media (min-device-width:1322px){#S:before{content:\"1322\";}}\n@media (min-device-width:1323px){#S:before{content:\"1323\";}}\n@media (min-device-width:1324px){#S:before{content:\"1324\";}}\n@media (min-device-width:1325px){#S:before{content:\"1325\";}}\n@media (min-device-width:1326px){#S:before{content:\"1326\";}}\n@media (min-device-width:1327px){#S:before{content:\"1327\";}}\n@media (min-device-width:1328px){#S:before{content:\"1328\";}}\n@media (min-device-width:1329px){#S:before{content:\"1329\";}}\n@media (min-device-width:1330px){#S:before{content:\"1330\";}}\n@media (min-device-width:1331px){#S:before{content:\"1331\";}}\n@media (min-device-width:1332px){#S:before{content:\"1332\";}}\n@media (min-device-width:1333px){#S:before{content:\"1333\";}}\n@media (min-device-width:1334px){#S:before{content:\"1334\";}}\n@media (min-device-width:1335px){#S:before{content:\"1335\";}}\n@media (min-device-width:1336px){#S:before{content:\"1336\";}}\n@media (min-device-width:1337px){#S:before{content:\"1337\";}}\n@media (min-device-width:1338px){#S:before{content:\"1338\";}}\n@media (min-device-width:1339px){#S:before{content:\"1339\";}}\n@media (min-device-width:1340px){#S:before{content:\"1340\";}}\n@media (min-device-width:1341px){#S:before{content:\"1341\";}}\n@media (min-device-width:1342px){#S:before{content:\"1342\";}}\n@media (min-device-width:1343px){#S:before{content:\"1343\";}}\n@media (min-device-width:1344px){#S:before{content:\"1344\";}}\n@media (min-device-width:1345px){#S:before{content:\"1345\";}}\n@media (min-device-width:1346px){#S:before{content:\"1346\";}}\n@media (min-device-width:1347px){#S:before{content:\"1347\";}}\n@media (min-device-width:1348px){#S:before{content:\"1348\";}}\n@media (min-device-width:1349px){#S:before{content:\"1349\";}}\n@media (min-device-width:1350px){#S:before{content:\"1350\";}}\n@media (min-device-width:1351px){#S:before{content:\"1351\";}}\n@media (min-device-width:1352px){#S:before{content:\"1352\";}}\n@media (min-device-width:1353px){#S:before{content:\"1353\";}}\n@media (min-device-width:1354px){#S:before{content:\"1354\";}}\n@media (min-device-width:1355px){#S:before{content:\"1355\";}}\n@media (min-device-width:1356px){#S:before{content:\"1356\";}}\n@media (min-device-width:1357px){#S:before{content:\"1357\";}}\n@media (min-device-width:1358px){#S:before{content:\"1358\";}}\n@media (min-device-width:1359px){#S:before{content:\"1359\";}}\n@media (min-device-width:1360px){#S:before{content:\"1360\";}}\n@media (min-device-width:1361px){#S:before{content:\"1361\";}}\n@media (min-device-width:1362px){#S:before{content:\"1362\";}}\n@media (min-device-width:1363px){#S:before{content:\"1363\";}}\n@media (min-device-width:1364px){#S:before{content:\"1364\";}}\n@media (min-device-width:1365px){#S:before{content:\"1365\";}}\n@media (min-device-width:1366px){#S:before{content:\"1366\";}}\n@media (min-device-width:1367px){#S:before{content:\"1367\";}}\n@media (min-device-width:1368px){#S:before{content:\"1368\";}}\n@media (min-device-width:1369px){#S:before{content:\"1369\";}}\n@media (min-device-width:1370px){#S:before{content:\"1370\";}}\n@media (min-device-width:1371px){#S:before{content:\"1371\";}}\n@media (min-device-width:1372px){#S:before{content:\"1372\";}}\n@media (min-device-width:1373px){#S:before{content:\"1373\";}}\n@media (min-device-width:1374px){#S:before{content:\"1374\";}}\n@media (min-device-width:1375px){#S:before{content:\"1375\";}}\n@media (min-device-width:1376px){#S:before{content:\"1376\";}}\n@media (min-device-width:1377px){#S:before{content:\"1377\";}}\n@media (min-device-width:1378px){#S:before{content:\"1378\";}}\n@media (min-device-width:1379px){#S:before{content:\"1379\";}}\n@media (min-device-width:1380px){#S:before{content:\"1380\";}}\n@media (min-device-width:1381px){#S:before{content:\"1381\";}}\n@media (min-device-width:1382px){#S:before{content:\"1382\";}}\n@media (min-device-width:1383px){#S:before{content:\"1383\";}}\n@media (min-device-width:1384px){#S:before{content:\"1384\";}}\n@media (min-device-width:1385px){#S:before{content:\"1385\";}}\n@media (min-device-width:1386px){#S:before{content:\"1386\";}}\n@media (min-device-width:1387px){#S:before{content:\"1387\";}}\n@media (min-device-width:1388px){#S:before{content:\"1388\";}}\n@media (min-device-width:1389px){#S:before{content:\"1389\";}}\n@media (min-device-width:1390px){#S:before{content:\"1390\";}}\n@media (min-device-width:1391px){#S:before{content:\"1391\";}}\n@media (min-device-width:1392px){#S:before{content:\"1392\";}}\n@media (min-device-width:1393px){#S:before{content:\"1393\";}}\n@media (min-device-width:1394px){#S:before{content:\"1394\";}}\n@media (min-device-width:1395px){#S:before{content:\"1395\";}}\n@media (min-device-width:1396px){#S:before{content:\"1396\";}}\n@media (min-device-width:1397px){#S:before{content:\"1397\";}}\n@media (min-device-width:1398px){#S:before{content:\"1398\";}}\n@media (min-device-width:1399px){#S:before{content:\"1399\";}}\n@media (min-device-width:1400px){#S:before{content:\"1400\";}}\n@media (min-device-width:1401px){#S:before{content:\"1401\";}}\n@media (min-device-width:1402px){#S:before{content:\"1402\";}}\n@media (min-device-width:1403px){#S:before{content:\"1403\";}}\n@media (min-device-width:1404px){#S:before{content:\"1404\";}}\n@media (min-device-width:1405px){#S:before{content:\"1405\";}}\n@media (min-device-width:1406px){#S:before{content:\"1406\";}}\n@media (min-device-width:1407px){#S:before{content:\"1407\";}}\n@media (min-device-width:1408px){#S:before{content:\"1408\";}}\n@media (min-device-width:1409px){#S:before{content:\"1409\";}}\n@media (min-device-width:1410px){#S:before{content:\"1410\";}}\n@media (min-device-width:1411px){#S:before{content:\"1411\";}}\n@media (min-device-width:1412px){#S:before{content:\"1412\";}}\n@media (min-device-width:1413px){#S:before{content:\"1413\";}}\n@media (min-device-width:1414px){#S:before{content:\"1414\";}}\n@media (min-device-width:1415px){#S:before{content:\"1415\";}}\n@media (min-device-width:1416px){#S:before{content:\"1416\";}}\n@media (min-device-width:1417px){#S:before{content:\"1417\";}}\n@media (min-device-width:1418px){#S:before{content:\"1418\";}}\n@media (min-device-width:1419px){#S:before{content:\"1419\";}}\n@media (min-device-width:1420px){#S:before{content:\"1420\";}}\n@media (min-device-width:1421px){#S:before{content:\"1421\";}}\n@media (min-device-width:1422px){#S:before{content:\"1422\";}}\n@media (min-device-width:1423px){#S:before{content:\"1423\";}}\n@media (min-device-width:1424px){#S:before{content:\"1424\";}}\n@media (min-device-width:1425px){#S:before{content:\"1425\";}}\n@media (min-device-width:1426px){#S:before{content:\"1426\";}}\n@media (min-device-width:1427px){#S:before{content:\"1427\";}}\n@media (min-device-width:1428px){#S:before{content:\"1428\";}}\n@media (min-device-width:1429px){#S:before{content:\"1429\";}}\n@media (min-device-width:1430px){#S:before{content:\"1430\";}}\n@media (min-device-width:1431px){#S:before{content:\"1431\";}}\n@media (min-device-width:1432px){#S:before{content:\"1432\";}}\n@media (min-device-width:1433px){#S:before{content:\"1433\";}}\n@media (min-device-width:1434px){#S:before{content:\"1434\";}}\n@media (min-device-width:1435px){#S:before{content:\"1435\";}}\n@media (min-device-width:1436px){#S:before{content:\"1436\";}}\n@media (min-device-width:1437px){#S:before{content:\"1437\";}}\n@media (min-device-width:1438px){#S:before{content:\"1438\";}}\n@media (min-device-width:1439px){#S:before{content:\"1439\";}}\n@media (min-device-width:1440px){#S:before{content:\"1440\";}}\n@media (min-device-width:1441px){#S:before{content:\"1441\";}}\n@media (min-device-width:1442px){#S:before{content:\"1442\";}}\n@media (min-device-width:1443px){#S:before{content:\"1443\";}}\n@media (min-device-width:1444px){#S:before{content:\"1444\";}}\n@media (min-device-width:1445px){#S:before{content:\"1445\";}}\n@media (min-device-width:1446px){#S:before{content:\"1446\";}}\n@media (min-device-width:1447px){#S:before{content:\"1447\";}}\n@media (min-device-width:1448px){#S:before{content:\"1448\";}}\n@media (min-device-width:1449px){#S:before{content:\"1449\";}}\n@media (min-device-width:1450px){#S:before{content:\"1450\";}}\n@media (min-device-width:1451px){#S:before{content:\"1451\";}}\n@media (min-device-width:1452px){#S:before{content:\"1452\";}}\n@media (min-device-width:1453px){#S:before{content:\"1453\";}}\n@media (min-device-width:1454px){#S:before{content:\"1454\";}}\n@media (min-device-width:1455px){#S:before{content:\"1455\";}}\n@media (min-device-width:1456px){#S:before{content:\"1456\";}}\n@media (min-device-width:1457px){#S:before{content:\"1457\";}}\n@media (min-device-width:1458px){#S:before{content:\"1458\";}}\n@media (min-device-width:1459px){#S:before{content:\"1459\";}}\n@media (min-device-width:1460px){#S:before{content:\"1460\";}}\n@media (min-device-width:1461px){#S:before{content:\"1461\";}}\n@media (min-device-width:1462px){#S:before{content:\"1462\";}}\n@media (min-device-width:1463px){#S:before{content:\"1463\";}}\n@media (min-device-width:1464px){#S:before{content:\"1464\";}}\n@media (min-device-width:1465px){#S:before{content:\"1465\";}}\n@media (min-device-width:1466px){#S:before{content:\"1466\";}}\n@media (min-device-width:1467px){#S:before{content:\"1467\";}}\n@media (min-device-width:1468px){#S:before{content:\"1468\";}}\n@media (min-device-width:1469px){#S:before{content:\"1469\";}}\n@media (min-device-width:1470px){#S:before{content:\"1470\";}}\n@media (min-device-width:1471px){#S:before{content:\"1471\";}}\n@media (min-device-width:1472px){#S:before{content:\"1472\";}}\n@media (min-device-width:1473px){#S:before{content:\"1473\";}}\n@media (min-device-width:1474px){#S:before{content:\"1474\";}}\n@media (min-device-width:1475px){#S:before{content:\"1475\";}}\n@media (min-device-width:1476px){#S:before{content:\"1476\";}}\n@media (min-device-width:1477px){#S:before{content:\"1477\";}}\n@media (min-device-width:1478px){#S:before{content:\"1478\";}}\n@media (min-device-width:1479px){#S:before{content:\"1479\";}}\n@media (min-device-width:1480px){#S:before{content:\"1480\";}}\n@media (min-device-width:1481px){#S:before{content:\"1481\";}}\n@media (min-device-width:1482px){#S:before{content:\"1482\";}}\n@media (min-device-width:1483px){#S:before{content:\"1483\";}}\n@media (min-device-width:1484px){#S:before{content:\"1484\";}}\n@media (min-device-width:1485px){#S:before{content:\"1485\";}}\n@media (min-device-width:1486px){#S:before{content:\"1486\";}}\n@media (min-device-width:1487px){#S:before{content:\"1487\";}}\n@media (min-device-width:1488px){#S:before{content:\"1488\";}}\n@media (min-device-width:1489px){#S:before{content:\"1489\";}}\n@media (min-device-width:1490px){#S:before{content:\"1490\";}}\n@media (min-device-width:1491px){#S:before{content:\"1491\";}}\n@media (min-device-width:1492px){#S:before{content:\"1492\";}}\n@media (min-device-width:1493px){#S:before{content:\"1493\";}}\n@media (min-device-width:1494px){#S:before{content:\"1494\";}}\n@media (min-device-width:1495px){#S:before{content:\"1495\";}}\n@media (min-device-width:1496px){#S:before{content:\"1496\";}}\n@media (min-device-width:1497px){#S:before{content:\"1497\";}}\n@media (min-device-width:1498px){#S:before{content:\"1498\";}}\n@media (min-device-width:1499px){#S:before{content:\"1499\";}}\n@media (min-device-width:1500px){#S:before{content:\"1500\";}}\n@media (min-device-width:1501px){#S:before{content:\"1501\";}}\n@media (min-device-width:1502px){#S:before{content:\"1502\";}}\n@media (min-device-width:1503px){#S:before{content:\"1503\";}}\n@media (min-device-width:1504px){#S:before{content:\"1504\";}}\n@media (min-device-width:1505px){#S:before{content:\"1505\";}}\n@media (min-device-width:1506px){#S:before{content:\"1506\";}}\n@media (min-device-width:1507px){#S:before{content:\"1507\";}}\n@media (min-device-width:1508px){#S:before{content:\"1508\";}}\n@media (min-device-width:1509px){#S:before{content:\"1509\";}}\n@media (min-device-width:1510px){#S:before{content:\"1510\";}}\n@media (min-device-width:1511px){#S:before{content:\"1511\";}}\n@media (min-device-width:1512px){#S:before{content:\"1512\";}}\n@media (min-device-width:1513px){#S:before{content:\"1513\";}}\n@media (min-device-width:1514px){#S:before{content:\"1514\";}}\n@media (min-device-width:1515px){#S:before{content:\"1515\";}}\n@media (min-device-width:1516px){#S:before{content:\"1516\";}}\n@media (min-device-width:1517px){#S:before{content:\"1517\";}}\n@media (min-device-width:1518px){#S:before{content:\"1518\";}}\n@media (min-device-width:1519px){#S:before{content:\"1519\";}}\n@media (min-device-width:1520px){#S:before{content:\"1520\";}}\n@media (min-device-width:1521px){#S:before{content:\"1521\";}}\n@media (min-device-width:1522px){#S:before{content:\"1522\";}}\n@media (min-device-width:1523px){#S:before{content:\"1523\";}}\n@media (min-device-width:1524px){#S:before{content:\"1524\";}}\n@media (min-device-width:1525px){#S:before{content:\"1525\";}}\n@media (min-device-width:1526px){#S:before{content:\"1526\";}}\n@media (min-device-width:1527px){#S:before{content:\"1527\";}}\n@media (min-device-width:1528px){#S:before{content:\"1528\";}}\n@media (min-device-width:1529px){#S:before{content:\"1529\";}}\n@media (min-device-width:1530px){#S:before{content:\"1530\";}}\n@media (min-device-width:1531px){#S:before{content:\"1531\";}}\n@media (min-device-width:1532px){#S:before{content:\"1532\";}}\n@media (min-device-width:1533px){#S:before{content:\"1533\";}}\n@media (min-device-width:1534px){#S:before{content:\"1534\";}}\n@media (min-device-width:1535px){#S:before{content:\"1535\";}}\n@media (min-device-width:1536px){#S:before{content:\"1536\";}}\n@media (min-device-width:1537px){#S:before{content:\"1537\";}}\n@media (min-device-width:1538px){#S:before{content:\"1538\";}}\n@media (min-device-width:1539px){#S:before{content:\"1539\";}}\n@media (min-device-width:1540px){#S:before{content:\"1540\";}}\n@media (min-device-width:1541px){#S:before{content:\"1541\";}}\n@media (min-device-width:1542px){#S:before{content:\"1542\";}}\n@media (min-device-width:1543px){#S:before{content:\"1543\";}}\n@media (min-device-width:1544px){#S:before{content:\"1544\";}}\n@media (min-device-width:1545px){#S:before{content:\"1545\";}}\n@media (min-device-width:1546px){#S:before{content:\"1546\";}}\n@media (min-device-width:1547px){#S:before{content:\"1547\";}}\n@media (min-device-width:1548px){#S:before{content:\"1548\";}}\n@media (min-device-width:1549px){#S:before{content:\"1549\";}}\n@media (min-device-width:1550px){#S:before{content:\"1550\";}}\n@media (min-device-width:1551px){#S:before{content:\"1551\";}}\n@media (min-device-width:1552px){#S:before{content:\"1552\";}}\n@media (min-device-width:1553px){#S:before{content:\"1553\";}}\n@media (min-device-width:1554px){#S:before{content:\"1554\";}}\n@media (min-device-width:1555px){#S:before{content:\"1555\";}}\n@media (min-device-width:1556px){#S:before{content:\"1556\";}}\n@media (min-device-width:1557px){#S:before{content:\"1557\";}}\n@media (min-device-width:1558px){#S:before{content:\"1558\";}}\n@media (min-device-width:1559px){#S:before{content:\"1559\";}}\n@media (min-device-width:1560px){#S:before{content:\"1560\";}}\n@media (min-device-width:1561px){#S:before{content:\"1561\";}}\n@media (min-device-width:1562px){#S:before{content:\"1562\";}}\n@media (min-device-width:1563px){#S:before{content:\"1563\";}}\n@media (min-device-width:1564px){#S:before{content:\"1564\";}}\n@media (min-device-width:1565px){#S:before{content:\"1565\";}}\n@media (min-device-width:1566px){#S:before{content:\"1566\";}}\n@media (min-device-width:1567px){#S:before{content:\"1567\";}}\n@media (min-device-width:1568px){#S:before{content:\"1568\";}}\n@media (min-device-width:1569px){#S:before{content:\"1569\";}}\n@media (min-device-width:1570px){#S:before{content:\"1570\";}}\n@media (min-device-width:1571px){#S:before{content:\"1571\";}}\n@media (min-device-width:1572px){#S:before{content:\"1572\";}}\n@media (min-device-width:1573px){#S:before{content:\"1573\";}}\n@media (min-device-width:1574px){#S:before{content:\"1574\";}}\n@media (min-device-width:1575px){#S:before{content:\"1575\";}}\n@media (min-device-width:1576px){#S:before{content:\"1576\";}}\n@media (min-device-width:1577px){#S:before{content:\"1577\";}}\n@media (min-device-width:1578px){#S:before{content:\"1578\";}}\n@media (min-device-width:1579px){#S:before{content:\"1579\";}}\n@media (min-device-width:1580px){#S:before{content:\"1580\";}}\n@media (min-device-width:1581px){#S:before{content:\"1581\";}}\n@media (min-device-width:1582px){#S:before{content:\"1582\";}}\n@media (min-device-width:1583px){#S:before{content:\"1583\";}}\n@media (min-device-width:1584px){#S:before{content:\"1584\";}}\n@media (min-device-width:1585px){#S:before{content:\"1585\";}}\n@media (min-device-width:1586px){#S:before{content:\"1586\";}}\n@media (min-device-width:1587px){#S:before{content:\"1587\";}}\n@media (min-device-width:1588px){#S:before{content:\"1588\";}}\n@media (min-device-width:1589px){#S:before{content:\"1589\";}}\n@media (min-device-width:1590px){#S:before{content:\"1590\";}}\n@media (min-device-width:1591px){#S:before{content:\"1591\";}}\n@media (min-device-width:1592px){#S:before{content:\"1592\";}}\n@media (min-device-width:1593px){#S:before{content:\"1593\";}}\n@media (min-device-width:1594px){#S:before{content:\"1594\";}}\n@media (min-device-width:1595px){#S:before{content:\"1595\";}}\n@media (min-device-width:1596px){#S:before{content:\"1596\";}}\n@media (min-device-width:1597px){#S:before{content:\"1597\";}}\n@media (min-device-width:1598px){#S:before{content:\"1598\";}}\n@media (min-device-width:1599px){#S:before{content:\"1599\";}}\n@media (min-device-width:1600px){#S:before{content:\"1600\";}}\n@media (min-device-width:1601px){#S:before{content:\"1601\";}}\n@media (min-device-width:1602px){#S:before{content:\"1602\";}}\n@media (min-device-width:1603px){#S:before{content:\"1603\";}}\n@media (min-device-width:1604px){#S:before{content:\"1604\";}}\n@media (min-device-width:1605px){#S:before{content:\"1605\";}}\n@media (min-device-width:1606px){#S:before{content:\"1606\";}}\n@media (min-device-width:1607px){#S:before{content:\"1607\";}}\n@media (min-device-width:1608px){#S:before{content:\"1608\";}}\n@media (min-device-width:1609px){#S:before{content:\"1609\";}}\n@media (min-device-width:1610px){#S:before{content:\"1610\";}}\n@media (min-device-width:1611px){#S:before{content:\"1611\";}}\n@media (min-device-width:1612px){#S:before{content:\"1612\";}}\n@media (min-device-width:1613px){#S:before{content:\"1613\";}}\n@media (min-device-width:1614px){#S:before{content:\"1614\";}}\n@media (min-device-width:1615px){#S:before{content:\"1615\";}}\n@media (min-device-width:1616px){#S:before{content:\"1616\";}}\n@media (min-device-width:1617px){#S:before{content:\"1617\";}}\n@media (min-device-width:1618px){#S:before{content:\"1618\";}}\n@media (min-device-width:1619px){#S:before{content:\"1619\";}}\n@media (min-device-width:1620px){#S:before{content:\"1620\";}}\n@media (min-device-width:1621px){#S:before{content:\"1621\";}}\n@media (min-device-width:1622px){#S:before{content:\"1622\";}}\n@media (min-device-width:1623px){#S:before{content:\"1623\";}}\n@media (min-device-width:1624px){#S:before{content:\"1624\";}}\n@media (min-device-width:1625px){#S:before{content:\"1625\";}}\n@media (min-device-width:1626px){#S:before{content:\"1626\";}}\n@media (min-device-width:1627px){#S:before{content:\"1627\";}}\n@media (min-device-width:1628px){#S:before{content:\"1628\";}}\n@media (min-device-width:1629px){#S:before{content:\"1629\";}}\n@media (min-device-width:1630px){#S:before{content:\"1630\";}}\n@media (min-device-width:1631px){#S:before{content:\"1631\";}}\n@media (min-device-width:1632px){#S:before{content:\"1632\";}}\n@media (min-device-width:1633px){#S:before{content:\"1633\";}}\n@media (min-device-width:1634px){#S:before{content:\"1634\";}}\n@media (min-device-width:1635px){#S:before{content:\"1635\";}}\n@media (min-device-width:1636px){#S:before{content:\"1636\";}}\n@media (min-device-width:1637px){#S:before{content:\"1637\";}}\n@media (min-device-width:1638px){#S:before{content:\"1638\";}}\n@media (min-device-width:1639px){#S:before{content:\"1639\";}}\n@media (min-device-width:1640px){#S:before{content:\"1640\";}}\n@media (min-device-width:1641px){#S:before{content:\"1641\";}}\n@media (min-device-width:1642px){#S:before{content:\"1642\";}}\n@media (min-device-width:1643px){#S:before{content:\"1643\";}}\n@media (min-device-width:1644px){#S:before{content:\"1644\";}}\n@media (min-device-width:1645px){#S:before{content:\"1645\";}}\n@media (min-device-width:1646px){#S:before{content:\"1646\";}}\n@media (min-device-width:1647px){#S:before{content:\"1647\";}}\n@media (min-device-width:1648px){#S:before{content:\"1648\";}}\n@media (min-device-width:1649px){#S:before{content:\"1649\";}}\n@media (min-device-width:1650px){#S:before{content:\"1650\";}}\n@media (min-device-width:1651px){#S:before{content:\"1651\";}}\n@media (min-device-width:1652px){#S:before{content:\"1652\";}}\n@media (min-device-width:1653px){#S:before{content:\"1653\";}}\n@media (min-device-width:1654px){#S:before{content:\"1654\";}}\n@media (min-device-width:1655px){#S:before{content:\"1655\";}}\n@media (min-device-width:1656px){#S:before{content:\"1656\";}}\n@media (min-device-width:1657px){#S:before{content:\"1657\";}}\n@media (min-device-width:1658px){#S:before{content:\"1658\";}}\n@media (min-device-width:1659px){#S:before{content:\"1659\";}}\n@media (min-device-width:1660px){#S:before{content:\"1660\";}}\n@media (min-device-width:1661px){#S:before{content:\"1661\";}}\n@media (min-device-width:1662px){#S:before{content:\"1662\";}}\n@media (min-device-width:1663px){#S:before{content:\"1663\";}}\n@media (min-device-width:1664px){#S:before{content:\"1664\";}}\n@media (min-device-width:1665px){#S:before{content:\"1665\";}}\n@media (min-device-width:1666px){#S:before{content:\"1666\";}}\n@media (min-device-width:1667px){#S:before{content:\"1667\";}}\n@media (min-device-width:1668px){#S:before{content:\"1668\";}}\n@media (min-device-width:1669px){#S:before{content:\"1669\";}}\n@media (min-device-width:1670px){#S:before{content:\"1670\";}}\n@media (min-device-width:1671px){#S:before{content:\"1671\";}}\n@media (min-device-width:1672px){#S:before{content:\"1672\";}}\n@media (min-device-width:1673px){#S:before{content:\"1673\";}}\n@media (min-device-width:1674px){#S:before{content:\"1674\";}}\n@media (min-device-width:1675px){#S:before{content:\"1675\";}}\n@media (min-device-width:1676px){#S:before{content:\"1676\";}}\n@media (min-device-width:1677px){#S:before{content:\"1677\";}}\n@media (min-device-width:1678px){#S:before{content:\"1678\";}}\n@media (min-device-width:1679px){#S:before{content:\"1679\";}}\n@media (min-device-width:1680px){#S:before{content:\"1680\";}}\n@media (min-device-width:1681px){#S:before{content:\"1681\";}}\n@media (min-device-width:1682px){#S:before{content:\"1682\";}}\n@media (min-device-width:1683px){#S:before{content:\"1683\";}}\n@media (min-device-width:1684px){#S:before{content:\"1684\";}}\n@media (min-device-width:1685px){#S:before{content:\"1685\";}}\n@media (min-device-width:1686px){#S:before{content:\"1686\";}}\n@media (min-device-width:1687px){#S:before{content:\"1687\";}}\n@media (min-device-width:1688px){#S:before{content:\"1688\";}}\n@media (min-device-width:1689px){#S:before{content:\"1689\";}}\n@media (min-device-width:1690px){#S:before{content:\"1690\";}}\n@media (min-device-width:1691px){#S:before{content:\"1691\";}}\n@media (min-device-width:1692px){#S:before{content:\"1692\";}}\n@media (min-device-width:1693px){#S:before{content:\"1693\";}}\n@media (min-device-width:1694px){#S:before{content:\"1694\";}}\n@media (min-device-width:1695px){#S:before{content:\"1695\";}}\n@media (min-device-width:1696px){#S:before{content:\"1696\";}}\n@media (min-device-width:1697px){#S:before{content:\"1697\";}}\n@media (min-device-width:1698px){#S:before{content:\"1698\";}}\n@media (min-device-width:1699px){#S:before{content:\"1699\";}}\n@media (min-device-width:1700px){#S:before{content:\"1700\";}}\n@media (min-device-width:1701px){#S:before{content:\"1701\";}}\n@media (min-device-width:1702px){#S:before{content:\"1702\";}}\n@media (min-device-width:1703px){#S:before{content:\"1703\";}}\n@media (min-device-width:1704px){#S:before{content:\"1704\";}}\n@media (min-device-width:1705px){#S:before{content:\"1705\";}}\n@media (min-device-width:1706px){#S:before{content:\"1706\";}}\n@media (min-device-width:1707px){#S:before{content:\"1707\";}}\n@media (min-device-width:1708px){#S:before{content:\"1708\";}}\n@media (min-device-width:1709px){#S:before{content:\"1709\";}}\n@media (min-device-width:1710px){#S:before{content:\"1710\";}}\n@media (min-device-width:1711px){#S:before{content:\"1711\";}}\n@media (min-device-width:1712px){#S:before{content:\"1712\";}}\n@media (min-device-width:1713px){#S:before{content:\"1713\";}}\n@media (min-device-width:1714px){#S:before{content:\"1714\";}}\n@media (min-device-width:1715px){#S:before{content:\"1715\";}}\n@media (min-device-width:1716px){#S:before{content:\"1716\";}}\n@media (min-device-width:1717px){#S:before{content:\"1717\";}}\n@media (min-device-width:1718px){#S:before{content:\"1718\";}}\n@media (min-device-width:1719px){#S:before{content:\"1719\";}}\n@media (min-device-width:1720px){#S:before{content:\"1720\";}}\n@media (min-device-width:1721px){#S:before{content:\"1721\";}}\n@media (min-device-width:1722px){#S:before{content:\"1722\";}}\n@media (min-device-width:1723px){#S:before{content:\"1723\";}}\n@media (min-device-width:1724px){#S:before{content:\"1724\";}}\n@media (min-device-width:1725px){#S:before{content:\"1725\";}}\n@media (min-device-width:1726px){#S:before{content:\"1726\";}}\n@media (min-device-width:1727px){#S:before{content:\"1727\";}}\n@media (min-device-width:1728px){#S:before{content:\"1728\";}}\n@media (min-device-width:1729px){#S:before{content:\"1729\";}}\n@media (min-device-width:1730px){#S:before{content:\"1730\";}}\n@media (min-device-width:1731px){#S:before{content:\"1731\";}}\n@media (min-device-width:1732px){#S:before{content:\"1732\";}}\n@media (min-device-width:1733px){#S:before{content:\"1733\";}}\n@media (min-device-width:1734px){#S:before{content:\"1734\";}}\n@media (min-device-width:1735px){#S:before{content:\"1735\";}}\n@media (min-device-width:1736px){#S:before{content:\"1736\";}}\n@media (min-device-width:1737px){#S:before{content:\"1737\";}}\n@media (min-device-width:1738px){#S:before{content:\"1738\";}}\n@media (min-device-width:1739px){#S:before{content:\"1739\";}}\n@media (min-device-width:1740px){#S:before{content:\"1740\";}}\n@media (min-device-width:1741px){#S:before{content:\"1741\";}}\n@media (min-device-width:1742px){#S:before{content:\"1742\";}}\n@media (min-device-width:1743px){#S:before{content:\"1743\";}}\n@media (min-device-width:1744px){#S:before{content:\"1744\";}}\n@media (min-device-width:1745px){#S:before{content:\"1745\";}}\n@media (min-device-width:1746px){#S:before{content:\"1746\";}}\n@media (min-device-width:1747px){#S:before{content:\"1747\";}}\n@media (min-device-width:1748px){#S:before{content:\"1748\";}}\n@media (min-device-width:1749px){#S:before{content:\"1749\";}}\n@media (min-device-width:1750px){#S:before{content:\"1750\";}}\n@media (min-device-width:1751px){#S:before{content:\"1751\";}}\n@media (min-device-width:1752px){#S:before{content:\"1752\";}}\n@media (min-device-width:1753px){#S:before{content:\"1753\";}}\n@media (min-device-width:1754px){#S:before{content:\"1754\";}}\n@media (min-device-width:1755px){#S:before{content:\"1755\";}}\n@media (min-device-width:1756px){#S:before{content:\"1756\";}}\n@media (min-device-width:1757px){#S:before{content:\"1757\";}}\n@media (min-device-width:1758px){#S:before{content:\"1758\";}}\n@media (min-device-width:1759px){#S:before{content:\"1759\";}}\n@media (min-device-width:1760px){#S:before{content:\"1760\";}}\n@media (min-device-width:1761px){#S:before{content:\"1761\";}}\n@media (min-device-width:1762px){#S:before{content:\"1762\";}}\n@media (min-device-width:1763px){#S:before{content:\"1763\";}}\n@media (min-device-width:1764px){#S:before{content:\"1764\";}}\n@media (min-device-width:1765px){#S:before{content:\"1765\";}}\n@media (min-device-width:1766px){#S:before{content:\"1766\";}}\n@media (min-device-width:1767px){#S:before{content:\"1767\";}}\n@media (min-device-width:1768px){#S:before{content:\"1768\";}}\n@media (min-device-width:1769px){#S:before{content:\"1769\";}}\n@media (min-device-width:1770px){#S:before{content:\"1770\";}}\n@media (min-device-width:1771px){#S:before{content:\"1771\";}}\n@media (min-device-width:1772px){#S:before{content:\"1772\";}}\n@media (min-device-width:1773px){#S:before{content:\"1773\";}}\n@media (min-device-width:1774px){#S:before{content:\"1774\";}}\n@media (min-device-width:1775px){#S:before{content:\"1775\";}}\n@media (min-device-width:1776px){#S:before{content:\"1776\";}}\n@media (min-device-width:1777px){#S:before{content:\"1777\";}}\n@media (min-device-width:1778px){#S:before{content:\"1778\";}}\n@media (min-device-width:1779px){#S:before{content:\"1779\";}}\n@media (min-device-width:1780px){#S:before{content:\"1780\";}}\n@media (min-device-width:1781px){#S:before{content:\"1781\";}}\n@media (min-device-width:1782px){#S:before{content:\"1782\";}}\n@media (min-device-width:1783px){#S:before{content:\"1783\";}}\n@media (min-device-width:1784px){#S:before{content:\"1784\";}}\n@media (min-device-width:1785px){#S:before{content:\"1785\";}}\n@media (min-device-width:1786px){#S:before{content:\"1786\";}}\n@media (min-device-width:1787px){#S:before{content:\"1787\";}}\n@media (min-device-width:1788px){#S:before{content:\"1788\";}}\n@media (min-device-width:1789px){#S:before{content:\"1789\";}}\n@media (min-device-width:1790px){#S:before{content:\"1790\";}}\n@media (min-device-width:1791px){#S:before{content:\"1791\";}}\n@media (min-device-width:1792px){#S:before{content:\"1792\";}}\n@media (min-device-width:1793px){#S:before{content:\"1793\";}}\n@media (min-device-width:1794px){#S:before{content:\"1794\";}}\n@media (min-device-width:1795px){#S:before{content:\"1795\";}}\n@media (min-device-width:1796px){#S:before{content:\"1796\";}}\n@media (min-device-width:1797px){#S:before{content:\"1797\";}}\n@media (min-device-width:1798px){#S:before{content:\"1798\";}}\n@media (min-device-width:1799px){#S:before{content:\"1799\";}}\n@media (min-device-width:1800px){#S:before{content:\"1800\";}}\n@media (min-device-width:1801px){#S:before{content:\"1801\";}}\n@media (min-device-width:1802px){#S:before{content:\"1802\";}}\n@media (min-device-width:1803px){#S:before{content:\"1803\";}}\n@media (min-device-width:1804px){#S:before{content:\"1804\";}}\n@media (min-device-width:1805px){#S:before{content:\"1805\";}}\n@media (min-device-width:1806px){#S:before{content:\"1806\";}}\n@media (min-device-width:1807px){#S:before{content:\"1807\";}}\n@media (min-device-width:1808px){#S:before{content:\"1808\";}}\n@media (min-device-width:1809px){#S:before{content:\"1809\";}}\n@media (min-device-width:1810px){#S:before{content:\"1810\";}}\n@media (min-device-width:1811px){#S:before{content:\"1811\";}}\n@media (min-device-width:1812px){#S:before{content:\"1812\";}}\n@media (min-device-width:1813px){#S:before{content:\"1813\";}}\n@media (min-device-width:1814px){#S:before{content:\"1814\";}}\n@media (min-device-width:1815px){#S:before{content:\"1815\";}}\n@media (min-device-width:1816px){#S:before{content:\"1816\";}}\n@media (min-device-width:1817px){#S:before{content:\"1817\";}}\n@media (min-device-width:1818px){#S:before{content:\"1818\";}}\n@media (min-device-width:1819px){#S:before{content:\"1819\";}}\n@media (min-device-width:1820px){#S:before{content:\"1820\";}}\n@media (min-device-width:1821px){#S:before{content:\"1821\";}}\n@media (min-device-width:1822px){#S:before{content:\"1822\";}}\n@media (min-device-width:1823px){#S:before{content:\"1823\";}}\n@media (min-device-width:1824px){#S:before{content:\"1824\";}}\n@media (min-device-width:1825px){#S:before{content:\"1825\";}}\n@media (min-device-width:1826px){#S:before{content:\"1826\";}}\n@media (min-device-width:1827px){#S:before{content:\"1827\";}}\n@media (min-device-width:1828px){#S:before{content:\"1828\";}}\n@media (min-device-width:1829px){#S:before{content:\"1829\";}}\n@media (min-device-width:1830px){#S:before{content:\"1830\";}}\n@media (min-device-width:1831px){#S:before{content:\"1831\";}}\n@media (min-device-width:1832px){#S:before{content:\"1832\";}}\n@media (min-device-width:1833px){#S:before{content:\"1833\";}}\n@media (min-device-width:1834px){#S:before{content:\"1834\";}}\n@media (min-device-width:1835px){#S:before{content:\"1835\";}}\n@media (min-device-width:1836px){#S:before{content:\"1836\";}}\n@media (min-device-width:1837px){#S:before{content:\"1837\";}}\n@media (min-device-width:1838px){#S:before{content:\"1838\";}}\n@media (min-device-width:1839px){#S:before{content:\"1839\";}}\n@media (min-device-width:1840px){#S:before{content:\"1840\";}}\n@media (min-device-width:1841px){#S:before{content:\"1841\";}}\n@media (min-device-width:1842px){#S:before{content:\"1842\";}}\n@media (min-device-width:1843px){#S:before{content:\"1843\";}}\n@media (min-device-width:1844px){#S:before{content:\"1844\";}}\n@media (min-device-width:1845px){#S:before{content:\"1845\";}}\n@media (min-device-width:1846px){#S:before{content:\"1846\";}}\n@media (min-device-width:1847px){#S:before{content:\"1847\";}}\n@media (min-device-width:1848px){#S:before{content:\"1848\";}}\n@media (min-device-width:1849px){#S:before{content:\"1849\";}}\n@media (min-device-width:1850px){#S:before{content:\"1850\";}}\n@media (min-device-width:1851px){#S:before{content:\"1851\";}}\n@media (min-device-width:1852px){#S:before{content:\"1852\";}}\n@media (min-device-width:1853px){#S:before{content:\"1853\";}}\n@media (min-device-width:1854px){#S:before{content:\"1854\";}}\n@media (min-device-width:1855px){#S:before{content:\"1855\";}}\n@media (min-device-width:1856px){#S:before{content:\"1856\";}}\n@media (min-device-width:1857px){#S:before{content:\"1857\";}}\n@media (min-device-width:1858px){#S:before{content:\"1858\";}}\n@media (min-device-width:1859px){#S:before{content:\"1859\";}}\n@media (min-device-width:1860px){#S:before{content:\"1860\";}}\n@media (min-device-width:1861px){#S:before{content:\"1861\";}}\n@media (min-device-width:1862px){#S:before{content:\"1862\";}}\n@media (min-device-width:1863px){#S:before{content:\"1863\";}}\n@media (min-device-width:1864px){#S:before{content:\"1864\";}}\n@media (min-device-width:1865px){#S:before{content:\"1865\";}}\n@media (min-device-width:1866px){#S:before{content:\"1866\";}}\n@media (min-device-width:1867px){#S:before{content:\"1867\";}}\n@media (min-device-width:1868px){#S:before{content:\"1868\";}}\n@media (min-device-width:1869px){#S:before{content:\"1869\";}}\n@media (min-device-width:1870px){#S:before{content:\"1870\";}}\n@media (min-device-width:1871px){#S:before{content:\"1871\";}}\n@media (min-device-width:1872px){#S:before{content:\"1872\";}}\n@media (min-device-width:1873px){#S:before{content:\"1873\";}}\n@media (min-device-width:1874px){#S:before{content:\"1874\";}}\n@media (min-device-width:1875px){#S:before{content:\"1875\";}}\n@media (min-device-width:1876px){#S:before{content:\"1876\";}}\n@media (min-device-width:1877px){#S:before{content:\"1877\";}}\n@media (min-device-width:1878px){#S:before{content:\"1878\";}}\n@media (min-device-width:1879px){#S:before{content:\"1879\";}}\n@media (min-device-width:1880px){#S:before{content:\"1880\";}}\n@media (min-device-width:1881px){#S:before{content:\"1881\";}}\n@media (min-device-width:1882px){#S:before{content:\"1882\";}}\n@media (min-device-width:1883px){#S:before{content:\"1883\";}}\n@media (min-device-width:1884px){#S:before{content:\"1884\";}}\n@media (min-device-width:1885px){#S:before{content:\"1885\";}}\n@media (min-device-width:1886px){#S:before{content:\"1886\";}}\n@media (min-device-width:1887px){#S:before{content:\"1887\";}}\n@media (min-device-width:1888px){#S:before{content:\"1888\";}}\n@media (min-device-width:1889px){#S:before{content:\"1889\";}}\n@media (min-device-width:1890px){#S:before{content:\"1890\";}}\n@media (min-device-width:1891px){#S:before{content:\"1891\";}}\n@media (min-device-width:1892px){#S:before{content:\"1892\";}}\n@media (min-device-width:1893px){#S:before{content:\"1893\";}}\n@media (min-device-width:1894px){#S:before{content:\"1894\";}}\n@media (min-device-width:1895px){#S:before{content:\"1895\";}}\n@media (min-device-width:1896px){#S:before{content:\"1896\";}}\n@media (min-device-width:1897px){#S:before{content:\"1897\";}}\n@media (min-device-width:1898px){#S:before{content:\"1898\";}}\n@media (min-device-width:1899px){#S:before{content:\"1899\";}}\n@media (min-device-width:1900px){#S:before{content:\"1900\";}}\n@media (min-device-width:1901px){#S:before{content:\"1901\";}}\n@media (min-device-width:1902px){#S:before{content:\"1902\";}}\n@media (min-device-width:1903px){#S:before{content:\"1903\";}}\n@media (min-device-width:1904px){#S:before{content:\"1904\";}}\n@media (min-device-width:1905px){#S:before{content:\"1905\";}}\n@media (min-device-width:1906px){#S:before{content:\"1906\";}}\n@media (min-device-width:1907px){#S:before{content:\"1907\";}}\n@media (min-device-width:1908px){#S:before{content:\"1908\";}}\n@media (min-device-width:1909px){#S:before{content:\"1909\";}}\n@media (min-device-width:1910px){#S:before{content:\"1910\";}}\n@media (min-device-width:1911px){#S:before{content:\"1911\";}}\n@media (min-device-width:1912px){#S:before{content:\"1912\";}}\n@media (min-device-width:1913px){#S:before{content:\"1913\";}}\n@media (min-device-width:1914px){#S:before{content:\"1914\";}}\n@media (min-device-width:1915px){#S:before{content:\"1915\";}}\n@media (min-device-width:1916px){#S:before{content:\"1916\";}}\n@media (min-device-width:1917px){#S:before{content:\"1917\";}}\n@media (min-device-width:1918px){#S:before{content:\"1918\";}}\n@media (min-device-width:1919px){#S:before{content:\"1919\";}}\n@media (min-device-width:1920px){#S:before{content:\"1920\";}}\n@media (min-device-width:1921px){#S:before{content:\"1921\";}}\n@media (min-device-width:1922px){#S:before{content:\"1922\";}}\n@media (min-device-width:1923px){#S:before{content:\"1923\";}}\n@media (min-device-width:1924px){#S:before{content:\"1924\";}}\n@media (min-device-width:1925px){#S:before{content:\"1925\";}}\n@media (min-device-width:1926px){#S:before{content:\"1926\";}}\n@media (min-device-width:1927px){#S:before{content:\"1927\";}}\n@media (min-device-width:1928px){#S:before{content:\"1928\";}}\n@media (min-device-width:1929px){#S:before{content:\"1929\";}}\n@media (min-device-width:1930px){#S:before{content:\"1930\";}}\n@media (min-device-width:1931px){#S:before{content:\"1931\";}}\n@media (min-device-width:1932px){#S:before{content:\"1932\";}}\n@media (min-device-width:1933px){#S:before{content:\"1933\";}}\n@media (min-device-width:1934px){#S:before{content:\"1934\";}}\n@media (min-device-width:1935px){#S:before{content:\"1935\";}}\n@media (min-device-width:1936px){#S:before{content:\"1936\";}}\n@media (min-device-width:1937px){#S:before{content:\"1937\";}}\n@media (min-device-width:1938px){#S:before{content:\"1938\";}}\n@media (min-device-width:1939px){#S:before{content:\"1939\";}}\n@media (min-device-width:1940px){#S:before{content:\"1940\";}}\n@media (min-device-width:1941px){#S:before{content:\"1941\";}}\n@media (min-device-width:1942px){#S:before{content:\"1942\";}}\n@media (min-device-width:1943px){#S:before{content:\"1943\";}}\n@media (min-device-width:1944px){#S:before{content:\"1944\";}}\n@media (min-device-width:1945px){#S:before{content:\"1945\";}}\n@media (min-device-width:1946px){#S:before{content:\"1946\";}}\n@media (min-device-width:1947px){#S:before{content:\"1947\";}}\n@media (min-device-width:1948px){#S:before{content:\"1948\";}}\n@media (min-device-width:1949px){#S:before{content:\"1949\";}}\n@media (min-device-width:1950px){#S:before{content:\"1950\";}}\n@media (min-device-width:1951px){#S:before{content:\"1951\";}}\n@media (min-device-width:1952px){#S:before{content:\"1952\";}}\n@media (min-device-width:1953px){#S:before{content:\"1953\";}}\n@media (min-device-width:1954px){#S:before{content:\"1954\";}}\n@media (min-device-width:1955px){#S:before{content:\"1955\";}}\n@media (min-device-width:1956px){#S:before{content:\"1956\";}}\n@media (min-device-width:1957px){#S:before{content:\"1957\";}}\n@media (min-device-width:1958px){#S:before{content:\"1958\";}}\n@media (min-device-width:1959px){#S:before{content:\"1959\";}}\n@media (min-device-width:1960px){#S:before{content:\"1960\";}}\n@media (min-device-width:1961px){#S:before{content:\"1961\";}}\n@media (min-device-width:1962px){#S:before{content:\"1962\";}}\n@media (min-device-width:1963px){#S:before{content:\"1963\";}}\n@media (min-device-width:1964px){#S:before{content:\"1964\";}}\n@media (min-device-width:1965px){#S:before{content:\"1965\";}}\n@media (min-device-width:1966px){#S:before{content:\"1966\";}}\n@media (min-device-width:1967px){#S:before{content:\"1967\";}}\n@media (min-device-width:1968px){#S:before{content:\"1968\";}}\n@media (min-device-width:1969px){#S:before{content:\"1969\";}}\n@media (min-device-width:1970px){#S:before{content:\"1970\";}}\n@media (min-device-width:1971px){#S:before{content:\"1971\";}}\n@media (min-device-width:1972px){#S:before{content:\"1972\";}}\n@media (min-device-width:1973px){#S:before{content:\"1973\";}}\n@media (min-device-width:1974px){#S:before{content:\"1974\";}}\n@media (min-device-width:1975px){#S:before{content:\"1975\";}}\n@media (min-device-width:1976px){#S:before{content:\"1976\";}}\n@media (min-device-width:1977px){#S:before{content:\"1977\";}}\n@media (min-device-width:1978px){#S:before{content:\"1978\";}}\n@media (min-device-width:1979px){#S:before{content:\"1979\";}}\n@media (min-device-width:1980px){#S:before{content:\"1980\";}}\n@media (min-device-width:1981px){#S:before{content:\"1981\";}}\n@media (min-device-width:1982px){#S:before{content:\"1982\";}}\n@media (min-device-width:1983px){#S:before{content:\"1983\";}}\n@media (min-device-width:1984px){#S:before{content:\"1984\";}}\n@media (min-device-width:1985px){#S:before{content:\"1985\";}}\n@media (min-device-width:1986px){#S:before{content:\"1986\";}}\n@media (min-device-width:1987px){#S:before{content:\"1987\";}}\n@media (min-device-width:1988px){#S:before{content:\"1988\";}}\n@media (min-device-width:1989px){#S:before{content:\"1989\";}}\n@media (min-device-width:1990px){#S:before{content:\"1990\";}}\n@media (min-device-width:1991px){#S:before{content:\"1991\";}}\n@media (min-device-width:1992px){#S:before{content:\"1992\";}}\n@media (min-device-width:1993px){#S:before{content:\"1993\";}}\n@media (min-device-width:1994px){#S:before{content:\"1994\";}}\n@media (min-device-width:1995px){#S:before{content:\"1995\";}}\n@media (min-device-width:1996px){#S:before{content:\"1996\";}}\n@media (min-device-width:1997px){#S:before{content:\"1997\";}}\n@media (min-device-width:1998px){#S:before{content:\"1998\";}}\n@media (min-device-width:1999px){#S:before{content:\"1999\";}}\n@media (min-device-width:2000px){#S:before{content:\"2000\";}}\n@media (min-device-width:2001px){#S:before{content:\"2001\";}}\n@media (min-device-width:2002px){#S:before{content:\"2002\";}}\n@media (min-device-width:2003px){#S:before{content:\"2003\";}}\n@media (min-device-width:2004px){#S:before{content:\"2004\";}}\n@media (min-device-width:2005px){#S:before{content:\"2005\";}}\n@media (min-device-width:2006px){#S:before{content:\"2006\";}}\n@media (min-device-width:2007px){#S:before{content:\"2007\";}}\n@media (min-device-width:2008px){#S:before{content:\"2008\";}}\n@media (min-device-width:2009px){#S:before{content:\"2009\";}}\n@media (min-device-width:2010px){#S:before{content:\"2010\";}}\n@media (min-device-width:2011px){#S:before{content:\"2011\";}}\n@media (min-device-width:2012px){#S:before{content:\"2012\";}}\n@media (min-device-width:2013px){#S:before{content:\"2013\";}}\n@media (min-device-width:2014px){#S:before{content:\"2014\";}}\n@media (min-device-width:2015px){#S:before{content:\"2015\";}}\n@media (min-device-width:2016px){#S:before{content:\"2016\";}}\n@media (min-device-width:2017px){#S:before{content:\"2017\";}}\n@media (min-device-width:2018px){#S:before{content:\"2018\";}}\n@media (min-device-width:2019px){#S:before{content:\"2019\";}}\n@media (min-device-width:2020px){#S:before{content:\"2020\";}}\n@media (min-device-width:2021px){#S:before{content:\"2021\";}}\n@media (min-device-width:2022px){#S:before{content:\"2022\";}}\n@media (min-device-width:2023px){#S:before{content:\"2023\";}}\n@media (min-device-width:2024px){#S:before{content:\"2024\";}}\n@media (min-device-width:2025px){#S:before{content:\"2025\";}}\n@media (min-device-width:2026px){#S:before{content:\"2026\";}}\n@media (min-device-width:2027px){#S:before{content:\"2027\";}}\n@media (min-device-width:2028px){#S:before{content:\"2028\";}}\n@media (min-device-width:2029px){#S:before{content:\"2029\";}}\n@media (min-device-width:2030px){#S:before{content:\"2030\";}}\n@media (min-device-width:2031px){#S:before{content:\"2031\";}}\n@media (min-device-width:2032px){#S:before{content:\"2032\";}}\n@media (min-device-width:2033px){#S:before{content:\"2033\";}}\n@media (min-device-width:2034px){#S:before{content:\"2034\";}}\n@media (min-device-width:2035px){#S:before{content:\"2035\";}}\n@media (min-device-width:2036px){#S:before{content:\"2036\";}}\n@media (min-device-width:2037px){#S:before{content:\"2037\";}}\n@media (min-device-width:2038px){#S:before{content:\"2038\";}}\n@media (min-device-width:2039px){#S:before{content:\"2039\";}}\n@media (min-device-width:2040px){#S:before{content:\"2040\";}}\n@media (min-device-width:2041px){#S:before{content:\"2041\";}}\n@media (min-device-width:2042px){#S:before{content:\"2042\";}}\n@media (min-device-width:2043px){#S:before{content:\"2043\";}}\n@media (min-device-width:2044px){#S:before{content:\"2044\";}}\n@media (min-device-width:2045px){#S:before{content:\"2045\";}}\n@media (min-device-width:2046px){#S:before{content:\"2046\";}}\n@media (min-device-width:2047px){#S:before{content:\"2047\";}}\n@media (min-device-width:2048px){#S:before{content:\"2048\";}}\n@media (min-device-width:2049px){#S:before{content:\"2049\";}}\n@media (min-device-width:2050px){#S:before{content:\"2050\";}}\n@media (min-device-width:2051px){#S:before{content:\"2051\";}}\n@media (min-device-width:2052px){#S:before{content:\"2052\";}}\n@media (min-device-width:2053px){#S:before{content:\"2053\";}}\n@media (min-device-width:2054px){#S:before{content:\"2054\";}}\n@media (min-device-width:2055px){#S:before{content:\"2055\";}}\n@media (min-device-width:2056px){#S:before{content:\"2056\";}}\n@media (min-device-width:2057px){#S:before{content:\"2057\";}}\n@media (min-device-width:2058px){#S:before{content:\"2058\";}}\n@media (min-device-width:2059px){#S:before{content:\"2059\";}}\n@media (min-device-width:2060px){#S:before{content:\"2060\";}}\n@media (min-device-width:2061px){#S:before{content:\"2061\";}}\n@media (min-device-width:2062px){#S:before{content:\"2062\";}}\n@media (min-device-width:2063px){#S:before{content:\"2063\";}}\n@media (min-device-width:2064px){#S:before{content:\"2064\";}}\n@media (min-device-width:2065px){#S:before{content:\"2065\";}}\n@media (min-device-width:2066px){#S:before{content:\"2066\";}}\n@media (min-device-width:2067px){#S:before{content:\"2067\";}}\n@media (min-device-width:2068px){#S:before{content:\"2068\";}}\n@media (min-device-width:2069px){#S:before{content:\"2069\";}}\n@media (min-device-width:2070px){#S:before{content:\"2070\";}}\n@media (min-device-width:2071px){#S:before{content:\"2071\";}}\n@media (min-device-width:2072px){#S:before{content:\"2072\";}}\n@media (min-device-width:2073px){#S:before{content:\"2073\";}}\n@media (min-device-width:2074px){#S:before{content:\"2074\";}}\n@media (min-device-width:2075px){#S:before{content:\"2075\";}}\n@media (min-device-width:2076px){#S:before{content:\"2076\";}}\n@media (min-device-width:2077px){#S:before{content:\"2077\";}}\n@media (min-device-width:2078px){#S:before{content:\"2078\";}}\n@media (min-device-width:2079px){#S:before{content:\"2079\";}}\n@media (min-device-width:2080px){#S:before{content:\"2080\";}}\n@media (min-device-width:2081px){#S:before{content:\"2081\";}}\n@media (min-device-width:2082px){#S:before{content:\"2082\";}}\n@media (min-device-width:2083px){#S:before{content:\"2083\";}}\n@media (min-device-width:2084px){#S:before{content:\"2084\";}}\n@media (min-device-width:2085px){#S:before{content:\"2085\";}}\n@media (min-device-width:2086px){#S:before{content:\"2086\";}}\n@media (min-device-width:2087px){#S:before{content:\"2087\";}}\n@media (min-device-width:2088px){#S:before{content:\"2088\";}}\n@media (min-device-width:2089px){#S:before{content:\"2089\";}}\n@media (min-device-width:2090px){#S:before{content:\"2090\";}}\n@media (min-device-width:2091px){#S:before{content:\"2091\";}}\n@media (min-device-width:2092px){#S:before{content:\"2092\";}}\n@media (min-device-width:2093px){#S:before{content:\"2093\";}}\n@media (min-device-width:2094px){#S:before{content:\"2094\";}}\n@media (min-device-width:2095px){#S:before{content:\"2095\";}}\n@media (min-device-width:2096px){#S:before{content:\"2096\";}}\n@media (min-device-width:2097px){#S:before{content:\"2097\";}}\n@media (min-device-width:2098px){#S:before{content:\"2098\";}}\n@media (min-device-width:2099px){#S:before{content:\"2099\";}}\n@media (min-device-width:2100px){#S:before{content:\"2100\";}}\n@media (min-device-width:2101px){#S:before{content:\"2101\";}}\n@media (min-device-width:2102px){#S:before{content:\"2102\";}}\n@media (min-device-width:2103px){#S:before{content:\"2103\";}}\n@media (min-device-width:2104px){#S:before{content:\"2104\";}}\n@media (min-device-width:2105px){#S:before{content:\"2105\";}}\n@media (min-device-width:2106px){#S:before{content:\"2106\";}}\n@media (min-device-width:2107px){#S:before{content:\"2107\";}}\n@media (min-device-width:2108px){#S:before{content:\"2108\";}}\n@media (min-device-width:2109px){#S:before{content:\"2109\";}}\n@media (min-device-width:2110px){#S:before{content:\"2110\";}}\n@media (min-device-width:2111px){#S:before{content:\"2111\";}}\n@media (min-device-width:2112px){#S:before{content:\"2112\";}}\n@media (min-device-width:2113px){#S:before{content:\"2113\";}}\n@media (min-device-width:2114px){#S:before{content:\"2114\";}}\n@media (min-device-width:2115px){#S:before{content:\"2115\";}}\n@media (min-device-width:2116px){#S:before{content:\"2116\";}}\n@media (min-device-width:2117px){#S:before{content:\"2117\";}}\n@media (min-device-width:2118px){#S:before{content:\"2118\";}}\n@media (min-device-width:2119px){#S:before{content:\"2119\";}}\n@media (min-device-width:2120px){#S:before{content:\"2120\";}}\n@media (min-device-width:2121px){#S:before{content:\"2121\";}}\n@media (min-device-width:2122px){#S:before{content:\"2122\";}}\n@media (min-device-width:2123px){#S:before{content:\"2123\";}}\n@media (min-device-width:2124px){#S:before{content:\"2124\";}}\n@media (min-device-width:2125px){#S:before{content:\"2125\";}}\n@media (min-device-width:2126px){#S:before{content:\"2126\";}}\n@media (min-device-width:2127px){#S:before{content:\"2127\";}}\n@media (min-device-width:2128px){#S:before{content:\"2128\";}}\n@media (min-device-width:2129px){#S:before{content:\"2129\";}}\n@media (min-device-width:2130px){#S:before{content:\"2130\";}}\n@media (min-device-width:2131px){#S:before{content:\"2131\";}}\n@media (min-device-width:2132px){#S:before{content:\"2132\";}}\n@media (min-device-width:2133px){#S:before{content:\"2133\";}}\n@media (min-device-width:2134px){#S:before{content:\"2134\";}}\n@media (min-device-width:2135px){#S:before{content:\"2135\";}}\n@media (min-device-width:2136px){#S:before{content:\"2136\";}}\n@media (min-device-width:2137px){#S:before{content:\"2137\";}}\n@media (min-device-width:2138px){#S:before{content:\"2138\";}}\n@media (min-device-width:2139px){#S:before{content:\"2139\";}}\n@media (min-device-width:2140px){#S:before{content:\"2140\";}}\n@media (min-device-width:2141px){#S:before{content:\"2141\";}}\n@media (min-device-width:2142px){#S:before{content:\"2142\";}}\n@media (min-device-width:2143px){#S:before{content:\"2143\";}}\n@media (min-device-width:2144px){#S:before{content:\"2144\";}}\n@media (min-device-width:2145px){#S:before{content:\"2145\";}}\n@media (min-device-width:2146px){#S:before{content:\"2146\";}}\n@media (min-device-width:2147px){#S:before{content:\"2147\";}}\n@media (min-device-width:2148px){#S:before{content:\"2148\";}}\n@media (min-device-width:2149px){#S:before{content:\"2149\";}}\n@media (min-device-width:2150px){#S:before{content:\"2150\";}}\n@media (min-device-width:2151px){#S:before{content:\"2151\";}}\n@media (min-device-width:2152px){#S:before{content:\"2152\";}}\n@media (min-device-width:2153px){#S:before{content:\"2153\";}}\n@media (min-device-width:2154px){#S:before{content:\"2154\";}}\n@media (min-device-width:2155px){#S:before{content:\"2155\";}}\n@media (min-device-width:2156px){#S:before{content:\"2156\";}}\n@media (min-device-width:2157px){#S:before{content:\"2157\";}}\n@media (min-device-width:2158px){#S:before{content:\"2158\";}}\n@media (min-device-width:2159px){#S:before{content:\"2159\";}}\n@media (min-device-width:2160px){#S:before{content:\"2160\";}}\n@media (min-device-width:2161px){#S:before{content:\"2161\";}}\n@media (min-device-width:2162px){#S:before{content:\"2162\";}}\n@media (min-device-width:2163px){#S:before{content:\"2163\";}}\n@media (min-device-width:2164px){#S:before{content:\"2164\";}}\n@media (min-device-width:2165px){#S:before{content:\"2165\";}}\n@media (min-device-width:2166px){#S:before{content:\"2166\";}}\n@media (min-device-width:2167px){#S:before{content:\"2167\";}}\n@media (min-device-width:2168px){#S:before{content:\"2168\";}}\n@media (min-device-width:2169px){#S:before{content:\"2169\";}}\n@media (min-device-width:2170px){#S:before{content:\"2170\";}}\n@media (min-device-width:2171px){#S:before{content:\"2171\";}}\n@media (min-device-width:2172px){#S:before{content:\"2172\";}}\n@media (min-device-width:2173px){#S:before{content:\"2173\";}}\n@media (min-device-width:2174px){#S:before{content:\"2174\";}}\n@media (min-device-width:2175px){#S:before{content:\"2175\";}}\n@media (min-device-width:2176px){#S:before{content:\"2176\";}}\n@media (min-device-width:2177px){#S:before{content:\"2177\";}}\n@media (min-device-width:2178px){#S:before{content:\"2178\";}}\n@media (min-device-width:2179px){#S:before{content:\"2179\";}}\n@media (min-device-width:2180px){#S:before{content:\"2180\";}}\n@media (min-device-width:2181px){#S:before{content:\"2181\";}}\n@media (min-device-width:2182px){#S:before{content:\"2182\";}}\n@media (min-device-width:2183px){#S:before{content:\"2183\";}}\n@media (min-device-width:2184px){#S:before{content:\"2184\";}}\n@media (min-device-width:2185px){#S:before{content:\"2185\";}}\n@media (min-device-width:2186px){#S:before{content:\"2186\";}}\n@media (min-device-width:2187px){#S:before{content:\"2187\";}}\n@media (min-device-width:2188px){#S:before{content:\"2188\";}}\n@media (min-device-width:2189px){#S:before{content:\"2189\";}}\n@media (min-device-width:2190px){#S:before{content:\"2190\";}}\n@media (min-device-width:2191px){#S:before{content:\"2191\";}}\n@media (min-device-width:2192px){#S:before{content:\"2192\";}}\n@media (min-device-width:2193px){#S:before{content:\"2193\";}}\n@media (min-device-width:2194px){#S:before{content:\"2194\";}}\n@media (min-device-width:2195px){#S:before{content:\"2195\";}}\n@media (min-device-width:2196px){#S:before{content:\"2196\";}}\n@media (min-device-width:2197px){#S:before{content:\"2197\";}}\n@media (min-device-width:2198px){#S:before{content:\"2198\";}}\n@media (min-device-width:2199px){#S:before{content:\"2199\";}}\n@media (min-device-width:2200px){#S:before{content:\"2200\";}}\n@media (min-device-width:2201px){#S:before{content:\"2201\";}}\n@media (min-device-width:2202px){#S:before{content:\"2202\";}}\n@media (min-device-width:2203px){#S:before{content:\"2203\";}}\n@media (min-device-width:2204px){#S:before{content:\"2204\";}}\n@media (min-device-width:2205px){#S:before{content:\"2205\";}}\n@media (min-device-width:2206px){#S:before{content:\"2206\";}}\n@media (min-device-width:2207px){#S:before{content:\"2207\";}}\n@media (min-device-width:2208px){#S:before{content:\"2208\";}}\n@media (min-device-width:2209px){#S:before{content:\"2209\";}}\n@media (min-device-width:2210px){#S:before{content:\"2210\";}}\n@media (min-device-width:2211px){#S:before{content:\"2211\";}}\n@media (min-device-width:2212px){#S:before{content:\"2212\";}}\n@media (min-device-width:2213px){#S:before{content:\"2213\";}}\n@media (min-device-width:2214px){#S:before{content:\"2214\";}}\n@media (min-device-width:2215px){#S:before{content:\"2215\";}}\n@media (min-device-width:2216px){#S:before{content:\"2216\";}}\n@media (min-device-width:2217px){#S:before{content:\"2217\";}}\n@media (min-device-width:2218px){#S:before{content:\"2218\";}}\n@media (min-device-width:2219px){#S:before{content:\"2219\";}}\n@media (min-device-width:2220px){#S:before{content:\"2220\";}}\n@media (min-device-width:2221px){#S:before{content:\"2221\";}}\n@media (min-device-width:2222px){#S:before{content:\"2222\";}}\n@media (min-device-width:2223px){#S:before{content:\"2223\";}}\n@media (min-device-width:2224px){#S:before{content:\"2224\";}}\n@media (min-device-width:2225px){#S:before{content:\"2225\";}}\n@media (min-device-width:2226px){#S:before{content:\"2226\";}}\n@media (min-device-width:2227px){#S:before{content:\"2227\";}}\n@media (min-device-width:2228px){#S:before{content:\"2228\";}}\n@media (min-device-width:2229px){#S:before{content:\"2229\";}}\n@media (min-device-width:2230px){#S:before{content:\"2230\";}}\n@media (min-device-width:2231px){#S:before{content:\"2231\";}}\n@media (min-device-width:2232px){#S:before{content:\"2232\";}}\n@media (min-device-width:2233px){#S:before{content:\"2233\";}}\n@media (min-device-width:2234px){#S:before{content:\"2234\";}}\n@media (min-device-width:2235px){#S:before{content:\"2235\";}}\n@media (min-device-width:2236px){#S:before{content:\"2236\";}}\n@media (min-device-width:2237px){#S:before{content:\"2237\";}}\n@media (min-device-width:2238px){#S:before{content:\"2238\";}}\n@media (min-device-width:2239px){#S:before{content:\"2239\";}}\n@media (min-device-width:2240px){#S:before{content:\"2240\";}}\n@media (min-device-width:2241px){#S:before{content:\"2241\";}}\n@media (min-device-width:2242px){#S:before{content:\"2242\";}}\n@media (min-device-width:2243px){#S:before{content:\"2243\";}}\n@media (min-device-width:2244px){#S:before{content:\"2244\";}}\n@media (min-device-width:2245px){#S:before{content:\"2245\";}}\n@media (min-device-width:2246px){#S:before{content:\"2246\";}}\n@media (min-device-width:2247px){#S:before{content:\"2247\";}}\n@media (min-device-width:2248px){#S:before{content:\"2248\";}}\n@media (min-device-width:2249px){#S:before{content:\"2249\";}}\n@media (min-device-width:2250px){#S:before{content:\"2250\";}}\n@media (min-device-width:2251px){#S:before{content:\"2251\";}}\n@media (min-device-width:2252px){#S:before{content:\"2252\";}}\n@media (min-device-width:2253px){#S:before{content:\"2253\";}}\n@media (min-device-width:2254px){#S:before{content:\"2254\";}}\n@media (min-device-width:2255px){#S:before{content:\"2255\";}}\n@media (min-device-width:2256px){#S:before{content:\"2256\";}}\n@media (min-device-width:2257px){#S:before{content:\"2257\";}}\n@media (min-device-width:2258px){#S:before{content:\"2258\";}}\n@media (min-device-width:2259px){#S:before{content:\"2259\";}}\n@media (min-device-width:2260px){#S:before{content:\"2260\";}}\n@media (min-device-width:2261px){#S:before{content:\"2261\";}}\n@media (min-device-width:2262px){#S:before{content:\"2262\";}}\n@media (min-device-width:2263px){#S:before{content:\"2263\";}}\n@media (min-device-width:2264px){#S:before{content:\"2264\";}}\n@media (min-device-width:2265px){#S:before{content:\"2265\";}}\n@media (min-device-width:2266px){#S:before{content:\"2266\";}}\n@media (min-device-width:2267px){#S:before{content:\"2267\";}}\n@media (min-device-width:2268px){#S:before{content:\"2268\";}}\n@media (min-device-width:2269px){#S:before{content:\"2269\";}}\n@media (min-device-width:2270px){#S:before{content:\"2270\";}}\n@media (min-device-width:2271px){#S:before{content:\"2271\";}}\n@media (min-device-width:2272px){#S:before{content:\"2272\";}}\n@media (min-device-width:2273px){#S:before{content:\"2273\";}}\n@media (min-device-width:2274px){#S:before{content:\"2274\";}}\n@media (min-device-width:2275px){#S:before{content:\"2275\";}}\n@media (min-device-width:2276px){#S:before{content:\"2276\";}}\n@media (min-device-width:2277px){#S:before{content:\"2277\";}}\n@media (min-device-width:2278px){#S:before{content:\"2278\";}}\n@media (min-device-width:2279px){#S:before{content:\"2279\";}}\n@media (min-device-width:2280px){#S:before{content:\"2280\";}}\n@media (min-device-width:2281px){#S:before{content:\"2281\";}}\n@media (min-device-width:2282px){#S:before{content:\"2282\";}}\n@media (min-device-width:2283px){#S:before{content:\"2283\";}}\n@media (min-device-width:2284px){#S:before{content:\"2284\";}}\n@media (min-device-width:2285px){#S:before{content:\"2285\";}}\n@media (min-device-width:2286px){#S:before{content:\"2286\";}}\n@media (min-device-width:2287px){#S:before{content:\"2287\";}}\n@media (min-device-width:2288px){#S:before{content:\"2288\";}}\n@media (min-device-width:2289px){#S:before{content:\"2289\";}}\n@media (min-device-width:2290px){#S:before{content:\"2290\";}}\n@media (min-device-width:2291px){#S:before{content:\"2291\";}}\n@media (min-device-width:2292px){#S:before{content:\"2292\";}}\n@media (min-device-width:2293px){#S:before{content:\"2293\";}}\n@media (min-device-width:2294px){#S:before{content:\"2294\";}}\n@media (min-device-width:2295px){#S:before{content:\"2295\";}}\n@media (min-device-width:2296px){#S:before{content:\"2296\";}}\n@media (min-device-width:2297px){#S:before{content:\"2297\";}}\n@media (min-device-width:2298px){#S:before{content:\"2298\";}}\n@media (min-device-width:2299px){#S:before{content:\"2299\";}}\n@media (min-device-width:2300px){#S:before{content:\"2300\";}}\n@media (min-device-width:2301px){#S:before{content:\"2301\";}}\n@media (min-device-width:2302px){#S:before{content:\"2302\";}}\n@media (min-device-width:2303px){#S:before{content:\"2303\";}}\n@media (min-device-width:2304px){#S:before{content:\"2304\";}}\n@media (min-device-width:2305px){#S:before{content:\"2305\";}}\n@media (min-device-width:2306px){#S:before{content:\"2306\";}}\n@media (min-device-width:2307px){#S:before{content:\"2307\";}}\n@media (min-device-width:2308px){#S:before{content:\"2308\";}}\n@media (min-device-width:2309px){#S:before{content:\"2309\";}}\n@media (min-device-width:2310px){#S:before{content:\"2310\";}}\n@media (min-device-width:2311px){#S:before{content:\"2311\";}}\n@media (min-device-width:2312px){#S:before{content:\"2312\";}}\n@media (min-device-width:2313px){#S:before{content:\"2313\";}}\n@media (min-device-width:2314px){#S:before{content:\"2314\";}}\n@media (min-device-width:2315px){#S:before{content:\"2315\";}}\n@media (min-device-width:2316px){#S:before{content:\"2316\";}}\n@media (min-device-width:2317px){#S:before{content:\"2317\";}}\n@media (min-device-width:2318px){#S:before{content:\"2318\";}}\n@media (min-device-width:2319px){#S:before{content:\"2319\";}}\n@media (min-device-width:2320px){#S:before{content:\"2320\";}}\n@media (min-device-width:2321px){#S:before{content:\"2321\";}}\n@media (min-device-width:2322px){#S:before{content:\"2322\";}}\n@media (min-device-width:2323px){#S:before{content:\"2323\";}}\n@media (min-device-width:2324px){#S:before{content:\"2324\";}}\n@media (min-device-width:2325px){#S:before{content:\"2325\";}}\n@media (min-device-width:2326px){#S:before{content:\"2326\";}}\n@media (min-device-width:2327px){#S:before{content:\"2327\";}}\n@media (min-device-width:2328px){#S:before{content:\"2328\";}}\n@media (min-device-width:2329px){#S:before{content:\"2329\";}}\n@media (min-device-width:2330px){#S:before{content:\"2330\";}}\n@media (min-device-width:2331px){#S:before{content:\"2331\";}}\n@media (min-device-width:2332px){#S:before{content:\"2332\";}}\n@media (min-device-width:2333px){#S:before{content:\"2333\";}}\n@media (min-device-width:2334px){#S:before{content:\"2334\";}}\n@media (min-device-width:2335px){#S:before{content:\"2335\";}}\n@media (min-device-width:2336px){#S:before{content:\"2336\";}}\n@media (min-device-width:2337px){#S:before{content:\"2337\";}}\n@media (min-device-width:2338px){#S:before{content:\"2338\";}}\n@media (min-device-width:2339px){#S:before{content:\"2339\";}}\n@media (min-device-width:2340px){#S:before{content:\"2340\";}}\n@media (min-device-width:2341px){#S:before{content:\"2341\";}}\n@media (min-device-width:2342px){#S:before{content:\"2342\";}}\n@media (min-device-width:2343px){#S:before{content:\"2343\";}}\n@media (min-device-width:2344px){#S:before{content:\"2344\";}}\n@media (min-device-width:2345px){#S:before{content:\"2345\";}}\n@media (min-device-width:2346px){#S:before{content:\"2346\";}}\n@media (min-device-width:2347px){#S:before{content:\"2347\";}}\n@media (min-device-width:2348px){#S:before{content:\"2348\";}}\n@media (min-device-width:2349px){#S:before{content:\"2349\";}}\n@media (min-device-width:2350px){#S:before{content:\"2350\";}}\n@media (min-device-width:2351px){#S:before{content:\"2351\";}}\n@media (min-device-width:2352px){#S:before{content:\"2352\";}}\n@media (min-device-width:2353px){#S:before{content:\"2353\";}}\n@media (min-device-width:2354px){#S:before{content:\"2354\";}}\n@media (min-device-width:2355px){#S:before{content:\"2355\";}}\n@media (min-device-width:2356px){#S:before{content:\"2356\";}}\n@media (min-device-width:2357px){#S:before{content:\"2357\";}}\n@media (min-device-width:2358px){#S:before{content:\"2358\";}}\n@media (min-device-width:2359px){#S:before{content:\"2359\";}}\n@media (min-device-width:2360px){#S:before{content:\"2360\";}}\n@media (min-device-width:2361px){#S:before{content:\"2361\";}}\n@media (min-device-width:2362px){#S:before{content:\"2362\";}}\n@media (min-device-width:2363px){#S:before{content:\"2363\";}}\n@media (min-device-width:2364px){#S:before{content:\"2364\";}}\n@media (min-device-width:2365px){#S:before{content:\"2365\";}}\n@media (min-device-width:2366px){#S:before{content:\"2366\";}}\n@media (min-device-width:2367px){#S:before{content:\"2367\";}}\n@media (min-device-width:2368px){#S:before{content:\"2368\";}}\n@media (min-device-width:2369px){#S:before{content:\"2369\";}}\n@media (min-device-width:2370px){#S:before{content:\"2370\";}}\n@media (min-device-width:2371px){#S:before{content:\"2371\";}}\n@media (min-device-width:2372px){#S:before{content:\"2372\";}}\n@media (min-device-width:2373px){#S:before{content:\"2373\";}}\n@media (min-device-width:2374px){#S:before{content:\"2374\";}}\n@media (min-device-width:2375px){#S:before{content:\"2375\";}}\n@media (min-device-width:2376px){#S:before{content:\"2376\";}}\n@media (min-device-width:2377px){#S:before{content:\"2377\";}}\n@media (min-device-width:2378px){#S:before{content:\"2378\";}}\n@media (min-device-width:2379px){#S:before{content:\"2379\";}}\n@media (min-device-width:2380px){#S:before{content:\"2380\";}}\n@media (min-device-width:2381px){#S:before{content:\"2381\";}}\n@media (min-device-width:2382px){#S:before{content:\"2382\";}}\n@media (min-device-width:2383px){#S:before{content:\"2383\";}}\n@media (min-device-width:2384px){#S:before{content:\"2384\";}}\n@media (min-device-width:2385px){#S:before{content:\"2385\";}}\n@media (min-device-width:2386px){#S:before{content:\"2386\";}}\n@media (min-device-width:2387px){#S:before{content:\"2387\";}}\n@media (min-device-width:2388px){#S:before{content:\"2388\";}}\n@media (min-device-width:2389px){#S:before{content:\"2389\";}}\n@media (min-device-width:2390px){#S:before{content:\"2390\";}}\n@media (min-device-width:2391px){#S:before{content:\"2391\";}}\n@media (min-device-width:2392px){#S:before{content:\"2392\";}}\n@media (min-device-width:2393px){#S:before{content:\"2393\";}}\n@media (min-device-width:2394px){#S:before{content:\"2394\";}}\n@media (min-device-width:2395px){#S:before{content:\"2395\";}}\n@media (min-device-width:2396px){#S:before{content:\"2396\";}}\n@media (min-device-width:2397px){#S:before{content:\"2397\";}}\n@media (min-device-width:2398px){#S:before{content:\"2398\";}}\n@media (min-device-width:2399px){#S:before{content:\"2399\";}}\n@media (min-device-width:2400px){#S:before{content:\"2400\";}}\n@media (min-device-width:2401px){#S:before{content:\"2401\";}}\n@media (min-device-width:2402px){#S:before{content:\"2402\";}}\n@media (min-device-width:2403px){#S:before{content:\"2403\";}}\n@media (min-device-width:2404px){#S:before{content:\"2404\";}}\n@media (min-device-width:2405px){#S:before{content:\"2405\";}}\n@media (min-device-width:2406px){#S:before{content:\"2406\";}}\n@media (min-device-width:2407px){#S:before{content:\"2407\";}}\n@media (min-device-width:2408px){#S:before{content:\"2408\";}}\n@media (min-device-width:2409px){#S:before{content:\"2409\";}}\n@media (min-device-width:2410px){#S:before{content:\"2410\";}}\n@media (min-device-width:2411px){#S:before{content:\"2411\";}}\n@media (min-device-width:2412px){#S:before{content:\"2412\";}}\n@media (min-device-width:2413px){#S:before{content:\"2413\";}}\n@media (min-device-width:2414px){#S:before{content:\"2414\";}}\n@media (min-device-width:2415px){#S:before{content:\"2415\";}}\n@media (min-device-width:2416px){#S:before{content:\"2416\";}}\n@media (min-device-width:2417px){#S:before{content:\"2417\";}}\n@media (min-device-width:2418px){#S:before{content:\"2418\";}}\n@media (min-device-width:2419px){#S:before{content:\"2419\";}}\n@media (min-device-width:2420px){#S:before{content:\"2420\";}}\n@media (min-device-width:2421px){#S:before{content:\"2421\";}}\n@media (min-device-width:2422px){#S:before{content:\"2422\";}}\n@media (min-device-width:2423px){#S:before{content:\"2423\";}}\n@media (min-device-width:2424px){#S:before{content:\"2424\";}}\n@media (min-device-width:2425px){#S:before{content:\"2425\";}}\n@media (min-device-width:2426px){#S:before{content:\"2426\";}}\n@media (min-device-width:2427px){#S:before{content:\"2427\";}}\n@media (min-device-width:2428px){#S:before{content:\"2428\";}}\n@media (min-device-width:2429px){#S:before{content:\"2429\";}}\n@media (min-device-width:2430px){#S:before{content:\"2430\";}}\n@media (min-device-width:2431px){#S:before{content:\"2431\";}}\n@media (min-device-width:2432px){#S:before{content:\"2432\";}}\n@media (min-device-width:2433px){#S:before{content:\"2433\";}}\n@media (min-device-width:2434px){#S:before{content:\"2434\";}}\n@media (min-device-width:2435px){#S:before{content:\"2435\";}}\n@media (min-device-width:2436px){#S:before{content:\"2436\";}}\n@media (min-device-width:2437px){#S:before{content:\"2437\";}}\n@media (min-device-width:2438px){#S:before{content:\"2438\";}}\n@media (min-device-width:2439px){#S:before{content:\"2439\";}}\n@media (min-device-width:2440px){#S:before{content:\"2440\";}}\n@media (min-device-width:2441px){#S:before{content:\"2441\";}}\n@media (min-device-width:2442px){#S:before{content:\"2442\";}}\n@media (min-device-width:2443px){#S:before{content:\"2443\";}}\n@media (min-device-width:2444px){#S:before{content:\"2444\";}}\n@media (min-device-width:2445px){#S:before{content:\"2445\";}}\n@media (min-device-width:2446px){#S:before{content:\"2446\";}}\n@media (min-device-width:2447px){#S:before{content:\"2447\";}}\n@media (min-device-width:2448px){#S:before{content:\"2448\";}}\n@media (min-device-width:2449px){#S:before{content:\"2449\";}}\n@media (min-device-width:2450px){#S:before{content:\"2450\";}}\n@media (min-device-width:2451px){#S:before{content:\"2451\";}}\n@media (min-device-width:2452px){#S:before{content:\"2452\";}}\n@media (min-device-width:2453px){#S:before{content:\"2453\";}}\n@media (min-device-width:2454px){#S:before{content:\"2454\";}}\n@media (min-device-width:2455px){#S:before{content:\"2455\";}}\n@media (min-device-width:2456px){#S:before{content:\"2456\";}}\n@media (min-device-width:2457px){#S:before{content:\"2457\";}}\n@media (min-device-width:2458px){#S:before{content:\"2458\";}}\n@media (min-device-width:2459px){#S:before{content:\"2459\";}}\n@media (min-device-width:2460px){#S:before{content:\"2460\";}}\n@media (min-device-width:2461px){#S:before{content:\"2461\";}}\n@media (min-device-width:2462px){#S:before{content:\"2462\";}}\n@media (min-device-width:2463px){#S:before{content:\"2463\";}}\n@media (min-device-width:2464px){#S:before{content:\"2464\";}}\n@media (min-device-width:2465px){#S:before{content:\"2465\";}}\n@media (min-device-width:2466px){#S:before{content:\"2466\";}}\n@media (min-device-width:2467px){#S:before{content:\"2467\";}}\n@media (min-device-width:2468px){#S:before{content:\"2468\";}}\n@media (min-device-width:2469px){#S:before{content:\"2469\";}}\n@media (min-device-width:2470px){#S:before{content:\"2470\";}}\n@media (min-device-width:2471px){#S:before{content:\"2471\";}}\n@media (min-device-width:2472px){#S:before{content:\"2472\";}}\n@media (min-device-width:2473px){#S:before{content:\"2473\";}}\n@media (min-device-width:2474px){#S:before{content:\"2474\";}}\n@media (min-device-width:2475px){#S:before{content:\"2475\";}}\n@media (min-device-width:2476px){#S:before{content:\"2476\";}}\n@media (min-device-width:2477px){#S:before{content:\"2477\";}}\n@media (min-device-width:2478px){#S:before{content:\"2478\";}}\n@media (min-device-width:2479px){#S:before{content:\"2479\";}}\n@media (min-device-width:2480px){#S:before{content:\"2480\";}}\n@media (min-device-width:2481px){#S:before{content:\"2481\";}}\n@media (min-device-width:2482px){#S:before{content:\"2482\";}}\n@media (min-device-width:2483px){#S:before{content:\"2483\";}}\n@media (min-device-width:2484px){#S:before{content:\"2484\";}}\n@media (min-device-width:2485px){#S:before{content:\"2485\";}}\n@media (min-device-width:2486px){#S:before{content:\"2486\";}}\n@media (min-device-width:2487px){#S:before{content:\"2487\";}}\n@media (min-device-width:2488px){#S:before{content:\"2488\";}}\n@media (min-device-width:2489px){#S:before{content:\"2489\";}}\n@media (min-device-width:2490px){#S:before{content:\"2490\";}}\n@media (min-device-width:2491px){#S:before{content:\"2491\";}}\n@media (min-device-width:2492px){#S:before{content:\"2492\";}}\n@media (min-device-width:2493px){#S:before{content:\"2493\";}}\n@media (min-device-width:2494px){#S:before{content:\"2494\";}}\n@media (min-device-width:2495px){#S:before{content:\"2495\";}}\n@media (min-device-width:2496px){#S:before{content:\"2496\";}}\n@media (min-device-width:2497px){#S:before{content:\"2497\";}}\n@media (min-device-width:2498px){#S:before{content:\"2498\";}}\n@media (min-device-width:2499px){#S:before{content:\"2499\";}}\n@media (min-device-width:2500px){#S:before{content:\"2500\";}}\n@media (min-device-width:2501px){#S:before{content:\"2501\";}}\n@media (min-device-width:2502px){#S:before{content:\"2502\";}}\n@media (min-device-width:2503px){#S:before{content:\"2503\";}}\n@media (min-device-width:2504px){#S:before{content:\"2504\";}}\n@media (min-device-width:2505px){#S:before{content:\"2505\";}}\n@media (min-device-width:2506px){#S:before{content:\"2506\";}}\n@media (min-device-width:2507px){#S:before{content:\"2507\";}}\n@media (min-device-width:2508px){#S:before{content:\"2508\";}}\n@media (min-device-width:2509px){#S:before{content:\"2509\";}}\n@media (min-device-width:2510px){#S:before{content:\"2510\";}}\n@media (min-device-width:2511px){#S:before{content:\"2511\";}}\n@media (min-device-width:2512px){#S:before{content:\"2512\";}}\n@media (min-device-width:2513px){#S:before{content:\"2513\";}}\n@media (min-device-width:2514px){#S:before{content:\"2514\";}}\n@media (min-device-width:2515px){#S:before{content:\"2515\";}}\n@media (min-device-width:2516px){#S:before{content:\"2516\";}}\n@media (min-device-width:2517px){#S:before{content:\"2517\";}}\n@media (min-device-width:2518px){#S:before{content:\"2518\";}}\n@media (min-device-width:2519px){#S:before{content:\"2519\";}}\n@media (min-device-width:2520px){#S:before{content:\"2520\";}}\n@media (min-device-width:2521px){#S:before{content:\"2521\";}}\n@media (min-device-width:2522px){#S:before{content:\"2522\";}}\n@media (min-device-width:2523px){#S:before{content:\"2523\";}}\n@media (min-device-width:2524px){#S:before{content:\"2524\";}}\n@media (min-device-width:2525px){#S:before{content:\"2525\";}}\n@media (min-device-width:2526px){#S:before{content:\"2526\";}}\n@media (min-device-width:2527px){#S:before{content:\"2527\";}}\n@media (min-device-width:2528px){#S:before{content:\"2528\";}}\n@media (min-device-width:2529px){#S:before{content:\"2529\";}}\n@media (min-device-width:2530px){#S:before{content:\"2530\";}}\n@media (min-device-width:2531px){#S:before{content:\"2531\";}}\n@media (min-device-width:2532px){#S:before{content:\"2532\";}}\n@media (min-device-width:2533px){#S:before{content:\"2533\";}}\n@media (min-device-width:2534px){#S:before{content:\"2534\";}}\n@media (min-device-width:2535px){#S:before{content:\"2535\";}}\n@media (min-device-width:2536px){#S:before{content:\"2536\";}}\n@media (min-device-width:2537px){#S:before{content:\"2537\";}}\n@media (min-device-width:2538px){#S:before{content:\"2538\";}}\n@media (min-device-width:2539px){#S:before{content:\"2539\";}}\n@media (min-device-width:2540px){#S:before{content:\"2540\";}}\n@media (min-device-width:2541px){#S:before{content:\"2541\";}}\n@media (min-device-width:2542px){#S:before{content:\"2542\";}}\n@media (min-device-width:2543px){#S:before{content:\"2543\";}}\n@media (min-device-width:2544px){#S:before{content:\"2544\";}}\n@media (min-device-width:2545px){#S:before{content:\"2545\";}}\n@media (min-device-width:2546px){#S:before{content:\"2546\";}}\n@media (min-device-width:2547px){#S:before{content:\"2547\";}}\n@media (min-device-width:2548px){#S:before{content:\"2548\";}}\n@media (min-device-width:2549px){#S:before{content:\"2549\";}}\n@media (min-device-width:2550px){#S:before{content:\"2550\";}}\n@media (min-device-width:2551px){#S:before{content:\"2551\";}}\n@media (min-device-width:2552px){#S:before{content:\"2552\";}}\n@media (min-device-width:2553px){#S:before{content:\"2553\";}}\n@media (min-device-width:2554px){#S:before{content:\"2554\";}}\n@media (min-device-width:2555px){#S:before{content:\"2555\";}}\n@media (min-device-width:2556px){#S:before{content:\"2556\";}}\n@media (min-device-width:2557px){#S:before{content:\"2557\";}}\n@media (min-device-width:2558px){#S:before{content:\"2558\";}}\n@media (min-device-width:2559px){#S:before{content:\"2559\";}}\n@media (min-device-width:2560px){#S:before{content:\"2560\";}}\n@media (min-device-width:2561px){#S:before{content:\"\";}} /* upper */\n\n@media (min-device-height:399px){#S:after{content:\"\";}} /* lower */\n@media (min-device-height:400px){#S:after{content:\" x 400\";}}\n@media (min-device-height:401px){#S:after{content:\" x 401\";}}\n@media (min-device-height:402px){#S:after{content:\" x 402\";}}\n@media (min-device-height:403px){#S:after{content:\" x 403\";}}\n@media (min-device-height:404px){#S:after{content:\" x 404\";}}\n@media (min-device-height:405px){#S:after{content:\" x 405\";}}\n@media (min-device-height:406px){#S:after{content:\" x 406\";}}\n@media (min-device-height:407px){#S:after{content:\" x 407\";}}\n@media (min-device-height:408px){#S:after{content:\" x 408\";}}\n@media (min-device-height:409px){#S:after{content:\" x 409\";}}\n@media (min-device-height:410px){#S:after{content:\" x 410\";}}\n@media (min-device-height:411px){#S:after{content:\" x 411\";}}\n@media (min-device-height:412px){#S:after{content:\" x 412\";}}\n@media (min-device-height:413px){#S:after{content:\" x 413\";}}\n@media (min-device-height:414px){#S:after{content:\" x 414\";}}\n@media (min-device-height:415px){#S:after{content:\" x 415\";}}\n@media (min-device-height:416px){#S:after{content:\" x 416\";}}\n@media (min-device-height:417px){#S:after{content:\" x 417\";}}\n@media (min-device-height:418px){#S:after{content:\" x 418\";}}\n@media (min-device-height:419px){#S:after{content:\" x 419\";}}\n@media (min-device-height:420px){#S:after{content:\" x 420\";}}\n@media (min-device-height:421px){#S:after{content:\" x 421\";}}\n@media (min-device-height:422px){#S:after{content:\" x 422\";}}\n@media (min-device-height:423px){#S:after{content:\" x 423\";}}\n@media (min-device-height:424px){#S:after{content:\" x 424\";}}\n@media (min-device-height:425px){#S:after{content:\" x 425\";}}\n@media (min-device-height:426px){#S:after{content:\" x 426\";}}\n@media (min-device-height:427px){#S:after{content:\" x 427\";}}\n@media (min-device-height:428px){#S:after{content:\" x 428\";}}\n@media (min-device-height:429px){#S:after{content:\" x 429\";}}\n@media (min-device-height:430px){#S:after{content:\" x 430\";}}\n@media (min-device-height:431px){#S:after{content:\" x 431\";}}\n@media (min-device-height:432px){#S:after{content:\" x 432\";}}\n@media (min-device-height:433px){#S:after{content:\" x 433\";}}\n@media (min-device-height:434px){#S:after{content:\" x 434\";}}\n@media (min-device-height:435px){#S:after{content:\" x 435\";}}\n@media (min-device-height:436px){#S:after{content:\" x 436\";}}\n@media (min-device-height:437px){#S:after{content:\" x 437\";}}\n@media (min-device-height:438px){#S:after{content:\" x 438\";}}\n@media (min-device-height:439px){#S:after{content:\" x 439\";}}\n@media (min-device-height:440px){#S:after{content:\" x 440\";}}\n@media (min-device-height:441px){#S:after{content:\" x 441\";}}\n@media (min-device-height:442px){#S:after{content:\" x 442\";}}\n@media (min-device-height:443px){#S:after{content:\" x 443\";}}\n@media (min-device-height:444px){#S:after{content:\" x 444\";}}\n@media (min-device-height:445px){#S:after{content:\" x 445\";}}\n@media (min-device-height:446px){#S:after{content:\" x 446\";}}\n@media (min-device-height:447px){#S:after{content:\" x 447\";}}\n@media (min-device-height:448px){#S:after{content:\" x 448\";}}\n@media (min-device-height:449px){#S:after{content:\" x 449\";}}\n@media (min-device-height:450px){#S:after{content:\" x 450\";}}\n@media (min-device-height:451px){#S:after{content:\" x 451\";}}\n@media (min-device-height:452px){#S:after{content:\" x 452\";}}\n@media (min-device-height:453px){#S:after{content:\" x 453\";}}\n@media (min-device-height:454px){#S:after{content:\" x 454\";}}\n@media (min-device-height:455px){#S:after{content:\" x 455\";}}\n@media (min-device-height:456px){#S:after{content:\" x 456\";}}\n@media (min-device-height:457px){#S:after{content:\" x 457\";}}\n@media (min-device-height:458px){#S:after{content:\" x 458\";}}\n@media (min-device-height:459px){#S:after{content:\" x 459\";}}\n@media (min-device-height:460px){#S:after{content:\" x 460\";}}\n@media (min-device-height:461px){#S:after{content:\" x 461\";}}\n@media (min-device-height:462px){#S:after{content:\" x 462\";}}\n@media (min-device-height:463px){#S:after{content:\" x 463\";}}\n@media (min-device-height:464px){#S:after{content:\" x 464\";}}\n@media (min-device-height:465px){#S:after{content:\" x 465\";}}\n@media (min-device-height:466px){#S:after{content:\" x 466\";}}\n@media (min-device-height:467px){#S:after{content:\" x 467\";}}\n@media (min-device-height:468px){#S:after{content:\" x 468\";}}\n@media (min-device-height:469px){#S:after{content:\" x 469\";}}\n@media (min-device-height:470px){#S:after{content:\" x 470\";}}\n@media (min-device-height:471px){#S:after{content:\" x 471\";}}\n@media (min-device-height:472px){#S:after{content:\" x 472\";}}\n@media (min-device-height:473px){#S:after{content:\" x 473\";}}\n@media (min-device-height:474px){#S:after{content:\" x 474\";}}\n@media (min-device-height:475px){#S:after{content:\" x 475\";}}\n@media (min-device-height:476px){#S:after{content:\" x 476\";}}\n@media (min-device-height:477px){#S:after{content:\" x 477\";}}\n@media (min-device-height:478px){#S:after{content:\" x 478\";}}\n@media (min-device-height:479px){#S:after{content:\" x 479\";}}\n@media (min-device-height:480px){#S:after{content:\" x 480\";}}\n@media (min-device-height:481px){#S:after{content:\" x 481\";}}\n@media (min-device-height:482px){#S:after{content:\" x 482\";}}\n@media (min-device-height:483px){#S:after{content:\" x 483\";}}\n@media (min-device-height:484px){#S:after{content:\" x 484\";}}\n@media (min-device-height:485px){#S:after{content:\" x 485\";}}\n@media (min-device-height:486px){#S:after{content:\" x 486\";}}\n@media (min-device-height:487px){#S:after{content:\" x 487\";}}\n@media (min-device-height:488px){#S:after{content:\" x 488\";}}\n@media (min-device-height:489px){#S:after{content:\" x 489\";}}\n@media (min-device-height:490px){#S:after{content:\" x 490\";}}\n@media (min-device-height:491px){#S:after{content:\" x 491\";}}\n@media (min-device-height:492px){#S:after{content:\" x 492\";}}\n@media (min-device-height:493px){#S:after{content:\" x 493\";}}\n@media (min-device-height:494px){#S:after{content:\" x 494\";}}\n@media (min-device-height:495px){#S:after{content:\" x 495\";}}\n@media (min-device-height:496px){#S:after{content:\" x 496\";}}\n@media (min-device-height:497px){#S:after{content:\" x 497\";}}\n@media (min-device-height:498px){#S:after{content:\" x 498\";}}\n@media (min-device-height:499px){#S:after{content:\" x 499\";}}\n@media (min-device-height:500px){#S:after{content:\" x 500\";}}\n@media (min-device-height:501px){#S:after{content:\" x 501\";}}\n@media (min-device-height:502px){#S:after{content:\" x 502\";}}\n@media (min-device-height:503px){#S:after{content:\" x 503\";}}\n@media (min-device-height:504px){#S:after{content:\" x 504\";}}\n@media (min-device-height:505px){#S:after{content:\" x 505\";}}\n@media (min-device-height:506px){#S:after{content:\" x 506\";}}\n@media (min-device-height:507px){#S:after{content:\" x 507\";}}\n@media (min-device-height:508px){#S:after{content:\" x 508\";}}\n@media (min-device-height:509px){#S:after{content:\" x 509\";}}\n@media (min-device-height:510px){#S:after{content:\" x 510\";}}\n@media (min-device-height:511px){#S:after{content:\" x 511\";}}\n@media (min-device-height:512px){#S:after{content:\" x 512\";}}\n@media (min-device-height:513px){#S:after{content:\" x 513\";}}\n@media (min-device-height:514px){#S:after{content:\" x 514\";}}\n@media (min-device-height:515px){#S:after{content:\" x 515\";}}\n@media (min-device-height:516px){#S:after{content:\" x 516\";}}\n@media (min-device-height:517px){#S:after{content:\" x 517\";}}\n@media (min-device-height:518px){#S:after{content:\" x 518\";}}\n@media (min-device-height:519px){#S:after{content:\" x 519\";}}\n@media (min-device-height:520px){#S:after{content:\" x 520\";}}\n@media (min-device-height:521px){#S:after{content:\" x 521\";}}\n@media (min-device-height:522px){#S:after{content:\" x 522\";}}\n@media (min-device-height:523px){#S:after{content:\" x 523\";}}\n@media (min-device-height:524px){#S:after{content:\" x 524\";}}\n@media (min-device-height:525px){#S:after{content:\" x 525\";}}\n@media (min-device-height:526px){#S:after{content:\" x 526\";}}\n@media (min-device-height:527px){#S:after{content:\" x 527\";}}\n@media (min-device-height:528px){#S:after{content:\" x 528\";}}\n@media (min-device-height:529px){#S:after{content:\" x 529\";}}\n@media (min-device-height:530px){#S:after{content:\" x 530\";}}\n@media (min-device-height:531px){#S:after{content:\" x 531\";}}\n@media (min-device-height:532px){#S:after{content:\" x 532\";}}\n@media (min-device-height:533px){#S:after{content:\" x 533\";}}\n@media (min-device-height:534px){#S:after{content:\" x 534\";}}\n@media (min-device-height:535px){#S:after{content:\" x 535\";}}\n@media (min-device-height:536px){#S:after{content:\" x 536\";}}\n@media (min-device-height:537px){#S:after{content:\" x 537\";}}\n@media (min-device-height:538px){#S:after{content:\" x 538\";}}\n@media (min-device-height:539px){#S:after{content:\" x 539\";}}\n@media (min-device-height:540px){#S:after{content:\" x 540\";}}\n@media (min-device-height:541px){#S:after{content:\" x 541\";}}\n@media (min-device-height:542px){#S:after{content:\" x 542\";}}\n@media (min-device-height:543px){#S:after{content:\" x 543\";}}\n@media (min-device-height:544px){#S:after{content:\" x 544\";}}\n@media (min-device-height:545px){#S:after{content:\" x 545\";}}\n@media (min-device-height:546px){#S:after{content:\" x 546\";}}\n@media (min-device-height:547px){#S:after{content:\" x 547\";}}\n@media (min-device-height:548px){#S:after{content:\" x 548\";}}\n@media (min-device-height:549px){#S:after{content:\" x 549\";}}\n@media (min-device-height:550px){#S:after{content:\" x 550\";}}\n@media (min-device-height:551px){#S:after{content:\" x 551\";}}\n@media (min-device-height:552px){#S:after{content:\" x 552\";}}\n@media (min-device-height:553px){#S:after{content:\" x 553\";}}\n@media (min-device-height:554px){#S:after{content:\" x 554\";}}\n@media (min-device-height:555px){#S:after{content:\" x 555\";}}\n@media (min-device-height:556px){#S:after{content:\" x 556\";}}\n@media (min-device-height:557px){#S:after{content:\" x 557\";}}\n@media (min-device-height:558px){#S:after{content:\" x 558\";}}\n@media (min-device-height:559px){#S:after{content:\" x 559\";}}\n@media (min-device-height:560px){#S:after{content:\" x 560\";}}\n@media (min-device-height:561px){#S:after{content:\" x 561\";}}\n@media (min-device-height:562px){#S:after{content:\" x 562\";}}\n@media (min-device-height:563px){#S:after{content:\" x 563\";}}\n@media (min-device-height:564px){#S:after{content:\" x 564\";}}\n@media (min-device-height:565px){#S:after{content:\" x 565\";}}\n@media (min-device-height:566px){#S:after{content:\" x 566\";}}\n@media (min-device-height:567px){#S:after{content:\" x 567\";}}\n@media (min-device-height:568px){#S:after{content:\" x 568\";}}\n@media (min-device-height:569px){#S:after{content:\" x 569\";}}\n@media (min-device-height:570px){#S:after{content:\" x 570\";}}\n@media (min-device-height:571px){#S:after{content:\" x 571\";}}\n@media (min-device-height:572px){#S:after{content:\" x 572\";}}\n@media (min-device-height:573px){#S:after{content:\" x 573\";}}\n@media (min-device-height:574px){#S:after{content:\" x 574\";}}\n@media (min-device-height:575px){#S:after{content:\" x 575\";}}\n@media (min-device-height:576px){#S:after{content:\" x 576\";}}\n@media (min-device-height:577px){#S:after{content:\" x 577\";}}\n@media (min-device-height:578px){#S:after{content:\" x 578\";}}\n@media (min-device-height:579px){#S:after{content:\" x 579\";}}\n@media (min-device-height:580px){#S:after{content:\" x 580\";}}\n@media (min-device-height:581px){#S:after{content:\" x 581\";}}\n@media (min-device-height:582px){#S:after{content:\" x 582\";}}\n@media (min-device-height:583px){#S:after{content:\" x 583\";}}\n@media (min-device-height:584px){#S:after{content:\" x 584\";}}\n@media (min-device-height:585px){#S:after{content:\" x 585\";}}\n@media (min-device-height:586px){#S:after{content:\" x 586\";}}\n@media (min-device-height:587px){#S:after{content:\" x 587\";}}\n@media (min-device-height:588px){#S:after{content:\" x 588\";}}\n@media (min-device-height:589px){#S:after{content:\" x 589\";}}\n@media (min-device-height:590px){#S:after{content:\" x 590\";}}\n@media (min-device-height:591px){#S:after{content:\" x 591\";}}\n@media (min-device-height:592px){#S:after{content:\" x 592\";}}\n@media (min-device-height:593px){#S:after{content:\" x 593\";}}\n@media (min-device-height:594px){#S:after{content:\" x 594\";}}\n@media (min-device-height:595px){#S:after{content:\" x 595\";}}\n@media (min-device-height:596px){#S:after{content:\" x 596\";}}\n@media (min-device-height:597px){#S:after{content:\" x 597\";}}\n@media (min-device-height:598px){#S:after{content:\" x 598\";}}\n@media (min-device-height:599px){#S:after{content:\" x 599\";}}\n@media (min-device-height:600px){#S:after{content:\" x 600\";}}\n@media (min-device-height:601px){#S:after{content:\" x 601\";}}\n@media (min-device-height:602px){#S:after{content:\" x 602\";}}\n@media (min-device-height:603px){#S:after{content:\" x 603\";}}\n@media (min-device-height:604px){#S:after{content:\" x 604\";}}\n@media (min-device-height:605px){#S:after{content:\" x 605\";}}\n@media (min-device-height:606px){#S:after{content:\" x 606\";}}\n@media (min-device-height:607px){#S:after{content:\" x 607\";}}\n@media (min-device-height:608px){#S:after{content:\" x 608\";}}\n@media (min-device-height:609px){#S:after{content:\" x 609\";}}\n@media (min-device-height:610px){#S:after{content:\" x 610\";}}\n@media (min-device-height:611px){#S:after{content:\" x 611\";}}\n@media (min-device-height:612px){#S:after{content:\" x 612\";}}\n@media (min-device-height:613px){#S:after{content:\" x 613\";}}\n@media (min-device-height:614px){#S:after{content:\" x 614\";}}\n@media (min-device-height:615px){#S:after{content:\" x 615\";}}\n@media (min-device-height:616px){#S:after{content:\" x 616\";}}\n@media (min-device-height:617px){#S:after{content:\" x 617\";}}\n@media (min-device-height:618px){#S:after{content:\" x 618\";}}\n@media (min-device-height:619px){#S:after{content:\" x 619\";}}\n@media (min-device-height:620px){#S:after{content:\" x 620\";}}\n@media (min-device-height:621px){#S:after{content:\" x 621\";}}\n@media (min-device-height:622px){#S:after{content:\" x 622\";}}\n@media (min-device-height:623px){#S:after{content:\" x 623\";}}\n@media (min-device-height:624px){#S:after{content:\" x 624\";}}\n@media (min-device-height:625px){#S:after{content:\" x 625\";}}\n@media (min-device-height:626px){#S:after{content:\" x 626\";}}\n@media (min-device-height:627px){#S:after{content:\" x 627\";}}\n@media (min-device-height:628px){#S:after{content:\" x 628\";}}\n@media (min-device-height:629px){#S:after{content:\" x 629\";}}\n@media (min-device-height:630px){#S:after{content:\" x 630\";}}\n@media (min-device-height:631px){#S:after{content:\" x 631\";}}\n@media (min-device-height:632px){#S:after{content:\" x 632\";}}\n@media (min-device-height:633px){#S:after{content:\" x 633\";}}\n@media (min-device-height:634px){#S:after{content:\" x 634\";}}\n@media (min-device-height:635px){#S:after{content:\" x 635\";}}\n@media (min-device-height:636px){#S:after{content:\" x 636\";}}\n@media (min-device-height:637px){#S:after{content:\" x 637\";}}\n@media (min-device-height:638px){#S:after{content:\" x 638\";}}\n@media (min-device-height:639px){#S:after{content:\" x 639\";}}\n@media (min-device-height:640px){#S:after{content:\" x 640\";}}\n@media (min-device-height:641px){#S:after{content:\" x 641\";}}\n@media (min-device-height:642px){#S:after{content:\" x 642\";}}\n@media (min-device-height:643px){#S:after{content:\" x 643\";}}\n@media (min-device-height:644px){#S:after{content:\" x 644\";}}\n@media (min-device-height:645px){#S:after{content:\" x 645\";}}\n@media (min-device-height:646px){#S:after{content:\" x 646\";}}\n@media (min-device-height:647px){#S:after{content:\" x 647\";}}\n@media (min-device-height:648px){#S:after{content:\" x 648\";}}\n@media (min-device-height:649px){#S:after{content:\" x 649\";}}\n@media (min-device-height:650px){#S:after{content:\" x 650\";}}\n@media (min-device-height:651px){#S:after{content:\" x 651\";}}\n@media (min-device-height:652px){#S:after{content:\" x 652\";}}\n@media (min-device-height:653px){#S:after{content:\" x 653\";}}\n@media (min-device-height:654px){#S:after{content:\" x 654\";}}\n@media (min-device-height:655px){#S:after{content:\" x 655\";}}\n@media (min-device-height:656px){#S:after{content:\" x 656\";}}\n@media (min-device-height:657px){#S:after{content:\" x 657\";}}\n@media (min-device-height:658px){#S:after{content:\" x 658\";}}\n@media (min-device-height:659px){#S:after{content:\" x 659\";}}\n@media (min-device-height:660px){#S:after{content:\" x 660\";}}\n@media (min-device-height:661px){#S:after{content:\" x 661\";}}\n@media (min-device-height:662px){#S:after{content:\" x 662\";}}\n@media (min-device-height:663px){#S:after{content:\" x 663\";}}\n@media (min-device-height:664px){#S:after{content:\" x 664\";}}\n@media (min-device-height:665px){#S:after{content:\" x 665\";}}\n@media (min-device-height:666px){#S:after{content:\" x 666\";}}\n@media (min-device-height:667px){#S:after{content:\" x 667\";}}\n@media (min-device-height:668px){#S:after{content:\" x 668\";}}\n@media (min-device-height:669px){#S:after{content:\" x 669\";}}\n@media (min-device-height:670px){#S:after{content:\" x 670\";}}\n@media (min-device-height:671px){#S:after{content:\" x 671\";}}\n@media (min-device-height:672px){#S:after{content:\" x 672\";}}\n@media (min-device-height:673px){#S:after{content:\" x 673\";}}\n@media (min-device-height:674px){#S:after{content:\" x 674\";}}\n@media (min-device-height:675px){#S:after{content:\" x 675\";}}\n@media (min-device-height:676px){#S:after{content:\" x 676\";}}\n@media (min-device-height:677px){#S:after{content:\" x 677\";}}\n@media (min-device-height:678px){#S:after{content:\" x 678\";}}\n@media (min-device-height:679px){#S:after{content:\" x 679\";}}\n@media (min-device-height:680px){#S:after{content:\" x 680\";}}\n@media (min-device-height:681px){#S:after{content:\" x 681\";}}\n@media (min-device-height:682px){#S:after{content:\" x 682\";}}\n@media (min-device-height:683px){#S:after{content:\" x 683\";}}\n@media (min-device-height:684px){#S:after{content:\" x 684\";}}\n@media (min-device-height:685px){#S:after{content:\" x 685\";}}\n@media (min-device-height:686px){#S:after{content:\" x 686\";}}\n@media (min-device-height:687px){#S:after{content:\" x 687\";}}\n@media (min-device-height:688px){#S:after{content:\" x 688\";}}\n@media (min-device-height:689px){#S:after{content:\" x 689\";}}\n@media (min-device-height:690px){#S:after{content:\" x 690\";}}\n@media (min-device-height:691px){#S:after{content:\" x 691\";}}\n@media (min-device-height:692px){#S:after{content:\" x 692\";}}\n@media (min-device-height:693px){#S:after{content:\" x 693\";}}\n@media (min-device-height:694px){#S:after{content:\" x 694\";}}\n@media (min-device-height:695px){#S:after{content:\" x 695\";}}\n@media (min-device-height:696px){#S:after{content:\" x 696\";}}\n@media (min-device-height:697px){#S:after{content:\" x 697\";}}\n@media (min-device-height:698px){#S:after{content:\" x 698\";}}\n@media (min-device-height:699px){#S:after{content:\" x 699\";}}\n@media (min-device-height:700px){#S:after{content:\" x 700\";}}\n@media (min-device-height:701px){#S:after{content:\" x 701\";}}\n@media (min-device-height:702px){#S:after{content:\" x 702\";}}\n@media (min-device-height:703px){#S:after{content:\" x 703\";}}\n@media (min-device-height:704px){#S:after{content:\" x 704\";}}\n@media (min-device-height:705px){#S:after{content:\" x 705\";}}\n@media (min-device-height:706px){#S:after{content:\" x 706\";}}\n@media (min-device-height:707px){#S:after{content:\" x 707\";}}\n@media (min-device-height:708px){#S:after{content:\" x 708\";}}\n@media (min-device-height:709px){#S:after{content:\" x 709\";}}\n@media (min-device-height:710px){#S:after{content:\" x 710\";}}\n@media (min-device-height:711px){#S:after{content:\" x 711\";}}\n@media (min-device-height:712px){#S:after{content:\" x 712\";}}\n@media (min-device-height:713px){#S:after{content:\" x 713\";}}\n@media (min-device-height:714px){#S:after{content:\" x 714\";}}\n@media (min-device-height:715px){#S:after{content:\" x 715\";}}\n@media (min-device-height:716px){#S:after{content:\" x 716\";}}\n@media (min-device-height:717px){#S:after{content:\" x 717\";}}\n@media (min-device-height:718px){#S:after{content:\" x 718\";}}\n@media (min-device-height:719px){#S:after{content:\" x 719\";}}\n@media (min-device-height:720px){#S:after{content:\" x 720\";}}\n@media (min-device-height:721px){#S:after{content:\" x 721\";}}\n@media (min-device-height:722px){#S:after{content:\" x 722\";}}\n@media (min-device-height:723px){#S:after{content:\" x 723\";}}\n@media (min-device-height:724px){#S:after{content:\" x 724\";}}\n@media (min-device-height:725px){#S:after{content:\" x 725\";}}\n@media (min-device-height:726px){#S:after{content:\" x 726\";}}\n@media (min-device-height:727px){#S:after{content:\" x 727\";}}\n@media (min-device-height:728px){#S:after{content:\" x 728\";}}\n@media (min-device-height:729px){#S:after{content:\" x 729\";}}\n@media (min-device-height:730px){#S:after{content:\" x 730\";}}\n@media (min-device-height:731px){#S:after{content:\" x 731\";}}\n@media (min-device-height:732px){#S:after{content:\" x 732\";}}\n@media (min-device-height:733px){#S:after{content:\" x 733\";}}\n@media (min-device-height:734px){#S:after{content:\" x 734\";}}\n@media (min-device-height:735px){#S:after{content:\" x 735\";}}\n@media (min-device-height:736px){#S:after{content:\" x 736\";}}\n@media (min-device-height:737px){#S:after{content:\" x 737\";}}\n@media (min-device-height:738px){#S:after{content:\" x 738\";}}\n@media (min-device-height:739px){#S:after{content:\" x 739\";}}\n@media (min-device-height:740px){#S:after{content:\" x 740\";}}\n@media (min-device-height:741px){#S:after{content:\" x 741\";}}\n@media (min-device-height:742px){#S:after{content:\" x 742\";}}\n@media (min-device-height:743px){#S:after{content:\" x 743\";}}\n@media (min-device-height:744px){#S:after{content:\" x 744\";}}\n@media (min-device-height:745px){#S:after{content:\" x 745\";}}\n@media (min-device-height:746px){#S:after{content:\" x 746\";}}\n@media (min-device-height:747px){#S:after{content:\" x 747\";}}\n@media (min-device-height:748px){#S:after{content:\" x 748\";}}\n@media (min-device-height:749px){#S:after{content:\" x 749\";}}\n@media (min-device-height:750px){#S:after{content:\" x 750\";}}\n@media (min-device-height:751px){#S:after{content:\" x 751\";}}\n@media (min-device-height:752px){#S:after{content:\" x 752\";}}\n@media (min-device-height:753px){#S:after{content:\" x 753\";}}\n@media (min-device-height:754px){#S:after{content:\" x 754\";}}\n@media (min-device-height:755px){#S:after{content:\" x 755\";}}\n@media (min-device-height:756px){#S:after{content:\" x 756\";}}\n@media (min-device-height:757px){#S:after{content:\" x 757\";}}\n@media (min-device-height:758px){#S:after{content:\" x 758\";}}\n@media (min-device-height:759px){#S:after{content:\" x 759\";}}\n@media (min-device-height:760px){#S:after{content:\" x 760\";}}\n@media (min-device-height:761px){#S:after{content:\" x 761\";}}\n@media (min-device-height:762px){#S:after{content:\" x 762\";}}\n@media (min-device-height:763px){#S:after{content:\" x 763\";}}\n@media (min-device-height:764px){#S:after{content:\" x 764\";}}\n@media (min-device-height:765px){#S:after{content:\" x 765\";}}\n@media (min-device-height:766px){#S:after{content:\" x 766\";}}\n@media (min-device-height:767px){#S:after{content:\" x 767\";}}\n@media (min-device-height:768px){#S:after{content:\" x 768\";}}\n@media (min-device-height:769px){#S:after{content:\" x 769\";}}\n@media (min-device-height:770px){#S:after{content:\" x 770\";}}\n@media (min-device-height:771px){#S:after{content:\" x 771\";}}\n@media (min-device-height:772px){#S:after{content:\" x 772\";}}\n@media (min-device-height:773px){#S:after{content:\" x 773\";}}\n@media (min-device-height:774px){#S:after{content:\" x 774\";}}\n@media (min-device-height:775px){#S:after{content:\" x 775\";}}\n@media (min-device-height:776px){#S:after{content:\" x 776\";}}\n@media (min-device-height:777px){#S:after{content:\" x 777\";}}\n@media (min-device-height:778px){#S:after{content:\" x 778\";}}\n@media (min-device-height:779px){#S:after{content:\" x 779\";}}\n@media (min-device-height:780px){#S:after{content:\" x 780\";}}\n@media (min-device-height:781px){#S:after{content:\" x 781\";}}\n@media (min-device-height:782px){#S:after{content:\" x 782\";}}\n@media (min-device-height:783px){#S:after{content:\" x 783\";}}\n@media (min-device-height:784px){#S:after{content:\" x 784\";}}\n@media (min-device-height:785px){#S:after{content:\" x 785\";}}\n@media (min-device-height:786px){#S:after{content:\" x 786\";}}\n@media (min-device-height:787px){#S:after{content:\" x 787\";}}\n@media (min-device-height:788px){#S:after{content:\" x 788\";}}\n@media (min-device-height:789px){#S:after{content:\" x 789\";}}\n@media (min-device-height:790px){#S:after{content:\" x 790\";}}\n@media (min-device-height:791px){#S:after{content:\" x 791\";}}\n@media (min-device-height:792px){#S:after{content:\" x 792\";}}\n@media (min-device-height:793px){#S:after{content:\" x 793\";}}\n@media (min-device-height:794px){#S:after{content:\" x 794\";}}\n@media (min-device-height:795px){#S:after{content:\" x 795\";}}\n@media (min-device-height:796px){#S:after{content:\" x 796\";}}\n@media (min-device-height:797px){#S:after{content:\" x 797\";}}\n@media (min-device-height:798px){#S:after{content:\" x 798\";}}\n@media (min-device-height:799px){#S:after{content:\" x 799\";}}\n@media (min-device-height:800px){#S:after{content:\" x 800\";}}\n@media (min-device-height:801px){#S:after{content:\" x 801\";}}\n@media (min-device-height:802px){#S:after{content:\" x 802\";}}\n@media (min-device-height:803px){#S:after{content:\" x 803\";}}\n@media (min-device-height:804px){#S:after{content:\" x 804\";}}\n@media (min-device-height:805px){#S:after{content:\" x 805\";}}\n@media (min-device-height:806px){#S:after{content:\" x 806\";}}\n@media (min-device-height:807px){#S:after{content:\" x 807\";}}\n@media (min-device-height:808px){#S:after{content:\" x 808\";}}\n@media (min-device-height:809px){#S:after{content:\" x 809\";}}\n@media (min-device-height:810px){#S:after{content:\" x 810\";}}\n@media (min-device-height:811px){#S:after{content:\" x 811\";}}\n@media (min-device-height:812px){#S:after{content:\" x 812\";}}\n@media (min-device-height:813px){#S:after{content:\" x 813\";}}\n@media (min-device-height:814px){#S:after{content:\" x 814\";}}\n@media (min-device-height:815px){#S:after{content:\" x 815\";}}\n@media (min-device-height:816px){#S:after{content:\" x 816\";}}\n@media (min-device-height:817px){#S:after{content:\" x 817\";}}\n@media (min-device-height:818px){#S:after{content:\" x 818\";}}\n@media (min-device-height:819px){#S:after{content:\" x 819\";}}\n@media (min-device-height:820px){#S:after{content:\" x 820\";}}\n@media (min-device-height:821px){#S:after{content:\" x 821\";}}\n@media (min-device-height:822px){#S:after{content:\" x 822\";}}\n@media (min-device-height:823px){#S:after{content:\" x 823\";}}\n@media (min-device-height:824px){#S:after{content:\" x 824\";}}\n@media (min-device-height:825px){#S:after{content:\" x 825\";}}\n@media (min-device-height:826px){#S:after{content:\" x 826\";}}\n@media (min-device-height:827px){#S:after{content:\" x 827\";}}\n@media (min-device-height:828px){#S:after{content:\" x 828\";}}\n@media (min-device-height:829px){#S:after{content:\" x 829\";}}\n@media (min-device-height:830px){#S:after{content:\" x 830\";}}\n@media (min-device-height:831px){#S:after{content:\" x 831\";}}\n@media (min-device-height:832px){#S:after{content:\" x 832\";}}\n@media (min-device-height:833px){#S:after{content:\" x 833\";}}\n@media (min-device-height:834px){#S:after{content:\" x 834\";}}\n@media (min-device-height:835px){#S:after{content:\" x 835\";}}\n@media (min-device-height:836px){#S:after{content:\" x 836\";}}\n@media (min-device-height:837px){#S:after{content:\" x 837\";}}\n@media (min-device-height:838px){#S:after{content:\" x 838\";}}\n@media (min-device-height:839px){#S:after{content:\" x 839\";}}\n@media (min-device-height:840px){#S:after{content:\" x 840\";}}\n@media (min-device-height:841px){#S:after{content:\" x 841\";}}\n@media (min-device-height:842px){#S:after{content:\" x 842\";}}\n@media (min-device-height:843px){#S:after{content:\" x 843\";}}\n@media (min-device-height:844px){#S:after{content:\" x 844\";}}\n@media (min-device-height:845px){#S:after{content:\" x 845\";}}\n@media (min-device-height:846px){#S:after{content:\" x 846\";}}\n@media (min-device-height:847px){#S:after{content:\" x 847\";}}\n@media (min-device-height:848px){#S:after{content:\" x 848\";}}\n@media (min-device-height:849px){#S:after{content:\" x 849\";}}\n@media (min-device-height:850px){#S:after{content:\" x 850\";}}\n@media (min-device-height:851px){#S:after{content:\" x 851\";}}\n@media (min-device-height:852px){#S:after{content:\" x 852\";}}\n@media (min-device-height:853px){#S:after{content:\" x 853\";}}\n@media (min-device-height:854px){#S:after{content:\" x 854\";}}\n@media (min-device-height:855px){#S:after{content:\" x 855\";}}\n@media (min-device-height:856px){#S:after{content:\" x 856\";}}\n@media (min-device-height:857px){#S:after{content:\" x 857\";}}\n@media (min-device-height:858px){#S:after{content:\" x 858\";}}\n@media (min-device-height:859px){#S:after{content:\" x 859\";}}\n@media (min-device-height:860px){#S:after{content:\" x 860\";}}\n@media (min-device-height:861px){#S:after{content:\" x 861\";}}\n@media (min-device-height:862px){#S:after{content:\" x 862\";}}\n@media (min-device-height:863px){#S:after{content:\" x 863\";}}\n@media (min-device-height:864px){#S:after{content:\" x 864\";}}\n@media (min-device-height:865px){#S:after{content:\" x 865\";}}\n@media (min-device-height:866px){#S:after{content:\" x 866\";}}\n@media (min-device-height:867px){#S:after{content:\" x 867\";}}\n@media (min-device-height:868px){#S:after{content:\" x 868\";}}\n@media (min-device-height:869px){#S:after{content:\" x 869\";}}\n@media (min-device-height:870px){#S:after{content:\" x 870\";}}\n@media (min-device-height:871px){#S:after{content:\" x 871\";}}\n@media (min-device-height:872px){#S:after{content:\" x 872\";}}\n@media (min-device-height:873px){#S:after{content:\" x 873\";}}\n@media (min-device-height:874px){#S:after{content:\" x 874\";}}\n@media (min-device-height:875px){#S:after{content:\" x 875\";}}\n@media (min-device-height:876px){#S:after{content:\" x 876\";}}\n@media (min-device-height:877px){#S:after{content:\" x 877\";}}\n@media (min-device-height:878px){#S:after{content:\" x 878\";}}\n@media (min-device-height:879px){#S:after{content:\" x 879\";}}\n@media (min-device-height:880px){#S:after{content:\" x 880\";}}\n@media (min-device-height:881px){#S:after{content:\" x 881\";}}\n@media (min-device-height:882px){#S:after{content:\" x 882\";}}\n@media (min-device-height:883px){#S:after{content:\" x 883\";}}\n@media (min-device-height:884px){#S:after{content:\" x 884\";}}\n@media (min-device-height:885px){#S:after{content:\" x 885\";}}\n@media (min-device-height:886px){#S:after{content:\" x 886\";}}\n@media (min-device-height:887px){#S:after{content:\" x 887\";}}\n@media (min-device-height:888px){#S:after{content:\" x 888\";}}\n@media (min-device-height:889px){#S:after{content:\" x 889\";}}\n@media (min-device-height:890px){#S:after{content:\" x 890\";}}\n@media (min-device-height:891px){#S:after{content:\" x 891\";}}\n@media (min-device-height:892px){#S:after{content:\" x 892\";}}\n@media (min-device-height:893px){#S:after{content:\" x 893\";}}\n@media (min-device-height:894px){#S:after{content:\" x 894\";}}\n@media (min-device-height:895px){#S:after{content:\" x 895\";}}\n@media (min-device-height:896px){#S:after{content:\" x 896\";}}\n@media (min-device-height:897px){#S:after{content:\" x 897\";}}\n@media (min-device-height:898px){#S:after{content:\" x 898\";}}\n@media (min-device-height:899px){#S:after{content:\" x 899\";}}\n@media (min-device-height:900px){#S:after{content:\" x 900\";}}\n@media (min-device-height:901px){#S:after{content:\" x 901\";}}\n@media (min-device-height:902px){#S:after{content:\" x 902\";}}\n@media (min-device-height:903px){#S:after{content:\" x 903\";}}\n@media (min-device-height:904px){#S:after{content:\" x 904\";}}\n@media (min-device-height:905px){#S:after{content:\" x 905\";}}\n@media (min-device-height:906px){#S:after{content:\" x 906\";}}\n@media (min-device-height:907px){#S:after{content:\" x 907\";}}\n@media (min-device-height:908px){#S:after{content:\" x 908\";}}\n@media (min-device-height:909px){#S:after{content:\" x 909\";}}\n@media (min-device-height:910px){#S:after{content:\" x 910\";}}\n@media (min-device-height:911px){#S:after{content:\" x 911\";}}\n@media (min-device-height:912px){#S:after{content:\" x 912\";}}\n@media (min-device-height:913px){#S:after{content:\" x 913\";}}\n@media (min-device-height:914px){#S:after{content:\" x 914\";}}\n@media (min-device-height:915px){#S:after{content:\" x 915\";}}\n@media (min-device-height:916px){#S:after{content:\" x 916\";}}\n@media (min-device-height:917px){#S:after{content:\" x 917\";}}\n@media (min-device-height:918px){#S:after{content:\" x 918\";}}\n@media (min-device-height:919px){#S:after{content:\" x 919\";}}\n@media (min-device-height:920px){#S:after{content:\" x 920\";}}\n@media (min-device-height:921px){#S:after{content:\" x 921\";}}\n@media (min-device-height:922px){#S:after{content:\" x 922\";}}\n@media (min-device-height:923px){#S:after{content:\" x 923\";}}\n@media (min-device-height:924px){#S:after{content:\" x 924\";}}\n@media (min-device-height:925px){#S:after{content:\" x 925\";}}\n@media (min-device-height:926px){#S:after{content:\" x 926\";}}\n@media (min-device-height:927px){#S:after{content:\" x 927\";}}\n@media (min-device-height:928px){#S:after{content:\" x 928\";}}\n@media (min-device-height:929px){#S:after{content:\" x 929\";}}\n@media (min-device-height:930px){#S:after{content:\" x 930\";}}\n@media (min-device-height:931px){#S:after{content:\" x 931\";}}\n@media (min-device-height:932px){#S:after{content:\" x 932\";}}\n@media (min-device-height:933px){#S:after{content:\" x 933\";}}\n@media (min-device-height:934px){#S:after{content:\" x 934\";}}\n@media (min-device-height:935px){#S:after{content:\" x 935\";}}\n@media (min-device-height:936px){#S:after{content:\" x 936\";}}\n@media (min-device-height:937px){#S:after{content:\" x 937\";}}\n@media (min-device-height:938px){#S:after{content:\" x 938\";}}\n@media (min-device-height:939px){#S:after{content:\" x 939\";}}\n@media (min-device-height:940px){#S:after{content:\" x 940\";}}\n@media (min-device-height:941px){#S:after{content:\" x 941\";}}\n@media (min-device-height:942px){#S:after{content:\" x 942\";}}\n@media (min-device-height:943px){#S:after{content:\" x 943\";}}\n@media (min-device-height:944px){#S:after{content:\" x 944\";}}\n@media (min-device-height:945px){#S:after{content:\" x 945\";}}\n@media (min-device-height:946px){#S:after{content:\" x 946\";}}\n@media (min-device-height:947px){#S:after{content:\" x 947\";}}\n@media (min-device-height:948px){#S:after{content:\" x 948\";}}\n@media (min-device-height:949px){#S:after{content:\" x 949\";}}\n@media (min-device-height:950px){#S:after{content:\" x 950\";}}\n@media (min-device-height:951px){#S:after{content:\" x 951\";}}\n@media (min-device-height:952px){#S:after{content:\" x 952\";}}\n@media (min-device-height:953px){#S:after{content:\" x 953\";}}\n@media (min-device-height:954px){#S:after{content:\" x 954\";}}\n@media (min-device-height:955px){#S:after{content:\" x 955\";}}\n@media (min-device-height:956px){#S:after{content:\" x 956\";}}\n@media (min-device-height:957px){#S:after{content:\" x 957\";}}\n@media (min-device-height:958px){#S:after{content:\" x 958\";}}\n@media (min-device-height:959px){#S:after{content:\" x 959\";}}\n@media (min-device-height:960px){#S:after{content:\" x 960\";}}\n@media (min-device-height:961px){#S:after{content:\" x 961\";}}\n@media (min-device-height:962px){#S:after{content:\" x 962\";}}\n@media (min-device-height:963px){#S:after{content:\" x 963\";}}\n@media (min-device-height:964px){#S:after{content:\" x 964\";}}\n@media (min-device-height:965px){#S:after{content:\" x 965\";}}\n@media (min-device-height:966px){#S:after{content:\" x 966\";}}\n@media (min-device-height:967px){#S:after{content:\" x 967\";}}\n@media (min-device-height:968px){#S:after{content:\" x 968\";}}\n@media (min-device-height:969px){#S:after{content:\" x 969\";}}\n@media (min-device-height:970px){#S:after{content:\" x 970\";}}\n@media (min-device-height:971px){#S:after{content:\" x 971\";}}\n@media (min-device-height:972px){#S:after{content:\" x 972\";}}\n@media (min-device-height:973px){#S:after{content:\" x 973\";}}\n@media (min-device-height:974px){#S:after{content:\" x 974\";}}\n@media (min-device-height:975px){#S:after{content:\" x 975\";}}\n@media (min-device-height:976px){#S:after{content:\" x 976\";}}\n@media (min-device-height:977px){#S:after{content:\" x 977\";}}\n@media (min-device-height:978px){#S:after{content:\" x 978\";}}\n@media (min-device-height:979px){#S:after{content:\" x 979\";}}\n@media (min-device-height:980px){#S:after{content:\" x 980\";}}\n@media (min-device-height:981px){#S:after{content:\" x 981\";}}\n@media (min-device-height:982px){#S:after{content:\" x 982\";}}\n@media (min-device-height:983px){#S:after{content:\" x 983\";}}\n@media (min-device-height:984px){#S:after{content:\" x 984\";}}\n@media (min-device-height:985px){#S:after{content:\" x 985\";}}\n@media (min-device-height:986px){#S:after{content:\" x 986\";}}\n@media (min-device-height:987px){#S:after{content:\" x 987\";}}\n@media (min-device-height:988px){#S:after{content:\" x 988\";}}\n@media (min-device-height:989px){#S:after{content:\" x 989\";}}\n@media (min-device-height:990px){#S:after{content:\" x 990\";}}\n@media (min-device-height:991px){#S:after{content:\" x 991\";}}\n@media (min-device-height:992px){#S:after{content:\" x 992\";}}\n@media (min-device-height:993px){#S:after{content:\" x 993\";}}\n@media (min-device-height:994px){#S:after{content:\" x 994\";}}\n@media (min-device-height:995px){#S:after{content:\" x 995\";}}\n@media (min-device-height:996px){#S:after{content:\" x 996\";}}\n@media (min-device-height:997px){#S:after{content:\" x 997\";}}\n@media (min-device-height:998px){#S:after{content:\" x 998\";}}\n@media (min-device-height:999px){#S:after{content:\" x 999\";}}\n@media (min-device-height:1000px){#S:after{content:\" x 1000\";}}\n@media (min-device-height:1001px){#S:after{content:\" x 1001\";}}\n@media (min-device-height:1002px){#S:after{content:\" x 1002\";}}\n@media (min-device-height:1003px){#S:after{content:\" x 1003\";}}\n@media (min-device-height:1004px){#S:after{content:\" x 1004\";}}\n@media (min-device-height:1005px){#S:after{content:\" x 1005\";}}\n@media (min-device-height:1006px){#S:after{content:\" x 1006\";}}\n@media (min-device-height:1007px){#S:after{content:\" x 1007\";}}\n@media (min-device-height:1008px){#S:after{content:\" x 1008\";}}\n@media (min-device-height:1009px){#S:after{content:\" x 1009\";}}\n@media (min-device-height:1010px){#S:after{content:\" x 1010\";}}\n@media (min-device-height:1011px){#S:after{content:\" x 1011\";}}\n@media (min-device-height:1012px){#S:after{content:\" x 1012\";}}\n@media (min-device-height:1013px){#S:after{content:\" x 1013\";}}\n@media (min-device-height:1014px){#S:after{content:\" x 1014\";}}\n@media (min-device-height:1015px){#S:after{content:\" x 1015\";}}\n@media (min-device-height:1016px){#S:after{content:\" x 1016\";}}\n@media (min-device-height:1017px){#S:after{content:\" x 1017\";}}\n@media (min-device-height:1018px){#S:after{content:\" x 1018\";}}\n@media (min-device-height:1019px){#S:after{content:\" x 1019\";}}\n@media (min-device-height:1020px){#S:after{content:\" x 1020\";}}\n@media (min-device-height:1021px){#S:after{content:\" x 1021\";}}\n@media (min-device-height:1022px){#S:after{content:\" x 1022\";}}\n@media (min-device-height:1023px){#S:after{content:\" x 1023\";}}\n@media (min-device-height:1024px){#S:after{content:\" x 1024\";}}\n@media (min-device-height:1025px){#S:after{content:\" x 1025\";}}\n@media (min-device-height:1026px){#S:after{content:\" x 1026\";}}\n@media (min-device-height:1027px){#S:after{content:\" x 1027\";}}\n@media (min-device-height:1028px){#S:after{content:\" x 1028\";}}\n@media (min-device-height:1029px){#S:after{content:\" x 1029\";}}\n@media (min-device-height:1030px){#S:after{content:\" x 1030\";}}\n@media (min-device-height:1031px){#S:after{content:\" x 1031\";}}\n@media (min-device-height:1032px){#S:after{content:\" x 1032\";}}\n@media (min-device-height:1033px){#S:after{content:\" x 1033\";}}\n@media (min-device-height:1034px){#S:after{content:\" x 1034\";}}\n@media (min-device-height:1035px){#S:after{content:\" x 1035\";}}\n@media (min-device-height:1036px){#S:after{content:\" x 1036\";}}\n@media (min-device-height:1037px){#S:after{content:\" x 1037\";}}\n@media (min-device-height:1038px){#S:after{content:\" x 1038\";}}\n@media (min-device-height:1039px){#S:after{content:\" x 1039\";}}\n@media (min-device-height:1040px){#S:after{content:\" x 1040\";}}\n@media (min-device-height:1041px){#S:after{content:\" x 1041\";}}\n@media (min-device-height:1042px){#S:after{content:\" x 1042\";}}\n@media (min-device-height:1043px){#S:after{content:\" x 1043\";}}\n@media (min-device-height:1044px){#S:after{content:\" x 1044\";}}\n@media (min-device-height:1045px){#S:after{content:\" x 1045\";}}\n@media (min-device-height:1046px){#S:after{content:\" x 1046\";}}\n@media (min-device-height:1047px){#S:after{content:\" x 1047\";}}\n@media (min-device-height:1048px){#S:after{content:\" x 1048\";}}\n@media (min-device-height:1049px){#S:after{content:\" x 1049\";}}\n@media (min-device-height:1050px){#S:after{content:\" x 1050\";}}\n@media (min-device-height:1051px){#S:after{content:\" x 1051\";}}\n@media (min-device-height:1052px){#S:after{content:\" x 1052\";}}\n@media (min-device-height:1053px){#S:after{content:\" x 1053\";}}\n@media (min-device-height:1054px){#S:after{content:\" x 1054\";}}\n@media (min-device-height:1055px){#S:after{content:\" x 1055\";}}\n@media (min-device-height:1056px){#S:after{content:\" x 1056\";}}\n@media (min-device-height:1057px){#S:after{content:\" x 1057\";}}\n@media (min-device-height:1058px){#S:after{content:\" x 1058\";}}\n@media (min-device-height:1059px){#S:after{content:\" x 1059\";}}\n@media (min-device-height:1060px){#S:after{content:\" x 1060\";}}\n@media (min-device-height:1061px){#S:after{content:\" x 1061\";}}\n@media (min-device-height:1062px){#S:after{content:\" x 1062\";}}\n@media (min-device-height:1063px){#S:after{content:\" x 1063\";}}\n@media (min-device-height:1064px){#S:after{content:\" x 1064\";}}\n@media (min-device-height:1065px){#S:after{content:\" x 1065\";}}\n@media (min-device-height:1066px){#S:after{content:\" x 1066\";}}\n@media (min-device-height:1067px){#S:after{content:\" x 1067\";}}\n@media (min-device-height:1068px){#S:after{content:\" x 1068\";}}\n@media (min-device-height:1069px){#S:after{content:\" x 1069\";}}\n@media (min-device-height:1070px){#S:after{content:\" x 1070\";}}\n@media (min-device-height:1071px){#S:after{content:\" x 1071\";}}\n@media (min-device-height:1072px){#S:after{content:\" x 1072\";}}\n@media (min-device-height:1073px){#S:after{content:\" x 1073\";}}\n@media (min-device-height:1074px){#S:after{content:\" x 1074\";}}\n@media (min-device-height:1075px){#S:after{content:\" x 1075\";}}\n@media (min-device-height:1076px){#S:after{content:\" x 1076\";}}\n@media (min-device-height:1077px){#S:after{content:\" x 1077\";}}\n@media (min-device-height:1078px){#S:after{content:\" x 1078\";}}\n@media (min-device-height:1079px){#S:after{content:\" x 1079\";}}\n@media (min-device-height:1080px){#S:after{content:\" x 1080\";}}\n@media (min-device-height:1081px){#S:after{content:\" x 1081\";}}\n@media (min-device-height:1082px){#S:after{content:\" x 1082\";}}\n@media (min-device-height:1083px){#S:after{content:\" x 1083\";}}\n@media (min-device-height:1084px){#S:after{content:\" x 1084\";}}\n@media (min-device-height:1085px){#S:after{content:\" x 1085\";}}\n@media (min-device-height:1086px){#S:after{content:\" x 1086\";}}\n@media (min-device-height:1087px){#S:after{content:\" x 1087\";}}\n@media (min-device-height:1088px){#S:after{content:\" x 1088\";}}\n@media (min-device-height:1089px){#S:after{content:\" x 1089\";}}\n@media (min-device-height:1090px){#S:after{content:\" x 1090\";}}\n@media (min-device-height:1091px){#S:after{content:\" x 1091\";}}\n@media (min-device-height:1092px){#S:after{content:\" x 1092\";}}\n@media (min-device-height:1093px){#S:after{content:\" x 1093\";}}\n@media (min-device-height:1094px){#S:after{content:\" x 1094\";}}\n@media (min-device-height:1095px){#S:after{content:\" x 1095\";}}\n@media (min-device-height:1096px){#S:after{content:\" x 1096\";}}\n@media (min-device-height:1097px){#S:after{content:\" x 1097\";}}\n@media (min-device-height:1098px){#S:after{content:\" x 1098\";}}\n@media (min-device-height:1099px){#S:after{content:\" x 1099\";}}\n@media (min-device-height:1100px){#S:after{content:\" x 1100\";}}\n@media (min-device-height:1101px){#S:after{content:\" x 1101\";}}\n@media (min-device-height:1102px){#S:after{content:\" x 1102\";}}\n@media (min-device-height:1103px){#S:after{content:\" x 1103\";}}\n@media (min-device-height:1104px){#S:after{content:\" x 1104\";}}\n@media (min-device-height:1105px){#S:after{content:\" x 1105\";}}\n@media (min-device-height:1106px){#S:after{content:\" x 1106\";}}\n@media (min-device-height:1107px){#S:after{content:\" x 1107\";}}\n@media (min-device-height:1108px){#S:after{content:\" x 1108\";}}\n@media (min-device-height:1109px){#S:after{content:\" x 1109\";}}\n@media (min-device-height:1110px){#S:after{content:\" x 1110\";}}\n@media (min-device-height:1111px){#S:after{content:\" x 1111\";}}\n@media (min-device-height:1112px){#S:after{content:\" x 1112\";}}\n@media (min-device-height:1113px){#S:after{content:\" x 1113\";}}\n@media (min-device-height:1114px){#S:after{content:\" x 1114\";}}\n@media (min-device-height:1115px){#S:after{content:\" x 1115\";}}\n@media (min-device-height:1116px){#S:after{content:\" x 1116\";}}\n@media (min-device-height:1117px){#S:after{content:\" x 1117\";}}\n@media (min-device-height:1118px){#S:after{content:\" x 1118\";}}\n@media (min-device-height:1119px){#S:after{content:\" x 1119\";}}\n@media (min-device-height:1120px){#S:after{content:\" x 1120\";}}\n@media (min-device-height:1121px){#S:after{content:\" x 1121\";}}\n@media (min-device-height:1122px){#S:after{content:\" x 1122\";}}\n@media (min-device-height:1123px){#S:after{content:\" x 1123\";}}\n@media (min-device-height:1124px){#S:after{content:\" x 1124\";}}\n@media (min-device-height:1125px){#S:after{content:\" x 1125\";}}\n@media (min-device-height:1126px){#S:after{content:\" x 1126\";}}\n@media (min-device-height:1127px){#S:after{content:\" x 1127\";}}\n@media (min-device-height:1128px){#S:after{content:\" x 1128\";}}\n@media (min-device-height:1129px){#S:after{content:\" x 1129\";}}\n@media (min-device-height:1130px){#S:after{content:\" x 1130\";}}\n@media (min-device-height:1131px){#S:after{content:\" x 1131\";}}\n@media (min-device-height:1132px){#S:after{content:\" x 1132\";}}\n@media (min-device-height:1133px){#S:after{content:\" x 1133\";}}\n@media (min-device-height:1134px){#S:after{content:\" x 1134\";}}\n@media (min-device-height:1135px){#S:after{content:\" x 1135\";}}\n@media (min-device-height:1136px){#S:after{content:\" x 1136\";}}\n@media (min-device-height:1137px){#S:after{content:\" x 1137\";}}\n@media (min-device-height:1138px){#S:after{content:\" x 1138\";}}\n@media (min-device-height:1139px){#S:after{content:\" x 1139\";}}\n@media (min-device-height:1140px){#S:after{content:\" x 1140\";}}\n@media (min-device-height:1141px){#S:after{content:\" x 1141\";}}\n@media (min-device-height:1142px){#S:after{content:\" x 1142\";}}\n@media (min-device-height:1143px){#S:after{content:\" x 1143\";}}\n@media (min-device-height:1144px){#S:after{content:\" x 1144\";}}\n@media (min-device-height:1145px){#S:after{content:\" x 1145\";}}\n@media (min-device-height:1146px){#S:after{content:\" x 1146\";}}\n@media (min-device-height:1147px){#S:after{content:\" x 1147\";}}\n@media (min-device-height:1148px){#S:after{content:\" x 1148\";}}\n@media (min-device-height:1149px){#S:after{content:\" x 1149\";}}\n@media (min-device-height:1150px){#S:after{content:\" x 1150\";}}\n@media (min-device-height:1151px){#S:after{content:\" x 1151\";}}\n@media (min-device-height:1152px){#S:after{content:\" x 1152\";}}\n@media (min-device-height:1153px){#S:after{content:\" x 1153\";}}\n@media (min-device-height:1154px){#S:after{content:\" x 1154\";}}\n@media (min-device-height:1155px){#S:after{content:\" x 1155\";}}\n@media (min-device-height:1156px){#S:after{content:\" x 1156\";}}\n@media (min-device-height:1157px){#S:after{content:\" x 1157\";}}\n@media (min-device-height:1158px){#S:after{content:\" x 1158\";}}\n@media (min-device-height:1159px){#S:after{content:\" x 1159\";}}\n@media (min-device-height:1160px){#S:after{content:\" x 1160\";}}\n@media (min-device-height:1161px){#S:after{content:\" x 1161\";}}\n@media (min-device-height:1162px){#S:after{content:\" x 1162\";}}\n@media (min-device-height:1163px){#S:after{content:\" x 1163\";}}\n@media (min-device-height:1164px){#S:after{content:\" x 1164\";}}\n@media (min-device-height:1165px){#S:after{content:\" x 1165\";}}\n@media (min-device-height:1166px){#S:after{content:\" x 1166\";}}\n@media (min-device-height:1167px){#S:after{content:\" x 1167\";}}\n@media (min-device-height:1168px){#S:after{content:\" x 1168\";}}\n@media (min-device-height:1169px){#S:after{content:\" x 1169\";}}\n@media (min-device-height:1170px){#S:after{content:\" x 1170\";}}\n@media (min-device-height:1171px){#S:after{content:\" x 1171\";}}\n@media (min-device-height:1172px){#S:after{content:\" x 1172\";}}\n@media (min-device-height:1173px){#S:after{content:\" x 1173\";}}\n@media (min-device-height:1174px){#S:after{content:\" x 1174\";}}\n@media (min-device-height:1175px){#S:after{content:\" x 1175\";}}\n@media (min-device-height:1176px){#S:after{content:\" x 1176\";}}\n@media (min-device-height:1177px){#S:after{content:\" x 1177\";}}\n@media (min-device-height:1178px){#S:after{content:\" x 1178\";}}\n@media (min-device-height:1179px){#S:after{content:\" x 1179\";}}\n@media (min-device-height:1180px){#S:after{content:\" x 1180\";}}\n@media (min-device-height:1181px){#S:after{content:\" x 1181\";}}\n@media (min-device-height:1182px){#S:after{content:\" x 1182\";}}\n@media (min-device-height:1183px){#S:after{content:\" x 1183\";}}\n@media (min-device-height:1184px){#S:after{content:\" x 1184\";}}\n@media (min-device-height:1185px){#S:after{content:\" x 1185\";}}\n@media (min-device-height:1186px){#S:after{content:\" x 1186\";}}\n@media (min-device-height:1187px){#S:after{content:\" x 1187\";}}\n@media (min-device-height:1188px){#S:after{content:\" x 1188\";}}\n@media (min-device-height:1189px){#S:after{content:\" x 1189\";}}\n@media (min-device-height:1190px){#S:after{content:\" x 1190\";}}\n@media (min-device-height:1191px){#S:after{content:\" x 1191\";}}\n@media (min-device-height:1192px){#S:after{content:\" x 1192\";}}\n@media (min-device-height:1193px){#S:after{content:\" x 1193\";}}\n@media (min-device-height:1194px){#S:after{content:\" x 1194\";}}\n@media (min-device-height:1195px){#S:after{content:\" x 1195\";}}\n@media (min-device-height:1196px){#S:after{content:\" x 1196\";}}\n@media (min-device-height:1197px){#S:after{content:\" x 1197\";}}\n@media (min-device-height:1198px){#S:after{content:\" x 1198\";}}\n@media (min-device-height:1199px){#S:after{content:\" x 1199\";}}\n@media (min-device-height:1200px){#S:after{content:\" x 1200\";}}\n@media (min-device-height:1201px){#S:after{content:\" x 1201\";}}\n@media (min-device-height:1202px){#S:after{content:\" x 1202\";}}\n@media (min-device-height:1203px){#S:after{content:\" x 1203\";}}\n@media (min-device-height:1204px){#S:after{content:\" x 1204\";}}\n@media (min-device-height:1205px){#S:after{content:\" x 1205\";}}\n@media (min-device-height:1206px){#S:after{content:\" x 1206\";}}\n@media (min-device-height:1207px){#S:after{content:\" x 1207\";}}\n@media (min-device-height:1208px){#S:after{content:\" x 1208\";}}\n@media (min-device-height:1209px){#S:after{content:\" x 1209\";}}\n@media (min-device-height:1210px){#S:after{content:\" x 1210\";}}\n@media (min-device-height:1211px){#S:after{content:\" x 1211\";}}\n@media (min-device-height:1212px){#S:after{content:\" x 1212\";}}\n@media (min-device-height:1213px){#S:after{content:\" x 1213\";}}\n@media (min-device-height:1214px){#S:after{content:\" x 1214\";}}\n@media (min-device-height:1215px){#S:after{content:\" x 1215\";}}\n@media (min-device-height:1216px){#S:after{content:\" x 1216\";}}\n@media (min-device-height:1217px){#S:after{content:\" x 1217\";}}\n@media (min-device-height:1218px){#S:after{content:\" x 1218\";}}\n@media (min-device-height:1219px){#S:after{content:\" x 1219\";}}\n@media (min-device-height:1220px){#S:after{content:\" x 1220\";}}\n@media (min-device-height:1221px){#S:after{content:\" x 1221\";}}\n@media (min-device-height:1222px){#S:after{content:\" x 1222\";}}\n@media (min-device-height:1223px){#S:after{content:\" x 1223\";}}\n@media (min-device-height:1224px){#S:after{content:\" x 1224\";}}\n@media (min-device-height:1225px){#S:after{content:\" x 1225\";}}\n@media (min-device-height:1226px){#S:after{content:\" x 1226\";}}\n@media (min-device-height:1227px){#S:after{content:\" x 1227\";}}\n@media (min-device-height:1228px){#S:after{content:\" x 1228\";}}\n@media (min-device-height:1229px){#S:after{content:\" x 1229\";}}\n@media (min-device-height:1230px){#S:after{content:\" x 1230\";}}\n@media (min-device-height:1231px){#S:after{content:\" x 1231\";}}\n@media (min-device-height:1232px){#S:after{content:\" x 1232\";}}\n@media (min-device-height:1233px){#S:after{content:\" x 1233\";}}\n@media (min-device-height:1234px){#S:after{content:\" x 1234\";}}\n@media (min-device-height:1235px){#S:after{content:\" x 1235\";}}\n@media (min-device-height:1236px){#S:after{content:\" x 1236\";}}\n@media (min-device-height:1237px){#S:after{content:\" x 1237\";}}\n@media (min-device-height:1238px){#S:after{content:\" x 1238\";}}\n@media (min-device-height:1239px){#S:after{content:\" x 1239\";}}\n@media (min-device-height:1240px){#S:after{content:\" x 1240\";}}\n@media (min-device-height:1241px){#S:after{content:\" x 1241\";}}\n@media (min-device-height:1242px){#S:after{content:\" x 1242\";}}\n@media (min-device-height:1243px){#S:after{content:\" x 1243\";}}\n@media (min-device-height:1244px){#S:after{content:\" x 1244\";}}\n@media (min-device-height:1245px){#S:after{content:\" x 1245\";}}\n@media (min-device-height:1246px){#S:after{content:\" x 1246\";}}\n@media (min-device-height:1247px){#S:after{content:\" x 1247\";}}\n@media (min-device-height:1248px){#S:after{content:\" x 1248\";}}\n@media (min-device-height:1249px){#S:after{content:\" x 1249\";}}\n@media (min-device-height:1250px){#S:after{content:\" x 1250\";}}\n@media (min-device-height:1251px){#S:after{content:\" x 1251\";}}\n@media (min-device-height:1252px){#S:after{content:\" x 1252\";}}\n@media (min-device-height:1253px){#S:after{content:\" x 1253\";}}\n@media (min-device-height:1254px){#S:after{content:\" x 1254\";}}\n@media (min-device-height:1255px){#S:after{content:\" x 1255\";}}\n@media (min-device-height:1256px){#S:after{content:\" x 1256\";}}\n@media (min-device-height:1257px){#S:after{content:\" x 1257\";}}\n@media (min-device-height:1258px){#S:after{content:\" x 1258\";}}\n@media (min-device-height:1259px){#S:after{content:\" x 1259\";}}\n@media (min-device-height:1260px){#S:after{content:\" x 1260\";}}\n@media (min-device-height:1261px){#S:after{content:\" x 1261\";}}\n@media (min-device-height:1262px){#S:after{content:\" x 1262\";}}\n@media (min-device-height:1263px){#S:after{content:\" x 1263\";}}\n@media (min-device-height:1264px){#S:after{content:\" x 1264\";}}\n@media (min-device-height:1265px){#S:after{content:\" x 1265\";}}\n@media (min-device-height:1266px){#S:after{content:\" x 1266\";}}\n@media (min-device-height:1267px){#S:after{content:\" x 1267\";}}\n@media (min-device-height:1268px){#S:after{content:\" x 1268\";}}\n@media (min-device-height:1269px){#S:after{content:\" x 1269\";}}\n@media (min-device-height:1270px){#S:after{content:\" x 1270\";}}\n@media (min-device-height:1271px){#S:after{content:\" x 1271\";}}\n@media (min-device-height:1272px){#S:after{content:\" x 1272\";}}\n@media (min-device-height:1273px){#S:after{content:\" x 1273\";}}\n@media (min-device-height:1274px){#S:after{content:\" x 1274\";}}\n@media (min-device-height:1275px){#S:after{content:\" x 1275\";}}\n@media (min-device-height:1276px){#S:after{content:\" x 1276\";}}\n@media (min-device-height:1277px){#S:after{content:\" x 1277\";}}\n@media (min-device-height:1278px){#S:after{content:\" x 1278\";}}\n@media (min-device-height:1279px){#S:after{content:\" x 1279\";}}\n@media (min-device-height:1280px){#S:after{content:\" x 1280\";}}\n@media (min-device-height:1281px){#S:after{content:\" x 1281\";}}\n@media (min-device-height:1282px){#S:after{content:\" x 1282\";}}\n@media (min-device-height:1283px){#S:after{content:\" x 1283\";}}\n@media (min-device-height:1284px){#S:after{content:\" x 1284\";}}\n@media (min-device-height:1285px){#S:after{content:\" x 1285\";}}\n@media (min-device-height:1286px){#S:after{content:\" x 1286\";}}\n@media (min-device-height:1287px){#S:after{content:\" x 1287\";}}\n@media (min-device-height:1288px){#S:after{content:\" x 1288\";}}\n@media (min-device-height:1289px){#S:after{content:\" x 1289\";}}\n@media (min-device-height:1290px){#S:after{content:\" x 1290\";}}\n@media (min-device-height:1291px){#S:after{content:\" x 1291\";}}\n@media (min-device-height:1292px){#S:after{content:\" x 1292\";}}\n@media (min-device-height:1293px){#S:after{content:\" x 1293\";}}\n@media (min-device-height:1294px){#S:after{content:\" x 1294\";}}\n@media (min-device-height:1295px){#S:after{content:\" x 1295\";}}\n@media (min-device-height:1296px){#S:after{content:\" x 1296\";}}\n@media (min-device-height:1297px){#S:after{content:\" x 1297\";}}\n@media (min-device-height:1298px){#S:after{content:\" x 1298\";}}\n@media (min-device-height:1299px){#S:after{content:\" x 1299\";}}\n@media (min-device-height:1300px){#S:after{content:\" x 1300\";}}\n@media (min-device-height:1301px){#S:after{content:\" x 1301\";}}\n@media (min-device-height:1302px){#S:after{content:\" x 1302\";}}\n@media (min-device-height:1303px){#S:after{content:\" x 1303\";}}\n@media (min-device-height:1304px){#S:after{content:\" x 1304\";}}\n@media (min-device-height:1305px){#S:after{content:\" x 1305\";}}\n@media (min-device-height:1306px){#S:after{content:\" x 1306\";}}\n@media (min-device-height:1307px){#S:after{content:\" x 1307\";}}\n@media (min-device-height:1308px){#S:after{content:\" x 1308\";}}\n@media (min-device-height:1309px){#S:after{content:\" x 1309\";}}\n@media (min-device-height:1310px){#S:after{content:\" x 1310\";}}\n@media (min-device-height:1311px){#S:after{content:\" x 1311\";}}\n@media (min-device-height:1312px){#S:after{content:\" x 1312\";}}\n@media (min-device-height:1313px){#S:after{content:\" x 1313\";}}\n@media (min-device-height:1314px){#S:after{content:\" x 1314\";}}\n@media (min-device-height:1315px){#S:after{content:\" x 1315\";}}\n@media (min-device-height:1316px){#S:after{content:\" x 1316\";}}\n@media (min-device-height:1317px){#S:after{content:\" x 1317\";}}\n@media (min-device-height:1318px){#S:after{content:\" x 1318\";}}\n@media (min-device-height:1319px){#S:after{content:\" x 1319\";}}\n@media (min-device-height:1320px){#S:after{content:\" x 1320\";}}\n@media (min-device-height:1321px){#S:after{content:\" x 1321\";}}\n@media (min-device-height:1322px){#S:after{content:\" x 1322\";}}\n@media (min-device-height:1323px){#S:after{content:\" x 1323\";}}\n@media (min-device-height:1324px){#S:after{content:\" x 1324\";}}\n@media (min-device-height:1325px){#S:after{content:\" x 1325\";}}\n@media (min-device-height:1326px){#S:after{content:\" x 1326\";}}\n@media (min-device-height:1327px){#S:after{content:\" x 1327\";}}\n@media (min-device-height:1328px){#S:after{content:\" x 1328\";}}\n@media (min-device-height:1329px){#S:after{content:\" x 1329\";}}\n@media (min-device-height:1330px){#S:after{content:\" x 1330\";}}\n@media (min-device-height:1331px){#S:after{content:\" x 1331\";}}\n@media (min-device-height:1332px){#S:after{content:\" x 1332\";}}\n@media (min-device-height:1333px){#S:after{content:\" x 1333\";}}\n@media (min-device-height:1334px){#S:after{content:\" x 1334\";}}\n@media (min-device-height:1335px){#S:after{content:\" x 1335\";}}\n@media (min-device-height:1336px){#S:after{content:\" x 1336\";}}\n@media (min-device-height:1337px){#S:after{content:\" x 1337\";}}\n@media (min-device-height:1338px){#S:after{content:\" x 1338\";}}\n@media (min-device-height:1339px){#S:after{content:\" x 1339\";}}\n@media (min-device-height:1340px){#S:after{content:\" x 1340\";}}\n@media (min-device-height:1341px){#S:after{content:\" x 1341\";}}\n@media (min-device-height:1342px){#S:after{content:\" x 1342\";}}\n@media (min-device-height:1343px){#S:after{content:\" x 1343\";}}\n@media (min-device-height:1344px){#S:after{content:\" x 1344\";}}\n@media (min-device-height:1345px){#S:after{content:\" x 1345\";}}\n@media (min-device-height:1346px){#S:after{content:\" x 1346\";}}\n@media (min-device-height:1347px){#S:after{content:\" x 1347\";}}\n@media (min-device-height:1348px){#S:after{content:\" x 1348\";}}\n@media (min-device-height:1349px){#S:after{content:\" x 1349\";}}\n@media (min-device-height:1350px){#S:after{content:\" x 1350\";}}\n@media (min-device-height:1351px){#S:after{content:\" x 1351\";}}\n@media (min-device-height:1352px){#S:after{content:\" x 1352\";}}\n@media (min-device-height:1353px){#S:after{content:\" x 1353\";}}\n@media (min-device-height:1354px){#S:after{content:\" x 1354\";}}\n@media (min-device-height:1355px){#S:after{content:\" x 1355\";}}\n@media (min-device-height:1356px){#S:after{content:\" x 1356\";}}\n@media (min-device-height:1357px){#S:after{content:\" x 1357\";}}\n@media (min-device-height:1358px){#S:after{content:\" x 1358\";}}\n@media (min-device-height:1359px){#S:after{content:\" x 1359\";}}\n@media (min-device-height:1360px){#S:after{content:\" x 1360\";}}\n@media (min-device-height:1361px){#S:after{content:\" x 1361\";}}\n@media (min-device-height:1362px){#S:after{content:\" x 1362\";}}\n@media (min-device-height:1363px){#S:after{content:\" x 1363\";}}\n@media (min-device-height:1364px){#S:after{content:\" x 1364\";}}\n@media (min-device-height:1365px){#S:after{content:\" x 1365\";}}\n@media (min-device-height:1366px){#S:after{content:\" x 1366\";}}\n@media (min-device-height:1367px){#S:after{content:\" x 1367\";}}\n@media (min-device-height:1368px){#S:after{content:\" x 1368\";}}\n@media (min-device-height:1369px){#S:after{content:\" x 1369\";}}\n@media (min-device-height:1370px){#S:after{content:\" x 1370\";}}\n@media (min-device-height:1371px){#S:after{content:\" x 1371\";}}\n@media (min-device-height:1372px){#S:after{content:\" x 1372\";}}\n@media (min-device-height:1373px){#S:after{content:\" x 1373\";}}\n@media (min-device-height:1374px){#S:after{content:\" x 1374\";}}\n@media (min-device-height:1375px){#S:after{content:\" x 1375\";}}\n@media (min-device-height:1376px){#S:after{content:\" x 1376\";}}\n@media (min-device-height:1377px){#S:after{content:\" x 1377\";}}\n@media (min-device-height:1378px){#S:after{content:\" x 1378\";}}\n@media (min-device-height:1379px){#S:after{content:\" x 1379\";}}\n@media (min-device-height:1380px){#S:after{content:\" x 1380\";}}\n@media (min-device-height:1381px){#S:after{content:\" x 1381\";}}\n@media (min-device-height:1382px){#S:after{content:\" x 1382\";}}\n@media (min-device-height:1383px){#S:after{content:\" x 1383\";}}\n@media (min-device-height:1384px){#S:after{content:\" x 1384\";}}\n@media (min-device-height:1385px){#S:after{content:\" x 1385\";}}\n@media (min-device-height:1386px){#S:after{content:\" x 1386\";}}\n@media (min-device-height:1387px){#S:after{content:\" x 1387\";}}\n@media (min-device-height:1388px){#S:after{content:\" x 1388\";}}\n@media (min-device-height:1389px){#S:after{content:\" x 1389\";}}\n@media (min-device-height:1390px){#S:after{content:\" x 1390\";}}\n@media (min-device-height:1391px){#S:after{content:\" x 1391\";}}\n@media (min-device-height:1392px){#S:after{content:\" x 1392\";}}\n@media (min-device-height:1393px){#S:after{content:\" x 1393\";}}\n@media (min-device-height:1394px){#S:after{content:\" x 1394\";}}\n@media (min-device-height:1395px){#S:after{content:\" x 1395\";}}\n@media (min-device-height:1396px){#S:after{content:\" x 1396\";}}\n@media (min-device-height:1397px){#S:after{content:\" x 1397\";}}\n@media (min-device-height:1398px){#S:after{content:\" x 1398\";}}\n@media (min-device-height:1399px){#S:after{content:\" x 1399\";}}\n@media (min-device-height:1400px){#S:after{content:\" x 1400\";}}\n@media (min-device-height:1401px){#S:after{content:\" x 1401\";}}\n@media (min-device-height:1402px){#S:after{content:\" x 1402\";}}\n@media (min-device-height:1403px){#S:after{content:\" x 1403\";}}\n@media (min-device-height:1404px){#S:after{content:\" x 1404\";}}\n@media (min-device-height:1405px){#S:after{content:\" x 1405\";}}\n@media (min-device-height:1406px){#S:after{content:\" x 1406\";}}\n@media (min-device-height:1407px){#S:after{content:\" x 1407\";}}\n@media (min-device-height:1408px){#S:after{content:\" x 1408\";}}\n@media (min-device-height:1409px){#S:after{content:\" x 1409\";}}\n@media (min-device-height:1410px){#S:after{content:\" x 1410\";}}\n@media (min-device-height:1411px){#S:after{content:\" x 1411\";}}\n@media (min-device-height:1412px){#S:after{content:\" x 1412\";}}\n@media (min-device-height:1413px){#S:after{content:\" x 1413\";}}\n@media (min-device-height:1414px){#S:after{content:\" x 1414\";}}\n@media (min-device-height:1415px){#S:after{content:\" x 1415\";}}\n@media (min-device-height:1416px){#S:after{content:\" x 1416\";}}\n@media (min-device-height:1417px){#S:after{content:\" x 1417\";}}\n@media (min-device-height:1418px){#S:after{content:\" x 1418\";}}\n@media (min-device-height:1419px){#S:after{content:\" x 1419\";}}\n@media (min-device-height:1420px){#S:after{content:\" x 1420\";}}\n@media (min-device-height:1421px){#S:after{content:\" x 1421\";}}\n@media (min-device-height:1422px){#S:after{content:\" x 1422\";}}\n@media (min-device-height:1423px){#S:after{content:\" x 1423\";}}\n@media (min-device-height:1424px){#S:after{content:\" x 1424\";}}\n@media (min-device-height:1425px){#S:after{content:\" x 1425\";}}\n@media (min-device-height:1426px){#S:after{content:\" x 1426\";}}\n@media (min-device-height:1427px){#S:after{content:\" x 1427\";}}\n@media (min-device-height:1428px){#S:after{content:\" x 1428\";}}\n@media (min-device-height:1429px){#S:after{content:\" x 1429\";}}\n@media (min-device-height:1430px){#S:after{content:\" x 1430\";}}\n@media (min-device-height:1431px){#S:after{content:\" x 1431\";}}\n@media (min-device-height:1432px){#S:after{content:\" x 1432\";}}\n@media (min-device-height:1433px){#S:after{content:\" x 1433\";}}\n@media (min-device-height:1434px){#S:after{content:\" x 1434\";}}\n@media (min-device-height:1435px){#S:after{content:\" x 1435\";}}\n@media (min-device-height:1436px){#S:after{content:\" x 1436\";}}\n@media (min-device-height:1437px){#S:after{content:\" x 1437\";}}\n@media (min-device-height:1438px){#S:after{content:\" x 1438\";}}\n@media (min-device-height:1439px){#S:after{content:\" x 1439\";}}\n@media (min-device-height:1440px){#S:after{content:\" x 1440\";}}\n@media (min-device-height:1441px){#S:after{content:\" x 1441\";}}\n@media (min-device-height:1442px){#S:after{content:\" x 1442\";}}\n@media (min-device-height:1443px){#S:after{content:\" x 1443\";}}\n@media (min-device-height:1444px){#S:after{content:\" x 1444\";}}\n@media (min-device-height:1445px){#S:after{content:\" x 1445\";}}\n@media (min-device-height:1446px){#S:after{content:\" x 1446\";}}\n@media (min-device-height:1447px){#S:after{content:\" x 1447\";}}\n@media (min-device-height:1448px){#S:after{content:\" x 1448\";}}\n@media (min-device-height:1449px){#S:after{content:\" x 1449\";}}\n@media (min-device-height:1450px){#S:after{content:\" x 1450\";}}\n@media (min-device-height:1451px){#S:after{content:\" x 1451\";}}\n@media (min-device-height:1452px){#S:after{content:\" x 1452\";}}\n@media (min-device-height:1453px){#S:after{content:\" x 1453\";}}\n@media (min-device-height:1454px){#S:after{content:\" x 1454\";}}\n@media (min-device-height:1455px){#S:after{content:\" x 1455\";}}\n@media (min-device-height:1456px){#S:after{content:\" x 1456\";}}\n@media (min-device-height:1457px){#S:after{content:\" x 1457\";}}\n@media (min-device-height:1458px){#S:after{content:\" x 1458\";}}\n@media (min-device-height:1459px){#S:after{content:\" x 1459\";}}\n@media (min-device-height:1460px){#S:after{content:\" x 1460\";}}\n@media (min-device-height:1461px){#S:after{content:\" x 1461\";}}\n@media (min-device-height:1462px){#S:after{content:\" x 1462\";}}\n@media (min-device-height:1463px){#S:after{content:\" x 1463\";}}\n@media (min-device-height:1464px){#S:after{content:\" x 1464\";}}\n@media (min-device-height:1465px){#S:after{content:\" x 1465\";}}\n@media (min-device-height:1466px){#S:after{content:\" x 1466\";}}\n@media (min-device-height:1467px){#S:after{content:\" x 1467\";}}\n@media (min-device-height:1468px){#S:after{content:\" x 1468\";}}\n@media (min-device-height:1469px){#S:after{content:\" x 1469\";}}\n@media (min-device-height:1470px){#S:after{content:\" x 1470\";}}\n@media (min-device-height:1471px){#S:after{content:\" x 1471\";}}\n@media (min-device-height:1472px){#S:after{content:\" x 1472\";}}\n@media (min-device-height:1473px){#S:after{content:\" x 1473\";}}\n@media (min-device-height:1474px){#S:after{content:\" x 1474\";}}\n@media (min-device-height:1475px){#S:after{content:\" x 1475\";}}\n@media (min-device-height:1476px){#S:after{content:\" x 1476\";}}\n@media (min-device-height:1477px){#S:after{content:\" x 1477\";}}\n@media (min-device-height:1478px){#S:after{content:\" x 1478\";}}\n@media (min-device-height:1479px){#S:after{content:\" x 1479\";}}\n@media (min-device-height:1480px){#S:after{content:\" x 1480\";}}\n@media (min-device-height:1481px){#S:after{content:\" x 1481\";}}\n@media (min-device-height:1482px){#S:after{content:\" x 1482\";}}\n@media (min-device-height:1483px){#S:after{content:\" x 1483\";}}\n@media (min-device-height:1484px){#S:after{content:\" x 1484\";}}\n@media (min-device-height:1485px){#S:after{content:\" x 1485\";}}\n@media (min-device-height:1486px){#S:after{content:\" x 1486\";}}\n@media (min-device-height:1487px){#S:after{content:\" x 1487\";}}\n@media (min-device-height:1488px){#S:after{content:\" x 1488\";}}\n@media (min-device-height:1489px){#S:after{content:\" x 1489\";}}\n@media (min-device-height:1490px){#S:after{content:\" x 1490\";}}\n@media (min-device-height:1491px){#S:after{content:\" x 1491\";}}\n@media (min-device-height:1492px){#S:after{content:\" x 1492\";}}\n@media (min-device-height:1493px){#S:after{content:\" x 1493\";}}\n@media (min-device-height:1494px){#S:after{content:\" x 1494\";}}\n@media (min-device-height:1495px){#S:after{content:\" x 1495\";}}\n@media (min-device-height:1496px){#S:after{content:\" x 1496\";}}\n@media (min-device-height:1497px){#S:after{content:\" x 1497\";}}\n@media (min-device-height:1498px){#S:after{content:\" x 1498\";}}\n@media (min-device-height:1499px){#S:after{content:\" x 1499\";}}\n@media (min-device-height:1500px){#S:after{content:\" x 1500\";}}\n@media (min-device-height:1501px){#S:after{content:\" x 1501\";}}\n@media (min-device-height:1502px){#S:after{content:\" x 1502\";}}\n@media (min-device-height:1503px){#S:after{content:\" x 1503\";}}\n@media (min-device-height:1504px){#S:after{content:\" x 1504\";}}\n@media (min-device-height:1505px){#S:after{content:\" x 1505\";}}\n@media (min-device-height:1506px){#S:after{content:\" x 1506\";}}\n@media (min-device-height:1507px){#S:after{content:\" x 1507\";}}\n@media (min-device-height:1508px){#S:after{content:\" x 1508\";}}\n@media (min-device-height:1509px){#S:after{content:\" x 1509\";}}\n@media (min-device-height:1510px){#S:after{content:\" x 1510\";}}\n@media (min-device-height:1511px){#S:after{content:\" x 1511\";}}\n@media (min-device-height:1512px){#S:after{content:\" x 1512\";}}\n@media (min-device-height:1513px){#S:after{content:\" x 1513\";}}\n@media (min-device-height:1514px){#S:after{content:\" x 1514\";}}\n@media (min-device-height:1515px){#S:after{content:\" x 1515\";}}\n@media (min-device-height:1516px){#S:after{content:\" x 1516\";}}\n@media (min-device-height:1517px){#S:after{content:\" x 1517\";}}\n@media (min-device-height:1518px){#S:after{content:\" x 1518\";}}\n@media (min-device-height:1519px){#S:after{content:\" x 1519\";}}\n@media (min-device-height:1520px){#S:after{content:\" x 1520\";}}\n@media (min-device-height:1521px){#S:after{content:\" x 1521\";}}\n@media (min-device-height:1522px){#S:after{content:\" x 1522\";}}\n@media (min-device-height:1523px){#S:after{content:\" x 1523\";}}\n@media (min-device-height:1524px){#S:after{content:\" x 1524\";}}\n@media (min-device-height:1525px){#S:after{content:\" x 1525\";}}\n@media (min-device-height:1526px){#S:after{content:\" x 1526\";}}\n@media (min-device-height:1527px){#S:after{content:\" x 1527\";}}\n@media (min-device-height:1528px){#S:after{content:\" x 1528\";}}\n@media (min-device-height:1529px){#S:after{content:\" x 1529\";}}\n@media (min-device-height:1530px){#S:after{content:\" x 1530\";}}\n@media (min-device-height:1531px){#S:after{content:\" x 1531\";}}\n@media (min-device-height:1532px){#S:after{content:\" x 1532\";}}\n@media (min-device-height:1533px){#S:after{content:\" x 1533\";}}\n@media (min-device-height:1534px){#S:after{content:\" x 1534\";}}\n@media (min-device-height:1535px){#S:after{content:\" x 1535\";}}\n@media (min-device-height:1536px){#S:after{content:\" x 1536\";}}\n@media (min-device-height:1537px){#S:after{content:\" x 1537\";}}\n@media (min-device-height:1538px){#S:after{content:\" x 1538\";}}\n@media (min-device-height:1539px){#S:after{content:\" x 1539\";}}\n@media (min-device-height:1540px){#S:after{content:\" x 1540\";}}\n@media (min-device-height:1541px){#S:after{content:\" x 1541\";}}\n@media (min-device-height:1542px){#S:after{content:\" x 1542\";}}\n@media (min-device-height:1543px){#S:after{content:\" x 1543\";}}\n@media (min-device-height:1544px){#S:after{content:\" x 1544\";}}\n@media (min-device-height:1545px){#S:after{content:\" x 1545\";}}\n@media (min-device-height:1546px){#S:after{content:\" x 1546\";}}\n@media (min-device-height:1547px){#S:after{content:\" x 1547\";}}\n@media (min-device-height:1548px){#S:after{content:\" x 1548\";}}\n@media (min-device-height:1549px){#S:after{content:\" x 1549\";}}\n@media (min-device-height:1550px){#S:after{content:\" x 1550\";}}\n@media (min-device-height:1551px){#S:after{content:\" x 1551\";}}\n@media (min-device-height:1552px){#S:after{content:\" x 1552\";}}\n@media (min-device-height:1553px){#S:after{content:\" x 1553\";}}\n@media (min-device-height:1554px){#S:after{content:\" x 1554\";}}\n@media (min-device-height:1555px){#S:after{content:\" x 1555\";}}\n@media (min-device-height:1556px){#S:after{content:\" x 1556\";}}\n@media (min-device-height:1557px){#S:after{content:\" x 1557\";}}\n@media (min-device-height:1558px){#S:after{content:\" x 1558\";}}\n@media (min-device-height:1559px){#S:after{content:\" x 1559\";}}\n@media (min-device-height:1560px){#S:after{content:\" x 1560\";}}\n@media (min-device-height:1561px){#S:after{content:\" x 1561\";}}\n@media (min-device-height:1562px){#S:after{content:\" x 1562\";}}\n@media (min-device-height:1563px){#S:after{content:\" x 1563\";}}\n@media (min-device-height:1564px){#S:after{content:\" x 1564\";}}\n@media (min-device-height:1565px){#S:after{content:\" x 1565\";}}\n@media (min-device-height:1566px){#S:after{content:\" x 1566\";}}\n@media (min-device-height:1567px){#S:after{content:\" x 1567\";}}\n@media (min-device-height:1568px){#S:after{content:\" x 1568\";}}\n@media (min-device-height:1569px){#S:after{content:\" x 1569\";}}\n@media (min-device-height:1570px){#S:after{content:\" x 1570\";}}\n@media (min-device-height:1571px){#S:after{content:\" x 1571\";}}\n@media (min-device-height:1572px){#S:after{content:\" x 1572\";}}\n@media (min-device-height:1573px){#S:after{content:\" x 1573\";}}\n@media (min-device-height:1574px){#S:after{content:\" x 1574\";}}\n@media (min-device-height:1575px){#S:after{content:\" x 1575\";}}\n@media (min-device-height:1576px){#S:after{content:\" x 1576\";}}\n@media (min-device-height:1577px){#S:after{content:\" x 1577\";}}\n@media (min-device-height:1578px){#S:after{content:\" x 1578\";}}\n@media (min-device-height:1579px){#S:after{content:\" x 1579\";}}\n@media (min-device-height:1580px){#S:after{content:\" x 1580\";}}\n@media (min-device-height:1581px){#S:after{content:\" x 1581\";}}\n@media (min-device-height:1582px){#S:after{content:\" x 1582\";}}\n@media (min-device-height:1583px){#S:after{content:\" x 1583\";}}\n@media (min-device-height:1584px){#S:after{content:\" x 1584\";}}\n@media (min-device-height:1585px){#S:after{content:\" x 1585\";}}\n@media (min-device-height:1586px){#S:after{content:\" x 1586\";}}\n@media (min-device-height:1587px){#S:after{content:\" x 1587\";}}\n@media (min-device-height:1588px){#S:after{content:\" x 1588\";}}\n@media (min-device-height:1589px){#S:after{content:\" x 1589\";}}\n@media (min-device-height:1590px){#S:after{content:\" x 1590\";}}\n@media (min-device-height:1591px){#S:after{content:\" x 1591\";}}\n@media (min-device-height:1592px){#S:after{content:\" x 1592\";}}\n@media (min-device-height:1593px){#S:after{content:\" x 1593\";}}\n@media (min-device-height:1594px){#S:after{content:\" x 1594\";}}\n@media (min-device-height:1595px){#S:after{content:\" x 1595\";}}\n@media (min-device-height:1596px){#S:after{content:\" x 1596\";}}\n@media (min-device-height:1597px){#S:after{content:\" x 1597\";}}\n@media (min-device-height:1598px){#S:after{content:\" x 1598\";}}\n@media (min-device-height:1599px){#S:after{content:\" x 1599\";}}\n@media (min-device-height:1600px){#S:after{content:\" x 1600\";}}\n@media (min-device-height:1601px){#S:after{content:\" x 1601\";}}\n@media (min-device-height:1602px){#S:after{content:\" x 1602\";}}\n@media (min-device-height:1603px){#S:after{content:\" x 1603\";}}\n@media (min-device-height:1604px){#S:after{content:\" x 1604\";}}\n@media (min-device-height:1605px){#S:after{content:\" x 1605\";}}\n@media (min-device-height:1606px){#S:after{content:\" x 1606\";}}\n@media (min-device-height:1607px){#S:after{content:\" x 1607\";}}\n@media (min-device-height:1608px){#S:after{content:\" x 1608\";}}\n@media (min-device-height:1609px){#S:after{content:\" x 1609\";}}\n@media (min-device-height:1610px){#S:after{content:\" x 1610\";}}\n@media (min-device-height:1611px){#S:after{content:\" x 1611\";}}\n@media (min-device-height:1612px){#S:after{content:\" x 1612\";}}\n@media (min-device-height:1613px){#S:after{content:\" x 1613\";}}\n@media (min-device-height:1614px){#S:after{content:\" x 1614\";}}\n@media (min-device-height:1615px){#S:after{content:\" x 1615\";}}\n@media (min-device-height:1616px){#S:after{content:\" x 1616\";}}\n@media (min-device-height:1617px){#S:after{content:\" x 1617\";}}\n@media (min-device-height:1618px){#S:after{content:\" x 1618\";}}\n@media (min-device-height:1619px){#S:after{content:\" x 1619\";}}\n@media (min-device-height:1620px){#S:after{content:\" x 1620\";}}\n@media (min-device-height:1621px){#S:after{content:\" x 1621\";}}\n@media (min-device-height:1622px){#S:after{content:\" x 1622\";}}\n@media (min-device-height:1623px){#S:after{content:\" x 1623\";}}\n@media (min-device-height:1624px){#S:after{content:\" x 1624\";}}\n@media (min-device-height:1625px){#S:after{content:\" x 1625\";}}\n@media (min-device-height:1626px){#S:after{content:\" x 1626\";}}\n@media (min-device-height:1627px){#S:after{content:\" x 1627\";}}\n@media (min-device-height:1628px){#S:after{content:\" x 1628\";}}\n@media (min-device-height:1629px){#S:after{content:\" x 1629\";}}\n@media (min-device-height:1630px){#S:after{content:\" x 1630\";}}\n@media (min-device-height:1631px){#S:after{content:\" x 1631\";}}\n@media (min-device-height:1632px){#S:after{content:\" x 1632\";}}\n@media (min-device-height:1633px){#S:after{content:\" x 1633\";}}\n@media (min-device-height:1634px){#S:after{content:\" x 1634\";}}\n@media (min-device-height:1635px){#S:after{content:\" x 1635\";}}\n@media (min-device-height:1636px){#S:after{content:\" x 1636\";}}\n@media (min-device-height:1637px){#S:after{content:\" x 1637\";}}\n@media (min-device-height:1638px){#S:after{content:\" x 1638\";}}\n@media (min-device-height:1639px){#S:after{content:\" x 1639\";}}\n@media (min-device-height:1640px){#S:after{content:\" x 1640\";}}\n@media (min-device-height:1641px){#S:after{content:\" x 1641\";}}\n@media (min-device-height:1642px){#S:after{content:\" x 1642\";}}\n@media (min-device-height:1643px){#S:after{content:\" x 1643\";}}\n@media (min-device-height:1644px){#S:after{content:\" x 1644\";}}\n@media (min-device-height:1645px){#S:after{content:\" x 1645\";}}\n@media (min-device-height:1646px){#S:after{content:\" x 1646\";}}\n@media (min-device-height:1647px){#S:after{content:\" x 1647\";}}\n@media (min-device-height:1648px){#S:after{content:\" x 1648\";}}\n@media (min-device-height:1649px){#S:after{content:\" x 1649\";}}\n@media (min-device-height:1650px){#S:after{content:\" x 1650\";}}\n@media (min-device-height:1651px){#S:after{content:\" x 1651\";}}\n@media (min-device-height:1652px){#S:after{content:\" x 1652\";}}\n@media (min-device-height:1653px){#S:after{content:\" x 1653\";}}\n@media (min-device-height:1654px){#S:after{content:\" x 1654\";}}\n@media (min-device-height:1655px){#S:after{content:\" x 1655\";}}\n@media (min-device-height:1656px){#S:after{content:\" x 1656\";}}\n@media (min-device-height:1657px){#S:after{content:\" x 1657\";}}\n@media (min-device-height:1658px){#S:after{content:\" x 1658\";}}\n@media (min-device-height:1659px){#S:after{content:\" x 1659\";}}\n@media (min-device-height:1660px){#S:after{content:\" x 1660\";}}\n@media (min-device-height:1661px){#S:after{content:\" x 1661\";}}\n@media (min-device-height:1662px){#S:after{content:\" x 1662\";}}\n@media (min-device-height:1663px){#S:after{content:\" x 1663\";}}\n@media (min-device-height:1664px){#S:after{content:\" x 1664\";}}\n@media (min-device-height:1665px){#S:after{content:\" x 1665\";}}\n@media (min-device-height:1666px){#S:after{content:\" x 1666\";}}\n@media (min-device-height:1667px){#S:after{content:\" x 1667\";}}\n@media (min-device-height:1668px){#S:after{content:\" x 1668\";}}\n@media (min-device-height:1669px){#S:after{content:\" x 1669\";}}\n@media (min-device-height:1670px){#S:after{content:\" x 1670\";}}\n@media (min-device-height:1671px){#S:after{content:\" x 1671\";}}\n@media (min-device-height:1672px){#S:after{content:\" x 1672\";}}\n@media (min-device-height:1673px){#S:after{content:\" x 1673\";}}\n@media (min-device-height:1674px){#S:after{content:\" x 1674\";}}\n@media (min-device-height:1675px){#S:after{content:\" x 1675\";}}\n@media (min-device-height:1676px){#S:after{content:\" x 1676\";}}\n@media (min-device-height:1677px){#S:after{content:\" x 1677\";}}\n@media (min-device-height:1678px){#S:after{content:\" x 1678\";}}\n@media (min-device-height:1679px){#S:after{content:\" x 1679\";}}\n@media (min-device-height:1680px){#S:after{content:\" x 1680\";}}\n@media (min-device-height:1681px){#S:after{content:\" x 1681\";}}\n@media (min-device-height:1682px){#S:after{content:\" x 1682\";}}\n@media (min-device-height:1683px){#S:after{content:\" x 1683\";}}\n@media (min-device-height:1684px){#S:after{content:\" x 1684\";}}\n@media (min-device-height:1685px){#S:after{content:\" x 1685\";}}\n@media (min-device-height:1686px){#S:after{content:\" x 1686\";}}\n@media (min-device-height:1687px){#S:after{content:\" x 1687\";}}\n@media (min-device-height:1688px){#S:after{content:\" x 1688\";}}\n@media (min-device-height:1689px){#S:after{content:\" x 1689\";}}\n@media (min-device-height:1690px){#S:after{content:\" x 1690\";}}\n@media (min-device-height:1691px){#S:after{content:\" x 1691\";}}\n@media (min-device-height:1692px){#S:after{content:\" x 1692\";}}\n@media (min-device-height:1693px){#S:after{content:\" x 1693\";}}\n@media (min-device-height:1694px){#S:after{content:\" x 1694\";}}\n@media (min-device-height:1695px){#S:after{content:\" x 1695\";}}\n@media (min-device-height:1696px){#S:after{content:\" x 1696\";}}\n@media (min-device-height:1697px){#S:after{content:\" x 1697\";}}\n@media (min-device-height:1698px){#S:after{content:\" x 1698\";}}\n@media (min-device-height:1699px){#S:after{content:\" x 1699\";}}\n@media (min-device-height:1700px){#S:after{content:\" x 1700\";}}\n@media (min-device-height:1701px){#S:after{content:\" x 1701\";}}\n@media (min-device-height:1702px){#S:after{content:\" x 1702\";}}\n@media (min-device-height:1703px){#S:after{content:\" x 1703\";}}\n@media (min-device-height:1704px){#S:after{content:\" x 1704\";}}\n@media (min-device-height:1705px){#S:after{content:\" x 1705\";}}\n@media (min-device-height:1706px){#S:after{content:\" x 1706\";}}\n@media (min-device-height:1707px){#S:after{content:\" x 1707\";}}\n@media (min-device-height:1708px){#S:after{content:\" x 1708\";}}\n@media (min-device-height:1709px){#S:after{content:\" x 1709\";}}\n@media (min-device-height:1710px){#S:after{content:\" x 1710\";}}\n@media (min-device-height:1711px){#S:after{content:\" x 1711\";}}\n@media (min-device-height:1712px){#S:after{content:\" x 1712\";}}\n@media (min-device-height:1713px){#S:after{content:\" x 1713\";}}\n@media (min-device-height:1714px){#S:after{content:\" x 1714\";}}\n@media (min-device-height:1715px){#S:after{content:\" x 1715\";}}\n@media (min-device-height:1716px){#S:after{content:\" x 1716\";}}\n@media (min-device-height:1717px){#S:after{content:\" x 1717\";}}\n@media (min-device-height:1718px){#S:after{content:\" x 1718\";}}\n@media (min-device-height:1719px){#S:after{content:\" x 1719\";}}\n@media (min-device-height:1720px){#S:after{content:\" x 1720\";}}\n@media (min-device-height:1721px){#S:after{content:\" x 1721\";}}\n@media (min-device-height:1722px){#S:after{content:\" x 1722\";}}\n@media (min-device-height:1723px){#S:after{content:\" x 1723\";}}\n@media (min-device-height:1724px){#S:after{content:\" x 1724\";}}\n@media (min-device-height:1725px){#S:after{content:\" x 1725\";}}\n@media (min-device-height:1726px){#S:after{content:\" x 1726\";}}\n@media (min-device-height:1727px){#S:after{content:\" x 1727\";}}\n@media (min-device-height:1728px){#S:after{content:\" x 1728\";}}\n@media (min-device-height:1729px){#S:after{content:\" x 1729\";}}\n@media (min-device-height:1730px){#S:after{content:\" x 1730\";}}\n@media (min-device-height:1731px){#S:after{content:\" x 1731\";}}\n@media (min-device-height:1732px){#S:after{content:\" x 1732\";}}\n@media (min-device-height:1733px){#S:after{content:\" x 1733\";}}\n@media (min-device-height:1734px){#S:after{content:\" x 1734\";}}\n@media (min-device-height:1735px){#S:after{content:\" x 1735\";}}\n@media (min-device-height:1736px){#S:after{content:\" x 1736\";}}\n@media (min-device-height:1737px){#S:after{content:\" x 1737\";}}\n@media (min-device-height:1738px){#S:after{content:\" x 1738\";}}\n@media (min-device-height:1739px){#S:after{content:\" x 1739\";}}\n@media (min-device-height:1740px){#S:after{content:\" x 1740\";}}\n@media (min-device-height:1741px){#S:after{content:\" x 1741\";}}\n@media (min-device-height:1742px){#S:after{content:\" x 1742\";}}\n@media (min-device-height:1743px){#S:after{content:\" x 1743\";}}\n@media (min-device-height:1744px){#S:after{content:\" x 1744\";}}\n@media (min-device-height:1745px){#S:after{content:\" x 1745\";}}\n@media (min-device-height:1746px){#S:after{content:\" x 1746\";}}\n@media (min-device-height:1747px){#S:after{content:\" x 1747\";}}\n@media (min-device-height:1748px){#S:after{content:\" x 1748\";}}\n@media (min-device-height:1749px){#S:after{content:\" x 1749\";}}\n@media (min-device-height:1750px){#S:after{content:\" x 1750\";}}\n@media (min-device-height:1751px){#S:after{content:\" x 1751\";}}\n@media (min-device-height:1752px){#S:after{content:\" x 1752\";}}\n@media (min-device-height:1753px){#S:after{content:\" x 1753\";}}\n@media (min-device-height:1754px){#S:after{content:\" x 1754\";}}\n@media (min-device-height:1755px){#S:after{content:\" x 1755\";}}\n@media (min-device-height:1756px){#S:after{content:\" x 1756\";}}\n@media (min-device-height:1757px){#S:after{content:\" x 1757\";}}\n@media (min-device-height:1758px){#S:after{content:\" x 1758\";}}\n@media (min-device-height:1759px){#S:after{content:\" x 1759\";}}\n@media (min-device-height:1760px){#S:after{content:\" x 1760\";}}\n@media (min-device-height:1761px){#S:after{content:\" x 1761\";}}\n@media (min-device-height:1762px){#S:after{content:\" x 1762\";}}\n@media (min-device-height:1763px){#S:after{content:\" x 1763\";}}\n@media (min-device-height:1764px){#S:after{content:\" x 1764\";}}\n@media (min-device-height:1765px){#S:after{content:\" x 1765\";}}\n@media (min-device-height:1766px){#S:after{content:\" x 1766\";}}\n@media (min-device-height:1767px){#S:after{content:\" x 1767\";}}\n@media (min-device-height:1768px){#S:after{content:\" x 1768\";}}\n@media (min-device-height:1769px){#S:after{content:\" x 1769\";}}\n@media (min-device-height:1770px){#S:after{content:\" x 1770\";}}\n@media (min-device-height:1771px){#S:after{content:\" x 1771\";}}\n@media (min-device-height:1772px){#S:after{content:\" x 1772\";}}\n@media (min-device-height:1773px){#S:after{content:\" x 1773\";}}\n@media (min-device-height:1774px){#S:after{content:\" x 1774\";}}\n@media (min-device-height:1775px){#S:after{content:\" x 1775\";}}\n@media (min-device-height:1776px){#S:after{content:\" x 1776\";}}\n@media (min-device-height:1777px){#S:after{content:\" x 1777\";}}\n@media (min-device-height:1778px){#S:after{content:\" x 1778\";}}\n@media (min-device-height:1779px){#S:after{content:\" x 1779\";}}\n@media (min-device-height:1780px){#S:after{content:\" x 1780\";}}\n@media (min-device-height:1781px){#S:after{content:\" x 1781\";}}\n@media (min-device-height:1782px){#S:after{content:\" x 1782\";}}\n@media (min-device-height:1783px){#S:after{content:\" x 1783\";}}\n@media (min-device-height:1784px){#S:after{content:\" x 1784\";}}\n@media (min-device-height:1785px){#S:after{content:\" x 1785\";}}\n@media (min-device-height:1786px){#S:after{content:\" x 1786\";}}\n@media (min-device-height:1787px){#S:after{content:\" x 1787\";}}\n@media (min-device-height:1788px){#S:after{content:\" x 1788\";}}\n@media (min-device-height:1789px){#S:after{content:\" x 1789\";}}\n@media (min-device-height:1790px){#S:after{content:\" x 1790\";}}\n@media (min-device-height:1791px){#S:after{content:\" x 1791\";}}\n@media (min-device-height:1792px){#S:after{content:\" x 1792\";}}\n@media (min-device-height:1793px){#S:after{content:\" x 1793\";}}\n@media (min-device-height:1794px){#S:after{content:\" x 1794\";}}\n@media (min-device-height:1795px){#S:after{content:\" x 1795\";}}\n@media (min-device-height:1796px){#S:after{content:\" x 1796\";}}\n@media (min-device-height:1797px){#S:after{content:\" x 1797\";}}\n@media (min-device-height:1798px){#S:after{content:\" x 1798\";}}\n@media (min-device-height:1799px){#S:after{content:\" x 1799\";}}\n@media (min-device-height:1800px){#S:after{content:\" x 1800\";}}\n@media (min-device-height:1801px){#S:after{content:\" x 1801\";}}\n@media (min-device-height:1802px){#S:after{content:\" x 1802\";}}\n@media (min-device-height:1803px){#S:after{content:\" x 1803\";}}\n@media (min-device-height:1804px){#S:after{content:\" x 1804\";}}\n@media (min-device-height:1805px){#S:after{content:\" x 1805\";}}\n@media (min-device-height:1806px){#S:after{content:\" x 1806\";}}\n@media (min-device-height:1807px){#S:after{content:\" x 1807\";}}\n@media (min-device-height:1808px){#S:after{content:\" x 1808\";}}\n@media (min-device-height:1809px){#S:after{content:\" x 1809\";}}\n@media (min-device-height:1810px){#S:after{content:\" x 1810\";}}\n@media (min-device-height:1811px){#S:after{content:\" x 1811\";}}\n@media (min-device-height:1812px){#S:after{content:\" x 1812\";}}\n@media (min-device-height:1813px){#S:after{content:\" x 1813\";}}\n@media (min-device-height:1814px){#S:after{content:\" x 1814\";}}\n@media (min-device-height:1815px){#S:after{content:\" x 1815\";}}\n@media (min-device-height:1816px){#S:after{content:\" x 1816\";}}\n@media (min-device-height:1817px){#S:after{content:\" x 1817\";}}\n@media (min-device-height:1818px){#S:after{content:\" x 1818\";}}\n@media (min-device-height:1819px){#S:after{content:\" x 1819\";}}\n@media (min-device-height:1820px){#S:after{content:\" x 1820\";}}\n@media (min-device-height:1821px){#S:after{content:\" x 1821\";}}\n@media (min-device-height:1822px){#S:after{content:\" x 1822\";}}\n@media (min-device-height:1823px){#S:after{content:\" x 1823\";}}\n@media (min-device-height:1824px){#S:after{content:\" x 1824\";}}\n@media (min-device-height:1825px){#S:after{content:\" x 1825\";}}\n@media (min-device-height:1826px){#S:after{content:\" x 1826\";}}\n@media (min-device-height:1827px){#S:after{content:\" x 1827\";}}\n@media (min-device-height:1828px){#S:after{content:\" x 1828\";}}\n@media (min-device-height:1829px){#S:after{content:\" x 1829\";}}\n@media (min-device-height:1830px){#S:after{content:\" x 1830\";}}\n@media (min-device-height:1831px){#S:after{content:\" x 1831\";}}\n@media (min-device-height:1832px){#S:after{content:\" x 1832\";}}\n@media (min-device-height:1833px){#S:after{content:\" x 1833\";}}\n@media (min-device-height:1834px){#S:after{content:\" x 1834\";}}\n@media (min-device-height:1835px){#S:after{content:\" x 1835\";}}\n@media (min-device-height:1836px){#S:after{content:\" x 1836\";}}\n@media (min-device-height:1837px){#S:after{content:\" x 1837\";}}\n@media (min-device-height:1838px){#S:after{content:\" x 1838\";}}\n@media (min-device-height:1839px){#S:after{content:\" x 1839\";}}\n@media (min-device-height:1840px){#S:after{content:\" x 1840\";}}\n@media (min-device-height:1841px){#S:after{content:\" x 1841\";}}\n@media (min-device-height:1842px){#S:after{content:\" x 1842\";}}\n@media (min-device-height:1843px){#S:after{content:\" x 1843\";}}\n@media (min-device-height:1844px){#S:after{content:\" x 1844\";}}\n@media (min-device-height:1845px){#S:after{content:\" x 1845\";}}\n@media (min-device-height:1846px){#S:after{content:\" x 1846\";}}\n@media (min-device-height:1847px){#S:after{content:\" x 1847\";}}\n@media (min-device-height:1848px){#S:after{content:\" x 1848\";}}\n@media (min-device-height:1849px){#S:after{content:\" x 1849\";}}\n@media (min-device-height:1850px){#S:after{content:\" x 1850\";}}\n@media (min-device-height:1851px){#S:after{content:\" x 1851\";}}\n@media (min-device-height:1852px){#S:after{content:\" x 1852\";}}\n@media (min-device-height:1853px){#S:after{content:\" x 1853\";}}\n@media (min-device-height:1854px){#S:after{content:\" x 1854\";}}\n@media (min-device-height:1855px){#S:after{content:\" x 1855\";}}\n@media (min-device-height:1856px){#S:after{content:\" x 1856\";}}\n@media (min-device-height:1857px){#S:after{content:\" x 1857\";}}\n@media (min-device-height:1858px){#S:after{content:\" x 1858\";}}\n@media (min-device-height:1859px){#S:after{content:\" x 1859\";}}\n@media (min-device-height:1860px){#S:after{content:\" x 1860\";}}\n@media (min-device-height:1861px){#S:after{content:\" x 1861\";}}\n@media (min-device-height:1862px){#S:after{content:\" x 1862\";}}\n@media (min-device-height:1863px){#S:after{content:\" x 1863\";}}\n@media (min-device-height:1864px){#S:after{content:\" x 1864\";}}\n@media (min-device-height:1865px){#S:after{content:\" x 1865\";}}\n@media (min-device-height:1866px){#S:after{content:\" x 1866\";}}\n@media (min-device-height:1867px){#S:after{content:\" x 1867\";}}\n@media (min-device-height:1868px){#S:after{content:\" x 1868\";}}\n@media (min-device-height:1869px){#S:after{content:\" x 1869\";}}\n@media (min-device-height:1870px){#S:after{content:\" x 1870\";}}\n@media (min-device-height:1871px){#S:after{content:\" x 1871\";}}\n@media (min-device-height:1872px){#S:after{content:\" x 1872\";}}\n@media (min-device-height:1873px){#S:after{content:\" x 1873\";}}\n@media (min-device-height:1874px){#S:after{content:\" x 1874\";}}\n@media (min-device-height:1875px){#S:after{content:\" x 1875\";}}\n@media (min-device-height:1876px){#S:after{content:\" x 1876\";}}\n@media (min-device-height:1877px){#S:after{content:\" x 1877\";}}\n@media (min-device-height:1878px){#S:after{content:\" x 1878\";}}\n@media (min-device-height:1879px){#S:after{content:\" x 1879\";}}\n@media (min-device-height:1880px){#S:after{content:\" x 1880\";}}\n@media (min-device-height:1881px){#S:after{content:\" x 1881\";}}\n@media (min-device-height:1882px){#S:after{content:\" x 1882\";}}\n@media (min-device-height:1883px){#S:after{content:\" x 1883\";}}\n@media (min-device-height:1884px){#S:after{content:\" x 1884\";}}\n@media (min-device-height:1885px){#S:after{content:\" x 1885\";}}\n@media (min-device-height:1886px){#S:after{content:\" x 1886\";}}\n@media (min-device-height:1887px){#S:after{content:\" x 1887\";}}\n@media (min-device-height:1888px){#S:after{content:\" x 1888\";}}\n@media (min-device-height:1889px){#S:after{content:\" x 1889\";}}\n@media (min-device-height:1890px){#S:after{content:\" x 1890\";}}\n@media (min-device-height:1891px){#S:after{content:\" x 1891\";}}\n@media (min-device-height:1892px){#S:after{content:\" x 1892\";}}\n@media (min-device-height:1893px){#S:after{content:\" x 1893\";}}\n@media (min-device-height:1894px){#S:after{content:\" x 1894\";}}\n@media (min-device-height:1895px){#S:after{content:\" x 1895\";}}\n@media (min-device-height:1896px){#S:after{content:\" x 1896\";}}\n@media (min-device-height:1897px){#S:after{content:\" x 1897\";}}\n@media (min-device-height:1898px){#S:after{content:\" x 1898\";}}\n@media (min-device-height:1899px){#S:after{content:\" x 1899\";}}\n@media (min-device-height:1900px){#S:after{content:\" x 1900\";}}\n@media (min-device-height:1901px){#S:after{content:\" x 1901\";}}\n@media (min-device-height:1902px){#S:after{content:\" x 1902\";}}\n@media (min-device-height:1903px){#S:after{content:\" x 1903\";}}\n@media (min-device-height:1904px){#S:after{content:\" x 1904\";}}\n@media (min-device-height:1905px){#S:after{content:\" x 1905\";}}\n@media (min-device-height:1906px){#S:after{content:\" x 1906\";}}\n@media (min-device-height:1907px){#S:after{content:\" x 1907\";}}\n@media (min-device-height:1908px){#S:after{content:\" x 1908\";}}\n@media (min-device-height:1909px){#S:after{content:\" x 1909\";}}\n@media (min-device-height:1910px){#S:after{content:\" x 1910\";}}\n@media (min-device-height:1911px){#S:after{content:\" x 1911\";}}\n@media (min-device-height:1912px){#S:after{content:\" x 1912\";}}\n@media (min-device-height:1913px){#S:after{content:\" x 1913\";}}\n@media (min-device-height:1914px){#S:after{content:\" x 1914\";}}\n@media (min-device-height:1915px){#S:after{content:\" x 1915\";}}\n@media (min-device-height:1916px){#S:after{content:\" x 1916\";}}\n@media (min-device-height:1917px){#S:after{content:\" x 1917\";}}\n@media (min-device-height:1918px){#S:after{content:\" x 1918\";}}\n@media (min-device-height:1919px){#S:after{content:\" x 1919\";}}\n@media (min-device-height:1920px){#S:after{content:\" x 1920\";}}\n@media (min-device-height:1921px){#S:after{content:\" x 1921\";}}\n@media (min-device-height:1922px){#S:after{content:\" x 1922\";}}\n@media (min-device-height:1923px){#S:after{content:\" x 1923\";}}\n@media (min-device-height:1924px){#S:after{content:\" x 1924\";}}\n@media (min-device-height:1925px){#S:after{content:\" x 1925\";}}\n@media (min-device-height:1926px){#S:after{content:\" x 1926\";}}\n@media (min-device-height:1927px){#S:after{content:\" x 1927\";}}\n@media (min-device-height:1928px){#S:after{content:\" x 1928\";}}\n@media (min-device-height:1929px){#S:after{content:\" x 1929\";}}\n@media (min-device-height:1930px){#S:after{content:\" x 1930\";}}\n@media (min-device-height:1931px){#S:after{content:\" x 1931\";}}\n@media (min-device-height:1932px){#S:after{content:\" x 1932\";}}\n@media (min-device-height:1933px){#S:after{content:\" x 1933\";}}\n@media (min-device-height:1934px){#S:after{content:\" x 1934\";}}\n@media (min-device-height:1935px){#S:after{content:\" x 1935\";}}\n@media (min-device-height:1936px){#S:after{content:\" x 1936\";}}\n@media (min-device-height:1937px){#S:after{content:\" x 1937\";}}\n@media (min-device-height:1938px){#S:after{content:\" x 1938\";}}\n@media (min-device-height:1939px){#S:after{content:\" x 1939\";}}\n@media (min-device-height:1940px){#S:after{content:\" x 1940\";}}\n@media (min-device-height:1941px){#S:after{content:\" x 1941\";}}\n@media (min-device-height:1942px){#S:after{content:\" x 1942\";}}\n@media (min-device-height:1943px){#S:after{content:\" x 1943\";}}\n@media (min-device-height:1944px){#S:after{content:\" x 1944\";}}\n@media (min-device-height:1945px){#S:after{content:\" x 1945\";}}\n@media (min-device-height:1946px){#S:after{content:\" x 1946\";}}\n@media (min-device-height:1947px){#S:after{content:\" x 1947\";}}\n@media (min-device-height:1948px){#S:after{content:\" x 1948\";}}\n@media (min-device-height:1949px){#S:after{content:\" x 1949\";}}\n@media (min-device-height:1950px){#S:after{content:\" x 1950\";}}\n@media (min-device-height:1951px){#S:after{content:\" x 1951\";}}\n@media (min-device-height:1952px){#S:after{content:\" x 1952\";}}\n@media (min-device-height:1953px){#S:after{content:\" x 1953\";}}\n@media (min-device-height:1954px){#S:after{content:\" x 1954\";}}\n@media (min-device-height:1955px){#S:after{content:\" x 1955\";}}\n@media (min-device-height:1956px){#S:after{content:\" x 1956\";}}\n@media (min-device-height:1957px){#S:after{content:\" x 1957\";}}\n@media (min-device-height:1958px){#S:after{content:\" x 1958\";}}\n@media (min-device-height:1959px){#S:after{content:\" x 1959\";}}\n@media (min-device-height:1960px){#S:after{content:\" x 1960\";}}\n@media (min-device-height:1961px){#S:after{content:\" x 1961\";}}\n@media (min-device-height:1962px){#S:after{content:\" x 1962\";}}\n@media (min-device-height:1963px){#S:after{content:\" x 1963\";}}\n@media (min-device-height:1964px){#S:after{content:\" x 1964\";}}\n@media (min-device-height:1965px){#S:after{content:\" x 1965\";}}\n@media (min-device-height:1966px){#S:after{content:\" x 1966\";}}\n@media (min-device-height:1967px){#S:after{content:\" x 1967\";}}\n@media (min-device-height:1968px){#S:after{content:\" x 1968\";}}\n@media (min-device-height:1969px){#S:after{content:\" x 1969\";}}\n@media (min-device-height:1970px){#S:after{content:\" x 1970\";}}\n@media (min-device-height:1971px){#S:after{content:\" x 1971\";}}\n@media (min-device-height:1972px){#S:after{content:\" x 1972\";}}\n@media (min-device-height:1973px){#S:after{content:\" x 1973\";}}\n@media (min-device-height:1974px){#S:after{content:\" x 1974\";}}\n@media (min-device-height:1975px){#S:after{content:\" x 1975\";}}\n@media (min-device-height:1976px){#S:after{content:\" x 1976\";}}\n@media (min-device-height:1977px){#S:after{content:\" x 1977\";}}\n@media (min-device-height:1978px){#S:after{content:\" x 1978\";}}\n@media (min-device-height:1979px){#S:after{content:\" x 1979\";}}\n@media (min-device-height:1980px){#S:after{content:\" x 1980\";}}\n@media (min-device-height:1981px){#S:after{content:\" x 1981\";}}\n@media (min-device-height:1982px){#S:after{content:\" x 1982\";}}\n@media (min-device-height:1983px){#S:after{content:\" x 1983\";}}\n@media (min-device-height:1984px){#S:after{content:\" x 1984\";}}\n@media (min-device-height:1985px){#S:after{content:\" x 1985\";}}\n@media (min-device-height:1986px){#S:after{content:\" x 1986\";}}\n@media (min-device-height:1987px){#S:after{content:\" x 1987\";}}\n@media (min-device-height:1988px){#S:after{content:\" x 1988\";}}\n@media (min-device-height:1989px){#S:after{content:\" x 1989\";}}\n@media (min-device-height:1990px){#S:after{content:\" x 1990\";}}\n@media (min-device-height:1991px){#S:after{content:\" x 1991\";}}\n@media (min-device-height:1992px){#S:after{content:\" x 1992\";}}\n@media (min-device-height:1993px){#S:after{content:\" x 1993\";}}\n@media (min-device-height:1994px){#S:after{content:\" x 1994\";}}\n@media (min-device-height:1995px){#S:after{content:\" x 1995\";}}\n@media (min-device-height:1996px){#S:after{content:\" x 1996\";}}\n@media (min-device-height:1997px){#S:after{content:\" x 1997\";}}\n@media (min-device-height:1998px){#S:after{content:\" x 1998\";}}\n@media (min-device-height:1999px){#S:after{content:\" x 1999\";}}\n@media (min-device-height:2000px){#S:after{content:\" x 2000\";}}\n@media (min-device-height:2001px){#S:after{content:\" x 2001\";}}\n@media (min-device-height:2002px){#S:after{content:\" x 2002\";}}\n@media (min-device-height:2003px){#S:after{content:\" x 2003\";}}\n@media (min-device-height:2004px){#S:after{content:\" x 2004\";}}\n@media (min-device-height:2005px){#S:after{content:\" x 2005\";}}\n@media (min-device-height:2006px){#S:after{content:\" x 2006\";}}\n@media (min-device-height:2007px){#S:after{content:\" x 2007\";}}\n@media (min-device-height:2008px){#S:after{content:\" x 2008\";}}\n@media (min-device-height:2009px){#S:after{content:\" x 2009\";}}\n@media (min-device-height:2010px){#S:after{content:\" x 2010\";}}\n@media (min-device-height:2011px){#S:after{content:\" x 2011\";}}\n@media (min-device-height:2012px){#S:after{content:\" x 2012\";}}\n@media (min-device-height:2013px){#S:after{content:\" x 2013\";}}\n@media (min-device-height:2014px){#S:after{content:\" x 2014\";}}\n@media (min-device-height:2015px){#S:after{content:\" x 2015\";}}\n@media (min-device-height:2016px){#S:after{content:\" x 2016\";}}\n@media (min-device-height:2017px){#S:after{content:\" x 2017\";}}\n@media (min-device-height:2018px){#S:after{content:\" x 2018\";}}\n@media (min-device-height:2019px){#S:after{content:\" x 2019\";}}\n@media (min-device-height:2020px){#S:after{content:\" x 2020\";}}\n@media (min-device-height:2021px){#S:after{content:\" x 2021\";}}\n@media (min-device-height:2022px){#S:after{content:\" x 2022\";}}\n@media (min-device-height:2023px){#S:after{content:\" x 2023\";}}\n@media (min-device-height:2024px){#S:after{content:\" x 2024\";}}\n@media (min-device-height:2025px){#S:after{content:\" x 2025\";}}\n@media (min-device-height:2026px){#S:after{content:\" x 2026\";}}\n@media (min-device-height:2027px){#S:after{content:\" x 2027\";}}\n@media (min-device-height:2028px){#S:after{content:\" x 2028\";}}\n@media (min-device-height:2029px){#S:after{content:\" x 2029\";}}\n@media (min-device-height:2030px){#S:after{content:\" x 2030\";}}\n@media (min-device-height:2031px){#S:after{content:\" x 2031\";}}\n@media (min-device-height:2032px){#S:after{content:\" x 2032\";}}\n@media (min-device-height:2033px){#S:after{content:\" x 2033\";}}\n@media (min-device-height:2034px){#S:after{content:\" x 2034\";}}\n@media (min-device-height:2035px){#S:after{content:\" x 2035\";}}\n@media (min-device-height:2036px){#S:after{content:\" x 2036\";}}\n@media (min-device-height:2037px){#S:after{content:\" x 2037\";}}\n@media (min-device-height:2038px){#S:after{content:\" x 2038\";}}\n@media (min-device-height:2039px){#S:after{content:\" x 2039\";}}\n@media (min-device-height:2040px){#S:after{content:\" x 2040\";}}\n@media (min-device-height:2041px){#S:after{content:\" x 2041\";}}\n@media (min-device-height:2042px){#S:after{content:\" x 2042\";}}\n@media (min-device-height:2043px){#S:after{content:\" x 2043\";}}\n@media (min-device-height:2044px){#S:after{content:\" x 2044\";}}\n@media (min-device-height:2045px){#S:after{content:\" x 2045\";}}\n@media (min-device-height:2046px){#S:after{content:\" x 2046\";}}\n@media (min-device-height:2047px){#S:after{content:\" x 2047\";}}\n@media (min-device-height:2048px){#S:after{content:\" x 2048\";}}\n@media (min-device-height:2049px){#S:after{content:\" x 2049\";}}\n@media (min-device-height:2050px){#S:after{content:\" x 2050\";}}\n@media (min-device-height:2051px){#S:after{content:\" x 2051\";}}\n@media (min-device-height:2052px){#S:after{content:\" x 2052\";}}\n@media (min-device-height:2053px){#S:after{content:\" x 2053\";}}\n@media (min-device-height:2054px){#S:after{content:\" x 2054\";}}\n@media (min-device-height:2055px){#S:after{content:\" x 2055\";}}\n@media (min-device-height:2056px){#S:after{content:\" x 2056\";}}\n@media (min-device-height:2057px){#S:after{content:\" x 2057\";}}\n@media (min-device-height:2058px){#S:after{content:\" x 2058\";}}\n@media (min-device-height:2059px){#S:after{content:\" x 2059\";}}\n@media (min-device-height:2060px){#S:after{content:\" x 2060\";}}\n@media (min-device-height:2061px){#S:after{content:\" x 2061\";}}\n@media (min-device-height:2062px){#S:after{content:\" x 2062\";}}\n@media (min-device-height:2063px){#S:after{content:\" x 2063\";}}\n@media (min-device-height:2064px){#S:after{content:\" x 2064\";}}\n@media (min-device-height:2065px){#S:after{content:\" x 2065\";}}\n@media (min-device-height:2066px){#S:after{content:\" x 2066\";}}\n@media (min-device-height:2067px){#S:after{content:\" x 2067\";}}\n@media (min-device-height:2068px){#S:after{content:\" x 2068\";}}\n@media (min-device-height:2069px){#S:after{content:\" x 2069\";}}\n@media (min-device-height:2070px){#S:after{content:\" x 2070\";}}\n@media (min-device-height:2071px){#S:after{content:\" x 2071\";}}\n@media (min-device-height:2072px){#S:after{content:\" x 2072\";}}\n@media (min-device-height:2073px){#S:after{content:\" x 2073\";}}\n@media (min-device-height:2074px){#S:after{content:\" x 2074\";}}\n@media (min-device-height:2075px){#S:after{content:\" x 2075\";}}\n@media (min-device-height:2076px){#S:after{content:\" x 2076\";}}\n@media (min-device-height:2077px){#S:after{content:\" x 2077\";}}\n@media (min-device-height:2078px){#S:after{content:\" x 2078\";}}\n@media (min-device-height:2079px){#S:after{content:\" x 2079\";}}\n@media (min-device-height:2080px){#S:after{content:\" x 2080\";}}\n@media (min-device-height:2081px){#S:after{content:\" x 2081\";}}\n@media (min-device-height:2082px){#S:after{content:\" x 2082\";}}\n@media (min-device-height:2083px){#S:after{content:\" x 2083\";}}\n@media (min-device-height:2084px){#S:after{content:\" x 2084\";}}\n@media (min-device-height:2085px){#S:after{content:\" x 2085\";}}\n@media (min-device-height:2086px){#S:after{content:\" x 2086\";}}\n@media (min-device-height:2087px){#S:after{content:\" x 2087\";}}\n@media (min-device-height:2088px){#S:after{content:\" x 2088\";}}\n@media (min-device-height:2089px){#S:after{content:\" x 2089\";}}\n@media (min-device-height:2090px){#S:after{content:\" x 2090\";}}\n@media (min-device-height:2091px){#S:after{content:\" x 2091\";}}\n@media (min-device-height:2092px){#S:after{content:\" x 2092\";}}\n@media (min-device-height:2093px){#S:after{content:\" x 2093\";}}\n@media (min-device-height:2094px){#S:after{content:\" x 2094\";}}\n@media (min-device-height:2095px){#S:after{content:\" x 2095\";}}\n@media (min-device-height:2096px){#S:after{content:\" x 2096\";}}\n@media (min-device-height:2097px){#S:after{content:\" x 2097\";}}\n@media (min-device-height:2098px){#S:after{content:\" x 2098\";}}\n@media (min-device-height:2099px){#S:after{content:\" x 2099\";}}\n@media (min-device-height:2100px){#S:after{content:\" x 2100\";}}\n@media (min-device-height:2101px){#S:after{content:\" x 2101\";}}\n@media (min-device-height:2102px){#S:after{content:\" x 2102\";}}\n@media (min-device-height:2103px){#S:after{content:\" x 2103\";}}\n@media (min-device-height:2104px){#S:after{content:\" x 2104\";}}\n@media (min-device-height:2105px){#S:after{content:\" x 2105\";}}\n@media (min-device-height:2106px){#S:after{content:\" x 2106\";}}\n@media (min-device-height:2107px){#S:after{content:\" x 2107\";}}\n@media (min-device-height:2108px){#S:after{content:\" x 2108\";}}\n@media (min-device-height:2109px){#S:after{content:\" x 2109\";}}\n@media (min-device-height:2110px){#S:after{content:\" x 2110\";}}\n@media (min-device-height:2111px){#S:after{content:\" x 2111\";}}\n@media (min-device-height:2112px){#S:after{content:\" x 2112\";}}\n@media (min-device-height:2113px){#S:after{content:\" x 2113\";}}\n@media (min-device-height:2114px){#S:after{content:\" x 2114\";}}\n@media (min-device-height:2115px){#S:after{content:\" x 2115\";}}\n@media (min-device-height:2116px){#S:after{content:\" x 2116\";}}\n@media (min-device-height:2117px){#S:after{content:\" x 2117\";}}\n@media (min-device-height:2118px){#S:after{content:\" x 2118\";}}\n@media (min-device-height:2119px){#S:after{content:\" x 2119\";}}\n@media (min-device-height:2120px){#S:after{content:\" x 2120\";}}\n@media (min-device-height:2121px){#S:after{content:\" x 2121\";}}\n@media (min-device-height:2122px){#S:after{content:\" x 2122\";}}\n@media (min-device-height:2123px){#S:after{content:\" x 2123\";}}\n@media (min-device-height:2124px){#S:after{content:\" x 2124\";}}\n@media (min-device-height:2125px){#S:after{content:\" x 2125\";}}\n@media (min-device-height:2126px){#S:after{content:\" x 2126\";}}\n@media (min-device-height:2127px){#S:after{content:\" x 2127\";}}\n@media (min-device-height:2128px){#S:after{content:\" x 2128\";}}\n@media (min-device-height:2129px){#S:after{content:\" x 2129\";}}\n@media (min-device-height:2130px){#S:after{content:\" x 2130\";}}\n@media (min-device-height:2131px){#S:after{content:\" x 2131\";}}\n@media (min-device-height:2132px){#S:after{content:\" x 2132\";}}\n@media (min-device-height:2133px){#S:after{content:\" x 2133\";}}\n@media (min-device-height:2134px){#S:after{content:\" x 2134\";}}\n@media (min-device-height:2135px){#S:after{content:\" x 2135\";}}\n@media (min-device-height:2136px){#S:after{content:\" x 2136\";}}\n@media (min-device-height:2137px){#S:after{content:\" x 2137\";}}\n@media (min-device-height:2138px){#S:after{content:\" x 2138\";}}\n@media (min-device-height:2139px){#S:after{content:\" x 2139\";}}\n@media (min-device-height:2140px){#S:after{content:\" x 2140\";}}\n@media (min-device-height:2141px){#S:after{content:\" x 2141\";}}\n@media (min-device-height:2142px){#S:after{content:\" x 2142\";}}\n@media (min-device-height:2143px){#S:after{content:\" x 2143\";}}\n@media (min-device-height:2144px){#S:after{content:\" x 2144\";}}\n@media (min-device-height:2145px){#S:after{content:\" x 2145\";}}\n@media (min-device-height:2146px){#S:after{content:\" x 2146\";}}\n@media (min-device-height:2147px){#S:after{content:\" x 2147\";}}\n@media (min-device-height:2148px){#S:after{content:\" x 2148\";}}\n@media (min-device-height:2149px){#S:after{content:\" x 2149\";}}\n@media (min-device-height:2150px){#S:after{content:\" x 2150\";}}\n@media (min-device-height:2151px){#S:after{content:\" x 2151\";}}\n@media (min-device-height:2152px){#S:after{content:\" x 2152\";}}\n@media (min-device-height:2153px){#S:after{content:\" x 2153\";}}\n@media (min-device-height:2154px){#S:after{content:\" x 2154\";}}\n@media (min-device-height:2155px){#S:after{content:\" x 2155\";}}\n@media (min-device-height:2156px){#S:after{content:\" x 2156\";}}\n@media (min-device-height:2157px){#S:after{content:\" x 2157\";}}\n@media (min-device-height:2158px){#S:after{content:\" x 2158\";}}\n@media (min-device-height:2159px){#S:after{content:\" x 2159\";}}\n@media (min-device-height:2160px){#S:after{content:\" x 2160\";}}\n@media (min-device-height:2161px){#S:after{content:\" x 2161\";}}\n@media (min-device-height:2162px){#S:after{content:\" x 2162\";}}\n@media (min-device-height:2163px){#S:after{content:\" x 2163\";}}\n@media (min-device-height:2164px){#S:after{content:\" x 2164\";}}\n@media (min-device-height:2165px){#S:after{content:\" x 2165\";}}\n@media (min-device-height:2166px){#S:after{content:\" x 2166\";}}\n@media (min-device-height:2167px){#S:after{content:\" x 2167\";}}\n@media (min-device-height:2168px){#S:after{content:\" x 2168\";}}\n@media (min-device-height:2169px){#S:after{content:\" x 2169\";}}\n@media (min-device-height:2170px){#S:after{content:\" x 2170\";}}\n@media (min-device-height:2171px){#S:after{content:\" x 2171\";}}\n@media (min-device-height:2172px){#S:after{content:\" x 2172\";}}\n@media (min-device-height:2173px){#S:after{content:\" x 2173\";}}\n@media (min-device-height:2174px){#S:after{content:\" x 2174\";}}\n@media (min-device-height:2175px){#S:after{content:\" x 2175\";}}\n@media (min-device-height:2176px){#S:after{content:\" x 2176\";}}\n@media (min-device-height:2177px){#S:after{content:\" x 2177\";}}\n@media (min-device-height:2178px){#S:after{content:\" x 2178\";}}\n@media (min-device-height:2179px){#S:after{content:\" x 2179\";}}\n@media (min-device-height:2180px){#S:after{content:\" x 2180\";}}\n@media (min-device-height:2181px){#S:after{content:\" x 2181\";}}\n@media (min-device-height:2182px){#S:after{content:\" x 2182\";}}\n@media (min-device-height:2183px){#S:after{content:\" x 2183\";}}\n@media (min-device-height:2184px){#S:after{content:\" x 2184\";}}\n@media (min-device-height:2185px){#S:after{content:\" x 2185\";}}\n@media (min-device-height:2186px){#S:after{content:\" x 2186\";}}\n@media (min-device-height:2187px){#S:after{content:\" x 2187\";}}\n@media (min-device-height:2188px){#S:after{content:\" x 2188\";}}\n@media (min-device-height:2189px){#S:after{content:\" x 2189\";}}\n@media (min-device-height:2190px){#S:after{content:\" x 2190\";}}\n@media (min-device-height:2191px){#S:after{content:\" x 2191\";}}\n@media (min-device-height:2192px){#S:after{content:\" x 2192\";}}\n@media (min-device-height:2193px){#S:after{content:\" x 2193\";}}\n@media (min-device-height:2194px){#S:after{content:\" x 2194\";}}\n@media (min-device-height:2195px){#S:after{content:\" x 2195\";}}\n@media (min-device-height:2196px){#S:after{content:\" x 2196\";}}\n@media (min-device-height:2197px){#S:after{content:\" x 2197\";}}\n@media (min-device-height:2198px){#S:after{content:\" x 2198\";}}\n@media (min-device-height:2199px){#S:after{content:\" x 2199\";}}\n@media (min-device-height:2200px){#S:after{content:\" x 2200\";}}\n@media (min-device-height:2201px){#S:after{content:\" x 2201\";}}\n@media (min-device-height:2202px){#S:after{content:\" x 2202\";}}\n@media (min-device-height:2203px){#S:after{content:\" x 2203\";}}\n@media (min-device-height:2204px){#S:after{content:\" x 2204\";}}\n@media (min-device-height:2205px){#S:after{content:\" x 2205\";}}\n@media (min-device-height:2206px){#S:after{content:\" x 2206\";}}\n@media (min-device-height:2207px){#S:after{content:\" x 2207\";}}\n@media (min-device-height:2208px){#S:after{content:\" x 2208\";}}\n@media (min-device-height:2209px){#S:after{content:\" x 2209\";}}\n@media (min-device-height:2210px){#S:after{content:\" x 2210\";}}\n@media (min-device-height:2211px){#S:after{content:\" x 2211\";}}\n@media (min-device-height:2212px){#S:after{content:\" x 2212\";}}\n@media (min-device-height:2213px){#S:after{content:\" x 2213\";}}\n@media (min-device-height:2214px){#S:after{content:\" x 2214\";}}\n@media (min-device-height:2215px){#S:after{content:\" x 2215\";}}\n@media (min-device-height:2216px){#S:after{content:\" x 2216\";}}\n@media (min-device-height:2217px){#S:after{content:\" x 2217\";}}\n@media (min-device-height:2218px){#S:after{content:\" x 2218\";}}\n@media (min-device-height:2219px){#S:after{content:\" x 2219\";}}\n@media (min-device-height:2220px){#S:after{content:\" x 2220\";}}\n@media (min-device-height:2221px){#S:after{content:\" x 2221\";}}\n@media (min-device-height:2222px){#S:after{content:\" x 2222\";}}\n@media (min-device-height:2223px){#S:after{content:\" x 2223\";}}\n@media (min-device-height:2224px){#S:after{content:\" x 2224\";}}\n@media (min-device-height:2225px){#S:after{content:\" x 2225\";}}\n@media (min-device-height:2226px){#S:after{content:\" x 2226\";}}\n@media (min-device-height:2227px){#S:after{content:\" x 2227\";}}\n@media (min-device-height:2228px){#S:after{content:\" x 2228\";}}\n@media (min-device-height:2229px){#S:after{content:\" x 2229\";}}\n@media (min-device-height:2230px){#S:after{content:\" x 2230\";}}\n@media (min-device-height:2231px){#S:after{content:\" x 2231\";}}\n@media (min-device-height:2232px){#S:after{content:\" x 2232\";}}\n@media (min-device-height:2233px){#S:after{content:\" x 2233\";}}\n@media (min-device-height:2234px){#S:after{content:\" x 2234\";}}\n@media (min-device-height:2235px){#S:after{content:\" x 2235\";}}\n@media (min-device-height:2236px){#S:after{content:\" x 2236\";}}\n@media (min-device-height:2237px){#S:after{content:\" x 2237\";}}\n@media (min-device-height:2238px){#S:after{content:\" x 2238\";}}\n@media (min-device-height:2239px){#S:after{content:\" x 2239\";}}\n@media (min-device-height:2240px){#S:after{content:\" x 2240\";}}\n@media (min-device-height:2241px){#S:after{content:\" x 2241\";}}\n@media (min-device-height:2242px){#S:after{content:\" x 2242\";}}\n@media (min-device-height:2243px){#S:after{content:\" x 2243\";}}\n@media (min-device-height:2244px){#S:after{content:\" x 2244\";}}\n@media (min-device-height:2245px){#S:after{content:\" x 2245\";}}\n@media (min-device-height:2246px){#S:after{content:\" x 2246\";}}\n@media (min-device-height:2247px){#S:after{content:\" x 2247\";}}\n@media (min-device-height:2248px){#S:after{content:\" x 2248\";}}\n@media (min-device-height:2249px){#S:after{content:\" x 2249\";}}\n@media (min-device-height:2250px){#S:after{content:\" x 2250\";}}\n@media (min-device-height:2251px){#S:after{content:\" x 2251\";}}\n@media (min-device-height:2252px){#S:after{content:\" x 2252\";}}\n@media (min-device-height:2253px){#S:after{content:\" x 2253\";}}\n@media (min-device-height:2254px){#S:after{content:\" x 2254\";}}\n@media (min-device-height:2255px){#S:after{content:\" x 2255\";}}\n@media (min-device-height:2256px){#S:after{content:\" x 2256\";}}\n@media (min-device-height:2257px){#S:after{content:\" x 2257\";}}\n@media (min-device-height:2258px){#S:after{content:\" x 2258\";}}\n@media (min-device-height:2259px){#S:after{content:\" x 2259\";}}\n@media (min-device-height:2260px){#S:after{content:\" x 2260\";}}\n@media (min-device-height:2261px){#S:after{content:\" x 2261\";}}\n@media (min-device-height:2262px){#S:after{content:\" x 2262\";}}\n@media (min-device-height:2263px){#S:after{content:\" x 2263\";}}\n@media (min-device-height:2264px){#S:after{content:\" x 2264\";}}\n@media (min-device-height:2265px){#S:after{content:\" x 2265\";}}\n@media (min-device-height:2266px){#S:after{content:\" x 2266\";}}\n@media (min-device-height:2267px){#S:after{content:\" x 2267\";}}\n@media (min-device-height:2268px){#S:after{content:\" x 2268\";}}\n@media (min-device-height:2269px){#S:after{content:\" x 2269\";}}\n@media (min-device-height:2270px){#S:after{content:\" x 2270\";}}\n@media (min-device-height:2271px){#S:after{content:\" x 2271\";}}\n@media (min-device-height:2272px){#S:after{content:\" x 2272\";}}\n@media (min-device-height:2273px){#S:after{content:\" x 2273\";}}\n@media (min-device-height:2274px){#S:after{content:\" x 2274\";}}\n@media (min-device-height:2275px){#S:after{content:\" x 2275\";}}\n@media (min-device-height:2276px){#S:after{content:\" x 2276\";}}\n@media (min-device-height:2277px){#S:after{content:\" x 2277\";}}\n@media (min-device-height:2278px){#S:after{content:\" x 2278\";}}\n@media (min-device-height:2279px){#S:after{content:\" x 2279\";}}\n@media (min-device-height:2280px){#S:after{content:\" x 2280\";}}\n@media (min-device-height:2281px){#S:after{content:\" x 2281\";}}\n@media (min-device-height:2282px){#S:after{content:\" x 2282\";}}\n@media (min-device-height:2283px){#S:after{content:\" x 2283\";}}\n@media (min-device-height:2284px){#S:after{content:\" x 2284\";}}\n@media (min-device-height:2285px){#S:after{content:\" x 2285\";}}\n@media (min-device-height:2286px){#S:after{content:\" x 2286\";}}\n@media (min-device-height:2287px){#S:after{content:\" x 2287\";}}\n@media (min-device-height:2288px){#S:after{content:\" x 2288\";}}\n@media (min-device-height:2289px){#S:after{content:\" x 2289\";}}\n@media (min-device-height:2290px){#S:after{content:\" x 2290\";}}\n@media (min-device-height:2291px){#S:after{content:\" x 2291\";}}\n@media (min-device-height:2292px){#S:after{content:\" x 2292\";}}\n@media (min-device-height:2293px){#S:after{content:\" x 2293\";}}\n@media (min-device-height:2294px){#S:after{content:\" x 2294\";}}\n@media (min-device-height:2295px){#S:after{content:\" x 2295\";}}\n@media (min-device-height:2296px){#S:after{content:\" x 2296\";}}\n@media (min-device-height:2297px){#S:after{content:\" x 2297\";}}\n@media (min-device-height:2298px){#S:after{content:\" x 2298\";}}\n@media (min-device-height:2299px){#S:after{content:\" x 2299\";}}\n@media (min-device-height:2300px){#S:after{content:\" x 2300\";}}\n@media (min-device-height:2301px){#S:after{content:\" x 2301\";}}\n@media (min-device-height:2302px){#S:after{content:\" x 2302\";}}\n@media (min-device-height:2303px){#S:after{content:\" x 2303\";}}\n@media (min-device-height:2304px){#S:after{content:\" x 2304\";}}\n@media (min-device-height:2305px){#S:after{content:\" x 2305\";}}\n@media (min-device-height:2306px){#S:after{content:\" x 2306\";}}\n@media (min-device-height:2307px){#S:after{content:\" x 2307\";}}\n@media (min-device-height:2308px){#S:after{content:\" x 2308\";}}\n@media (min-device-height:2309px){#S:after{content:\" x 2309\";}}\n@media (min-device-height:2310px){#S:after{content:\" x 2310\";}}\n@media (min-device-height:2311px){#S:after{content:\" x 2311\";}}\n@media (min-device-height:2312px){#S:after{content:\" x 2312\";}}\n@media (min-device-height:2313px){#S:after{content:\" x 2313\";}}\n@media (min-device-height:2314px){#S:after{content:\" x 2314\";}}\n@media (min-device-height:2315px){#S:after{content:\" x 2315\";}}\n@media (min-device-height:2316px){#S:after{content:\" x 2316\";}}\n@media (min-device-height:2317px){#S:after{content:\" x 2317\";}}\n@media (min-device-height:2318px){#S:after{content:\" x 2318\";}}\n@media (min-device-height:2319px){#S:after{content:\" x 2319\";}}\n@media (min-device-height:2320px){#S:after{content:\" x 2320\";}}\n@media (min-device-height:2321px){#S:after{content:\" x 2321\";}}\n@media (min-device-height:2322px){#S:after{content:\" x 2322\";}}\n@media (min-device-height:2323px){#S:after{content:\" x 2323\";}}\n@media (min-device-height:2324px){#S:after{content:\" x 2324\";}}\n@media (min-device-height:2325px){#S:after{content:\" x 2325\";}}\n@media (min-device-height:2326px){#S:after{content:\" x 2326\";}}\n@media (min-device-height:2327px){#S:after{content:\" x 2327\";}}\n@media (min-device-height:2328px){#S:after{content:\" x 2328\";}}\n@media (min-device-height:2329px){#S:after{content:\" x 2329\";}}\n@media (min-device-height:2330px){#S:after{content:\" x 2330\";}}\n@media (min-device-height:2331px){#S:after{content:\" x 2331\";}}\n@media (min-device-height:2332px){#S:after{content:\" x 2332\";}}\n@media (min-device-height:2333px){#S:after{content:\" x 2333\";}}\n@media (min-device-height:2334px){#S:after{content:\" x 2334\";}}\n@media (min-device-height:2335px){#S:after{content:\" x 2335\";}}\n@media (min-device-height:2336px){#S:after{content:\" x 2336\";}}\n@media (min-device-height:2337px){#S:after{content:\" x 2337\";}}\n@media (min-device-height:2338px){#S:after{content:\" x 2338\";}}\n@media (min-device-height:2339px){#S:after{content:\" x 2339\";}}\n@media (min-device-height:2340px){#S:after{content:\" x 2340\";}}\n@media (min-device-height:2341px){#S:after{content:\" x 2341\";}}\n@media (min-device-height:2342px){#S:after{content:\" x 2342\";}}\n@media (min-device-height:2343px){#S:after{content:\" x 2343\";}}\n@media (min-device-height:2344px){#S:after{content:\" x 2344\";}}\n@media (min-device-height:2345px){#S:after{content:\" x 2345\";}}\n@media (min-device-height:2346px){#S:after{content:\" x 2346\";}}\n@media (min-device-height:2347px){#S:after{content:\" x 2347\";}}\n@media (min-device-height:2348px){#S:after{content:\" x 2348\";}}\n@media (min-device-height:2349px){#S:after{content:\" x 2349\";}}\n@media (min-device-height:2350px){#S:after{content:\" x 2350\";}}\n@media (min-device-height:2351px){#S:after{content:\" x 2351\";}}\n@media (min-device-height:2352px){#S:after{content:\" x 2352\";}}\n@media (min-device-height:2353px){#S:after{content:\" x 2353\";}}\n@media (min-device-height:2354px){#S:after{content:\" x 2354\";}}\n@media (min-device-height:2355px){#S:after{content:\" x 2355\";}}\n@media (min-device-height:2356px){#S:after{content:\" x 2356\";}}\n@media (min-device-height:2357px){#S:after{content:\" x 2357\";}}\n@media (min-device-height:2358px){#S:after{content:\" x 2358\";}}\n@media (min-device-height:2359px){#S:after{content:\" x 2359\";}}\n@media (min-device-height:2360px){#S:after{content:\" x 2360\";}}\n@media (min-device-height:2361px){#S:after{content:\" x 2361\";}}\n@media (min-device-height:2362px){#S:after{content:\" x 2362\";}}\n@media (min-device-height:2363px){#S:after{content:\" x 2363\";}}\n@media (min-device-height:2364px){#S:after{content:\" x 2364\";}}\n@media (min-device-height:2365px){#S:after{content:\" x 2365\";}}\n@media (min-device-height:2366px){#S:after{content:\" x 2366\";}}\n@media (min-device-height:2367px){#S:after{content:\" x 2367\";}}\n@media (min-device-height:2368px){#S:after{content:\" x 2368\";}}\n@media (min-device-height:2369px){#S:after{content:\" x 2369\";}}\n@media (min-device-height:2370px){#S:after{content:\" x 2370\";}}\n@media (min-device-height:2371px){#S:after{content:\" x 2371\";}}\n@media (min-device-height:2372px){#S:after{content:\" x 2372\";}}\n@media (min-device-height:2373px){#S:after{content:\" x 2373\";}}\n@media (min-device-height:2374px){#S:after{content:\" x 2374\";}}\n@media (min-device-height:2375px){#S:after{content:\" x 2375\";}}\n@media (min-device-height:2376px){#S:after{content:\" x 2376\";}}\n@media (min-device-height:2377px){#S:after{content:\" x 2377\";}}\n@media (min-device-height:2378px){#S:after{content:\" x 2378\";}}\n@media (min-device-height:2379px){#S:after{content:\" x 2379\";}}\n@media (min-device-height:2380px){#S:after{content:\" x 2380\";}}\n@media (min-device-height:2381px){#S:after{content:\" x 2381\";}}\n@media (min-device-height:2382px){#S:after{content:\" x 2382\";}}\n@media (min-device-height:2383px){#S:after{content:\" x 2383\";}}\n@media (min-device-height:2384px){#S:after{content:\" x 2384\";}}\n@media (min-device-height:2385px){#S:after{content:\" x 2385\";}}\n@media (min-device-height:2386px){#S:after{content:\" x 2386\";}}\n@media (min-device-height:2387px){#S:after{content:\" x 2387\";}}\n@media (min-device-height:2388px){#S:after{content:\" x 2388\";}}\n@media (min-device-height:2389px){#S:after{content:\" x 2389\";}}\n@media (min-device-height:2390px){#S:after{content:\" x 2390\";}}\n@media (min-device-height:2391px){#S:after{content:\" x 2391\";}}\n@media (min-device-height:2392px){#S:after{content:\" x 2392\";}}\n@media (min-device-height:2393px){#S:after{content:\" x 2393\";}}\n@media (min-device-height:2394px){#S:after{content:\" x 2394\";}}\n@media (min-device-height:2395px){#S:after{content:\" x 2395\";}}\n@media (min-device-height:2396px){#S:after{content:\" x 2396\";}}\n@media (min-device-height:2397px){#S:after{content:\" x 2397\";}}\n@media (min-device-height:2398px){#S:after{content:\" x 2398\";}}\n@media (min-device-height:2399px){#S:after{content:\" x 2399\";}}\n@media (min-device-height:2400px){#S:after{content:\" x 2400\";}}\n@media (min-device-height:2401px){#S:after{content:\" x 2401\";}}\n@media (min-device-height:2402px){#S:after{content:\" x 2402\";}}\n@media (min-device-height:2403px){#S:after{content:\" x 2403\";}}\n@media (min-device-height:2404px){#S:after{content:\" x 2404\";}}\n@media (min-device-height:2405px){#S:after{content:\" x 2405\";}}\n@media (min-device-height:2406px){#S:after{content:\" x 2406\";}}\n@media (min-device-height:2407px){#S:after{content:\" x 2407\";}}\n@media (min-device-height:2408px){#S:after{content:\" x 2408\";}}\n@media (min-device-height:2409px){#S:after{content:\" x 2409\";}}\n@media (min-device-height:2410px){#S:after{content:\" x 2410\";}}\n@media (min-device-height:2411px){#S:after{content:\" x 2411\";}}\n@media (min-device-height:2412px){#S:after{content:\" x 2412\";}}\n@media (min-device-height:2413px){#S:after{content:\" x 2413\";}}\n@media (min-device-height:2414px){#S:after{content:\" x 2414\";}}\n@media (min-device-height:2415px){#S:after{content:\" x 2415\";}}\n@media (min-device-height:2416px){#S:after{content:\" x 2416\";}}\n@media (min-device-height:2417px){#S:after{content:\" x 2417\";}}\n@media (min-device-height:2418px){#S:after{content:\" x 2418\";}}\n@media (min-device-height:2419px){#S:after{content:\" x 2419\";}}\n@media (min-device-height:2420px){#S:after{content:\" x 2420\";}}\n@media (min-device-height:2421px){#S:after{content:\" x 2421\";}}\n@media (min-device-height:2422px){#S:after{content:\" x 2422\";}}\n@media (min-device-height:2423px){#S:after{content:\" x 2423\";}}\n@media (min-device-height:2424px){#S:after{content:\" x 2424\";}}\n@media (min-device-height:2425px){#S:after{content:\" x 2425\";}}\n@media (min-device-height:2426px){#S:after{content:\" x 2426\";}}\n@media (min-device-height:2427px){#S:after{content:\" x 2427\";}}\n@media (min-device-height:2428px){#S:after{content:\" x 2428\";}}\n@media (min-device-height:2429px){#S:after{content:\" x 2429\";}}\n@media (min-device-height:2430px){#S:after{content:\" x 2430\";}}\n@media (min-device-height:2431px){#S:after{content:\" x 2431\";}}\n@media (min-device-height:2432px){#S:after{content:\" x 2432\";}}\n@media (min-device-height:2433px){#S:after{content:\" x 2433\";}}\n@media (min-device-height:2434px){#S:after{content:\" x 2434\";}}\n@media (min-device-height:2435px){#S:after{content:\" x 2435\";}}\n@media (min-device-height:2436px){#S:after{content:\" x 2436\";}}\n@media (min-device-height:2437px){#S:after{content:\" x 2437\";}}\n@media (min-device-height:2438px){#S:after{content:\" x 2438\";}}\n@media (min-device-height:2439px){#S:after{content:\" x 2439\";}}\n@media (min-device-height:2440px){#S:after{content:\" x 2440\";}}\n@media (min-device-height:2441px){#S:after{content:\" x 2441\";}}\n@media (min-device-height:2442px){#S:after{content:\" x 2442\";}}\n@media (min-device-height:2443px){#S:after{content:\" x 2443\";}}\n@media (min-device-height:2444px){#S:after{content:\" x 2444\";}}\n@media (min-device-height:2445px){#S:after{content:\" x 2445\";}}\n@media (min-device-height:2446px){#S:after{content:\" x 2446\";}}\n@media (min-device-height:2447px){#S:after{content:\" x 2447\";}}\n@media (min-device-height:2448px){#S:after{content:\" x 2448\";}}\n@media (min-device-height:2449px){#S:after{content:\" x 2449\";}}\n@media (min-device-height:2450px){#S:after{content:\" x 2450\";}}\n@media (min-device-height:2451px){#S:after{content:\" x 2451\";}}\n@media (min-device-height:2452px){#S:after{content:\" x 2452\";}}\n@media (min-device-height:2453px){#S:after{content:\" x 2453\";}}\n@media (min-device-height:2454px){#S:after{content:\" x 2454\";}}\n@media (min-device-height:2455px){#S:after{content:\" x 2455\";}}\n@media (min-device-height:2456px){#S:after{content:\" x 2456\";}}\n@media (min-device-height:2457px){#S:after{content:\" x 2457\";}}\n@media (min-device-height:2458px){#S:after{content:\" x 2458\";}}\n@media (min-device-height:2459px){#S:after{content:\" x 2459\";}}\n@media (min-device-height:2460px){#S:after{content:\" x 2460\";}}\n@media (min-device-height:2461px){#S:after{content:\" x 2461\";}}\n@media (min-device-height:2462px){#S:after{content:\" x 2462\";}}\n@media (min-device-height:2463px){#S:after{content:\" x 2463\";}}\n@media (min-device-height:2464px){#S:after{content:\" x 2464\";}}\n@media (min-device-height:2465px){#S:after{content:\" x 2465\";}}\n@media (min-device-height:2466px){#S:after{content:\" x 2466\";}}\n@media (min-device-height:2467px){#S:after{content:\" x 2467\";}}\n@media (min-device-height:2468px){#S:after{content:\" x 2468\";}}\n@media (min-device-height:2469px){#S:after{content:\" x 2469\";}}\n@media (min-device-height:2470px){#S:after{content:\" x 2470\";}}\n@media (min-device-height:2471px){#S:after{content:\" x 2471\";}}\n@media (min-device-height:2472px){#S:after{content:\" x 2472\";}}\n@media (min-device-height:2473px){#S:after{content:\" x 2473\";}}\n@media (min-device-height:2474px){#S:after{content:\" x 2474\";}}\n@media (min-device-height:2475px){#S:after{content:\" x 2475\";}}\n@media (min-device-height:2476px){#S:after{content:\" x 2476\";}}\n@media (min-device-height:2477px){#S:after{content:\" x 2477\";}}\n@media (min-device-height:2478px){#S:after{content:\" x 2478\";}}\n@media (min-device-height:2479px){#S:after{content:\" x 2479\";}}\n@media (min-device-height:2480px){#S:after{content:\" x 2480\";}}\n@media (min-device-height:2481px){#S:after{content:\" x 2481\";}}\n@media (min-device-height:2482px){#S:after{content:\" x 2482\";}}\n@media (min-device-height:2483px){#S:after{content:\" x 2483\";}}\n@media (min-device-height:2484px){#S:after{content:\" x 2484\";}}\n@media (min-device-height:2485px){#S:after{content:\" x 2485\";}}\n@media (min-device-height:2486px){#S:after{content:\" x 2486\";}}\n@media (min-device-height:2487px){#S:after{content:\" x 2487\";}}\n@media (min-device-height:2488px){#S:after{content:\" x 2488\";}}\n@media (min-device-height:2489px){#S:after{content:\" x 2489\";}}\n@media (min-device-height:2490px){#S:after{content:\" x 2490\";}}\n@media (min-device-height:2491px){#S:after{content:\" x 2491\";}}\n@media (min-device-height:2492px){#S:after{content:\" x 2492\";}}\n@media (min-device-height:2493px){#S:after{content:\" x 2493\";}}\n@media (min-device-height:2494px){#S:after{content:\" x 2494\";}}\n@media (min-device-height:2495px){#S:after{content:\" x 2495\";}}\n@media (min-device-height:2496px){#S:after{content:\" x 2496\";}}\n@media (min-device-height:2497px){#S:after{content:\" x 2497\";}}\n@media (min-device-height:2498px){#S:after{content:\" x 2498\";}}\n@media (min-device-height:2499px){#S:after{content:\" x 2499\";}}\n@media (min-device-height:2500px){#S:after{content:\" x 2500\";}}\n@media (min-device-height:2501px){#S:after{content:\" x 2501\";}}\n@media (min-device-height:2502px){#S:after{content:\" x 2502\";}}\n@media (min-device-height:2503px){#S:after{content:\" x 2503\";}}\n@media (min-device-height:2504px){#S:after{content:\" x 2504\";}}\n@media (min-device-height:2505px){#S:after{content:\" x 2505\";}}\n@media (min-device-height:2506px){#S:after{content:\" x 2506\";}}\n@media (min-device-height:2507px){#S:after{content:\" x 2507\";}}\n@media (min-device-height:2508px){#S:after{content:\" x 2508\";}}\n@media (min-device-height:2509px){#S:after{content:\" x 2509\";}}\n@media (min-device-height:2510px){#S:after{content:\" x 2510\";}}\n@media (min-device-height:2511px){#S:after{content:\" x 2511\";}}\n@media (min-device-height:2512px){#S:after{content:\" x 2512\";}}\n@media (min-device-height:2513px){#S:after{content:\" x 2513\";}}\n@media (min-device-height:2514px){#S:after{content:\" x 2514\";}}\n@media (min-device-height:2515px){#S:after{content:\" x 2515\";}}\n@media (min-device-height:2516px){#S:after{content:\" x 2516\";}}\n@media (min-device-height:2517px){#S:after{content:\" x 2517\";}}\n@media (min-device-height:2518px){#S:after{content:\" x 2518\";}}\n@media (min-device-height:2519px){#S:after{content:\" x 2519\";}}\n@media (min-device-height:2520px){#S:after{content:\" x 2520\";}}\n@media (min-device-height:2521px){#S:after{content:\" x 2521\";}}\n@media (min-device-height:2522px){#S:after{content:\" x 2522\";}}\n@media (min-device-height:2523px){#S:after{content:\" x 2523\";}}\n@media (min-device-height:2524px){#S:after{content:\" x 2524\";}}\n@media (min-device-height:2525px){#S:after{content:\" x 2525\";}}\n@media (min-device-height:2526px){#S:after{content:\" x 2526\";}}\n@media (min-device-height:2527px){#S:after{content:\" x 2527\";}}\n@media (min-device-height:2528px){#S:after{content:\" x 2528\";}}\n@media (min-device-height:2529px){#S:after{content:\" x 2529\";}}\n@media (min-device-height:2530px){#S:after{content:\" x 2530\";}}\n@media (min-device-height:2531px){#S:after{content:\" x 2531\";}}\n@media (min-device-height:2532px){#S:after{content:\" x 2532\";}}\n@media (min-device-height:2533px){#S:after{content:\" x 2533\";}}\n@media (min-device-height:2534px){#S:after{content:\" x 2534\";}}\n@media (min-device-height:2535px){#S:after{content:\" x 2535\";}}\n@media (min-device-height:2536px){#S:after{content:\" x 2536\";}}\n@media (min-device-height:2537px){#S:after{content:\" x 2537\";}}\n@media (min-device-height:2538px){#S:after{content:\" x 2538\";}}\n@media (min-device-height:2539px){#S:after{content:\" x 2539\";}}\n@media (min-device-height:2540px){#S:after{content:\" x 2540\";}}\n@media (min-device-height:2541px){#S:after{content:\" x 2541\";}}\n@media (min-device-height:2542px){#S:after{content:\" x 2542\";}}\n@media (min-device-height:2543px){#S:after{content:\" x 2543\";}}\n@media (min-device-height:2544px){#S:after{content:\" x 2544\";}}\n@media (min-device-height:2545px){#S:after{content:\" x 2545\";}}\n@media (min-device-height:2546px){#S:after{content:\" x 2546\";}}\n@media (min-device-height:2547px){#S:after{content:\" x 2547\";}}\n@media (min-device-height:2548px){#S:after{content:\" x 2548\";}}\n@media (min-device-height:2549px){#S:after{content:\" x 2549\";}}\n@media (min-device-height:2550px){#S:after{content:\" x 2550\";}}\n@media (min-device-height:2551px){#S:after{content:\" x 2551\";}}\n@media (min-device-height:2552px){#S:after{content:\" x 2552\";}}\n@media (min-device-height:2553px){#S:after{content:\" x 2553\";}}\n@media (min-device-height:2554px){#S:after{content:\" x 2554\";}}\n@media (min-device-height:2555px){#S:after{content:\" x 2555\";}}\n@media (min-device-height:2556px){#S:after{content:\" x 2556\";}}\n@media (min-device-height:2557px){#S:after{content:\" x 2557\";}}\n@media (min-device-height:2558px){#S:after{content:\" x 2558\";}}\n@media (min-device-height:2559px){#S:after{content:\" x 2559\";}}\n@media (min-device-height:2560px){#S:after{content:\" x 2560\";}}\n@media (min-device-height:2561px){#S:after{content:\"\";}}\n"
  },
  {
    "path": "css/window_size.css",
    "content": "@media (min-width:399px){#D:before{content:\"\";}}\n@media (min-width:400px){#D:before{content:\"400\";}}\n@media (min-width:401px){#D:before{content:\"401\";}}\n@media (min-width:402px){#D:before{content:\"402\";}}\n@media (min-width:403px){#D:before{content:\"403\";}}\n@media (min-width:404px){#D:before{content:\"404\";}}\n@media (min-width:405px){#D:before{content:\"405\";}}\n@media (min-width:406px){#D:before{content:\"406\";}}\n@media (min-width:407px){#D:before{content:\"407\";}}\n@media (min-width:408px){#D:before{content:\"408\";}}\n@media (min-width:409px){#D:before{content:\"409\";}}\n@media (min-width:410px){#D:before{content:\"410\";}}\n@media (min-width:411px){#D:before{content:\"411\";}}\n@media (min-width:412px){#D:before{content:\"412\";}}\n@media (min-width:413px){#D:before{content:\"413\";}}\n@media (min-width:414px){#D:before{content:\"414\";}}\n@media (min-width:415px){#D:before{content:\"415\";}}\n@media (min-width:416px){#D:before{content:\"416\";}}\n@media (min-width:417px){#D:before{content:\"417\";}}\n@media (min-width:418px){#D:before{content:\"418\";}}\n@media (min-width:419px){#D:before{content:\"419\";}}\n@media (min-width:420px){#D:before{content:\"420\";}}\n@media (min-width:421px){#D:before{content:\"421\";}}\n@media (min-width:422px){#D:before{content:\"422\";}}\n@media (min-width:423px){#D:before{content:\"423\";}}\n@media (min-width:424px){#D:before{content:\"424\";}}\n@media (min-width:425px){#D:before{content:\"425\";}}\n@media (min-width:426px){#D:before{content:\"426\";}}\n@media (min-width:427px){#D:before{content:\"427\";}}\n@media (min-width:428px){#D:before{content:\"428\";}}\n@media (min-width:429px){#D:before{content:\"429\";}}\n@media (min-width:430px){#D:before{content:\"430\";}}\n@media (min-width:431px){#D:before{content:\"431\";}}\n@media (min-width:432px){#D:before{content:\"432\";}}\n@media (min-width:433px){#D:before{content:\"433\";}}\n@media (min-width:434px){#D:before{content:\"434\";}}\n@media (min-width:435px){#D:before{content:\"435\";}}\n@media (min-width:436px){#D:before{content:\"436\";}}\n@media (min-width:437px){#D:before{content:\"437\";}}\n@media (min-width:438px){#D:before{content:\"438\";}}\n@media (min-width:439px){#D:before{content:\"439\";}}\n@media (min-width:440px){#D:before{content:\"440\";}}\n@media (min-width:441px){#D:before{content:\"441\";}}\n@media (min-width:442px){#D:before{content:\"442\";}}\n@media (min-width:443px){#D:before{content:\"443\";}}\n@media (min-width:444px){#D:before{content:\"444\";}}\n@media (min-width:445px){#D:before{content:\"445\";}}\n@media (min-width:446px){#D:before{content:\"446\";}}\n@media (min-width:447px){#D:before{content:\"447\";}}\n@media (min-width:448px){#D:before{content:\"448\";}}\n@media (min-width:449px){#D:before{content:\"449\";}}\n@media (min-width:450px){#D:before{content:\"450\";}}\n@media (min-width:451px){#D:before{content:\"451\";}}\n@media (min-width:452px){#D:before{content:\"452\";}}\n@media (min-width:453px){#D:before{content:\"453\";}}\n@media (min-width:454px){#D:before{content:\"454\";}}\n@media (min-width:455px){#D:before{content:\"455\";}}\n@media (min-width:456px){#D:before{content:\"456\";}}\n@media (min-width:457px){#D:before{content:\"457\";}}\n@media (min-width:458px){#D:before{content:\"458\";}}\n@media (min-width:459px){#D:before{content:\"459\";}}\n@media (min-width:460px){#D:before{content:\"460\";}}\n@media (min-width:461px){#D:before{content:\"461\";}}\n@media (min-width:462px){#D:before{content:\"462\";}}\n@media (min-width:463px){#D:before{content:\"463\";}}\n@media (min-width:464px){#D:before{content:\"464\";}}\n@media (min-width:465px){#D:before{content:\"465\";}}\n@media (min-width:466px){#D:before{content:\"466\";}}\n@media (min-width:467px){#D:before{content:\"467\";}}\n@media (min-width:468px){#D:before{content:\"468\";}}\n@media (min-width:469px){#D:before{content:\"469\";}}\n@media (min-width:470px){#D:before{content:\"470\";}}\n@media (min-width:471px){#D:before{content:\"471\";}}\n@media (min-width:472px){#D:before{content:\"472\";}}\n@media (min-width:473px){#D:before{content:\"473\";}}\n@media (min-width:474px){#D:before{content:\"474\";}}\n@media (min-width:475px){#D:before{content:\"475\";}}\n@media (min-width:476px){#D:before{content:\"476\";}}\n@media (min-width:477px){#D:before{content:\"477\";}}\n@media (min-width:478px){#D:before{content:\"478\";}}\n@media (min-width:479px){#D:before{content:\"479\";}}\n@media (min-width:480px){#D:before{content:\"480\";}}\n@media (min-width:481px){#D:before{content:\"481\";}}\n@media (min-width:482px){#D:before{content:\"482\";}}\n@media (min-width:483px){#D:before{content:\"483\";}}\n@media (min-width:484px){#D:before{content:\"484\";}}\n@media (min-width:485px){#D:before{content:\"485\";}}\n@media (min-width:486px){#D:before{content:\"486\";}}\n@media (min-width:487px){#D:before{content:\"487\";}}\n@media (min-width:488px){#D:before{content:\"488\";}}\n@media (min-width:489px){#D:before{content:\"489\";}}\n@media (min-width:490px){#D:before{content:\"490\";}}\n@media (min-width:491px){#D:before{content:\"491\";}}\n@media (min-width:492px){#D:before{content:\"492\";}}\n@media (min-width:493px){#D:before{content:\"493\";}}\n@media (min-width:494px){#D:before{content:\"494\";}}\n@media (min-width:495px){#D:before{content:\"495\";}}\n@media (min-width:496px){#D:before{content:\"496\";}}\n@media (min-width:497px){#D:before{content:\"497\";}}\n@media (min-width:498px){#D:before{content:\"498\";}}\n@media (min-width:499px){#D:before{content:\"499\";}}\n@media (min-width:500px){#D:before{content:\"500\";}}\n@media (min-width:501px){#D:before{content:\"501\";}}\n@media (min-width:502px){#D:before{content:\"502\";}}\n@media (min-width:503px){#D:before{content:\"503\";}}\n@media (min-width:504px){#D:before{content:\"504\";}}\n@media (min-width:505px){#D:before{content:\"505\";}}\n@media (min-width:506px){#D:before{content:\"506\";}}\n@media (min-width:507px){#D:before{content:\"507\";}}\n@media (min-width:508px){#D:before{content:\"508\";}}\n@media (min-width:509px){#D:before{content:\"509\";}}\n@media (min-width:510px){#D:before{content:\"510\";}}\n@media (min-width:511px){#D:before{content:\"511\";}}\n@media (min-width:512px){#D:before{content:\"512\";}}\n@media (min-width:513px){#D:before{content:\"513\";}}\n@media (min-width:514px){#D:before{content:\"514\";}}\n@media (min-width:515px){#D:before{content:\"515\";}}\n@media (min-width:516px){#D:before{content:\"516\";}}\n@media (min-width:517px){#D:before{content:\"517\";}}\n@media (min-width:518px){#D:before{content:\"518\";}}\n@media (min-width:519px){#D:before{content:\"519\";}}\n@media (min-width:520px){#D:before{content:\"520\";}}\n@media (min-width:521px){#D:before{content:\"521\";}}\n@media (min-width:522px){#D:before{content:\"522\";}}\n@media (min-width:523px){#D:before{content:\"523\";}}\n@media (min-width:524px){#D:before{content:\"524\";}}\n@media (min-width:525px){#D:before{content:\"525\";}}\n@media (min-width:526px){#D:before{content:\"526\";}}\n@media (min-width:527px){#D:before{content:\"527\";}}\n@media (min-width:528px){#D:before{content:\"528\";}}\n@media (min-width:529px){#D:before{content:\"529\";}}\n@media (min-width:530px){#D:before{content:\"530\";}}\n@media (min-width:531px){#D:before{content:\"531\";}}\n@media (min-width:532px){#D:before{content:\"532\";}}\n@media (min-width:533px){#D:before{content:\"533\";}}\n@media (min-width:534px){#D:before{content:\"534\";}}\n@media (min-width:535px){#D:before{content:\"535\";}}\n@media (min-width:536px){#D:before{content:\"536\";}}\n@media (min-width:537px){#D:before{content:\"537\";}}\n@media (min-width:538px){#D:before{content:\"538\";}}\n@media (min-width:539px){#D:before{content:\"539\";}}\n@media (min-width:540px){#D:before{content:\"540\";}}\n@media (min-width:541px){#D:before{content:\"541\";}}\n@media (min-width:542px){#D:before{content:\"542\";}}\n@media (min-width:543px){#D:before{content:\"543\";}}\n@media (min-width:544px){#D:before{content:\"544\";}}\n@media (min-width:545px){#D:before{content:\"545\";}}\n@media (min-width:546px){#D:before{content:\"546\";}}\n@media (min-width:547px){#D:before{content:\"547\";}}\n@media (min-width:548px){#D:before{content:\"548\";}}\n@media (min-width:549px){#D:before{content:\"549\";}}\n@media (min-width:550px){#D:before{content:\"550\";}}\n@media (min-width:551px){#D:before{content:\"551\";}}\n@media (min-width:552px){#D:before{content:\"552\";}}\n@media (min-width:553px){#D:before{content:\"553\";}}\n@media (min-width:554px){#D:before{content:\"554\";}}\n@media (min-width:555px){#D:before{content:\"555\";}}\n@media (min-width:556px){#D:before{content:\"556\";}}\n@media (min-width:557px){#D:before{content:\"557\";}}\n@media (min-width:558px){#D:before{content:\"558\";}}\n@media (min-width:559px){#D:before{content:\"559\";}}\n@media (min-width:560px){#D:before{content:\"560\";}}\n@media (min-width:561px){#D:before{content:\"561\";}}\n@media (min-width:562px){#D:before{content:\"562\";}}\n@media (min-width:563px){#D:before{content:\"563\";}}\n@media (min-width:564px){#D:before{content:\"564\";}}\n@media (min-width:565px){#D:before{content:\"565\";}}\n@media (min-width:566px){#D:before{content:\"566\";}}\n@media (min-width:567px){#D:before{content:\"567\";}}\n@media (min-width:568px){#D:before{content:\"568\";}}\n@media (min-width:569px){#D:before{content:\"569\";}}\n@media (min-width:570px){#D:before{content:\"570\";}}\n@media (min-width:571px){#D:before{content:\"571\";}}\n@media (min-width:572px){#D:before{content:\"572\";}}\n@media (min-width:573px){#D:before{content:\"573\";}}\n@media (min-width:574px){#D:before{content:\"574\";}}\n@media (min-width:575px){#D:before{content:\"575\";}}\n@media (min-width:576px){#D:before{content:\"576\";}}\n@media (min-width:577px){#D:before{content:\"577\";}}\n@media (min-width:578px){#D:before{content:\"578\";}}\n@media (min-width:579px){#D:before{content:\"579\";}}\n@media (min-width:580px){#D:before{content:\"580\";}}\n@media (min-width:581px){#D:before{content:\"581\";}}\n@media (min-width:582px){#D:before{content:\"582\";}}\n@media (min-width:583px){#D:before{content:\"583\";}}\n@media (min-width:584px){#D:before{content:\"584\";}}\n@media (min-width:585px){#D:before{content:\"585\";}}\n@media (min-width:586px){#D:before{content:\"586\";}}\n@media (min-width:587px){#D:before{content:\"587\";}}\n@media (min-width:588px){#D:before{content:\"588\";}}\n@media (min-width:589px){#D:before{content:\"589\";}}\n@media (min-width:590px){#D:before{content:\"590\";}}\n@media (min-width:591px){#D:before{content:\"591\";}}\n@media (min-width:592px){#D:before{content:\"592\";}}\n@media (min-width:593px){#D:before{content:\"593\";}}\n@media (min-width:594px){#D:before{content:\"594\";}}\n@media (min-width:595px){#D:before{content:\"595\";}}\n@media (min-width:596px){#D:before{content:\"596\";}}\n@media (min-width:597px){#D:before{content:\"597\";}}\n@media (min-width:598px){#D:before{content:\"598\";}}\n@media (min-width:599px){#D:before{content:\"599\";}}\n@media (min-width:600px){#D:before{content:\"600\";}}\n@media (min-width:601px){#D:before{content:\"601\";}}\n@media (min-width:602px){#D:before{content:\"602\";}}\n@media (min-width:603px){#D:before{content:\"603\";}}\n@media (min-width:604px){#D:before{content:\"604\";}}\n@media (min-width:605px){#D:before{content:\"605\";}}\n@media (min-width:606px){#D:before{content:\"606\";}}\n@media (min-width:607px){#D:before{content:\"607\";}}\n@media (min-width:608px){#D:before{content:\"608\";}}\n@media (min-width:609px){#D:before{content:\"609\";}}\n@media (min-width:610px){#D:before{content:\"610\";}}\n@media (min-width:611px){#D:before{content:\"611\";}}\n@media (min-width:612px){#D:before{content:\"612\";}}\n@media (min-width:613px){#D:before{content:\"613\";}}\n@media (min-width:614px){#D:before{content:\"614\";}}\n@media (min-width:615px){#D:before{content:\"615\";}}\n@media (min-width:616px){#D:before{content:\"616\";}}\n@media (min-width:617px){#D:before{content:\"617\";}}\n@media (min-width:618px){#D:before{content:\"618\";}}\n@media (min-width:619px){#D:before{content:\"619\";}}\n@media (min-width:620px){#D:before{content:\"620\";}}\n@media (min-width:621px){#D:before{content:\"621\";}}\n@media (min-width:622px){#D:before{content:\"622\";}}\n@media (min-width:623px){#D:before{content:\"623\";}}\n@media (min-width:624px){#D:before{content:\"624\";}}\n@media (min-width:625px){#D:before{content:\"625\";}}\n@media (min-width:626px){#D:before{content:\"626\";}}\n@media (min-width:627px){#D:before{content:\"627\";}}\n@media (min-width:628px){#D:before{content:\"628\";}}\n@media (min-width:629px){#D:before{content:\"629\";}}\n@media (min-width:630px){#D:before{content:\"630\";}}\n@media (min-width:631px){#D:before{content:\"631\";}}\n@media (min-width:632px){#D:before{content:\"632\";}}\n@media (min-width:633px){#D:before{content:\"633\";}}\n@media (min-width:634px){#D:before{content:\"634\";}}\n@media (min-width:635px){#D:before{content:\"635\";}}\n@media (min-width:636px){#D:before{content:\"636\";}}\n@media (min-width:637px){#D:before{content:\"637\";}}\n@media (min-width:638px){#D:before{content:\"638\";}}\n@media (min-width:639px){#D:before{content:\"639\";}}\n@media (min-width:640px){#D:before{content:\"640\";}}\n@media (min-width:641px){#D:before{content:\"641\";}}\n@media (min-width:642px){#D:before{content:\"642\";}}\n@media (min-width:643px){#D:before{content:\"643\";}}\n@media (min-width:644px){#D:before{content:\"644\";}}\n@media (min-width:645px){#D:before{content:\"645\";}}\n@media (min-width:646px){#D:before{content:\"646\";}}\n@media (min-width:647px){#D:before{content:\"647\";}}\n@media (min-width:648px){#D:before{content:\"648\";}}\n@media (min-width:649px){#D:before{content:\"649\";}}\n@media (min-width:650px){#D:before{content:\"650\";}}\n@media (min-width:651px){#D:before{content:\"651\";}}\n@media (min-width:652px){#D:before{content:\"652\";}}\n@media (min-width:653px){#D:before{content:\"653\";}}\n@media (min-width:654px){#D:before{content:\"654\";}}\n@media (min-width:655px){#D:before{content:\"655\";}}\n@media (min-width:656px){#D:before{content:\"656\";}}\n@media (min-width:657px){#D:before{content:\"657\";}}\n@media (min-width:658px){#D:before{content:\"658\";}}\n@media (min-width:659px){#D:before{content:\"659\";}}\n@media (min-width:660px){#D:before{content:\"660\";}}\n@media (min-width:661px){#D:before{content:\"661\";}}\n@media (min-width:662px){#D:before{content:\"662\";}}\n@media (min-width:663px){#D:before{content:\"663\";}}\n@media (min-width:664px){#D:before{content:\"664\";}}\n@media (min-width:665px){#D:before{content:\"665\";}}\n@media (min-width:666px){#D:before{content:\"666\";}}\n@media (min-width:667px){#D:before{content:\"667\";}}\n@media (min-width:668px){#D:before{content:\"668\";}}\n@media (min-width:669px){#D:before{content:\"669\";}}\n@media (min-width:670px){#D:before{content:\"670\";}}\n@media (min-width:671px){#D:before{content:\"671\";}}\n@media (min-width:672px){#D:before{content:\"672\";}}\n@media (min-width:673px){#D:before{content:\"673\";}}\n@media (min-width:674px){#D:before{content:\"674\";}}\n@media (min-width:675px){#D:before{content:\"675\";}}\n@media (min-width:676px){#D:before{content:\"676\";}}\n@media (min-width:677px){#D:before{content:\"677\";}}\n@media (min-width:678px){#D:before{content:\"678\";}}\n@media (min-width:679px){#D:before{content:\"679\";}}\n@media (min-width:680px){#D:before{content:\"680\";}}\n@media (min-width:681px){#D:before{content:\"681\";}}\n@media (min-width:682px){#D:before{content:\"682\";}}\n@media (min-width:683px){#D:before{content:\"683\";}}\n@media (min-width:684px){#D:before{content:\"684\";}}\n@media (min-width:685px){#D:before{content:\"685\";}}\n@media (min-width:686px){#D:before{content:\"686\";}}\n@media (min-width:687px){#D:before{content:\"687\";}}\n@media (min-width:688px){#D:before{content:\"688\";}}\n@media (min-width:689px){#D:before{content:\"689\";}}\n@media (min-width:690px){#D:before{content:\"690\";}}\n@media (min-width:691px){#D:before{content:\"691\";}}\n@media (min-width:692px){#D:before{content:\"692\";}}\n@media (min-width:693px){#D:before{content:\"693\";}}\n@media (min-width:694px){#D:before{content:\"694\";}}\n@media (min-width:695px){#D:before{content:\"695\";}}\n@media (min-width:696px){#D:before{content:\"696\";}}\n@media (min-width:697px){#D:before{content:\"697\";}}\n@media (min-width:698px){#D:before{content:\"698\";}}\n@media (min-width:699px){#D:before{content:\"699\";}}\n@media (min-width:700px){#D:before{content:\"700\";}}\n@media (min-width:701px){#D:before{content:\"701\";}}\n@media (min-width:702px){#D:before{content:\"702\";}}\n@media (min-width:703px){#D:before{content:\"703\";}}\n@media (min-width:704px){#D:before{content:\"704\";}}\n@media (min-width:705px){#D:before{content:\"705\";}}\n@media (min-width:706px){#D:before{content:\"706\";}}\n@media (min-width:707px){#D:before{content:\"707\";}}\n@media (min-width:708px){#D:before{content:\"708\";}}\n@media (min-width:709px){#D:before{content:\"709\";}}\n@media (min-width:710px){#D:before{content:\"710\";}}\n@media (min-width:711px){#D:before{content:\"711\";}}\n@media (min-width:712px){#D:before{content:\"712\";}}\n@media (min-width:713px){#D:before{content:\"713\";}}\n@media (min-width:714px){#D:before{content:\"714\";}}\n@media (min-width:715px){#D:before{content:\"715\";}}\n@media (min-width:716px){#D:before{content:\"716\";}}\n@media (min-width:717px){#D:before{content:\"717\";}}\n@media (min-width:718px){#D:before{content:\"718\";}}\n@media (min-width:719px){#D:before{content:\"719\";}}\n@media (min-width:720px){#D:before{content:\"720\";}}\n@media (min-width:721px){#D:before{content:\"721\";}}\n@media (min-width:722px){#D:before{content:\"722\";}}\n@media (min-width:723px){#D:before{content:\"723\";}}\n@media (min-width:724px){#D:before{content:\"724\";}}\n@media (min-width:725px){#D:before{content:\"725\";}}\n@media (min-width:726px){#D:before{content:\"726\";}}\n@media (min-width:727px){#D:before{content:\"727\";}}\n@media (min-width:728px){#D:before{content:\"728\";}}\n@media (min-width:729px){#D:before{content:\"729\";}}\n@media (min-width:730px){#D:before{content:\"730\";}}\n@media (min-width:731px){#D:before{content:\"731\";}}\n@media (min-width:732px){#D:before{content:\"732\";}}\n@media (min-width:733px){#D:before{content:\"733\";}}\n@media (min-width:734px){#D:before{content:\"734\";}}\n@media (min-width:735px){#D:before{content:\"735\";}}\n@media (min-width:736px){#D:before{content:\"736\";}}\n@media (min-width:737px){#D:before{content:\"737\";}}\n@media (min-width:738px){#D:before{content:\"738\";}}\n@media (min-width:739px){#D:before{content:\"739\";}}\n@media (min-width:740px){#D:before{content:\"740\";}}\n@media (min-width:741px){#D:before{content:\"741\";}}\n@media (min-width:742px){#D:before{content:\"742\";}}\n@media (min-width:743px){#D:before{content:\"743\";}}\n@media (min-width:744px){#D:before{content:\"744\";}}\n@media (min-width:745px){#D:before{content:\"745\";}}\n@media (min-width:746px){#D:before{content:\"746\";}}\n@media (min-width:747px){#D:before{content:\"747\";}}\n@media (min-width:748px){#D:before{content:\"748\";}}\n@media (min-width:749px){#D:before{content:\"749\";}}\n@media (min-width:750px){#D:before{content:\"750\";}}\n@media (min-width:751px){#D:before{content:\"751\";}}\n@media (min-width:752px){#D:before{content:\"752\";}}\n@media (min-width:753px){#D:before{content:\"753\";}}\n@media (min-width:754px){#D:before{content:\"754\";}}\n@media (min-width:755px){#D:before{content:\"755\";}}\n@media (min-width:756px){#D:before{content:\"756\";}}\n@media (min-width:757px){#D:before{content:\"757\";}}\n@media (min-width:758px){#D:before{content:\"758\";}}\n@media (min-width:759px){#D:before{content:\"759\";}}\n@media (min-width:760px){#D:before{content:\"760\";}}\n@media (min-width:761px){#D:before{content:\"761\";}}\n@media (min-width:762px){#D:before{content:\"762\";}}\n@media (min-width:763px){#D:before{content:\"763\";}}\n@media (min-width:764px){#D:before{content:\"764\";}}\n@media (min-width:765px){#D:before{content:\"765\";}}\n@media (min-width:766px){#D:before{content:\"766\";}}\n@media (min-width:767px){#D:before{content:\"767\";}}\n@media (min-width:768px){#D:before{content:\"768\";}}\n@media (min-width:769px){#D:before{content:\"769\";}}\n@media (min-width:770px){#D:before{content:\"770\";}}\n@media (min-width:771px){#D:before{content:\"771\";}}\n@media (min-width:772px){#D:before{content:\"772\";}}\n@media (min-width:773px){#D:before{content:\"773\";}}\n@media (min-width:774px){#D:before{content:\"774\";}}\n@media (min-width:775px){#D:before{content:\"775\";}}\n@media (min-width:776px){#D:before{content:\"776\";}}\n@media (min-width:777px){#D:before{content:\"777\";}}\n@media (min-width:778px){#D:before{content:\"778\";}}\n@media (min-width:779px){#D:before{content:\"779\";}}\n@media (min-width:780px){#D:before{content:\"780\";}}\n@media (min-width:781px){#D:before{content:\"781\";}}\n@media (min-width:782px){#D:before{content:\"782\";}}\n@media (min-width:783px){#D:before{content:\"783\";}}\n@media (min-width:784px){#D:before{content:\"784\";}}\n@media (min-width:785px){#D:before{content:\"785\";}}\n@media (min-width:786px){#D:before{content:\"786\";}}\n@media (min-width:787px){#D:before{content:\"787\";}}\n@media (min-width:788px){#D:before{content:\"788\";}}\n@media (min-width:789px){#D:before{content:\"789\";}}\n@media (min-width:790px){#D:before{content:\"790\";}}\n@media (min-width:791px){#D:before{content:\"791\";}}\n@media (min-width:792px){#D:before{content:\"792\";}}\n@media (min-width:793px){#D:before{content:\"793\";}}\n@media (min-width:794px){#D:before{content:\"794\";}}\n@media (min-width:795px){#D:before{content:\"795\";}}\n@media (min-width:796px){#D:before{content:\"796\";}}\n@media (min-width:797px){#D:before{content:\"797\";}}\n@media (min-width:798px){#D:before{content:\"798\";}}\n@media (min-width:799px){#D:before{content:\"799\";}}\n@media (min-width:800px){#D:before{content:\"800\";}}\n@media (min-width:801px){#D:before{content:\"801\";}}\n@media (min-width:802px){#D:before{content:\"802\";}}\n@media (min-width:803px){#D:before{content:\"803\";}}\n@media (min-width:804px){#D:before{content:\"804\";}}\n@media (min-width:805px){#D:before{content:\"805\";}}\n@media (min-width:806px){#D:before{content:\"806\";}}\n@media (min-width:807px){#D:before{content:\"807\";}}\n@media (min-width:808px){#D:before{content:\"808\";}}\n@media (min-width:809px){#D:before{content:\"809\";}}\n@media (min-width:810px){#D:before{content:\"810\";}}\n@media (min-width:811px){#D:before{content:\"811\";}}\n@media (min-width:812px){#D:before{content:\"812\";}}\n@media (min-width:813px){#D:before{content:\"813\";}}\n@media (min-width:814px){#D:before{content:\"814\";}}\n@media (min-width:815px){#D:before{content:\"815\";}}\n@media (min-width:816px){#D:before{content:\"816\";}}\n@media (min-width:817px){#D:before{content:\"817\";}}\n@media (min-width:818px){#D:before{content:\"818\";}}\n@media (min-width:819px){#D:before{content:\"819\";}}\n@media (min-width:820px){#D:before{content:\"820\";}}\n@media (min-width:821px){#D:before{content:\"821\";}}\n@media (min-width:822px){#D:before{content:\"822\";}}\n@media (min-width:823px){#D:before{content:\"823\";}}\n@media (min-width:824px){#D:before{content:\"824\";}}\n@media (min-width:825px){#D:before{content:\"825\";}}\n@media (min-width:826px){#D:before{content:\"826\";}}\n@media (min-width:827px){#D:before{content:\"827\";}}\n@media (min-width:828px){#D:before{content:\"828\";}}\n@media (min-width:829px){#D:before{content:\"829\";}}\n@media (min-width:830px){#D:before{content:\"830\";}}\n@media (min-width:831px){#D:before{content:\"831\";}}\n@media (min-width:832px){#D:before{content:\"832\";}}\n@media (min-width:833px){#D:before{content:\"833\";}}\n@media (min-width:834px){#D:before{content:\"834\";}}\n@media (min-width:835px){#D:before{content:\"835\";}}\n@media (min-width:836px){#D:before{content:\"836\";}}\n@media (min-width:837px){#D:before{content:\"837\";}}\n@media (min-width:838px){#D:before{content:\"838\";}}\n@media (min-width:839px){#D:before{content:\"839\";}}\n@media (min-width:840px){#D:before{content:\"840\";}}\n@media (min-width:841px){#D:before{content:\"841\";}}\n@media (min-width:842px){#D:before{content:\"842\";}}\n@media (min-width:843px){#D:before{content:\"843\";}}\n@media (min-width:844px){#D:before{content:\"844\";}}\n@media (min-width:845px){#D:before{content:\"845\";}}\n@media (min-width:846px){#D:before{content:\"846\";}}\n@media (min-width:847px){#D:before{content:\"847\";}}\n@media (min-width:848px){#D:before{content:\"848\";}}\n@media (min-width:849px){#D:before{content:\"849\";}}\n@media (min-width:850px){#D:before{content:\"850\";}}\n@media (min-width:851px){#D:before{content:\"851\";}}\n@media (min-width:852px){#D:before{content:\"852\";}}\n@media (min-width:853px){#D:before{content:\"853\";}}\n@media (min-width:854px){#D:before{content:\"854\";}}\n@media (min-width:855px){#D:before{content:\"855\";}}\n@media (min-width:856px){#D:before{content:\"856\";}}\n@media (min-width:857px){#D:before{content:\"857\";}}\n@media (min-width:858px){#D:before{content:\"858\";}}\n@media (min-width:859px){#D:before{content:\"859\";}}\n@media (min-width:860px){#D:before{content:\"860\";}}\n@media (min-width:861px){#D:before{content:\"861\";}}\n@media (min-width:862px){#D:before{content:\"862\";}}\n@media (min-width:863px){#D:before{content:\"863\";}}\n@media (min-width:864px){#D:before{content:\"864\";}}\n@media (min-width:865px){#D:before{content:\"865\";}}\n@media (min-width:866px){#D:before{content:\"866\";}}\n@media (min-width:867px){#D:before{content:\"867\";}}\n@media (min-width:868px){#D:before{content:\"868\";}}\n@media (min-width:869px){#D:before{content:\"869\";}}\n@media (min-width:870px){#D:before{content:\"870\";}}\n@media (min-width:871px){#D:before{content:\"871\";}}\n@media (min-width:872px){#D:before{content:\"872\";}}\n@media (min-width:873px){#D:before{content:\"873\";}}\n@media (min-width:874px){#D:before{content:\"874\";}}\n@media (min-width:875px){#D:before{content:\"875\";}}\n@media (min-width:876px){#D:before{content:\"876\";}}\n@media (min-width:877px){#D:before{content:\"877\";}}\n@media (min-width:878px){#D:before{content:\"878\";}}\n@media (min-width:879px){#D:before{content:\"879\";}}\n@media (min-width:880px){#D:before{content:\"880\";}}\n@media (min-width:881px){#D:before{content:\"881\";}}\n@media (min-width:882px){#D:before{content:\"882\";}}\n@media (min-width:883px){#D:before{content:\"883\";}}\n@media (min-width:884px){#D:before{content:\"884\";}}\n@media (min-width:885px){#D:before{content:\"885\";}}\n@media (min-width:886px){#D:before{content:\"886\";}}\n@media (min-width:887px){#D:before{content:\"887\";}}\n@media (min-width:888px){#D:before{content:\"888\";}}\n@media (min-width:889px){#D:before{content:\"889\";}}\n@media (min-width:890px){#D:before{content:\"890\";}}\n@media (min-width:891px){#D:before{content:\"891\";}}\n@media (min-width:892px){#D:before{content:\"892\";}}\n@media (min-width:893px){#D:before{content:\"893\";}}\n@media (min-width:894px){#D:before{content:\"894\";}}\n@media (min-width:895px){#D:before{content:\"895\";}}\n@media (min-width:896px){#D:before{content:\"896\";}}\n@media (min-width:897px){#D:before{content:\"897\";}}\n@media (min-width:898px){#D:before{content:\"898\";}}\n@media (min-width:899px){#D:before{content:\"899\";}}\n@media (min-width:900px){#D:before{content:\"900\";}}\n@media (min-width:901px){#D:before{content:\"901\";}}\n@media (min-width:902px){#D:before{content:\"902\";}}\n@media (min-width:903px){#D:before{content:\"903\";}}\n@media (min-width:904px){#D:before{content:\"904\";}}\n@media (min-width:905px){#D:before{content:\"905\";}}\n@media (min-width:906px){#D:before{content:\"906\";}}\n@media (min-width:907px){#D:before{content:\"907\";}}\n@media (min-width:908px){#D:before{content:\"908\";}}\n@media (min-width:909px){#D:before{content:\"909\";}}\n@media (min-width:910px){#D:before{content:\"910\";}}\n@media (min-width:911px){#D:before{content:\"911\";}}\n@media (min-width:912px){#D:before{content:\"912\";}}\n@media (min-width:913px){#D:before{content:\"913\";}}\n@media (min-width:914px){#D:before{content:\"914\";}}\n@media (min-width:915px){#D:before{content:\"915\";}}\n@media (min-width:916px){#D:before{content:\"916\";}}\n@media (min-width:917px){#D:before{content:\"917\";}}\n@media (min-width:918px){#D:before{content:\"918\";}}\n@media (min-width:919px){#D:before{content:\"919\";}}\n@media (min-width:920px){#D:before{content:\"920\";}}\n@media (min-width:921px){#D:before{content:\"921\";}}\n@media (min-width:922px){#D:before{content:\"922\";}}\n@media (min-width:923px){#D:before{content:\"923\";}}\n@media (min-width:924px){#D:before{content:\"924\";}}\n@media (min-width:925px){#D:before{content:\"925\";}}\n@media (min-width:926px){#D:before{content:\"926\";}}\n@media (min-width:927px){#D:before{content:\"927\";}}\n@media (min-width:928px){#D:before{content:\"928\";}}\n@media (min-width:929px){#D:before{content:\"929\";}}\n@media (min-width:930px){#D:before{content:\"930\";}}\n@media (min-width:931px){#D:before{content:\"931\";}}\n@media (min-width:932px){#D:before{content:\"932\";}}\n@media (min-width:933px){#D:before{content:\"933\";}}\n@media (min-width:934px){#D:before{content:\"934\";}}\n@media (min-width:935px){#D:before{content:\"935\";}}\n@media (min-width:936px){#D:before{content:\"936\";}}\n@media (min-width:937px){#D:before{content:\"937\";}}\n@media (min-width:938px){#D:before{content:\"938\";}}\n@media (min-width:939px){#D:before{content:\"939\";}}\n@media (min-width:940px){#D:before{content:\"940\";}}\n@media (min-width:941px){#D:before{content:\"941\";}}\n@media (min-width:942px){#D:before{content:\"942\";}}\n@media (min-width:943px){#D:before{content:\"943\";}}\n@media (min-width:944px){#D:before{content:\"944\";}}\n@media (min-width:945px){#D:before{content:\"945\";}}\n@media (min-width:946px){#D:before{content:\"946\";}}\n@media (min-width:947px){#D:before{content:\"947\";}}\n@media (min-width:948px){#D:before{content:\"948\";}}\n@media (min-width:949px){#D:before{content:\"949\";}}\n@media (min-width:950px){#D:before{content:\"950\";}}\n@media (min-width:951px){#D:before{content:\"951\";}}\n@media (min-width:952px){#D:before{content:\"952\";}}\n@media (min-width:953px){#D:before{content:\"953\";}}\n@media (min-width:954px){#D:before{content:\"954\";}}\n@media (min-width:955px){#D:before{content:\"955\";}}\n@media (min-width:956px){#D:before{content:\"956\";}}\n@media (min-width:957px){#D:before{content:\"957\";}}\n@media (min-width:958px){#D:before{content:\"958\";}}\n@media (min-width:959px){#D:before{content:\"959\";}}\n@media (min-width:960px){#D:before{content:\"960\";}}\n@media (min-width:961px){#D:before{content:\"961\";}}\n@media (min-width:962px){#D:before{content:\"962\";}}\n@media (min-width:963px){#D:before{content:\"963\";}}\n@media (min-width:964px){#D:before{content:\"964\";}}\n@media (min-width:965px){#D:before{content:\"965\";}}\n@media (min-width:966px){#D:before{content:\"966\";}}\n@media (min-width:967px){#D:before{content:\"967\";}}\n@media (min-width:968px){#D:before{content:\"968\";}}\n@media (min-width:969px){#D:before{content:\"969\";}}\n@media (min-width:970px){#D:before{content:\"970\";}}\n@media (min-width:971px){#D:before{content:\"971\";}}\n@media (min-width:972px){#D:before{content:\"972\";}}\n@media (min-width:973px){#D:before{content:\"973\";}}\n@media (min-width:974px){#D:before{content:\"974\";}}\n@media (min-width:975px){#D:before{content:\"975\";}}\n@media (min-width:976px){#D:before{content:\"976\";}}\n@media (min-width:977px){#D:before{content:\"977\";}}\n@media (min-width:978px){#D:before{content:\"978\";}}\n@media (min-width:979px){#D:before{content:\"979\";}}\n@media (min-width:980px){#D:before{content:\"980\";}}\n@media (min-width:981px){#D:before{content:\"981\";}}\n@media (min-width:982px){#D:before{content:\"982\";}}\n@media (min-width:983px){#D:before{content:\"983\";}}\n@media (min-width:984px){#D:before{content:\"984\";}}\n@media (min-width:985px){#D:before{content:\"985\";}}\n@media (min-width:986px){#D:before{content:\"986\";}}\n@media (min-width:987px){#D:before{content:\"987\";}}\n@media (min-width:988px){#D:before{content:\"988\";}}\n@media (min-width:989px){#D:before{content:\"989\";}}\n@media (min-width:990px){#D:before{content:\"990\";}}\n@media (min-width:991px){#D:before{content:\"991\";}}\n@media (min-width:992px){#D:before{content:\"992\";}}\n@media (min-width:993px){#D:before{content:\"993\";}}\n@media (min-width:994px){#D:before{content:\"994\";}}\n@media (min-width:995px){#D:before{content:\"995\";}}\n@media (min-width:996px){#D:before{content:\"996\";}}\n@media (min-width:997px){#D:before{content:\"997\";}}\n@media (min-width:998px){#D:before{content:\"998\";}}\n@media (min-width:999px){#D:before{content:\"999\";}}\n@media (min-width:1000px){#D:before{content:\"1000\";}}\n@media (min-width:1001px){#D:before{content:\"1001\";}}\n@media (min-width:1002px){#D:before{content:\"1002\";}}\n@media (min-width:1003px){#D:before{content:\"1003\";}}\n@media (min-width:1004px){#D:before{content:\"1004\";}}\n@media (min-width:1005px){#D:before{content:\"1005\";}}\n@media (min-width:1006px){#D:before{content:\"1006\";}}\n@media (min-width:1007px){#D:before{content:\"1007\";}}\n@media (min-width:1008px){#D:before{content:\"1008\";}}\n@media (min-width:1009px){#D:before{content:\"1009\";}}\n@media (min-width:1010px){#D:before{content:\"1010\";}}\n@media (min-width:1011px){#D:before{content:\"1011\";}}\n@media (min-width:1012px){#D:before{content:\"1012\";}}\n@media (min-width:1013px){#D:before{content:\"1013\";}}\n@media (min-width:1014px){#D:before{content:\"1014\";}}\n@media (min-width:1015px){#D:before{content:\"1015\";}}\n@media (min-width:1016px){#D:before{content:\"1016\";}}\n@media (min-width:1017px){#D:before{content:\"1017\";}}\n@media (min-width:1018px){#D:before{content:\"1018\";}}\n@media (min-width:1019px){#D:before{content:\"1019\";}}\n@media (min-width:1020px){#D:before{content:\"1020\";}}\n@media (min-width:1021px){#D:before{content:\"1021\";}}\n@media (min-width:1022px){#D:before{content:\"1022\";}}\n@media (min-width:1023px){#D:before{content:\"1023\";}}\n@media (min-width:1024px){#D:before{content:\"1024\";}}\n@media (min-width:1025px){#D:before{content:\"1025\";}}\n@media (min-width:1026px){#D:before{content:\"1026\";}}\n@media (min-width:1027px){#D:before{content:\"1027\";}}\n@media (min-width:1028px){#D:before{content:\"1028\";}}\n@media (min-width:1029px){#D:before{content:\"1029\";}}\n@media (min-width:1030px){#D:before{content:\"1030\";}}\n@media (min-width:1031px){#D:before{content:\"1031\";}}\n@media (min-width:1032px){#D:before{content:\"1032\";}}\n@media (min-width:1033px){#D:before{content:\"1033\";}}\n@media (min-width:1034px){#D:before{content:\"1034\";}}\n@media (min-width:1035px){#D:before{content:\"1035\";}}\n@media (min-width:1036px){#D:before{content:\"1036\";}}\n@media (min-width:1037px){#D:before{content:\"1037\";}}\n@media (min-width:1038px){#D:before{content:\"1038\";}}\n@media (min-width:1039px){#D:before{content:\"1039\";}}\n@media (min-width:1040px){#D:before{content:\"1040\";}}\n@media (min-width:1041px){#D:before{content:\"1041\";}}\n@media (min-width:1042px){#D:before{content:\"1042\";}}\n@media (min-width:1043px){#D:before{content:\"1043\";}}\n@media (min-width:1044px){#D:before{content:\"1044\";}}\n@media (min-width:1045px){#D:before{content:\"1045\";}}\n@media (min-width:1046px){#D:before{content:\"1046\";}}\n@media (min-width:1047px){#D:before{content:\"1047\";}}\n@media (min-width:1048px){#D:before{content:\"1048\";}}\n@media (min-width:1049px){#D:before{content:\"1049\";}}\n@media (min-width:1050px){#D:before{content:\"1050\";}}\n@media (min-width:1051px){#D:before{content:\"1051\";}}\n@media (min-width:1052px){#D:before{content:\"1052\";}}\n@media (min-width:1053px){#D:before{content:\"1053\";}}\n@media (min-width:1054px){#D:before{content:\"1054\";}}\n@media (min-width:1055px){#D:before{content:\"1055\";}}\n@media (min-width:1056px){#D:before{content:\"1056\";}}\n@media (min-width:1057px){#D:before{content:\"1057\";}}\n@media (min-width:1058px){#D:before{content:\"1058\";}}\n@media (min-width:1059px){#D:before{content:\"1059\";}}\n@media (min-width:1060px){#D:before{content:\"1060\";}}\n@media (min-width:1061px){#D:before{content:\"1061\";}}\n@media (min-width:1062px){#D:before{content:\"1062\";}}\n@media (min-width:1063px){#D:before{content:\"1063\";}}\n@media (min-width:1064px){#D:before{content:\"1064\";}}\n@media (min-width:1065px){#D:before{content:\"1065\";}}\n@media (min-width:1066px){#D:before{content:\"1066\";}}\n@media (min-width:1067px){#D:before{content:\"1067\";}}\n@media (min-width:1068px){#D:before{content:\"1068\";}}\n@media (min-width:1069px){#D:before{content:\"1069\";}}\n@media (min-width:1070px){#D:before{content:\"1070\";}}\n@media (min-width:1071px){#D:before{content:\"1071\";}}\n@media (min-width:1072px){#D:before{content:\"1072\";}}\n@media (min-width:1073px){#D:before{content:\"1073\";}}\n@media (min-width:1074px){#D:before{content:\"1074\";}}\n@media (min-width:1075px){#D:before{content:\"1075\";}}\n@media (min-width:1076px){#D:before{content:\"1076\";}}\n@media (min-width:1077px){#D:before{content:\"1077\";}}\n@media (min-width:1078px){#D:before{content:\"1078\";}}\n@media (min-width:1079px){#D:before{content:\"1079\";}}\n@media (min-width:1080px){#D:before{content:\"1080\";}}\n@media (min-width:1081px){#D:before{content:\"1081\";}}\n@media (min-width:1082px){#D:before{content:\"1082\";}}\n@media (min-width:1083px){#D:before{content:\"1083\";}}\n@media (min-width:1084px){#D:before{content:\"1084\";}}\n@media (min-width:1085px){#D:before{content:\"1085\";}}\n@media (min-width:1086px){#D:before{content:\"1086\";}}\n@media (min-width:1087px){#D:before{content:\"1087\";}}\n@media (min-width:1088px){#D:before{content:\"1088\";}}\n@media (min-width:1089px){#D:before{content:\"1089\";}}\n@media (min-width:1090px){#D:before{content:\"1090\";}}\n@media (min-width:1091px){#D:before{content:\"1091\";}}\n@media (min-width:1092px){#D:before{content:\"1092\";}}\n@media (min-width:1093px){#D:before{content:\"1093\";}}\n@media (min-width:1094px){#D:before{content:\"1094\";}}\n@media (min-width:1095px){#D:before{content:\"1095\";}}\n@media (min-width:1096px){#D:before{content:\"1096\";}}\n@media (min-width:1097px){#D:before{content:\"1097\";}}\n@media (min-width:1098px){#D:before{content:\"1098\";}}\n@media (min-width:1099px){#D:before{content:\"1099\";}}\n@media (min-width:1100px){#D:before{content:\"1100\";}}\n@media (min-width:1101px){#D:before{content:\"1101\";}}\n@media (min-width:1102px){#D:before{content:\"1102\";}}\n@media (min-width:1103px){#D:before{content:\"1103\";}}\n@media (min-width:1104px){#D:before{content:\"1104\";}}\n@media (min-width:1105px){#D:before{content:\"1105\";}}\n@media (min-width:1106px){#D:before{content:\"1106\";}}\n@media (min-width:1107px){#D:before{content:\"1107\";}}\n@media (min-width:1108px){#D:before{content:\"1108\";}}\n@media (min-width:1109px){#D:before{content:\"1109\";}}\n@media (min-width:1110px){#D:before{content:\"1110\";}}\n@media (min-width:1111px){#D:before{content:\"1111\";}}\n@media (min-width:1112px){#D:before{content:\"1112\";}}\n@media (min-width:1113px){#D:before{content:\"1113\";}}\n@media (min-width:1114px){#D:before{content:\"1114\";}}\n@media (min-width:1115px){#D:before{content:\"1115\";}}\n@media (min-width:1116px){#D:before{content:\"1116\";}}\n@media (min-width:1117px){#D:before{content:\"1117\";}}\n@media (min-width:1118px){#D:before{content:\"1118\";}}\n@media (min-width:1119px){#D:before{content:\"1119\";}}\n@media (min-width:1120px){#D:before{content:\"1120\";}}\n@media (min-width:1121px){#D:before{content:\"1121\";}}\n@media (min-width:1122px){#D:before{content:\"1122\";}}\n@media (min-width:1123px){#D:before{content:\"1123\";}}\n@media (min-width:1124px){#D:before{content:\"1124\";}}\n@media (min-width:1125px){#D:before{content:\"1125\";}}\n@media (min-width:1126px){#D:before{content:\"1126\";}}\n@media (min-width:1127px){#D:before{content:\"1127\";}}\n@media (min-width:1128px){#D:before{content:\"1128\";}}\n@media (min-width:1129px){#D:before{content:\"1129\";}}\n@media (min-width:1130px){#D:before{content:\"1130\";}}\n@media (min-width:1131px){#D:before{content:\"1131\";}}\n@media (min-width:1132px){#D:before{content:\"1132\";}}\n@media (min-width:1133px){#D:before{content:\"1133\";}}\n@media (min-width:1134px){#D:before{content:\"1134\";}}\n@media (min-width:1135px){#D:before{content:\"1135\";}}\n@media (min-width:1136px){#D:before{content:\"1136\";}}\n@media (min-width:1137px){#D:before{content:\"1137\";}}\n@media (min-width:1138px){#D:before{content:\"1138\";}}\n@media (min-width:1139px){#D:before{content:\"1139\";}}\n@media (min-width:1140px){#D:before{content:\"1140\";}}\n@media (min-width:1141px){#D:before{content:\"1141\";}}\n@media (min-width:1142px){#D:before{content:\"1142\";}}\n@media (min-width:1143px){#D:before{content:\"1143\";}}\n@media (min-width:1144px){#D:before{content:\"1144\";}}\n@media (min-width:1145px){#D:before{content:\"1145\";}}\n@media (min-width:1146px){#D:before{content:\"1146\";}}\n@media (min-width:1147px){#D:before{content:\"1147\";}}\n@media (min-width:1148px){#D:before{content:\"1148\";}}\n@media (min-width:1149px){#D:before{content:\"1149\";}}\n@media (min-width:1150px){#D:before{content:\"1150\";}}\n@media (min-width:1151px){#D:before{content:\"1151\";}}\n@media (min-width:1152px){#D:before{content:\"1152\";}}\n@media (min-width:1153px){#D:before{content:\"1153\";}}\n@media (min-width:1154px){#D:before{content:\"1154\";}}\n@media (min-width:1155px){#D:before{content:\"1155\";}}\n@media (min-width:1156px){#D:before{content:\"1156\";}}\n@media (min-width:1157px){#D:before{content:\"1157\";}}\n@media (min-width:1158px){#D:before{content:\"1158\";}}\n@media (min-width:1159px){#D:before{content:\"1159\";}}\n@media (min-width:1160px){#D:before{content:\"1160\";}}\n@media (min-width:1161px){#D:before{content:\"1161\";}}\n@media (min-width:1162px){#D:before{content:\"1162\";}}\n@media (min-width:1163px){#D:before{content:\"1163\";}}\n@media (min-width:1164px){#D:before{content:\"1164\";}}\n@media (min-width:1165px){#D:before{content:\"1165\";}}\n@media (min-width:1166px){#D:before{content:\"1166\";}}\n@media (min-width:1167px){#D:before{content:\"1167\";}}\n@media (min-width:1168px){#D:before{content:\"1168\";}}\n@media (min-width:1169px){#D:before{content:\"1169\";}}\n@media (min-width:1170px){#D:before{content:\"1170\";}}\n@media (min-width:1171px){#D:before{content:\"1171\";}}\n@media (min-width:1172px){#D:before{content:\"1172\";}}\n@media (min-width:1173px){#D:before{content:\"1173\";}}\n@media (min-width:1174px){#D:before{content:\"1174\";}}\n@media (min-width:1175px){#D:before{content:\"1175\";}}\n@media (min-width:1176px){#D:before{content:\"1176\";}}\n@media (min-width:1177px){#D:before{content:\"1177\";}}\n@media (min-width:1178px){#D:before{content:\"1178\";}}\n@media (min-width:1179px){#D:before{content:\"1179\";}}\n@media (min-width:1180px){#D:before{content:\"1180\";}}\n@media (min-width:1181px){#D:before{content:\"1181\";}}\n@media (min-width:1182px){#D:before{content:\"1182\";}}\n@media (min-width:1183px){#D:before{content:\"1183\";}}\n@media (min-width:1184px){#D:before{content:\"1184\";}}\n@media (min-width:1185px){#D:before{content:\"1185\";}}\n@media (min-width:1186px){#D:before{content:\"1186\";}}\n@media (min-width:1187px){#D:before{content:\"1187\";}}\n@media (min-width:1188px){#D:before{content:\"1188\";}}\n@media (min-width:1189px){#D:before{content:\"1189\";}}\n@media (min-width:1190px){#D:before{content:\"1190\";}}\n@media (min-width:1191px){#D:before{content:\"1191\";}}\n@media (min-width:1192px){#D:before{content:\"1192\";}}\n@media (min-width:1193px){#D:before{content:\"1193\";}}\n@media (min-width:1194px){#D:before{content:\"1194\";}}\n@media (min-width:1195px){#D:before{content:\"1195\";}}\n@media (min-width:1196px){#D:before{content:\"1196\";}}\n@media (min-width:1197px){#D:before{content:\"1197\";}}\n@media (min-width:1198px){#D:before{content:\"1198\";}}\n@media (min-width:1199px){#D:before{content:\"1199\";}}\n@media (min-width:1200px){#D:before{content:\"1200\";}}\n@media (min-width:1201px){#D:before{content:\"1201\";}}\n@media (min-width:1202px){#D:before{content:\"1202\";}}\n@media (min-width:1203px){#D:before{content:\"1203\";}}\n@media (min-width:1204px){#D:before{content:\"1204\";}}\n@media (min-width:1205px){#D:before{content:\"1205\";}}\n@media (min-width:1206px){#D:before{content:\"1206\";}}\n@media (min-width:1207px){#D:before{content:\"1207\";}}\n@media (min-width:1208px){#D:before{content:\"1208\";}}\n@media (min-width:1209px){#D:before{content:\"1209\";}}\n@media (min-width:1210px){#D:before{content:\"1210\";}}\n@media (min-width:1211px){#D:before{content:\"1211\";}}\n@media (min-width:1212px){#D:before{content:\"1212\";}}\n@media (min-width:1213px){#D:before{content:\"1213\";}}\n@media (min-width:1214px){#D:before{content:\"1214\";}}\n@media (min-width:1215px){#D:before{content:\"1215\";}}\n@media (min-width:1216px){#D:before{content:\"1216\";}}\n@media (min-width:1217px){#D:before{content:\"1217\";}}\n@media (min-width:1218px){#D:before{content:\"1218\";}}\n@media (min-width:1219px){#D:before{content:\"1219\";}}\n@media (min-width:1220px){#D:before{content:\"1220\";}}\n@media (min-width:1221px){#D:before{content:\"1221\";}}\n@media (min-width:1222px){#D:before{content:\"1222\";}}\n@media (min-width:1223px){#D:before{content:\"1223\";}}\n@media (min-width:1224px){#D:before{content:\"1224\";}}\n@media (min-width:1225px){#D:before{content:\"1225\";}}\n@media (min-width:1226px){#D:before{content:\"1226\";}}\n@media (min-width:1227px){#D:before{content:\"1227\";}}\n@media (min-width:1228px){#D:before{content:\"1228\";}}\n@media (min-width:1229px){#D:before{content:\"1229\";}}\n@media (min-width:1230px){#D:before{content:\"1230\";}}\n@media (min-width:1231px){#D:before{content:\"1231\";}}\n@media (min-width:1232px){#D:before{content:\"1232\";}}\n@media (min-width:1233px){#D:before{content:\"1233\";}}\n@media (min-width:1234px){#D:before{content:\"1234\";}}\n@media (min-width:1235px){#D:before{content:\"1235\";}}\n@media (min-width:1236px){#D:before{content:\"1236\";}}\n@media (min-width:1237px){#D:before{content:\"1237\";}}\n@media (min-width:1238px){#D:before{content:\"1238\";}}\n@media (min-width:1239px){#D:before{content:\"1239\";}}\n@media (min-width:1240px){#D:before{content:\"1240\";}}\n@media (min-width:1241px){#D:before{content:\"1241\";}}\n@media (min-width:1242px){#D:before{content:\"1242\";}}\n@media (min-width:1243px){#D:before{content:\"1243\";}}\n@media (min-width:1244px){#D:before{content:\"1244\";}}\n@media (min-width:1245px){#D:before{content:\"1245\";}}\n@media (min-width:1246px){#D:before{content:\"1246\";}}\n@media (min-width:1247px){#D:before{content:\"1247\";}}\n@media (min-width:1248px){#D:before{content:\"1248\";}}\n@media (min-width:1249px){#D:before{content:\"1249\";}}\n@media (min-width:1250px){#D:before{content:\"1250\";}}\n@media (min-width:1251px){#D:before{content:\"1251\";}}\n@media (min-width:1252px){#D:before{content:\"1252\";}}\n@media (min-width:1253px){#D:before{content:\"1253\";}}\n@media (min-width:1254px){#D:before{content:\"1254\";}}\n@media (min-width:1255px){#D:before{content:\"1255\";}}\n@media (min-width:1256px){#D:before{content:\"1256\";}}\n@media (min-width:1257px){#D:before{content:\"1257\";}}\n@media (min-width:1258px){#D:before{content:\"1258\";}}\n@media (min-width:1259px){#D:before{content:\"1259\";}}\n@media (min-width:1260px){#D:before{content:\"1260\";}}\n@media (min-width:1261px){#D:before{content:\"1261\";}}\n@media (min-width:1262px){#D:before{content:\"1262\";}}\n@media (min-width:1263px){#D:before{content:\"1263\";}}\n@media (min-width:1264px){#D:before{content:\"1264\";}}\n@media (min-width:1265px){#D:before{content:\"1265\";}}\n@media (min-width:1266px){#D:before{content:\"1266\";}}\n@media (min-width:1267px){#D:before{content:\"1267\";}}\n@media (min-width:1268px){#D:before{content:\"1268\";}}\n@media (min-width:1269px){#D:before{content:\"1269\";}}\n@media (min-width:1270px){#D:before{content:\"1270\";}}\n@media (min-width:1271px){#D:before{content:\"1271\";}}\n@media (min-width:1272px){#D:before{content:\"1272\";}}\n@media (min-width:1273px){#D:before{content:\"1273\";}}\n@media (min-width:1274px){#D:before{content:\"1274\";}}\n@media (min-width:1275px){#D:before{content:\"1275\";}}\n@media (min-width:1276px){#D:before{content:\"1276\";}}\n@media (min-width:1277px){#D:before{content:\"1277\";}}\n@media (min-width:1278px){#D:before{content:\"1278\";}}\n@media (min-width:1279px){#D:before{content:\"1279\";}}\n@media (min-width:1280px){#D:before{content:\"1280\";}}\n@media (min-width:1281px){#D:before{content:\"1281\";}}\n@media (min-width:1282px){#D:before{content:\"1282\";}}\n@media (min-width:1283px){#D:before{content:\"1283\";}}\n@media (min-width:1284px){#D:before{content:\"1284\";}}\n@media (min-width:1285px){#D:before{content:\"1285\";}}\n@media (min-width:1286px){#D:before{content:\"1286\";}}\n@media (min-width:1287px){#D:before{content:\"1287\";}}\n@media (min-width:1288px){#D:before{content:\"1288\";}}\n@media (min-width:1289px){#D:before{content:\"1289\";}}\n@media (min-width:1290px){#D:before{content:\"1290\";}}\n@media (min-width:1291px){#D:before{content:\"1291\";}}\n@media (min-width:1292px){#D:before{content:\"1292\";}}\n@media (min-width:1293px){#D:before{content:\"1293\";}}\n@media (min-width:1294px){#D:before{content:\"1294\";}}\n@media (min-width:1295px){#D:before{content:\"1295\";}}\n@media (min-width:1296px){#D:before{content:\"1296\";}}\n@media (min-width:1297px){#D:before{content:\"1297\";}}\n@media (min-width:1298px){#D:before{content:\"1298\";}}\n@media (min-width:1299px){#D:before{content:\"1299\";}}\n@media (min-width:1300px){#D:before{content:\"1300\";}}\n@media (min-width:1301px){#D:before{content:\"1301\";}}\n@media (min-width:1302px){#D:before{content:\"1302\";}}\n@media (min-width:1303px){#D:before{content:\"1303\";}}\n@media (min-width:1304px){#D:before{content:\"1304\";}}\n@media (min-width:1305px){#D:before{content:\"1305\";}}\n@media (min-width:1306px){#D:before{content:\"1306\";}}\n@media (min-width:1307px){#D:before{content:\"1307\";}}\n@media (min-width:1308px){#D:before{content:\"1308\";}}\n@media (min-width:1309px){#D:before{content:\"1309\";}}\n@media (min-width:1310px){#D:before{content:\"1310\";}}\n@media (min-width:1311px){#D:before{content:\"1311\";}}\n@media (min-width:1312px){#D:before{content:\"1312\";}}\n@media (min-width:1313px){#D:before{content:\"1313\";}}\n@media (min-width:1314px){#D:before{content:\"1314\";}}\n@media (min-width:1315px){#D:before{content:\"1315\";}}\n@media (min-width:1316px){#D:before{content:\"1316\";}}\n@media (min-width:1317px){#D:before{content:\"1317\";}}\n@media (min-width:1318px){#D:before{content:\"1318\";}}\n@media (min-width:1319px){#D:before{content:\"1319\";}}\n@media (min-width:1320px){#D:before{content:\"1320\";}}\n@media (min-width:1321px){#D:before{content:\"1321\";}}\n@media (min-width:1322px){#D:before{content:\"1322\";}}\n@media (min-width:1323px){#D:before{content:\"1323\";}}\n@media (min-width:1324px){#D:before{content:\"1324\";}}\n@media (min-width:1325px){#D:before{content:\"1325\";}}\n@media (min-width:1326px){#D:before{content:\"1326\";}}\n@media (min-width:1327px){#D:before{content:\"1327\";}}\n@media (min-width:1328px){#D:before{content:\"1328\";}}\n@media (min-width:1329px){#D:before{content:\"1329\";}}\n@media (min-width:1330px){#D:before{content:\"1330\";}}\n@media (min-width:1331px){#D:before{content:\"1331\";}}\n@media (min-width:1332px){#D:before{content:\"1332\";}}\n@media (min-width:1333px){#D:before{content:\"1333\";}}\n@media (min-width:1334px){#D:before{content:\"1334\";}}\n@media (min-width:1335px){#D:before{content:\"1335\";}}\n@media (min-width:1336px){#D:before{content:\"1336\";}}\n@media (min-width:1337px){#D:before{content:\"1337\";}}\n@media (min-width:1338px){#D:before{content:\"1338\";}}\n@media (min-width:1339px){#D:before{content:\"1339\";}}\n@media (min-width:1340px){#D:before{content:\"1340\";}}\n@media (min-width:1341px){#D:before{content:\"1341\";}}\n@media (min-width:1342px){#D:before{content:\"1342\";}}\n@media (min-width:1343px){#D:before{content:\"1343\";}}\n@media (min-width:1344px){#D:before{content:\"1344\";}}\n@media (min-width:1345px){#D:before{content:\"1345\";}}\n@media (min-width:1346px){#D:before{content:\"1346\";}}\n@media (min-width:1347px){#D:before{content:\"1347\";}}\n@media (min-width:1348px){#D:before{content:\"1348\";}}\n@media (min-width:1349px){#D:before{content:\"1349\";}}\n@media (min-width:1350px){#D:before{content:\"1350\";}}\n@media (min-width:1351px){#D:before{content:\"1351\";}}\n@media (min-width:1352px){#D:before{content:\"1352\";}}\n@media (min-width:1353px){#D:before{content:\"1353\";}}\n@media (min-width:1354px){#D:before{content:\"1354\";}}\n@media (min-width:1355px){#D:before{content:\"1355\";}}\n@media (min-width:1356px){#D:before{content:\"1356\";}}\n@media (min-width:1357px){#D:before{content:\"1357\";}}\n@media (min-width:1358px){#D:before{content:\"1358\";}}\n@media (min-width:1359px){#D:before{content:\"1359\";}}\n@media (min-width:1360px){#D:before{content:\"1360\";}}\n@media (min-width:1361px){#D:before{content:\"1361\";}}\n@media (min-width:1362px){#D:before{content:\"1362\";}}\n@media (min-width:1363px){#D:before{content:\"1363\";}}\n@media (min-width:1364px){#D:before{content:\"1364\";}}\n@media (min-width:1365px){#D:before{content:\"1365\";}}\n@media (min-width:1366px){#D:before{content:\"1366\";}}\n@media (min-width:1367px){#D:before{content:\"1367\";}}\n@media (min-width:1368px){#D:before{content:\"1368\";}}\n@media (min-width:1369px){#D:before{content:\"1369\";}}\n@media (min-width:1370px){#D:before{content:\"1370\";}}\n@media (min-width:1371px){#D:before{content:\"1371\";}}\n@media (min-width:1372px){#D:before{content:\"1372\";}}\n@media (min-width:1373px){#D:before{content:\"1373\";}}\n@media (min-width:1374px){#D:before{content:\"1374\";}}\n@media (min-width:1375px){#D:before{content:\"1375\";}}\n@media (min-width:1376px){#D:before{content:\"1376\";}}\n@media (min-width:1377px){#D:before{content:\"1377\";}}\n@media (min-width:1378px){#D:before{content:\"1378\";}}\n@media (min-width:1379px){#D:before{content:\"1379\";}}\n@media (min-width:1380px){#D:before{content:\"1380\";}}\n@media (min-width:1381px){#D:before{content:\"1381\";}}\n@media (min-width:1382px){#D:before{content:\"1382\";}}\n@media (min-width:1383px){#D:before{content:\"1383\";}}\n@media (min-width:1384px){#D:before{content:\"1384\";}}\n@media (min-width:1385px){#D:before{content:\"1385\";}}\n@media (min-width:1386px){#D:before{content:\"1386\";}}\n@media (min-width:1387px){#D:before{content:\"1387\";}}\n@media (min-width:1388px){#D:before{content:\"1388\";}}\n@media (min-width:1389px){#D:before{content:\"1389\";}}\n@media (min-width:1390px){#D:before{content:\"1390\";}}\n@media (min-width:1391px){#D:before{content:\"1391\";}}\n@media (min-width:1392px){#D:before{content:\"1392\";}}\n@media (min-width:1393px){#D:before{content:\"1393\";}}\n@media (min-width:1394px){#D:before{content:\"1394\";}}\n@media (min-width:1395px){#D:before{content:\"1395\";}}\n@media (min-width:1396px){#D:before{content:\"1396\";}}\n@media (min-width:1397px){#D:before{content:\"1397\";}}\n@media (min-width:1398px){#D:before{content:\"1398\";}}\n@media (min-width:1399px){#D:before{content:\"1399\";}}\n@media (min-width:1400px){#D:before{content:\"1400\";}}\n@media (min-width:1401px){#D:before{content:\"1401\";}}\n@media (min-width:1402px){#D:before{content:\"1402\";}}\n@media (min-width:1403px){#D:before{content:\"1403\";}}\n@media (min-width:1404px){#D:before{content:\"1404\";}}\n@media (min-width:1405px){#D:before{content:\"1405\";}}\n@media (min-width:1406px){#D:before{content:\"1406\";}}\n@media (min-width:1407px){#D:before{content:\"1407\";}}\n@media (min-width:1408px){#D:before{content:\"1408\";}}\n@media (min-width:1409px){#D:before{content:\"1409\";}}\n@media (min-width:1410px){#D:before{content:\"1410\";}}\n@media (min-width:1411px){#D:before{content:\"1411\";}}\n@media (min-width:1412px){#D:before{content:\"1412\";}}\n@media (min-width:1413px){#D:before{content:\"1413\";}}\n@media (min-width:1414px){#D:before{content:\"1414\";}}\n@media (min-width:1415px){#D:before{content:\"1415\";}}\n@media (min-width:1416px){#D:before{content:\"1416\";}}\n@media (min-width:1417px){#D:before{content:\"1417\";}}\n@media (min-width:1418px){#D:before{content:\"1418\";}}\n@media (min-width:1419px){#D:before{content:\"1419\";}}\n@media (min-width:1420px){#D:before{content:\"1420\";}}\n@media (min-width:1421px){#D:before{content:\"1421\";}}\n@media (min-width:1422px){#D:before{content:\"1422\";}}\n@media (min-width:1423px){#D:before{content:\"1423\";}}\n@media (min-width:1424px){#D:before{content:\"1424\";}}\n@media (min-width:1425px){#D:before{content:\"1425\";}}\n@media (min-width:1426px){#D:before{content:\"1426\";}}\n@media (min-width:1427px){#D:before{content:\"1427\";}}\n@media (min-width:1428px){#D:before{content:\"1428\";}}\n@media (min-width:1429px){#D:before{content:\"1429\";}}\n@media (min-width:1430px){#D:before{content:\"1430\";}}\n@media (min-width:1431px){#D:before{content:\"1431\";}}\n@media (min-width:1432px){#D:before{content:\"1432\";}}\n@media (min-width:1433px){#D:before{content:\"1433\";}}\n@media (min-width:1434px){#D:before{content:\"1434\";}}\n@media (min-width:1435px){#D:before{content:\"1435\";}}\n@media (min-width:1436px){#D:before{content:\"1436\";}}\n@media (min-width:1437px){#D:before{content:\"1437\";}}\n@media (min-width:1438px){#D:before{content:\"1438\";}}\n@media (min-width:1439px){#D:before{content:\"1439\";}}\n@media (min-width:1440px){#D:before{content:\"1440\";}}\n@media (min-width:1441px){#D:before{content:\"1441\";}}\n@media (min-width:1442px){#D:before{content:\"1442\";}}\n@media (min-width:1443px){#D:before{content:\"1443\";}}\n@media (min-width:1444px){#D:before{content:\"1444\";}}\n@media (min-width:1445px){#D:before{content:\"1445\";}}\n@media (min-width:1446px){#D:before{content:\"1446\";}}\n@media (min-width:1447px){#D:before{content:\"1447\";}}\n@media (min-width:1448px){#D:before{content:\"1448\";}}\n@media (min-width:1449px){#D:before{content:\"1449\";}}\n@media (min-width:1450px){#D:before{content:\"1450\";}}\n@media (min-width:1451px){#D:before{content:\"1451\";}}\n@media (min-width:1452px){#D:before{content:\"1452\";}}\n@media (min-width:1453px){#D:before{content:\"1453\";}}\n@media (min-width:1454px){#D:before{content:\"1454\";}}\n@media (min-width:1455px){#D:before{content:\"1455\";}}\n@media (min-width:1456px){#D:before{content:\"1456\";}}\n@media (min-width:1457px){#D:before{content:\"1457\";}}\n@media (min-width:1458px){#D:before{content:\"1458\";}}\n@media (min-width:1459px){#D:before{content:\"1459\";}}\n@media (min-width:1460px){#D:before{content:\"1460\";}}\n@media (min-width:1461px){#D:before{content:\"1461\";}}\n@media (min-width:1462px){#D:before{content:\"1462\";}}\n@media (min-width:1463px){#D:before{content:\"1463\";}}\n@media (min-width:1464px){#D:before{content:\"1464\";}}\n@media (min-width:1465px){#D:before{content:\"1465\";}}\n@media (min-width:1466px){#D:before{content:\"1466\";}}\n@media (min-width:1467px){#D:before{content:\"1467\";}}\n@media (min-width:1468px){#D:before{content:\"1468\";}}\n@media (min-width:1469px){#D:before{content:\"1469\";}}\n@media (min-width:1470px){#D:before{content:\"1470\";}}\n@media (min-width:1471px){#D:before{content:\"1471\";}}\n@media (min-width:1472px){#D:before{content:\"1472\";}}\n@media (min-width:1473px){#D:before{content:\"1473\";}}\n@media (min-width:1474px){#D:before{content:\"1474\";}}\n@media (min-width:1475px){#D:before{content:\"1475\";}}\n@media (min-width:1476px){#D:before{content:\"1476\";}}\n@media (min-width:1477px){#D:before{content:\"1477\";}}\n@media (min-width:1478px){#D:before{content:\"1478\";}}\n@media (min-width:1479px){#D:before{content:\"1479\";}}\n@media (min-width:1480px){#D:before{content:\"1480\";}}\n@media (min-width:1481px){#D:before{content:\"1481\";}}\n@media (min-width:1482px){#D:before{content:\"1482\";}}\n@media (min-width:1483px){#D:before{content:\"1483\";}}\n@media (min-width:1484px){#D:before{content:\"1484\";}}\n@media (min-width:1485px){#D:before{content:\"1485\";}}\n@media (min-width:1486px){#D:before{content:\"1486\";}}\n@media (min-width:1487px){#D:before{content:\"1487\";}}\n@media (min-width:1488px){#D:before{content:\"1488\";}}\n@media (min-width:1489px){#D:before{content:\"1489\";}}\n@media (min-width:1490px){#D:before{content:\"1490\";}}\n@media (min-width:1491px){#D:before{content:\"1491\";}}\n@media (min-width:1492px){#D:before{content:\"1492\";}}\n@media (min-width:1493px){#D:before{content:\"1493\";}}\n@media (min-width:1494px){#D:before{content:\"1494\";}}\n@media (min-width:1495px){#D:before{content:\"1495\";}}\n@media (min-width:1496px){#D:before{content:\"1496\";}}\n@media (min-width:1497px){#D:before{content:\"1497\";}}\n@media (min-width:1498px){#D:before{content:\"1498\";}}\n@media (min-width:1499px){#D:before{content:\"1499\";}}\n@media (min-width:1500px){#D:before{content:\"1500\";}}\n@media (min-width:1501px){#D:before{content:\"1501\";}}\n@media (min-width:1502px){#D:before{content:\"1502\";}}\n@media (min-width:1503px){#D:before{content:\"1503\";}}\n@media (min-width:1504px){#D:before{content:\"1504\";}}\n@media (min-width:1505px){#D:before{content:\"1505\";}}\n@media (min-width:1506px){#D:before{content:\"1506\";}}\n@media (min-width:1507px){#D:before{content:\"1507\";}}\n@media (min-width:1508px){#D:before{content:\"1508\";}}\n@media (min-width:1509px){#D:before{content:\"1509\";}}\n@media (min-width:1510px){#D:before{content:\"1510\";}}\n@media (min-width:1511px){#D:before{content:\"1511\";}}\n@media (min-width:1512px){#D:before{content:\"1512\";}}\n@media (min-width:1513px){#D:before{content:\"1513\";}}\n@media (min-width:1514px){#D:before{content:\"1514\";}}\n@media (min-width:1515px){#D:before{content:\"1515\";}}\n@media (min-width:1516px){#D:before{content:\"1516\";}}\n@media (min-width:1517px){#D:before{content:\"1517\";}}\n@media (min-width:1518px){#D:before{content:\"1518\";}}\n@media (min-width:1519px){#D:before{content:\"1519\";}}\n@media (min-width:1520px){#D:before{content:\"1520\";}}\n@media (min-width:1521px){#D:before{content:\"1521\";}}\n@media (min-width:1522px){#D:before{content:\"1522\";}}\n@media (min-width:1523px){#D:before{content:\"1523\";}}\n@media (min-width:1524px){#D:before{content:\"1524\";}}\n@media (min-width:1525px){#D:before{content:\"1525\";}}\n@media (min-width:1526px){#D:before{content:\"1526\";}}\n@media (min-width:1527px){#D:before{content:\"1527\";}}\n@media (min-width:1528px){#D:before{content:\"1528\";}}\n@media (min-width:1529px){#D:before{content:\"1529\";}}\n@media (min-width:1530px){#D:before{content:\"1530\";}}\n@media (min-width:1531px){#D:before{content:\"1531\";}}\n@media (min-width:1532px){#D:before{content:\"1532\";}}\n@media (min-width:1533px){#D:before{content:\"1533\";}}\n@media (min-width:1534px){#D:before{content:\"1534\";}}\n@media (min-width:1535px){#D:before{content:\"1535\";}}\n@media (min-width:1536px){#D:before{content:\"1536\";}}\n@media (min-width:1537px){#D:before{content:\"1537\";}}\n@media (min-width:1538px){#D:before{content:\"1538\";}}\n@media (min-width:1539px){#D:before{content:\"1539\";}}\n@media (min-width:1540px){#D:before{content:\"1540\";}}\n@media (min-width:1541px){#D:before{content:\"1541\";}}\n@media (min-width:1542px){#D:before{content:\"1542\";}}\n@media (min-width:1543px){#D:before{content:\"1543\";}}\n@media (min-width:1544px){#D:before{content:\"1544\";}}\n@media (min-width:1545px){#D:before{content:\"1545\";}}\n@media (min-width:1546px){#D:before{content:\"1546\";}}\n@media (min-width:1547px){#D:before{content:\"1547\";}}\n@media (min-width:1548px){#D:before{content:\"1548\";}}\n@media (min-width:1549px){#D:before{content:\"1549\";}}\n@media (min-width:1550px){#D:before{content:\"1550\";}}\n@media (min-width:1551px){#D:before{content:\"1551\";}}\n@media (min-width:1552px){#D:before{content:\"1552\";}}\n@media (min-width:1553px){#D:before{content:\"1553\";}}\n@media (min-width:1554px){#D:before{content:\"1554\";}}\n@media (min-width:1555px){#D:before{content:\"1555\";}}\n@media (min-width:1556px){#D:before{content:\"1556\";}}\n@media (min-width:1557px){#D:before{content:\"1557\";}}\n@media (min-width:1558px){#D:before{content:\"1558\";}}\n@media (min-width:1559px){#D:before{content:\"1559\";}}\n@media (min-width:1560px){#D:before{content:\"1560\";}}\n@media (min-width:1561px){#D:before{content:\"1561\";}}\n@media (min-width:1562px){#D:before{content:\"1562\";}}\n@media (min-width:1563px){#D:before{content:\"1563\";}}\n@media (min-width:1564px){#D:before{content:\"1564\";}}\n@media (min-width:1565px){#D:before{content:\"1565\";}}\n@media (min-width:1566px){#D:before{content:\"1566\";}}\n@media (min-width:1567px){#D:before{content:\"1567\";}}\n@media (min-width:1568px){#D:before{content:\"1568\";}}\n@media (min-width:1569px){#D:before{content:\"1569\";}}\n@media (min-width:1570px){#D:before{content:\"1570\";}}\n@media (min-width:1571px){#D:before{content:\"1571\";}}\n@media (min-width:1572px){#D:before{content:\"1572\";}}\n@media (min-width:1573px){#D:before{content:\"1573\";}}\n@media (min-width:1574px){#D:before{content:\"1574\";}}\n@media (min-width:1575px){#D:before{content:\"1575\";}}\n@media (min-width:1576px){#D:before{content:\"1576\";}}\n@media (min-width:1577px){#D:before{content:\"1577\";}}\n@media (min-width:1578px){#D:before{content:\"1578\";}}\n@media (min-width:1579px){#D:before{content:\"1579\";}}\n@media (min-width:1580px){#D:before{content:\"1580\";}}\n@media (min-width:1581px){#D:before{content:\"1581\";}}\n@media (min-width:1582px){#D:before{content:\"1582\";}}\n@media (min-width:1583px){#D:before{content:\"1583\";}}\n@media (min-width:1584px){#D:before{content:\"1584\";}}\n@media (min-width:1585px){#D:before{content:\"1585\";}}\n@media (min-width:1586px){#D:before{content:\"1586\";}}\n@media (min-width:1587px){#D:before{content:\"1587\";}}\n@media (min-width:1588px){#D:before{content:\"1588\";}}\n@media (min-width:1589px){#D:before{content:\"1589\";}}\n@media (min-width:1590px){#D:before{content:\"1590\";}}\n@media (min-width:1591px){#D:before{content:\"1591\";}}\n@media (min-width:1592px){#D:before{content:\"1592\";}}\n@media (min-width:1593px){#D:before{content:\"1593\";}}\n@media (min-width:1594px){#D:before{content:\"1594\";}}\n@media (min-width:1595px){#D:before{content:\"1595\";}}\n@media (min-width:1596px){#D:before{content:\"1596\";}}\n@media (min-width:1597px){#D:before{content:\"1597\";}}\n@media (min-width:1598px){#D:before{content:\"1598\";}}\n@media (min-width:1599px){#D:before{content:\"1599\";}}\n@media (min-width:1600px){#D:before{content:\"1600\";}}\n@media (min-width:1601px){#D:before{content:\"1601\";}}\n@media (min-width:1602px){#D:before{content:\"1602\";}}\n@media (min-width:1603px){#D:before{content:\"1603\";}}\n@media (min-width:1604px){#D:before{content:\"1604\";}}\n@media (min-width:1605px){#D:before{content:\"1605\";}}\n@media (min-width:1606px){#D:before{content:\"1606\";}}\n@media (min-width:1607px){#D:before{content:\"1607\";}}\n@media (min-width:1608px){#D:before{content:\"1608\";}}\n@media (min-width:1609px){#D:before{content:\"1609\";}}\n@media (min-width:1610px){#D:before{content:\"1610\";}}\n@media (min-width:1611px){#D:before{content:\"1611\";}}\n@media (min-width:1612px){#D:before{content:\"1612\";}}\n@media (min-width:1613px){#D:before{content:\"1613\";}}\n@media (min-width:1614px){#D:before{content:\"1614\";}}\n@media (min-width:1615px){#D:before{content:\"1615\";}}\n@media (min-width:1616px){#D:before{content:\"1616\";}}\n@media (min-width:1617px){#D:before{content:\"1617\";}}\n@media (min-width:1618px){#D:before{content:\"1618\";}}\n@media (min-width:1619px){#D:before{content:\"1619\";}}\n@media (min-width:1620px){#D:before{content:\"1620\";}}\n@media (min-width:1621px){#D:before{content:\"1621\";}}\n@media (min-width:1622px){#D:before{content:\"1622\";}}\n@media (min-width:1623px){#D:before{content:\"1623\";}}\n@media (min-width:1624px){#D:before{content:\"1624\";}}\n@media (min-width:1625px){#D:before{content:\"1625\";}}\n@media (min-width:1626px){#D:before{content:\"1626\";}}\n@media (min-width:1627px){#D:before{content:\"1627\";}}\n@media (min-width:1628px){#D:before{content:\"1628\";}}\n@media (min-width:1629px){#D:before{content:\"1629\";}}\n@media (min-width:1630px){#D:before{content:\"1630\";}}\n@media (min-width:1631px){#D:before{content:\"1631\";}}\n@media (min-width:1632px){#D:before{content:\"1632\";}}\n@media (min-width:1633px){#D:before{content:\"1633\";}}\n@media (min-width:1634px){#D:before{content:\"1634\";}}\n@media (min-width:1635px){#D:before{content:\"1635\";}}\n@media (min-width:1636px){#D:before{content:\"1636\";}}\n@media (min-width:1637px){#D:before{content:\"1637\";}}\n@media (min-width:1638px){#D:before{content:\"1638\";}}\n@media (min-width:1639px){#D:before{content:\"1639\";}}\n@media (min-width:1640px){#D:before{content:\"1640\";}}\n@media (min-width:1641px){#D:before{content:\"1641\";}}\n@media (min-width:1642px){#D:before{content:\"1642\";}}\n@media (min-width:1643px){#D:before{content:\"1643\";}}\n@media (min-width:1644px){#D:before{content:\"1644\";}}\n@media (min-width:1645px){#D:before{content:\"1645\";}}\n@media (min-width:1646px){#D:before{content:\"1646\";}}\n@media (min-width:1647px){#D:before{content:\"1647\";}}\n@media (min-width:1648px){#D:before{content:\"1648\";}}\n@media (min-width:1649px){#D:before{content:\"1649\";}}\n@media (min-width:1650px){#D:before{content:\"1650\";}}\n@media (min-width:1651px){#D:before{content:\"1651\";}}\n@media (min-width:1652px){#D:before{content:\"1652\";}}\n@media (min-width:1653px){#D:before{content:\"1653\";}}\n@media (min-width:1654px){#D:before{content:\"1654\";}}\n@media (min-width:1655px){#D:before{content:\"1655\";}}\n@media (min-width:1656px){#D:before{content:\"1656\";}}\n@media (min-width:1657px){#D:before{content:\"1657\";}}\n@media (min-width:1658px){#D:before{content:\"1658\";}}\n@media (min-width:1659px){#D:before{content:\"1659\";}}\n@media (min-width:1660px){#D:before{content:\"1660\";}}\n@media (min-width:1661px){#D:before{content:\"1661\";}}\n@media (min-width:1662px){#D:before{content:\"1662\";}}\n@media (min-width:1663px){#D:before{content:\"1663\";}}\n@media (min-width:1664px){#D:before{content:\"1664\";}}\n@media (min-width:1665px){#D:before{content:\"1665\";}}\n@media (min-width:1666px){#D:before{content:\"1666\";}}\n@media (min-width:1667px){#D:before{content:\"1667\";}}\n@media (min-width:1668px){#D:before{content:\"1668\";}}\n@media (min-width:1669px){#D:before{content:\"1669\";}}\n@media (min-width:1670px){#D:before{content:\"1670\";}}\n@media (min-width:1671px){#D:before{content:\"1671\";}}\n@media (min-width:1672px){#D:before{content:\"1672\";}}\n@media (min-width:1673px){#D:before{content:\"1673\";}}\n@media (min-width:1674px){#D:before{content:\"1674\";}}\n@media (min-width:1675px){#D:before{content:\"1675\";}}\n@media (min-width:1676px){#D:before{content:\"1676\";}}\n@media (min-width:1677px){#D:before{content:\"1677\";}}\n@media (min-width:1678px){#D:before{content:\"1678\";}}\n@media (min-width:1679px){#D:before{content:\"1679\";}}\n@media (min-width:1680px){#D:before{content:\"1680\";}}\n@media (min-width:1681px){#D:before{content:\"1681\";}}\n@media (min-width:1682px){#D:before{content:\"1682\";}}\n@media (min-width:1683px){#D:before{content:\"1683\";}}\n@media (min-width:1684px){#D:before{content:\"1684\";}}\n@media (min-width:1685px){#D:before{content:\"1685\";}}\n@media (min-width:1686px){#D:before{content:\"1686\";}}\n@media (min-width:1687px){#D:before{content:\"1687\";}}\n@media (min-width:1688px){#D:before{content:\"1688\";}}\n@media (min-width:1689px){#D:before{content:\"1689\";}}\n@media (min-width:1690px){#D:before{content:\"1690\";}}\n@media (min-width:1691px){#D:before{content:\"1691\";}}\n@media (min-width:1692px){#D:before{content:\"1692\";}}\n@media (min-width:1693px){#D:before{content:\"1693\";}}\n@media (min-width:1694px){#D:before{content:\"1694\";}}\n@media (min-width:1695px){#D:before{content:\"1695\";}}\n@media (min-width:1696px){#D:before{content:\"1696\";}}\n@media (min-width:1697px){#D:before{content:\"1697\";}}\n@media (min-width:1698px){#D:before{content:\"1698\";}}\n@media (min-width:1699px){#D:before{content:\"1699\";}}\n@media (min-width:1700px){#D:before{content:\"1700\";}}\n@media (min-width:1701px){#D:before{content:\"1701\";}}\n@media (min-width:1702px){#D:before{content:\"1702\";}}\n@media (min-width:1703px){#D:before{content:\"1703\";}}\n@media (min-width:1704px){#D:before{content:\"1704\";}}\n@media (min-width:1705px){#D:before{content:\"1705\";}}\n@media (min-width:1706px){#D:before{content:\"1706\";}}\n@media (min-width:1707px){#D:before{content:\"1707\";}}\n@media (min-width:1708px){#D:before{content:\"1708\";}}\n@media (min-width:1709px){#D:before{content:\"1709\";}}\n@media (min-width:1710px){#D:before{content:\"1710\";}}\n@media (min-width:1711px){#D:before{content:\"1711\";}}\n@media (min-width:1712px){#D:before{content:\"1712\";}}\n@media (min-width:1713px){#D:before{content:\"1713\";}}\n@media (min-width:1714px){#D:before{content:\"1714\";}}\n@media (min-width:1715px){#D:before{content:\"1715\";}}\n@media (min-width:1716px){#D:before{content:\"1716\";}}\n@media (min-width:1717px){#D:before{content:\"1717\";}}\n@media (min-width:1718px){#D:before{content:\"1718\";}}\n@media (min-width:1719px){#D:before{content:\"1719\";}}\n@media (min-width:1720px){#D:before{content:\"1720\";}}\n@media (min-width:1721px){#D:before{content:\"1721\";}}\n@media (min-width:1722px){#D:before{content:\"1722\";}}\n@media (min-width:1723px){#D:before{content:\"1723\";}}\n@media (min-width:1724px){#D:before{content:\"1724\";}}\n@media (min-width:1725px){#D:before{content:\"1725\";}}\n@media (min-width:1726px){#D:before{content:\"1726\";}}\n@media (min-width:1727px){#D:before{content:\"1727\";}}\n@media (min-width:1728px){#D:before{content:\"1728\";}}\n@media (min-width:1729px){#D:before{content:\"1729\";}}\n@media (min-width:1730px){#D:before{content:\"1730\";}}\n@media (min-width:1731px){#D:before{content:\"1731\";}}\n@media (min-width:1732px){#D:before{content:\"1732\";}}\n@media (min-width:1733px){#D:before{content:\"1733\";}}\n@media (min-width:1734px){#D:before{content:\"1734\";}}\n@media (min-width:1735px){#D:before{content:\"1735\";}}\n@media (min-width:1736px){#D:before{content:\"1736\";}}\n@media (min-width:1737px){#D:before{content:\"1737\";}}\n@media (min-width:1738px){#D:before{content:\"1738\";}}\n@media (min-width:1739px){#D:before{content:\"1739\";}}\n@media (min-width:1740px){#D:before{content:\"1740\";}}\n@media (min-width:1741px){#D:before{content:\"1741\";}}\n@media (min-width:1742px){#D:before{content:\"1742\";}}\n@media (min-width:1743px){#D:before{content:\"1743\";}}\n@media (min-width:1744px){#D:before{content:\"1744\";}}\n@media (min-width:1745px){#D:before{content:\"1745\";}}\n@media (min-width:1746px){#D:before{content:\"1746\";}}\n@media (min-width:1747px){#D:before{content:\"1747\";}}\n@media (min-width:1748px){#D:before{content:\"1748\";}}\n@media (min-width:1749px){#D:before{content:\"1749\";}}\n@media (min-width:1750px){#D:before{content:\"1750\";}}\n@media (min-width:1751px){#D:before{content:\"1751\";}}\n@media (min-width:1752px){#D:before{content:\"1752\";}}\n@media (min-width:1753px){#D:before{content:\"1753\";}}\n@media (min-width:1754px){#D:before{content:\"1754\";}}\n@media (min-width:1755px){#D:before{content:\"1755\";}}\n@media (min-width:1756px){#D:before{content:\"1756\";}}\n@media (min-width:1757px){#D:before{content:\"1757\";}}\n@media (min-width:1758px){#D:before{content:\"1758\";}}\n@media (min-width:1759px){#D:before{content:\"1759\";}}\n@media (min-width:1760px){#D:before{content:\"1760\";}}\n@media (min-width:1761px){#D:before{content:\"1761\";}}\n@media (min-width:1762px){#D:before{content:\"1762\";}}\n@media (min-width:1763px){#D:before{content:\"1763\";}}\n@media (min-width:1764px){#D:before{content:\"1764\";}}\n@media (min-width:1765px){#D:before{content:\"1765\";}}\n@media (min-width:1766px){#D:before{content:\"1766\";}}\n@media (min-width:1767px){#D:before{content:\"1767\";}}\n@media (min-width:1768px){#D:before{content:\"1768\";}}\n@media (min-width:1769px){#D:before{content:\"1769\";}}\n@media (min-width:1770px){#D:before{content:\"1770\";}}\n@media (min-width:1771px){#D:before{content:\"1771\";}}\n@media (min-width:1772px){#D:before{content:\"1772\";}}\n@media (min-width:1773px){#D:before{content:\"1773\";}}\n@media (min-width:1774px){#D:before{content:\"1774\";}}\n@media (min-width:1775px){#D:before{content:\"1775\";}}\n@media (min-width:1776px){#D:before{content:\"1776\";}}\n@media (min-width:1777px){#D:before{content:\"1777\";}}\n@media (min-width:1778px){#D:before{content:\"1778\";}}\n@media (min-width:1779px){#D:before{content:\"1779\";}}\n@media (min-width:1780px){#D:before{content:\"1780\";}}\n@media (min-width:1781px){#D:before{content:\"1781\";}}\n@media (min-width:1782px){#D:before{content:\"1782\";}}\n@media (min-width:1783px){#D:before{content:\"1783\";}}\n@media (min-width:1784px){#D:before{content:\"1784\";}}\n@media (min-width:1785px){#D:before{content:\"1785\";}}\n@media (min-width:1786px){#D:before{content:\"1786\";}}\n@media (min-width:1787px){#D:before{content:\"1787\";}}\n@media (min-width:1788px){#D:before{content:\"1788\";}}\n@media (min-width:1789px){#D:before{content:\"1789\";}}\n@media (min-width:1790px){#D:before{content:\"1790\";}}\n@media (min-width:1791px){#D:before{content:\"1791\";}}\n@media (min-width:1792px){#D:before{content:\"1792\";}}\n@media (min-width:1793px){#D:before{content:\"1793\";}}\n@media (min-width:1794px){#D:before{content:\"1794\";}}\n@media (min-width:1795px){#D:before{content:\"1795\";}}\n@media (min-width:1796px){#D:before{content:\"1796\";}}\n@media (min-width:1797px){#D:before{content:\"1797\";}}\n@media (min-width:1798px){#D:before{content:\"1798\";}}\n@media (min-width:1799px){#D:before{content:\"1799\";}}\n@media (min-width:1800px){#D:before{content:\"1800\";}}\n@media (min-width:1801px){#D:before{content:\"1801\";}}\n@media (min-width:1802px){#D:before{content:\"1802\";}}\n@media (min-width:1803px){#D:before{content:\"1803\";}}\n@media (min-width:1804px){#D:before{content:\"1804\";}}\n@media (min-width:1805px){#D:before{content:\"1805\";}}\n@media (min-width:1806px){#D:before{content:\"1806\";}}\n@media (min-width:1807px){#D:before{content:\"1807\";}}\n@media (min-width:1808px){#D:before{content:\"1808\";}}\n@media (min-width:1809px){#D:before{content:\"1809\";}}\n@media (min-width:1810px){#D:before{content:\"1810\";}}\n@media (min-width:1811px){#D:before{content:\"1811\";}}\n@media (min-width:1812px){#D:before{content:\"1812\";}}\n@media (min-width:1813px){#D:before{content:\"1813\";}}\n@media (min-width:1814px){#D:before{content:\"1814\";}}\n@media (min-width:1815px){#D:before{content:\"1815\";}}\n@media (min-width:1816px){#D:before{content:\"1816\";}}\n@media (min-width:1817px){#D:before{content:\"1817\";}}\n@media (min-width:1818px){#D:before{content:\"1818\";}}\n@media (min-width:1819px){#D:before{content:\"1819\";}}\n@media (min-width:1820px){#D:before{content:\"1820\";}}\n@media (min-width:1821px){#D:before{content:\"1821\";}}\n@media (min-width:1822px){#D:before{content:\"1822\";}}\n@media (min-width:1823px){#D:before{content:\"1823\";}}\n@media (min-width:1824px){#D:before{content:\"1824\";}}\n@media (min-width:1825px){#D:before{content:\"1825\";}}\n@media (min-width:1826px){#D:before{content:\"1826\";}}\n@media (min-width:1827px){#D:before{content:\"1827\";}}\n@media (min-width:1828px){#D:before{content:\"1828\";}}\n@media (min-width:1829px){#D:before{content:\"1829\";}}\n@media (min-width:1830px){#D:before{content:\"1830\";}}\n@media (min-width:1831px){#D:before{content:\"1831\";}}\n@media (min-width:1832px){#D:before{content:\"1832\";}}\n@media (min-width:1833px){#D:before{content:\"1833\";}}\n@media (min-width:1834px){#D:before{content:\"1834\";}}\n@media (min-width:1835px){#D:before{content:\"1835\";}}\n@media (min-width:1836px){#D:before{content:\"1836\";}}\n@media (min-width:1837px){#D:before{content:\"1837\";}}\n@media (min-width:1838px){#D:before{content:\"1838\";}}\n@media (min-width:1839px){#D:before{content:\"1839\";}}\n@media (min-width:1840px){#D:before{content:\"1840\";}}\n@media (min-width:1841px){#D:before{content:\"1841\";}}\n@media (min-width:1842px){#D:before{content:\"1842\";}}\n@media (min-width:1843px){#D:before{content:\"1843\";}}\n@media (min-width:1844px){#D:before{content:\"1844\";}}\n@media (min-width:1845px){#D:before{content:\"1845\";}}\n@media (min-width:1846px){#D:before{content:\"1846\";}}\n@media (min-width:1847px){#D:before{content:\"1847\";}}\n@media (min-width:1848px){#D:before{content:\"1848\";}}\n@media (min-width:1849px){#D:before{content:\"1849\";}}\n@media (min-width:1850px){#D:before{content:\"1850\";}}\n@media (min-width:1851px){#D:before{content:\"1851\";}}\n@media (min-width:1852px){#D:before{content:\"1852\";}}\n@media (min-width:1853px){#D:before{content:\"1853\";}}\n@media (min-width:1854px){#D:before{content:\"1854\";}}\n@media (min-width:1855px){#D:before{content:\"1855\";}}\n@media (min-width:1856px){#D:before{content:\"1856\";}}\n@media (min-width:1857px){#D:before{content:\"1857\";}}\n@media (min-width:1858px){#D:before{content:\"1858\";}}\n@media (min-width:1859px){#D:before{content:\"1859\";}}\n@media (min-width:1860px){#D:before{content:\"1860\";}}\n@media (min-width:1861px){#D:before{content:\"1861\";}}\n@media (min-width:1862px){#D:before{content:\"1862\";}}\n@media (min-width:1863px){#D:before{content:\"1863\";}}\n@media (min-width:1864px){#D:before{content:\"1864\";}}\n@media (min-width:1865px){#D:before{content:\"1865\";}}\n@media (min-width:1866px){#D:before{content:\"1866\";}}\n@media (min-width:1867px){#D:before{content:\"1867\";}}\n@media (min-width:1868px){#D:before{content:\"1868\";}}\n@media (min-width:1869px){#D:before{content:\"1869\";}}\n@media (min-width:1870px){#D:before{content:\"1870\";}}\n@media (min-width:1871px){#D:before{content:\"1871\";}}\n@media (min-width:1872px){#D:before{content:\"1872\";}}\n@media (min-width:1873px){#D:before{content:\"1873\";}}\n@media (min-width:1874px){#D:before{content:\"1874\";}}\n@media (min-width:1875px){#D:before{content:\"1875\";}}\n@media (min-width:1876px){#D:before{content:\"1876\";}}\n@media (min-width:1877px){#D:before{content:\"1877\";}}\n@media (min-width:1878px){#D:before{content:\"1878\";}}\n@media (min-width:1879px){#D:before{content:\"1879\";}}\n@media (min-width:1880px){#D:before{content:\"1880\";}}\n@media (min-width:1881px){#D:before{content:\"1881\";}}\n@media (min-width:1882px){#D:before{content:\"1882\";}}\n@media (min-width:1883px){#D:before{content:\"1883\";}}\n@media (min-width:1884px){#D:before{content:\"1884\";}}\n@media (min-width:1885px){#D:before{content:\"1885\";}}\n@media (min-width:1886px){#D:before{content:\"1886\";}}\n@media (min-width:1887px){#D:before{content:\"1887\";}}\n@media (min-width:1888px){#D:before{content:\"1888\";}}\n@media (min-width:1889px){#D:before{content:\"1889\";}}\n@media (min-width:1890px){#D:before{content:\"1890\";}}\n@media (min-width:1891px){#D:before{content:\"1891\";}}\n@media (min-width:1892px){#D:before{content:\"1892\";}}\n@media (min-width:1893px){#D:before{content:\"1893\";}}\n@media (min-width:1894px){#D:before{content:\"1894\";}}\n@media (min-width:1895px){#D:before{content:\"1895\";}}\n@media (min-width:1896px){#D:before{content:\"1896\";}}\n@media (min-width:1897px){#D:before{content:\"1897\";}}\n@media (min-width:1898px){#D:before{content:\"1898\";}}\n@media (min-width:1899px){#D:before{content:\"1899\";}}\n@media (min-width:1900px){#D:before{content:\"1900\";}}\n@media (min-width:1901px){#D:before{content:\"1901\";}}\n@media (min-width:1902px){#D:before{content:\"1902\";}}\n@media (min-width:1903px){#D:before{content:\"1903\";}}\n@media (min-width:1904px){#D:before{content:\"1904\";}}\n@media (min-width:1905px){#D:before{content:\"1905\";}}\n@media (min-width:1906px){#D:before{content:\"1906\";}}\n@media (min-width:1907px){#D:before{content:\"1907\";}}\n@media (min-width:1908px){#D:before{content:\"1908\";}}\n@media (min-width:1909px){#D:before{content:\"1909\";}}\n@media (min-width:1910px){#D:before{content:\"1910\";}}\n@media (min-width:1911px){#D:before{content:\"1911\";}}\n@media (min-width:1912px){#D:before{content:\"1912\";}}\n@media (min-width:1913px){#D:before{content:\"1913\";}}\n@media (min-width:1914px){#D:before{content:\"1914\";}}\n@media (min-width:1915px){#D:before{content:\"1915\";}}\n@media (min-width:1916px){#D:before{content:\"1916\";}}\n@media (min-width:1917px){#D:before{content:\"1917\";}}\n@media (min-width:1918px){#D:before{content:\"1918\";}}\n@media (min-width:1919px){#D:before{content:\"1919\";}}\n@media (min-width:1920px){#D:before{content:\"1920\";}}\n@media (min-width:1921px){#D:before{content:\"1921\";}}\n@media (min-width:1922px){#D:before{content:\"1922\";}}\n@media (min-width:1923px){#D:before{content:\"1923\";}}\n@media (min-width:1924px){#D:before{content:\"1924\";}}\n@media (min-width:1925px){#D:before{content:\"1925\";}}\n@media (min-width:1926px){#D:before{content:\"1926\";}}\n@media (min-width:1927px){#D:before{content:\"1927\";}}\n@media (min-width:1928px){#D:before{content:\"1928\";}}\n@media (min-width:1929px){#D:before{content:\"1929\";}}\n@media (min-width:1930px){#D:before{content:\"1930\";}}\n@media (min-width:1931px){#D:before{content:\"1931\";}}\n@media (min-width:1932px){#D:before{content:\"1932\";}}\n@media (min-width:1933px){#D:before{content:\"1933\";}}\n@media (min-width:1934px){#D:before{content:\"1934\";}}\n@media (min-width:1935px){#D:before{content:\"1935\";}}\n@media (min-width:1936px){#D:before{content:\"1936\";}}\n@media (min-width:1937px){#D:before{content:\"1937\";}}\n@media (min-width:1938px){#D:before{content:\"1938\";}}\n@media (min-width:1939px){#D:before{content:\"1939\";}}\n@media (min-width:1940px){#D:before{content:\"1940\";}}\n@media (min-width:1941px){#D:before{content:\"1941\";}}\n@media (min-width:1942px){#D:before{content:\"1942\";}}\n@media (min-width:1943px){#D:before{content:\"1943\";}}\n@media (min-width:1944px){#D:before{content:\"1944\";}}\n@media (min-width:1945px){#D:before{content:\"1945\";}}\n@media (min-width:1946px){#D:before{content:\"1946\";}}\n@media (min-width:1947px){#D:before{content:\"1947\";}}\n@media (min-width:1948px){#D:before{content:\"1948\";}}\n@media (min-width:1949px){#D:before{content:\"1949\";}}\n@media (min-width:1950px){#D:before{content:\"1950\";}}\n@media (min-width:1951px){#D:before{content:\"1951\";}}\n@media (min-width:1952px){#D:before{content:\"1952\";}}\n@media (min-width:1953px){#D:before{content:\"1953\";}}\n@media (min-width:1954px){#D:before{content:\"1954\";}}\n@media (min-width:1955px){#D:before{content:\"1955\";}}\n@media (min-width:1956px){#D:before{content:\"1956\";}}\n@media (min-width:1957px){#D:before{content:\"1957\";}}\n@media (min-width:1958px){#D:before{content:\"1958\";}}\n@media (min-width:1959px){#D:before{content:\"1959\";}}\n@media (min-width:1960px){#D:before{content:\"1960\";}}\n@media (min-width:1961px){#D:before{content:\"1961\";}}\n@media (min-width:1962px){#D:before{content:\"1962\";}}\n@media (min-width:1963px){#D:before{content:\"1963\";}}\n@media (min-width:1964px){#D:before{content:\"1964\";}}\n@media (min-width:1965px){#D:before{content:\"1965\";}}\n@media (min-width:1966px){#D:before{content:\"1966\";}}\n@media (min-width:1967px){#D:before{content:\"1967\";}}\n@media (min-width:1968px){#D:before{content:\"1968\";}}\n@media (min-width:1969px){#D:before{content:\"1969\";}}\n@media (min-width:1970px){#D:before{content:\"1970\";}}\n@media (min-width:1971px){#D:before{content:\"1971\";}}\n@media (min-width:1972px){#D:before{content:\"1972\";}}\n@media (min-width:1973px){#D:before{content:\"1973\";}}\n@media (min-width:1974px){#D:before{content:\"1974\";}}\n@media (min-width:1975px){#D:before{content:\"1975\";}}\n@media (min-width:1976px){#D:before{content:\"1976\";}}\n@media (min-width:1977px){#D:before{content:\"1977\";}}\n@media (min-width:1978px){#D:before{content:\"1978\";}}\n@media (min-width:1979px){#D:before{content:\"1979\";}}\n@media (min-width:1980px){#D:before{content:\"1980\";}}\n@media (min-width:1981px){#D:before{content:\"1981\";}}\n@media (min-width:1982px){#D:before{content:\"1982\";}}\n@media (min-width:1983px){#D:before{content:\"1983\";}}\n@media (min-width:1984px){#D:before{content:\"1984\";}}\n@media (min-width:1985px){#D:before{content:\"1985\";}}\n@media (min-width:1986px){#D:before{content:\"1986\";}}\n@media (min-width:1987px){#D:before{content:\"1987\";}}\n@media (min-width:1988px){#D:before{content:\"1988\";}}\n@media (min-width:1989px){#D:before{content:\"1989\";}}\n@media (min-width:1990px){#D:before{content:\"1990\";}}\n@media (min-width:1991px){#D:before{content:\"1991\";}}\n@media (min-width:1992px){#D:before{content:\"1992\";}}\n@media (min-width:1993px){#D:before{content:\"1993\";}}\n@media (min-width:1994px){#D:before{content:\"1994\";}}\n@media (min-width:1995px){#D:before{content:\"1995\";}}\n@media (min-width:1996px){#D:before{content:\"1996\";}}\n@media (min-width:1997px){#D:before{content:\"1997\";}}\n@media (min-width:1998px){#D:before{content:\"1998\";}}\n@media (min-width:1999px){#D:before{content:\"1999\";}}\n@media (min-width:2000px){#D:before{content:\"2000\";}}\n@media (min-width:2001px){#D:before{content:\"2001\";}}\n@media (min-width:2002px){#D:before{content:\"2002\";}}\n@media (min-width:2003px){#D:before{content:\"2003\";}}\n@media (min-width:2004px){#D:before{content:\"2004\";}}\n@media (min-width:2005px){#D:before{content:\"2005\";}}\n@media (min-width:2006px){#D:before{content:\"2006\";}}\n@media (min-width:2007px){#D:before{content:\"2007\";}}\n@media (min-width:2008px){#D:before{content:\"2008\";}}\n@media (min-width:2009px){#D:before{content:\"2009\";}}\n@media (min-width:2010px){#D:before{content:\"2010\";}}\n@media (min-width:2011px){#D:before{content:\"2011\";}}\n@media (min-width:2012px){#D:before{content:\"2012\";}}\n@media (min-width:2013px){#D:before{content:\"2013\";}}\n@media (min-width:2014px){#D:before{content:\"2014\";}}\n@media (min-width:2015px){#D:before{content:\"2015\";}}\n@media (min-width:2016px){#D:before{content:\"2016\";}}\n@media (min-width:2017px){#D:before{content:\"2017\";}}\n@media (min-width:2018px){#D:before{content:\"2018\";}}\n@media (min-width:2019px){#D:before{content:\"2019\";}}\n@media (min-width:2020px){#D:before{content:\"2020\";}}\n@media (min-width:2021px){#D:before{content:\"2021\";}}\n@media (min-width:2022px){#D:before{content:\"2022\";}}\n@media (min-width:2023px){#D:before{content:\"2023\";}}\n@media (min-width:2024px){#D:before{content:\"2024\";}}\n@media (min-width:2025px){#D:before{content:\"2025\";}}\n@media (min-width:2026px){#D:before{content:\"2026\";}}\n@media (min-width:2027px){#D:before{content:\"2027\";}}\n@media (min-width:2028px){#D:before{content:\"2028\";}}\n@media (min-width:2029px){#D:before{content:\"2029\";}}\n@media (min-width:2030px){#D:before{content:\"2030\";}}\n@media (min-width:2031px){#D:before{content:\"2031\";}}\n@media (min-width:2032px){#D:before{content:\"2032\";}}\n@media (min-width:2033px){#D:before{content:\"2033\";}}\n@media (min-width:2034px){#D:before{content:\"2034\";}}\n@media (min-width:2035px){#D:before{content:\"2035\";}}\n@media (min-width:2036px){#D:before{content:\"2036\";}}\n@media (min-width:2037px){#D:before{content:\"2037\";}}\n@media (min-width:2038px){#D:before{content:\"2038\";}}\n@media (min-width:2039px){#D:before{content:\"2039\";}}\n@media (min-width:2040px){#D:before{content:\"2040\";}}\n@media (min-width:2041px){#D:before{content:\"2041\";}}\n@media (min-width:2042px){#D:before{content:\"2042\";}}\n@media (min-width:2043px){#D:before{content:\"2043\";}}\n@media (min-width:2044px){#D:before{content:\"2044\";}}\n@media (min-width:2045px){#D:before{content:\"2045\";}}\n@media (min-width:2046px){#D:before{content:\"2046\";}}\n@media (min-width:2047px){#D:before{content:\"2047\";}}\n@media (min-width:2048px){#D:before{content:\"2048\";}}\n@media (min-width:2049px){#D:before{content:\"2049\";}}\n@media (min-width:2050px){#D:before{content:\"2050\";}}\n@media (min-width:2051px){#D:before{content:\"2051\";}}\n@media (min-width:2052px){#D:before{content:\"2052\";}}\n@media (min-width:2053px){#D:before{content:\"2053\";}}\n@media (min-width:2054px){#D:before{content:\"2054\";}}\n@media (min-width:2055px){#D:before{content:\"2055\";}}\n@media (min-width:2056px){#D:before{content:\"2056\";}}\n@media (min-width:2057px){#D:before{content:\"2057\";}}\n@media (min-width:2058px){#D:before{content:\"2058\";}}\n@media (min-width:2059px){#D:before{content:\"2059\";}}\n@media (min-width:2060px){#D:before{content:\"2060\";}}\n@media (min-width:2061px){#D:before{content:\"2061\";}}\n@media (min-width:2062px){#D:before{content:\"2062\";}}\n@media (min-width:2063px){#D:before{content:\"2063\";}}\n@media (min-width:2064px){#D:before{content:\"2064\";}}\n@media (min-width:2065px){#D:before{content:\"2065\";}}\n@media (min-width:2066px){#D:before{content:\"2066\";}}\n@media (min-width:2067px){#D:before{content:\"2067\";}}\n@media (min-width:2068px){#D:before{content:\"2068\";}}\n@media (min-width:2069px){#D:before{content:\"2069\";}}\n@media (min-width:2070px){#D:before{content:\"2070\";}}\n@media (min-width:2071px){#D:before{content:\"2071\";}}\n@media (min-width:2072px){#D:before{content:\"2072\";}}\n@media (min-width:2073px){#D:before{content:\"2073\";}}\n@media (min-width:2074px){#D:before{content:\"2074\";}}\n@media (min-width:2075px){#D:before{content:\"2075\";}}\n@media (min-width:2076px){#D:before{content:\"2076\";}}\n@media (min-width:2077px){#D:before{content:\"2077\";}}\n@media (min-width:2078px){#D:before{content:\"2078\";}}\n@media (min-width:2079px){#D:before{content:\"2079\";}}\n@media (min-width:2080px){#D:before{content:\"2080\";}}\n@media (min-width:2081px){#D:before{content:\"2081\";}}\n@media (min-width:2082px){#D:before{content:\"2082\";}}\n@media (min-width:2083px){#D:before{content:\"2083\";}}\n@media (min-width:2084px){#D:before{content:\"2084\";}}\n@media (min-width:2085px){#D:before{content:\"2085\";}}\n@media (min-width:2086px){#D:before{content:\"2086\";}}\n@media (min-width:2087px){#D:before{content:\"2087\";}}\n@media (min-width:2088px){#D:before{content:\"2088\";}}\n@media (min-width:2089px){#D:before{content:\"2089\";}}\n@media (min-width:2090px){#D:before{content:\"2090\";}}\n@media (min-width:2091px){#D:before{content:\"2091\";}}\n@media (min-width:2092px){#D:before{content:\"2092\";}}\n@media (min-width:2093px){#D:before{content:\"2093\";}}\n@media (min-width:2094px){#D:before{content:\"2094\";}}\n@media (min-width:2095px){#D:before{content:\"2095\";}}\n@media (min-width:2096px){#D:before{content:\"2096\";}}\n@media (min-width:2097px){#D:before{content:\"2097\";}}\n@media (min-width:2098px){#D:before{content:\"2098\";}}\n@media (min-width:2099px){#D:before{content:\"2099\";}}\n@media (min-width:2100px){#D:before{content:\"2100\";}}\n@media (min-width:2101px){#D:before{content:\"2101\";}}\n@media (min-width:2102px){#D:before{content:\"2102\";}}\n@media (min-width:2103px){#D:before{content:\"2103\";}}\n@media (min-width:2104px){#D:before{content:\"2104\";}}\n@media (min-width:2105px){#D:before{content:\"2105\";}}\n@media (min-width:2106px){#D:before{content:\"2106\";}}\n@media (min-width:2107px){#D:before{content:\"2107\";}}\n@media (min-width:2108px){#D:before{content:\"2108\";}}\n@media (min-width:2109px){#D:before{content:\"2109\";}}\n@media (min-width:2110px){#D:before{content:\"2110\";}}\n@media (min-width:2111px){#D:before{content:\"2111\";}}\n@media (min-width:2112px){#D:before{content:\"2112\";}}\n@media (min-width:2113px){#D:before{content:\"2113\";}}\n@media (min-width:2114px){#D:before{content:\"2114\";}}\n@media (min-width:2115px){#D:before{content:\"2115\";}}\n@media (min-width:2116px){#D:before{content:\"2116\";}}\n@media (min-width:2117px){#D:before{content:\"2117\";}}\n@media (min-width:2118px){#D:before{content:\"2118\";}}\n@media (min-width:2119px){#D:before{content:\"2119\";}}\n@media (min-width:2120px){#D:before{content:\"2120\";}}\n@media (min-width:2121px){#D:before{content:\"2121\";}}\n@media (min-width:2122px){#D:before{content:\"2122\";}}\n@media (min-width:2123px){#D:before{content:\"2123\";}}\n@media (min-width:2124px){#D:before{content:\"2124\";}}\n@media (min-width:2125px){#D:before{content:\"2125\";}}\n@media (min-width:2126px){#D:before{content:\"2126\";}}\n@media (min-width:2127px){#D:before{content:\"2127\";}}\n@media (min-width:2128px){#D:before{content:\"2128\";}}\n@media (min-width:2129px){#D:before{content:\"2129\";}}\n@media (min-width:2130px){#D:before{content:\"2130\";}}\n@media (min-width:2131px){#D:before{content:\"2131\";}}\n@media (min-width:2132px){#D:before{content:\"2132\";}}\n@media (min-width:2133px){#D:before{content:\"2133\";}}\n@media (min-width:2134px){#D:before{content:\"2134\";}}\n@media (min-width:2135px){#D:before{content:\"2135\";}}\n@media (min-width:2136px){#D:before{content:\"2136\";}}\n@media (min-width:2137px){#D:before{content:\"2137\";}}\n@media (min-width:2138px){#D:before{content:\"2138\";}}\n@media (min-width:2139px){#D:before{content:\"2139\";}}\n@media (min-width:2140px){#D:before{content:\"2140\";}}\n@media (min-width:2141px){#D:before{content:\"2141\";}}\n@media (min-width:2142px){#D:before{content:\"2142\";}}\n@media (min-width:2143px){#D:before{content:\"2143\";}}\n@media (min-width:2144px){#D:before{content:\"2144\";}}\n@media (min-width:2145px){#D:before{content:\"2145\";}}\n@media (min-width:2146px){#D:before{content:\"2146\";}}\n@media (min-width:2147px){#D:before{content:\"2147\";}}\n@media (min-width:2148px){#D:before{content:\"2148\";}}\n@media (min-width:2149px){#D:before{content:\"2149\";}}\n@media (min-width:2150px){#D:before{content:\"2150\";}}\n@media (min-width:2151px){#D:before{content:\"2151\";}}\n@media (min-width:2152px){#D:before{content:\"2152\";}}\n@media (min-width:2153px){#D:before{content:\"2153\";}}\n@media (min-width:2154px){#D:before{content:\"2154\";}}\n@media (min-width:2155px){#D:before{content:\"2155\";}}\n@media (min-width:2156px){#D:before{content:\"2156\";}}\n@media (min-width:2157px){#D:before{content:\"2157\";}}\n@media (min-width:2158px){#D:before{content:\"2158\";}}\n@media (min-width:2159px){#D:before{content:\"2159\";}}\n@media (min-width:2160px){#D:before{content:\"2160\";}}\n@media (min-width:2161px){#D:before{content:\"2161\";}}\n@media (min-width:2162px){#D:before{content:\"2162\";}}\n@media (min-width:2163px){#D:before{content:\"2163\";}}\n@media (min-width:2164px){#D:before{content:\"2164\";}}\n@media (min-width:2165px){#D:before{content:\"2165\";}}\n@media (min-width:2166px){#D:before{content:\"2166\";}}\n@media (min-width:2167px){#D:before{content:\"2167\";}}\n@media (min-width:2168px){#D:before{content:\"2168\";}}\n@media (min-width:2169px){#D:before{content:\"2169\";}}\n@media (min-width:2170px){#D:before{content:\"2170\";}}\n@media (min-width:2171px){#D:before{content:\"2171\";}}\n@media (min-width:2172px){#D:before{content:\"2172\";}}\n@media (min-width:2173px){#D:before{content:\"2173\";}}\n@media (min-width:2174px){#D:before{content:\"2174\";}}\n@media (min-width:2175px){#D:before{content:\"2175\";}}\n@media (min-width:2176px){#D:before{content:\"2176\";}}\n@media (min-width:2177px){#D:before{content:\"2177\";}}\n@media (min-width:2178px){#D:before{content:\"2178\";}}\n@media (min-width:2179px){#D:before{content:\"2179\";}}\n@media (min-width:2180px){#D:before{content:\"2180\";}}\n@media (min-width:2181px){#D:before{content:\"2181\";}}\n@media (min-width:2182px){#D:before{content:\"2182\";}}\n@media (min-width:2183px){#D:before{content:\"2183\";}}\n@media (min-width:2184px){#D:before{content:\"2184\";}}\n@media (min-width:2185px){#D:before{content:\"2185\";}}\n@media (min-width:2186px){#D:before{content:\"2186\";}}\n@media (min-width:2187px){#D:before{content:\"2187\";}}\n@media (min-width:2188px){#D:before{content:\"2188\";}}\n@media (min-width:2189px){#D:before{content:\"2189\";}}\n@media (min-width:2190px){#D:before{content:\"2190\";}}\n@media (min-width:2191px){#D:before{content:\"2191\";}}\n@media (min-width:2192px){#D:before{content:\"2192\";}}\n@media (min-width:2193px){#D:before{content:\"2193\";}}\n@media (min-width:2194px){#D:before{content:\"2194\";}}\n@media (min-width:2195px){#D:before{content:\"2195\";}}\n@media (min-width:2196px){#D:before{content:\"2196\";}}\n@media (min-width:2197px){#D:before{content:\"2197\";}}\n@media (min-width:2198px){#D:before{content:\"2198\";}}\n@media (min-width:2199px){#D:before{content:\"2199\";}}\n@media (min-width:2200px){#D:before{content:\"2200\";}}\n@media (min-width:2201px){#D:before{content:\"2201\";}}\n@media (min-width:2202px){#D:before{content:\"2202\";}}\n@media (min-width:2203px){#D:before{content:\"2203\";}}\n@media (min-width:2204px){#D:before{content:\"2204\";}}\n@media (min-width:2205px){#D:before{content:\"2205\";}}\n@media (min-width:2206px){#D:before{content:\"2206\";}}\n@media (min-width:2207px){#D:before{content:\"2207\";}}\n@media (min-width:2208px){#D:before{content:\"2208\";}}\n@media (min-width:2209px){#D:before{content:\"2209\";}}\n@media (min-width:2210px){#D:before{content:\"2210\";}}\n@media (min-width:2211px){#D:before{content:\"2211\";}}\n@media (min-width:2212px){#D:before{content:\"2212\";}}\n@media (min-width:2213px){#D:before{content:\"2213\";}}\n@media (min-width:2214px){#D:before{content:\"2214\";}}\n@media (min-width:2215px){#D:before{content:\"2215\";}}\n@media (min-width:2216px){#D:before{content:\"2216\";}}\n@media (min-width:2217px){#D:before{content:\"2217\";}}\n@media (min-width:2218px){#D:before{content:\"2218\";}}\n@media (min-width:2219px){#D:before{content:\"2219\";}}\n@media (min-width:2220px){#D:before{content:\"2220\";}}\n@media (min-width:2221px){#D:before{content:\"2221\";}}\n@media (min-width:2222px){#D:before{content:\"2222\";}}\n@media (min-width:2223px){#D:before{content:\"2223\";}}\n@media (min-width:2224px){#D:before{content:\"2224\";}}\n@media (min-width:2225px){#D:before{content:\"2225\";}}\n@media (min-width:2226px){#D:before{content:\"2226\";}}\n@media (min-width:2227px){#D:before{content:\"2227\";}}\n@media (min-width:2228px){#D:before{content:\"2228\";}}\n@media (min-width:2229px){#D:before{content:\"2229\";}}\n@media (min-width:2230px){#D:before{content:\"2230\";}}\n@media (min-width:2231px){#D:before{content:\"2231\";}}\n@media (min-width:2232px){#D:before{content:\"2232\";}}\n@media (min-width:2233px){#D:before{content:\"2233\";}}\n@media (min-width:2234px){#D:before{content:\"2234\";}}\n@media (min-width:2235px){#D:before{content:\"2235\";}}\n@media (min-width:2236px){#D:before{content:\"2236\";}}\n@media (min-width:2237px){#D:before{content:\"2237\";}}\n@media (min-width:2238px){#D:before{content:\"2238\";}}\n@media (min-width:2239px){#D:before{content:\"2239\";}}\n@media (min-width:2240px){#D:before{content:\"2240\";}}\n@media (min-width:2241px){#D:before{content:\"2241\";}}\n@media (min-width:2242px){#D:before{content:\"2242\";}}\n@media (min-width:2243px){#D:before{content:\"2243\";}}\n@media (min-width:2244px){#D:before{content:\"2244\";}}\n@media (min-width:2245px){#D:before{content:\"2245\";}}\n@media (min-width:2246px){#D:before{content:\"2246\";}}\n@media (min-width:2247px){#D:before{content:\"2247\";}}\n@media (min-width:2248px){#D:before{content:\"2248\";}}\n@media (min-width:2249px){#D:before{content:\"2249\";}}\n@media (min-width:2250px){#D:before{content:\"2250\";}}\n@media (min-width:2251px){#D:before{content:\"2251\";}}\n@media (min-width:2252px){#D:before{content:\"2252\";}}\n@media (min-width:2253px){#D:before{content:\"2253\";}}\n@media (min-width:2254px){#D:before{content:\"2254\";}}\n@media (min-width:2255px){#D:before{content:\"2255\";}}\n@media (min-width:2256px){#D:before{content:\"2256\";}}\n@media (min-width:2257px){#D:before{content:\"2257\";}}\n@media (min-width:2258px){#D:before{content:\"2258\";}}\n@media (min-width:2259px){#D:before{content:\"2259\";}}\n@media (min-width:2260px){#D:before{content:\"2260\";}}\n@media (min-width:2261px){#D:before{content:\"2261\";}}\n@media (min-width:2262px){#D:before{content:\"2262\";}}\n@media (min-width:2263px){#D:before{content:\"2263\";}}\n@media (min-width:2264px){#D:before{content:\"2264\";}}\n@media (min-width:2265px){#D:before{content:\"2265\";}}\n@media (min-width:2266px){#D:before{content:\"2266\";}}\n@media (min-width:2267px){#D:before{content:\"2267\";}}\n@media (min-width:2268px){#D:before{content:\"2268\";}}\n@media (min-width:2269px){#D:before{content:\"2269\";}}\n@media (min-width:2270px){#D:before{content:\"2270\";}}\n@media (min-width:2271px){#D:before{content:\"2271\";}}\n@media (min-width:2272px){#D:before{content:\"2272\";}}\n@media (min-width:2273px){#D:before{content:\"2273\";}}\n@media (min-width:2274px){#D:before{content:\"2274\";}}\n@media (min-width:2275px){#D:before{content:\"2275\";}}\n@media (min-width:2276px){#D:before{content:\"2276\";}}\n@media (min-width:2277px){#D:before{content:\"2277\";}}\n@media (min-width:2278px){#D:before{content:\"2278\";}}\n@media (min-width:2279px){#D:before{content:\"2279\";}}\n@media (min-width:2280px){#D:before{content:\"2280\";}}\n@media (min-width:2281px){#D:before{content:\"2281\";}}\n@media (min-width:2282px){#D:before{content:\"2282\";}}\n@media (min-width:2283px){#D:before{content:\"2283\";}}\n@media (min-width:2284px){#D:before{content:\"2284\";}}\n@media (min-width:2285px){#D:before{content:\"2285\";}}\n@media (min-width:2286px){#D:before{content:\"2286\";}}\n@media (min-width:2287px){#D:before{content:\"2287\";}}\n@media (min-width:2288px){#D:before{content:\"2288\";}}\n@media (min-width:2289px){#D:before{content:\"2289\";}}\n@media (min-width:2290px){#D:before{content:\"2290\";}}\n@media (min-width:2291px){#D:before{content:\"2291\";}}\n@media (min-width:2292px){#D:before{content:\"2292\";}}\n@media (min-width:2293px){#D:before{content:\"2293\";}}\n@media (min-width:2294px){#D:before{content:\"2294\";}}\n@media (min-width:2295px){#D:before{content:\"2295\";}}\n@media (min-width:2296px){#D:before{content:\"2296\";}}\n@media (min-width:2297px){#D:before{content:\"2297\";}}\n@media (min-width:2298px){#D:before{content:\"2298\";}}\n@media (min-width:2299px){#D:before{content:\"2299\";}}\n@media (min-width:2300px){#D:before{content:\"2300\";}}\n@media (min-width:2301px){#D:before{content:\"2301\";}}\n@media (min-width:2302px){#D:before{content:\"2302\";}}\n@media (min-width:2303px){#D:before{content:\"2303\";}}\n@media (min-width:2304px){#D:before{content:\"2304\";}}\n@media (min-width:2305px){#D:before{content:\"2305\";}}\n@media (min-width:2306px){#D:before{content:\"2306\";}}\n@media (min-width:2307px){#D:before{content:\"2307\";}}\n@media (min-width:2308px){#D:before{content:\"2308\";}}\n@media (min-width:2309px){#D:before{content:\"2309\";}}\n@media (min-width:2310px){#D:before{content:\"2310\";}}\n@media (min-width:2311px){#D:before{content:\"2311\";}}\n@media (min-width:2312px){#D:before{content:\"2312\";}}\n@media (min-width:2313px){#D:before{content:\"2313\";}}\n@media (min-width:2314px){#D:before{content:\"2314\";}}\n@media (min-width:2315px){#D:before{content:\"2315\";}}\n@media (min-width:2316px){#D:before{content:\"2316\";}}\n@media (min-width:2317px){#D:before{content:\"2317\";}}\n@media (min-width:2318px){#D:before{content:\"2318\";}}\n@media (min-width:2319px){#D:before{content:\"2319\";}}\n@media (min-width:2320px){#D:before{content:\"2320\";}}\n@media (min-width:2321px){#D:before{content:\"2321\";}}\n@media (min-width:2322px){#D:before{content:\"2322\";}}\n@media (min-width:2323px){#D:before{content:\"2323\";}}\n@media (min-width:2324px){#D:before{content:\"2324\";}}\n@media (min-width:2325px){#D:before{content:\"2325\";}}\n@media (min-width:2326px){#D:before{content:\"2326\";}}\n@media (min-width:2327px){#D:before{content:\"2327\";}}\n@media (min-width:2328px){#D:before{content:\"2328\";}}\n@media (min-width:2329px){#D:before{content:\"2329\";}}\n@media (min-width:2330px){#D:before{content:\"2330\";}}\n@media (min-width:2331px){#D:before{content:\"2331\";}}\n@media (min-width:2332px){#D:before{content:\"2332\";}}\n@media (min-width:2333px){#D:before{content:\"2333\";}}\n@media (min-width:2334px){#D:before{content:\"2334\";}}\n@media (min-width:2335px){#D:before{content:\"2335\";}}\n@media (min-width:2336px){#D:before{content:\"2336\";}}\n@media (min-width:2337px){#D:before{content:\"2337\";}}\n@media (min-width:2338px){#D:before{content:\"2338\";}}\n@media (min-width:2339px){#D:before{content:\"2339\";}}\n@media (min-width:2340px){#D:before{content:\"2340\";}}\n@media (min-width:2341px){#D:before{content:\"2341\";}}\n@media (min-width:2342px){#D:before{content:\"2342\";}}\n@media (min-width:2343px){#D:before{content:\"2343\";}}\n@media (min-width:2344px){#D:before{content:\"2344\";}}\n@media (min-width:2345px){#D:before{content:\"2345\";}}\n@media (min-width:2346px){#D:before{content:\"2346\";}}\n@media (min-width:2347px){#D:before{content:\"2347\";}}\n@media (min-width:2348px){#D:before{content:\"2348\";}}\n@media (min-width:2349px){#D:before{content:\"2349\";}}\n@media (min-width:2350px){#D:before{content:\"2350\";}}\n@media (min-width:2351px){#D:before{content:\"2351\";}}\n@media (min-width:2352px){#D:before{content:\"2352\";}}\n@media (min-width:2353px){#D:before{content:\"2353\";}}\n@media (min-width:2354px){#D:before{content:\"2354\";}}\n@media (min-width:2355px){#D:before{content:\"2355\";}}\n@media (min-width:2356px){#D:before{content:\"2356\";}}\n@media (min-width:2357px){#D:before{content:\"2357\";}}\n@media (min-width:2358px){#D:before{content:\"2358\";}}\n@media (min-width:2359px){#D:before{content:\"2359\";}}\n@media (min-width:2360px){#D:before{content:\"2360\";}}\n@media (min-width:2361px){#D:before{content:\"2361\";}}\n@media (min-width:2362px){#D:before{content:\"2362\";}}\n@media (min-width:2363px){#D:before{content:\"2363\";}}\n@media (min-width:2364px){#D:before{content:\"2364\";}}\n@media (min-width:2365px){#D:before{content:\"2365\";}}\n@media (min-width:2366px){#D:before{content:\"2366\";}}\n@media (min-width:2367px){#D:before{content:\"2367\";}}\n@media (min-width:2368px){#D:before{content:\"2368\";}}\n@media (min-width:2369px){#D:before{content:\"2369\";}}\n@media (min-width:2370px){#D:before{content:\"2370\";}}\n@media (min-width:2371px){#D:before{content:\"2371\";}}\n@media (min-width:2372px){#D:before{content:\"2372\";}}\n@media (min-width:2373px){#D:before{content:\"2373\";}}\n@media (min-width:2374px){#D:before{content:\"2374\";}}\n@media (min-width:2375px){#D:before{content:\"2375\";}}\n@media (min-width:2376px){#D:before{content:\"2376\";}}\n@media (min-width:2377px){#D:before{content:\"2377\";}}\n@media (min-width:2378px){#D:before{content:\"2378\";}}\n@media (min-width:2379px){#D:before{content:\"2379\";}}\n@media (min-width:2380px){#D:before{content:\"2380\";}}\n@media (min-width:2381px){#D:before{content:\"2381\";}}\n@media (min-width:2382px){#D:before{content:\"2382\";}}\n@media (min-width:2383px){#D:before{content:\"2383\";}}\n@media (min-width:2384px){#D:before{content:\"2384\";}}\n@media (min-width:2385px){#D:before{content:\"2385\";}}\n@media (min-width:2386px){#D:before{content:\"2386\";}}\n@media (min-width:2387px){#D:before{content:\"2387\";}}\n@media (min-width:2388px){#D:before{content:\"2388\";}}\n@media (min-width:2389px){#D:before{content:\"2389\";}}\n@media (min-width:2390px){#D:before{content:\"2390\";}}\n@media (min-width:2391px){#D:before{content:\"2391\";}}\n@media (min-width:2392px){#D:before{content:\"2392\";}}\n@media (min-width:2393px){#D:before{content:\"2393\";}}\n@media (min-width:2394px){#D:before{content:\"2394\";}}\n@media (min-width:2395px){#D:before{content:\"2395\";}}\n@media (min-width:2396px){#D:before{content:\"2396\";}}\n@media (min-width:2397px){#D:before{content:\"2397\";}}\n@media (min-width:2398px){#D:before{content:\"2398\";}}\n@media (min-width:2399px){#D:before{content:\"2399\";}}\n@media (min-width:2400px){#D:before{content:\"2400\";}}\n@media (min-width:2401px){#D:before{content:\"2401\";}}\n@media (min-width:2402px){#D:before{content:\"2402\";}}\n@media (min-width:2403px){#D:before{content:\"2403\";}}\n@media (min-width:2404px){#D:before{content:\"2404\";}}\n@media (min-width:2405px){#D:before{content:\"2405\";}}\n@media (min-width:2406px){#D:before{content:\"2406\";}}\n@media (min-width:2407px){#D:before{content:\"2407\";}}\n@media (min-width:2408px){#D:before{content:\"2408\";}}\n@media (min-width:2409px){#D:before{content:\"2409\";}}\n@media (min-width:2410px){#D:before{content:\"2410\";}}\n@media (min-width:2411px){#D:before{content:\"2411\";}}\n@media (min-width:2412px){#D:before{content:\"2412\";}}\n@media (min-width:2413px){#D:before{content:\"2413\";}}\n@media (min-width:2414px){#D:before{content:\"2414\";}}\n@media (min-width:2415px){#D:before{content:\"2415\";}}\n@media (min-width:2416px){#D:before{content:\"2416\";}}\n@media (min-width:2417px){#D:before{content:\"2417\";}}\n@media (min-width:2418px){#D:before{content:\"2418\";}}\n@media (min-width:2419px){#D:before{content:\"2419\";}}\n@media (min-width:2420px){#D:before{content:\"2420\";}}\n@media (min-width:2421px){#D:before{content:\"2421\";}}\n@media (min-width:2422px){#D:before{content:\"2422\";}}\n@media (min-width:2423px){#D:before{content:\"2423\";}}\n@media (min-width:2424px){#D:before{content:\"2424\";}}\n@media (min-width:2425px){#D:before{content:\"2425\";}}\n@media (min-width:2426px){#D:before{content:\"2426\";}}\n@media (min-width:2427px){#D:before{content:\"2427\";}}\n@media (min-width:2428px){#D:before{content:\"2428\";}}\n@media (min-width:2429px){#D:before{content:\"2429\";}}\n@media (min-width:2430px){#D:before{content:\"2430\";}}\n@media (min-width:2431px){#D:before{content:\"2431\";}}\n@media (min-width:2432px){#D:before{content:\"2432\";}}\n@media (min-width:2433px){#D:before{content:\"2433\";}}\n@media (min-width:2434px){#D:before{content:\"2434\";}}\n@media (min-width:2435px){#D:before{content:\"2435\";}}\n@media (min-width:2436px){#D:before{content:\"2436\";}}\n@media (min-width:2437px){#D:before{content:\"2437\";}}\n@media (min-width:2438px){#D:before{content:\"2438\";}}\n@media (min-width:2439px){#D:before{content:\"2439\";}}\n@media (min-width:2440px){#D:before{content:\"2440\";}}\n@media (min-width:2441px){#D:before{content:\"2441\";}}\n@media (min-width:2442px){#D:before{content:\"2442\";}}\n@media (min-width:2443px){#D:before{content:\"2443\";}}\n@media (min-width:2444px){#D:before{content:\"2444\";}}\n@media (min-width:2445px){#D:before{content:\"2445\";}}\n@media (min-width:2446px){#D:before{content:\"2446\";}}\n@media (min-width:2447px){#D:before{content:\"2447\";}}\n@media (min-width:2448px){#D:before{content:\"2448\";}}\n@media (min-width:2449px){#D:before{content:\"2449\";}}\n@media (min-width:2450px){#D:before{content:\"2450\";}}\n@media (min-width:2451px){#D:before{content:\"2451\";}}\n@media (min-width:2452px){#D:before{content:\"2452\";}}\n@media (min-width:2453px){#D:before{content:\"2453\";}}\n@media (min-width:2454px){#D:before{content:\"2454\";}}\n@media (min-width:2455px){#D:before{content:\"2455\";}}\n@media (min-width:2456px){#D:before{content:\"2456\";}}\n@media (min-width:2457px){#D:before{content:\"2457\";}}\n@media (min-width:2458px){#D:before{content:\"2458\";}}\n@media (min-width:2459px){#D:before{content:\"2459\";}}\n@media (min-width:2460px){#D:before{content:\"2460\";}}\n@media (min-width:2461px){#D:before{content:\"2461\";}}\n@media (min-width:2462px){#D:before{content:\"2462\";}}\n@media (min-width:2463px){#D:before{content:\"2463\";}}\n@media (min-width:2464px){#D:before{content:\"2464\";}}\n@media (min-width:2465px){#D:before{content:\"2465\";}}\n@media (min-width:2466px){#D:before{content:\"2466\";}}\n@media (min-width:2467px){#D:before{content:\"2467\";}}\n@media (min-width:2468px){#D:before{content:\"2468\";}}\n@media (min-width:2469px){#D:before{content:\"2469\";}}\n@media (min-width:2470px){#D:before{content:\"2470\";}}\n@media (min-width:2471px){#D:before{content:\"2471\";}}\n@media (min-width:2472px){#D:before{content:\"2472\";}}\n@media (min-width:2473px){#D:before{content:\"2473\";}}\n@media (min-width:2474px){#D:before{content:\"2474\";}}\n@media (min-width:2475px){#D:before{content:\"2475\";}}\n@media (min-width:2476px){#D:before{content:\"2476\";}}\n@media (min-width:2477px){#D:before{content:\"2477\";}}\n@media (min-width:2478px){#D:before{content:\"2478\";}}\n@media (min-width:2479px){#D:before{content:\"2479\";}}\n@media (min-width:2480px){#D:before{content:\"2480\";}}\n@media (min-width:2481px){#D:before{content:\"2481\";}}\n@media (min-width:2482px){#D:before{content:\"2482\";}}\n@media (min-width:2483px){#D:before{content:\"2483\";}}\n@media (min-width:2484px){#D:before{content:\"2484\";}}\n@media (min-width:2485px){#D:before{content:\"2485\";}}\n@media (min-width:2486px){#D:before{content:\"2486\";}}\n@media (min-width:2487px){#D:before{content:\"2487\";}}\n@media (min-width:2488px){#D:before{content:\"2488\";}}\n@media (min-width:2489px){#D:before{content:\"2489\";}}\n@media (min-width:2490px){#D:before{content:\"2490\";}}\n@media (min-width:2491px){#D:before{content:\"2491\";}}\n@media (min-width:2492px){#D:before{content:\"2492\";}}\n@media (min-width:2493px){#D:before{content:\"2493\";}}\n@media (min-width:2494px){#D:before{content:\"2494\";}}\n@media (min-width:2495px){#D:before{content:\"2495\";}}\n@media (min-width:2496px){#D:before{content:\"2496\";}}\n@media (min-width:2497px){#D:before{content:\"2497\";}}\n@media (min-width:2498px){#D:before{content:\"2498\";}}\n@media (min-width:2499px){#D:before{content:\"2499\";}}\n@media (min-width:2500px){#D:before{content:\"2500\";}}\n@media (min-width:2501px){#D:before{content:\"2501\";}}\n@media (min-width:2502px){#D:before{content:\"2502\";}}\n@media (min-width:2503px){#D:before{content:\"2503\";}}\n@media (min-width:2504px){#D:before{content:\"2504\";}}\n@media (min-width:2505px){#D:before{content:\"2505\";}}\n@media (min-width:2506px){#D:before{content:\"2506\";}}\n@media (min-width:2507px){#D:before{content:\"2507\";}}\n@media (min-width:2508px){#D:before{content:\"2508\";}}\n@media (min-width:2509px){#D:before{content:\"2509\";}}\n@media (min-width:2510px){#D:before{content:\"2510\";}}\n@media (min-width:2511px){#D:before{content:\"2511\";}}\n@media (min-width:2512px){#D:before{content:\"2512\";}}\n@media (min-width:2513px){#D:before{content:\"2513\";}}\n@media (min-width:2514px){#D:before{content:\"2514\";}}\n@media (min-width:2515px){#D:before{content:\"2515\";}}\n@media (min-width:2516px){#D:before{content:\"2516\";}}\n@media (min-width:2517px){#D:before{content:\"2517\";}}\n@media (min-width:2518px){#D:before{content:\"2518\";}}\n@media (min-width:2519px){#D:before{content:\"2519\";}}\n@media (min-width:2520px){#D:before{content:\"2520\";}}\n@media (min-width:2521px){#D:before{content:\"2521\";}}\n@media (min-width:2522px){#D:before{content:\"2522\";}}\n@media (min-width:2523px){#D:before{content:\"2523\";}}\n@media (min-width:2524px){#D:before{content:\"2524\";}}\n@media (min-width:2525px){#D:before{content:\"2525\";}}\n@media (min-width:2526px){#D:before{content:\"2526\";}}\n@media (min-width:2527px){#D:before{content:\"2527\";}}\n@media (min-width:2528px){#D:before{content:\"2528\";}}\n@media (min-width:2529px){#D:before{content:\"2529\";}}\n@media (min-width:2530px){#D:before{content:\"2530\";}}\n@media (min-width:2531px){#D:before{content:\"2531\";}}\n@media (min-width:2532px){#D:before{content:\"2532\";}}\n@media (min-width:2533px){#D:before{content:\"2533\";}}\n@media (min-width:2534px){#D:before{content:\"2534\";}}\n@media (min-width:2535px){#D:before{content:\"2535\";}}\n@media (min-width:2536px){#D:before{content:\"2536\";}}\n@media (min-width:2537px){#D:before{content:\"2537\";}}\n@media (min-width:2538px){#D:before{content:\"2538\";}}\n@media (min-width:2539px){#D:before{content:\"2539\";}}\n@media (min-width:2540px){#D:before{content:\"2540\";}}\n@media (min-width:2541px){#D:before{content:\"2541\";}}\n@media (min-width:2542px){#D:before{content:\"2542\";}}\n@media (min-width:2543px){#D:before{content:\"2543\";}}\n@media (min-width:2544px){#D:before{content:\"2544\";}}\n@media (min-width:2545px){#D:before{content:\"2545\";}}\n@media (min-width:2546px){#D:before{content:\"2546\";}}\n@media (min-width:2547px){#D:before{content:\"2547\";}}\n@media (min-width:2548px){#D:before{content:\"2548\";}}\n@media (min-width:2549px){#D:before{content:\"2549\";}}\n@media (min-width:2550px){#D:before{content:\"2550\";}}\n@media (min-width:2551px){#D:before{content:\"2551\";}}\n@media (min-width:2552px){#D:before{content:\"2552\";}}\n@media (min-width:2553px){#D:before{content:\"2553\";}}\n@media (min-width:2554px){#D:before{content:\"2554\";}}\n@media (min-width:2555px){#D:before{content:\"2555\";}}\n@media (min-width:2556px){#D:before{content:\"2556\";}}\n@media (min-width:2557px){#D:before{content:\"2557\";}}\n@media (min-width:2558px){#D:before{content:\"2558\";}}\n@media (min-width:2559px){#D:before{content:\"2559\";}}\n@media (min-width:2560px){#D:before{content:\"2560\";}}\n@media (min-width:2561px){#D:before{content:\"\";}} /* upper */\n\n@media (min-height:399px){#D:after{content:\"\";}} /* lower */\n@media (min-height:400px){#D:after{content:\" x 400\";}}\n@media (min-height:401px){#D:after{content:\" x 401\";}}\n@media (min-height:402px){#D:after{content:\" x 402\";}}\n@media (min-height:403px){#D:after{content:\" x 403\";}}\n@media (min-height:404px){#D:after{content:\" x 404\";}}\n@media (min-height:405px){#D:after{content:\" x 405\";}}\n@media (min-height:406px){#D:after{content:\" x 406\";}}\n@media (min-height:407px){#D:after{content:\" x 407\";}}\n@media (min-height:408px){#D:after{content:\" x 408\";}}\n@media (min-height:409px){#D:after{content:\" x 409\";}}\n@media (min-height:410px){#D:after{content:\" x 410\";}}\n@media (min-height:411px){#D:after{content:\" x 411\";}}\n@media (min-height:412px){#D:after{content:\" x 412\";}}\n@media (min-height:413px){#D:after{content:\" x 413\";}}\n@media (min-height:414px){#D:after{content:\" x 414\";}}\n@media (min-height:415px){#D:after{content:\" x 415\";}}\n@media (min-height:416px){#D:after{content:\" x 416\";}}\n@media (min-height:417px){#D:after{content:\" x 417\";}}\n@media (min-height:418px){#D:after{content:\" x 418\";}}\n@media (min-height:419px){#D:after{content:\" x 419\";}}\n@media (min-height:420px){#D:after{content:\" x 420\";}}\n@media (min-height:421px){#D:after{content:\" x 421\";}}\n@media (min-height:422px){#D:after{content:\" x 422\";}}\n@media (min-height:423px){#D:after{content:\" x 423\";}}\n@media (min-height:424px){#D:after{content:\" x 424\";}}\n@media (min-height:425px){#D:after{content:\" x 425\";}}\n@media (min-height:426px){#D:after{content:\" x 426\";}}\n@media (min-height:427px){#D:after{content:\" x 427\";}}\n@media (min-height:428px){#D:after{content:\" x 428\";}}\n@media (min-height:429px){#D:after{content:\" x 429\";}}\n@media (min-height:430px){#D:after{content:\" x 430\";}}\n@media (min-height:431px){#D:after{content:\" x 431\";}}\n@media (min-height:432px){#D:after{content:\" x 432\";}}\n@media (min-height:433px){#D:after{content:\" x 433\";}}\n@media (min-height:434px){#D:after{content:\" x 434\";}}\n@media (min-height:435px){#D:after{content:\" x 435\";}}\n@media (min-height:436px){#D:after{content:\" x 436\";}}\n@media (min-height:437px){#D:after{content:\" x 437\";}}\n@media (min-height:438px){#D:after{content:\" x 438\";}}\n@media (min-height:439px){#D:after{content:\" x 439\";}}\n@media (min-height:440px){#D:after{content:\" x 440\";}}\n@media (min-height:441px){#D:after{content:\" x 441\";}}\n@media (min-height:442px){#D:after{content:\" x 442\";}}\n@media (min-height:443px){#D:after{content:\" x 443\";}}\n@media (min-height:444px){#D:after{content:\" x 444\";}}\n@media (min-height:445px){#D:after{content:\" x 445\";}}\n@media (min-height:446px){#D:after{content:\" x 446\";}}\n@media (min-height:447px){#D:after{content:\" x 447\";}}\n@media (min-height:448px){#D:after{content:\" x 448\";}}\n@media (min-height:449px){#D:after{content:\" x 449\";}}\n@media (min-height:450px){#D:after{content:\" x 450\";}}\n@media (min-height:451px){#D:after{content:\" x 451\";}}\n@media (min-height:452px){#D:after{content:\" x 452\";}}\n@media (min-height:453px){#D:after{content:\" x 453\";}}\n@media (min-height:454px){#D:after{content:\" x 454\";}}\n@media (min-height:455px){#D:after{content:\" x 455\";}}\n@media (min-height:456px){#D:after{content:\" x 456\";}}\n@media (min-height:457px){#D:after{content:\" x 457\";}}\n@media (min-height:458px){#D:after{content:\" x 458\";}}\n@media (min-height:459px){#D:after{content:\" x 459\";}}\n@media (min-height:460px){#D:after{content:\" x 460\";}}\n@media (min-height:461px){#D:after{content:\" x 461\";}}\n@media (min-height:462px){#D:after{content:\" x 462\";}}\n@media (min-height:463px){#D:after{content:\" x 463\";}}\n@media (min-height:464px){#D:after{content:\" x 464\";}}\n@media (min-height:465px){#D:after{content:\" x 465\";}}\n@media (min-height:466px){#D:after{content:\" x 466\";}}\n@media (min-height:467px){#D:after{content:\" x 467\";}}\n@media (min-height:468px){#D:after{content:\" x 468\";}}\n@media (min-height:469px){#D:after{content:\" x 469\";}}\n@media (min-height:470px){#D:after{content:\" x 470\";}}\n@media (min-height:471px){#D:after{content:\" x 471\";}}\n@media (min-height:472px){#D:after{content:\" x 472\";}}\n@media (min-height:473px){#D:after{content:\" x 473\";}}\n@media (min-height:474px){#D:after{content:\" x 474\";}}\n@media (min-height:475px){#D:after{content:\" x 475\";}}\n@media (min-height:476px){#D:after{content:\" x 476\";}}\n@media (min-height:477px){#D:after{content:\" x 477\";}}\n@media (min-height:478px){#D:after{content:\" x 478\";}}\n@media (min-height:479px){#D:after{content:\" x 479\";}}\n@media (min-height:480px){#D:after{content:\" x 480\";}}\n@media (min-height:481px){#D:after{content:\" x 481\";}}\n@media (min-height:482px){#D:after{content:\" x 482\";}}\n@media (min-height:483px){#D:after{content:\" x 483\";}}\n@media (min-height:484px){#D:after{content:\" x 484\";}}\n@media (min-height:485px){#D:after{content:\" x 485\";}}\n@media (min-height:486px){#D:after{content:\" x 486\";}}\n@media (min-height:487px){#D:after{content:\" x 487\";}}\n@media (min-height:488px){#D:after{content:\" x 488\";}}\n@media (min-height:489px){#D:after{content:\" x 489\";}}\n@media (min-height:490px){#D:after{content:\" x 490\";}}\n@media (min-height:491px){#D:after{content:\" x 491\";}}\n@media (min-height:492px){#D:after{content:\" x 492\";}}\n@media (min-height:493px){#D:after{content:\" x 493\";}}\n@media (min-height:494px){#D:after{content:\" x 494\";}}\n@media (min-height:495px){#D:after{content:\" x 495\";}}\n@media (min-height:496px){#D:after{content:\" x 496\";}}\n@media (min-height:497px){#D:after{content:\" x 497\";}}\n@media (min-height:498px){#D:after{content:\" x 498\";}}\n@media (min-height:499px){#D:after{content:\" x 499\";}}\n@media (min-height:500px){#D:after{content:\" x 500\";}}\n@media (min-height:501px){#D:after{content:\" x 501\";}}\n@media (min-height:502px){#D:after{content:\" x 502\";}}\n@media (min-height:503px){#D:after{content:\" x 503\";}}\n@media (min-height:504px){#D:after{content:\" x 504\";}}\n@media (min-height:505px){#D:after{content:\" x 505\";}}\n@media (min-height:506px){#D:after{content:\" x 506\";}}\n@media (min-height:507px){#D:after{content:\" x 507\";}}\n@media (min-height:508px){#D:after{content:\" x 508\";}}\n@media (min-height:509px){#D:after{content:\" x 509\";}}\n@media (min-height:510px){#D:after{content:\" x 510\";}}\n@media (min-height:511px){#D:after{content:\" x 511\";}}\n@media (min-height:512px){#D:after{content:\" x 512\";}}\n@media (min-height:513px){#D:after{content:\" x 513\";}}\n@media (min-height:514px){#D:after{content:\" x 514\";}}\n@media (min-height:515px){#D:after{content:\" x 515\";}}\n@media (min-height:516px){#D:after{content:\" x 516\";}}\n@media (min-height:517px){#D:after{content:\" x 517\";}}\n@media (min-height:518px){#D:after{content:\" x 518\";}}\n@media (min-height:519px){#D:after{content:\" x 519\";}}\n@media (min-height:520px){#D:after{content:\" x 520\";}}\n@media (min-height:521px){#D:after{content:\" x 521\";}}\n@media (min-height:522px){#D:after{content:\" x 522\";}}\n@media (min-height:523px){#D:after{content:\" x 523\";}}\n@media (min-height:524px){#D:after{content:\" x 524\";}}\n@media (min-height:525px){#D:after{content:\" x 525\";}}\n@media (min-height:526px){#D:after{content:\" x 526\";}}\n@media (min-height:527px){#D:after{content:\" x 527\";}}\n@media (min-height:528px){#D:after{content:\" x 528\";}}\n@media (min-height:529px){#D:after{content:\" x 529\";}}\n@media (min-height:530px){#D:after{content:\" x 530\";}}\n@media (min-height:531px){#D:after{content:\" x 531\";}}\n@media (min-height:532px){#D:after{content:\" x 532\";}}\n@media (min-height:533px){#D:after{content:\" x 533\";}}\n@media (min-height:534px){#D:after{content:\" x 534\";}}\n@media (min-height:535px){#D:after{content:\" x 535\";}}\n@media (min-height:536px){#D:after{content:\" x 536\";}}\n@media (min-height:537px){#D:after{content:\" x 537\";}}\n@media (min-height:538px){#D:after{content:\" x 538\";}}\n@media (min-height:539px){#D:after{content:\" x 539\";}}\n@media (min-height:540px){#D:after{content:\" x 540\";}}\n@media (min-height:541px){#D:after{content:\" x 541\";}}\n@media (min-height:542px){#D:after{content:\" x 542\";}}\n@media (min-height:543px){#D:after{content:\" x 543\";}}\n@media (min-height:544px){#D:after{content:\" x 544\";}}\n@media (min-height:545px){#D:after{content:\" x 545\";}}\n@media (min-height:546px){#D:after{content:\" x 546\";}}\n@media (min-height:547px){#D:after{content:\" x 547\";}}\n@media (min-height:548px){#D:after{content:\" x 548\";}}\n@media (min-height:549px){#D:after{content:\" x 549\";}}\n@media (min-height:550px){#D:after{content:\" x 550\";}}\n@media (min-height:551px){#D:after{content:\" x 551\";}}\n@media (min-height:552px){#D:after{content:\" x 552\";}}\n@media (min-height:553px){#D:after{content:\" x 553\";}}\n@media (min-height:554px){#D:after{content:\" x 554\";}}\n@media (min-height:555px){#D:after{content:\" x 555\";}}\n@media (min-height:556px){#D:after{content:\" x 556\";}}\n@media (min-height:557px){#D:after{content:\" x 557\";}}\n@media (min-height:558px){#D:after{content:\" x 558\";}}\n@media (min-height:559px){#D:after{content:\" x 559\";}}\n@media (min-height:560px){#D:after{content:\" x 560\";}}\n@media (min-height:561px){#D:after{content:\" x 561\";}}\n@media (min-height:562px){#D:after{content:\" x 562\";}}\n@media (min-height:563px){#D:after{content:\" x 563\";}}\n@media (min-height:564px){#D:after{content:\" x 564\";}}\n@media (min-height:565px){#D:after{content:\" x 565\";}}\n@media (min-height:566px){#D:after{content:\" x 566\";}}\n@media (min-height:567px){#D:after{content:\" x 567\";}}\n@media (min-height:568px){#D:after{content:\" x 568\";}}\n@media (min-height:569px){#D:after{content:\" x 569\";}}\n@media (min-height:570px){#D:after{content:\" x 570\";}}\n@media (min-height:571px){#D:after{content:\" x 571\";}}\n@media (min-height:572px){#D:after{content:\" x 572\";}}\n@media (min-height:573px){#D:after{content:\" x 573\";}}\n@media (min-height:574px){#D:after{content:\" x 574\";}}\n@media (min-height:575px){#D:after{content:\" x 575\";}}\n@media (min-height:576px){#D:after{content:\" x 576\";}}\n@media (min-height:577px){#D:after{content:\" x 577\";}}\n@media (min-height:578px){#D:after{content:\" x 578\";}}\n@media (min-height:579px){#D:after{content:\" x 579\";}}\n@media (min-height:580px){#D:after{content:\" x 580\";}}\n@media (min-height:581px){#D:after{content:\" x 581\";}}\n@media (min-height:582px){#D:after{content:\" x 582\";}}\n@media (min-height:583px){#D:after{content:\" x 583\";}}\n@media (min-height:584px){#D:after{content:\" x 584\";}}\n@media (min-height:585px){#D:after{content:\" x 585\";}}\n@media (min-height:586px){#D:after{content:\" x 586\";}}\n@media (min-height:587px){#D:after{content:\" x 587\";}}\n@media (min-height:588px){#D:after{content:\" x 588\";}}\n@media (min-height:589px){#D:after{content:\" x 589\";}}\n@media (min-height:590px){#D:after{content:\" x 590\";}}\n@media (min-height:591px){#D:after{content:\" x 591\";}}\n@media (min-height:592px){#D:after{content:\" x 592\";}}\n@media (min-height:593px){#D:after{content:\" x 593\";}}\n@media (min-height:594px){#D:after{content:\" x 594\";}}\n@media (min-height:595px){#D:after{content:\" x 595\";}}\n@media (min-height:596px){#D:after{content:\" x 596\";}}\n@media (min-height:597px){#D:after{content:\" x 597\";}}\n@media (min-height:598px){#D:after{content:\" x 598\";}}\n@media (min-height:599px){#D:after{content:\" x 599\";}}\n@media (min-height:600px){#D:after{content:\" x 600\";}}\n@media (min-height:601px){#D:after{content:\" x 601\";}}\n@media (min-height:602px){#D:after{content:\" x 602\";}}\n@media (min-height:603px){#D:after{content:\" x 603\";}}\n@media (min-height:604px){#D:after{content:\" x 604\";}}\n@media (min-height:605px){#D:after{content:\" x 605\";}}\n@media (min-height:606px){#D:after{content:\" x 606\";}}\n@media (min-height:607px){#D:after{content:\" x 607\";}}\n@media (min-height:608px){#D:after{content:\" x 608\";}}\n@media (min-height:609px){#D:after{content:\" x 609\";}}\n@media (min-height:610px){#D:after{content:\" x 610\";}}\n@media (min-height:611px){#D:after{content:\" x 611\";}}\n@media (min-height:612px){#D:after{content:\" x 612\";}}\n@media (min-height:613px){#D:after{content:\" x 613\";}}\n@media (min-height:614px){#D:after{content:\" x 614\";}}\n@media (min-height:615px){#D:after{content:\" x 615\";}}\n@media (min-height:616px){#D:after{content:\" x 616\";}}\n@media (min-height:617px){#D:after{content:\" x 617\";}}\n@media (min-height:618px){#D:after{content:\" x 618\";}}\n@media (min-height:619px){#D:after{content:\" x 619\";}}\n@media (min-height:620px){#D:after{content:\" x 620\";}}\n@media (min-height:621px){#D:after{content:\" x 621\";}}\n@media (min-height:622px){#D:after{content:\" x 622\";}}\n@media (min-height:623px){#D:after{content:\" x 623\";}}\n@media (min-height:624px){#D:after{content:\" x 624\";}}\n@media (min-height:625px){#D:after{content:\" x 625\";}}\n@media (min-height:626px){#D:after{content:\" x 626\";}}\n@media (min-height:627px){#D:after{content:\" x 627\";}}\n@media (min-height:628px){#D:after{content:\" x 628\";}}\n@media (min-height:629px){#D:after{content:\" x 629\";}}\n@media (min-height:630px){#D:after{content:\" x 630\";}}\n@media (min-height:631px){#D:after{content:\" x 631\";}}\n@media (min-height:632px){#D:after{content:\" x 632\";}}\n@media (min-height:633px){#D:after{content:\" x 633\";}}\n@media (min-height:634px){#D:after{content:\" x 634\";}}\n@media (min-height:635px){#D:after{content:\" x 635\";}}\n@media (min-height:636px){#D:after{content:\" x 636\";}}\n@media (min-height:637px){#D:after{content:\" x 637\";}}\n@media (min-height:638px){#D:after{content:\" x 638\";}}\n@media (min-height:639px){#D:after{content:\" x 639\";}}\n@media (min-height:640px){#D:after{content:\" x 640\";}}\n@media (min-height:641px){#D:after{content:\" x 641\";}}\n@media (min-height:642px){#D:after{content:\" x 642\";}}\n@media (min-height:643px){#D:after{content:\" x 643\";}}\n@media (min-height:644px){#D:after{content:\" x 644\";}}\n@media (min-height:645px){#D:after{content:\" x 645\";}}\n@media (min-height:646px){#D:after{content:\" x 646\";}}\n@media (min-height:647px){#D:after{content:\" x 647\";}}\n@media (min-height:648px){#D:after{content:\" x 648\";}}\n@media (min-height:649px){#D:after{content:\" x 649\";}}\n@media (min-height:650px){#D:after{content:\" x 650\";}}\n@media (min-height:651px){#D:after{content:\" x 651\";}}\n@media (min-height:652px){#D:after{content:\" x 652\";}}\n@media (min-height:653px){#D:after{content:\" x 653\";}}\n@media (min-height:654px){#D:after{content:\" x 654\";}}\n@media (min-height:655px){#D:after{content:\" x 655\";}}\n@media (min-height:656px){#D:after{content:\" x 656\";}}\n@media (min-height:657px){#D:after{content:\" x 657\";}}\n@media (min-height:658px){#D:after{content:\" x 658\";}}\n@media (min-height:659px){#D:after{content:\" x 659\";}}\n@media (min-height:660px){#D:after{content:\" x 660\";}}\n@media (min-height:661px){#D:after{content:\" x 661\";}}\n@media (min-height:662px){#D:after{content:\" x 662\";}}\n@media (min-height:663px){#D:after{content:\" x 663\";}}\n@media (min-height:664px){#D:after{content:\" x 664\";}}\n@media (min-height:665px){#D:after{content:\" x 665\";}}\n@media (min-height:666px){#D:after{content:\" x 666\";}}\n@media (min-height:667px){#D:after{content:\" x 667\";}}\n@media (min-height:668px){#D:after{content:\" x 668\";}}\n@media (min-height:669px){#D:after{content:\" x 669\";}}\n@media (min-height:670px){#D:after{content:\" x 670\";}}\n@media (min-height:671px){#D:after{content:\" x 671\";}}\n@media (min-height:672px){#D:after{content:\" x 672\";}}\n@media (min-height:673px){#D:after{content:\" x 673\";}}\n@media (min-height:674px){#D:after{content:\" x 674\";}}\n@media (min-height:675px){#D:after{content:\" x 675\";}}\n@media (min-height:676px){#D:after{content:\" x 676\";}}\n@media (min-height:677px){#D:after{content:\" x 677\";}}\n@media (min-height:678px){#D:after{content:\" x 678\";}}\n@media (min-height:679px){#D:after{content:\" x 679\";}}\n@media (min-height:680px){#D:after{content:\" x 680\";}}\n@media (min-height:681px){#D:after{content:\" x 681\";}}\n@media (min-height:682px){#D:after{content:\" x 682\";}}\n@media (min-height:683px){#D:after{content:\" x 683\";}}\n@media (min-height:684px){#D:after{content:\" x 684\";}}\n@media (min-height:685px){#D:after{content:\" x 685\";}}\n@media (min-height:686px){#D:after{content:\" x 686\";}}\n@media (min-height:687px){#D:after{content:\" x 687\";}}\n@media (min-height:688px){#D:after{content:\" x 688\";}}\n@media (min-height:689px){#D:after{content:\" x 689\";}}\n@media (min-height:690px){#D:after{content:\" x 690\";}}\n@media (min-height:691px){#D:after{content:\" x 691\";}}\n@media (min-height:692px){#D:after{content:\" x 692\";}}\n@media (min-height:693px){#D:after{content:\" x 693\";}}\n@media (min-height:694px){#D:after{content:\" x 694\";}}\n@media (min-height:695px){#D:after{content:\" x 695\";}}\n@media (min-height:696px){#D:after{content:\" x 696\";}}\n@media (min-height:697px){#D:after{content:\" x 697\";}}\n@media (min-height:698px){#D:after{content:\" x 698\";}}\n@media (min-height:699px){#D:after{content:\" x 699\";}}\n@media (min-height:700px){#D:after{content:\" x 700\";}}\n@media (min-height:701px){#D:after{content:\" x 701\";}}\n@media (min-height:702px){#D:after{content:\" x 702\";}}\n@media (min-height:703px){#D:after{content:\" x 703\";}}\n@media (min-height:704px){#D:after{content:\" x 704\";}}\n@media (min-height:705px){#D:after{content:\" x 705\";}}\n@media (min-height:706px){#D:after{content:\" x 706\";}}\n@media (min-height:707px){#D:after{content:\" x 707\";}}\n@media (min-height:708px){#D:after{content:\" x 708\";}}\n@media (min-height:709px){#D:after{content:\" x 709\";}}\n@media (min-height:710px){#D:after{content:\" x 710\";}}\n@media (min-height:711px){#D:after{content:\" x 711\";}}\n@media (min-height:712px){#D:after{content:\" x 712\";}}\n@media (min-height:713px){#D:after{content:\" x 713\";}}\n@media (min-height:714px){#D:after{content:\" x 714\";}}\n@media (min-height:715px){#D:after{content:\" x 715\";}}\n@media (min-height:716px){#D:after{content:\" x 716\";}}\n@media (min-height:717px){#D:after{content:\" x 717\";}}\n@media (min-height:718px){#D:after{content:\" x 718\";}}\n@media (min-height:719px){#D:after{content:\" x 719\";}}\n@media (min-height:720px){#D:after{content:\" x 720\";}}\n@media (min-height:721px){#D:after{content:\" x 721\";}}\n@media (min-height:722px){#D:after{content:\" x 722\";}}\n@media (min-height:723px){#D:after{content:\" x 723\";}}\n@media (min-height:724px){#D:after{content:\" x 724\";}}\n@media (min-height:725px){#D:after{content:\" x 725\";}}\n@media (min-height:726px){#D:after{content:\" x 726\";}}\n@media (min-height:727px){#D:after{content:\" x 727\";}}\n@media (min-height:728px){#D:after{content:\" x 728\";}}\n@media (min-height:729px){#D:after{content:\" x 729\";}}\n@media (min-height:730px){#D:after{content:\" x 730\";}}\n@media (min-height:731px){#D:after{content:\" x 731\";}}\n@media (min-height:732px){#D:after{content:\" x 732\";}}\n@media (min-height:733px){#D:after{content:\" x 733\";}}\n@media (min-height:734px){#D:after{content:\" x 734\";}}\n@media (min-height:735px){#D:after{content:\" x 735\";}}\n@media (min-height:736px){#D:after{content:\" x 736\";}}\n@media (min-height:737px){#D:after{content:\" x 737\";}}\n@media (min-height:738px){#D:after{content:\" x 738\";}}\n@media (min-height:739px){#D:after{content:\" x 739\";}}\n@media (min-height:740px){#D:after{content:\" x 740\";}}\n@media (min-height:741px){#D:after{content:\" x 741\";}}\n@media (min-height:742px){#D:after{content:\" x 742\";}}\n@media (min-height:743px){#D:after{content:\" x 743\";}}\n@media (min-height:744px){#D:after{content:\" x 744\";}}\n@media (min-height:745px){#D:after{content:\" x 745\";}}\n@media (min-height:746px){#D:after{content:\" x 746\";}}\n@media (min-height:747px){#D:after{content:\" x 747\";}}\n@media (min-height:748px){#D:after{content:\" x 748\";}}\n@media (min-height:749px){#D:after{content:\" x 749\";}}\n@media (min-height:750px){#D:after{content:\" x 750\";}}\n@media (min-height:751px){#D:after{content:\" x 751\";}}\n@media (min-height:752px){#D:after{content:\" x 752\";}}\n@media (min-height:753px){#D:after{content:\" x 753\";}}\n@media (min-height:754px){#D:after{content:\" x 754\";}}\n@media (min-height:755px){#D:after{content:\" x 755\";}}\n@media (min-height:756px){#D:after{content:\" x 756\";}}\n@media (min-height:757px){#D:after{content:\" x 757\";}}\n@media (min-height:758px){#D:after{content:\" x 758\";}}\n@media (min-height:759px){#D:after{content:\" x 759\";}}\n@media (min-height:760px){#D:after{content:\" x 760\";}}\n@media (min-height:761px){#D:after{content:\" x 761\";}}\n@media (min-height:762px){#D:after{content:\" x 762\";}}\n@media (min-height:763px){#D:after{content:\" x 763\";}}\n@media (min-height:764px){#D:after{content:\" x 764\";}}\n@media (min-height:765px){#D:after{content:\" x 765\";}}\n@media (min-height:766px){#D:after{content:\" x 766\";}}\n@media (min-height:767px){#D:after{content:\" x 767\";}}\n@media (min-height:768px){#D:after{content:\" x 768\";}}\n@media (min-height:769px){#D:after{content:\" x 769\";}}\n@media (min-height:770px){#D:after{content:\" x 770\";}}\n@media (min-height:771px){#D:after{content:\" x 771\";}}\n@media (min-height:772px){#D:after{content:\" x 772\";}}\n@media (min-height:773px){#D:after{content:\" x 773\";}}\n@media (min-height:774px){#D:after{content:\" x 774\";}}\n@media (min-height:775px){#D:after{content:\" x 775\";}}\n@media (min-height:776px){#D:after{content:\" x 776\";}}\n@media (min-height:777px){#D:after{content:\" x 777\";}}\n@media (min-height:778px){#D:after{content:\" x 778\";}}\n@media (min-height:779px){#D:after{content:\" x 779\";}}\n@media (min-height:780px){#D:after{content:\" x 780\";}}\n@media (min-height:781px){#D:after{content:\" x 781\";}}\n@media (min-height:782px){#D:after{content:\" x 782\";}}\n@media (min-height:783px){#D:after{content:\" x 783\";}}\n@media (min-height:784px){#D:after{content:\" x 784\";}}\n@media (min-height:785px){#D:after{content:\" x 785\";}}\n@media (min-height:786px){#D:after{content:\" x 786\";}}\n@media (min-height:787px){#D:after{content:\" x 787\";}}\n@media (min-height:788px){#D:after{content:\" x 788\";}}\n@media (min-height:789px){#D:after{content:\" x 789\";}}\n@media (min-height:790px){#D:after{content:\" x 790\";}}\n@media (min-height:791px){#D:after{content:\" x 791\";}}\n@media (min-height:792px){#D:after{content:\" x 792\";}}\n@media (min-height:793px){#D:after{content:\" x 793\";}}\n@media (min-height:794px){#D:after{content:\" x 794\";}}\n@media (min-height:795px){#D:after{content:\" x 795\";}}\n@media (min-height:796px){#D:after{content:\" x 796\";}}\n@media (min-height:797px){#D:after{content:\" x 797\";}}\n@media (min-height:798px){#D:after{content:\" x 798\";}}\n@media (min-height:799px){#D:after{content:\" x 799\";}}\n@media (min-height:800px){#D:after{content:\" x 800\";}}\n@media (min-height:801px){#D:after{content:\" x 801\";}}\n@media (min-height:802px){#D:after{content:\" x 802\";}}\n@media (min-height:803px){#D:after{content:\" x 803\";}}\n@media (min-height:804px){#D:after{content:\" x 804\";}}\n@media (min-height:805px){#D:after{content:\" x 805\";}}\n@media (min-height:806px){#D:after{content:\" x 806\";}}\n@media (min-height:807px){#D:after{content:\" x 807\";}}\n@media (min-height:808px){#D:after{content:\" x 808\";}}\n@media (min-height:809px){#D:after{content:\" x 809\";}}\n@media (min-height:810px){#D:after{content:\" x 810\";}}\n@media (min-height:811px){#D:after{content:\" x 811\";}}\n@media (min-height:812px){#D:after{content:\" x 812\";}}\n@media (min-height:813px){#D:after{content:\" x 813\";}}\n@media (min-height:814px){#D:after{content:\" x 814\";}}\n@media (min-height:815px){#D:after{content:\" x 815\";}}\n@media (min-height:816px){#D:after{content:\" x 816\";}}\n@media (min-height:817px){#D:after{content:\" x 817\";}}\n@media (min-height:818px){#D:after{content:\" x 818\";}}\n@media (min-height:819px){#D:after{content:\" x 819\";}}\n@media (min-height:820px){#D:after{content:\" x 820\";}}\n@media (min-height:821px){#D:after{content:\" x 821\";}}\n@media (min-height:822px){#D:after{content:\" x 822\";}}\n@media (min-height:823px){#D:after{content:\" x 823\";}}\n@media (min-height:824px){#D:after{content:\" x 824\";}}\n@media (min-height:825px){#D:after{content:\" x 825\";}}\n@media (min-height:826px){#D:after{content:\" x 826\";}}\n@media (min-height:827px){#D:after{content:\" x 827\";}}\n@media (min-height:828px){#D:after{content:\" x 828\";}}\n@media (min-height:829px){#D:after{content:\" x 829\";}}\n@media (min-height:830px){#D:after{content:\" x 830\";}}\n@media (min-height:831px){#D:after{content:\" x 831\";}}\n@media (min-height:832px){#D:after{content:\" x 832\";}}\n@media (min-height:833px){#D:after{content:\" x 833\";}}\n@media (min-height:834px){#D:after{content:\" x 834\";}}\n@media (min-height:835px){#D:after{content:\" x 835\";}}\n@media (min-height:836px){#D:after{content:\" x 836\";}}\n@media (min-height:837px){#D:after{content:\" x 837\";}}\n@media (min-height:838px){#D:after{content:\" x 838\";}}\n@media (min-height:839px){#D:after{content:\" x 839\";}}\n@media (min-height:840px){#D:after{content:\" x 840\";}}\n@media (min-height:841px){#D:after{content:\" x 841\";}}\n@media (min-height:842px){#D:after{content:\" x 842\";}}\n@media (min-height:843px){#D:after{content:\" x 843\";}}\n@media (min-height:844px){#D:after{content:\" x 844\";}}\n@media (min-height:845px){#D:after{content:\" x 845\";}}\n@media (min-height:846px){#D:after{content:\" x 846\";}}\n@media (min-height:847px){#D:after{content:\" x 847\";}}\n@media (min-height:848px){#D:after{content:\" x 848\";}}\n@media (min-height:849px){#D:after{content:\" x 849\";}}\n@media (min-height:850px){#D:after{content:\" x 850\";}}\n@media (min-height:851px){#D:after{content:\" x 851\";}}\n@media (min-height:852px){#D:after{content:\" x 852\";}}\n@media (min-height:853px){#D:after{content:\" x 853\";}}\n@media (min-height:854px){#D:after{content:\" x 854\";}}\n@media (min-height:855px){#D:after{content:\" x 855\";}}\n@media (min-height:856px){#D:after{content:\" x 856\";}}\n@media (min-height:857px){#D:after{content:\" x 857\";}}\n@media (min-height:858px){#D:after{content:\" x 858\";}}\n@media (min-height:859px){#D:after{content:\" x 859\";}}\n@media (min-height:860px){#D:after{content:\" x 860\";}}\n@media (min-height:861px){#D:after{content:\" x 861\";}}\n@media (min-height:862px){#D:after{content:\" x 862\";}}\n@media (min-height:863px){#D:after{content:\" x 863\";}}\n@media (min-height:864px){#D:after{content:\" x 864\";}}\n@media (min-height:865px){#D:after{content:\" x 865\";}}\n@media (min-height:866px){#D:after{content:\" x 866\";}}\n@media (min-height:867px){#D:after{content:\" x 867\";}}\n@media (min-height:868px){#D:after{content:\" x 868\";}}\n@media (min-height:869px){#D:after{content:\" x 869\";}}\n@media (min-height:870px){#D:after{content:\" x 870\";}}\n@media (min-height:871px){#D:after{content:\" x 871\";}}\n@media (min-height:872px){#D:after{content:\" x 872\";}}\n@media (min-height:873px){#D:after{content:\" x 873\";}}\n@media (min-height:874px){#D:after{content:\" x 874\";}}\n@media (min-height:875px){#D:after{content:\" x 875\";}}\n@media (min-height:876px){#D:after{content:\" x 876\";}}\n@media (min-height:877px){#D:after{content:\" x 877\";}}\n@media (min-height:878px){#D:after{content:\" x 878\";}}\n@media (min-height:879px){#D:after{content:\" x 879\";}}\n@media (min-height:880px){#D:after{content:\" x 880\";}}\n@media (min-height:881px){#D:after{content:\" x 881\";}}\n@media (min-height:882px){#D:after{content:\" x 882\";}}\n@media (min-height:883px){#D:after{content:\" x 883\";}}\n@media (min-height:884px){#D:after{content:\" x 884\";}}\n@media (min-height:885px){#D:after{content:\" x 885\";}}\n@media (min-height:886px){#D:after{content:\" x 886\";}}\n@media (min-height:887px){#D:after{content:\" x 887\";}}\n@media (min-height:888px){#D:after{content:\" x 888\";}}\n@media (min-height:889px){#D:after{content:\" x 889\";}}\n@media (min-height:890px){#D:after{content:\" x 890\";}}\n@media (min-height:891px){#D:after{content:\" x 891\";}}\n@media (min-height:892px){#D:after{content:\" x 892\";}}\n@media (min-height:893px){#D:after{content:\" x 893\";}}\n@media (min-height:894px){#D:after{content:\" x 894\";}}\n@media (min-height:895px){#D:after{content:\" x 895\";}}\n@media (min-height:896px){#D:after{content:\" x 896\";}}\n@media (min-height:897px){#D:after{content:\" x 897\";}}\n@media (min-height:898px){#D:after{content:\" x 898\";}}\n@media (min-height:899px){#D:after{content:\" x 899\";}}\n@media (min-height:900px){#D:after{content:\" x 900\";}}\n@media (min-height:901px){#D:after{content:\" x 901\";}}\n@media (min-height:902px){#D:after{content:\" x 902\";}}\n@media (min-height:903px){#D:after{content:\" x 903\";}}\n@media (min-height:904px){#D:after{content:\" x 904\";}}\n@media (min-height:905px){#D:after{content:\" x 905\";}}\n@media (min-height:906px){#D:after{content:\" x 906\";}}\n@media (min-height:907px){#D:after{content:\" x 907\";}}\n@media (min-height:908px){#D:after{content:\" x 908\";}}\n@media (min-height:909px){#D:after{content:\" x 909\";}}\n@media (min-height:910px){#D:after{content:\" x 910\";}}\n@media (min-height:911px){#D:after{content:\" x 911\";}}\n@media (min-height:912px){#D:after{content:\" x 912\";}}\n@media (min-height:913px){#D:after{content:\" x 913\";}}\n@media (min-height:914px){#D:after{content:\" x 914\";}}\n@media (min-height:915px){#D:after{content:\" x 915\";}}\n@media (min-height:916px){#D:after{content:\" x 916\";}}\n@media (min-height:917px){#D:after{content:\" x 917\";}}\n@media (min-height:918px){#D:after{content:\" x 918\";}}\n@media (min-height:919px){#D:after{content:\" x 919\";}}\n@media (min-height:920px){#D:after{content:\" x 920\";}}\n@media (min-height:921px){#D:after{content:\" x 921\";}}\n@media (min-height:922px){#D:after{content:\" x 922\";}}\n@media (min-height:923px){#D:after{content:\" x 923\";}}\n@media (min-height:924px){#D:after{content:\" x 924\";}}\n@media (min-height:925px){#D:after{content:\" x 925\";}}\n@media (min-height:926px){#D:after{content:\" x 926\";}}\n@media (min-height:927px){#D:after{content:\" x 927\";}}\n@media (min-height:928px){#D:after{content:\" x 928\";}}\n@media (min-height:929px){#D:after{content:\" x 929\";}}\n@media (min-height:930px){#D:after{content:\" x 930\";}}\n@media (min-height:931px){#D:after{content:\" x 931\";}}\n@media (min-height:932px){#D:after{content:\" x 932\";}}\n@media (min-height:933px){#D:after{content:\" x 933\";}}\n@media (min-height:934px){#D:after{content:\" x 934\";}}\n@media (min-height:935px){#D:after{content:\" x 935\";}}\n@media (min-height:936px){#D:after{content:\" x 936\";}}\n@media (min-height:937px){#D:after{content:\" x 937\";}}\n@media (min-height:938px){#D:after{content:\" x 938\";}}\n@media (min-height:939px){#D:after{content:\" x 939\";}}\n@media (min-height:940px){#D:after{content:\" x 940\";}}\n@media (min-height:941px){#D:after{content:\" x 941\";}}\n@media (min-height:942px){#D:after{content:\" x 942\";}}\n@media (min-height:943px){#D:after{content:\" x 943\";}}\n@media (min-height:944px){#D:after{content:\" x 944\";}}\n@media (min-height:945px){#D:after{content:\" x 945\";}}\n@media (min-height:946px){#D:after{content:\" x 946\";}}\n@media (min-height:947px){#D:after{content:\" x 947\";}}\n@media (min-height:948px){#D:after{content:\" x 948\";}}\n@media (min-height:949px){#D:after{content:\" x 949\";}}\n@media (min-height:950px){#D:after{content:\" x 950\";}}\n@media (min-height:951px){#D:after{content:\" x 951\";}}\n@media (min-height:952px){#D:after{content:\" x 952\";}}\n@media (min-height:953px){#D:after{content:\" x 953\";}}\n@media (min-height:954px){#D:after{content:\" x 954\";}}\n@media (min-height:955px){#D:after{content:\" x 955\";}}\n@media (min-height:956px){#D:after{content:\" x 956\";}}\n@media (min-height:957px){#D:after{content:\" x 957\";}}\n@media (min-height:958px){#D:after{content:\" x 958\";}}\n@media (min-height:959px){#D:after{content:\" x 959\";}}\n@media (min-height:960px){#D:after{content:\" x 960\";}}\n@media (min-height:961px){#D:after{content:\" x 961\";}}\n@media (min-height:962px){#D:after{content:\" x 962\";}}\n@media (min-height:963px){#D:after{content:\" x 963\";}}\n@media (min-height:964px){#D:after{content:\" x 964\";}}\n@media (min-height:965px){#D:after{content:\" x 965\";}}\n@media (min-height:966px){#D:after{content:\" x 966\";}}\n@media (min-height:967px){#D:after{content:\" x 967\";}}\n@media (min-height:968px){#D:after{content:\" x 968\";}}\n@media (min-height:969px){#D:after{content:\" x 969\";}}\n@media (min-height:970px){#D:after{content:\" x 970\";}}\n@media (min-height:971px){#D:after{content:\" x 971\";}}\n@media (min-height:972px){#D:after{content:\" x 972\";}}\n@media (min-height:973px){#D:after{content:\" x 973\";}}\n@media (min-height:974px){#D:after{content:\" x 974\";}}\n@media (min-height:975px){#D:after{content:\" x 975\";}}\n@media (min-height:976px){#D:after{content:\" x 976\";}}\n@media (min-height:977px){#D:after{content:\" x 977\";}}\n@media (min-height:978px){#D:after{content:\" x 978\";}}\n@media (min-height:979px){#D:after{content:\" x 979\";}}\n@media (min-height:980px){#D:after{content:\" x 980\";}}\n@media (min-height:981px){#D:after{content:\" x 981\";}}\n@media (min-height:982px){#D:after{content:\" x 982\";}}\n@media (min-height:983px){#D:after{content:\" x 983\";}}\n@media (min-height:984px){#D:after{content:\" x 984\";}}\n@media (min-height:985px){#D:after{content:\" x 985\";}}\n@media (min-height:986px){#D:after{content:\" x 986\";}}\n@media (min-height:987px){#D:after{content:\" x 987\";}}\n@media (min-height:988px){#D:after{content:\" x 988\";}}\n@media (min-height:989px){#D:after{content:\" x 989\";}}\n@media (min-height:990px){#D:after{content:\" x 990\";}}\n@media (min-height:991px){#D:after{content:\" x 991\";}}\n@media (min-height:992px){#D:after{content:\" x 992\";}}\n@media (min-height:993px){#D:after{content:\" x 993\";}}\n@media (min-height:994px){#D:after{content:\" x 994\";}}\n@media (min-height:995px){#D:after{content:\" x 995\";}}\n@media (min-height:996px){#D:after{content:\" x 996\";}}\n@media (min-height:997px){#D:after{content:\" x 997\";}}\n@media (min-height:998px){#D:after{content:\" x 998\";}}\n@media (min-height:999px){#D:after{content:\" x 999\";}}\n@media (min-height:1000px){#D:after{content:\" x 1000\";}}\n@media (min-height:1001px){#D:after{content:\" x 1001\";}}\n@media (min-height:1002px){#D:after{content:\" x 1002\";}}\n@media (min-height:1003px){#D:after{content:\" x 1003\";}}\n@media (min-height:1004px){#D:after{content:\" x 1004\";}}\n@media (min-height:1005px){#D:after{content:\" x 1005\";}}\n@media (min-height:1006px){#D:after{content:\" x 1006\";}}\n@media (min-height:1007px){#D:after{content:\" x 1007\";}}\n@media (min-height:1008px){#D:after{content:\" x 1008\";}}\n@media (min-height:1009px){#D:after{content:\" x 1009\";}}\n@media (min-height:1010px){#D:after{content:\" x 1010\";}}\n@media (min-height:1011px){#D:after{content:\" x 1011\";}}\n@media (min-height:1012px){#D:after{content:\" x 1012\";}}\n@media (min-height:1013px){#D:after{content:\" x 1013\";}}\n@media (min-height:1014px){#D:after{content:\" x 1014\";}}\n@media (min-height:1015px){#D:after{content:\" x 1015\";}}\n@media (min-height:1016px){#D:after{content:\" x 1016\";}}\n@media (min-height:1017px){#D:after{content:\" x 1017\";}}\n@media (min-height:1018px){#D:after{content:\" x 1018\";}}\n@media (min-height:1019px){#D:after{content:\" x 1019\";}}\n@media (min-height:1020px){#D:after{content:\" x 1020\";}}\n@media (min-height:1021px){#D:after{content:\" x 1021\";}}\n@media (min-height:1022px){#D:after{content:\" x 1022\";}}\n@media (min-height:1023px){#D:after{content:\" x 1023\";}}\n@media (min-height:1024px){#D:after{content:\" x 1024\";}}\n@media (min-height:1025px){#D:after{content:\" x 1025\";}}\n@media (min-height:1026px){#D:after{content:\" x 1026\";}}\n@media (min-height:1027px){#D:after{content:\" x 1027\";}}\n@media (min-height:1028px){#D:after{content:\" x 1028\";}}\n@media (min-height:1029px){#D:after{content:\" x 1029\";}}\n@media (min-height:1030px){#D:after{content:\" x 1030\";}}\n@media (min-height:1031px){#D:after{content:\" x 1031\";}}\n@media (min-height:1032px){#D:after{content:\" x 1032\";}}\n@media (min-height:1033px){#D:after{content:\" x 1033\";}}\n@media (min-height:1034px){#D:after{content:\" x 1034\";}}\n@media (min-height:1035px){#D:after{content:\" x 1035\";}}\n@media (min-height:1036px){#D:after{content:\" x 1036\";}}\n@media (min-height:1037px){#D:after{content:\" x 1037\";}}\n@media (min-height:1038px){#D:after{content:\" x 1038\";}}\n@media (min-height:1039px){#D:after{content:\" x 1039\";}}\n@media (min-height:1040px){#D:after{content:\" x 1040\";}}\n@media (min-height:1041px){#D:after{content:\" x 1041\";}}\n@media (min-height:1042px){#D:after{content:\" x 1042\";}}\n@media (min-height:1043px){#D:after{content:\" x 1043\";}}\n@media (min-height:1044px){#D:after{content:\" x 1044\";}}\n@media (min-height:1045px){#D:after{content:\" x 1045\";}}\n@media (min-height:1046px){#D:after{content:\" x 1046\";}}\n@media (min-height:1047px){#D:after{content:\" x 1047\";}}\n@media (min-height:1048px){#D:after{content:\" x 1048\";}}\n@media (min-height:1049px){#D:after{content:\" x 1049\";}}\n@media (min-height:1050px){#D:after{content:\" x 1050\";}}\n@media (min-height:1051px){#D:after{content:\" x 1051\";}}\n@media (min-height:1052px){#D:after{content:\" x 1052\";}}\n@media (min-height:1053px){#D:after{content:\" x 1053\";}}\n@media (min-height:1054px){#D:after{content:\" x 1054\";}}\n@media (min-height:1055px){#D:after{content:\" x 1055\";}}\n@media (min-height:1056px){#D:after{content:\" x 1056\";}}\n@media (min-height:1057px){#D:after{content:\" x 1057\";}}\n@media (min-height:1058px){#D:after{content:\" x 1058\";}}\n@media (min-height:1059px){#D:after{content:\" x 1059\";}}\n@media (min-height:1060px){#D:after{content:\" x 1060\";}}\n@media (min-height:1061px){#D:after{content:\" x 1061\";}}\n@media (min-height:1062px){#D:after{content:\" x 1062\";}}\n@media (min-height:1063px){#D:after{content:\" x 1063\";}}\n@media (min-height:1064px){#D:after{content:\" x 1064\";}}\n@media (min-height:1065px){#D:after{content:\" x 1065\";}}\n@media (min-height:1066px){#D:after{content:\" x 1066\";}}\n@media (min-height:1067px){#D:after{content:\" x 1067\";}}\n@media (min-height:1068px){#D:after{content:\" x 1068\";}}\n@media (min-height:1069px){#D:after{content:\" x 1069\";}}\n@media (min-height:1070px){#D:after{content:\" x 1070\";}}\n@media (min-height:1071px){#D:after{content:\" x 1071\";}}\n@media (min-height:1072px){#D:after{content:\" x 1072\";}}\n@media (min-height:1073px){#D:after{content:\" x 1073\";}}\n@media (min-height:1074px){#D:after{content:\" x 1074\";}}\n@media (min-height:1075px){#D:after{content:\" x 1075\";}}\n@media (min-height:1076px){#D:after{content:\" x 1076\";}}\n@media (min-height:1077px){#D:after{content:\" x 1077\";}}\n@media (min-height:1078px){#D:after{content:\" x 1078\";}}\n@media (min-height:1079px){#D:after{content:\" x 1079\";}}\n@media (min-height:1080px){#D:after{content:\" x 1080\";}}\n@media (min-height:1081px){#D:after{content:\" x 1081\";}}\n@media (min-height:1082px){#D:after{content:\" x 1082\";}}\n@media (min-height:1083px){#D:after{content:\" x 1083\";}}\n@media (min-height:1084px){#D:after{content:\" x 1084\";}}\n@media (min-height:1085px){#D:after{content:\" x 1085\";}}\n@media (min-height:1086px){#D:after{content:\" x 1086\";}}\n@media (min-height:1087px){#D:after{content:\" x 1087\";}}\n@media (min-height:1088px){#D:after{content:\" x 1088\";}}\n@media (min-height:1089px){#D:after{content:\" x 1089\";}}\n@media (min-height:1090px){#D:after{content:\" x 1090\";}}\n@media (min-height:1091px){#D:after{content:\" x 1091\";}}\n@media (min-height:1092px){#D:after{content:\" x 1092\";}}\n@media (min-height:1093px){#D:after{content:\" x 1093\";}}\n@media (min-height:1094px){#D:after{content:\" x 1094\";}}\n@media (min-height:1095px){#D:after{content:\" x 1095\";}}\n@media (min-height:1096px){#D:after{content:\" x 1096\";}}\n@media (min-height:1097px){#D:after{content:\" x 1097\";}}\n@media (min-height:1098px){#D:after{content:\" x 1098\";}}\n@media (min-height:1099px){#D:after{content:\" x 1099\";}}\n@media (min-height:1100px){#D:after{content:\" x 1100\";}}\n@media (min-height:1101px){#D:after{content:\" x 1101\";}}\n@media (min-height:1102px){#D:after{content:\" x 1102\";}}\n@media (min-height:1103px){#D:after{content:\" x 1103\";}}\n@media (min-height:1104px){#D:after{content:\" x 1104\";}}\n@media (min-height:1105px){#D:after{content:\" x 1105\";}}\n@media (min-height:1106px){#D:after{content:\" x 1106\";}}\n@media (min-height:1107px){#D:after{content:\" x 1107\";}}\n@media (min-height:1108px){#D:after{content:\" x 1108\";}}\n@media (min-height:1109px){#D:after{content:\" x 1109\";}}\n@media (min-height:1110px){#D:after{content:\" x 1110\";}}\n@media (min-height:1111px){#D:after{content:\" x 1111\";}}\n@media (min-height:1112px){#D:after{content:\" x 1112\";}}\n@media (min-height:1113px){#D:after{content:\" x 1113\";}}\n@media (min-height:1114px){#D:after{content:\" x 1114\";}}\n@media (min-height:1115px){#D:after{content:\" x 1115\";}}\n@media (min-height:1116px){#D:after{content:\" x 1116\";}}\n@media (min-height:1117px){#D:after{content:\" x 1117\";}}\n@media (min-height:1118px){#D:after{content:\" x 1118\";}}\n@media (min-height:1119px){#D:after{content:\" x 1119\";}}\n@media (min-height:1120px){#D:after{content:\" x 1120\";}}\n@media (min-height:1121px){#D:after{content:\" x 1121\";}}\n@media (min-height:1122px){#D:after{content:\" x 1122\";}}\n@media (min-height:1123px){#D:after{content:\" x 1123\";}}\n@media (min-height:1124px){#D:after{content:\" x 1124\";}}\n@media (min-height:1125px){#D:after{content:\" x 1125\";}}\n@media (min-height:1126px){#D:after{content:\" x 1126\";}}\n@media (min-height:1127px){#D:after{content:\" x 1127\";}}\n@media (min-height:1128px){#D:after{content:\" x 1128\";}}\n@media (min-height:1129px){#D:after{content:\" x 1129\";}}\n@media (min-height:1130px){#D:after{content:\" x 1130\";}}\n@media (min-height:1131px){#D:after{content:\" x 1131\";}}\n@media (min-height:1132px){#D:after{content:\" x 1132\";}}\n@media (min-height:1133px){#D:after{content:\" x 1133\";}}\n@media (min-height:1134px){#D:after{content:\" x 1134\";}}\n@media (min-height:1135px){#D:after{content:\" x 1135\";}}\n@media (min-height:1136px){#D:after{content:\" x 1136\";}}\n@media (min-height:1137px){#D:after{content:\" x 1137\";}}\n@media (min-height:1138px){#D:after{content:\" x 1138\";}}\n@media (min-height:1139px){#D:after{content:\" x 1139\";}}\n@media (min-height:1140px){#D:after{content:\" x 1140\";}}\n@media (min-height:1141px){#D:after{content:\" x 1141\";}}\n@media (min-height:1142px){#D:after{content:\" x 1142\";}}\n@media (min-height:1143px){#D:after{content:\" x 1143\";}}\n@media (min-height:1144px){#D:after{content:\" x 1144\";}}\n@media (min-height:1145px){#D:after{content:\" x 1145\";}}\n@media (min-height:1146px){#D:after{content:\" x 1146\";}}\n@media (min-height:1147px){#D:after{content:\" x 1147\";}}\n@media (min-height:1148px){#D:after{content:\" x 1148\";}}\n@media (min-height:1149px){#D:after{content:\" x 1149\";}}\n@media (min-height:1150px){#D:after{content:\" x 1150\";}}\n@media (min-height:1151px){#D:after{content:\" x 1151\";}}\n@media (min-height:1152px){#D:after{content:\" x 1152\";}}\n@media (min-height:1153px){#D:after{content:\" x 1153\";}}\n@media (min-height:1154px){#D:after{content:\" x 1154\";}}\n@media (min-height:1155px){#D:after{content:\" x 1155\";}}\n@media (min-height:1156px){#D:after{content:\" x 1156\";}}\n@media (min-height:1157px){#D:after{content:\" x 1157\";}}\n@media (min-height:1158px){#D:after{content:\" x 1158\";}}\n@media (min-height:1159px){#D:after{content:\" x 1159\";}}\n@media (min-height:1160px){#D:after{content:\" x 1160\";}}\n@media (min-height:1161px){#D:after{content:\" x 1161\";}}\n@media (min-height:1162px){#D:after{content:\" x 1162\";}}\n@media (min-height:1163px){#D:after{content:\" x 1163\";}}\n@media (min-height:1164px){#D:after{content:\" x 1164\";}}\n@media (min-height:1165px){#D:after{content:\" x 1165\";}}\n@media (min-height:1166px){#D:after{content:\" x 1166\";}}\n@media (min-height:1167px){#D:after{content:\" x 1167\";}}\n@media (min-height:1168px){#D:after{content:\" x 1168\";}}\n@media (min-height:1169px){#D:after{content:\" x 1169\";}}\n@media (min-height:1170px){#D:after{content:\" x 1170\";}}\n@media (min-height:1171px){#D:after{content:\" x 1171\";}}\n@media (min-height:1172px){#D:after{content:\" x 1172\";}}\n@media (min-height:1173px){#D:after{content:\" x 1173\";}}\n@media (min-height:1174px){#D:after{content:\" x 1174\";}}\n@media (min-height:1175px){#D:after{content:\" x 1175\";}}\n@media (min-height:1176px){#D:after{content:\" x 1176\";}}\n@media (min-height:1177px){#D:after{content:\" x 1177\";}}\n@media (min-height:1178px){#D:after{content:\" x 1178\";}}\n@media (min-height:1179px){#D:after{content:\" x 1179\";}}\n@media (min-height:1180px){#D:after{content:\" x 1180\";}}\n@media (min-height:1181px){#D:after{content:\" x 1181\";}}\n@media (min-height:1182px){#D:after{content:\" x 1182\";}}\n@media (min-height:1183px){#D:after{content:\" x 1183\";}}\n@media (min-height:1184px){#D:after{content:\" x 1184\";}}\n@media (min-height:1185px){#D:after{content:\" x 1185\";}}\n@media (min-height:1186px){#D:after{content:\" x 1186\";}}\n@media (min-height:1187px){#D:after{content:\" x 1187\";}}\n@media (min-height:1188px){#D:after{content:\" x 1188\";}}\n@media (min-height:1189px){#D:after{content:\" x 1189\";}}\n@media (min-height:1190px){#D:after{content:\" x 1190\";}}\n@media (min-height:1191px){#D:after{content:\" x 1191\";}}\n@media (min-height:1192px){#D:after{content:\" x 1192\";}}\n@media (min-height:1193px){#D:after{content:\" x 1193\";}}\n@media (min-height:1194px){#D:after{content:\" x 1194\";}}\n@media (min-height:1195px){#D:after{content:\" x 1195\";}}\n@media (min-height:1196px){#D:after{content:\" x 1196\";}}\n@media (min-height:1197px){#D:after{content:\" x 1197\";}}\n@media (min-height:1198px){#D:after{content:\" x 1198\";}}\n@media (min-height:1199px){#D:after{content:\" x 1199\";}}\n@media (min-height:1200px){#D:after{content:\" x 1200\";}}\n@media (min-height:1201px){#D:after{content:\" x 1201\";}}\n@media (min-height:1202px){#D:after{content:\" x 1202\";}}\n@media (min-height:1203px){#D:after{content:\" x 1203\";}}\n@media (min-height:1204px){#D:after{content:\" x 1204\";}}\n@media (min-height:1205px){#D:after{content:\" x 1205\";}}\n@media (min-height:1206px){#D:after{content:\" x 1206\";}}\n@media (min-height:1207px){#D:after{content:\" x 1207\";}}\n@media (min-height:1208px){#D:after{content:\" x 1208\";}}\n@media (min-height:1209px){#D:after{content:\" x 1209\";}}\n@media (min-height:1210px){#D:after{content:\" x 1210\";}}\n@media (min-height:1211px){#D:after{content:\" x 1211\";}}\n@media (min-height:1212px){#D:after{content:\" x 1212\";}}\n@media (min-height:1213px){#D:after{content:\" x 1213\";}}\n@media (min-height:1214px){#D:after{content:\" x 1214\";}}\n@media (min-height:1215px){#D:after{content:\" x 1215\";}}\n@media (min-height:1216px){#D:after{content:\" x 1216\";}}\n@media (min-height:1217px){#D:after{content:\" x 1217\";}}\n@media (min-height:1218px){#D:after{content:\" x 1218\";}}\n@media (min-height:1219px){#D:after{content:\" x 1219\";}}\n@media (min-height:1220px){#D:after{content:\" x 1220\";}}\n@media (min-height:1221px){#D:after{content:\" x 1221\";}}\n@media (min-height:1222px){#D:after{content:\" x 1222\";}}\n@media (min-height:1223px){#D:after{content:\" x 1223\";}}\n@media (min-height:1224px){#D:after{content:\" x 1224\";}}\n@media (min-height:1225px){#D:after{content:\" x 1225\";}}\n@media (min-height:1226px){#D:after{content:\" x 1226\";}}\n@media (min-height:1227px){#D:after{content:\" x 1227\";}}\n@media (min-height:1228px){#D:after{content:\" x 1228\";}}\n@media (min-height:1229px){#D:after{content:\" x 1229\";}}\n@media (min-height:1230px){#D:after{content:\" x 1230\";}}\n@media (min-height:1231px){#D:after{content:\" x 1231\";}}\n@media (min-height:1232px){#D:after{content:\" x 1232\";}}\n@media (min-height:1233px){#D:after{content:\" x 1233\";}}\n@media (min-height:1234px){#D:after{content:\" x 1234\";}}\n@media (min-height:1235px){#D:after{content:\" x 1235\";}}\n@media (min-height:1236px){#D:after{content:\" x 1236\";}}\n@media (min-height:1237px){#D:after{content:\" x 1237\";}}\n@media (min-height:1238px){#D:after{content:\" x 1238\";}}\n@media (min-height:1239px){#D:after{content:\" x 1239\";}}\n@media (min-height:1240px){#D:after{content:\" x 1240\";}}\n@media (min-height:1241px){#D:after{content:\" x 1241\";}}\n@media (min-height:1242px){#D:after{content:\" x 1242\";}}\n@media (min-height:1243px){#D:after{content:\" x 1243\";}}\n@media (min-height:1244px){#D:after{content:\" x 1244\";}}\n@media (min-height:1245px){#D:after{content:\" x 1245\";}}\n@media (min-height:1246px){#D:after{content:\" x 1246\";}}\n@media (min-height:1247px){#D:after{content:\" x 1247\";}}\n@media (min-height:1248px){#D:after{content:\" x 1248\";}}\n@media (min-height:1249px){#D:after{content:\" x 1249\";}}\n@media (min-height:1250px){#D:after{content:\" x 1250\";}}\n@media (min-height:1251px){#D:after{content:\" x 1251\";}}\n@media (min-height:1252px){#D:after{content:\" x 1252\";}}\n@media (min-height:1253px){#D:after{content:\" x 1253\";}}\n@media (min-height:1254px){#D:after{content:\" x 1254\";}}\n@media (min-height:1255px){#D:after{content:\" x 1255\";}}\n@media (min-height:1256px){#D:after{content:\" x 1256\";}}\n@media (min-height:1257px){#D:after{content:\" x 1257\";}}\n@media (min-height:1258px){#D:after{content:\" x 1258\";}}\n@media (min-height:1259px){#D:after{content:\" x 1259\";}}\n@media (min-height:1260px){#D:after{content:\" x 1260\";}}\n@media (min-height:1261px){#D:after{content:\" x 1261\";}}\n@media (min-height:1262px){#D:after{content:\" x 1262\";}}\n@media (min-height:1263px){#D:after{content:\" x 1263\";}}\n@media (min-height:1264px){#D:after{content:\" x 1264\";}}\n@media (min-height:1265px){#D:after{content:\" x 1265\";}}\n@media (min-height:1266px){#D:after{content:\" x 1266\";}}\n@media (min-height:1267px){#D:after{content:\" x 1267\";}}\n@media (min-height:1268px){#D:after{content:\" x 1268\";}}\n@media (min-height:1269px){#D:after{content:\" x 1269\";}}\n@media (min-height:1270px){#D:after{content:\" x 1270\";}}\n@media (min-height:1271px){#D:after{content:\" x 1271\";}}\n@media (min-height:1272px){#D:after{content:\" x 1272\";}}\n@media (min-height:1273px){#D:after{content:\" x 1273\";}}\n@media (min-height:1274px){#D:after{content:\" x 1274\";}}\n@media (min-height:1275px){#D:after{content:\" x 1275\";}}\n@media (min-height:1276px){#D:after{content:\" x 1276\";}}\n@media (min-height:1277px){#D:after{content:\" x 1277\";}}\n@media (min-height:1278px){#D:after{content:\" x 1278\";}}\n@media (min-height:1279px){#D:after{content:\" x 1279\";}}\n@media (min-height:1280px){#D:after{content:\" x 1280\";}}\n@media (min-height:1281px){#D:after{content:\" x 1281\";}}\n@media (min-height:1282px){#D:after{content:\" x 1282\";}}\n@media (min-height:1283px){#D:after{content:\" x 1283\";}}\n@media (min-height:1284px){#D:after{content:\" x 1284\";}}\n@media (min-height:1285px){#D:after{content:\" x 1285\";}}\n@media (min-height:1286px){#D:after{content:\" x 1286\";}}\n@media (min-height:1287px){#D:after{content:\" x 1287\";}}\n@media (min-height:1288px){#D:after{content:\" x 1288\";}}\n@media (min-height:1289px){#D:after{content:\" x 1289\";}}\n@media (min-height:1290px){#D:after{content:\" x 1290\";}}\n@media (min-height:1291px){#D:after{content:\" x 1291\";}}\n@media (min-height:1292px){#D:after{content:\" x 1292\";}}\n@media (min-height:1293px){#D:after{content:\" x 1293\";}}\n@media (min-height:1294px){#D:after{content:\" x 1294\";}}\n@media (min-height:1295px){#D:after{content:\" x 1295\";}}\n@media (min-height:1296px){#D:after{content:\" x 1296\";}}\n@media (min-height:1297px){#D:after{content:\" x 1297\";}}\n@media (min-height:1298px){#D:after{content:\" x 1298\";}}\n@media (min-height:1299px){#D:after{content:\" x 1299\";}}\n@media (min-height:1300px){#D:after{content:\" x 1300\";}}\n@media (min-height:1301px){#D:after{content:\" x 1301\";}}\n@media (min-height:1302px){#D:after{content:\" x 1302\";}}\n@media (min-height:1303px){#D:after{content:\" x 1303\";}}\n@media (min-height:1304px){#D:after{content:\" x 1304\";}}\n@media (min-height:1305px){#D:after{content:\" x 1305\";}}\n@media (min-height:1306px){#D:after{content:\" x 1306\";}}\n@media (min-height:1307px){#D:after{content:\" x 1307\";}}\n@media (min-height:1308px){#D:after{content:\" x 1308\";}}\n@media (min-height:1309px){#D:after{content:\" x 1309\";}}\n@media (min-height:1310px){#D:after{content:\" x 1310\";}}\n@media (min-height:1311px){#D:after{content:\" x 1311\";}}\n@media (min-height:1312px){#D:after{content:\" x 1312\";}}\n@media (min-height:1313px){#D:after{content:\" x 1313\";}}\n@media (min-height:1314px){#D:after{content:\" x 1314\";}}\n@media (min-height:1315px){#D:after{content:\" x 1315\";}}\n@media (min-height:1316px){#D:after{content:\" x 1316\";}}\n@media (min-height:1317px){#D:after{content:\" x 1317\";}}\n@media (min-height:1318px){#D:after{content:\" x 1318\";}}\n@media (min-height:1319px){#D:after{content:\" x 1319\";}}\n@media (min-height:1320px){#D:after{content:\" x 1320\";}}\n@media (min-height:1321px){#D:after{content:\" x 1321\";}}\n@media (min-height:1322px){#D:after{content:\" x 1322\";}}\n@media (min-height:1323px){#D:after{content:\" x 1323\";}}\n@media (min-height:1324px){#D:after{content:\" x 1324\";}}\n@media (min-height:1325px){#D:after{content:\" x 1325\";}}\n@media (min-height:1326px){#D:after{content:\" x 1326\";}}\n@media (min-height:1327px){#D:after{content:\" x 1327\";}}\n@media (min-height:1328px){#D:after{content:\" x 1328\";}}\n@media (min-height:1329px){#D:after{content:\" x 1329\";}}\n@media (min-height:1330px){#D:after{content:\" x 1330\";}}\n@media (min-height:1331px){#D:after{content:\" x 1331\";}}\n@media (min-height:1332px){#D:after{content:\" x 1332\";}}\n@media (min-height:1333px){#D:after{content:\" x 1333\";}}\n@media (min-height:1334px){#D:after{content:\" x 1334\";}}\n@media (min-height:1335px){#D:after{content:\" x 1335\";}}\n@media (min-height:1336px){#D:after{content:\" x 1336\";}}\n@media (min-height:1337px){#D:after{content:\" x 1337\";}}\n@media (min-height:1338px){#D:after{content:\" x 1338\";}}\n@media (min-height:1339px){#D:after{content:\" x 1339\";}}\n@media (min-height:1340px){#D:after{content:\" x 1340\";}}\n@media (min-height:1341px){#D:after{content:\" x 1341\";}}\n@media (min-height:1342px){#D:after{content:\" x 1342\";}}\n@media (min-height:1343px){#D:after{content:\" x 1343\";}}\n@media (min-height:1344px){#D:after{content:\" x 1344\";}}\n@media (min-height:1345px){#D:after{content:\" x 1345\";}}\n@media (min-height:1346px){#D:after{content:\" x 1346\";}}\n@media (min-height:1347px){#D:after{content:\" x 1347\";}}\n@media (min-height:1348px){#D:after{content:\" x 1348\";}}\n@media (min-height:1349px){#D:after{content:\" x 1349\";}}\n@media (min-height:1350px){#D:after{content:\" x 1350\";}}\n@media (min-height:1351px){#D:after{content:\" x 1351\";}}\n@media (min-height:1352px){#D:after{content:\" x 1352\";}}\n@media (min-height:1353px){#D:after{content:\" x 1353\";}}\n@media (min-height:1354px){#D:after{content:\" x 1354\";}}\n@media (min-height:1355px){#D:after{content:\" x 1355\";}}\n@media (min-height:1356px){#D:after{content:\" x 1356\";}}\n@media (min-height:1357px){#D:after{content:\" x 1357\";}}\n@media (min-height:1358px){#D:after{content:\" x 1358\";}}\n@media (min-height:1359px){#D:after{content:\" x 1359\";}}\n@media (min-height:1360px){#D:after{content:\" x 1360\";}}\n@media (min-height:1361px){#D:after{content:\" x 1361\";}}\n@media (min-height:1362px){#D:after{content:\" x 1362\";}}\n@media (min-height:1363px){#D:after{content:\" x 1363\";}}\n@media (min-height:1364px){#D:after{content:\" x 1364\";}}\n@media (min-height:1365px){#D:after{content:\" x 1365\";}}\n@media (min-height:1366px){#D:after{content:\" x 1366\";}}\n@media (min-height:1367px){#D:after{content:\" x 1367\";}}\n@media (min-height:1368px){#D:after{content:\" x 1368\";}}\n@media (min-height:1369px){#D:after{content:\" x 1369\";}}\n@media (min-height:1370px){#D:after{content:\" x 1370\";}}\n@media (min-height:1371px){#D:after{content:\" x 1371\";}}\n@media (min-height:1372px){#D:after{content:\" x 1372\";}}\n@media (min-height:1373px){#D:after{content:\" x 1373\";}}\n@media (min-height:1374px){#D:after{content:\" x 1374\";}}\n@media (min-height:1375px){#D:after{content:\" x 1375\";}}\n@media (min-height:1376px){#D:after{content:\" x 1376\";}}\n@media (min-height:1377px){#D:after{content:\" x 1377\";}}\n@media (min-height:1378px){#D:after{content:\" x 1378\";}}\n@media (min-height:1379px){#D:after{content:\" x 1379\";}}\n@media (min-height:1380px){#D:after{content:\" x 1380\";}}\n@media (min-height:1381px){#D:after{content:\" x 1381\";}}\n@media (min-height:1382px){#D:after{content:\" x 1382\";}}\n@media (min-height:1383px){#D:after{content:\" x 1383\";}}\n@media (min-height:1384px){#D:after{content:\" x 1384\";}}\n@media (min-height:1385px){#D:after{content:\" x 1385\";}}\n@media (min-height:1386px){#D:after{content:\" x 1386\";}}\n@media (min-height:1387px){#D:after{content:\" x 1387\";}}\n@media (min-height:1388px){#D:after{content:\" x 1388\";}}\n@media (min-height:1389px){#D:after{content:\" x 1389\";}}\n@media (min-height:1390px){#D:after{content:\" x 1390\";}}\n@media (min-height:1391px){#D:after{content:\" x 1391\";}}\n@media (min-height:1392px){#D:after{content:\" x 1392\";}}\n@media (min-height:1393px){#D:after{content:\" x 1393\";}}\n@media (min-height:1394px){#D:after{content:\" x 1394\";}}\n@media (min-height:1395px){#D:after{content:\" x 1395\";}}\n@media (min-height:1396px){#D:after{content:\" x 1396\";}}\n@media (min-height:1397px){#D:after{content:\" x 1397\";}}\n@media (min-height:1398px){#D:after{content:\" x 1398\";}}\n@media (min-height:1399px){#D:after{content:\" x 1399\";}}\n@media (min-height:1400px){#D:after{content:\" x 1400\";}}\n@media (min-height:1401px){#D:after{content:\" x 1401\";}}\n@media (min-height:1402px){#D:after{content:\" x 1402\";}}\n@media (min-height:1403px){#D:after{content:\" x 1403\";}}\n@media (min-height:1404px){#D:after{content:\" x 1404\";}}\n@media (min-height:1405px){#D:after{content:\" x 1405\";}}\n@media (min-height:1406px){#D:after{content:\" x 1406\";}}\n@media (min-height:1407px){#D:after{content:\" x 1407\";}}\n@media (min-height:1408px){#D:after{content:\" x 1408\";}}\n@media (min-height:1409px){#D:after{content:\" x 1409\";}}\n@media (min-height:1410px){#D:after{content:\" x 1410\";}}\n@media (min-height:1411px){#D:after{content:\" x 1411\";}}\n@media (min-height:1412px){#D:after{content:\" x 1412\";}}\n@media (min-height:1413px){#D:after{content:\" x 1413\";}}\n@media (min-height:1414px){#D:after{content:\" x 1414\";}}\n@media (min-height:1415px){#D:after{content:\" x 1415\";}}\n@media (min-height:1416px){#D:after{content:\" x 1416\";}}\n@media (min-height:1417px){#D:after{content:\" x 1417\";}}\n@media (min-height:1418px){#D:after{content:\" x 1418\";}}\n@media (min-height:1419px){#D:after{content:\" x 1419\";}}\n@media (min-height:1420px){#D:after{content:\" x 1420\";}}\n@media (min-height:1421px){#D:after{content:\" x 1421\";}}\n@media (min-height:1422px){#D:after{content:\" x 1422\";}}\n@media (min-height:1423px){#D:after{content:\" x 1423\";}}\n@media (min-height:1424px){#D:after{content:\" x 1424\";}}\n@media (min-height:1425px){#D:after{content:\" x 1425\";}}\n@media (min-height:1426px){#D:after{content:\" x 1426\";}}\n@media (min-height:1427px){#D:after{content:\" x 1427\";}}\n@media (min-height:1428px){#D:after{content:\" x 1428\";}}\n@media (min-height:1429px){#D:after{content:\" x 1429\";}}\n@media (min-height:1430px){#D:after{content:\" x 1430\";}}\n@media (min-height:1431px){#D:after{content:\" x 1431\";}}\n@media (min-height:1432px){#D:after{content:\" x 1432\";}}\n@media (min-height:1433px){#D:after{content:\" x 1433\";}}\n@media (min-height:1434px){#D:after{content:\" x 1434\";}}\n@media (min-height:1435px){#D:after{content:\" x 1435\";}}\n@media (min-height:1436px){#D:after{content:\" x 1436\";}}\n@media (min-height:1437px){#D:after{content:\" x 1437\";}}\n@media (min-height:1438px){#D:after{content:\" x 1438\";}}\n@media (min-height:1439px){#D:after{content:\" x 1439\";}}\n@media (min-height:1440px){#D:after{content:\" x 1440\";}}\n@media (min-height:1441px){#D:after{content:\" x 1441\";}}\n@media (min-height:1442px){#D:after{content:\" x 1442\";}}\n@media (min-height:1443px){#D:after{content:\" x 1443\";}}\n@media (min-height:1444px){#D:after{content:\" x 1444\";}}\n@media (min-height:1445px){#D:after{content:\" x 1445\";}}\n@media (min-height:1446px){#D:after{content:\" x 1446\";}}\n@media (min-height:1447px){#D:after{content:\" x 1447\";}}\n@media (min-height:1448px){#D:after{content:\" x 1448\";}}\n@media (min-height:1449px){#D:after{content:\" x 1449\";}}\n@media (min-height:1450px){#D:after{content:\" x 1450\";}}\n@media (min-height:1451px){#D:after{content:\" x 1451\";}}\n@media (min-height:1452px){#D:after{content:\" x 1452\";}}\n@media (min-height:1453px){#D:after{content:\" x 1453\";}}\n@media (min-height:1454px){#D:after{content:\" x 1454\";}}\n@media (min-height:1455px){#D:after{content:\" x 1455\";}}\n@media (min-height:1456px){#D:after{content:\" x 1456\";}}\n@media (min-height:1457px){#D:after{content:\" x 1457\";}}\n@media (min-height:1458px){#D:after{content:\" x 1458\";}}\n@media (min-height:1459px){#D:after{content:\" x 1459\";}}\n@media (min-height:1460px){#D:after{content:\" x 1460\";}}\n@media (min-height:1461px){#D:after{content:\" x 1461\";}}\n@media (min-height:1462px){#D:after{content:\" x 1462\";}}\n@media (min-height:1463px){#D:after{content:\" x 1463\";}}\n@media (min-height:1464px){#D:after{content:\" x 1464\";}}\n@media (min-height:1465px){#D:after{content:\" x 1465\";}}\n@media (min-height:1466px){#D:after{content:\" x 1466\";}}\n@media (min-height:1467px){#D:after{content:\" x 1467\";}}\n@media (min-height:1468px){#D:after{content:\" x 1468\";}}\n@media (min-height:1469px){#D:after{content:\" x 1469\";}}\n@media (min-height:1470px){#D:after{content:\" x 1470\";}}\n@media (min-height:1471px){#D:after{content:\" x 1471\";}}\n@media (min-height:1472px){#D:after{content:\" x 1472\";}}\n@media (min-height:1473px){#D:after{content:\" x 1473\";}}\n@media (min-height:1474px){#D:after{content:\" x 1474\";}}\n@media (min-height:1475px){#D:after{content:\" x 1475\";}}\n@media (min-height:1476px){#D:after{content:\" x 1476\";}}\n@media (min-height:1477px){#D:after{content:\" x 1477\";}}\n@media (min-height:1478px){#D:after{content:\" x 1478\";}}\n@media (min-height:1479px){#D:after{content:\" x 1479\";}}\n@media (min-height:1480px){#D:after{content:\" x 1480\";}}\n@media (min-height:1481px){#D:after{content:\" x 1481\";}}\n@media (min-height:1482px){#D:after{content:\" x 1482\";}}\n@media (min-height:1483px){#D:after{content:\" x 1483\";}}\n@media (min-height:1484px){#D:after{content:\" x 1484\";}}\n@media (min-height:1485px){#D:after{content:\" x 1485\";}}\n@media (min-height:1486px){#D:after{content:\" x 1486\";}}\n@media (min-height:1487px){#D:after{content:\" x 1487\";}}\n@media (min-height:1488px){#D:after{content:\" x 1488\";}}\n@media (min-height:1489px){#D:after{content:\" x 1489\";}}\n@media (min-height:1490px){#D:after{content:\" x 1490\";}}\n@media (min-height:1491px){#D:after{content:\" x 1491\";}}\n@media (min-height:1492px){#D:after{content:\" x 1492\";}}\n@media (min-height:1493px){#D:after{content:\" x 1493\";}}\n@media (min-height:1494px){#D:after{content:\" x 1494\";}}\n@media (min-height:1495px){#D:after{content:\" x 1495\";}}\n@media (min-height:1496px){#D:after{content:\" x 1496\";}}\n@media (min-height:1497px){#D:after{content:\" x 1497\";}}\n@media (min-height:1498px){#D:after{content:\" x 1498\";}}\n@media (min-height:1499px){#D:after{content:\" x 1499\";}}\n@media (min-height:1500px){#D:after{content:\" x 1500\";}}\n@media (min-height:1501px){#D:after{content:\" x 1501\";}}\n@media (min-height:1502px){#D:after{content:\" x 1502\";}}\n@media (min-height:1503px){#D:after{content:\" x 1503\";}}\n@media (min-height:1504px){#D:after{content:\" x 1504\";}}\n@media (min-height:1505px){#D:after{content:\" x 1505\";}}\n@media (min-height:1506px){#D:after{content:\" x 1506\";}}\n@media (min-height:1507px){#D:after{content:\" x 1507\";}}\n@media (min-height:1508px){#D:after{content:\" x 1508\";}}\n@media (min-height:1509px){#D:after{content:\" x 1509\";}}\n@media (min-height:1510px){#D:after{content:\" x 1510\";}}\n@media (min-height:1511px){#D:after{content:\" x 1511\";}}\n@media (min-height:1512px){#D:after{content:\" x 1512\";}}\n@media (min-height:1513px){#D:after{content:\" x 1513\";}}\n@media (min-height:1514px){#D:after{content:\" x 1514\";}}\n@media (min-height:1515px){#D:after{content:\" x 1515\";}}\n@media (min-height:1516px){#D:after{content:\" x 1516\";}}\n@media (min-height:1517px){#D:after{content:\" x 1517\";}}\n@media (min-height:1518px){#D:after{content:\" x 1518\";}}\n@media (min-height:1519px){#D:after{content:\" x 1519\";}}\n@media (min-height:1520px){#D:after{content:\" x 1520\";}}\n@media (min-height:1521px){#D:after{content:\" x 1521\";}}\n@media (min-height:1522px){#D:after{content:\" x 1522\";}}\n@media (min-height:1523px){#D:after{content:\" x 1523\";}}\n@media (min-height:1524px){#D:after{content:\" x 1524\";}}\n@media (min-height:1525px){#D:after{content:\" x 1525\";}}\n@media (min-height:1526px){#D:after{content:\" x 1526\";}}\n@media (min-height:1527px){#D:after{content:\" x 1527\";}}\n@media (min-height:1528px){#D:after{content:\" x 1528\";}}\n@media (min-height:1529px){#D:after{content:\" x 1529\";}}\n@media (min-height:1530px){#D:after{content:\" x 1530\";}}\n@media (min-height:1531px){#D:after{content:\" x 1531\";}}\n@media (min-height:1532px){#D:after{content:\" x 1532\";}}\n@media (min-height:1533px){#D:after{content:\" x 1533\";}}\n@media (min-height:1534px){#D:after{content:\" x 1534\";}}\n@media (min-height:1535px){#D:after{content:\" x 1535\";}}\n@media (min-height:1536px){#D:after{content:\" x 1536\";}}\n@media (min-height:1537px){#D:after{content:\" x 1537\";}}\n@media (min-height:1538px){#D:after{content:\" x 1538\";}}\n@media (min-height:1539px){#D:after{content:\" x 1539\";}}\n@media (min-height:1540px){#D:after{content:\" x 1540\";}}\n@media (min-height:1541px){#D:after{content:\" x 1541\";}}\n@media (min-height:1542px){#D:after{content:\" x 1542\";}}\n@media (min-height:1543px){#D:after{content:\" x 1543\";}}\n@media (min-height:1544px){#D:after{content:\" x 1544\";}}\n@media (min-height:1545px){#D:after{content:\" x 1545\";}}\n@media (min-height:1546px){#D:after{content:\" x 1546\";}}\n@media (min-height:1547px){#D:after{content:\" x 1547\";}}\n@media (min-height:1548px){#D:after{content:\" x 1548\";}}\n@media (min-height:1549px){#D:after{content:\" x 1549\";}}\n@media (min-height:1550px){#D:after{content:\" x 1550\";}}\n@media (min-height:1551px){#D:after{content:\" x 1551\";}}\n@media (min-height:1552px){#D:after{content:\" x 1552\";}}\n@media (min-height:1553px){#D:after{content:\" x 1553\";}}\n@media (min-height:1554px){#D:after{content:\" x 1554\";}}\n@media (min-height:1555px){#D:after{content:\" x 1555\";}}\n@media (min-height:1556px){#D:after{content:\" x 1556\";}}\n@media (min-height:1557px){#D:after{content:\" x 1557\";}}\n@media (min-height:1558px){#D:after{content:\" x 1558\";}}\n@media (min-height:1559px){#D:after{content:\" x 1559\";}}\n@media (min-height:1560px){#D:after{content:\" x 1560\";}}\n@media (min-height:1561px){#D:after{content:\" x 1561\";}}\n@media (min-height:1562px){#D:after{content:\" x 1562\";}}\n@media (min-height:1563px){#D:after{content:\" x 1563\";}}\n@media (min-height:1564px){#D:after{content:\" x 1564\";}}\n@media (min-height:1565px){#D:after{content:\" x 1565\";}}\n@media (min-height:1566px){#D:after{content:\" x 1566\";}}\n@media (min-height:1567px){#D:after{content:\" x 1567\";}}\n@media (min-height:1568px){#D:after{content:\" x 1568\";}}\n@media (min-height:1569px){#D:after{content:\" x 1569\";}}\n@media (min-height:1570px){#D:after{content:\" x 1570\";}}\n@media (min-height:1571px){#D:after{content:\" x 1571\";}}\n@media (min-height:1572px){#D:after{content:\" x 1572\";}}\n@media (min-height:1573px){#D:after{content:\" x 1573\";}}\n@media (min-height:1574px){#D:after{content:\" x 1574\";}}\n@media (min-height:1575px){#D:after{content:\" x 1575\";}}\n@media (min-height:1576px){#D:after{content:\" x 1576\";}}\n@media (min-height:1577px){#D:after{content:\" x 1577\";}}\n@media (min-height:1578px){#D:after{content:\" x 1578\";}}\n@media (min-height:1579px){#D:after{content:\" x 1579\";}}\n@media (min-height:1580px){#D:after{content:\" x 1580\";}}\n@media (min-height:1581px){#D:after{content:\" x 1581\";}}\n@media (min-height:1582px){#D:after{content:\" x 1582\";}}\n@media (min-height:1583px){#D:after{content:\" x 1583\";}}\n@media (min-height:1584px){#D:after{content:\" x 1584\";}}\n@media (min-height:1585px){#D:after{content:\" x 1585\";}}\n@media (min-height:1586px){#D:after{content:\" x 1586\";}}\n@media (min-height:1587px){#D:after{content:\" x 1587\";}}\n@media (min-height:1588px){#D:after{content:\" x 1588\";}}\n@media (min-height:1589px){#D:after{content:\" x 1589\";}}\n@media (min-height:1590px){#D:after{content:\" x 1590\";}}\n@media (min-height:1591px){#D:after{content:\" x 1591\";}}\n@media (min-height:1592px){#D:after{content:\" x 1592\";}}\n@media (min-height:1593px){#D:after{content:\" x 1593\";}}\n@media (min-height:1594px){#D:after{content:\" x 1594\";}}\n@media (min-height:1595px){#D:after{content:\" x 1595\";}}\n@media (min-height:1596px){#D:after{content:\" x 1596\";}}\n@media (min-height:1597px){#D:after{content:\" x 1597\";}}\n@media (min-height:1598px){#D:after{content:\" x 1598\";}}\n@media (min-height:1599px){#D:after{content:\" x 1599\";}}\n@media (min-height:1600px){#D:after{content:\" x 1600\";}}\n@media (min-height:1601px){#D:after{content:\" x 1601\";}}\n@media (min-height:1602px){#D:after{content:\" x 1602\";}}\n@media (min-height:1603px){#D:after{content:\" x 1603\";}}\n@media (min-height:1604px){#D:after{content:\" x 1604\";}}\n@media (min-height:1605px){#D:after{content:\" x 1605\";}}\n@media (min-height:1606px){#D:after{content:\" x 1606\";}}\n@media (min-height:1607px){#D:after{content:\" x 1607\";}}\n@media (min-height:1608px){#D:after{content:\" x 1608\";}}\n@media (min-height:1609px){#D:after{content:\" x 1609\";}}\n@media (min-height:1610px){#D:after{content:\" x 1610\";}}\n@media (min-height:1611px){#D:after{content:\" x 1611\";}}\n@media (min-height:1612px){#D:after{content:\" x 1612\";}}\n@media (min-height:1613px){#D:after{content:\" x 1613\";}}\n@media (min-height:1614px){#D:after{content:\" x 1614\";}}\n@media (min-height:1615px){#D:after{content:\" x 1615\";}}\n@media (min-height:1616px){#D:after{content:\" x 1616\";}}\n@media (min-height:1617px){#D:after{content:\" x 1617\";}}\n@media (min-height:1618px){#D:after{content:\" x 1618\";}}\n@media (min-height:1619px){#D:after{content:\" x 1619\";}}\n@media (min-height:1620px){#D:after{content:\" x 1620\";}}\n@media (min-height:1621px){#D:after{content:\" x 1621\";}}\n@media (min-height:1622px){#D:after{content:\" x 1622\";}}\n@media (min-height:1623px){#D:after{content:\" x 1623\";}}\n@media (min-height:1624px){#D:after{content:\" x 1624\";}}\n@media (min-height:1625px){#D:after{content:\" x 1625\";}}\n@media (min-height:1626px){#D:after{content:\" x 1626\";}}\n@media (min-height:1627px){#D:after{content:\" x 1627\";}}\n@media (min-height:1628px){#D:after{content:\" x 1628\";}}\n@media (min-height:1629px){#D:after{content:\" x 1629\";}}\n@media (min-height:1630px){#D:after{content:\" x 1630\";}}\n@media (min-height:1631px){#D:after{content:\" x 1631\";}}\n@media (min-height:1632px){#D:after{content:\" x 1632\";}}\n@media (min-height:1633px){#D:after{content:\" x 1633\";}}\n@media (min-height:1634px){#D:after{content:\" x 1634\";}}\n@media (min-height:1635px){#D:after{content:\" x 1635\";}}\n@media (min-height:1636px){#D:after{content:\" x 1636\";}}\n@media (min-height:1637px){#D:after{content:\" x 1637\";}}\n@media (min-height:1638px){#D:after{content:\" x 1638\";}}\n@media (min-height:1639px){#D:after{content:\" x 1639\";}}\n@media (min-height:1640px){#D:after{content:\" x 1640\";}}\n@media (min-height:1641px){#D:after{content:\" x 1641\";}}\n@media (min-height:1642px){#D:after{content:\" x 1642\";}}\n@media (min-height:1643px){#D:after{content:\" x 1643\";}}\n@media (min-height:1644px){#D:after{content:\" x 1644\";}}\n@media (min-height:1645px){#D:after{content:\" x 1645\";}}\n@media (min-height:1646px){#D:after{content:\" x 1646\";}}\n@media (min-height:1647px){#D:after{content:\" x 1647\";}}\n@media (min-height:1648px){#D:after{content:\" x 1648\";}}\n@media (min-height:1649px){#D:after{content:\" x 1649\";}}\n@media (min-height:1650px){#D:after{content:\" x 1650\";}}\n@media (min-height:1651px){#D:after{content:\" x 1651\";}}\n@media (min-height:1652px){#D:after{content:\" x 1652\";}}\n@media (min-height:1653px){#D:after{content:\" x 1653\";}}\n@media (min-height:1654px){#D:after{content:\" x 1654\";}}\n@media (min-height:1655px){#D:after{content:\" x 1655\";}}\n@media (min-height:1656px){#D:after{content:\" x 1656\";}}\n@media (min-height:1657px){#D:after{content:\" x 1657\";}}\n@media (min-height:1658px){#D:after{content:\" x 1658\";}}\n@media (min-height:1659px){#D:after{content:\" x 1659\";}}\n@media (min-height:1660px){#D:after{content:\" x 1660\";}}\n@media (min-height:1661px){#D:after{content:\" x 1661\";}}\n@media (min-height:1662px){#D:after{content:\" x 1662\";}}\n@media (min-height:1663px){#D:after{content:\" x 1663\";}}\n@media (min-height:1664px){#D:after{content:\" x 1664\";}}\n@media (min-height:1665px){#D:after{content:\" x 1665\";}}\n@media (min-height:1666px){#D:after{content:\" x 1666\";}}\n@media (min-height:1667px){#D:after{content:\" x 1667\";}}\n@media (min-height:1668px){#D:after{content:\" x 1668\";}}\n@media (min-height:1669px){#D:after{content:\" x 1669\";}}\n@media (min-height:1670px){#D:after{content:\" x 1670\";}}\n@media (min-height:1671px){#D:after{content:\" x 1671\";}}\n@media (min-height:1672px){#D:after{content:\" x 1672\";}}\n@media (min-height:1673px){#D:after{content:\" x 1673\";}}\n@media (min-height:1674px){#D:after{content:\" x 1674\";}}\n@media (min-height:1675px){#D:after{content:\" x 1675\";}}\n@media (min-height:1676px){#D:after{content:\" x 1676\";}}\n@media (min-height:1677px){#D:after{content:\" x 1677\";}}\n@media (min-height:1678px){#D:after{content:\" x 1678\";}}\n@media (min-height:1679px){#D:after{content:\" x 1679\";}}\n@media (min-height:1680px){#D:after{content:\" x 1680\";}}\n@media (min-height:1681px){#D:after{content:\" x 1681\";}}\n@media (min-height:1682px){#D:after{content:\" x 1682\";}}\n@media (min-height:1683px){#D:after{content:\" x 1683\";}}\n@media (min-height:1684px){#D:after{content:\" x 1684\";}}\n@media (min-height:1685px){#D:after{content:\" x 1685\";}}\n@media (min-height:1686px){#D:after{content:\" x 1686\";}}\n@media (min-height:1687px){#D:after{content:\" x 1687\";}}\n@media (min-height:1688px){#D:after{content:\" x 1688\";}}\n@media (min-height:1689px){#D:after{content:\" x 1689\";}}\n@media (min-height:1690px){#D:after{content:\" x 1690\";}}\n@media (min-height:1691px){#D:after{content:\" x 1691\";}}\n@media (min-height:1692px){#D:after{content:\" x 1692\";}}\n@media (min-height:1693px){#D:after{content:\" x 1693\";}}\n@media (min-height:1694px){#D:after{content:\" x 1694\";}}\n@media (min-height:1695px){#D:after{content:\" x 1695\";}}\n@media (min-height:1696px){#D:after{content:\" x 1696\";}}\n@media (min-height:1697px){#D:after{content:\" x 1697\";}}\n@media (min-height:1698px){#D:after{content:\" x 1698\";}}\n@media (min-height:1699px){#D:after{content:\" x 1699\";}}\n@media (min-height:1700px){#D:after{content:\" x 1700\";}}\n@media (min-height:1701px){#D:after{content:\" x 1701\";}}\n@media (min-height:1702px){#D:after{content:\" x 1702\";}}\n@media (min-height:1703px){#D:after{content:\" x 1703\";}}\n@media (min-height:1704px){#D:after{content:\" x 1704\";}}\n@media (min-height:1705px){#D:after{content:\" x 1705\";}}\n@media (min-height:1706px){#D:after{content:\" x 1706\";}}\n@media (min-height:1707px){#D:after{content:\" x 1707\";}}\n@media (min-height:1708px){#D:after{content:\" x 1708\";}}\n@media (min-height:1709px){#D:after{content:\" x 1709\";}}\n@media (min-height:1710px){#D:after{content:\" x 1710\";}}\n@media (min-height:1711px){#D:after{content:\" x 1711\";}}\n@media (min-height:1712px){#D:after{content:\" x 1712\";}}\n@media (min-height:1713px){#D:after{content:\" x 1713\";}}\n@media (min-height:1714px){#D:after{content:\" x 1714\";}}\n@media (min-height:1715px){#D:after{content:\" x 1715\";}}\n@media (min-height:1716px){#D:after{content:\" x 1716\";}}\n@media (min-height:1717px){#D:after{content:\" x 1717\";}}\n@media (min-height:1718px){#D:after{content:\" x 1718\";}}\n@media (min-height:1719px){#D:after{content:\" x 1719\";}}\n@media (min-height:1720px){#D:after{content:\" x 1720\";}}\n@media (min-height:1721px){#D:after{content:\" x 1721\";}}\n@media (min-height:1722px){#D:after{content:\" x 1722\";}}\n@media (min-height:1723px){#D:after{content:\" x 1723\";}}\n@media (min-height:1724px){#D:after{content:\" x 1724\";}}\n@media (min-height:1725px){#D:after{content:\" x 1725\";}}\n@media (min-height:1726px){#D:after{content:\" x 1726\";}}\n@media (min-height:1727px){#D:after{content:\" x 1727\";}}\n@media (min-height:1728px){#D:after{content:\" x 1728\";}}\n@media (min-height:1729px){#D:after{content:\" x 1729\";}}\n@media (min-height:1730px){#D:after{content:\" x 1730\";}}\n@media (min-height:1731px){#D:after{content:\" x 1731\";}}\n@media (min-height:1732px){#D:after{content:\" x 1732\";}}\n@media (min-height:1733px){#D:after{content:\" x 1733\";}}\n@media (min-height:1734px){#D:after{content:\" x 1734\";}}\n@media (min-height:1735px){#D:after{content:\" x 1735\";}}\n@media (min-height:1736px){#D:after{content:\" x 1736\";}}\n@media (min-height:1737px){#D:after{content:\" x 1737\";}}\n@media (min-height:1738px){#D:after{content:\" x 1738\";}}\n@media (min-height:1739px){#D:after{content:\" x 1739\";}}\n@media (min-height:1740px){#D:after{content:\" x 1740\";}}\n@media (min-height:1741px){#D:after{content:\" x 1741\";}}\n@media (min-height:1742px){#D:after{content:\" x 1742\";}}\n@media (min-height:1743px){#D:after{content:\" x 1743\";}}\n@media (min-height:1744px){#D:after{content:\" x 1744\";}}\n@media (min-height:1745px){#D:after{content:\" x 1745\";}}\n@media (min-height:1746px){#D:after{content:\" x 1746\";}}\n@media (min-height:1747px){#D:after{content:\" x 1747\";}}\n@media (min-height:1748px){#D:after{content:\" x 1748\";}}\n@media (min-height:1749px){#D:after{content:\" x 1749\";}}\n@media (min-height:1750px){#D:after{content:\" x 1750\";}}\n@media (min-height:1751px){#D:after{content:\" x 1751\";}}\n@media (min-height:1752px){#D:after{content:\" x 1752\";}}\n@media (min-height:1753px){#D:after{content:\" x 1753\";}}\n@media (min-height:1754px){#D:after{content:\" x 1754\";}}\n@media (min-height:1755px){#D:after{content:\" x 1755\";}}\n@media (min-height:1756px){#D:after{content:\" x 1756\";}}\n@media (min-height:1757px){#D:after{content:\" x 1757\";}}\n@media (min-height:1758px){#D:after{content:\" x 1758\";}}\n@media (min-height:1759px){#D:after{content:\" x 1759\";}}\n@media (min-height:1760px){#D:after{content:\" x 1760\";}}\n@media (min-height:1761px){#D:after{content:\" x 1761\";}}\n@media (min-height:1762px){#D:after{content:\" x 1762\";}}\n@media (min-height:1763px){#D:after{content:\" x 1763\";}}\n@media (min-height:1764px){#D:after{content:\" x 1764\";}}\n@media (min-height:1765px){#D:after{content:\" x 1765\";}}\n@media (min-height:1766px){#D:after{content:\" x 1766\";}}\n@media (min-height:1767px){#D:after{content:\" x 1767\";}}\n@media (min-height:1768px){#D:after{content:\" x 1768\";}}\n@media (min-height:1769px){#D:after{content:\" x 1769\";}}\n@media (min-height:1770px){#D:after{content:\" x 1770\";}}\n@media (min-height:1771px){#D:after{content:\" x 1771\";}}\n@media (min-height:1772px){#D:after{content:\" x 1772\";}}\n@media (min-height:1773px){#D:after{content:\" x 1773\";}}\n@media (min-height:1774px){#D:after{content:\" x 1774\";}}\n@media (min-height:1775px){#D:after{content:\" x 1775\";}}\n@media (min-height:1776px){#D:after{content:\" x 1776\";}}\n@media (min-height:1777px){#D:after{content:\" x 1777\";}}\n@media (min-height:1778px){#D:after{content:\" x 1778\";}}\n@media (min-height:1779px){#D:after{content:\" x 1779\";}}\n@media (min-height:1780px){#D:after{content:\" x 1780\";}}\n@media (min-height:1781px){#D:after{content:\" x 1781\";}}\n@media (min-height:1782px){#D:after{content:\" x 1782\";}}\n@media (min-height:1783px){#D:after{content:\" x 1783\";}}\n@media (min-height:1784px){#D:after{content:\" x 1784\";}}\n@media (min-height:1785px){#D:after{content:\" x 1785\";}}\n@media (min-height:1786px){#D:after{content:\" x 1786\";}}\n@media (min-height:1787px){#D:after{content:\" x 1787\";}}\n@media (min-height:1788px){#D:after{content:\" x 1788\";}}\n@media (min-height:1789px){#D:after{content:\" x 1789\";}}\n@media (min-height:1790px){#D:after{content:\" x 1790\";}}\n@media (min-height:1791px){#D:after{content:\" x 1791\";}}\n@media (min-height:1792px){#D:after{content:\" x 1792\";}}\n@media (min-height:1793px){#D:after{content:\" x 1793\";}}\n@media (min-height:1794px){#D:after{content:\" x 1794\";}}\n@media (min-height:1795px){#D:after{content:\" x 1795\";}}\n@media (min-height:1796px){#D:after{content:\" x 1796\";}}\n@media (min-height:1797px){#D:after{content:\" x 1797\";}}\n@media (min-height:1798px){#D:after{content:\" x 1798\";}}\n@media (min-height:1799px){#D:after{content:\" x 1799\";}}\n@media (min-height:1800px){#D:after{content:\" x 1800\";}}\n@media (min-height:1801px){#D:after{content:\" x 1801\";}}\n@media (min-height:1802px){#D:after{content:\" x 1802\";}}\n@media (min-height:1803px){#D:after{content:\" x 1803\";}}\n@media (min-height:1804px){#D:after{content:\" x 1804\";}}\n@media (min-height:1805px){#D:after{content:\" x 1805\";}}\n@media (min-height:1806px){#D:after{content:\" x 1806\";}}\n@media (min-height:1807px){#D:after{content:\" x 1807\";}}\n@media (min-height:1808px){#D:after{content:\" x 1808\";}}\n@media (min-height:1809px){#D:after{content:\" x 1809\";}}\n@media (min-height:1810px){#D:after{content:\" x 1810\";}}\n@media (min-height:1811px){#D:after{content:\" x 1811\";}}\n@media (min-height:1812px){#D:after{content:\" x 1812\";}}\n@media (min-height:1813px){#D:after{content:\" x 1813\";}}\n@media (min-height:1814px){#D:after{content:\" x 1814\";}}\n@media (min-height:1815px){#D:after{content:\" x 1815\";}}\n@media (min-height:1816px){#D:after{content:\" x 1816\";}}\n@media (min-height:1817px){#D:after{content:\" x 1817\";}}\n@media (min-height:1818px){#D:after{content:\" x 1818\";}}\n@media (min-height:1819px){#D:after{content:\" x 1819\";}}\n@media (min-height:1820px){#D:after{content:\" x 1820\";}}\n@media (min-height:1821px){#D:after{content:\" x 1821\";}}\n@media (min-height:1822px){#D:after{content:\" x 1822\";}}\n@media (min-height:1823px){#D:after{content:\" x 1823\";}}\n@media (min-height:1824px){#D:after{content:\" x 1824\";}}\n@media (min-height:1825px){#D:after{content:\" x 1825\";}}\n@media (min-height:1826px){#D:after{content:\" x 1826\";}}\n@media (min-height:1827px){#D:after{content:\" x 1827\";}}\n@media (min-height:1828px){#D:after{content:\" x 1828\";}}\n@media (min-height:1829px){#D:after{content:\" x 1829\";}}\n@media (min-height:1830px){#D:after{content:\" x 1830\";}}\n@media (min-height:1831px){#D:after{content:\" x 1831\";}}\n@media (min-height:1832px){#D:after{content:\" x 1832\";}}\n@media (min-height:1833px){#D:after{content:\" x 1833\";}}\n@media (min-height:1834px){#D:after{content:\" x 1834\";}}\n@media (min-height:1835px){#D:after{content:\" x 1835\";}}\n@media (min-height:1836px){#D:after{content:\" x 1836\";}}\n@media (min-height:1837px){#D:after{content:\" x 1837\";}}\n@media (min-height:1838px){#D:after{content:\" x 1838\";}}\n@media (min-height:1839px){#D:after{content:\" x 1839\";}}\n@media (min-height:1840px){#D:after{content:\" x 1840\";}}\n@media (min-height:1841px){#D:after{content:\" x 1841\";}}\n@media (min-height:1842px){#D:after{content:\" x 1842\";}}\n@media (min-height:1843px){#D:after{content:\" x 1843\";}}\n@media (min-height:1844px){#D:after{content:\" x 1844\";}}\n@media (min-height:1845px){#D:after{content:\" x 1845\";}}\n@media (min-height:1846px){#D:after{content:\" x 1846\";}}\n@media (min-height:1847px){#D:after{content:\" x 1847\";}}\n@media (min-height:1848px){#D:after{content:\" x 1848\";}}\n@media (min-height:1849px){#D:after{content:\" x 1849\";}}\n@media (min-height:1850px){#D:after{content:\" x 1850\";}}\n@media (min-height:1851px){#D:after{content:\" x 1851\";}}\n@media (min-height:1852px){#D:after{content:\" x 1852\";}}\n@media (min-height:1853px){#D:after{content:\" x 1853\";}}\n@media (min-height:1854px){#D:after{content:\" x 1854\";}}\n@media (min-height:1855px){#D:after{content:\" x 1855\";}}\n@media (min-height:1856px){#D:after{content:\" x 1856\";}}\n@media (min-height:1857px){#D:after{content:\" x 1857\";}}\n@media (min-height:1858px){#D:after{content:\" x 1858\";}}\n@media (min-height:1859px){#D:after{content:\" x 1859\";}}\n@media (min-height:1860px){#D:after{content:\" x 1860\";}}\n@media (min-height:1861px){#D:after{content:\" x 1861\";}}\n@media (min-height:1862px){#D:after{content:\" x 1862\";}}\n@media (min-height:1863px){#D:after{content:\" x 1863\";}}\n@media (min-height:1864px){#D:after{content:\" x 1864\";}}\n@media (min-height:1865px){#D:after{content:\" x 1865\";}}\n@media (min-height:1866px){#D:after{content:\" x 1866\";}}\n@media (min-height:1867px){#D:after{content:\" x 1867\";}}\n@media (min-height:1868px){#D:after{content:\" x 1868\";}}\n@media (min-height:1869px){#D:after{content:\" x 1869\";}}\n@media (min-height:1870px){#D:after{content:\" x 1870\";}}\n@media (min-height:1871px){#D:after{content:\" x 1871\";}}\n@media (min-height:1872px){#D:after{content:\" x 1872\";}}\n@media (min-height:1873px){#D:after{content:\" x 1873\";}}\n@media (min-height:1874px){#D:after{content:\" x 1874\";}}\n@media (min-height:1875px){#D:after{content:\" x 1875\";}}\n@media (min-height:1876px){#D:after{content:\" x 1876\";}}\n@media (min-height:1877px){#D:after{content:\" x 1877\";}}\n@media (min-height:1878px){#D:after{content:\" x 1878\";}}\n@media (min-height:1879px){#D:after{content:\" x 1879\";}}\n@media (min-height:1880px){#D:after{content:\" x 1880\";}}\n@media (min-height:1881px){#D:after{content:\" x 1881\";}}\n@media (min-height:1882px){#D:after{content:\" x 1882\";}}\n@media (min-height:1883px){#D:after{content:\" x 1883\";}}\n@media (min-height:1884px){#D:after{content:\" x 1884\";}}\n@media (min-height:1885px){#D:after{content:\" x 1885\";}}\n@media (min-height:1886px){#D:after{content:\" x 1886\";}}\n@media (min-height:1887px){#D:after{content:\" x 1887\";}}\n@media (min-height:1888px){#D:after{content:\" x 1888\";}}\n@media (min-height:1889px){#D:after{content:\" x 1889\";}}\n@media (min-height:1890px){#D:after{content:\" x 1890\";}}\n@media (min-height:1891px){#D:after{content:\" x 1891\";}}\n@media (min-height:1892px){#D:after{content:\" x 1892\";}}\n@media (min-height:1893px){#D:after{content:\" x 1893\";}}\n@media (min-height:1894px){#D:after{content:\" x 1894\";}}\n@media (min-height:1895px){#D:after{content:\" x 1895\";}}\n@media (min-height:1896px){#D:after{content:\" x 1896\";}}\n@media (min-height:1897px){#D:after{content:\" x 1897\";}}\n@media (min-height:1898px){#D:after{content:\" x 1898\";}}\n@media (min-height:1899px){#D:after{content:\" x 1899\";}}\n@media (min-height:1900px){#D:after{content:\" x 1900\";}}\n@media (min-height:1901px){#D:after{content:\" x 1901\";}}\n@media (min-height:1902px){#D:after{content:\" x 1902\";}}\n@media (min-height:1903px){#D:after{content:\" x 1903\";}}\n@media (min-height:1904px){#D:after{content:\" x 1904\";}}\n@media (min-height:1905px){#D:after{content:\" x 1905\";}}\n@media (min-height:1906px){#D:after{content:\" x 1906\";}}\n@media (min-height:1907px){#D:after{content:\" x 1907\";}}\n@media (min-height:1908px){#D:after{content:\" x 1908\";}}\n@media (min-height:1909px){#D:after{content:\" x 1909\";}}\n@media (min-height:1910px){#D:after{content:\" x 1910\";}}\n@media (min-height:1911px){#D:after{content:\" x 1911\";}}\n@media (min-height:1912px){#D:after{content:\" x 1912\";}}\n@media (min-height:1913px){#D:after{content:\" x 1913\";}}\n@media (min-height:1914px){#D:after{content:\" x 1914\";}}\n@media (min-height:1915px){#D:after{content:\" x 1915\";}}\n@media (min-height:1916px){#D:after{content:\" x 1916\";}}\n@media (min-height:1917px){#D:after{content:\" x 1917\";}}\n@media (min-height:1918px){#D:after{content:\" x 1918\";}}\n@media (min-height:1919px){#D:after{content:\" x 1919\";}}\n@media (min-height:1920px){#D:after{content:\" x 1920\";}}\n@media (min-height:1921px){#D:after{content:\" x 1921\";}}\n@media (min-height:1922px){#D:after{content:\" x 1922\";}}\n@media (min-height:1923px){#D:after{content:\" x 1923\";}}\n@media (min-height:1924px){#D:after{content:\" x 1924\";}}\n@media (min-height:1925px){#D:after{content:\" x 1925\";}}\n@media (min-height:1926px){#D:after{content:\" x 1926\";}}\n@media (min-height:1927px){#D:after{content:\" x 1927\";}}\n@media (min-height:1928px){#D:after{content:\" x 1928\";}}\n@media (min-height:1929px){#D:after{content:\" x 1929\";}}\n@media (min-height:1930px){#D:after{content:\" x 1930\";}}\n@media (min-height:1931px){#D:after{content:\" x 1931\";}}\n@media (min-height:1932px){#D:after{content:\" x 1932\";}}\n@media (min-height:1933px){#D:after{content:\" x 1933\";}}\n@media (min-height:1934px){#D:after{content:\" x 1934\";}}\n@media (min-height:1935px){#D:after{content:\" x 1935\";}}\n@media (min-height:1936px){#D:after{content:\" x 1936\";}}\n@media (min-height:1937px){#D:after{content:\" x 1937\";}}\n@media (min-height:1938px){#D:after{content:\" x 1938\";}}\n@media (min-height:1939px){#D:after{content:\" x 1939\";}}\n@media (min-height:1940px){#D:after{content:\" x 1940\";}}\n@media (min-height:1941px){#D:after{content:\" x 1941\";}}\n@media (min-height:1942px){#D:after{content:\" x 1942\";}}\n@media (min-height:1943px){#D:after{content:\" x 1943\";}}\n@media (min-height:1944px){#D:after{content:\" x 1944\";}}\n@media (min-height:1945px){#D:after{content:\" x 1945\";}}\n@media (min-height:1946px){#D:after{content:\" x 1946\";}}\n@media (min-height:1947px){#D:after{content:\" x 1947\";}}\n@media (min-height:1948px){#D:after{content:\" x 1948\";}}\n@media (min-height:1949px){#D:after{content:\" x 1949\";}}\n@media (min-height:1950px){#D:after{content:\" x 1950\";}}\n@media (min-height:1951px){#D:after{content:\" x 1951\";}}\n@media (min-height:1952px){#D:after{content:\" x 1952\";}}\n@media (min-height:1953px){#D:after{content:\" x 1953\";}}\n@media (min-height:1954px){#D:after{content:\" x 1954\";}}\n@media (min-height:1955px){#D:after{content:\" x 1955\";}}\n@media (min-height:1956px){#D:after{content:\" x 1956\";}}\n@media (min-height:1957px){#D:after{content:\" x 1957\";}}\n@media (min-height:1958px){#D:after{content:\" x 1958\";}}\n@media (min-height:1959px){#D:after{content:\" x 1959\";}}\n@media (min-height:1960px){#D:after{content:\" x 1960\";}}\n@media (min-height:1961px){#D:after{content:\" x 1961\";}}\n@media (min-height:1962px){#D:after{content:\" x 1962\";}}\n@media (min-height:1963px){#D:after{content:\" x 1963\";}}\n@media (min-height:1964px){#D:after{content:\" x 1964\";}}\n@media (min-height:1965px){#D:after{content:\" x 1965\";}}\n@media (min-height:1966px){#D:after{content:\" x 1966\";}}\n@media (min-height:1967px){#D:after{content:\" x 1967\";}}\n@media (min-height:1968px){#D:after{content:\" x 1968\";}}\n@media (min-height:1969px){#D:after{content:\" x 1969\";}}\n@media (min-height:1970px){#D:after{content:\" x 1970\";}}\n@media (min-height:1971px){#D:after{content:\" x 1971\";}}\n@media (min-height:1972px){#D:after{content:\" x 1972\";}}\n@media (min-height:1973px){#D:after{content:\" x 1973\";}}\n@media (min-height:1974px){#D:after{content:\" x 1974\";}}\n@media (min-height:1975px){#D:after{content:\" x 1975\";}}\n@media (min-height:1976px){#D:after{content:\" x 1976\";}}\n@media (min-height:1977px){#D:after{content:\" x 1977\";}}\n@media (min-height:1978px){#D:after{content:\" x 1978\";}}\n@media (min-height:1979px){#D:after{content:\" x 1979\";}}\n@media (min-height:1980px){#D:after{content:\" x 1980\";}}\n@media (min-height:1981px){#D:after{content:\" x 1981\";}}\n@media (min-height:1982px){#D:after{content:\" x 1982\";}}\n@media (min-height:1983px){#D:after{content:\" x 1983\";}}\n@media (min-height:1984px){#D:after{content:\" x 1984\";}}\n@media (min-height:1985px){#D:after{content:\" x 1985\";}}\n@media (min-height:1986px){#D:after{content:\" x 1986\";}}\n@media (min-height:1987px){#D:after{content:\" x 1987\";}}\n@media (min-height:1988px){#D:after{content:\" x 1988\";}}\n@media (min-height:1989px){#D:after{content:\" x 1989\";}}\n@media (min-height:1990px){#D:after{content:\" x 1990\";}}\n@media (min-height:1991px){#D:after{content:\" x 1991\";}}\n@media (min-height:1992px){#D:after{content:\" x 1992\";}}\n@media (min-height:1993px){#D:after{content:\" x 1993\";}}\n@media (min-height:1994px){#D:after{content:\" x 1994\";}}\n@media (min-height:1995px){#D:after{content:\" x 1995\";}}\n@media (min-height:1996px){#D:after{content:\" x 1996\";}}\n@media (min-height:1997px){#D:after{content:\" x 1997\";}}\n@media (min-height:1998px){#D:after{content:\" x 1998\";}}\n@media (min-height:1999px){#D:after{content:\" x 1999\";}}\n@media (min-height:2000px){#D:after{content:\" x 2000\";}}\n@media (min-height:2001px){#D:after{content:\" x 2001\";}}\n@media (min-height:2002px){#D:after{content:\" x 2002\";}}\n@media (min-height:2003px){#D:after{content:\" x 2003\";}}\n@media (min-height:2004px){#D:after{content:\" x 2004\";}}\n@media (min-height:2005px){#D:after{content:\" x 2005\";}}\n@media (min-height:2006px){#D:after{content:\" x 2006\";}}\n@media (min-height:2007px){#D:after{content:\" x 2007\";}}\n@media (min-height:2008px){#D:after{content:\" x 2008\";}}\n@media (min-height:2009px){#D:after{content:\" x 2009\";}}\n@media (min-height:2010px){#D:after{content:\" x 2010\";}}\n@media (min-height:2011px){#D:after{content:\" x 2011\";}}\n@media (min-height:2012px){#D:after{content:\" x 2012\";}}\n@media (min-height:2013px){#D:after{content:\" x 2013\";}}\n@media (min-height:2014px){#D:after{content:\" x 2014\";}}\n@media (min-height:2015px){#D:after{content:\" x 2015\";}}\n@media (min-height:2016px){#D:after{content:\" x 2016\";}}\n@media (min-height:2017px){#D:after{content:\" x 2017\";}}\n@media (min-height:2018px){#D:after{content:\" x 2018\";}}\n@media (min-height:2019px){#D:after{content:\" x 2019\";}}\n@media (min-height:2020px){#D:after{content:\" x 2020\";}}\n@media (min-height:2021px){#D:after{content:\" x 2021\";}}\n@media (min-height:2022px){#D:after{content:\" x 2022\";}}\n@media (min-height:2023px){#D:after{content:\" x 2023\";}}\n@media (min-height:2024px){#D:after{content:\" x 2024\";}}\n@media (min-height:2025px){#D:after{content:\" x 2025\";}}\n@media (min-height:2026px){#D:after{content:\" x 2026\";}}\n@media (min-height:2027px){#D:after{content:\" x 2027\";}}\n@media (min-height:2028px){#D:after{content:\" x 2028\";}}\n@media (min-height:2029px){#D:after{content:\" x 2029\";}}\n@media (min-height:2030px){#D:after{content:\" x 2030\";}}\n@media (min-height:2031px){#D:after{content:\" x 2031\";}}\n@media (min-height:2032px){#D:after{content:\" x 2032\";}}\n@media (min-height:2033px){#D:after{content:\" x 2033\";}}\n@media (min-height:2034px){#D:after{content:\" x 2034\";}}\n@media (min-height:2035px){#D:after{content:\" x 2035\";}}\n@media (min-height:2036px){#D:after{content:\" x 2036\";}}\n@media (min-height:2037px){#D:after{content:\" x 2037\";}}\n@media (min-height:2038px){#D:after{content:\" x 2038\";}}\n@media (min-height:2039px){#D:after{content:\" x 2039\";}}\n@media (min-height:2040px){#D:after{content:\" x 2040\";}}\n@media (min-height:2041px){#D:after{content:\" x 2041\";}}\n@media (min-height:2042px){#D:after{content:\" x 2042\";}}\n@media (min-height:2043px){#D:after{content:\" x 2043\";}}\n@media (min-height:2044px){#D:after{content:\" x 2044\";}}\n@media (min-height:2045px){#D:after{content:\" x 2045\";}}\n@media (min-height:2046px){#D:after{content:\" x 2046\";}}\n@media (min-height:2047px){#D:after{content:\" x 2047\";}}\n@media (min-height:2048px){#D:after{content:\" x 2048\";}}\n@media (min-height:2049px){#D:after{content:\" x 2049\";}}\n@media (min-height:2050px){#D:after{content:\" x 2050\";}}\n@media (min-height:2051px){#D:after{content:\" x 2051\";}}\n@media (min-height:2052px){#D:after{content:\" x 2052\";}}\n@media (min-height:2053px){#D:after{content:\" x 2053\";}}\n@media (min-height:2054px){#D:after{content:\" x 2054\";}}\n@media (min-height:2055px){#D:after{content:\" x 2055\";}}\n@media (min-height:2056px){#D:after{content:\" x 2056\";}}\n@media (min-height:2057px){#D:after{content:\" x 2057\";}}\n@media (min-height:2058px){#D:after{content:\" x 2058\";}}\n@media (min-height:2059px){#D:after{content:\" x 2059\";}}\n@media (min-height:2060px){#D:after{content:\" x 2060\";}}\n@media (min-height:2061px){#D:after{content:\" x 2061\";}}\n@media (min-height:2062px){#D:after{content:\" x 2062\";}}\n@media (min-height:2063px){#D:after{content:\" x 2063\";}}\n@media (min-height:2064px){#D:after{content:\" x 2064\";}}\n@media (min-height:2065px){#D:after{content:\" x 2065\";}}\n@media (min-height:2066px){#D:after{content:\" x 2066\";}}\n@media (min-height:2067px){#D:after{content:\" x 2067\";}}\n@media (min-height:2068px){#D:after{content:\" x 2068\";}}\n@media (min-height:2069px){#D:after{content:\" x 2069\";}}\n@media (min-height:2070px){#D:after{content:\" x 2070\";}}\n@media (min-height:2071px){#D:after{content:\" x 2071\";}}\n@media (min-height:2072px){#D:after{content:\" x 2072\";}}\n@media (min-height:2073px){#D:after{content:\" x 2073\";}}\n@media (min-height:2074px){#D:after{content:\" x 2074\";}}\n@media (min-height:2075px){#D:after{content:\" x 2075\";}}\n@media (min-height:2076px){#D:after{content:\" x 2076\";}}\n@media (min-height:2077px){#D:after{content:\" x 2077\";}}\n@media (min-height:2078px){#D:after{content:\" x 2078\";}}\n@media (min-height:2079px){#D:after{content:\" x 2079\";}}\n@media (min-height:2080px){#D:after{content:\" x 2080\";}}\n@media (min-height:2081px){#D:after{content:\" x 2081\";}}\n@media (min-height:2082px){#D:after{content:\" x 2082\";}}\n@media (min-height:2083px){#D:after{content:\" x 2083\";}}\n@media (min-height:2084px){#D:after{content:\" x 2084\";}}\n@media (min-height:2085px){#D:after{content:\" x 2085\";}}\n@media (min-height:2086px){#D:after{content:\" x 2086\";}}\n@media (min-height:2087px){#D:after{content:\" x 2087\";}}\n@media (min-height:2088px){#D:after{content:\" x 2088\";}}\n@media (min-height:2089px){#D:after{content:\" x 2089\";}}\n@media (min-height:2090px){#D:after{content:\" x 2090\";}}\n@media (min-height:2091px){#D:after{content:\" x 2091\";}}\n@media (min-height:2092px){#D:after{content:\" x 2092\";}}\n@media (min-height:2093px){#D:after{content:\" x 2093\";}}\n@media (min-height:2094px){#D:after{content:\" x 2094\";}}\n@media (min-height:2095px){#D:after{content:\" x 2095\";}}\n@media (min-height:2096px){#D:after{content:\" x 2096\";}}\n@media (min-height:2097px){#D:after{content:\" x 2097\";}}\n@media (min-height:2098px){#D:after{content:\" x 2098\";}}\n@media (min-height:2099px){#D:after{content:\" x 2099\";}}\n@media (min-height:2100px){#D:after{content:\" x 2100\";}}\n@media (min-height:2101px){#D:after{content:\" x 2101\";}}\n@media (min-height:2102px){#D:after{content:\" x 2102\";}}\n@media (min-height:2103px){#D:after{content:\" x 2103\";}}\n@media (min-height:2104px){#D:after{content:\" x 2104\";}}\n@media (min-height:2105px){#D:after{content:\" x 2105\";}}\n@media (min-height:2106px){#D:after{content:\" x 2106\";}}\n@media (min-height:2107px){#D:after{content:\" x 2107\";}}\n@media (min-height:2108px){#D:after{content:\" x 2108\";}}\n@media (min-height:2109px){#D:after{content:\" x 2109\";}}\n@media (min-height:2110px){#D:after{content:\" x 2110\";}}\n@media (min-height:2111px){#D:after{content:\" x 2111\";}}\n@media (min-height:2112px){#D:after{content:\" x 2112\";}}\n@media (min-height:2113px){#D:after{content:\" x 2113\";}}\n@media (min-height:2114px){#D:after{content:\" x 2114\";}}\n@media (min-height:2115px){#D:after{content:\" x 2115\";}}\n@media (min-height:2116px){#D:after{content:\" x 2116\";}}\n@media (min-height:2117px){#D:after{content:\" x 2117\";}}\n@media (min-height:2118px){#D:after{content:\" x 2118\";}}\n@media (min-height:2119px){#D:after{content:\" x 2119\";}}\n@media (min-height:2120px){#D:after{content:\" x 2120\";}}\n@media (min-height:2121px){#D:after{content:\" x 2121\";}}\n@media (min-height:2122px){#D:after{content:\" x 2122\";}}\n@media (min-height:2123px){#D:after{content:\" x 2123\";}}\n@media (min-height:2124px){#D:after{content:\" x 2124\";}}\n@media (min-height:2125px){#D:after{content:\" x 2125\";}}\n@media (min-height:2126px){#D:after{content:\" x 2126\";}}\n@media (min-height:2127px){#D:after{content:\" x 2127\";}}\n@media (min-height:2128px){#D:after{content:\" x 2128\";}}\n@media (min-height:2129px){#D:after{content:\" x 2129\";}}\n@media (min-height:2130px){#D:after{content:\" x 2130\";}}\n@media (min-height:2131px){#D:after{content:\" x 2131\";}}\n@media (min-height:2132px){#D:after{content:\" x 2132\";}}\n@media (min-height:2133px){#D:after{content:\" x 2133\";}}\n@media (min-height:2134px){#D:after{content:\" x 2134\";}}\n@media (min-height:2135px){#D:after{content:\" x 2135\";}}\n@media (min-height:2136px){#D:after{content:\" x 2136\";}}\n@media (min-height:2137px){#D:after{content:\" x 2137\";}}\n@media (min-height:2138px){#D:after{content:\" x 2138\";}}\n@media (min-height:2139px){#D:after{content:\" x 2139\";}}\n@media (min-height:2140px){#D:after{content:\" x 2140\";}}\n@media (min-height:2141px){#D:after{content:\" x 2141\";}}\n@media (min-height:2142px){#D:after{content:\" x 2142\";}}\n@media (min-height:2143px){#D:after{content:\" x 2143\";}}\n@media (min-height:2144px){#D:after{content:\" x 2144\";}}\n@media (min-height:2145px){#D:after{content:\" x 2145\";}}\n@media (min-height:2146px){#D:after{content:\" x 2146\";}}\n@media (min-height:2147px){#D:after{content:\" x 2147\";}}\n@media (min-height:2148px){#D:after{content:\" x 2148\";}}\n@media (min-height:2149px){#D:after{content:\" x 2149\";}}\n@media (min-height:2150px){#D:after{content:\" x 2150\";}}\n@media (min-height:2151px){#D:after{content:\" x 2151\";}}\n@media (min-height:2152px){#D:after{content:\" x 2152\";}}\n@media (min-height:2153px){#D:after{content:\" x 2153\";}}\n@media (min-height:2154px){#D:after{content:\" x 2154\";}}\n@media (min-height:2155px){#D:after{content:\" x 2155\";}}\n@media (min-height:2156px){#D:after{content:\" x 2156\";}}\n@media (min-height:2157px){#D:after{content:\" x 2157\";}}\n@media (min-height:2158px){#D:after{content:\" x 2158\";}}\n@media (min-height:2159px){#D:after{content:\" x 2159\";}}\n@media (min-height:2160px){#D:after{content:\" x 2160\";}}\n@media (min-height:2161px){#D:after{content:\" x 2161\";}}\n@media (min-height:2162px){#D:after{content:\" x 2162\";}}\n@media (min-height:2163px){#D:after{content:\" x 2163\";}}\n@media (min-height:2164px){#D:after{content:\" x 2164\";}}\n@media (min-height:2165px){#D:after{content:\" x 2165\";}}\n@media (min-height:2166px){#D:after{content:\" x 2166\";}}\n@media (min-height:2167px){#D:after{content:\" x 2167\";}}\n@media (min-height:2168px){#D:after{content:\" x 2168\";}}\n@media (min-height:2169px){#D:after{content:\" x 2169\";}}\n@media (min-height:2170px){#D:after{content:\" x 2170\";}}\n@media (min-height:2171px){#D:after{content:\" x 2171\";}}\n@media (min-height:2172px){#D:after{content:\" x 2172\";}}\n@media (min-height:2173px){#D:after{content:\" x 2173\";}}\n@media (min-height:2174px){#D:after{content:\" x 2174\";}}\n@media (min-height:2175px){#D:after{content:\" x 2175\";}}\n@media (min-height:2176px){#D:after{content:\" x 2176\";}}\n@media (min-height:2177px){#D:after{content:\" x 2177\";}}\n@media (min-height:2178px){#D:after{content:\" x 2178\";}}\n@media (min-height:2179px){#D:after{content:\" x 2179\";}}\n@media (min-height:2180px){#D:after{content:\" x 2180\";}}\n@media (min-height:2181px){#D:after{content:\" x 2181\";}}\n@media (min-height:2182px){#D:after{content:\" x 2182\";}}\n@media (min-height:2183px){#D:after{content:\" x 2183\";}}\n@media (min-height:2184px){#D:after{content:\" x 2184\";}}\n@media (min-height:2185px){#D:after{content:\" x 2185\";}}\n@media (min-height:2186px){#D:after{content:\" x 2186\";}}\n@media (min-height:2187px){#D:after{content:\" x 2187\";}}\n@media (min-height:2188px){#D:after{content:\" x 2188\";}}\n@media (min-height:2189px){#D:after{content:\" x 2189\";}}\n@media (min-height:2190px){#D:after{content:\" x 2190\";}}\n@media (min-height:2191px){#D:after{content:\" x 2191\";}}\n@media (min-height:2192px){#D:after{content:\" x 2192\";}}\n@media (min-height:2193px){#D:after{content:\" x 2193\";}}\n@media (min-height:2194px){#D:after{content:\" x 2194\";}}\n@media (min-height:2195px){#D:after{content:\" x 2195\";}}\n@media (min-height:2196px){#D:after{content:\" x 2196\";}}\n@media (min-height:2197px){#D:after{content:\" x 2197\";}}\n@media (min-height:2198px){#D:after{content:\" x 2198\";}}\n@media (min-height:2199px){#D:after{content:\" x 2199\";}}\n@media (min-height:2200px){#D:after{content:\" x 2200\";}}\n@media (min-height:2201px){#D:after{content:\" x 2201\";}}\n@media (min-height:2202px){#D:after{content:\" x 2202\";}}\n@media (min-height:2203px){#D:after{content:\" x 2203\";}}\n@media (min-height:2204px){#D:after{content:\" x 2204\";}}\n@media (min-height:2205px){#D:after{content:\" x 2205\";}}\n@media (min-height:2206px){#D:after{content:\" x 2206\";}}\n@media (min-height:2207px){#D:after{content:\" x 2207\";}}\n@media (min-height:2208px){#D:after{content:\" x 2208\";}}\n@media (min-height:2209px){#D:after{content:\" x 2209\";}}\n@media (min-height:2210px){#D:after{content:\" x 2210\";}}\n@media (min-height:2211px){#D:after{content:\" x 2211\";}}\n@media (min-height:2212px){#D:after{content:\" x 2212\";}}\n@media (min-height:2213px){#D:after{content:\" x 2213\";}}\n@media (min-height:2214px){#D:after{content:\" x 2214\";}}\n@media (min-height:2215px){#D:after{content:\" x 2215\";}}\n@media (min-height:2216px){#D:after{content:\" x 2216\";}}\n@media (min-height:2217px){#D:after{content:\" x 2217\";}}\n@media (min-height:2218px){#D:after{content:\" x 2218\";}}\n@media (min-height:2219px){#D:after{content:\" x 2219\";}}\n@media (min-height:2220px){#D:after{content:\" x 2220\";}}\n@media (min-height:2221px){#D:after{content:\" x 2221\";}}\n@media (min-height:2222px){#D:after{content:\" x 2222\";}}\n@media (min-height:2223px){#D:after{content:\" x 2223\";}}\n@media (min-height:2224px){#D:after{content:\" x 2224\";}}\n@media (min-height:2225px){#D:after{content:\" x 2225\";}}\n@media (min-height:2226px){#D:after{content:\" x 2226\";}}\n@media (min-height:2227px){#D:after{content:\" x 2227\";}}\n@media (min-height:2228px){#D:after{content:\" x 2228\";}}\n@media (min-height:2229px){#D:after{content:\" x 2229\";}}\n@media (min-height:2230px){#D:after{content:\" x 2230\";}}\n@media (min-height:2231px){#D:after{content:\" x 2231\";}}\n@media (min-height:2232px){#D:after{content:\" x 2232\";}}\n@media (min-height:2233px){#D:after{content:\" x 2233\";}}\n@media (min-height:2234px){#D:after{content:\" x 2234\";}}\n@media (min-height:2235px){#D:after{content:\" x 2235\";}}\n@media (min-height:2236px){#D:after{content:\" x 2236\";}}\n@media (min-height:2237px){#D:after{content:\" x 2237\";}}\n@media (min-height:2238px){#D:after{content:\" x 2238\";}}\n@media (min-height:2239px){#D:after{content:\" x 2239\";}}\n@media (min-height:2240px){#D:after{content:\" x 2240\";}}\n@media (min-height:2241px){#D:after{content:\" x 2241\";}}\n@media (min-height:2242px){#D:after{content:\" x 2242\";}}\n@media (min-height:2243px){#D:after{content:\" x 2243\";}}\n@media (min-height:2244px){#D:after{content:\" x 2244\";}}\n@media (min-height:2245px){#D:after{content:\" x 2245\";}}\n@media (min-height:2246px){#D:after{content:\" x 2246\";}}\n@media (min-height:2247px){#D:after{content:\" x 2247\";}}\n@media (min-height:2248px){#D:after{content:\" x 2248\";}}\n@media (min-height:2249px){#D:after{content:\" x 2249\";}}\n@media (min-height:2250px){#D:after{content:\" x 2250\";}}\n@media (min-height:2251px){#D:after{content:\" x 2251\";}}\n@media (min-height:2252px){#D:after{content:\" x 2252\";}}\n@media (min-height:2253px){#D:after{content:\" x 2253\";}}\n@media (min-height:2254px){#D:after{content:\" x 2254\";}}\n@media (min-height:2255px){#D:after{content:\" x 2255\";}}\n@media (min-height:2256px){#D:after{content:\" x 2256\";}}\n@media (min-height:2257px){#D:after{content:\" x 2257\";}}\n@media (min-height:2258px){#D:after{content:\" x 2258\";}}\n@media (min-height:2259px){#D:after{content:\" x 2259\";}}\n@media (min-height:2260px){#D:after{content:\" x 2260\";}}\n@media (min-height:2261px){#D:after{content:\" x 2261\";}}\n@media (min-height:2262px){#D:after{content:\" x 2262\";}}\n@media (min-height:2263px){#D:after{content:\" x 2263\";}}\n@media (min-height:2264px){#D:after{content:\" x 2264\";}}\n@media (min-height:2265px){#D:after{content:\" x 2265\";}}\n@media (min-height:2266px){#D:after{content:\" x 2266\";}}\n@media (min-height:2267px){#D:after{content:\" x 2267\";}}\n@media (min-height:2268px){#D:after{content:\" x 2268\";}}\n@media (min-height:2269px){#D:after{content:\" x 2269\";}}\n@media (min-height:2270px){#D:after{content:\" x 2270\";}}\n@media (min-height:2271px){#D:after{content:\" x 2271\";}}\n@media (min-height:2272px){#D:after{content:\" x 2272\";}}\n@media (min-height:2273px){#D:after{content:\" x 2273\";}}\n@media (min-height:2274px){#D:after{content:\" x 2274\";}}\n@media (min-height:2275px){#D:after{content:\" x 2275\";}}\n@media (min-height:2276px){#D:after{content:\" x 2276\";}}\n@media (min-height:2277px){#D:after{content:\" x 2277\";}}\n@media (min-height:2278px){#D:after{content:\" x 2278\";}}\n@media (min-height:2279px){#D:after{content:\" x 2279\";}}\n@media (min-height:2280px){#D:after{content:\" x 2280\";}}\n@media (min-height:2281px){#D:after{content:\" x 2281\";}}\n@media (min-height:2282px){#D:after{content:\" x 2282\";}}\n@media (min-height:2283px){#D:after{content:\" x 2283\";}}\n@media (min-height:2284px){#D:after{content:\" x 2284\";}}\n@media (min-height:2285px){#D:after{content:\" x 2285\";}}\n@media (min-height:2286px){#D:after{content:\" x 2286\";}}\n@media (min-height:2287px){#D:after{content:\" x 2287\";}}\n@media (min-height:2288px){#D:after{content:\" x 2288\";}}\n@media (min-height:2289px){#D:after{content:\" x 2289\";}}\n@media (min-height:2290px){#D:after{content:\" x 2290\";}}\n@media (min-height:2291px){#D:after{content:\" x 2291\";}}\n@media (min-height:2292px){#D:after{content:\" x 2292\";}}\n@media (min-height:2293px){#D:after{content:\" x 2293\";}}\n@media (min-height:2294px){#D:after{content:\" x 2294\";}}\n@media (min-height:2295px){#D:after{content:\" x 2295\";}}\n@media (min-height:2296px){#D:after{content:\" x 2296\";}}\n@media (min-height:2297px){#D:after{content:\" x 2297\";}}\n@media (min-height:2298px){#D:after{content:\" x 2298\";}}\n@media (min-height:2299px){#D:after{content:\" x 2299\";}}\n@media (min-height:2300px){#D:after{content:\" x 2300\";}}\n@media (min-height:2301px){#D:after{content:\" x 2301\";}}\n@media (min-height:2302px){#D:after{content:\" x 2302\";}}\n@media (min-height:2303px){#D:after{content:\" x 2303\";}}\n@media (min-height:2304px){#D:after{content:\" x 2304\";}}\n@media (min-height:2305px){#D:after{content:\" x 2305\";}}\n@media (min-height:2306px){#D:after{content:\" x 2306\";}}\n@media (min-height:2307px){#D:after{content:\" x 2307\";}}\n@media (min-height:2308px){#D:after{content:\" x 2308\";}}\n@media (min-height:2309px){#D:after{content:\" x 2309\";}}\n@media (min-height:2310px){#D:after{content:\" x 2310\";}}\n@media (min-height:2311px){#D:after{content:\" x 2311\";}}\n@media (min-height:2312px){#D:after{content:\" x 2312\";}}\n@media (min-height:2313px){#D:after{content:\" x 2313\";}}\n@media (min-height:2314px){#D:after{content:\" x 2314\";}}\n@media (min-height:2315px){#D:after{content:\" x 2315\";}}\n@media (min-height:2316px){#D:after{content:\" x 2316\";}}\n@media (min-height:2317px){#D:after{content:\" x 2317\";}}\n@media (min-height:2318px){#D:after{content:\" x 2318\";}}\n@media (min-height:2319px){#D:after{content:\" x 2319\";}}\n@media (min-height:2320px){#D:after{content:\" x 2320\";}}\n@media (min-height:2321px){#D:after{content:\" x 2321\";}}\n@media (min-height:2322px){#D:after{content:\" x 2322\";}}\n@media (min-height:2323px){#D:after{content:\" x 2323\";}}\n@media (min-height:2324px){#D:after{content:\" x 2324\";}}\n@media (min-height:2325px){#D:after{content:\" x 2325\";}}\n@media (min-height:2326px){#D:after{content:\" x 2326\";}}\n@media (min-height:2327px){#D:after{content:\" x 2327\";}}\n@media (min-height:2328px){#D:after{content:\" x 2328\";}}\n@media (min-height:2329px){#D:after{content:\" x 2329\";}}\n@media (min-height:2330px){#D:after{content:\" x 2330\";}}\n@media (min-height:2331px){#D:after{content:\" x 2331\";}}\n@media (min-height:2332px){#D:after{content:\" x 2332\";}}\n@media (min-height:2333px){#D:after{content:\" x 2333\";}}\n@media (min-height:2334px){#D:after{content:\" x 2334\";}}\n@media (min-height:2335px){#D:after{content:\" x 2335\";}}\n@media (min-height:2336px){#D:after{content:\" x 2336\";}}\n@media (min-height:2337px){#D:after{content:\" x 2337\";}}\n@media (min-height:2338px){#D:after{content:\" x 2338\";}}\n@media (min-height:2339px){#D:after{content:\" x 2339\";}}\n@media (min-height:2340px){#D:after{content:\" x 2340\";}}\n@media (min-height:2341px){#D:after{content:\" x 2341\";}}\n@media (min-height:2342px){#D:after{content:\" x 2342\";}}\n@media (min-height:2343px){#D:after{content:\" x 2343\";}}\n@media (min-height:2344px){#D:after{content:\" x 2344\";}}\n@media (min-height:2345px){#D:after{content:\" x 2345\";}}\n@media (min-height:2346px){#D:after{content:\" x 2346\";}}\n@media (min-height:2347px){#D:after{content:\" x 2347\";}}\n@media (min-height:2348px){#D:after{content:\" x 2348\";}}\n@media (min-height:2349px){#D:after{content:\" x 2349\";}}\n@media (min-height:2350px){#D:after{content:\" x 2350\";}}\n@media (min-height:2351px){#D:after{content:\" x 2351\";}}\n@media (min-height:2352px){#D:after{content:\" x 2352\";}}\n@media (min-height:2353px){#D:after{content:\" x 2353\";}}\n@media (min-height:2354px){#D:after{content:\" x 2354\";}}\n@media (min-height:2355px){#D:after{content:\" x 2355\";}}\n@media (min-height:2356px){#D:after{content:\" x 2356\";}}\n@media (min-height:2357px){#D:after{content:\" x 2357\";}}\n@media (min-height:2358px){#D:after{content:\" x 2358\";}}\n@media (min-height:2359px){#D:after{content:\" x 2359\";}}\n@media (min-height:2360px){#D:after{content:\" x 2360\";}}\n@media (min-height:2361px){#D:after{content:\" x 2361\";}}\n@media (min-height:2362px){#D:after{content:\" x 2362\";}}\n@media (min-height:2363px){#D:after{content:\" x 2363\";}}\n@media (min-height:2364px){#D:after{content:\" x 2364\";}}\n@media (min-height:2365px){#D:after{content:\" x 2365\";}}\n@media (min-height:2366px){#D:after{content:\" x 2366\";}}\n@media (min-height:2367px){#D:after{content:\" x 2367\";}}\n@media (min-height:2368px){#D:after{content:\" x 2368\";}}\n@media (min-height:2369px){#D:after{content:\" x 2369\";}}\n@media (min-height:2370px){#D:after{content:\" x 2370\";}}\n@media (min-height:2371px){#D:after{content:\" x 2371\";}}\n@media (min-height:2372px){#D:after{content:\" x 2372\";}}\n@media (min-height:2373px){#D:after{content:\" x 2373\";}}\n@media (min-height:2374px){#D:after{content:\" x 2374\";}}\n@media (min-height:2375px){#D:after{content:\" x 2375\";}}\n@media (min-height:2376px){#D:after{content:\" x 2376\";}}\n@media (min-height:2377px){#D:after{content:\" x 2377\";}}\n@media (min-height:2378px){#D:after{content:\" x 2378\";}}\n@media (min-height:2379px){#D:after{content:\" x 2379\";}}\n@media (min-height:2380px){#D:after{content:\" x 2380\";}}\n@media (min-height:2381px){#D:after{content:\" x 2381\";}}\n@media (min-height:2382px){#D:after{content:\" x 2382\";}}\n@media (min-height:2383px){#D:after{content:\" x 2383\";}}\n@media (min-height:2384px){#D:after{content:\" x 2384\";}}\n@media (min-height:2385px){#D:after{content:\" x 2385\";}}\n@media (min-height:2386px){#D:after{content:\" x 2386\";}}\n@media (min-height:2387px){#D:after{content:\" x 2387\";}}\n@media (min-height:2388px){#D:after{content:\" x 2388\";}}\n@media (min-height:2389px){#D:after{content:\" x 2389\";}}\n@media (min-height:2390px){#D:after{content:\" x 2390\";}}\n@media (min-height:2391px){#D:after{content:\" x 2391\";}}\n@media (min-height:2392px){#D:after{content:\" x 2392\";}}\n@media (min-height:2393px){#D:after{content:\" x 2393\";}}\n@media (min-height:2394px){#D:after{content:\" x 2394\";}}\n@media (min-height:2395px){#D:after{content:\" x 2395\";}}\n@media (min-height:2396px){#D:after{content:\" x 2396\";}}\n@media (min-height:2397px){#D:after{content:\" x 2397\";}}\n@media (min-height:2398px){#D:after{content:\" x 2398\";}}\n@media (min-height:2399px){#D:after{content:\" x 2399\";}}\n@media (min-height:2400px){#D:after{content:\" x 2400\";}}\n@media (min-height:2401px){#D:after{content:\" x 2401\";}}\n@media (min-height:2402px){#D:after{content:\" x 2402\";}}\n@media (min-height:2403px){#D:after{content:\" x 2403\";}}\n@media (min-height:2404px){#D:after{content:\" x 2404\";}}\n@media (min-height:2405px){#D:after{content:\" x 2405\";}}\n@media (min-height:2406px){#D:after{content:\" x 2406\";}}\n@media (min-height:2407px){#D:after{content:\" x 2407\";}}\n@media (min-height:2408px){#D:after{content:\" x 2408\";}}\n@media (min-height:2409px){#D:after{content:\" x 2409\";}}\n@media (min-height:2410px){#D:after{content:\" x 2410\";}}\n@media (min-height:2411px){#D:after{content:\" x 2411\";}}\n@media (min-height:2412px){#D:after{content:\" x 2412\";}}\n@media (min-height:2413px){#D:after{content:\" x 2413\";}}\n@media (min-height:2414px){#D:after{content:\" x 2414\";}}\n@media (min-height:2415px){#D:after{content:\" x 2415\";}}\n@media (min-height:2416px){#D:after{content:\" x 2416\";}}\n@media (min-height:2417px){#D:after{content:\" x 2417\";}}\n@media (min-height:2418px){#D:after{content:\" x 2418\";}}\n@media (min-height:2419px){#D:after{content:\" x 2419\";}}\n@media (min-height:2420px){#D:after{content:\" x 2420\";}}\n@media (min-height:2421px){#D:after{content:\" x 2421\";}}\n@media (min-height:2422px){#D:after{content:\" x 2422\";}}\n@media (min-height:2423px){#D:after{content:\" x 2423\";}}\n@media (min-height:2424px){#D:after{content:\" x 2424\";}}\n@media (min-height:2425px){#D:after{content:\" x 2425\";}}\n@media (min-height:2426px){#D:after{content:\" x 2426\";}}\n@media (min-height:2427px){#D:after{content:\" x 2427\";}}\n@media (min-height:2428px){#D:after{content:\" x 2428\";}}\n@media (min-height:2429px){#D:after{content:\" x 2429\";}}\n@media (min-height:2430px){#D:after{content:\" x 2430\";}}\n@media (min-height:2431px){#D:after{content:\" x 2431\";}}\n@media (min-height:2432px){#D:after{content:\" x 2432\";}}\n@media (min-height:2433px){#D:after{content:\" x 2433\";}}\n@media (min-height:2434px){#D:after{content:\" x 2434\";}}\n@media (min-height:2435px){#D:after{content:\" x 2435\";}}\n@media (min-height:2436px){#D:after{content:\" x 2436\";}}\n@media (min-height:2437px){#D:after{content:\" x 2437\";}}\n@media (min-height:2438px){#D:after{content:\" x 2438\";}}\n@media (min-height:2439px){#D:after{content:\" x 2439\";}}\n@media (min-height:2440px){#D:after{content:\" x 2440\";}}\n@media (min-height:2441px){#D:after{content:\" x 2441\";}}\n@media (min-height:2442px){#D:after{content:\" x 2442\";}}\n@media (min-height:2443px){#D:after{content:\" x 2443\";}}\n@media (min-height:2444px){#D:after{content:\" x 2444\";}}\n@media (min-height:2445px){#D:after{content:\" x 2445\";}}\n@media (min-height:2446px){#D:after{content:\" x 2446\";}}\n@media (min-height:2447px){#D:after{content:\" x 2447\";}}\n@media (min-height:2448px){#D:after{content:\" x 2448\";}}\n@media (min-height:2449px){#D:after{content:\" x 2449\";}}\n@media (min-height:2450px){#D:after{content:\" x 2450\";}}\n@media (min-height:2451px){#D:after{content:\" x 2451\";}}\n@media (min-height:2452px){#D:after{content:\" x 2452\";}}\n@media (min-height:2453px){#D:after{content:\" x 2453\";}}\n@media (min-height:2454px){#D:after{content:\" x 2454\";}}\n@media (min-height:2455px){#D:after{content:\" x 2455\";}}\n@media (min-height:2456px){#D:after{content:\" x 2456\";}}\n@media (min-height:2457px){#D:after{content:\" x 2457\";}}\n@media (min-height:2458px){#D:after{content:\" x 2458\";}}\n@media (min-height:2459px){#D:after{content:\" x 2459\";}}\n@media (min-height:2460px){#D:after{content:\" x 2460\";}}\n@media (min-height:2461px){#D:after{content:\" x 2461\";}}\n@media (min-height:2462px){#D:after{content:\" x 2462\";}}\n@media (min-height:2463px){#D:after{content:\" x 2463\";}}\n@media (min-height:2464px){#D:after{content:\" x 2464\";}}\n@media (min-height:2465px){#D:after{content:\" x 2465\";}}\n@media (min-height:2466px){#D:after{content:\" x 2466\";}}\n@media (min-height:2467px){#D:after{content:\" x 2467\";}}\n@media (min-height:2468px){#D:after{content:\" x 2468\";}}\n@media (min-height:2469px){#D:after{content:\" x 2469\";}}\n@media (min-height:2470px){#D:after{content:\" x 2470\";}}\n@media (min-height:2471px){#D:after{content:\" x 2471\";}}\n@media (min-height:2472px){#D:after{content:\" x 2472\";}}\n@media (min-height:2473px){#D:after{content:\" x 2473\";}}\n@media (min-height:2474px){#D:after{content:\" x 2474\";}}\n@media (min-height:2475px){#D:after{content:\" x 2475\";}}\n@media (min-height:2476px){#D:after{content:\" x 2476\";}}\n@media (min-height:2477px){#D:after{content:\" x 2477\";}}\n@media (min-height:2478px){#D:after{content:\" x 2478\";}}\n@media (min-height:2479px){#D:after{content:\" x 2479\";}}\n@media (min-height:2480px){#D:after{content:\" x 2480\";}}\n@media (min-height:2481px){#D:after{content:\" x 2481\";}}\n@media (min-height:2482px){#D:after{content:\" x 2482\";}}\n@media (min-height:2483px){#D:after{content:\" x 2483\";}}\n@media (min-height:2484px){#D:after{content:\" x 2484\";}}\n@media (min-height:2485px){#D:after{content:\" x 2485\";}}\n@media (min-height:2486px){#D:after{content:\" x 2486\";}}\n@media (min-height:2487px){#D:after{content:\" x 2487\";}}\n@media (min-height:2488px){#D:after{content:\" x 2488\";}}\n@media (min-height:2489px){#D:after{content:\" x 2489\";}}\n@media (min-height:2490px){#D:after{content:\" x 2490\";}}\n@media (min-height:2491px){#D:after{content:\" x 2491\";}}\n@media (min-height:2492px){#D:after{content:\" x 2492\";}}\n@media (min-height:2493px){#D:after{content:\" x 2493\";}}\n@media (min-height:2494px){#D:after{content:\" x 2494\";}}\n@media (min-height:2495px){#D:after{content:\" x 2495\";}}\n@media (min-height:2496px){#D:after{content:\" x 2496\";}}\n@media (min-height:2497px){#D:after{content:\" x 2497\";}}\n@media (min-height:2498px){#D:after{content:\" x 2498\";}}\n@media (min-height:2499px){#D:after{content:\" x 2499\";}}\n@media (min-height:2500px){#D:after{content:\" x 2500\";}}\n@media (min-height:2501px){#D:after{content:\" x 2501\";}}\n@media (min-height:2502px){#D:after{content:\" x 2502\";}}\n@media (min-height:2503px){#D:after{content:\" x 2503\";}}\n@media (min-height:2504px){#D:after{content:\" x 2504\";}}\n@media (min-height:2505px){#D:after{content:\" x 2505\";}}\n@media (min-height:2506px){#D:after{content:\" x 2506\";}}\n@media (min-height:2507px){#D:after{content:\" x 2507\";}}\n@media (min-height:2508px){#D:after{content:\" x 2508\";}}\n@media (min-height:2509px){#D:after{content:\" x 2509\";}}\n@media (min-height:2510px){#D:after{content:\" x 2510\";}}\n@media (min-height:2511px){#D:after{content:\" x 2511\";}}\n@media (min-height:2512px){#D:after{content:\" x 2512\";}}\n@media (min-height:2513px){#D:after{content:\" x 2513\";}}\n@media (min-height:2514px){#D:after{content:\" x 2514\";}}\n@media (min-height:2515px){#D:after{content:\" x 2515\";}}\n@media (min-height:2516px){#D:after{content:\" x 2516\";}}\n@media (min-height:2517px){#D:after{content:\" x 2517\";}}\n@media (min-height:2518px){#D:after{content:\" x 2518\";}}\n@media (min-height:2519px){#D:after{content:\" x 2519\";}}\n@media (min-height:2520px){#D:after{content:\" x 2520\";}}\n@media (min-height:2521px){#D:after{content:\" x 2521\";}}\n@media (min-height:2522px){#D:after{content:\" x 2522\";}}\n@media (min-height:2523px){#D:after{content:\" x 2523\";}}\n@media (min-height:2524px){#D:after{content:\" x 2524\";}}\n@media (min-height:2525px){#D:after{content:\" x 2525\";}}\n@media (min-height:2526px){#D:after{content:\" x 2526\";}}\n@media (min-height:2527px){#D:after{content:\" x 2527\";}}\n@media (min-height:2528px){#D:after{content:\" x 2528\";}}\n@media (min-height:2529px){#D:after{content:\" x 2529\";}}\n@media (min-height:2530px){#D:after{content:\" x 2530\";}}\n@media (min-height:2531px){#D:after{content:\" x 2531\";}}\n@media (min-height:2532px){#D:after{content:\" x 2532\";}}\n@media (min-height:2533px){#D:after{content:\" x 2533\";}}\n@media (min-height:2534px){#D:after{content:\" x 2534\";}}\n@media (min-height:2535px){#D:after{content:\" x 2535\";}}\n@media (min-height:2536px){#D:after{content:\" x 2536\";}}\n@media (min-height:2537px){#D:after{content:\" x 2537\";}}\n@media (min-height:2538px){#D:after{content:\" x 2538\";}}\n@media (min-height:2539px){#D:after{content:\" x 2539\";}}\n@media (min-height:2540px){#D:after{content:\" x 2540\";}}\n@media (min-height:2541px){#D:after{content:\" x 2541\";}}\n@media (min-height:2542px){#D:after{content:\" x 2542\";}}\n@media (min-height:2543px){#D:after{content:\" x 2543\";}}\n@media (min-height:2544px){#D:after{content:\" x 2544\";}}\n@media (min-height:2545px){#D:after{content:\" x 2545\";}}\n@media (min-height:2546px){#D:after{content:\" x 2546\";}}\n@media (min-height:2547px){#D:after{content:\" x 2547\";}}\n@media (min-height:2548px){#D:after{content:\" x 2548\";}}\n@media (min-height:2549px){#D:after{content:\" x 2549\";}}\n@media (min-height:2550px){#D:after{content:\" x 2550\";}}\n@media (min-height:2551px){#D:after{content:\" x 2551\";}}\n@media (min-height:2552px){#D:after{content:\" x 2552\";}}\n@media (min-height:2553px){#D:after{content:\" x 2553\";}}\n@media (min-height:2554px){#D:after{content:\" x 2554\";}}\n@media (min-height:2555px){#D:after{content:\" x 2555\";}}\n@media (min-height:2556px){#D:after{content:\" x 2556\";}}\n@media (min-height:2557px){#D:after{content:\" x 2557\";}}\n@media (min-height:2558px){#D:after{content:\" x 2558\";}}\n@media (min-height:2559px){#D:after{content:\" x 2559\";}}\n@media (min-height:2560px){#D:after{content:\" x 2560\";}}\n@media (min-height:2561px){#D:after{content:\"\";}}\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=350\">\n\t<title>tzp index</title>\n\t<link rel=\"icon\" type=\"image/png\" href=\"chrome://branding/content/icon32.png\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"css/index.css\">\n\t<!-- custom -->\n\t<style>\n\t\ttable {width: 97%; min-width: 330px; max-width: 480px;}\n\t\t/* use dark mode */\n\t\t:root{\n\t\t\t--bg0: #161b22;\n\t\t\t--bg99: #808080;\n\t\t\t--testh: #ffffff;\n\t\t\t--test0: #b3b3b3;\n\t\t\t--test1: #dc9d9d;\n\t\t\t--test2: #dcb29d;\n\t\t\t--test3: #dcc79d;\n\t\t\t--test4: #dcdc9d;\n\t\t\t--test5: #c7dc9d;\n\t\t\t--test6: #b2dc9d;\n\t\t\t--test7: #9ddc9d;\n\t\t\t--test8: #9ddcb2;\n\t\t\t--test9: #9ddcc7;\n\t\t\t--test10: #9ddcdc;\n\t\t\t--test11: #9dc7dc;\n\t\t\t--test12: #9db2dc;\n\t\t\t--test13: #9d9ddc;\n\t\t\t--test14: #b29ddc;\n\t\t\t--test15: #c79ddc;\n\t\t\t--test16: #dc9ddc;\n\t\t\t--test17: #dc9dc7;\n\t\t\t--test18: #dc9db2;\n\t\t\t--testbad: #ff8787;\n\t\t\t--testred: #ff8787;\n\t\t\t--testweight: normal;\n\t\t\t--txtlink: #9db2dc;\n\t\t}\n\t\thtml {scrollbar-width: auto;}\n\t</style>\n</head>\n<body>\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"tzp.html\">take me to the main test</a></td></tr>\n\t</table>\n\n\t<table id=\"tb99\">\n\t\t<col width=\"1%\"><col width=\"34%\"><col width=\"65%\">\n\t\t<thead><tr><th colspan=\"3\">index</th></tr></thead>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\t\t<tr><td colspan=\"3\" class=\"intro\"><span class=\"no_color\"><b>GITHUB</b></span></td></tr>\n\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"https://github.com/arkenfox/TZP/\">repo</a></td><td>come and say hi</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"screen\"></a>\n\t\t\t<a href=\"#screen\"><span class=\"s1\">SCREEN</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/screeniframe.html\">screen iframe</a></td>\n\t\t\t\t<td>test: parent vs iframe measurements</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/screenorientation.html\">screen orientation</a></td>\n\t\t\t\t<td>test: orientation, aspect ratio, angles and rotation</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/newwinsim.html\">new window sizes</a></td>\n\t\t\t\t<td>a what if sim for RFP's new win sizes</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"feature\"></a>\n\t\t\t<a href=\"#feature\"><span class=\"s3\">FEATURE DETECTION</span></a>\n\t\t</td></tr>\n\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/chrome.html\">chrome</a></td>\n\t\t\t\t<td>test: chrome:// + resource:// files</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/engine.html\">engine</a></td>\n\t\t\t\t<td>... you can run but you can't hide</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/engineprop.html\">engine properties</a></td>\n\t\t\t\t<td>test: enumeration of engine properties</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/os.html\">os</a></td>\n\t\t\t\t<td>test: gecko os detection logic</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/versions.html\">versions</a></td>\n\t\t\t\t<td>a history of version pocs tzp uses or considered</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"region\"></a>\n\t\t\t<a href=\"#region\"><span class=\"s4\">REGION</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/applang.html\">app language</a></td>\n\t\t\t\t<td>application language tests</td></tr>\n\t\t\t<!--<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/intlcompare.html\">compare locales</a></td>\n\t\t\t\t<td>test: compare locales</td></tr>-->\n\n\t\t\t<tr><td colspan=\"2\"><span class=\"no_color\">intl</span></td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/collation.html\">collation</a></td></tr>\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\t\t\t<tr><td colspan=\"2\">datetimeformat</td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/dtfcomponents.html\">components</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dtfdatetimestyle.html\">date-&-timestyle</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dtfdayperiod.html\">dayperiod</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dtflistformat.html\">listformat</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dtfrelated.html\">relatedyear</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dtftimezonename.html\">timezonename</a>\n\t\t\t</td></tr>\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\t\t\t<tr><td colspan=\"2\">displaynames</td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/dncalendar.html\">calendar</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dncurrency.html\">currency</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dndatetime.html\">datetimefield</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dnlanguage.html\">language</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dnregion.html\">region</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/dnscript.html\">script</a>\n\t\t\t</td></tr>\n\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\t\t\t<tr><td colspan=\"2\"></td><td><a class=\"blue\" href=\"tests/duration.html\">durationformat</a></td></tr>\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\t\t\t<tr><td colspan=\"2\">numberformat</td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/nfcompact.html\">compactdisplay</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/nfcurrency.html\">currencydisplay</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/nfformattoparts.html\">formattoparts</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/nfnotation.html\">notation</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/nfsign.html\">signdisplay</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/nfunit.html\">unitdisplay</a>\n\t\t\t</td></tr>\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\n\t\t\t<tr><td colspan=\"2\">pluralrules</td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/pr.html\">select</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/prrange.html\">selectrange</a>\n\t\t\t</td></tr>\n \t\t\t<tr><td colspan =\"2\"></td><td class=\"border-bottom\"></td></tr>\n\n\t\t\t<tr><td colspan=\"2\"></td><td><a class=\"blue\" href=\"tests/rtf.html\">relativetimeformat</a></td></tr>\n\t\t\t<tr><td colspan=\"2\"></td><td><a class=\"blue\" href=\"tests/resolvedoptions.html\">resolvedoptions</a></td></tr>\n\t\t\t<!--<tr><td colspan=\"2\"></td><td><a class=\"blue\" href=\"tests/segmenter.html\">segmenter</a></td></tr>-->\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/supportedlocales.html\">supportedlocales</a></td>\n\t\t\t\t<td>test: supportedLocalesOf</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/supportedvalues.html\">supportedvalues</a></td>\n\t\t\t\t<td>test: supportedValuesOf</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/timezones.html\">timezones</a></td>\n\t\t\t\t<td>proof: min dates for max results</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"storage\"></a>\n\t\t\t<a href=\"#storage\"><span class=\"s6\">COOKIES & STORAGE</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/sanitizing.html\">sanitizing</a></td>\n\t\t\t\t<td>... with zombies!</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"devices\"></a>\n\t\t\t<a href=\"#devices\"><span class=\"s7\">DEVICES</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/recursion.html\">recursion</a></td>\n\t\t\t\t<td>test: recursion levels and stack lengths</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/scroll.html\">scrolling</a></td>\n\t\t\t\t<td>test: smooth scrolling</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/pointerevent.html\">pointer event</a></td>\n\t\t\t\t<td>test: pointer and touch events</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"canvas\"></a>\n\t\t\t<a href=\"#canvas\"><span class=\"s9\">CANVAS</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/canvasspoof.html\">canvas spoof</a></td>\n\t\t\t\t<td>canvas spoof detection</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/canvasnoise.html\">canvas noise</a></td>\n\t\t\t\t<td>canvas spoof fingerprinting</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/canvasrfp.html\">canvas rfp</a></td>\n\t\t\t\t<td>test: RFP random canvas characteristics</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"fonts\"></a>\n\t\t\t<a href=\"#fonts\"><span class=\"s12\">FONTS</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/bridgemoji.html\">bridge-moji</a></td>\n\t\t\t\t<td>test: accessibilty, looks & feel</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontasync.html\">font async</a></td>\n\t\t\t\t<td>test: async font fallback</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontdebug.html\">font debug</a></td>\n\t\t\t\t<td>test: full check on any font ... down the rabbit hole</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontsmac.html\">mac fonts</a></td>\n\t\t\t\t<td>analysis: mac font diffs per major release</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontdefaults.html\">script defaults</a></td>\n\t\t\t\t<td>test: script default proportional font and sizes</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontscripts.html\">scripts</a></td>\n\t\t\t\t<td>test: script support vs. font visibility</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontsystem.html\">system fonts</a></td>\n\t\t\t\t<td>test: system font exposure</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/fontview.html\">script view</a></td>\n\t\t\t\t<td>view: generic font-families per script</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"codecs\"></a>\n\t\t\t<a href=\"#codecs\"><span class=\"s13\">CODECS</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/codecs_can_is.html\">codecs</a></td>\n\t\t\t\t<td>test: canPlayType & isTypeSupported</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"css\"></a>\n\t\t\t<a href=\"#css\"><span class=\"s14\">CSS</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/csscolors.html\">css colors</a></td>\n\t\t\t\t<td>view: css colors</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"elements\"></a>\n\t\t\t<a href=\"#elements\"><span class=\"s15\">ELEMENTS</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/domrectspoof.html\">domrect</a></td>\n\t\t\t\t<td>domrect spoof detection</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/domrectspoofratio.html\"> [ratio] domrect</a></td>\n\t\t\t\t<td>domrect spoof detection</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/elementkeys.html\"> element keys</a></td>\n\t\t\t\t<td class=\"border-bottom\">test: element properties</td></tr>\n\n\t\t\t<tr><td colspan=\"2\">element sizes</td><td>\n\t\t\t\t<a class=\"blue\" href=\"tests/elementfont.html\">fonts</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/elementforms.html\">forms</a>\n\t\t\t\t| <a class=\"blue\" href=\"tests/elementother.html\">other</a>\n\t\t\t</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"misc\"></a>\n\t\t\t<a href=\"#misc\"><span class=\"s18\">MISC</span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/functionprops.html\"> functions</a></td>\n\t\t\t\t<td>... just scraping properties</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/math.html\">math</a></td>\n\t\t\t\t<td>research: likely math functions and polyfills</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/mathdata.html\">math data</a></td>\n\t\t\t\t<td>research: ongoing results</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/mathspoof.html\">math spoof</a></td>\n\t\t\t\t<td>math spoof detection</td></tr>\n\t\t<tr><td colspan=\"3\"></td></tr>\n\n\t\t<tr><td colspan=\"3\" class=\"intro\"><a name=\"other\"></a>\n\t\t\t<a href=\"#other\"><span class=\"no_color\"><b>OTHER</b></span></a>\n\t\t</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a class=\"blue\" href=\"tests/readerview.html\">reader view</a></td>\n\t\t\t\t<td>test: generic test page</td></tr>\n\t\t\t<tr><td colspan=\"2\"><a id=\"test\" class=\"blue\" href=\"tests/windownamea.html\">window.name</a></td>\n\t\t\t\t<td>check window.name clearance per eTLD+1</td></tr>\n\t</table>\n\t<br>\n\n</body>\n</html>\n"
  },
  {
    "path": "js/audio.js",
    "content": "'use strict';\n\n/* code based on\nhttps://canvasblocker.kkapsner.de/test/\nhttps://audiofingerprint.openwpm.com/ */\n\nfunction byteArrayToHex(arrayBuffer){\n\tvar chunks = [];\n\t(new Uint32Array(arrayBuffer)).forEach(function(num){\n\t\tchunks.push(num.toString(16));\n\t});\n\treturn chunks.map(function(chunk){\n\t\treturn '0'.repeat(8 - chunk.length) + chunk;\n\t}).join('');\n}\n\nfunction check_audioLies() {\n\tconst audioList = [\n\t\t'AnalyserNode.getByteFrequencyData','AnalyserNode.getByteTimeDomainData',\n\t\t'AnalyserNode.getFloatFrequencyData','AnalyserNode.getFloatTimeDomainData',\n\t\t'AudioBuffer.copyFromChannel','AudioBuffer.getChannelData',\n\t\t'BiquadFilterNode.getFrequencyResponse',\n\t]\n\tif (runSL) {addProxyLie('AudioBuffer.copyFromChannel')}\n\treturn audioList.some(lie => sData[SECT99].indexOf(lie) >= 0)\n}\n\nconst get_audio_context = (METRIC) => new Promise(resolve => {\n\tlet t0 = nowFn() \n\tlet hash, btn ='', data = {}, notation = rfp_red, isLies = false\n\n\ttry {\n\t\t// unsorted\n\t\tfunction a(a, b, c) {\n\t\t\tfor (let d in b) 'dopplerFactor' === d || 'speedOfSound' === d || 'currentTime' ===\n\t\t\td || 'number' !== typeof b[d] && 'string' !== typeof b[d] || (a[(c ? c : '') + d] = b[d])\n\t\t\treturn a\n\t\t}\n\t\tlet f = new window.AudioContext\n\t\tlet obj\n\t\tlet\td = f.createAnalyser()\n\t\tobj = a({}, f, 'ac-')\n\t\tobj = a(obj, f.destination, 'ac-')\n\t\tobj = a(obj, f.listener, 'ac-')\n\t\tobj = a(obj, d, 'an-')\n\n\t\t// sort, type check etc\n\t\tif (runST) {obj['ac-channelCount'] = '4'; obj['an-fftSize'] = null // change type: this will trigger isLies\n\t\t} else if (runSL) {\tobj['ac-channelCount'] = 4} // change expected value\n\t\tlet oHardcoded = {} // FF70+: keys [20] + expected hardcoded values [16]\n\t\tlet hardcodeExclude = ['ac-outputLatency','ac-sampleRate','ac-maxChannelCount','an-channelCount']\n\t\tlet numberExclude = [\n\t\t\t'ac-channelCountMode','ac-channelInterpretation','ac-state','an-channelCountMode',\n\t\t\t'an-channelInterpretation','ac-sinkId'\n\t\t]\n\t\tfor (const k of Object.keys(obj).sort()) {\n\t\t\tdata[k] = obj[k]\n\t\t\toHardcoded[k] = hardcodeExclude.includes(k) ? '' : obj[k]\n\t\t\t// regardless of hardcoded check, catch all type check entropy\n\t\t\tlet typeCheck = typeFn(obj[k])\n\t\t\tlet typeMatch = numberExclude.includes(k) ? ('ac-sinkId' == k ? 'empty string' : 'string') : 'number'\n\t\t\tif (typeMatch !== typeCheck) {\n\t\t\t\tlog_error(11, METRIC +'_'+ k, zErrType + typeCheck)\n\t\t\t\tif (!isSmart) {data[k] = zErr} // non smart reflect error in data\n\t\t\t\tisLies = true // otherwise smart uses isLies and returns untrustworthy\n\t\t\t}\n\t\t}\n\t\tif (mini(oHardcoded) !== 'dfda7813') {isLies = true}\n\n\t\t// ac-state changes in blink (IDK about webkit ToDo I guess) on a re-run\n\t\t\t// gRun = suspended, reruns = running\n\t\t\t// doesn't seem partically useful, so let's change it in non-gecko\n\t\tif (!isGecko) {\n\t\t\tif (undefined !== data['ac-state']) {data['ac-state'] = zNA}\n\t\t}\n\n\t\t// notate\n\t\t\t// non-RFP outputLatency can be variable per tab/run - return n/a + rehash to avoid any noise\n\t\thash = mini(data); btn = addButton(11, METRIC, Object.keys(data).length +' keys')\n\t\tif (isOS !== undefined) {\n\t\t\tif ('windows' == isOS && '67a3eeee' == hash) {notation = rfp_green // 0.04\n\t\t\t} else if ('mac' == isOS && 'debdefc0' == hash) {notation = rfp_green // 512/44100 (RFP hardcodes latency)\n\t\t\t} else if ('android' == isOS && '2b9d44b0' == hash) {notation = rfp_green // 0.02\n\t\t\t} else if ('9b69969b' == hash) {notation = rfp_green} // 0.025 catchall incl linux\n\t\t}\n\t\tif (isGecko && notation !== rfp_green) {\n\t\t\tnotation += ' [latency: '+ data['ac-outputLatency'] +']'\n\t\t\tdata['ac-outputLatency'] = zNA; hash = mini(data)\n\t\t}\n\t} catch(e) {\n\t\thash = log_error(11, METRIC, e); data = zErr\n\t}\n\taddBoth(11, METRIC, hash, btn, notation, data, isLies)\n\tlog_perf(11, METRIC, t0)\n\treturn resolve()\n})\n\nconst get_audio_offline = (METRIC) => new Promise(resolve => {\n\tlet t0 = nowFn(), notation = rfp_red, isLies = false\n\n\tfunction outputErrors(display) {\n\t\taddBoth(11, METRIC, display,'', notation, zErr)\n\t\treturn resolve()\n\t}\n\n\t// ToDo: maybe reduce bufferLen as long as it doesn't change entropy\n\t\t// also: when we add RFP + math PoC we need only check for protection (like canvas)\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tconst bufferLen = 5000 // 5000 to match documented\n\t\tconst context = new window.OfflineAudioContext(1, bufferLen, 44100)\n\t\tconst dynamicsCompressor = context.createDynamicsCompressor() // servo breaks here\n\t\tconst oscillator = context.createOscillator()\n \t\t// set\n\t\toscillator.type = 'triangle'\n\t\toscillator.frequency.value = 10000\n\t\tdynamicsCompressor.threshold && (dynamicsCompressor.threshold.value = -50)\n\t\tdynamicsCompressor.knee && (dynamicsCompressor.knee.value = 40)\n\t\tdynamicsCompressor.attack && (dynamicsCompressor.attack.value = 0)\n\t\tdynamicsCompressor.ratio && (dynamicsCompressor.ratio.value = 12)\n\t\tdynamicsCompressor.reduction && (dynamicsCompressor.reduction.value = -20) // does this do anything\n\t\tdynamicsCompressor.release && (dynamicsCompressor.release.value = .25)\n\t\t// connect\n\t\tdynamicsCompressor.connect(context.destination)\n\t\toscillator.connect(dynamicsCompressor)\n\t\t// start\n\t\toscillator.start(0)\n\t\tcontext.startRendering()\n\n\t\tcontext.oncomplete = function(event) {\n\t\t\ttry {\n\t\t\t\tdynamicsCompressor.disconnect()\n\t\t\t\tlet copyTest = new Float32Array(bufferLen)\n\t\t\t\tevent.renderedBuffer.copyFromChannel(copyTest, 0) // JSShelter errors here\n\t\t\t\tlet getTest = event.renderedBuffer.getChannelData(0) // JSShelter errors here\n\t\t\t\tPromise.all([\n\t\t\t\t\tcrypto.subtle.digest('SHA-1', getTest),\n\t\t\t\t\tcrypto.subtle.digest('SHA-1', copyTest),\n\t\t\t\t]).then(function(hashes){\n\t\t\t\t\t// sum\n\t\t\t\t\tlet sum = 0\n\t\t\t\t\tfor (let i=0; i < copyTest.length; i++) {\n\t\t\t\t\t\tlet x = copyTest[i]\n\t\t\t\t\t\tif (i > (bufferLen-501) && i < bufferLen) {sum += Math.abs(x)}\n\t\t\t\t\t}\n\t\t\t\t\t// get/copy\n\t\t\t\t\tlet hashC = mini(byteArrayToHex(hashes[1]))\n\t\t\t\t\tlet hashG = mini(byteArrayToHex(hashes[0]))\n\t\t\t\t\t// lies\n\t\t\t\t\tlet isSame = hashG == hashC, display, btn = addButton(11, METRIC +'_data')\n\t\t\t\t\tif (!isSame) {\n\t\t\t\t\t\tisLies = true\n\t\t\t\t\t\taddDetail(METRIC +'_data', {'copyFromChannel': copyTest, 'getChannelData': getTest})\n\t\t\t\t\t\tdisplay = 'mixed'\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// no need to list twice\n\t\t\t\t\t\tisLies = check_audioLies()\n\t\t\t\t\t\taddDetail(METRIC +'_data', copyTest)\n\t\t\t\t\t\tdisplay = hashC\n\t\t\t\t\t\tbtn += ' '+ sum\n\t\t\t\t\t}\n\t\t\t\t\t// notation: three results since 1877221 FF124+ split x86 into 32/64 bitness\n\t\t\t\t\t\t// isArch: true = large arrays else it's an error string\n\t\t\t\t\tif (true === isArch) {\n\t\t\t\t\t\tif ('a7c1fbb6' == hashC) {notation = sgtick+'x86_64/amd_64]'+sc\n\t\t\t\t\t\t} else if ('a34c73cd' == hashC) {notation = sgtick+'ARM64/aarch64]'+sc}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ('24fc63ce' == hashC) {notation = sgtick+'x86/i686/ARMv7]'+sc}\n\t\t\t\t\t}\n\t\t\t\t\taddData(11, METRIC, display,'', isLies)\n\t\t\t\t\taddDisplay(11, METRIC, display, btn, notation, isLies)\n\t\t\t\t\tlog_perf(11, METRIC, t0)\n\t\t\t\t\treturn resolve()\n\t\t\t\t})\n\t\t\t\t.catch(function(e){\n\t\t\t\t\toutputErrors(log_error(11, METRIC, e))\n\t\t\t\t})\n\t\t\t} catch(e) {\n\t\t\t\toutputErrors(log_error(11, METRIC, e))\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\ttry {\n\t\t\tif (gRun) {dom.audio_test_oscillator_compressor = zNA; dom.audio_test_oscillator = zNA; dom.audio_test = zNA}\n\t\t} catch {}\n\t\toutputErrors(log_error(11, METRIC, e))\n\t}\n})\n\nconst outputAudio = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('audio')) {return resolve()}\n\n\tPromise.all([\n\t\tget_audio_context('audioContext'),\n\t\tget_audio_offline('offlineAudioContext'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(11)\n"
  },
  {
    "path": "js/canvas.js",
    "content": "'use strict';\n\n/* outputCanvas() based on https://canvasblocker.kkapsner.de/test/ */\n\nfunction check_canvas_to(data) {\n\t// only called if per-execution\n\tlet len = data.length\n\tif (![166,170,174,178].includes(len)) {return false}\n\tlet slice1 = data.slice(72,80)\n\tif ('lEQVQoU2' == slice1) {\n\t\tlet\tslice2 = data.slice(data.length - 10, data.length)\n\t\tif ('VORK5CYII=' == slice2 || '5ErkJggg==' == slice2  || 'lFTkSuQmCC' == slice2) {\n\t\t\treturn true // RFP\n\t\t}\n\t}\n\treturn false\n}\n\nconst get_canvas = () => new Promise(resolve => {\n\n\tconst sizeW = 16, sizeH = 8, pixelcount = sizeW * sizeH, allZeros = '93bd94c5'\n\t// FF95+: compression 1724331 / 1737038 \n\tconst oKnown = {\n\t\t'ge_white': ['d5f8f171'],\n\t\t'isPointInPath': ['db0e3f08'],\n\t\t'isPointInStroke': ['a77e328a'],\n\t\t'to_white': ['35e41537'],\n\t\t'toBlob': ['3afc375a'],\n\t\t'toBlob_solid': ['56ea6104'],\n\t\t'toDataURL': ['3afc375a'],\n\t\t'toDataURL_solid': ['56ea6104'],\n\t}\n\t// libz-rs\n  \t// FF137 1910796: Enable libz-rs on nightly: this changes our known hashes\n  \t// FF139 1949947: Upgrade zlib-rs/libz-rs-sys to 0.4.2. (new to*_solids)\n\tif (isVer > 136) {\n\t\toKnown['toBlob'].push('e328ec8e')\n\t\toKnown['toBlob_solid'].push('9d0b9932','cfd52a1f')\n\t\toKnown['toDataURL'].push('e328ec8e')\n\t\toKnown['toDataURL_solid'].push('9d0b9932','cfd52a1f')\n\t\toKnown['to_white'].push('3e72d1fd')\n\t}\n\tlet isCanvasGet ='', isCanvasGetChannels ='', isGetStealth = false\n\n\tfunction check_canvas_get(dataname, runNo) {\n\t\tlet data = oData[dataname]\n\t\tlet dataDrawn = oDataDrawn[dataname]\n\t\tlet isMatch = mini(dataDrawn) == mini(data)\n\t\t// run1 return if a match or not\n\t\tif (runNo == 1) {return isMatch}\n\t\t// run2 quick exit: return skip if nothing to do\n\t\tif (isMatch) {return 'skip'}\n\n\t\t// run2 otherwise return if RFP-like and create strings\n\t\tlet aDrawn = [], aRead = [], indexChanged = []\n\t\tlet altP = 0, altR = 0, altG = 0, altB = 0, altA = 0, altAll = 0\n\t\tfor (let x=0; x < pixelcount; x++) {\n\t\t\tlet k = x * 4\n\t\t\taDrawn = dataDrawn.slice(k, k+4)\n\t\t\taRead = data.slice(k, k+4)\n\t\t\tif (aDrawn.join() !== aRead.join()) { // pixels\n\t\t\t\taltP++\n\t\t\t\tindexChanged.push(k)\n\t\t\t}\n\t\t\tif (aDrawn[0] !== aRead[0]) { altR++} // channels\n\t\t\tif (aDrawn[1] !== aRead[1]) { altG++}\n\t\t\tif (aDrawn[2] !== aRead[2]) { altB++}\n\t\t\tif (aDrawn[3] !== aRead[3]) { altA++}\n\t\t\t// ToDo: range: worth it?\n\t\t}\n\t\t// stealth check: anything in changed not in font\n\t\tlet aNotInFonts = indexChanged.filter(x => !indexFont.includes(x))\n\t\tisGetStealth = aNotInFonts.length == 0\n\n\t\t// noise FP\n\t\tlet strFP ='', aNote = []\n\t\taNote.push('p'+ Math.floor((altP / pixelcount) * 100))\n\t\tif (altR > 0) {strFP += 'r'; aNote.push('r'+Math.floor((altR / pixelcount) * 100))}\n\t\tif (altG > 0) {strFP += 'g'; aNote.push('g'+ Math.floor((altG / pixelcount) * 100))}\n\t\tif (altB > 0) {strFP += 'b'; aNote.push('b'+ Math.floor((altB / pixelcount) * 100))}\n\t\tif (altA > 0) {strFP += 'a'; aNote.push('a'+ Math.floor((altA / pixelcount) * 100))}\n\t\t// FP data\n\t\tisCanvasGetChannels = (isGetStealth ? 'stealth | ' : '') + strFP\n\t\t// display data: keep android short\n\t\tif (isDesktop) {isCanvasGet = ' ['+ (isGetStealth ? 'stealth ' : '')  +'%: '+ aNote.join(' ') +']'\n\t\t} else if (isGetStealth) {isCanvasGet = ' [stealth]'}\n\n\t\t// pixels: allow 2 collision\n\t\tif (altP < (pixelcount - 2)) {return false}\n\t\t// rgb: ran 100k tests: lowest 124/128: allow 8 collsions\n\t\t\t// with a solid, collisions are amplified: 112/128 seems to be the lowest given the pattern repeats\n\t\tlet maxCollisions = 'getImageData_solid' == dataname ? 24 : 8\n\t\tif (altR < (pixelcount - maxCollisions)) {return false}\n\t\tif (altG < (pixelcount - maxCollisions)) {return false}\n\t\tif (altB < (pixelcount - maxCollisions)) {return false}\n\t\t// alpha: not randomized: higher collisons: lowest 96/128: allow 33%\n\t\tif ((altA / pixelcount) < .66) {return false}\n\t\treturn true // RFP traits\n\t}\n\n\tvar known = {\n\t\tcreateHashes: function(window, runNo){\n\t\t\tlet outputs = [\n\t\t\t\t{\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\n\t\t\t\t\tname: 'getImageData',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tconst METRIC = 'getImageData'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar context = getKnownGet()\n\t\t\t\t\t\t\tlet imageData = context.getImageData(0,0, sizeW, sizeH)\n\t\t\t\t\t\t\tif (runST) {imageData = null} else if (runSI) {imageData = {}}\n\t\t\t\t\t\t\tif ('object' !== typeFn(imageData, true)) {throw zErrType + typeFn(imageData)}\n\t\t\t\t\t\t\tlet expected = '[object ImageData]'\n\t\t\t\t\t\t\tif (imageData+'' !== expected) {throw zErrInvalid +'expected '+ expected +': got '+ imageData+''}\n\t\t\t\t\t\t\toData[METRIC] = imageData.data\n\t\t\t\t\t\t\treturn mini(imageData.data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\n\t\t\t\t\tname: 'getImageData_solid',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tconst METRIC = 'getImageData_solid'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar context = getKnownGetSolid()\n\t\t\t\t\t\t\tlet imageData = context.getImageData(0,0, sizeW, sizeH)\n\t\t\t\t\t\t\tif (runST) {imageData = null} else if (runSI) {imageData = {}}\n\t\t\t\t\t\t\tif ('object' !== typeFn(imageData, true)) {throw zErrType + typeFn(imageData)}\n\t\t\t\t\t\t\tlet expected = '[object ImageData]'\n\t\t\t\t\t\t\tif (imageData+'' !== expected) {throw zErrInvalid +'expected '+ expected +': got '+ imageData+''}\n\t\t\t\t\t\t\toData[METRIC] = imageData.data\n\t\t\t\t\t\t\treturn mini(imageData.data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\n\t\t\t\t\tname: 'isPointInPath',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tconst METRIC = 'isPointInPath'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar context = getKnownPath()\n\t\t\t\t\t\t\tvar data = new Uint8Array(sizeW * sizeH)\n\t\t\t\t\t\t\tvar dataR = context.isPointInPath(0, 0)\n\t\t\t\t\t\t\tif (runST) {dataR = 0}\n\t\t\t\t\t\t\tlet typeCheck = typeFn(dataR)\n\t\t\t\t\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\tfor (let x = 0; x < sizeW; x++){\n\t\t\t\t\t\t\t\tfor (let y = 0; y < sizeH; y++){\n\t\t\t\t\t\t\t\t\tdata[y * sizeW + x] = context.isPointInPath(x, y)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdata = data.join('')\n\t\t\t\t\t\t\toData[METRIC] = data\n\t\t\t\t\t\t\treturn mini(data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\n\t\t\t\t\tname: 'isPointInStroke',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tconst METRIC = 'isPointInStroke'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tlet context = getKnownPath()\n\t\t\t\t\t\t\tvar data = new Uint8Array(sizeW * sizeH)\n\t\t\t\t\t\t\tvar dataR = context.isPointInStroke(0, 0)\n\t\t\t\t\t\t\tif (runST) {dataR = 'false'}\n\t\t\t\t\t\t\tlet typeCheck = typeFn(dataR)\n\t\t\t\t\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\tfor (let x = 0; x < sizeW; x++){\n\t\t\t\t\t\t\t\tfor (let y = 0; y < sizeH; y++){\n\t\t\t\t\t\t\t\t\tdata[y * sizeW + x] = context.isPointInStroke(x, y)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdata = data.join('')\n\t\t\t\t\t\t\toData[METRIC] = data\n\t\t\t\t\t\t\treturn mini(data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'toBlob',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\treturn new Promise(function(resolve, reject){\n\t\t\t\t\t\t\tconst METRIC = 'toBlob'\n\t\t\t\t\t\t\tif (aSkip.includes(METRIC)) {resolve('skip')}\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar timeout = window.setTimeout(function(){\n\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErrTime\n\t\t\t\t\t\t\t\t\tresolve(zErrTime)\n\t\t\t\t\t\t\t\t}, 750)\n\t\t\t\t\t\t\t\tif (!runTE) {\n\t\t\t\t\t\t\t\t\tgetKnownTo().canvas.toBlob(function(blob){\n\t\t\t\t\t\t\t\t\t\twindow.clearTimeout(timeout)\n\t\t\t\t\t\t\t\t\t\tvar reader = new FileReader()\n\t\t\t\t\t\t\t\t\t\treader.onload = function(){\n\t\t\t\t\t\t\t\t\t\t\tlet value = reader.result\n\t\t\t\t\t\t\t\t\t\t\tif (runST) {value =''}\n\t\t\t\t\t\t\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\t\t\t\t\t\t\tif ('string' === typeCheck ) {\n\t\t\t\t\t\t\t\t\t\t\t\toData[METRIC] = value\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(mini(reader.result))\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErrType + typeCheck\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(zErr)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treader.onerror = function(){\n\t\t\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErr +' undefined [.onerror]'\n\t\t\t\t\t\t\t\t\t\t\treject(zErr)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treader.readAsDataURL(blob)\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\t\tresolve(zErr)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'toBlob_solid',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\treturn new Promise(function(resolve, reject){\n\t\t\t\t\t\t\tconst METRIC = 'toBlob_solid'\n\t\t\t\t\t\t\tif (aSkip.includes(METRIC)) {resolve('skip')}\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar timeout = window.setTimeout(function(){\n\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErrTime\n\t\t\t\t\t\t\t\t\tresolve(zErrTime)\n\t\t\t\t\t\t\t\t}, 750)\n\t\t\t\t\t\t\t\tif (!runTE) {\n\t\t\t\t\t\t\t\t\tgetKnownToSolid().canvas.toBlob(function(blob){\n\t\t\t\t\t\t\t\t\t\twindow.clearTimeout(timeout)\n\t\t\t\t\t\t\t\t\t\tvar reader = new FileReader()\n\t\t\t\t\t\t\t\t\t\treader.onload = function(){\n\t\t\t\t\t\t\t\t\t\t\tlet value = reader.result\n\t\t\t\t\t\t\t\t\t\t\tif (runST) {value =''}\n\t\t\t\t\t\t\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\t\t\t\t\t\t\tif ('string' === typeCheck ) {\n\t\t\t\t\t\t\t\t\t\t\t\toData[METRIC] = value\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(mini(reader.result))\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErrType + typeCheck\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(zErr)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treader.onerror = function(){\n\t\t\t\t\t\t\t\t\t\t\toErrors[METRIC] = zErr +' undefined [.onerror]'\n\t\t\t\t\t\t\t\t\t\t\treject(zErr)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treader.readAsDataURL(blob)\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\t\tresolve(zErr)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'toDataURL',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tlet METRIC = 'toDataURL'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tlet data = getKnownTo().canvas.toDataURL()\n\t\t\t\t\t\t\tif (runST) {data = undefined}\n\t\t\t\t\t\t\tlet typeCheck = typeFn(data)\n\t\t\t\t\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\toData[METRIC] = data\n\t\t\t\t\t\t\treturn mini(data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'toDataURL_solid',\n\t\t\t\t\tvalue: function(){\n\t\t\t\t\t\tlet METRIC = 'toDataURL_solid'\n\t\t\t\t\t\tif (aSkip.includes(METRIC)) {return 'skip'}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tlet data = getKnownToSolid().canvas.toDataURL()\n\t\t\t\t\t\t\tif (runST) {data = undefined}\n\t\t\t\t\t\t\tlet typeCheck = typeFn(data)\n\t\t\t\t\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\toData[METRIC] = data\n\t\t\t\t\t\t\treturn mini(data)\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toErrors[METRIC] = e+''\n\t\t\t\t\t\t\treturn zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t];\n\t\t\tfunction isSupported(output){\n\t\t\t\tlet key = output.name\n\t\t\t\tif (key.includes('_solid')) {key = key.slice(0,-6)}\n\t\t\t\treturn !!(output.class? output.class: window.HTMLCanvasElement).prototype[key]\n\t\t\t}\n\t\t\tfunction getKnownTo(){\n\t\t\t\tlet canvas = dom.tzpCanvasTo\n\t\t\t\tlet ctx = canvas.getContext('2d')\n\t\t\t\tif (oDrawn['to']) {return ctx}\n\t\t\t\t// color the background\n\t\t\t\tctx.fillStyle = 'rgba('+ solidPink +')'\n\t\t\t\tctx.fillRect(0, 0, sizeW, sizeH)\n\t\t\t\t// trigger fillText stealth\n\t\t\t\tlet fpText = '\\u2588\\u2588\\u2588\\u2588' // full block\n\t\t\t\tctx.font = '512px sans-serif' // large\n\t\t\t\tctx.textBaseline = 'top'\n\t\t\t\tctx.textBaseline = 'alphabetic'\n\t\t\t\tctx.fillText(fpText,0,0)\n\t\t\t\tfor (let x = 0; x < sizeW; x++) {\n\t\t\t\t\tlet xEven = (x % 2 == 0)\n\t\t\t\t\tfor (let y = 0; y < sizeH; y++) {\n\t\t\t\t\t\tlet yEven = (y % 2 == 0)\n\t\t\t\t\t\tlet isRandom = (xEven + yEven == 1 || xEven + yEven == 2) // 3/4ths\n\t\t\t\t\t\tif (isRandom) {\n\t\t\t\t\t\t\tctx.fillStyle = 'rgba('+ (x*y) +','+ (x * 16) +','+ (y * 16) +',255)'\n\t\t\t\t\t\t\tctx.fillRect(x, y, 1, 1)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toDrawn['to'] = true\n\t\t\t\treturn ctx\n\t\t\t}\n\t\t\tfunction getKnownToSolid(){\n\t\t\t\tlet canvas = dom.tzpCanvasToSolid\n\t\t\t\tlet ctx = canvas.getContext('2d')\n\t\t\t\tif (oDrawn['to_solid']) {return ctx}\n\t\t\t\tctx.fillStyle = 'rgba('+ solidPink +')'\n\t\t\t\tctx.fillRect(0, 0, sizeW, sizeH)\n\t\t\t\toDrawn['to_solid'] = true\n\t\t\t\treturn ctx\n\t\t\t}\n\t\t\tfunction getKnownGet(){\n\t\t\t\tlet canvas = dom.tzpCanvasGet\n\t\t\t\tlet ctx = canvas.getContext('2d')\n\t\t\t\tif (oDrawn['get']) {return ctx}\n\t\t\t\t// color the background\n\t\t\t\tctx.fillStyle = 'rgba('+ solidClrs +')'\n\t\t\t\tctx.fillRect(0, 0, sizeW, sizeH)\n\t\t\t\t// trigger fillText stealth: try to cover every pixel\n\t\t\t\tlet fpText = '\\u2588\\u2588\\u2588\\u2588' // full block\n\t\t\t\tctx.font = '512px sans-serif' // large\n\t\t\t\tctx.textBaseline = 'top'\n\t\t\t\tctx.textBaseline = 'alphabetic'\n\t\t\t\tctx.fillText(fpText,0,0)\n\t\t\t\t/*\n\t\t\t\t// trigger strokeText stealth\n\t\t\t\t\t// don't overwrite all the fillText\n\t\t\t\t\t// see PoC notes: too risky\n\t\t\t\tfpText = '-'\n\t\t\t\tctx.font = '16px monospace'\n\t\t\t\tctx.strokeStyle ='rgba('+ solidClrs +')'\n\t\t\t\tfor (let x=0; x < sizeW/2; x++) {\n\t\t\t\t\tfor (let y=0; y < sizeH/2; y++) {ctx.strokeText(fpText,x,y)}\n\t\t\t\t}\n\t\t\t\t//*/\n\t\t\t\t// now color the rest with our random colors\n\t\t\t\t// swap x/y loop order to match getImageData uint\n\t\t\t\tlet ignore = 'rgba('+ solidClrs +')'\n\t\t\t\tfor (let y=0; y < sizeH; y++) {\n\t\t\t\t\tfor (let x=0; x < sizeW; x++) {\n\t\t\t\t\t\tlet style = dataToDraw[(y * sizeW) + x]\n\t\t\t\t\t\tif (style !== ignore) {\n\t\t\t\t\t\t\tctx.fillStyle = style\n\t\t\t\t\t\t\tctx.fillRect(x, y, 1, 1)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toDrawn['get'] = true\n\t\t\t\treturn ctx\n\t\t\t}\n\t\t\tfunction getKnownGetSolid(){\n\t\t\t\tlet canvas = dom.tzpCanvasGetSolid\n\t\t\t\tlet ctx = canvas.getContext('2d')\n\t\t\t\tif (oDrawn['get_solid']) {return ctx}\n\t\t\t\tctx.fillStyle = 'rgba('+ solidClrs +')'\n\t\t\t\tctx.fillRect(0, 0, sizeW, sizeH)\n\t\t\t\toDrawn['get_solid'] = true\n\t\t\t\treturn ctx\n\t\t\t}\n\t\t\tfunction getKnownPath(){\n\t\t\t\tlet ctx = dom.tzpCanvasPath.getContext('2d')\n\t\t\t\tif (oDrawn['path']) {return ctx}\n\t\t\t\tctx.fillStyle = 'rgba(255,255,255,255)'\n\t\t\t\tctx.beginPath()\n\t\t\t\tctx.rect(2,5,8,7)\n\t\t\t\tctx.closePath()\n\t\t\t\tctx.fill()\n\t\t\t\toDrawn['path'] = true\n\t\t\t\treturn ctx\n\t\t\t}\n\n\t\t\tvar finished = Promise.all(outputs.map(function(output){\n\t\t\t\treturn new Promise(function(resolve, reject){\n\t\t\t\t\tvar displayValue\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar supported = output.supported? output.supported(): isSupported(output);\n\t\t\t\t\t\tif (supported){\n\t\t\t\t\t\t\tdisplayValue = output.value()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toErrors[output.name] = zErr\n\t\t\t\t\t\t\tdisplayValue = zErr\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\toErrors[output.name] = e+''\n\t\t\t\t\t\tdisplayValue = zErr\n\t\t\t\t\t}\n\t\t\t\t\tPromise.resolve(displayValue).then(function(displayValue){\n\t\t\t\t\t\toutput.displayValue = displayValue\n\t\t\t\t\t\tresolve(output)\n\t\t\t\t\t}, function(e){\n\t\t\t\t\t\toErrors[output.name] = e+''\n\t\t\t\t\t\toutput.displayValue = zErr\n\t\t\t\t\t\tresolve(zErr)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}))\n\t\t\treturn finished\n\t\t}\n\t}\n\n\t// oDrawn: only draw the canvas once per runNo\n\t\t// if input is faked, it would also be faked the second time\n\tlet oDrawn = {'get': false, 'get_solid': false, 'path': false, 'to': false, 'to_solid': false}\n\tlet oRes = {}, oFP = {}, oErrors = {}, oData = {}, aSkip = [], countFake = 0\n\tlet solidPink = '224,33,138,255' // go Barbie!\n\n\t// random getImageData\n\tlet tmpDrawn = new Uint8ClampedArray(sizeW * sizeH * 4)\n\tlet tmpSolid = new Uint8ClampedArray(sizeW * sizeH * 4)\n\tlet dataToDraw = [], indexFont = []\n\tlet solidR = Math.floor(Math.random()*255),\n\t\tsolidG = Math.floor(Math.random()*255),\n\t\tsolidB = Math.floor(Math.random()*255)\n\tlet solidClrs = solidR +','+ solidG +','+ solidB +',255'\n\tlet counter = -1\n\tfor (let x=0; x < sizeW; x++) {\n\t\tlet xEven = (x % 2 == 0)\n\t\tfor (let y=0; y < sizeH; y++) {\n\t\t\tcounter ++\n\t\t\tlet k = counter * 4\n\t\t\tlet yEven = (y % 2 == 0)\n\t\t\t// xEven + yEven == 1 = checkerboard = 1/2\n\t\t\t// xEven + yEven == 2 = another 1/4\n\t\t\t// xEven + yEven == 0 = the remainder: of which we can further reduce e.g. multples of 3\n\t\t\tlet isRandom = (xEven + yEven == 1 || xEven + yEven == 2) // 3/4ths\n\t\t\tif (!isRandom) {\n\t\t\t\tif ((x * y) % 3 == 0 ) {isRandom = true} // brings us to 113/128\n\t\t\t}\n\t\t\tif (isRandom) {\n\t\t\t\t// random: 113\n\t\t\t\tlet valueR = Math.floor(Math.random()*255),\n\t\t\t\t\tvalueG = Math.floor(Math.random()*255),\n\t\t\t\t\tvalueB = Math.floor(Math.random()*255)\n\t\t\t\ttmpDrawn[k] = valueR\n\t\t\t\ttmpDrawn[k+1] = valueG\n\t\t\t\ttmpDrawn[k+2] = valueB\n\t\t\t\ttmpDrawn[k+3] = 255\n\t\t\t\tdataToDraw.push('rgba('+ valueR +','+ valueG +','+ valueB +',255)')\n\t\t\t} else {\n\t\t\t\tindexFont.push(k)\n\t\t\t\t// solid: 15\n\t\t\t\ttmpDrawn[k] = solidR\n\t\t\t\ttmpDrawn[k+1] = solidG\n\t\t\t\ttmpDrawn[k+2] = solidB\n\t\t\t\ttmpDrawn[k+3] = 255\n\t\t\t\tdataToDraw.push('rgba('+ solidClrs +')')\n\t\t\t}\n\t\t\t// solid\n\t\t\ttmpSolid[k] = solidR\n\t\t\ttmpSolid[k+1] = solidG\n\t\t\ttmpSolid[k+2] = solidB\n\t\t\ttmpSolid[k+3] = 255\n\t\t}\n\t}\n\tlet oDataDrawn = {'getImageData': tmpDrawn, 'getImageData_solid': tmpSolid}\n\n\t// ensure sizes\n\tlet aCanvas = ['Get','GetSolid','Path','To','ToSolid']\n\taCanvas.forEach(function(k){let el = dom['tzpCanvas'+ k]; el.width = sizeW; el.height = sizeH})\n\n\tfunction exit() {\n\t\tconsole.debug(oData)\n\t\tfor (const m of Object.keys(oFP)) {\n\t\t\taddBoth(9, m, oFP[m].value, '', oFP[m].notation, oFP[m].data)\n\t\t}\n\t\treturn resolve()\n\t}\n\n\tPromise.all([\n\t\tknown.createHashes(window, 1)\n\t]).then(function(run1){\n\t\t// ToDo: learn more about PNG file structure and detect this more robustly\n\t\tlet aChunk = [\n\t\t\t'ABBkZUJH', // initial analysis \n\t\t\t'AAQZGVCR', // this comes up in solid FF145 but not FF146+\n\t\t]\n\t\t//aChunk = ['5ErkJggg==','VORK5CYII='] // test\n\n\t\trun1[0].forEach(function(item){\n\t\t\tlet name = item.name, key = name.slice(0,2), value = item.displayValue, data =''\n\t\t\tlet notation = rfp_red // they're all red if only a single run: we green up on second runs\n\t\t\tlet hasChunk = false\n\t\t\toRes[name] = {}\n\t\t\toRes[name][1] = value\n\t\t\tif (undefined !== oErrors[name]) {\n\t\t\t\taSkip.push(name)\n\t\t\t\tvalue = oErrors[name]; notation = rfp_red; data = zErrLog\n\t\t\t} else {\n\t\t\t\tif (!isGecko) {\n\t\t\t\t\tif ('ge' == key) {data = zNA} // test is random, return a stable FP\n\t\t\t\t} else {\n\t\t\t\t\tif ('ge' == key) {\n\t\t\t\t\t\t// run 1 check returns mini(dataDrawn) == mini(data)\n\t\t\t\t\t\tlet getCheck = check_canvas_get(name, 1)\n\t\t\t\t\t\tif (getCheck) {\n\t\t\t\t\t\t\tdata = 'trustworthy' // the test is random, return a stable FP\n\t\t\t\t\t\t\taSkip.push(name)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdata = 'protected'\n\t\t\t\t\t\t\tcountFake++\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// chunk test\n\t\t\t\t\t\t// gecko: we can already detect tampering since we use known hashes\n\t\t\t\t\t\t// but in future we might use randomness and read back the value from the png\n\t\t\t\t\t\thasChunk = false // reset\n\t\t\t\t\t\tif ('to' == key) {aChunk.forEach(function(str){if (oData[name].includes(str)) {hasChunk = true}})}\n\t\t\t\t\t\tif (hasChunk) {\n\t\t\t\t\t\t\tcountFake++\n\t\t\t\t\t\t\thasChunk = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (oKnown[name].includes(value)) {\n\t\t\t\t\t\t\t\taSkip.push(name)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata = 'protected'\n\t\t\t\t\t\t\t\tcountFake++\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\toFP[name] = {'value': value, 'notation': notation, 'chunk': hasChunk, 'data': data}\n\t\t})\n\t\t/*\n\t\tconsole.log(aSkip)\n\t\tconsole.log(oData)\n\t\tconsole.log(oFP)\n\t\t//*/\n\n\t\t// test\n\t\t//aSkip = aSkip.filter(x => ![toBlob].includes(x))\n\n\t\t// we're testing for protection so always do two passes, including gecko basic mode\n\t\t\t// ToDo: handle canvas spoofing in nonGecko: e.g. we can easily test getImageData: for now just exit\n\t\tif (countFake == 0 || !isGecko) {\n\t\t\texit()\n\t\t\treturn\n\t\t}\n\t\tconst proxyMap = {\n\t\t\tconvertToBlob: 'OffscreenCanvas',\n\t\t\tgetImageData: 'CanvasRenderingContext2D',\n\t\t\tisPointInPath: 'CanvasRenderingContext2D',\n\t\t\tisPointInStroke: 'CanvasRenderingContext2D',\n\t\t\ttoBlob: 'HTMLCanvasElement',\n\t\t\ttoDataURL: 'HTMLCanvasElement',\n\t\t}\n\t\t// smart + some lies, do 2nd run\n\t\t// for non skips, force a redraw\n\t\toDrawn = {'get': false, 'get_solid': false, 'path': false, 'to': false, 'to_solid': false}\n\n\t\tPromise.all([\n\t\t\tknown.createHashes(window, 2)\n\t\t]).then(function(run2){\n\t\t\trun2[0].forEach(function(item){\n\t\t\t\tlet name = item.name, key = name.slice(0,2), proxyname = name.replace('_solid', '')\n\t\t\t\tlet value = item.displayValue\n\t\t\t\tlet checkValue = value\n\t\t\t\tlet hasChunk = false\n\t\t\t\t// getImageData doesn't get a 'skip' so we handle it differently\n\t\t\t\t// don't check if already skipped: e.g. type error null\n\t\t\t\t// run2 check returns skip if nothing to do, or true/false if RFP-like\n\t\t\t\t// why do I need this?\n\t\t\t\tif ('ge' == key && 'skip' !== checkValue) {\n\t\t\t\t\tlet getCheck = check_canvas_get(name, 2)\n\t\t\t\t\tif ('skip' == getCheck) {checkValue = 'skip'}\n\t\t\t\t}\n\t\t\t\tif (checkValue !== 'skip') {\n\t\t\t\t\tlet data ='', notation ='', stats ='', rfpvalue ='', isChunk =''\n\t\t\t\t\t// proxy\n\t\t\t\t\tlet isProxy = isProxyLie(proxyMap[proxyname] +'.'+ proxyname)\n\n\t\t\t\t\t// chunk test\n\t\t\t\t\thasChunk = false // reset\n\t\t\t\t\tif ('to' == key) {\n\t\t\t\t\t\taChunk.forEach(function(str){\n\t\t\t\t\t\t\tif (oData[name].includes(str)) {hasChunk = true}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tif (hasChunk) {\n\t\t\t\t\t\t// privacyX, which doesn't protect toBlob yet, is causing intermittent false positive isChunk\n\t\t\t\t\t\t// on it's (per execution) *toDataURL when FPP is on. This is FPP kicking in somewhere due to\n\t\t\t\t\t\t// timing. It's not sufficient to check the chunk is persistent (but we'll do that)\n\t\t\t\t\t\t// I think all we can do is exclude if proxylies\n\t\t\t\t\t\tif (oFP[name].chunk == true && !isProxy) {isChunk = '*'}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (oRes[name][1] == value) {\n\t\t\t\t\t\t// persistent\n\t\t\t\t\t\tlet isWhite = false\n\t\t\t\t\t\tif ('is' == key) {\n\t\t\t\t\t\t\tnotation = (value === allZeros && !isProxy) ? rfp_green : rfp_red // all zeros\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnotation = rfp_red\n\t\t\t\t\t\t\t// all white: e.g. perps stupidly being told to flip\n\t\t\t\t\t\t\t\t// privacy.resistFingerprinting.randomDataOnCanvasExtract\n\t\t\t\t\t\t\tif (oKnown[key +'_white'].includes(value)) {isWhite = true}\n\t\t\t\t\t\t\t// exclude BB which must fail if not RFP\n\t\t\t\t\t\t\tif (isFPPFallback) {\n\t\t\t\t\t\t\t\t// FPP: 119+ and no proxy lies and no getImageData stealth\n\t\t\t\t\t\t\t\t// FF144 or lower: exclude solids: FPP does not tamper with those\n\t\t\t\t\t\t\t\t// exclude if all white | exclude if proxy lies\n\t\t\t\t\t\t\t\t// note: isGetStealth is getImageData\n\t\t\t\t\t\t\t\tlet useSolid = !name.includes('_solid')\n\t\t\t\t\t\t\t\tif (isVer > 144 && 'to' == key && isChunk !== '') {useSolid = true} // FF145+ FPP now handles to* solids\n\t\t\t\t\t\t\t\tif (!isWhite && useSolid) {\n\t\t\t\t\t\t\t\t\tif (!isProxy) {\n\t\t\t\t\t\t\t\t\t\tif ('ge' == key && !isGetStealth || 'ge' !== key) {\n\t\t\t\t\t\t\t\t\t\t\t// no proxy lies but persistent, so must be FPP\n\t\t\t\t\t\t\t\t\t\t\tnotation = fpp_green\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\trfpvalue = notation == rfp_green ? ' | RFP' : (notation == fpp_green ? ' | FPP' : '')\n\t\t\t\t\t\tif ('ge' == key) {\n\t\t\t\t\t\t\tstats = isCanvasGet\n\t\t\t\t\t\t\trfpvalue += ' | '+ isCanvasGetChannels\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnotation += ' [persistent' + isChunk + (isWhite ? ' white]' : ']'+ stats)\n\t\t\t\t\t\tdata = 'protected | persistent'+ isChunk + (isWhite ? ' white' : rfpvalue)\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// per execution\n\t\t\t\t\t\tif ('is' == key) {\n\t\t\t\t\t\t\tnotation = rfp_red\n\t\t\t\t\t\t} else if ('to' == key) {\n\t\t\t\t\t\t\tnotation = check_canvas_to(oData[name]) ? rfp_green : rfp_red\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnotation = check_canvas_get(name, 2) ? rfp_green : rfp_red\n\t\t\t\t\t\t}\n\t\t\t\t\t\trfpvalue = notation == rfp_green ? ' | RFP' : ''\n\t\t\t\t\t\tif ('ge' == key) {\n\t\t\t\t\t\t\tstats = isCanvasGet\n\t\t\t\t\t\t\tdata += ' | '+ isCanvasGetChannels\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnotation += ' [per execution' + isChunk +']'+ stats\n\t\t\t\t\t\tdata = 'protected | per execution'+ isChunk + rfpvalue\n\t\t\t\t\t}\n\t\t\t\t\toFP[name] = {'value': value, 'notation': notation, 'data': data}\n\t\t\t\t}\n\t\t\t})\n\t\t\texit()\n\t\t})\n\t})\n})\n\nconst outputCanvas = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('canvas')) {return resolve()}\n\n\tPromise.all([\n\t\tget_canvas()\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(9)\n"
  },
  {
    "path": "js/codecs.js",
    "content": "'use strict';\n\nlet mediaList = {}\n\nfunction set_mediaList() {\n\tlet v = \"video/\", a = \"audio/\"\n\t// ToDo: add wmf: e.g. 1806552\n\tlet aList = [\n\t\t//'application/fake',\n\t\t'application/ogg',\n\t\ta+'aac',\n\t\ta+'flac',\n\t\ta+'matroska',\n\t\ta+'mp3',\n\t\ta+'mp4',\n\t\ta+'mp4; codecs=',\n\t\ta+'mp4; codecs=\"\"',\n\t\ta+'mp4; codecs=\"flac\"',\n\t\ta+'mp4; codecs=\"mp3\"',\n\t\ta+'mp4; codecs=\"mp4a.40.2\"',\n\t\ta+'mp4; codecs=\"mp4a.40.29\"',\n\t\ta+'mp4; codecs=\"mp4a.40.42\"', // FF143+ 1711882 - removed FF144+ 1989946\n\t\ta+'mp4; codecs=\"mp4a.40.5\"',\n\t\ta+'mp4; codecs=\"mp4a.67\"',\n\t\ta+'mp4; codecs=\"opus\"',\n\t\t//a+'mp4; codecs=\\'\\'',\n\t\ta+'mpeg',\n\t\ta+'mpeg; codecs=\"mp3\"',\n\t\ta+'ogg; codecs=\"flac\"',\n\t\ta+'ogg; codecs=\"opus\"',\n\t\ta+'ogg; codecs=\"vorbis\"',\n\t\ta+'wav',a+'wav; codecs=\"1\"',\n\t\ta+'wave',a+'wave; codecs=\"1\"',\n\t\ta+'webm',\n\t\ta+'webm; codecs=\"opus\"',\n\t\ta+'webm; codecs=\"vorbis\"',\n\t\ta+'x-aac',\n\t\ta+'x-flac',\n\t\ta+'x-matroska',\n\t\ta+'x-m4a',\n\t\ta+'x-pn-wav',a+'x-pn-wav; codecs=\"1\"',\n\t\ta+'x-wav',a+'x-wav; codecs=\"1\"',\n\t]\n\tlet vList = [\n\t\t'application/ogg',\n\t\tv+'3gpp',\n\t\tv+'matroska',\n\t\tv+'matroska; codecs=\"av1\"',\n\t\tv+'matroska; codecs=\"avc1.58000a\"',\n\t\tv+'matroska; codecs=\"avc1.6e000a\"',\n\t\tv+'matroska; codecs=\"avc1.64003E\"',\n\t\tv+'matroska; codecs=\"avc1.7a000a\"',\n\t\tv+'matroska; codecs=\"avc1.f4000a\"',\n\t\tv+'matroska; codecs=\"hvc1.1.6.L186.B0\"',\n\t\tv+'matroska; codecs=\"hvc1.1.6.L93.B0\"',\n\t\tv+'matroska; codecs=\"hev1.1.6.L186.B0\"',\n\t\tv+'matroska; codecs=\"hev1.1.6.L93.B0\"',\n\t\tv+'matroska; codecs=\"vp8\"',\n\t\tv+'matroska; codecs=\"vp9\"',\n\t\tv+'mp4',\n\t\tv+'mp4; codecs=',\n\t\tv+'mp4; codecs=\"\"',\n\t\tv+'mp4; codecs=\"av01.0.08M.08\"', // 8bit\n\t\tv+'mp4; codecs=\"av01.0.00M.10\"', // 10bit\n\t\tv+'mp4; codecs=\"av01.0.00M.12\"', // 12bit\n\t\tv+'mp4; codecs=\"av01.2.31H.12\"',\n\t\tv+'mp4; codecs=\"avc1\"',\n\t\tv+'mp4; codecs=\"avc1.58000a\"', // extended\n\t\tv+'mp4; codecs=\"avc1.6e000a\"', // high 10\n\t\tv+'mp4; codecs=\"avc1.64003E\"',\n\t\tv+'mp4; codecs=\"avc1.7a000a\"', // high 4:2:2\n\t\tv+'mp4; codecs=\"avc1.f4000a\"', // high 4:4:4\n\t\tv+'mp4; codecs=\"avc3\"',\n\t\tv+'mp4; codecs=\"avc3.64003E\"',\n\t\tv+'mp4; codecs=\"flac\"',\n\t\tv+'mp4; codecs=\"hev1.1.6.L186.B0\"',\n\t\t\t//v+'mp4; codecs=\"hev1.1.6.L186.B0, mp4a.40.2\"',\n\t\tv+'mp4; codecs=\"hev1.1.6.L93.B0\"', // 1853448\n\t\tv+'mp4; codecs=\"hev1.2.4.L120.B0\"',\n\t\tv+'mp4; codecs=\"hvc1.1.6.L186.B0\"',\n\t\tv+'mp4; codecs=\"hvc1.1.6.L93.B0\"',\n\t\tv+'mp4; codecs=\"hvc1.2.4.L120.B0\"',\n\t\tv+'mp4; codecs=\"opus\"',\n\t\tv+'mp4; codecs=\"vp09.00.10.08\"',\n\t\tv+'mp4; codecs=\"vp9\"',\n\t\t//v+'mp4; codecs=\\'\\'',\n\t\tv+'quicktime',\n\t\tv+'webm',\n\t\tv+'webm; codecs=\"av01\"',\n\t\tv+'webm; codecs=\"av1\"',\n\t\tv+'webm; codecs=\"vorbis\"',\n\t\tv+'webm; codecs=\"vp8\"',\n\t\tv+'webm; codecs=\"vp8, opus\"',\n\t\tv+'webm; codecs=\"vp8, vorbis\"',\n\t\tv+'webm; codecs=\"vp9\"',\n\t\tv+'webm; codecs=\"vp9, opus\"',\n\t\tv+'webm; codecs=\"vp9, vorbis\"',\n\t\tv+'x-m4v',\n\t\tv+'x-matroska', // 1986058 FF144+\n\t\tv+'x-matroska; codecs=\"av1\"',\n\t\tv+'x-matroska; codecs=\"av1, opus\"',\n\t\tv+'x-matroska; codecs=\"avc1.58000a\"',\n\t\tv+'x-matroska; codecs=\"avc1.6e000a\"',\n\t\tv+'x-matroska; codecs=\"avc1.64003E\"',\n\t\t\t//v+'x-matroska; codecs=\"avc1.64003E, opus\"',\n\t\tv+'x-matroska; codecs=\"avc1.7a000a\"',\n\t\tv+'x-matroska; codecs=\"avc1.f4000a\"',\n\t\tv+'x-matroska; codecs=\"hvc1.1.6.L186.B0\"',\n\t\tv+'x-matroska; codecs=\"hvc1.1.6.L93.B0\"',\n\t\tv+'x-matroska; codecs=\"hev1.1.6.L186.B0\"',\n\t\tv+'x-matroska; codecs=\"hev1.1.6.L93.B0\"',\n\t\tv+'x-matroska; codecs=\"vp8\"',\n\t\tv+'x-matroska; codecs=\"vp9\"',\n\t]\n\tif (isVer < 130) {\n\t\t// theora support: FF126 1860492 prep + FF130 1890370 remove\n\t\tvList.push(\n\t\t\tv+'ogg',\n\t\t\tv+'ogg; codecs=\"flac\"',\n\t\t\tv+'ogg; codecs=\"opus\"',\n\t\t\tv+'ogg; codecs=\"theora\"',\n\t\t\tv+'ogg; codecs=\"theora, flac\"',\n\t\t\tv+'ogg; codecs=\"theora, speex\"',\n\t\t\tv+'ogg; codecs=\"theora, vorbis\"',\n\t\t)\n\t}\n\t// add and record fakes\n\t\t// we use default 5 for length to ensure we don't randomly duplicate a real codec/mime name\n\tmediaList['fake'] = {\n\t\t'audio': ['application/'+ rnd_word(), a + rnd_word(), a+'mp4; codecs=\"'+ rnd_word() +'\"'],\n\t\t'video': ['application/'+ rnd_word(), v + rnd_word(), v+'mp4; codecs=\"'+ rnd_word() +'\"', v+'webm; codecs=\"'+ rnd_word() +'\"']\n\t}\n\tfor (const k of Object.keys(mediaList.fake)) {\n\t\tlet aFake = mediaList.fake[k]\n\t\taFake.forEach(function(item){if ('audio' == k) {aList.push(item)} else {vList.push(item)}})\n\t}\n\tmediaList['audio'] = aList.sort()\n\tmediaList['video'] = vList.sort()\n\tlet mediaBtn = addButton(13, 'audio_codecs', aList.length +' audio', 'btnc', 'lists')\n\t\t+ addButton(13, 'video_codecs', vList.length +' video', 'btnc', 'lists')\n\taddDisplay(13, 'mediaBtn', mediaBtn)\n}\n\nfunction get_autoplay(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getAutoplayPolicy\n\t// a check on a specific element is more reliable (though it doesn't matter on page load)\n\n\t// cached from page load\n\tlet value, data ='', notation = isDesktop ? default_red : ''\n\tif (undefined == isAutoPlayError) {\n\t\t// Note: this is inconsistent/unstable on android: e.g. can return 'disallowed | disallowed' if the\n\t\t// phone is on 'Do Not Disturb' )or depending on the session and transient user activity/actions?)\n\t\tif (isDesktop && '5be5c665' == mini(isAutoPlay)) {notation = default_green}\n\t\tvalue = isAutoPlay\n\t} else {\n\t\tvalue = isAutoPlayError; data = isAutoPlay\n\t}\n\taddBoth(13, METRIC, value,'', notation, data)\n\n\t// user: not part of FP; don't record errors etc\n\tconst METRICuser = METRIC +'_user'\n\tif (gLoad || 'undefined' == isAutoPlay) {\n\t\taddDisplay(13, METRICuser, zNA)\n\t\treturn\n\t}\n\ttry {\n\t\tlet atest, mtest\n\t\tlet ares = navigator.getAutoplayPolicy('audiocontext')\n\t\ttry {atest = navigator.getAutoplayPolicy(dom.tzpAudio)} catch {atest = zErr}\n\t\tlet mres = navigator.getAutoplayPolicy('mediaelement')\n\t\ttry {mtest = navigator.getAutoplayPolicy(dom.tzpVideo)} catch {mtest = zErr}\n\t\tlet display = (ares === atest ? ares : ares +', '+ atest) +' | '+ (mres === mtest ? mres : mres +', '+ mtest)\n\t\taddDisplay(13, METRICuser, display)\n\t} catch(e) {\n\t\taddDisplay(13, METRICuser, (e+'').slice(0,47) + '...')\n\t}\n\treturn\n}\n\nfunction get_capabilities_rfc(type) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/RTCCodecStats/sdpFmtpLine\n\t// https://w3c.github.io/webrtc-stats/#dom-rtccodecstats-sdpfmtpline\n\t// https://datatracker.ietf.org/doc/html/rfc7587\n\tconst METRIC = type +'_getCapabilities_rtc'\n\tlet hash, data = '', btn ='', notation = isTB ? bb_red : ''\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tlet receiver = window.RTCRtpReceiver\n\t\tif (runST) {receiver = []}\n\t\tlet typeCheck = typeFn(receiver)\n\t\tif ('undefined' == typeCheck) {\n\t\t\thash = typeCheck\n\t\t\tif (isTB) {notation = bb_green}\n\t\t} else if ('function' !== typeCheck) {\n\t\t\tthrow zErrType + typeCheck\n\t\t} else {\n\t\t\tdata = receiver.getCapabilities(type)\n\t\t\tif (runSI) {data = null}\n\t\t\ttypeCheck = typeFn(data)\n\t\t\tif ('object' !== typeCheck) {throw zErrInvalid +'expected object: got '+ typeCheck}\n\t\t\t//console.log(data)\n\t\t\thash = mini(data); btn = addButton(13, METRIC)\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(13, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_codecs(type) {\n\t// https://privacycheck.sec.lrz.de/active/fp_cpt/fp_can_play_type.html\n\t// https://cconcolato.github.io/media-mime-support/\n\tlet t0 = nowFn()\n\tconst metricC = type +'_canPlayType', metricT = type +'_isTypeSupported'\n\tlet list = mediaList[type], countMax = list.length, aFake\n\n\t// canPlayType\n\tfunction get_canPlay() {\n\t\tlet hash, data = {'maybe': [],'probably': []}, btn='', isLies = false, hasFake = false\n\t\ttry {\n\t\t\tlist.forEach(function(item) {\n\t\t\t\tlet tmp = item.replace(type +'\\/','') // strip 'video/','audio/'\n\t\t\t\tlet value = obj.canPlayType(item)\n\t\t\t\tif (runST) {value = 'audio' == type ? undefined : '  '}\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('string' !== typeCheck && 'empty string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif ('maybe' === value || 'probably' === value) {\n\t\t\t\t\tdata[value].push(tmp)\n\t\t\t\t\tif (aFake.includes(item)) {hasFake = true}\n\t\t\t\t} else if (runSL) {\n\t\t\t\t\tif (aFake.includes(item)) {\n\t\t\t\t\t\tif (item.includes('codecs')) {data.probably.push(tmp)} else {data.maybe.push(tmp)}\n\t\t\t\t\t\thasFake = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t// tests\n\t\t\t//data = {'maybe': [], 'probably': []} // none\n\t\t\t//data = {'maybe': [], 'probably': mediaList[type]} // all\n\t\t\t//data.maybe = [] // one empty\n\t\t\t// counts\n\t\t\tlet countMaybe = data.maybe.length,\n\t\t\t\tcountProbably = data.probably.length,\n\t\t\t\tcountTotal = countMaybe + countProbably\n\t\t\t// lies\n\t\t\tif (0 == countTotal || countMax == countTotal) {\n\t\t\t\t// can't be none, all\n\t\t\t\thash = (0 == countTotal ? 'none' : 'all'); data = ''; isLies = true\n\t\t\t} else {\n\t\t\t\tdata.maybe.sort() // we removed leading audio/ and video/, so do a final sort\n\t\t\t\tdata.probably.sort()\n\t\t\t\tif (hasFake) {\n\t\t\t\t\t// can't have fake\n\t\t\t\t\tisLies = true\n\t\t\t\t} else if (0 == countMaybe || 0 == countProbably) {\n\t\t\t\t\t// either is empty\n\t\t\t\t\tisLies = true\n\t\t\t\t}\n\t\t\t\tif (!isLies) {\n\t\t\t\t\t// probably: should only include \"codecs=\"something\"\"\n\t\t\t\t\tdata['probably'].forEach(function(item) {\n\t\t\t\t\t\tif (!item.includes(\"codecs=\\\"\")) {isLies = true}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tif (!isLies) {\n\t\t\t\t\t// maybe: shouldn't include \"codecs=\"something\"\" (i.e it has \"mp4; codecs=\",\"mp4; codecs=\\\"\\\"\")\n\t\t\t\t\tdata['maybe'].forEach(function(item) {\n\t\t\t\t\t\tif (item.includes(\"codecs=\\\"\")) {\n\t\t\t\t\t\t\tif (item !== \"mp4; codecs=\\\"\\\"\") {isLies = true}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\thash = mini(data), btn = addButton(13, metricC, countMaybe +'/' + countProbably)\n\t\t\t}\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t\taddBoth(13, metricC, hash, btn, '', data, isLies)\n\t\treturn\n\t}\n\n\t// isTypeSupported\n\tfunction get_isType() {\n\t\tlet hash, data = {'MediaRecorder': [],'MediaSource': []}, btn='', isLies = false, hasFakeR = false, hasFakeS = false\n\t\ttry {\n\t\t\tlet canRecord = true, canSource = true\n\t\t\tlist.forEach(function(item) {\n\t\t\t\tlet tmp = item.replace(type +'\\/','') // strip 'video/','audio/'\n\t\t\t\tif (canRecord) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet value = MediaRecorder.isTypeSupported(item)\n\t\t\t\t\t\tif (runST) {value = type == 'audio' ? undefined : ''}\n\t\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\tdata.MediaRecorder.push(tmp)\n\t\t\t\t\t\t\tif (aFake.includes(item)) {hasFakeR = true}\n\t\t\t\t\t\t} else if (runSL) {\n\t\t\t\t\t\t\tif (aFake.includes(item)) {\n\t\t\t\t\t\t\t\tdata.MediaRecorder.push(tmp)\n\t\t\t\t\t\t\t\thasFakeR = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//foo++\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\thasFakeR = false\n\t\t\t\t\t\tcanRecord = false // stop testing\n\t\t\t\t\t\tdata.MediaRecorder = zErr // replace data\n\t\t\t\t\t\tlog_error(13, metricT +\"_MediaRecorder\", e) // log error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (canSource) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet value = MediaSource.isTypeSupported(item)\n\t\t\t\t\t\tif (runST) {value = type == 'audio' ? 1 : null}\n\t\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\tdata.MediaSource.push(tmp)\n\t\t\t\t\t\t\tif (aFake.includes(item)) {hasFakeS = true}\n\t\t\t\t\t\t} else if (runSL) {\n\t\t\t\t\t\t\tif (aFake.includes(item)) {\n\t\t\t\t\t\t\t\tdata.MediaSource.push(tmp)\n\t\t\t\t\t\t\t\thasFakeS = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//foo++\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\thasFakeS = false\n\t\t\t\t\t\tcanSource = false; data.MediaSource = zErr; log_error(13, metricT +\"_MediaSource\", e)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t// both errors?\n\t\t\tif (!canRecord && !canSource) {\n\t\t\t\thash = zErr; data = ''\n\t\t\t} else {\n\t\t\t\t// test\n\t\t\t\t//data = {'MediaRecorder': [],'MediaSource': []}\n\t\t\t\t//data.MediaRecorder = []; hasFakeR = false\n\t\t\t\t//data.MediaSource = []; hasFakeS = false\n\t\t\t\t// counts for display\n\t\t\t\tlet countRecord = canRecord ? data.MediaRecorder.length : zErr,\n\t\t\t\t\tcountSource = canSource ? data.MediaSource.length: zErr\n\t\t\t\t// lies\n\t\t\t\tif (0 == countRecord && 0 == countSource) {\n\t\t\t\t\thash = 'none'; data = ''; isLies = true\n\t\t\t\t} else {\n\t\t\t\t\tif (canRecord) {data.MediaRecorder.sort()} // we removed leading audio/ and video/, so do a final sort\n\t\t\t\t\tif (canSource) {data.MediaSource.sort()}\n\t\t\t\t\tif (0 !== (hasFakeR + hasFakeS)) {\n\t\t\t\t\t\t// can't have fake: note each fake was set as false if we errored\n\t\t\t\t\t\tisLies = true\n\t\t\t\t\t} else if (canRecord && 0 == countRecord || canSource && 0 == countSource) {\n\t\t\t\t\t\t// either is empty and not an error\n\t\t\t\t\t\tisLies = true\n\t\t\t\t\t}\n\t\t\t\t\thash = mini(data), btn = addButton(13, metricT, countRecord +'/' + countSource)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t\taddBoth(13, metricT, hash, btn, '', data, isLies)\n\t\treturn\n\t}\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tvar obj = document.createElement(type)\n\t\taFake = mediaList.fake[type]\n\t\tPromise.all([\n\t\t\tget_canPlay(),\n\t\t\tget_isType(),\n\t\t]).then(function(){\n\t\t\tlog_perf(13, type, t0)\n\t\t\treturn\n\t\t})\n\t} catch(e) {\n\t\taddBoth(13, metricC, e, '', '', zErrLog)\n\t\taddBoth(13, metricT, e, '', '', zErrLog)\n\t\treturn\n\t}\n}\n\nconst get_eme = (METRIC) => new Promise(resolve => {\n\t/*\n\thttps://w3c.github.io/encrypted-media/#common-key-systems\n\tgecko only supports\n\t\t'org.w3.clearkey'\n\t\t'com.widevine.alpha'\n\tother\n\t\t'com.microsoft.playready',\n\t\t'com.youtube.playready',\n\t\t'webkit-org.w3.clearkey',\n\t\t'com.adobe.primetime',\n\t\t'com.adobe.access',\n\t\t'com.apple.fairplay'\n\tnote: media.gmp-gmpopenh264.enabled = no effect even after a restart\n\tnote: 1706121 FF128+ fixed PB mode\n\t*/\n\t/* widevine gecko issues\n\t\ttriggers DRM prompt if disabled\n\t\t\t^ error is \"NotSupportedError: EME has been preffed off\"\n\t\t\t^ this eats viewport/inner pixels\n\t\ton android it can hold up the result and we end up with eme == timeout\n\t\t\t^ if rerun/no-timeout we get\n\t\t\t\" error is: NotSupportedError: The application embedding this user agent has blocked MediaKeySystemAccess\"\n\t\ton android DRM in PB mode is always prompted\n\t*/\n\n\tlet isDone = false\n\t// really slow on first session loads in blink / also android needs help\n\tlet timeout = 'blink' == isEngine ? 4000 : 400 \n\tsetTimeout(function() {if (!isDone) {exit(zErrTime)}}, timeout)\n\tfunction exit(value, data ='', btn='') {\n\t\tif (!isDone) {\n\t\t\tisDone = true\n\t\t\t// results are not guaranteed to come back in the order requested: sort into a new object\n\t\t\tif ('object' == typeof data) {\n\t\t\t\tlet newobj = {}\n\t\t\t\tfor (const k of Object.keys(data).sort()) {\n\t\t\t\t\tnewobj[k] = {}\n\t\t\t\t\tfor (const j of Object.keys(data[k]).sort()) {newobj[k][j] = data[k][j]}\n\t\t\t\t}\n\t\t\t\tdata = newobj\n\t\t\t\tvalue = mini(data)\n\t\t\t\tbtn = addButton(13, METRIC)\n\t\t\t}\n\t\t\tlet notation = isBB ? bb_red : ''\n\t\t\tif (isBB && '1f5a84f8' == value) {notation = bb_green} // desktop + android\n\t\t\taddBoth(13, METRIC, value, btn, notation, data)\n\t\t\treturn resolve()\n\t\t}\n\t}\n\n\tlet oEME = {\n\t\tclearkey: ['org.w3.clearkey','webkit-org.w3.clearkey'],\n\t\tfairplay: ['com.apple.fairplay'],\n\t\tplayready: ['com.microsoft.playready','com.youtube.playready'],\n\t\tprimetime: ['com.adobe.access','com.adobe.primetime'],\n\t\twidevine: ['com.widevine.alpha'],\n\t}\n\t// widevine on non-BB android is problematic\n\tif (!isBB && !isDesktop) {delete oEME.widevine}\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tlet request = window.navigator.requestMediaKeySystemAccess\n\t\tif (runST) {request = ''}\n\t\tlet typeCheck = typeFn(request)\n\t\tif ('undefined' == typeCheck) {exit(typeCheck)\n\t\t} else if ('function' !== typeCheck) {throw zErrType +'requestMediaKeySystemAccess: ' + typeCheck\n\t\t} else {\n\t\t\tlet data = {}, maxCount = 0, counter = 0\n\t\t\tfor (const k of Object.keys(oEME)) {maxCount += oEME[k].length}\n\t\t\tconst config = {\n\t\t\t\tinitDataTypes: ['keyids', 'webm'],\n\t\t\t\taudioCapabilities: [{contentType: 'audio/webm; codecs=\"opus\"'}],\n\t\t\t}\n\t\t\tfor (const key of Object.keys(oEME).sort()) {\n\t\t\t\tdata[key] = {}\n\t\t\t\tlet value\n\t\t\t\toEME[key].forEach(function(item){\n\t\t\t\t\tnavigator.requestMediaKeySystemAccess(item, [config]).then((result) => {\n\t\t\t\t\ttypeCheck = typeFn(result)\n\t\t\t\t\tif ('empty object' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\tlet expected = '[object MediaKeySystemAccess]'\n\t\t\t\t\tif (result +'' !== expected) {throw zErrInvalid + 'expected '+ expected +': got '+ result}\n\t\t\t\t\t\tdata[key][item] = true\n\t\t\t\t\t\tcounter++\n\t\t\t\t\t\t// await all results\n\t\t\t\t\t\tif (maxCount == counter) {exit('', data)}\n\t\t\t\t\t}).catch(function(e){\n\t\t\t\t\t\tvalue = zErr\n\t\t\t\t\t\t// suppress expected errors\n\t\t\t\t\t\t\t// ToDo: check safari\n\t\t\t\t\t\tlet aCheck = []\n\t\t\t\t\t\tif (isGecko) {\n\t\t\t\t\t\t\tif (isBB) {\n\t\t\t\t\t\t\t\tlet checkvalue = 'Key system is unsupported'\n\t\t\t\t\t\t\t\tif ('com.widevine.alpha' == item) {checkvalue = 'EME has been preffed off'\n\t\t\t\t\t\t\t\t} else if ('org.w3.clearkey' == item) {checkvalue = 'CDM is not installed'}\n\t\t\t\t\t\t\t\taCheck.push('NotSupportedError: '+ checkvalue)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\taCheck.push('NotSupportedError: Key system is unsupported')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if ('blink' == isEngine) {\n\t\t\t\t\t\t\taCheck.push('NotSupportedError: Unsupported keySystem or supportedConfigurations.')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (aCheck.includes(e+'')) {\n\t\t\t\t\t\t\tvalue = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog_error(13, METRIC +'_'+ item, e) // item names are unique, we don't need the key\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdata[key][item] = value\n\t\t\t\t\t\tcounter++\n\t\t\t\t\t\t// wait for all the results\n\t\t\t\t\t\tif (maxCount == counter) {exit('', data)}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\nfunction get_preload_media(METRIC) {\n\t// FF142+/ESR140: 1972600 | also see 1969210\n\t// ToDo: I don't think this test is sufficient, we need some actual media\n\tlet value, data = '', notation = rfp_red\n\ttry {\n\t\tvalue = dom.tzpAudio.preload\n\t\tif (runST) {value = 99} else if (runSI) {value = 'banana'}\n\t\tif ('string' !== typeFn(value, true)) {throw zErrType + typeFn(value)}\n\t\tif ('' == value) {value = typeFn(value)}\n\t\tlet aValid = ['auto','metadata','none']\n\t\tif (isVer < 140) {aValid.push('empty string')} // 929890\n\t\tif (!aValid.includes(value)) {aValid.sort(); throw zErrInvalid +'expected ' + aValid.join(', ') + ': got '+ value}\n\t\tif ('auto' == value) {notation = rfp_green}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(13, METRIC, value,'', notation, data)\n\treturn\n}\n\n\nconst outputMedia = () => new Promise(resolve => {\n\tif (gLoad) {set_mediaList()}\n\tif (gRun && sectionIgnore.includes('codecs')) {return resolve()}\n\n\tif (gRun) {\n\t\taddDetail('audio_codecs', mediaList['audio'], 'lists')\n\t\taddDetail('video_codecs', mediaList['video'], 'lists')\n\t}\n\tPromise.all([\n\t\tget_eme('eme'),\n\t\tget_codecs('audio'),\n\t\tget_codecs('video'),\n\t\tget_preload_media('preload_htmlmediaelement'),\n\t\tget_autoplay('getAutoplayPolicy'),\n\t\tget_capabilities_rfc('audio'),\n\t\tget_capabilities_rfc('video'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(13)\n"
  },
  {
    "path": "js/css.js",
    "content": "'use strict';\n\nfunction rgba2hex(orig, hexOnly = false) {\n\tvar a, isPercent,\n\t\trgb = orig.replace(/\\s/g, '').match(/^rgba?\\((\\d+),(\\d+),(\\d+),?([^,\\s)]+)?/i),\n\t\talpha = (rgb && rgb[4] || '').trim(),\n\t\thex = rgb ?\n\t\t(rgb[1] | 1 << 8).toString(16).slice(1) +\n\t\t(rgb[2] | 1 << 8).toString(16).slice(1) +\n\t\t(rgb[3] | 1 << 8).toString(16).slice(1) : orig;\n\tif (alpha !== '') {a = alpha\n\t} else {\n\t\ta = 0o1\n\t\trgb = rgb.slice(0, rgb.length - 1)\n\t}\n\t// multiply before convert to HEX\n\ta = ((a * 255) | 1 << 8).toString(16).slice(1)\n\thex = hex + a\n\tif (!hexOnly) {\n\t\trgb = rgb.slice(1, rgb.length)\n\t\thex += ' '+ rgb.join('-')\n\t}\n\treturn hex\n}\n\nfunction get_colors() {\n\tlet t0 = nowFn()\n\t/* https://www.w3.org/TR/css-color-4/ */\n\tlet oList = {\t// sorted\n\t\tcss4: [\n\t\t\t'-moz-activehyperlinktext','-moz-default-color','-moz-default-background-color',\n\t\t\t'-moz-hyperlinktext','-moz-visitedhyperlinktext',\n\t\t\t'AccentColor','AccentColorText','ActiveText','ButtonBorder','ButtonFace','ButtonText',\n\t\t\t'Canvas','CanvasText','Field','FieldText','GrayText','Highlight','HighlightText','LinkText',\n\t\t\t'Mark','MarkText','SelectedItem','SelectedItemText','VisitedText',\n\t\t],\n\t\tdeprecated: [\n\t\t\t'ActiveBorder','ActiveCaption','AppWorkspace','Background','ButtonHighlight','ButtonShadow',\n\t\t\t'CaptionText','InactiveBorder','InactiveCaption','InactiveCaptionText','InfoBackground',\n\t\t\t'InfoText','Menu','MenuText','Scrollbar','ThreeDDarkShadow','ThreeDFace','ThreeDHighlight',\n\t\t\t'ThreeDLightShadow','ThreeDShadow','Window','WindowFrame','WindowText',\n\t\t],\n\t\tmoz: [\n\t\t\t'-moz-cellhighlight','-moz-cellhighlighttext','-moz-combobox','-moz-comboboxtext','-moz-dialog',\n\t\t\t'-moz-dialogtext','-moz-field','-moz-fieldtext','-moz-html-cellhighlight','-moz-html-cellhighlighttext',\n\t\t\t'-moz-menubarhovertext','-moz-menuhover','-moz-menuhovertext','-moz-oddtreerow',\n\t\t],\n\t}\n\t/* note: windows 11: tested in FF146 (both protected with RFP)\n\t\t'-moz-menuhover' has an opacity (0.118)\n\t\t'Menu' has an opacity (0.6) exposed when contrast control (forced colors) is enabled\n\t\twhere do these come from (app theme, system, user prefs, prefers-color-scheme etc?)\n\t\thow are they calculated or are they hardcoded\n\t*/\n\n\tif (!isGecko) {\n\t\tdelete oList.moz\n\t\taddBoth(14,'colors_moz', zNA)\n\t} else {\n\t// with forced colors, removed -moz named colors will be false positives (and our alpha setting is retained)\n\t\t// wrecking our RFP deterministic hash: to solve this we will add them if we expect them\n\t\tlet aAdd = []\n\t\tif (isVer < 141) {aAdd.push('-moz-buttonhoverface','-moz-buttonhovertext')} // removed FF141: 1968925\n\t\tif (isVer < 140) {aAdd.push('-moz-eventreerow')} // removed FF140: can't find bugzilla\n\t\tif (aAdd.length) {\n\t\t\toList.moz = oList.moz.concat(aAdd).sort()\n\t\t}\n\t}\n\t//console.log(oList)\n\n\tfor (const type of Object.keys(oList)) {\n\t\tconst element = dom.tzpColor\n\t\tconst strColor = 'rgba(1, 2, 3, 0.5)' // opacity used to help avoid collisions\n\t\tconst METRIC = 'colors_'+ type\n\n\t\tlet hash, btn ='', data = {}, notation = 'moz' == type ? rfp_red : ''\n\t\ttry {\n\t\t\tif (runSE) {foo++}\n\t\t\tlet aTemp = [], oTemp = {}, aList = oList[type]\n\t\t\taList.forEach(function(style){\n\t\t\t\telement.style.backgroundColor = strColor // reset color\n\t\t\t\telement.style.backgroundColor = style\n\t\t\t\tlet rgb = window.getComputedStyle(element, null).getPropertyValue('background-color')\n\t\t\t\tif (rgb !== strColor) { // drop obsolete\n\t\t\t\t\taTemp.push(style +':'+ rgb)\n\t\t\t\t\tif (oTemp[rgb] == undefined) {oTemp[rgb] = [style]} else {oTemp[rgb].push(style)}\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet tmpobj = {}, count = 0\n\t\t\tfor (const k of Object.keys(oTemp)) {tmpobj[rgba2hex(k)] = oTemp[k]} // rgba2hex\n\t\t\tfor (const k of Object.keys(tmpobj).sort()) {data[k] = tmpobj[k]; count += data[k].length} // sort/count\n\t\t\thash = mini(data); btn = addButton(14, METRIC, Object.keys(data).length +'/'+ count)\n\t\t\tif ('moz' == type) {\n\t\t\t\tlet expectedhash = isVer == 140 ? 'c04857b2' : '2439d123' // FF140 | FF141+\n\t\t\t\tnotation = expectedhash == hash ? rfp_green : rfp_red\n\t\t\t}\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t\taddBoth(14, METRIC, hash, btn, notation, data)\n\t}\n\tlog_perf(14, 'colors', t0)\n\treturn\n}\n\nfunction get_computed_styles(METRIC) {\n\t/* https://github.com/abrahamjuliot/creepjs */\n\tlet t0 = nowFn()\n\tconst names = ['cssrulelist','domparser','getcomputed','htmlelement',]\n\tlet aErr = [false, false, false, false]\n\tlet aHashes = [], intHashes = [], oDisplay = {}\n\tlet notation = isBBESR ? bb_red : '', isLies = false\n\n\tlet styleVersion = type => {\n\t\treturn new Promise(resolve => {\n\t\t\t// get CSSStyleDeclaration\n\t\t\ttry {\n\t\t\t\tif (runSE) {foo++}\n\t\t\t\tlet cssStyleDeclaration = (\n\t\t\t\t\ttype == 0 ? document.styleSheets[0].cssRules[0].style :\n\t\t\t\t\ttype == 1 ? ((new DOMParser).parseFromString('', 'text/html')).body.style :\n\t\t\t\t\ttype == 2 ? getComputedStyle(document.body) :\n\t\t\t\t\ttype == 3 ? document.body.style :\n\t\t\t\t\tundefined\n\t\t\t\t)\n\t\t\t\tif (!cssStyleDeclaration) {\n\t\t\t\t\tthrow new TypeError('invalid argument string')\n\t\t\t\t}\n\t\t\t\t// get properties\n\t\t\t\tlet prototype = Object.getPrototypeOf(cssStyleDeclaration),\n\t\t\t\t\tprototypeProperties = Object.getOwnPropertyNames(prototype),\n\t\t\t\t\townEnumerablePropertyNames = [],\n\t\t\t\t\tcssVar = /^--.*$/\n\t\t\t\t// chrome getComputedStyle prepends \"-\" to some webkit* keys which it doesn't do in the other methods\n\t\t\t\tif (type == 2 && 'blink' === isEngine) {cssVar = /^-.*$/}\n\n\t\t\t\tObject.keys(cssStyleDeclaration).forEach(key => {\n\t\t\t\t\tlet numericKey = !isNaN(key),\n\t\t\t\t\t\tvalue = cssStyleDeclaration[key],\n\t\t\t\t\t\tcustomPropKey = cssVar.test(key),\n\t\t\t\t\t\tcustomPropValue = cssVar.test(value)\n\t\t\t\t\tif (numericKey && !customPropValue) {\n\t\t\t\t\t\treturn ownEnumerablePropertyNames.push(value)\n\t\t\t\t\t} else if (!numericKey && !customPropKey) {\n\t\t\t\t\t\treturn ownEnumerablePropertyNames.push(key)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t})\n\t\t\t\t// get properties in prototype chain (required only in chrome)\n\t\t\t\tlet propertiesInPrototypeChain = {}\n\t\t\t\tlet capitalize = str => str.charAt(0).toUpperCase() + str.slice(1),\n\t\t\t\t\tuncapitalize = str => str.charAt(0).toLowerCase() + str.slice(1),\n\t\t\t\t\tremoveFirstChar = str => str.slice(1),\n\t\t\t\t\tcaps = /[A-Z]/g\n\t\t\t\townEnumerablePropertyNames.forEach(key => {\n\t\t\t\t\tif (propertiesInPrototypeChain[key]) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// determine attribute type\n\t\t\t\t\tlet isNamedAttribute = key.indexOf('-') > -1,\n\t\t\t\t\t\tisAliasAttribute = caps.test(key)\n\t\t\t\t\t// reduce key for computation\n\t\t\t\t\tlet firstChar = key.charAt(0),\n\t\t\t\t\t\tisPrefixedName = isNamedAttribute && firstChar == '-',\n\t\t\t\t\t\tisCapitalizedAlias = isAliasAttribute && firstChar == firstChar.toUpperCase()\n\t\t\t\t\tkey = (\n\t\t\t\t\t\tisPrefixedName ? removeFirstChar(key) :\n\t\t\t\t\t\tisCapitalizedAlias ? uncapitalize(key) :\n\t\t\t\t\t\tkey\n\t\t\t\t\t)\n\t\t\t\t\t// find counterpart in CSSStyleDeclaration object or its prototype chain\n\t\t\t\t\tif (isNamedAttribute) {\n\t\t\t\t\t\tlet aliasAttribute = key.split('-').map((word, index) => index == 0 ? word : capitalize(word)).join('')\n\t\t\t\t\t\tif (aliasAttribute in cssStyleDeclaration) {\n\t\t\t\t\t\t\tpropertiesInPrototypeChain[aliasAttribute] = true\n\t\t\t\t\t\t} else if (capitalize(aliasAttribute) in cssStyleDeclaration) {\n\t\t\t\t\t\t\tpropertiesInPrototypeChain[capitalize(aliasAttribute)] = true\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isAliasAttribute) {\n\t\t\t\t\t\tlet namedAttribute = key.replace(caps, char => '-' + char.toLowerCase())\n\t\t\t\t\t\tif (namedAttribute in cssStyleDeclaration) {\n\t\t\t\t\t\t\tpropertiesInPrototypeChain[namedAttribute] = true\n\t\t\t\t\t\t} else if (`-${namedAttribute}` in cssStyleDeclaration) {\n\t\t\t\t\t\t\tpropertiesInPrototypeChain[`-${namedAttribute}`] = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t})\n\t\t\t\t// compile keys\n\t\t\t\tlet keys = [\n\t\t\t\t\t...new Set([\n\t\t\t\t\t\t...prototypeProperties,\n\t\t\t\t\t\t...ownEnumerablePropertyNames,\n\t\t\t\t\t\t...Object.keys(propertiesInPrototypeChain)\n\t\t\t\t\t])\n\t\t\t\t]\n\t\t\t\t/* checks\n\t\t\t\tlet moz = keys.filter(key => (/moz/i).test(key)).length,\n\t\t\t\t\twebkit = keys.filter(key => (/webkit/i).test(key)).length,\n\t\t\t\t\tprototypeName = ('' + prototype).match(/\\[object (.+)\\]/)[1]\n\t\t\t\t//*/\n\t\t\t\t// output\n\t\t\t\treturn resolve({\n\t\t\t\t\tkeys,\n\t\t\t\t\t//moz,\n\t\t\t\t\t//webkit,\n\t\t\t\t\t//prototypeName\n\t\t\t\t})\n\t\t\t} catch(e) {\n\t\t\t\taErr[type] = true\n\t\t\t\treturn resolve(log_error(14, METRIC +'_'+ names[type], e))\n\t\t\t}\n\t\t})\n\t}\n\n\tfunction display() {\n\t\tfor (const k of Object.keys(oDisplay)) {addDisplay(14, k, oDisplay[k])}\n\t\tlog_perf(14, METRIC, t0)\n\t}\n\n\t// run\n\tPromise.all([\n\t\tstyleVersion(0),\n\t\tstyleVersion(1),\n\t\tstyleVersion(2),\n\t\tstyleVersion(3),\n\t]).then(res => {\n\t\t// simulate\n\t\t/* different hashes: !isLies\n\t\tres[0] = res[1]; aErr[0] = false\n\t\tres[2]['keys'] = ['a','constructor']\n\t\t//*/\n\t\t//* different hashes: isLies\n\t\t//res[2]['keys'] = ['a']\n\t\t//*/\n\t\t/* some same hashes: constructor not last\n\t\tres[1]['keys'].push('a')\n\t\tres[2]['keys'].push('a')\n\t\t//*/\n\t\t/* various errors\n\t\tres[0] = {}; aErr[0] = false\n\t\tres[1] = {'keys': ['a','b']}\n\t\tres[2] = {'keys': 5}\n\t\t//*/\n\t\t//console.log(res)\n\n\t\tfor (let i=0; i < res.length; i++) {\n\t\t\tlet obj = res[i]\n\t\t\tlet type = METRIC +'_'+ names[i]\n\t\t\tif (aErr[i]) {\n\t\t\t\toDisplay[type] = obj // error already logged\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tif (runST) {if (i == 1) {obj = {keys: []}} else if (i == 2) {obj = {}} else {obj = null}}\n\t\t\t\t\tlet typeCheck = typeFn(obj)\n\t\t\t\t\tif ('object' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\ttypeCheck = typeFn(obj.keys)\n\t\t\t\t\tif ('array' !== typeCheck) {throw zErrType + 'keys: '+ typeCheck}\n\t\t\t\t\tlet data = obj.keys\n\t\t\t\t\tif ('blink' == isEngine) {data.sort()} // sort for blink\n\t\t\t\t\tlet hash = mini(data)\n\t\t\t\t\taHashes.push(hash)\n\t\t\t\t\tintHashes.push(i)\n\t\t\t\t\toDisplay[type] = hash\n\t\t\t\t\t// last item s/be constructor: detects if items are added, not removed\n\t\t\t\t\tif (data[data.length-1] !== 'constructor') {isLies = true}\n\t\t\t\t} catch(e) {\n\t\t\t\t\taErr[i] = true\n\t\t\t\t\toDisplay[type] = log_error(14, type, e)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet hash, btn='', data =''\n\t\tif (aErr.every(x => x === true)) {\n\t\t\t// max errors\n\t\t\thash = zErr\n\t\t} else {\n\t\t\taHashes = dedupeArray(aHashes)\n\t\t\t// same hashes\n\t\t\tif (aHashes.length === 1) {\n\t\t\t\thash = aHashes[0], data = res[intHashes[0]]['keys']\n\t\t\t\tbtn = addButton(14, METRIC, data.length)\n\t\t\t\t// health: BB only if ESR\n\t\t\t\tif (isBBESR) {\n\t\t\t\t\tif ('mac' == isOS) {\n\t\t\t\t\t\t/* mac has\n\t\t\t\t\t\t\tMozOsxFontSmoothing,-moz-osx-font-smoothing,\n\t\t\t\t\t\t\tWebkitFontSmoothing,-webkit-font-smoothing,webkitFontSmoothing\n\t\t\t\t\t\t*/\n\t\t\t\t\t\tif ('1c5fe54d' == hash) {notation = bb_green} // BB15 1127\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41347\n\t\t\t\t\t\t\t// some older (mostly unsupported) win10 and android <= 6 will lack\n\t\t\t\t\t\t\t// fontOpticalSizing, font-optical-sizing, fontVariationSettings, font-variation-settings\n\t\t\t\t\t\t\t// but I consider these out-of-scope\n\t\t\t\t\t\tif ('ed89a929' == hash) {notation = bb_green} // BB15 1122\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// mixed hashes\n\t\t\t\thash = 'mixed'; isLies = true // gecko is never mixed\n\t\t\t\t// for the first of each unique hash add sDetail + update display\n\t\t\t\tlet aDone = {}\n\t\t\t\tintHashes.forEach(function(item) {\n\t\t\t\t\tlet name = METRIC +'_'+ names[item], hash = oDisplay[name]\n\t\t\t\t\tif (aDone[hash] == undefined) {\n\t\t\t\t\t\taDone[hash] = name\n\t\t\t\t\t\tsDetail[isScope][name] = res[item]['keys']\n\t\t\t\t\t\toDisplay[name] = hash + addButton(14, name, res[item]['keys'].length)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\taddBoth(14, METRIC, hash, btn, notation, data, isLies)\n\t\tdisplay()\n\t\treturn\n\t}).catch(e => {\n\t\taddBoth(14, METRIC, e,'', notation, zErrLog)\n\t\treturn\n\t})\n}\n\nfunction get_link(METRIC) {\n\t// FF120+ 1858397: layout.css.always_underline_links\n\t// FF143+ 1980562: returns 'none' or 'underline'\n\tlet value, data ='', notation = default_red\n\ttry {\n\t\tvalue = getComputedStyle(dom.tzpLink).textDecoration\n\t\tif (runST) {value = null} else if (runSI) {value = 'x'}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\tif (isGecko && isVer < 143) {\n\t\t\tif (!value.includes('rgb(')) {throw zErrInvalid +'got ' + value}\n\t\t}\n\t\t// ignore rgb values: we're using a custom value from css\n\t\t// but even if we weren't we already have that info from LinkText\n\t\tvalue = 'underline' == value.slice(0,9) ? 'underline' : 'none'\n\t\tif ('none' == value) {notation = default_green}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(14, METRIC, value,'', notation, data)\n\treturn\n}\n\nfunction get_media_css(METRIC) {\n\t// https://drafts.csswg.org/mediaqueries-5/\n\tlet oTmpData = {}, countFail = 0, countSuccess = 0\n\n\tfunction collect_data(metric, value, notation, data='', isLies = false) {\n\t\t//console.log(metric, '~'+value +'~', '~'+data+'~', notation)\n\t\t// data\n\t\tif (zErr == value) {isLies = false}\n\t\toTmpData[metric] = isSmart && isLies ? zLIE : (data == '' ? value : data)\n\t\t// failures: we catch failures only on checked items\n\t\tif (rfp_red == notation) {countFail++} else if (rfp_green == notation) {countSuccess++}\n\t\t// display\n\t\tif (zLIE == oTmpData[metric]) {value = log_known(14, METRIC +'_'+ metric, value+'')} // color up + record lies\n\t\taddDisplay(14, METRIC +'_'+ metric, value,'', notation)\n\t}\n\n\tfunction get_mm_color(metric = 'color') {\n\t\tlet value, isLies = false\n\t\tlet cssvalue = getElementProp(14, '#cssC', METRIC +'_css')\n\t\ttry {\n\t\t\tvalue = (function() {for (let i=0; i < 1000; i++) {if (matchMedia('(color:'+ i +')').matches === true) {return i}}\n\t\t\t\treturn zNA // to match\n\t\t\t})()\n\t\t\tif (runSE) {foo++} else if (runSI) {value = 4.5} else if (runSL) {value = 3}\n\t\t\tif (zNA !== cssvalue) {\n\t\t\t\t// unfortunately, servo returns all getElementProp calls with an empty string\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif (!Number.isInteger(value)) {throw ('number' == typeCheck ? zErrInvalid +'expected Integer: got '+ value: zErrType + typeCheck)}\n\t\t\t}\n\t\t\t// lies\n\t\t\tif (cssvalue !== zErr && value !== cssvalue) {isLies = true}\n\t\t} catch(e) {\n\t\t\tlog_error(14, METRIC +'_'+ metric, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\tlet notation = (zErr !== value && !isLies && 8 == value) ? rfp_green : rfp_red\n\t\tcollect_data(metric, value, notation, '', isLies)\n\t\tcollect_data(metric +'_css', '', (8 == cssvalue ? rfp_green : rfp_red), cssvalue)\n\t\treturn\n\t}\n\n\tfunction get_mm_css() {\n\t\t// https://searchfox.org/mozilla-central/source/servo/components/style/gecko/media_features.rs#660\n\t\t// only notate from when the mediaquery is enabled by default _and_ rfp is applied\n\t\tconst np = 'no-preference'\n\t\tlet oTests = {\n\t\t// expected\n\t\t\t'hover': {id: 'H', test: ['hover','none']},\n\t\t\t'any-hover': {id: 'AH', test: ['hover','none']},\n\t\t\t'prefers-reduced-motion': {id: 'PRM', test: [np,'reduce'], rfp: np, rfpver: 1}, // FF63+: 1478158\n\t\t\t'pointer': {id: 'P', test: ['fine','coarse', 'none']}, // FF64+\n\t\t\t'any-pointer': {id: 'AP', test: ['coarse','fine','none'], rfp: 'fine + fine', rfpver: 1}, // FF64+\n\t\t\t\t// ^ any-pointer: DO NOT CHANGE ORDER: this is our after value: coarse over fine: we break on first match\n\t\t\t'prefers-contrast': {id: 'PC', test: [np,'less','more','custom'], rfp: np, rfpver: 1}, // FF101+: 1656363\n\t\t\t'prefers-color-scheme': {id: 'PCS', test: ['light','dark'], rfp: 'light', rfpver: 1}, // FF67+: 1494034 | and see 1643656\n\t\t\t'forced-colors': {id: 'FC', test: ['none','active']}, // FF89+: 1659511\n\t\t\t'dynamic-range': {id: 'DR', test: ['standard','high']}, // FF100+\n\t\t\t'video-dynamic-range': {id: 'VDR', test: ['standard','high'], rfp: 'standard', rfpver: 1}, // FF100+\n\t\t\t'update': {id: 'UD', test: ['fast','slow','none']}, // FF102+: 1422312 || FYI: gecko currently only reports none or fast\n\t\t\t\t// ^ https://searchfox.org/firefox-main/source/servo/components/style/gecko/media_features.rs#366\n\t\t\t'color-gamut': {id: 'CG', test: ['rec2020','p3','srgb'], rfp: 'srgb', rfpver: 1}, // FF110+: 1422237\n\t\t\t\t// ^ https://drafts.csswg.org/mediaqueries/#color-gamut: An output device can return true for multiple values of this media\n\t\t\t\t\t// feature, if it's full output gamut is large enough, or one gamut is a subset of another supported gamut\n\t\t\t\t// ^ we break on first match: go wide to narrow (reverse to css)\n\t\t// not enabled yet\n\t\t\t'prefers-reduced-transparency': {id: 'PRT', test: [np,'reduce'], rfp: np, rfpver: 999}, // FF113+: 1736914\n\t\t\t\t// layout.css.prefers-reduced-transparency.enabled: 1822176: issue to enable it\n\t\t\t'inverted-colors': {id: 'IC', test: ['none','inverted'], rfp: 'none', rfpver: 999}, // FF114+\n\t\t\t\t// 1794628: layout.css.inverted-colors.enabled\n\t\t\t'prefers-reduced-data': {id: 'PRD', test: [np,'reduce']},\n\t\t// matchmedia only: maybe collect for completeness' sake?\n\t\t\t// these are either expected values or not implemented yet\n\t\t\t/*\n\t\t\t'environment-blending': {id: '', test: ['opaque','additive','subtractive']},\n\t\t\t'grid': {id: '', test: ['0','1']}, // always 0?\n\t\t\t'nav-controls': {id: '', test: ['none','active']},\n\t\t\t'overflow-block': {id: '', test: ['none','scroll','paged']}, // always scroll?\n\t\t\t'overflow-inline': {id: '', test: ['none','scroll']}, //  always scroll?\n\t\t\t//'scan': {id: '', test: ['progressive','interlace']}, // noone supports this\n\t\t\t'scripting': {id: '', test: ['enabled','initial-only','none']},\n\t\t\t'video-color-gamut': {id: '', test: ['srgb','p3','rec2020']},\n\t\t\t//*/\n\t\t}\n\t\t// ToDo: notation reduced-transparency | inverted-colors rfpver when feature enabled\n\n\t\tif (!isDesktop) {\n\t\t\toTests['hover']['rfp'] = 'none'; oTests['hover']['rfpver'] = 1\n\t\t\toTests['any-hover']['rfp'] = 'none'; oTests['any-hover']['rfpver'] = 1\n\t\t\toTests['pointer']['rfp'] = 'coarse'; oTests['pointer']['rfpver'] = 1\n\t\t\toTests['any-pointer']['rfp'] = 'coarse + coarse';\n\t\t}\n\n\t\tfor (const metric of Object.keys(oTests)) {\n\t\t\tlet isTest = '' == oTests[metric].id\n\t\t\tlet id = '#css'+ oTests[metric].id\n\t\t\tlet value = zNA // match css if not supported\n\t\t\tlet notation ='', cssnotation ='', aTest = oTests[metric].test\n\t\t\ttry {\n\t\t\t\tif (runSE) {foo++}\n\t\t\t\tfor (let i=0; i < aTest.length; i++) {\n\t\t\t\t\tif (window.matchMedia('('+ metric +':'+ aTest[i] +')').matches) {value = aTest[i]; break}\n\t\t\t\t}\n\t\t\t\tif (isGecko) {\n\t\t\t\t\t// can only be a valid value or zNA\n\t\t\t\t\tif (runSL) {\n\t\t\t\t\t\t// run lies just pick the non true value from tests\n\t\t\t\t\t\tif (value == aTest[1]) {value = aTest[0]} else {value = aTest[1]}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// same try catch so we don't concat errors\n\t\t\t\tif ('any-pointer' == metric) {\n\t\t\t\t\t// https://www.w3.org/TR/mediaqueries-4/#any-input\n\t\t\t\t\t// 'any-pointer, more than one of the values can match' / none = only if the others are not present\n\t\t\t\t\tlet value2 = zNA\n\t\t\t\t\taTest = ['fine','coarse','none']\n\t\t\t\t\t// ^ any-pointer: DO NOT CHANGE ORDER: this is our before value: fine over coarse: we break on first match\n\t\t\t\t\tfor (let i=0; i < aTest.length; i++) {\n\t\t\t\t\t\tif (window.matchMedia('('+ metric +':'+ aTest[i] +')').matches) {value2 = aTest[i]; break}\n\t\t\t\t\t}\n\t\t\t\t\t// value = after | value2 = before\n\t\t\t\t\tvalue = value2 + ' + '+ value\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tif(!isTest) {log_error(14, METRIC +'_'+ metric, e)}\n\t\t\t\tvalue = zErr\n\t\t\t}\n\t\t\tif (isTest) {\n\t\t\t\t//console.log(metric, value)\n\t\t\t\toTmpData[metric] = value\n\t\t\t} else {\n\t\t\t\tlet cssvalue = getElementProp(14, id, metric +'_css')\n\t\t\t\t// don't concat errors\n\t\t\t\tif ('any-pointer' == metric && cssvalue !== zErr) {\n\t\t\t\t\t// this is the 1st value - we use :before\n\t\t\t\t\tlet cssvalue2 = getElementProp(14, id, metric +'_css', ':before')\n\t\t\t\t\t// cssvalue = after | cssvalue2 = before\n\t\t\t\t\tlet joiner = ' + ' == cssvalue.slice(0,3) ? '' : ' + '\n\t\t\t\t\tcssvalue = cssvalue == zErr ? zErr : cssvalue2 + joiner + cssvalue\n\t\t\t\t}\n\t\t\t\tlet isLies = (value !== zErr && cssvalue !== zErr && value !== cssvalue)\n\t\t\t\tlet rfp = oTests[metric].rfp\n\t\t\t\tif (rfp !== undefined && isVer >= oTests[metric].rfpver) {\n\t\t\t\t\tnotation = value == rfp && !isLies ? rfp_green : rfp_red\n\t\t\t\t\tcssnotation = cssvalue == rfp ? rfp_green : rfp_red\n\t\t\t\t}\n\t\t\t\tcollect_data(metric, value, notation,'', isLies)\n\t\t\t\tcollect_data(metric +'_css', '', cssnotation, cssvalue)\n\t\t\t}\n\t\t}\n\t}\n\t// go!\n\tget_mm_color()\n\tget_mm_css()\n\n\t// sort into new object\n\tlet data = {}\n\tfor (const k of Object.keys(oTmpData).sort()) {data[k] = oTmpData[k]}\n\t// notation\n\tlet strCounts = (0 == countFail ? sg : sb) +'['+ countSuccess +'/'+ (countSuccess + countFail) +']'+ sc\n\tlet medianotation = (0 == countFail ? silent_rfp_green : silent_rfp_red)\n\t// add\n\taddBoth(14, METRIC, mini(data), addButton(14, METRIC), medianotation + strCounts, data)\n}\n\nfunction get_site_colors(METRIC) {\n\t// contrast control: only used when enabled or automatic\n\t\t// and these override site styles\n\t// background, text, visited link, unvisited link\n\t\t// see: visited link colors will/may be exposed soon: https://github.com/mozilla/standards-positions/issues/1234\n\n\t// all these are set by the site's css\n\tlet data = {}\n\ttry {\n\t\t// text, background-color\n\t\tlet target = document.body //dom.tzpBody\n\t\tlet styles = window.getComputedStyle(target)\n\t\tlet aList = ['background-color','color']\n\t\taList.forEach(function(item){\n\t\t\tdata[item] = styles.getPropertyValue(item)\n\t\t})\n\t\t// our 18 colors: adds entropy on whether the extension retains some semblance of color\n\t\t// or goes simple two-color or whatever (e.g. contrast, sepia, grayscale etc extension knobs)\n\t\tfor (let i=1; i < 19; i++) {\n\t\t\ttarget = dom['tb'+i].childNodes[2].children[0].children[0]\n\t\t\tstyles = window.getComputedStyle(target)\n\t\t\tlet suffix = (i+'').padStart(2,'0')\n\t\t\tdata['color'+ suffix] = styles.getPropertyValue('background-color')\n\t\t}\n\t\t// link, visited link\n\t\taList = [\n\t\t\t'link', // 0,0,238 blue\n\t\t\t'visited-link', // 85,26,139 purple\n\t\t]\n\t\taList.forEach(function(item){\n\t\t\ttarget = dom['tzpClr'+item]\n\t\t\tstyles = window.getComputedStyle(target)\n\t\t\tdata[item] = styles.getPropertyValue('color')\n\t\t})\n\n\t} catch (e) {\n\t\tdata = zErr\n\t\tlog_error(14, METRIC, e)\n\t}\n\t// display\n\tlet hash = mini(data), btn = addButton(14, METRIC), notation = default_red\n\tlet expected = ['e2399c6e','c2a28ecb'] // prefers light/dark\n\tif (expected.includes(hash)) {\n\t\tnotation = default_green\n\t\tsDetail[isScope][METRIC] = data\n\t\taddDisplay(14, METRIC, 'original', btn, notation)\n\t\taddData(14, METRIC, 'original')\n\t} else {\n\t\taddBoth(14, METRIC, hash, btn , notation, data)\n\t}\n\treturn\n}\n\nfunction get_site_styles(METRIC) {\n\t// NOTE: 'settings > contrast control' has no effect any of these\n\t/* we can't access rules\n\t\tSecurityError: CSSStyleSheet.rules getter: Not allowed to access cross-origin stylesheet\n\t\te.g. blink 64+ changed to match spec: https://www.w3.org/TR/cssom-1/#the-cssstylesheet-interface\n\t\tbecause of this, hashing them is super stable even if some rules change values\n\t*/\n\n\t// proxy\n\tlet metric = 'proxy', tmpArray = [], data = {}\n\tlet aList = ['CSSStyleDeclaration.removeProperty','CSSStyleDeclaration.setProperty',\n\t\t'Document.adoptedStyleSheets','Document.styleSheets','Element.attachShadow']\n\taList.forEach(function(item){\n\t\tif (isProxyLie(item)) {tmpArray.push(item)}\n\t})\n\tdata['proxy'] = tmpArray.length ? tmpArray : 'none'\n\t// styleElement\n\tmetric = 'styleElement', tmpArray = []\n\ttry {\n\t\tlet target = window.document.all\n\t\tfor (let i=2; i < 50; i++) { // start at 2 (0 and 1 are html and head)\n\t\t\tlet item = target[i], type = item+''\n\t\t\tif ('[object HTMLBodyElement]' == item) {\n\t\t\t\tbreak // stop here\n\t\t\t} else if ('[object HTMLStyleElement]' == type) {\n\t\t\t\tlet classValue = item.classList.value\n\t\t\t\tif ('' !== classValue) {tmpArray.push(classValue)}\n\t\t\t}\n\t\t}\n\t\tdata[metric] = tmpArray.length ? tmpArray : 'none'\n\t} catch(e) {\n\t\tdata[metric] = zErr\n\t\tlog_error(14, METRIC +'_'+ metric, e)\n\t}\n\t// styleSheets\n\tmetric = 'styleSheets'; tmpArray = []\n\ttry {\n\t\tlet ss = window.document.styleSheets\n\t\tdata[metric] = {'hash' : mini(ss)}\n\t\t\t// list\n\t\t\ttry {\n\t\t\t\tfor(let i = 0; i < ss.length; i++) {\n\t\t\t\t\tlet href = ss[i].ownerNode.attributes.href\n\t\t\t\t\tif (undefined == href) {href = ss[i].href +''} else {href = href.nodeValue}\n\t\t\t\t\ttmpArray.push(href)\n\t\t\t\t}\n\t\t\t\tif (!tmpArray.length) {tmpArray = 'none'}\n\t\t\t} catch(e) {\n\t\t\t\ttmpArray = zErr\n\t\t\t\tlog_error(14, METRIC +'_'+ metric +'_list', e)\n\t\t\t}\n\t\t\tdata[metric]['list'] = tmpArray\n\t} catch(e) {\n\t\tdata[metric] = zErr\n\t\tlog_error(14, METRIC +'_'+ metric, e)\n\t}\n\t// display\n\tlet hash = mini(data), btn = addButton(14, METRIC), notation = default_red\n\tlet expected = undefined !== isStylesheet ? '23bf083d' : '650f2257' // w w/out our extended window/screem range\n\tif (hash == expected) {\n\t\tnotation = default_green\n\t\tsDetail[isScope][METRIC] = data\n\t\taddDisplay(14, METRIC, 'original', btn, notation)\n\t\taddData(14, METRIC, 'original')\n\t} else {\n\t\taddBoth(14, METRIC, hash, btn , notation, data)\n\t}\n\treturn\n}\n\nconst outputCSS = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('css')) {return resolve()}\n\n\tPromise.all([\n\t\tget_colors(),\n\t\tget_media_css('media'),\n\t\tget_computed_styles('computed_styles'),\n\t\tget_link('underline_links'),\n\t\tget_site_colors('site_colors'),\n\t\tget_site_styles('site_styles'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(14)\n"
  },
  {
    "path": "js/devices.js",
    "content": "'use strict';\n\nconst get_battery = (METRIC) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getBattery\n\t// blink only (and FF43-51 which is blocked)\n\n\tfunction exit(value, data = '') {\n\t\taddBoth(7, METRIC, value,'','', data)\n\t\treturn resolve()\n\t}\n\ttry {\n\t\tlet value = navigator.getBattery\n\t\tif (runST) {value = ''}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. disabled by fork or due to sandboxing etc\n\t\t\texit(typeCheck)\n\t\t} else if ('blink' !== isEngine) {\n\t\t\t// non-blink\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else {\n\t\t\t// blink\n\t\t\tif ('function' !== typeCheck) {throw zErrType +'getBattery: '+ typeCheck}\n\t\t\tnavigator.getBattery().then((battery) => {\n\t\t\t\tlet data = {}, aTimes = []\n\t\t\t\tlet oItems = {\n\t\t\t\t\tcharging: 'boolean',\n\t\t\t\t\tchargingTime: 'Infinity', // integer seconds, 0 if full | Infinity if discharging\n\t\t\t\t\tdischargingTime: 'Infinity', // integer seconds | Infinity if charging \n\t\t\t\t\tlevel: 'number', // 0.0 to 1\n\t\t\t\t}\n\t\t\t\tfor (const k of Object.keys(oItems)) {\n\t\t\t\t\tlet x = battery[k]\n\t\t\t\t\t// type check\n\t\t\t\t\tlet typeCheck = typeFn(x), typeExpected = oItems[k]\n\t\t\t\t\tlet isTime = 'Time' == k.slice(-4), isTimeCheck = ('number' == typeCheck && isTime)\n\t\t\t\t\tif (typeCheck !== typeExpected) {if (!isTimeCheck) {throw zErrType + k +': '+ typeCheck}}\n\t\t\t\t\t// validity\n\t\t\t\t\tif (isTimeCheck) {\n\t\t\t\t\t\tif (!Number.isInteger(x) || x < 0) {throw zErrInvalid + k + ': expected a positive integer: got '+ x}\n\t\t\t\t\t} else if ('level' == k) {\n\t\t\t\t\t\tif (x < 0 || x > 1) {throw zErrInvalid + k + ': expected 0 to 1: got '+ x}\n\t\t\t\t\t}\n\t\t\t\t\tif (Infinity == x) (x += '')\n\t\t\t\t\tif (isTime) {aTimes.push(x)}\n\t\t\t\t\tdata[k] = x\n\t\t\t\t}\n\n\t\t\t\t// true, 0, Infinity, 1 == no battery or fully charged | else  == a battery (or tampering)\n\t\t\t\t// note: *Times are not reliable: change of charging state during a chrome session, we can get 2 x Infinity\n\t\t\t\t// logic: battery exists if the level is less than 1 || charging is false || 2 x Infinity: this should be enough\n\t\t\t\tlet fpvalue = 'unknown'\n\t\t\t\tif (!data.charging || data.level < 1 || 'InfinityInfinty' == aTimes.join('')) {fpvalue = true}\n\t\t\t\taddData(7, METRIC, fpvalue)\n\n\t\t\t\t// record object for clicking\n\t\t\t\tlet btn = addButton(7, METRIC +'_reported')\n\t\t\t\tsDetail[isScope][METRIC +'_reported'] = data\n\t\t\t\taddDisplay(7, METRIC, fpvalue +' '+ btn + (true == fpvalue ? '' : ' [false or 100% charged]'))\n\t\t\t\treturn resolve()\n\t\t\t}).catch(e => {\n\t\t\t\texit(e, zErrLog)\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\nfunction get_device_integer(METRIC, proxyCheck) {\n\t// https://webkit.org/b/233381 : webkit is clamped to 4 or 8\n\t\t// webkit now randomizes: https://bugzilla.mozilla.org/show_bug.cgi?id=1984333#c8\n\n\t// dom.maxHardwareConcurrency : 1958598: FF139+ 128\n\tlet value, data ='', notation = rfp_red, expected = 24\n\tlet isHWC = 'hardwareConcurrency' == METRIC\n\t// 1984333: FF143+ (backported to beta) RFP: 8 if mac else 4 | FPP 4 or 8\n\tif (isHWC) {expected = 2; if (isVer > 142 || isBB) {expected = 'mac' == isOS ? 8 : 4}} // RFP\n\ttry {\n\t\tvalue = isHWC ? navigator[METRIC] : screen[METRIC]\n\t\tif (runST) {value += ''} else if (runSL) {addProxyLie(proxyCheck + METRIC)}\n\t\tif (!Number.isInteger(value)) {throw zErrType + typeFn(value)}\n\t} catch(e) {\n\t\tvalue = e; data = isHWC ? zErrLog : zErrShort\n\t}\n\tif (value == expected) {\n\t\tnotation = rfp_green\n\t} else if (isHWC && isFPPFallback) {\n\t\t// non-BB: can fail RFP but may match FPP\n\t\t// 1984333: FF144+ FPP 4 or 8\t\n\t\tif (isVer > 142) {\n\t\t\tif (4 == value || 8 == value) {notation = fpp_green}\n\t\t}\n\t}\n\taddBoth(7, METRIC, value,'', notation, data, isProxyLie(proxyCheck + METRIC))\n\treturn\n}\n\nfunction get_device_memory(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory\n\t// blink only\n\n\tlet value, data =''\n\ttry {\n\t\tvalue = navigator.deviceMemory\n\t\tif (runST) {value += ''} else if (runSI) {value = 6}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. disabled by fork or due to sandboxing etc\n\t\t\tvalue = typeCheck\n\t\t} else if ('blink' !== isEngine) {\n\t\t\t// non-blink\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else {\n\t\t\t// blink\n\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t// https://www.w3.org/TR/device-memory/#sec-device-memory-js-api\n\t\t\t\t// \"While implementations may choose different values, the recommended upper bound\n\t\t\t\t// is 8GiB and the recommended lower bound is 0.25GiB (or 256MiB)\"\n\t\t\t// blink 147+: (approx march 2026) https://chromestatus.com/feature/6330376953921536\n\t\t\t/* Update to a new set of possible values for the Device Memory API:\n\t\t\t\t- Android: 1, 2, 4, 8\n\t\t\t\t- Others: 2, 4, 8, 16, 32\n\t\t\t\tReplacing the old values of 0.25, 0.5, 1, 2, 4, 8 which have grown outdated.\n\t\t\t*/\n\t\t\tlet aValid = [0.25, 0.5, 1, 2, 4, 8, 16, 32]\n\t\t\tif (!aValid.includes(value)) {\n\t\t\t\tthrow zErrInvalid +'expected '+ aValid.join(', ') +': got '+ value\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(7, METRIC, value,'','', data, isProxyLie('Navigator.'+ METRIC))\n\treturn\n}\n\nfunction get_device_posture(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/devicePosture\n\t// currently blink (132+) only\n\n\tlet oData = {}, aValid = ['continuous','folder']\n\tfunction get_nav(m) {\n\t\tlet value\n\t\ttry {\n\t\t\tvalue = navigator[m]\n\t\t\tif ('webkit' == isEngine && runST) {value = false} else if (runSI) {value = 'blink' == isEngine ? {} : 'folder'}\n\t\t\tlet typeCheck = typeFn(value, true)\n\t\t\tif ('undefined' == typeCheck) {\n\t\t\t\t// any engine e.g. not implemented yet or disabled by fork or due to sandboxing etc\n\t\t\t\tvalue = typeCheck\n\t\t\t} else if ('blink' !== isEngine) {\n\t\t\t\t// non-blink\n\t\t\t\tthrow zErrInvalid +'expected undefined: got '+ ('string' == typeCheck ? value : typeCheck)\n\t\t\t} else {\n\t\t\t\t// blink\n\t\t\t\tif ('object' !== typeCheck) {throw zErrType + 'devicePosture: '+ typeCheck}\n\t\t\t\tlet expected = '[object DevicePosture]'\n\t\t\t\tif (value+'' !== expected) {throw zErrInvalid + 'devicePosture expected '+ expected +': got '+ value+''}\n\t\t\t\tvalue = value.type\n\t\t\t\ttypeCheck = typeFn(value)\n\t\t\t\tif ('string' !== typeCheck) {throw zErrType + 'devicePosture.type: '+ typeCheck}\n\t\t\t\tif (!aValid.includes(value)) {\n\t\t\t\t\tthrow zErrInvalid +'expected '+ aValid.join(', ') +': got '+ value\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tvalue = zErr; log_error(7, METRIC +'_'+ m, e)\n\t\t}\n\t\toData[m] = value; addDisplay(7, METRIC +'_'+ m, value)\n\t}\n\n\tfunction get_mm(m) {\n\t\tlet cssvalue = getElementProp(7, '#cssDP', METRIC +'_'+ m +'_css')\n\t\tlet value = 'undefined'\n\t\ttry {\n\t\t\tif (runSE) {foo++}\n\t\t\tfor (let i=0; i < aValid.length; i++) {\n\t\t\t\tif (window.matchMedia('('+ m +':'+ aValid[i] +')').matches) {value = aValid[i]; break}\n\t\t\t}\n\t\t\tif ('webkit' !== isEngine && runSI) {value = 'folder'}\n\t\t\tif ('blink' !== isEngine && 'undefined' !== value) {\n\t\t\t\t// non-blink\n\t\t\t\tthrow zErrInvalid +'expected undefined: got '+ value\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tvalue = zErr; log_error(7, METRIC +'_'+ m, e)\n\t\t}\n\t\toData[m] = value; addDisplay(7, METRIC +'_'+ m, value)\n\n\t\t// css\n\t\tif (zErr !== cssvalue) {\n\t\t\tif ('webkit' !== isEngine && runSI) {cssvalue = 'folder'}\n\t\t\tif ('blink' !== isEngine && 'undefined' !== cssvalue) {\n\t\t\t\t// non-blink\n\t\t\t\tlog_error(7, METRIC +'_'+ m +'_css', zErrInvalid +'expected undefined: got '+ cssvalue)\n\t\t\t}\n\t\t}\n\t\toData[m +'_css'] = cssvalue\n\t}\n\n\t// do in alphabetival order\n\t// note: since this is non-gecko we won't cross check the values match for smarts\n\tget_mm('device-posture')\n\tget_nav('devicePosture')\n\taddData(7, METRIC, oData, mini(oData))\n\treturn\n}\n\nconst get_feature_policy = (METRIC) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/FeaturePolicy/allowsFeature\n\t// blink only but behind a pref for gecko 65+: dom.security.featurePolicy.webidl.enabled\n\n\tfunction exit(hash, data ='', btn ='') {\n\t\taddBoth(7, METRIC, hash, btn,'', data)\n\t\treturn resolve()\n\t}\n\ttry {\n\t\tlet f = document.featurePolicy\n\t\tif (runST) {f = ''} else if (runSI) {f = {}}\n\t\tlet typeCheck = typeFn(f)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. disabled by fork or due to sandboxing etc\n\t\t\texit(typeCheck)\n\t\t} else if ('webkit' == isEngine) {\n\t\t\t// webkit not supported\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else {\n\t\t\t// blink/gecko\n\t\t\tif ('empty object' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tlet expected = '[object FeaturePolicy]'\n\t\t\tif (f+'' !== expected) {throw zErrInvalid + 'expected '+ expected +': got '+ f}\n\t\t\t// enumerate: array\n\t\t\tlet aList = f.features()\n\t\t\t// gecko: disabling geo or blocking geo requests or both doesn't remove geolocation\n\t\t\t\t// so the assumption is these have no effect and we should always have a populated array\n\t\t\ttypeCheck = typeFn(aList)\n\t\t\tif ('array' !== typeCheck) {throw zErrType +'features: ' + typeCheck}\n\n\t\t\t// get properties: maintain order\n\t\t\tlet firstItem = aList[0]\n\t\t\tlet data = {'allowedFeatures': [],'false': [], 'true': []}\n\t\t\taList.forEach(function(item){\n\t\t\t\tlet isFirst = item == firstItem\n\t\t\t\tlet key = f.allowsFeature(item)\n\t\t\t\tif (isFirst) {\n\t\t\t\t\t//key = 'banana'\n\t\t\t\t\ttypeCheck = typeFn(key)\n\t\t\t\t\tif ('boolean' !== typeCheck) {throw zErrType +' allowsFeature: '+ typeCheck}\n\t\t\t\t}\n\t\t\t\tdata[key].push(item)\n\t\t\t})\n\t\t\t// should be redundant: allowedFeatures should match data['true']\n\t\t\tlet aAllowed = []\n\t\t\ttry {\n\t\t\t\taAllowed = f.allowedFeatures()\n\t\t\t\t//aAllowed = ''\n\t\t\t\ttypeCheck = typeFn(aAllowed)\n\t\t\t\tif ('array' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t// only add if this differs\n\t\t\t\tlet trueHash = mini(data['true'])\n\t\t\t\tif (trueHash == mini(aAllowed)) {delete data.allowedFeatures} else {data.allowedFeatures = aAllowed}\n\t\t\t} catch(e) {\n\t\t\t\tdata.allowedFeatures = zErr\n\t\t\t\tlog_error(7, METRIC +'_allowedFeatures', e)\n\t\t\t}\n\n\t\t\tlet hash = mini(data), btn = addButton(7, METRIC)\n\t\t\texit(hash, data, btn)\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\n\nconst get_keyboard = (METRIC) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API\n\t// blink only\n\n\t// https://wicg.github.io/keyboard-map/\n\t// https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system\n\tfunction exit(hash, data='', btn ='') {\n\t\taddBoth(7, METRIC, hash, btn,'', data)\n\t\treturn resolve()\n\t}\n\ttry {\n\t\tlet k = navigator.keyboard\n\t\tif (runSI) {k = []}\n\t\tlet typeCheck = typeFn(k)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. disabled by fork or due to sandboxing etc\n\t\t\texit(typeCheck)\n\t\t} else if ('blink' !== isEngine) {\n\t\t\t// non-blink\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else if ('object' !== typeFn(k, true)) {\n\t\t\tthrow zErrType + typeCheck\n\t\t} else {\n\t\t\t// blink\n\t\t\tlet expected = '[object Keyboard]'\n\t\t\tif (k+'' !== expected) {\n\t\t\t\tthrow zErrInvalid + 'expected '+ expected +': got '+ (typeCheck.includes('object') ? k : typeCheck)\n\t\t\t}\n\t\t\tlet aKeys = [\n\t\t\t\t'Backquote','Backslash','Backspace','BracketLeft','BracketRight','Comma','Digit0',\n\t\t\t\t'Digit1','Digit2','Digit3','Digit4','Digit5','Digit6','Digit7','Digit8','Digit9',\n\t\t\t\t'Equal','IntlBackslash','IntlRo','IntlYen','KeyA','KeyB','KeyC','KeyD','KeyE','KeyF',\n\t\t\t\t'KeyG','KeyH','KeyI','KeyJ','KeyK','KeyL','KeyM','KeyN','KeyO','KeyP','KeyQ','KeyR',\n\t\t\t\t'KeyS','KeyT','KeyU','KeyV','KeyW','KeyX','KeyY','KeyZ','Minus','Period','Quote',\n\t\t\t\t'Semicolon','Slash'\n\t\t\t]\n\t\t\tk.getLayoutMap().then(keyboardLayoutMap => {\n\t\t\t\t// check size\n\t\t\t\tif (keyboardLayoutMap.size > 0) {\n\t\t\t\t\tlet data = {}\n\t\t\t\t\taKeys.forEach(function(key) {data[key] = keyboardLayoutMap.get(key)})\n\t\t\t\t\texit(mini(data), data, addButton(7, METRIC))\n\t\t\t\t} else {\n\t\t\t\t\t// e.g. vivalid : is it this? https://wicg.github.io/keyboard-map/#permissions-policy\n\t\t\t\t\texit('keyboardLayoutMap.size: 0')\n\t\t\t\t}\n\t\t\t}).catch(function(err){\n\t\t\t\texit(err, zErrLog)\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\nfunction get_media_constraints(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints\n\t// I doubt this adds any entropy, it's equivalency of engine/version changes\n\t// but collect it anyway as yet one more piece of the browser object model\n\tlet hash, data ='', btn=''\n\ttry {\n\t\tlet m = navigator.mediaDevices\n\t\tif (undefined !== m) {\n\t\t\tdata = []\n\t\t\tconst s = m.getSupportedConstraints()\n\t\t\tfor (const c of Object.keys(s)) {data.push(c)}\n\t\t\tif (!data.length) {throw zErrInvalid +'none'}\n\t\t\thash = mini(data); btn = addButton(7, METRIC)\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(7, METRIC, hash, btn,'', data)\n}\n\nconst get_media_devices = (METRIC) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\n\tfunction set_notation(value ='') {\n\t\t// 1528042: FF115+ media.devices.enumerate.legacy.enabled\n\t\tlet notation =''\n\t\tif (isTB) {\n\t\t\tnotation = 'undefined' == value ? bb_green : bb_red\n\t\t} else {\n\t\t\tnotation = '75e77887' == value ? rfp_green : rfp_red\n\t\t}\n\t\treturn notation\n\t}\n\n\tfunction analyse(devices) {\n\t\tlet hash ='none', btn ='', data =''\n\t\ttry {\n\t\t\tif (runST) {devices = undefined} else if (runSI) {devices = [{}]}\n\t\t\tlet typeCheck = typeFn(devices, true)\n\t\t\tif ('array' !== typeCheck) {throw zErrType + typeFn(devices)}\n\t\t\tif (devices.length > 0) {\n\t\t\t\t// tampered\n\t\t\t\tlet aSplit = (devices +'').split(',')\n\t\t\t\tlet aValid = ['[object InputDeviceInfo]','[object MediaDeviceInfo]']\n\t\t\t\tfor (let i=0; i < aSplit.length; i++) {\n\t\t\t\t\tif (!aValid.includes(aSplit[i])) {throw zErrInvalid +'expected '+ aValid.join(', ') +': got '+ aSplit[i]}\n\t\t\t\t}\n\t\t\t\t// enumerate\n\t\t\t\t\t// don't combine kind, keep order, record length not strings\n\t\t\t\t\t// checking length of undefined (fake) will catch an error\n\t\t\t\tdata = {}\n\t\t\t\tlet sLen = new Set(), index = 0\n\t\t\t\tdevices.forEach(function(d) {\n\t\t\t\t\tlet kind = d.kind, kindtest = kind.length,\n\t\t\t\t\t\tdLen = d.deviceId.length,\n\t\t\t\t\t\tgLen = d.groupId.length,\n\t\t\t\t\t\tindexKey = (index+'').padStart(2,'0')\n\t\t\t\t\tdata[indexKey +'-'+ kind] = [dLen, gLen, d.label.length]\n\t\t\t\t\tsLen.add(dLen)\n\t\t\t\t\tsLen.add(gLen)\n\t\t\t\t\tindex ++\n\t\t\t\t\t// we could check valid lengths (0 or 44 in 115+: labels always 0)\n\t\t\t\t\t\t// and if 44 is valid then the last char is '=', and we could type check\n\t\t\t\t})\n\t\t\t\thash = mini(data); btn = addButton(7, METRIC, data.length)\n\t\t\t}\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t\taddBoth(7, METRIC, hash, btn, set_notation(hash), data, isProxyLie('MediaDevices.enumerateDevices'))\n\t\tlog_perf(7, METRIC, t0)\n\t\treturn resolve()\n\t}\n\n\tif (undefined == navigator.mediaDevices) {\n\t\taddBoth(7, METRIC, 'undefined','', set_notation('undefined'))\n\t\treturn resolve()\n\t}\n\n\tif (gLoad && isDevices !== undefined) {\n\t\tanalyse(isDevices) // warmup success\n\t} else {\n\t\ttry {\n\t\t\tlet timer = 2000\n\t\t\tif (runSG) {timer = 0} // timed out\n\t\t\t// await devices\n\t\t\tpromiseRaceFulfilled({\n\t\t\t\tpromise: navigator.mediaDevices.enumerateDevices(),\n\t\t\t\tresponseType: Array,\n\t\t\t\tlimit: timer // increase race limit for slow system/networks\n\t\t\t}).then(function(devices) {\n\t\t\t\tif (!devices) {\n\t\t\t\t\taddBoth(7, METRIC, zErrTime,'', set_notation(zErrTime), zErrLog) // promise failed\n\t\t\t\t\treturn resolve()\n\t\t\t\t} else {\n\t\t\t\t\tanalyse(devices)\n\t\t\t\t}\n\t\t\t})\n\t\t} catch(e) {\n\t\t\taddBoth(7, METRIC, e,'', set_notation(e+''), zErrLog)\n\t\t\treturn resolve()\n\t\t}\n\t}\n})\n\nfunction get_memory(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory\n\t// blink only and deprecated\n\n\t// super unstable in this form: just display for now\n\tfunction exit(hash, data ='', btn ='') {\n\t\tsDetail[isScope][METRIC] = data\n\t\taddDisplay(7, METRIC, hash, btn)\n\t\t//addBoth(7, METRIC, hash, btn,'', data)\n\t\treturn\n\t}\n\ttry {\n\t\tlet k = performance.memory\n\t\tif (runSI) {k = [1]}\n\t\tlet typeCheck = typeFn(k)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. removed by blink (deprecated) or disabled by fork or due to sandboxing etc\n\t\t\texit(typeCheck)\n\t\t} else if ('blink' !== isEngine) {\n\t\t\t// non-blink\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else {\n\t\t\t// blink\n\t\t\tlet expected = '[object MemoryInfo]', data = {}\n\t\t\tif (k+'' !== expected) {\n\t\t\t\tthrow zErrInvalid + 'expected '+ expected +': got '+ (typeCheck.includes('object') ? k : typeCheck)\n\t\t\t}\n\t\t\tlet aKeys = ['jsHeapSizeLimit','totalJSHeapSize','usedJSHeapSize']\n\t\t\taKeys.forEach(function(m){\n\t\t\t\tlet value, check\n\t\t\t\ttry {\n\t\t\t\t\tvalue = k[m]\n\t\t\t\t\tif (runSI) {value = null}\n\t\t\t\t\tlet check = typeFn(value)\n\t\t\t\t\tif ('number' !== check) {throw zErrType + check}\n\t\t\t\t} catch(e) {\n\t\t\t\t\tvalue = zErr; log_error(7, METRIC +'_'+ m, e)\n\t\t\t\t}\n\t\t\t\tdata[m] = value\n\t\t\t})\n\t\t\texit(mini(data), data, addButton(7, METRIC))\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n}\n\nconst get_permissions = (METRIC) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API#permission-aware_apis\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy#directives\n\tlet tmpData = {}, data = {}, count = 0\n\tlet aList = [\n\t\t// gecko\n\t\t'camera','geolocation','microphone','midi','midi_sysex','notifications',\n\t\t'persistent-storage','push','screen-wake-lock',\n\t\t// non-gecko\n\t\t'accelerometer','ambient-light-sensor','background-fetch','background-sync','clipboard-read',\n\t\t'clipboard-write','compute-pressure','gyroscope','local-fonts','magnetometer','payment-handler',\n\t\t'storage-access','top-level-storage-access','window-management',\n\t\t// not listed on mdn: but confirmed in blink as a non error\n\t\t'display-capture','nfc',\n\t\t// other\n\t\t//'accessibility-events', // \n\t\t'bluetooth','device-info','gamepad','speaker','speaker-selection',\n\t]\n\taList.sort()\n\tfor (let i=0; i < aList.length; i++) {\n\t\tlet k = aList[i], key = k\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus\n\t\t// spec: https://w3c.github.io/permissions/#permissions\n\t\tlet aValid = ['denied','granted','prompt']\n\t\tfunction accrue(k, value) {\n\t\t\tcount++\n\t\t\tif (undefined ==tmpData[value]) {tmpData[value] = [k]} else {tmpData[value].push(k)}\n\t\t\tif (count == (aList.length)) {exit()}\n\t\t}\n\t\ttry {\n\t\t\tif (runSE) {foo++}\n\t\t\tlet isSysex = k.includes('sysex')\n\t\t\tif (isSysex) {key = 'midi'}\n\t\t\tnavigator.permissions.query({name: key, sysex: isSysex}).then(function(r) {\n\t\t\t\tlet state = r.state\n\t\t\t\tif (runST) {state = undefined} else if (runSI) {state = 'allowed'}\n\t\t\t\t// checks\n\t\t\t\tlet typeCheck = typeFn(state)\n\t\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (!aValid.includes(state)) {throw zErrInvalid +'expected '+ aValid.join(', ') +': got '+ state}\n\t\t\t\taccrue(k, state)\n\t\t\t}).catch(err => {\n\t\t\t\t// only log non-standard gecko\n\t\t\t\tif (isGecko) {\n\t\t\t\t\tlet expected = 'TypeError: \\''+ k +'\\' (value of \\'name\\' member of '\n\t\t\t\t\t\t+ 'PermissionDescriptor) is not a valid value for enumeration PermissionName.'\n\t\t\t\t\tif (err+'' !== expected) {log_error(7, METRIC +'_'+ k, err)}\n\t\t\t\t}\n\t\t\t\taccrue(k, zErr)\n\t\t\t})\n\t\t} catch(e) {\n\t\t\tif (isGecko) {log_error(7, METRIC +'_'+ k, e)}\n\t\t\taccrue(k, zErr)\n\t\t}\n\t}\n\tfunction exit() {\n\t\t// sort object: sort arrays so permission delays don't create disorder\n\t\tfor (const k of Object.keys(tmpData).sort()) {data[k] = tmpData[k].sort()}\n\t\tlet hash = mini(data)\n\t\tlet notation = '88b7fbf8' == hash ? default_green : default_red\n\t\t// record\n\t\taddBoth(7, METRIC, hash, addButton(7, METRIC), notation, data)\n\t\treturn resolve()\n\t}\n})\n\nfunction get_screen_isextended(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Screen/isExtended\n\t// currently blink (100+) only\n\n\tlet value, data =''\n\ttry {\n\t\tvalue = screen.isExtended\n\t\tif (runST) {value = 'true'}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('undefined' == typeCheck) {\n\t\t\t// any engine e.g. disabled by fork or due to sandboxing etc\n\t\t\tvalue = typeCheck\n\t\t} else if ('blink' !== isEngine) {\n\t\t\t// non-blink\n\t\t\tthrow zErrInvalid +'expected undefined: got '+ typeCheck\n\t\t} else {\n\t\t\t// blink\n\t\t\tif ('boolean' !== typeCheck && 'undefined' !== typeCheck) {throw zErrType + typeCheck}\n\t\t}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(7, METRIC, value,'','', data)\n\treturn\n}\n\nfunction get_touc_h(METRIC) {\n\t// note: function name avoids \"ouch\" to avoid being picked up in window properties\n\t// the element keys and window properties are redundant but required for health/benign value checks\n\t// dom.w3c_touch_events.enabled: 0=disabled (macOS) 1=enabled 2=autodetect (linux/win/android)\n\n\tfunction get_maxTouchPoints(m) {\n\t\t// https://www.w3.org/TR/pointerevents/#extensions-to-the-navigator-interface\n\t\t// FF64+: RFP 1363508\n\t\tlet value\n\t\ttry {\n\t\t\tvalue = navigator[m]\n\t\t\tif (runST) {value = undefined} else if (runSI) {value = -5} else if (runSL) {addProxyLie('Navigator.'+ m)}\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (!Number.isInteger(value) || value < 0) {throw zErrInvalid + 'expected +Integer: got '+ value}\n\t\t\tif (isProxyLie('Navigator.'+ m)) {\n\t\t\t\tlog_known(7, METRIC +'_'+ m, value)\n\t\t\t\tvalue = zLIE\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(7, METRIC +'_'+ m, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\tdata[m] = value\n\t}\n\n\tfunction get_elements_touch() {\n\t\t// gecko: ontouch* only exists in android: desktop blocks these to avoid being identified as mobile\n\t\t\t// and onkly android has createTouch and createTouchList in Document\n\t\t\t// ~0.06ms\n\t\tlet eList = ['Document','HTMLElement','MathMLElement','SVGElement']\n\t\teList.forEach(function(m){\n\t\t\tlet value\n\t\t\ttry {\n\t\t\t\tif (runSE) {foo++}\n\t\t\t\tlet target = window[m]\n\t\t\t\tlet typeCheck = typeFn(target)\n\t\t\t\tif (runST) {typeCheck = undefined}\n\t\t\t\tif ('function' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tlet props = Object.getOwnPropertyNames(target.prototype)\n\t\t\t\tvalue = props.filter(x => x.includes('ouch'))\n\t\t\t\tvalue.sort() // we already capture order in window function properties\n\t\t\t\tif (0 == value.length) {value = 'none'}\n\t\t\t\tif (isGecko) {\n\t\t\t\t\t// gecko: ontouch* only exists in android: desktop blocks these to avoid being identified as mobile\n\t\t\t\t\tlet got = 'none' == value ? value : value.join(', ')\n\t\t\t\t\tif (!isDesktop) {\n\t\t\t\t\t\t// android\n\t\t\t\t\t\tlet expected = ['ontouchcancel','ontouchend','ontouchmove','ontouchstart']\n\t\t\t\t\t\tif ('Document' == m) {expected.push('createTouch','createTouchList'); expected.sort()}\n\t\t\t\t\t\tlet minihash = mini(value), miniexpected = mini(expected)\n\t\t\t\t\t\tif (minihash !== miniexpected) {\n\t\t\t\t\t\t\tthrow zErrInvalid +'expected '+ expected.join(', ') +': got '+ got\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ('none' !== value) {\n\t\t\t\t\t\t// desktop\n\t\t\t\t\t\tthrow zErrInvalid +'expected none: got '+ got\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(7, METRIC +'_'+ m, e)\n\t\t\t\tvalue = zErr\n\t\t\t}\n\t\t\tdata[m] = value\n\t\t})\n\t}\n\n\tfunction get_element_touch(m) {\n\t\t// domparser: 0.12ms | dom: 0.08 | just use domparser\n\t\tlet value = []\n\t\ttry {\n\t\t\tlet parser = new DOMParser\n\t\t\tlet doc = parser.parseFromString('<a>', \"text/html\")\n\t\t\tlet target = doc.body.firstChild\n\t\t\t//let target = dom.tzpDiv // dom test\n\t\t\tfor (const key in target) {if (key.includes('ouch')) {value.push(key)}}\n\t\t\tvalue.sort() // we already capture order in window properties\n\t\t\tif (0 == value.length) {value = 'none'}\n\t\t\tif (isGecko) {\n\t\t\t\tlet got = 'none' == value ? value : value.join(', ')\n\t\t\t\tif (!isDesktop) {\n\t\t\t\t\t// android\n\t\t\t\t\tif ('30ea93d7' !== mini(value)) {\n\t\t\t\t\t\tlet expected = ['ontouchcancel','ontouchend','ontouchmove','ontouchstart'] // ordered\n\t\t\t\t\t\tthrow zErrInvalid +'expected '+ expected.join(', ') +': got '+ got\n\t\t\t\t\t}\n\t\t\t\t} else if ('none' !== value) {\n\t\t\t\t\t// desktop\n\t\t\t\t\tthrow zErrInvalid +'expected none: got '+ got\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(7, METRIC +'_'+ m, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\tdata[m] = value\n\t}\n\n\tfunction get_window_touch(m) {\n\t\t// 0.4ms window | 1.2ms iframe\n\t\tlet value\n\t\ttry {\n\t\t\tlet props = Object.getOwnPropertyNames(window)\n\t\t\tvalue = props.filter(x => x.includes('ouch'))\n\t\t\tvalue.sort() // we already capture order in window properties\n\t\t\tif (0 == value.length) {value = 'none'}\n\t\t\tif (isGecko) {\n\t\t\t\tlet expected, got = 'none' == value ? value : value.join(', ')\n\t\t\t\tif ('mac' == isOS) {\n\t\t\t\t\t// mac doesn't have touch\n\t\t\t\t\tif ('none' !== value) {throw zErrInvalid +'expected none: got '+ got}\n\t\t\t\t} else if (!isDesktop) {\n\t\t\t\t\t// android\n\t\t\t\t\tif ('62482a70' !== mini(value)) {\n\t\t\t\t\t\texpected = ['Touch','TouchEvent','TouchList','ontouchcancel','ontouchend','ontouchmove','ontouchstart'] // ordered\n\t\t\t\t\t\tthrow zErrInvalid +'expected '+ expected.join(', ') +': got '+ got\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// windows/linux: none or ['Touch','TouchEvent','TouchList']\n\t\t\t\t\tif ('none' !== value && 'a8d0e340' !== mini(value)) {\n\t\t\t\t\t\texpected = ['Touch','TouchEvent','TouchList']\n\t\t\t\t\t\tthrow zErrInvalid +'expected none or '+ expected.join(', ') +': got '+ got\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(7, METRIC +'_'+ m, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\tdata[m] = value\n\t}\n\n\tlet data = {'Document': '', 'HTMLElement': '','MathMLElement': '','SVGElement': '','maxTouchPoints': '','window': ''} // pre-ordered\n\tlet notation = ''\n\t//get_element_touch('element') // skip this for now since we have HTMLElement (+ Mathml + SVG)\n\tget_elements_touch()\n\tget_maxTouchPoints('maxTouchPoints')\n\tget_window_touch('window')\n\n\tlet hash = mini(data), btn = addButton(7, METRIC)\n\tlet hash0 ='727b0fac', hash1 ='0eb47178', hash5 ='f492a7f4', hash10 ='5091c020', hashA5 = 'c51b1822'\n\n\t/*\n\t\t// desktop: note: if maxTouchPoints == 0 windows = 'none'\n\t\t{\n\t\t\t\"Document\": 'none',\n\t\t\t\"HTMLElement\": 'none',\n\t\t\t\"MathMLElement\": 'none',\n\t\t\t\"SVGElement\": 'none',\n\t\t\t\"maxTouchPoints\": 5,\n\t\t\t\"window\": ['Touch','TouchEvent','TouchList']\n\t\t}\n\t\t//android + 5\n\t\t{\n\t\t\t\"Document\": ['createTouch','createTouchList','ontouchcancel','ontouchend','ontouchmove','ontouchstart'],\n\t\t\t\"HTMLElement\": ['ontouchcancel','ontouchend','ontouchmove','ontouchstart'],\n\t\t\t\"MathMLElement\": ['ontouchcancel','ontouchend','ontouchmove','ontouchstart'],\n\t\t\t\"SVGElement\": ['ontouchcancel','ontouchend','ontouchmove','ontouchstart'],\n\t\t\t\"maxTouchPoints\": 5,\n\t\t\t\"window\": ['Touch','TouchEvent','TouchList','ontouchcancel','ontouchend','ontouchmove','ontouchstart']\n\t\t}\n\t*/\n\n\t// RFP\n\t\t// 1957658: FF143+, ESR140.2: 5 android, 10 windows, 0 mac and linux\n\t\t// 1991701: FF146+ (and BB15): Re-enable touch on Linux (and remove RFPTarget::PointerId)\n\t\t// 2021715: FF150+ 5 on linux\n\tlet rfpHashes = {\n\t\t'android': [hashA5],\n\t\t'linux': [hash5], // FF150+\n\t\t'mac': [hash0],\n\t\t'windows': [hash10],\n\t}\n\tif (isVer < 150) {rfpHashes.linux = '553ce3d9'} // maxTouchPoints = 0\n\tnotation = (undefined !== isOS && rfpHashes[isOS].includes(hash) ? rfp_green : rfp_red)\n\n\t// non-BB: fails RFP but may match FPP\n\tif (isFPPFallback && undefined !== isOS && notation == rfp_red) {\n\t\t// FPP: 1977836 FF142: 0 or 1, everything else as 5 | 1978414: ship touch points\n\t\tlet fppHashes = {\n\t\t\t'android': [hashA5],\n\t\t\t'mac': [hash0],\n\t\t\t'linux': [hash0],\n\t\t\t'windows': [hash0, hash1, hash5],\n\t\t}\n\t\tif (isVer > 149) {fppHashes.linux.push(hash5)} // FF150+: 2020170: linux wayland can now report 5\n\t\tif (fppHashes[isOS].includes(hash)) {notation = fpp_green}\n\t}\n\n\taddBoth(7, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_viewport_segments(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries\n\t// CSS level 5\n\n\tlet data = {}, display = {}, aList = ['horizontal','vertical']\n\taList.forEach(function(m) {\n\t\tlet value = zNA\n\t\ttry {\n\t\t\tfor (let i = 1; i < 6; i++) { // css only goes to 5\n\t\t\t\tif (window.matchMedia('('+ m +'-viewport-segments:'+ i +')').matches) {value = i; break}\n\t\t\t}\n\t\t\tif (runSE) {foo++} else if (runSL) {value = 6}\n\t\t} catch(e) {\n\t\t\tvalue = zErr; log_error(7, METRIC +'_'+ m, e)\n\t\t}\n\t\tlet pseudo = 'horizontal' == m ? ':before' : ':after'\n\t\tlet cssvalue = getElementProp(7, '#cssVS', METRIC +'_'+ m +'_css', pseudo)\n\t\tdisplay[m] = value\n\t\tif (isSmart) {\n\t\t\tif (cssvalue !== zErr && value !== zErr) {\n\t\t\t\tif (value !== cssvalue) {\n\t\t\t\t\tdisplay[m] = log_known(7, METRIC +'_'+ m, value) // record and color up lies\n\t\t\t\t\tvalue = zLIE\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdata[m] = value\n\t\tdata[m +'_css'] = cssvalue\n\t})\n\taddDisplay(7, METRIC, display.horizontal +' x '+ display.vertical)\n\taddData(7, METRIC, data, mini(data))\n\treturn\n}\n\nconst outputDevices = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('devices')) {return resolve()}\n\n\taddBoth(7, 'recursion', isRecursion[0],'','', isRecursion[1])\n\tPromise.all([\n\t\tget_media_devices('mediaDevices'),\n\t\tget_media_constraints('mediaDevices_constraints'),\n\t\tget_touc_h('touch'),\n\t\tget_device_integer('pixelDepth','Screen.'),\n\t\tget_device_integer('colorDepth','Screen.'),\n\t\tget_device_integer('hardwareConcurrency','Navigator.'),\n\t\tget_permissions('permissions'),\n\t\tget_feature_policy('featurePolicy'), // blink only | gecko behind a pref since FF65\n\t\tget_viewport_segments('viewport-segments'),\n\t\t// blink only\n\t\tget_battery('battery'),\n\t\tget_device_memory('deviceMemory'),\n\t\tget_device_posture('devicePosture'),\n\t\tget_keyboard('keyboard'),\n\t\tget_memory('memory'),\n\t\tget_screen_isextended('screen_isextended'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(7)\n"
  },
  {
    "path": "js/elements.js",
    "content": "'use strict';\n\n// element results always in this order: width, height, x, y\n/*\n it is up to the fingerprinter to ensure custom/website css doesn't influence\n measurements. TZP uses careful site css rules and revert as a PoC - more than\n enough to ensure defaults, but trying to mitigate all possible css rules is\n prohibitive. Perhaps one method could be to create and use an iframe on demand\n*/\n\nfunction get_domrect(METRIC) {\n\t// quick exits\n\tlet hash, data = {}\n\tif (!isGecko) {hash = zNA} else if ('9e6f19c5' == mini(oDomRect)) {hash = 'trustworthy'}\n\tif (undefined !== hash) {\n\t\taddBoth(15, METRIC, hash)\n\t\treturn\n\t}\n\n\tlet control = {\n\t\tbottom: 120.69999694824219,\n\t\theight: 141.41665649414062,\n\t\tleft: -20.716659545898438,\n\t\tright: 120.69999694824219,\n\t\ttop: -20.716659545898438,\n\t\twidth: 141.41665649414062,\n\t\tx: -20.716659545898438,\n\t\ty: -20.716659545898438\n\t}\n\n\t// for each method per key in oDomRect we return either\n\t// error, trustworthy, or some FPing on the diffs\n\t// note: errors are already recorded\n\tsDetail[isScope][METRIC +'_data'] = {}\n\tlet tmpdata = {}\n\tlet countPass = 0\n\tfor (const k of Object.keys(oDomRect).sort()) {\n\t\tsDetail[isScope][METRIC +'_data'][k] = oDomRect[k]\n\t\tlet value =''\n\t\tif (zErr == k) {value = zErr\n\t\t} else if ('642e7ef0' == k) {value = 'trustworthy'; countPass = oDomRect[k]['methods'].length\n\t\t} else {\n\t\t\tvalue = zLIE\n\t\t\t// analyse noise\n\t\t\tlet oDiffs = {}, aProps = [], max = 0\n\t\t\tlet isNegative = false, isPositive = false\n\t\t\tlet test = oDomRect[k]['data']\n\t\t\tfor (const p of Object.keys(test)) {\n\t\t\t\tlet diff = control[p] - test[p]\n\t\t\t\tif (diff > 0) {isPositive = true} else {isNegative = true}\n\t\t\t\tif (Math.abs(diff) > max) {max = Math.abs(diff)}\n\t\t\t\tif (0 !== diff) {\n\t\t\t\t\taProps.push(p)\n\t\t\t\t\tif (undefined == oDiffs[diff]) {oDiffs[diff] = [p]} else {oDiffs[diff].push(p)}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet multiples = []\n\t\t\tfor (const m of Object.keys(oDiffs)) {\n\t\t\t\tif (oDiffs[m].length > 1) {multiples.push(oDiffs[m].join(' + '))}\n\t\t\t}\n\t\t\t//console.log(k, oDiffs, multiples, max)\n\t\t\t// sign: chamelon seems to always be -, CB seems to always be ±\n\t\t\tlet sign =''\n\t\t\tif (isNegative && isPositive) {sign = '±'} else {\n\t\t\t\tsign = isNegative ? '-' : '+'\n\t\t\t}\n\t\t\tif (max > 0.1) {max = '> '+ sign +'0.1'\n\t\t\t} else {\n\t\t\t\t// note max is always positive\n\t\t\t\tvar z = -Math.floor(Math.log10(max) + 1) // leading zeros\n\t\t\t\t// cap at 5: chameleon varies from 6 to 9 in a few tests\n\t\t\t\tz = z > 5 ? 5 : z\n\t\t\t\tmax = '< '+ sign +'0.' + '0'.repeat(z-1) + '1'\n\t\t\t}\n\t\t\tvalue = {\n\t\t\t\t'properties': aProps.length == 8 ? 'all' : aProps.join(', '),\n\t\t\t\t'range': max,\n\t\t\t\t'same': (multiples.length ? multiples : 'none'),\n\t\t\t\t'total': Object.keys(oDiffs).length\n\t\t\t}\n\t\t}\n\t\toDomRect[k].methods.forEach(function(method){tmpdata[method] = value})\n\t}\n\tlet btnData = addButton(15, METRIC +'_data', 'data')\n\tfor (const k of Object.keys(tmpdata).sort()) {data[k] = tmpdata[k]}\n\thash = mini(data)\n\tlet btn = addButton(15, METRIC, countPass +'/4')\n\taddBoth(15, METRIC, hash, btn + btnData, default_red, data)\n\treturn\n}\n\nfunction get_element_keys(METRIC) {\n\tconst id = 'element-key'\n\tlet hash, btn ='', data = [], notation = isBBESR ? bb_red : '', isLies = false\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tconst element = document.createElement('a')\n\t\telement.setAttribute('id', id)\n\t\tdocument.body.appendChild(element)\n\t\tlet htmlElement = dom[id]\n\t\tfor (const key in htmlElement) {data.push(key)}\n\t\thash = mini(data); btn = addButton(15, METRIC, data.length)\n\t\t// cydec: changes order, removes some keys\n\t\t\t// ToDo: use post constructor when we enumerate all elements\n\t\tconst aExpected = ['scrollWidth','scrollHeight','clientWidth','clientHeight']\n\t\tif ((data.reduce((a, c) => a + aExpected.includes(c), 0)) < aExpected.length) {isLies = true}\n\t\t// health: BB only if ESR\n\t\tif (isBBESR) {\n\t\t\t// 40f682b2: 352 standard\n\t\t\t// 5e5ae1c9: 365 safer (including webgl click-to-play)\n\t\t\t// the 13 items diff are all NS tampering\n\t\t\tif ('40f682b2' == hash || '5e5ae1c9' == hash) {notation = bb_green}\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(15, METRIC, hash, btn, notation, data, isLies)\n\treturn\n}\n\nfunction get_element_font(METRIC, isLies) {\n\tlet t0 = nowFn()\n\t// we only need a few pt values: more than enough to correlate styles\n\t// but go more in-depth with mono/serif/sans\n\t// keep in increasing size order\n\tlet sizeA = ['3.9pt','141.7pt','266.6pt',]\n\tlet sizeB = ['3.9pt','xx-small','x-small','small','medium','large','x-large','xx-large','xxx-large','141.7pt','266.6pt']\n\tlet oList = {\n\t\t// keep in sorted order\n\t\t// https://developer.mozilla.org/en-US/docs/Web/CSS/generic-family\n\t\t'cursive': sizeA,\n\t\t'emoji': sizeA, // windows: emoji = serif\n\t\t'fangsong': sizeA,\n\t\t'fantasy': sizeA, // windows: fantasy = sans\n\t\t'math': sizeA,\n\t\t'monospace': sizeB,\n\t\t'sans-serif': sizeB,\n\t\t'serif': sizeB,\n\t\t'system-ui': sizeA,\n\t}\n\t//ToDo: each is only 3 extra tests: seem redundant\n\t\t// windows: they all = serif\n\t/*\n\t'ui-monospace': sizeA,\n\t'ui-rounded': sizeA,\n\t'ui-serif': sizeA,\n\t'ui-sans-serif': sizeA,\n\t//*/\n\n\tconst id = 'element-fp'\n\tlet hash, btn ='', data = {}, method\n\ttry {\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\t\tlet oData = {}, tmpobj = {}\n\t\tfor (const k of Object.keys(oList)) {\n\t\t\tlet sizes = oList[k]\n\t\t\tlet tmpsizes = [], isFirst = 'cursive' == k // this is a bit iffy if we change our keys: do BETTER!!\n\t\t\tsizes.forEach(function(size) {\n\t\t\t\tlet isTypeCheck = isFirst && size == sizes[0]\n\t\t\t\t// create + measure each individually as preceeding elements can affect subsequent ones\n\t\t\t\tdom[id].innerHTML = \"<div style='font-size:\"+ size +\";' class='\"+ k +\"'>...</div>\"\n\t\t\t\tlet target = div.firstChild\n\t\t\t\tmethod = measureFn(target, METRIC)\n\t\t\t\t// width+height = max entropy AFAICT but lets add x and y becuz we can\n\t\t\t\tif (isTypeCheck) {\n\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\t[method.width, method.height, method.x, method.y].forEach(function(item) {\n\t\t\t\t\t\tif (runST) {item = isLine ? undefined : '1'}\n\t\t\t\t\t\tlet typeCheck = typeFn(item)\n\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\ttmpsizes.push([size, method.width, method.height, method.x, method.y])\n\t\t\t})\n\t\t\tlet sizehash = mini(tmpsizes)\n\t\t\tif (oData[sizehash] == undefined) {oData[sizehash] = {data: tmpsizes, group: [k]}\n\t\t\t} else {oData[sizehash].group.push(k)}\n\t\t}\n\t\t// group by styles\n\t\tfor (const k of Object.keys(oData)){data[oData[k].group.join(' ')] = oData[k].data}\n\t\tlet count = Object.keys(data).length\n\t\thash = mini(data); btn = addButton(15, METRIC, count +' group'+ (count > 1 ? 's' : ''))\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(15, METRIC, hash, btn,'', data, isLies)\n\tlog_perf(15, METRIC, t0)\n\treturn\n}\n\nfunction get_element_forms(METRIC, isLies) {\n\tlet t0 = nowFn()\n\tlet hash, btn ='', data = {}, tmpdata = {}, newobj = {}\n\tlet oList = {\n\t\t// ignore: hidden\n\t\t// redundant: (drop 2) directory, file, files\n\t\t// redundant: (drop 9) datetime, email, month, number, password, search, tel, text, url, week\n\t\t\t// BUT (bring back 2) month + week differ from number-etc in blink | gecko may follow suit so keep those\n\t\t'native': {\n\t\t\tbutton: '',\n\t\t\tcheckbox: '',\n\t\t\tcolor: '',\n\t\t\tdate: '',\n\t\t\t'datetime-local': '',\n\t\t\tdetails: '<details></details>',\n\t\t\t'details_open': '<details open=\"\">.</details>',\n\t\t\tfile: '',\n\t\t\timage: '',\n\t\t\tmonth: '',\n\t\t\tnumber: '',\n\t\t\tprogress: '<progress></progress>',\n\t\t\tradio: '',\n\t\t\trange: '',\n\t\t\treset: '',\n\t\t\tselect: '<select><option></option></select>',\n\t\t\tselect_empty: '<select multiple=\"\"><option></option></select>',\n\t\t\tselect_empty_option: '<select multiple=\"\"><option></option></select>',\n\t\t\tselect_spaces: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\t\tselect_spaces_option: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\t\tselect_string: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\t\tselect_string_option: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\t\tsubmit: '',\n\t\t\ttextarea: '<textarea></textarea>',\n\t\t\ttextarea_3x5: '<textarea cols=\"5\" rows=\"3\"></textarea>',\n\t\t\ttime: '',\n\t\t\tweek: '',\n\t\t},\n\t\t'unstyled': {\n\t\t\t// differ on windows\n\t\t\t// ToDo: check linux/mac/android\n\t\t\tcheckbox: '',\n\t\t\tprogress: '<progress></progress>',\n\t\t\tradio: '',\n\t\t\tselect: '<select><option></option></select>',\n\t\t}\n\t}\n\tlet width, height, x, y, method\n\tconst id = 'element-fp'\n\ttry {\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\t\tlet parent = dom[id], isFirst = true\n\t\tfor (const key of Object.keys(oList)) {\n\t\t\ttmpdata[key] = {}, newobj[key] = {}, data[key] = {}\n\t\t\tfor (const k of Object.keys(oList[key])) {\n\t\t\t\t// important to clear the div so no other elements can affect measurements\n\t\t\t\tparent.innerHTML =''\n\t\t\t\tparent.innerHTML = ('' == oList[key][k] ? '<input type=\"'+ k +'\">' : oList[key][k])\n\t\t\t\tlet target = parent.firstChild\n\t\t\t\t// vertical seems to create subpixels in width before transform\n\t\t\t\ttarget.setAttribute('style', 'display:inline; writing-mode: vertical-lr;') \n\t\t\t\tif ('unstyled' == key) {target.classList.add('unstyled')}\n\t\t\t\tif (k.includes('_option')) {target = target.lastElementChild}\n\t\t\t\tmethod = measureFn(target, METRIC)\n\t\t\t\t// typecheck\n\t\t\t\tlet itemdata = [method.width, method.height, method.x, method.y]\n\t\t\t\tif (isFirst) {\n\t\t\t\t\tisFirst = false\n\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\titemdata.forEach(function(item){\n\t\t\t\t\t\tif (runST) {item = null}\n\t\t\t\t\t\tlet typeCheck = typeFn(item)\n\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tlet itemhash = mini(itemdata)\n\t\t\t\tif (undefined == tmpdata[key][itemhash]) {tmpdata[key][itemhash] = {'data': itemdata, 'group': [k]}\n\t\t\t\t} else {tmpdata[key][itemhash]['group'].push(k)}\n\t\t\t}\n\t\t}\n\t\t// group by results\n\t\tfor (const key of Object.keys(tmpdata)) {\n\t\t\tfor (const k of Object.keys(tmpdata[key])) {newobj[key][tmpdata[key][k].group.join(' ')] = tmpdata[key][k]['data']}\n\t\t}\n\t\tfor (const key of Object.keys(newobj)) {\n\t\t\tfor (const k of Object.keys(newobj[key]).sort()) {data[key][k] = newobj[key][k]}\n\t\t}\n\t\thash = mini(data), btn = addButton(15, METRIC)\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(15, METRIC, hash, btn,'', data, isLies)\n\tlog_perf(15, METRIC, t0)\n\treturn\n}\n\nfunction get_element_mathml(METRIC, isLies) {\n\tlet t0 = nowFn()\n\tconst id = 'element-fp'\n\tconst sizetype = 'px', sizes = [33,99,111], sizectl = sizes[0]\n\tlet hash, btn ='', data = {}, notation = isBB ? bb_slider_red : ''\n\ttry {\n\t\t// create element\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\n \t\tlet divcontrol = \"<div id='mathmldivctrl' style='font-size: \"\n\t\t\t+ sizectl + sizetype +\";'>x=−b ±b2−4 ac 2a</div>\"\n\t\tlet mathmlstr = \"<math><mrow><mi>x</mi><mo>=</mo><mfrac><mrow><mo form='prefix'>&minus;</mo>\"\n\t\t+\"<mi>b</mi><mo>&PlusMinus;</mo><msqrt><msup><mi>b</mi><mn>2</mn></msup><mo>&minus;</mo><mn>4</mn>\"\n\t\t+\"<mo>&InvisibleTimes;</mo><mi>a</mi><mo>&InvisibleTimes;</mo><mi>c</mi></msqrt></mrow>\"\n\t\t+\"<mrow><mn>2</mn><mo>&InvisibleTimes;</mo><mi>a</mi></mrow></mfrac></mrow></math>\"\n\t\tlet divcontent =''\n\t\tsizes.forEach(function(size) {\n\t\t\tdivcontent += \"<div id='mathmldiv\"+ size +\"' style='font-size:\"\n\t\t\t+ size + sizetype +\";'><span id='mathmlspan\"+ size +\"'>\"+ mathmlstr +\"</span></div>\"\n\t\t})\n\t\tdoc.getElementById(id).innerHTML = divcontrol + divcontent\n\n\t\t// measure\n\t\tlet control, width, height, methodW, methodH\n\t\tlet targetC = dom['mathmldivctrl'], targetH, targetW\n\t\tlet isDiff, wType, hType\n\t\tsizes.forEach(function(size) {\n\t\t\ttargetH = dom['mathmldiv'+size]; targetW = dom['mathmlspan'+size]\n\t\t\tlet isCtrlSize = size == sizectl\n\t\t\tsize = size + sizetype\n\n\t\t\t// get div height and span width\n\t\t\tmethodH = measureFn(targetH, METRIC)\n\t\t\tmethodW = measureFn(targetW, METRIC)\n\t\t\twidth = methodW.width\n\t\t\theight = methodH.height\n\n\t\t\t// one time: first elment + size\n\t\t\t\t// get a control size (for diffs) to detemine if mathml is enabled\n\t\t\tif (isCtrlSize) {\n\t\t\t\tmethodH = measureFn(targetC, METRIC)\n\t\t\t\tcontrol = methodH.height\n\t\t\t\tif (undefined !== methodH.error) {throw methodH.errorstring}\n\t\t\t\tif (undefined !== methodH.error) {throw methodH.errorstring}\n\t\t\t\tif (undefined !== methodW.error) {throw methodW.errorstring}\n\t\t\t\t// first item check/diff\n\t\t\t\tif (runST) {width = {}, height = ' '}\n\t\t\t\twType = typeFn(width); hType = typeFn(height)\n\t\t\t\tif ('number' !== wType || 'number' !== hType) {\n\t\t\t\t\tthrow zErrType + (wType == hType ? wType : wType +' x '+ hType)\n\t\t\t\t}\n\t\t\t\tisDiff = height - control\n\t\t\t}\n\t\t\tdata[size] = [width, height]\n\t\t})\n\t\tlet displayEnabled =''\n\t\tlet isEnabled = Math.abs(isDiff) > 0.001\n\t\tif (!isSmart || !isLies) {\n\t\t\tdata['enabled'] = isEnabled\n\t\t\tdisplayEnabled = ' ['+ (isEnabled ? zE : zD) +']'\n\t\t}\n\t\tif (isBB) {notation = isEnabled ? bb_standard : bb_safer}\n\t\thash = mini(data); btn = addButton(15, METRIC) + displayEnabled\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(15, METRIC, hash, btn, notation, data, isLies)\n\tlog_perf(15, METRIC, t0)\n\treturn\n}\n\nfunction get_element_other(METRIC, isLies) {\n\tlet t0 = nowFn()\n\tlet hash, btn ='', data = {}\n\n\t// note: some elements we insert a char \".\" to a) force a height\n\t// or b) for unique measurements without a char to get more precision/decimal places\n\t// always use the same char\n\tlet aUseFirstChild = ['hgroup'] // sometimes we want the first child\n\tlet oExtraStyles = {\n\t\t'marquee': '; width: 20px; height: 20px', // if we don't constrain it, it changes with inner window sizes\n\t}\n\n\tlet oList = {\n\t\t'horizontal-tb' : {\n\t\t\tbase: '<base href=\"\"/>',\n\t\t\tfigure: '<figure></figure>',\n\t\t},\n\t\t'vertical-lr' : {\n\t\t\ta: '<a href=\"\">.</a>',\n\t\t\taudio: '<audio controls=\"\"></audio>',\n\t\t\tbase: '<base href=\"\"/>',\n\t\t\tbig_x2: '<big><big>.</big></big>',\n\t\t\tbig_x3: '<big><big><big>.</big></big></big>',\n\t\t\tbr: '<br>',\n\t\t\tcanvas: '<canvas></canvas>',\n\t\t\tcaption: '<table><caption>.</caption></table>',\n\t\t\tdd: '<dl><dd>.</dd></dl>',\n\t\t\tdialog: '<dialog open=\"\"></dialog>',\n\t\t\tdt: '<dl><dt>.</dt></dl>',\n\t\t\tfieldset: '<fieldset></fieldset>',\n\t\t\tfigcaption: '<figure><figcaption>.</figcaption></figure>',\n\t\t\tgeolocation: '<geolocation></geolocation>',\n\t\t\thgroup: '<hgroup><h1>.</h1><p class=\"revert\">.</p></hgroup>', // code doesn't revert 2nd child so hardcode it\n\t\t\thr: '<hr>',\n\t\t\timg: '<img></img>',\n\t\t\tlegend: '<fieldset><legend>.</legend></fieldset>',\n\t\t\tli: '<ul><li></li></ul>',\n\t\t\t'q_empty': '<q></q>',\n\t\t\t'menu_li': '<menu>.<li></li></menu>',\n\t\t\tnoembed: '<embed><noembed>.</noembed>',\n\t\t\t'ol_li': '<ol><li>.</li></ol>',\n\t\t\toptgroup: '<optgroup></optgroup>',\n\t\t\toutput: '<form><input>=<output class=\"revert\"></output></form>',\n\t\t\tplaintext: '<plaintext>',\n\t\t\trt: '<ruby><rt>.</rt></ruby>',\n\t\t\ttd: '<table><tr><td></td></tr></table>',\n\t\t\ttfoot: '<table><tfoot></tfoot></table>',\n\t\t\t// test error\n\t\t\t//'error': '<frame></frame>'\n\t\t}\n\t}\n\tlet aVerticalAdd = [\n\t\t'b','big','blockquote','code','dl','h1','h2','h3','h4','h5','h6','i','iframe',\n\t\t'marquee','meter','noscript','option','pre','q','small','sub','sup','ul',\n\t]\n\taVerticalAdd.forEach(function(item){oList['vertical-lr'][item] = '<'+item+'>.</'+item+'>'})\n\n\tlet width, height, x, y, method, tmpdata = {}\n\tconst id = 'element-fp'\n\ttry {\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\t\tlet parent = dom[id], isFirst = true\n\t\tfor (const s of Object.keys(oList).sort()) {\n\t\t\tlet style = s.slice(0,-3), itemdata\n\t\t\ttmpdata[style] = {}\n\t\t\tfor (const k of Object.keys(oList[s]).sort()) {\n\t\t\t\t// set parent, determine target to measure and as we walk\n\t\t\t\t// the children, ensure no other css affects any element\n\t\t\t\tparent.innerHTML = oList[s][k]\n\t\t\t\ttry {\n\t\t\t\t\tlet target = parent.firstChild\n\t\t\t\t\t// revert everything\n\t\t\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\t\t\ttarget.classList.add('revert')\n\t\t\t\t\t\tlet newtarget = target.children[0]\n\t\t\t\t\t\tif (undefined == newtarget) {break}\n\t\t\t\t\t\ttarget = newtarget\n\t\t\t\t\t}\n\t\t\t\t\t// choose target\n\t\t\t\t\tif (aUseFirstChild.includes(k)) {target = parent.firstChild}\n\t\t\t\t\t// set writing-mode and style\n\t\t\t\t\tlet extraStyle = undefined == oExtraStyles[k] ? '' : oExtraStyles[k]\n\t\t\t\t\ttarget.setAttribute('style','display:inline; writing-mode: '+ s + extraStyle +';')\n\t\t\t\t\tmethod = measureFn(target, METRIC)\n\t\t\t\t\t// typecheck\n\t\t\t\t\titemdata = [method.width, method.height, method.x, method.y]\n\t\t\t\t\tif (isFirst) {\n\t\t\t\t\t\tisFirst = false\n\t\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\t\titemdata.forEach(function(item){\n\t\t\t\t\t\t\tif (runST) {item = null}\n\t\t\t\t\t\t\tlet typeCheck = typeFn(item)\n\t\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} catch(e) {\n\t\t\t\t\titemdata = zErr\n\t\t\t\t\tlog_error(15, METRIC +'_'+k, e)\n\t\t\t\t}\n\t\t\t\tlet itemhash = mini(itemdata)\n\t\t\t\tif (undefined == tmpdata[style][itemhash]) {tmpdata[style][itemhash] = {'data': itemdata, 'group': [k]}\n\t\t\t\t} else {tmpdata[style][itemhash]['group'].push(k)}\n\t\t\t}\n\t\t}\n\t\t// group by results\n\t\tlet newobj = {}\n\t\tfor (const s of Object.keys(tmpdata)) {\n\t\t\tnewobj[s] = {}\n\t\t\tfor (const k of Object.keys(tmpdata[s])) {\n\t\t\t\tlet keydata = tmpdata[s][k].group.sort()\n\t\t\t\tnewobj[s][keydata.join(' ')] = tmpdata[s][k]['data']\n\t\t\t}\n\t\t}\n\t\tfor (const s of Object.keys(newobj)) {\n\t\t\tdata[s] = {}\n\t\t\tfor (const k of Object.keys(newobj[s]).sort()) {data[s][k] = newobj[s][k]}\n\t\t}\n\t\thash = mini(data); btn = addButton(15, METRIC)\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(15, METRIC, hash, btn,'', data, isLies)\n\tlog_perf(15, METRIC, t0)\n\treturn\n}\n\nfunction get_element_scrollbars(METRIC, isLies) {\n  // https://bugzilla.mozilla.org/show_bug.cgi?id=1786665\n\t\t// ui.useOverlayScrollbars: 0 = yes, 1 = no\n\t\t// widget.non-native-theme.scrollbar.size.override <-- non-overlay only in css pixels at full zoom (default 0)\n\t\t\t// this bypasses TB and changes auto + thin\n\t\t// widget.non-native-theme.scrollbar.style = values 0 to 5 (default, mac, gtk, android, win10, win11)\n\t\t\t// values 1,2,3 bypass TB and change both sizes\n\t\t// layout.css.scrollbar-width-thin.disabled = true\n\t\t\t// this bypasses TB and changes thin to match auto\n\t\t// widget.non-native-theme.win.scrollbar.use-system-size = boolean\n\n\t// FF143+ layout.testing.scrollbars.always-hidden has no effect on measurements\n\t\t// maybe it only affects the viewport?\n\tlet oData = {'auto': {}, 'thin': {}}\n\tlet aAuto = [], aThin = [], aWindow = []\n\tlet list = ['auto','thin']\n\n\t// scrollWidth\n\tfunction get_scroll() {\n\t\tlet element\n\t\tlist.forEach(function(p) {\n\t\t\t// element\n\t\t\tlet value, item = 'element'\n\t\t\ttry {\n\t\t\t\telement = dom.tzpScroll\n\t\t\t\telement.style['scrollbar-width'] = p\n\t\t\t\tlet target = element.children[0]\n\t\t\t\tlet width, method\n\t\t\t\tmethod = measureFn(target, METRIC)\n\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\twidth = method.width\n\t\t\t\tif (runST) {width = NaN} else if (runSI) {width = 101}\n\t\t\t\tlet typeCheck = typeFn(width)\n\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tvalue = 100 - width // 100 set in html, not affected by zoom\n\t\t\t\tif (value < 0) {throw zErrInvalid + '< 0'}\n\t\t\t\t// get scrollbar width for json overlay\n\t\t\t\tif (undefined == isScrollbar && 'auto' == p) {isScrollbar = value}\n\t\t\t} catch(e) {\n\t\t\t\tvalue = zErr\n\t\t\t\tlog_error(15, METRIC +'_'+ p +'_'+ item, e)\n\t\t\t}\n\t\t\tlet fpvalue = value\n\t\t\tif (isLies && zErr !== value) {\n\t\t\t\tvalue = log_known(15, METRIC +'_'+ p +'_'+ item, value)\n\t\t\t\tfpvalue = zLIE\n\t\t\t}\n\t\t\toData[p][item] = fpvalue\n\t\t\tif ('auto' == p) {aAuto.push(value)} else {aThin.push(value)}\n\n\t\t\t// scrollWidth\n\t\t\tvalue = undefined, item = 'scrollWidth'\n\t\t\ttry {\n\t\t\t\telement.style['scrollbar-width'] = p\n\t\t\t\tvalue = 100 - element.scrollWidth\n\t\t\t\tif (runST) {value = NaN} else if (runSI) {value = -1}\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (value < 0) {throw zErrInvalid + '< 0'}\n\t\t\t\t// get scrollbar width for json overlay\n\t\t\t\tif (undefined == isScrollbar && 'auto' == p) {isScrollbar = value}\n\t\t\t} catch(e) {\n\t\t\t\tvalue = zErr\n\t\t\t\tlog_error(15, METRIC +'_'+ p +'_'+ item, e)\n\t\t\t}\n\t\t\toData[p][item] = value\n\t\t\tif ('auto' == p) {aAuto.push(value)} else {aThin.push(value)}\n\t\t})\n\t}\n\n\tget_scroll()\n\tif (undefined == isScrollbar) {isScrollbar = 20}\n\taddDisplay(15, METRIC, dedupeArray(aAuto, true) +' | '+ dedupeArray(aThin, true))\n\taddData(15, METRIC, oData, mini(oData))\n\treturn\n}\n\nconst outputElements = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('elements')) {return resolve()}\n\n\tlet isLies = isDomRect == -1\n\tPromise.all([\n\t\tget_domrect('domrect'),\n\t\tget_element_font('element_font', isLies),\n\t\tget_element_keys('htmlelement_keys'),\n\t\tget_element_forms('element_forms', isLies),\n\t\tget_element_mathml('element_mathml', isLies),\n\t\tget_element_other('element_other', isLies),\n\t\tget_element_scrollbars('element_scrollbars', isLies),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(15)\n"
  },
  {
    "path": "js/fonts.js",
    "content": "'use strict';\n\nlet fntCodes = [ // sorted\n\t// actualBoundingBox, width\n\t'0x007F','0x0218','0x058F','0x05C6','0x061C','0x0700','0x08E4','0x097F','0x09B3',\n\t'0x0B82','0x0D02','0x10A0','0x115A','0x17DD','0x1950','0x1C50','0x1CDA','0x1D790',\n\t'0x1E9E','0x20B0','0x20B8','0x20B9','0x20BA','0x20BD','0x20E3','0x21E4','0x23AE',\n\t'0x2425','0x2581','0x2619','0x2B06','0x2C7B','0x302E','0x3095','0x532D','0x6E2F',\n\t'0xA73D','0xA830','0xF003','0xF810','0xFBEE',\n\t/* ignore: https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character\n\t\tproblematic e.g windows 1st use\n\t\t'0xFFF9','0xFFFD',\n\t//*/\n\t'0xFFFF',\n]\n\nlet fntData = {},\n\tfntSize = '512px',\n\tfntString = '-\\uffff',\n\tfntBtn ='',\n\tfntFake,\n\tfntDocEnabled = false,\n\tfntBase = {},\n\tfntBaseInvalid = {},\n\tfntBaseMin = [],\n\tfntPlatformFont, // undefined\n\tfntHealth = []\n\nlet fntMaster = {\n\t// android core noto\n\tandroid: {\n\t\tnotoboth: [\n\t\t\t// sans: 5+ except CJK JP + Gujarati + Gurmukhi + Tibetan 9+\n\t\t\t// serifs: 9+ except tibetan 12+\n\t\t\t'Armenian','Bengali','CJK JP','Devanagari','Ethiopic','Georgian','Gujarati','Gurmukhi','Hebrew','Kannada',\n\t\t\t'Khmer','Lao','Malayalam','Myanmar','Sinhala','Tamil','Telugu','Thai','Tibetan'],\n\t\tnotosans: [\n\t\t\t// 5+\n\t\t\t'Bengali UI','Devanagari UI','Khmer UI','Lao UI','Malayalam UI','Myanmar UI','Symbols','Tamil UI','Telugu UI','Thai UI',\n\t\t\t// 9+\n\t\t\t'Adlam','Ahom','Anatolian Hieroglyphs','Avestan','Balinese','Bamum','Bassa Vah','Batak','Bhaiksuki','Brahmi','Buginese','Buhid',\n\t\t\t'Canadian Aboriginal','Carian','Chakma','Cham','Cherokee','Coptic','Cuneiform','Cypriot','Deseret','Egyptian Hieroglyphs',\n\t\t\t'Elbasan','Glagolitic','Gothic','Gujarati UI','Gurmukhi UI','Hanunoo','Hatran','Imperial Aramaic','Inscriptional Pahlavi',\n\t\t\t'Inscriptional Parthian','Javanese','Kaithi','Kayah Li','Kharoshthi','Lepcha','Limbu','Linear A','Linear B','Lisu','Lycian',\n\t\t\t'Lydian','Mandaic','Manichaean','Marchen','Meetei Mayek','Meroitic','Miao','Mongolian','Mro','Multani','Nabataean','Newa',\n\t\t\t'New Tai Lue','NKo','Ogham','Ol Chiki','Old Italic','Old North Arabian','Old Permic','Old Persian','Old South Arabian',\n\t\t\t'Old Turkic','Oriya','Oriya UI','Osage','Osmanya','Pahawh Hmong','Palmyrene','Pau Cin Hau','Phags Pa','Phoenician','Rejang',\n\t\t\t'Runic','Samaritan','Saurashtra','Sharada','Shavian','Sinhala UI','Sora Sompeng','Sundanese','Syloti Nagri','Syriac Eastern',\n\t\t\t'Syriac Estrangela','Syriac Western','Tagalog','Tagbanwa','Tai Le','Tai Tham','Tai Viet','Thaana','Tifinagh','Ugaritic','Vai','Yi',\n\t\t\t// 12+\n\t\t\t'Grantha','Gunjala Gondi','Hanifi Rohingya','Khojki','Masaram Gondi','Medefaidrin','Modi','Soyombo','Takri','Wancho','Warang Citi'],\n\t\tnotoserif: ['Dogra','Nyiakeng Puachue Hmong','Yezidi'], // 12+\n\t},\n\t// BB13+ bundled: reuse for android/linux\n\tbundled: {\n\t\t// all: win/mac/linux: 80sans-only 4serif-only 17both: total 118, sorted hash: 8949a424\n\t\tnotoboth: [\n\t\t\t'Balinese','Bengali','Devanagari','Ethiopic','Georgian','Grantha','Gujarati','Gurmukhi','Kannada','Khmer',\n\t\t\t'Khojki','Lao','Malayalam','Sinhala','Tamil','Telugu'],\n\t\tnotosans: [\n\t\t\t'Adlam','Bamum','Bassa Vah','Batak','Buginese','Buhid','Canadian Aboriginal','Chakma','Cham','Cherokee',\n\t\t\t'Coptic','Deseret','Elbasan','Gunjala Gondi','Hanifi Rohingya','Hanunoo','Javanese','Kayah Li','Khudawadi',\n\t\t\t'Lepcha','Limbu','Lisu','Mahajani','Mandaic','Masaram Gondi','Medefaidrin','Meetei Mayek','Mende Kikakui',\n\t\t\t'Miao','Modi','Mongolian','Mro','Multani','NKo','New Tai Lue','Newa','Ol Chiki','Oriya','Osage','Osmanya',\n\t\t\t'Pahawh Hmong','Pau Cin Hau','Rejang','Runic','Samaritan','Saurashtra','Sharada','Shavian','Sora Sompeng',\n\t\t\t'Soyombo','Sundanese','Syloti Nagri','Symbols','Symbols 2','Syriac','Tagalog','Tagbanwa','Tai Le','Tai Tham',\n\t\t\t'Tai Viet','Takri','Thaana','Tifinagh','Tifinagh APT','Tifinagh Adrar','Tifinagh Agraw Imazighen',\n\t\t\t'Tifinagh Ahaggar','Tifinagh Air','Tifinagh Azawagh','Tifinagh Ghat','Tifinagh Hawad','Tifinagh Rhissa Ixa',\n\t\t\t'Tifinagh SIL','Tifinagh Tawellemmet','Tirhuta','Vai','Wancho','Warang Citi','Yi','Zanabazar Square'],\n\t\tnotoserif: ['Dogra','Myanmar','NP Hmong','Tibetan','Yezidi'],\n\t\tandroid: [],\n\t\t// notos then linux +16, mac +5, win +4\n\t\tlinux: [\n\t\t\t'Arimo','Cousine','Noto Color Emoji','Noto Naskh Arabic','Noto Sans Armenian','Noto Sans Hebrew','Noto Sans JP',\n\t\t\t'Noto Sans KR','Noto Sans SC','Noto Sans TC','Noto Sans Thai','Noto Serif Armenian','Noto Serif Hebrew',\n\t\t\t'Noto Serif Thai','Pyidaungsu','STIX Two Math','Tinos','Twemoji Mozilla',\n\t\t],\n\t\tmac: ['Noto Sans Armenian','Noto Sans Hebrew','Noto Serif Armenian','Noto Serif Hebrew','Pyidaungsu','STIX Two Math'],\n\t\twindows: ['Noto Naskh Arabic','Noto Sans','Noto Serif','Pyidaungsu','Twemoji Mozilla'],\n\t},\n\t// BB whitelist system\n\tallowlist: {\n\t\tandroid: [],\n\t\tlinux: [\n\t\t\t'Arial','Courier','Courier New','Helvetica','Times','Times New Roman' // aliases\n\t\t],\n\t\tlinuxfaces: [\n\t\t\t// bundled\n\t\t\t'Arimo Regular','Cousine','Cousine Regular','Noto Sans JP','Noto Sans Symbols Regular','Tinos','Tinos Regular',\n\t\t],\n\t\tmac: [\n\t\t\t// 10.15+\n\t\t\t'AppleGothic','Arial','Courier New','Geneva','Georgia','Heiti TC',\n\t\t\t'Helvetica','Helvetica Neue','Kailasa','Lucida Grande','Menlo','Monaco','PingFang HK','PingFang SC',\n\t\t\t'PingFang TC','Songti SC','Songti TC','Tahoma','Thonburi','Times New Roman','Verdana',\n\t\t\t// other\n\t\t\t'Apple Color Emoji', // listed as 11+ but likely also on 10.15 \n\t\t\t'-apple-system', // always\n\t\t\t// 11- | legacy\n\t\t\t\t// https://searchfox.org/mozilla-central/rev/f53c09a22edc700ab1a9eaaf4da0f0dd9f11bff3/gfx/thebes/CoreTextFontList.cpp#38-44\n\t\t\t'Courier','Times',\n\t\t\t// ToDo: document-supported only\n\t\t\t'Hiragino Kaku Gothic ProN', //'ヒラギノ角ゴ', // mac doesn't seem to do localized\n\t\t\t// ToDo: these are weighted but seem to test correctly in mac but I don't like weighted fonts here\n\t\t\t\t// These are listed in faces: maybe remove the them from here once fontface is enabled in TB\n\t\t\t'Arial Black','Arial Narrow',\n\t\t],\n\t\tmacfaces: [\n\t\t\t// 10.15+\n\t\t\t'Arial Black',\n\t\t\t'Arial Bold','Arial Bold Italic','Arial Italic',\n\t\t\t'Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic',\n\t\t\t'Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Oblique',\n\t\t\t'Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Italic','Helvetica Neue Light',\n\t\t\t\t'Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin',\n\t\t\t\t'Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic',\n\t\t\t'Kailasa Bold',\n\t\t\t'Lucida Grande Bold',\n\t\t\t'Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight',\n\t\t\t'PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight',\n\t\t\t'PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Songti SC Black','Songti SC Bold','Songti SC Light',\n\t\t\t'Songti TC Bold','Songti TC Light',\n\t\t\t'Tahoma Bold',\n\t\t\t'Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t// problematic\n\t\t\t'Courier Bold','Courier Bold Oblique','Courier Oblique', // 11 or lower | legacy\n\t\t\t'Times Bold','Times Bold Italic','Times Italic', // 11 or lower\n\t\t\t// non weight/style\n\t\t\t'AppleGothic Regular','Geneva','Monaco', // 10.15+\n\t\t\t'Apple Color Emoji', // 11+\n\t\t\t'Noto Sans Hebrew Regular','Noto Serif Armenian Regular', // bundled not in macOS/kBaseFonts\n\t\t\t// ToDo: document-supported only\n\t\t\t'Hiragino Kaku Gothic ProN W3','Hiragino Kaku Gothic ProN W6', \n\t\t],\n\t\twindows: [\n\t\t\t// 7\n\t\t\t'Arial','Cambria Math','Consolas','Courier New','Georgia','Lucida Console','MS Gothic','ＭＳ ゴシック',\n\t\t\t'MS PGothic','ＭＳ Ｐゴシック','MV Boli','Malgun Gothic','맑은 고딕','Microsoft Himalaya','Microsoft JhengHei','微軟正黑體',\n\t\t\t'Microsoft YaHei','微软雅黑','Segoe UI','SimSun','宋体','Sylfaen','Tahoma','Times New Roman','Verdana',\n\t\t\t// system aliases\n\t\t\t\t// https://searchfox.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#1990\n\t\t\t\t// should always be the same but lets test everything in BB\n\t\t\t'MS Serif','Courier','Small Fonts','Roman', // TNR, Courier New, Arial, TNR\n\t\t\t// fntPlatformFont\n\t\t\t'MS Shell Dlg \\\\32',\n\t\t\t// FontSubstitutes\n\t\t\t\t// HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes\n\t\t\t\t// BB FontSubstitutes -> whitelisted\n\t\t\t'Arabic Transparent','Arial (Arabic)','Arial (Hebrew)','Arial Baltic','Arial CE','Arial CYR',\n\t\t\t'Arial Greek','Arial TUR','Courier New (Hebrew)','Courier New Baltic','Courier New CE',\n\t\t\t'Courier New CYR','Courier New Greek','Courier New TUR','Helvetica','MS Shell Dlg 2','Tahoma Armenian',\n\t\t\t'Times','Times New Roman (Hebrew)','Times New Roman Baltic','Times New Roman CE','Times New Roman CYR',\n\t\t\t'Times New Roman Greek','Times New Roman TUR','Tms Rmn','MS Serif Greek','Small Fonts Greek',\n\t\t\t'標準ゴシック','ゴシック','ｺﾞｼｯｸ', // ＭＳ ゴシック -> MS Gothic\n\t\t\t'ﾍﾙﾍﾞﾁｶ','ﾀｲﾑｽﾞﾛﾏﾝ','ｸｰﾘｴ', // Arial, TNR, Courier -> Courier New\n\t\t\t// see 44461: FF135+ windows 10 UX issues (1947324) fixed in FF147 (1996961)\n\t\t\t'Segoe MDL2 Assets',\n\t\t],\n\t\twindowsfaces: [\n\t\t\t// weighted/styles\n\t\t\t'Arial Black','Arial Bold','Arial Bold Italic','Arial Italic',\n\t\t\t'Consolas Bold','Consolas Bold Italic','Consolas Italic',\n\t\t\t'Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Malgun Gothic Bold','Malgun Gothic Semilight',\n\t\t\t'Microsoft JhengHei Bold','Microsoft JhengHei Light',\n\t\t\t'Microsoft YaHei Bold','Microsoft YaHei Light',\n\t\t\t'Segoe UI Black','Segoe UI Black Italic','Segoe UI Bold','Segoe UI Bold Italic',\n\t\t\t\t'Segoe UI Italic','Segoe UI Light','Segoe UI Light Italic','Segoe UI Semibold',\n\t\t\t\t'Segoe UI Semibold Italic','Segoe UI Semilight','Segoe UI Semilight Italic',\n\t\t\t'Tahoma Bold',\n\t\t\t'Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t// other\n\t\t\t'Cambria Math','Lucida Console','MS Gothic', // system\n\t\t\t'Noto Sans Gujarati Regular','Noto Serif Dogra Regular','Twemoji Mozilla', // bundled\n\t\t],\n\t\twindowsoffscreen: [\n\t\t\t// proportional only: in BB we test once against monospace + fallbacks\n\t\t\t'Arial','Cambria Math','MS PGothic','Malgun Gothic','Microsoft JhengHei','Microsoft YaHei',\n\t\t\t'Segoe UI','Sylfaen','Times New Roman','Verdana',\n\t\t\t// some windows-only bundled fonts + twemoji\n\t\t\t'Noto Naskh Arabic','Noto Sans','Noto Serif','Twemoji Mozilla',\n\t\t],\n\t},\n\t// BB unexpected\n\tblocklist: {\n\t\tandroid: [],\n\t\tlinux: [\n\t\t\t'Noto Emoji','Noto Mono','Noto Sans','Noto Serif', // notos\n\t\t\t'Cantarell','DejaVu Sans','DejaVu Serif','Droid Sans','STIX', // fedora\n\t\t\t'Dingbats','FreeMono','Ubuntu', // ubuntu\n\t\t\t'Bitstream Charter','C059','Nimbus Sans','P052','Quicksand', // debian\n\t\t\t'Liberation Mono','Liberation Sans','Liberation Serif', // popular\n\t\t\t'Noto Serif Hmong Nyiakeng','Noto Sans Symbols2','STIX Math', // BB12 fontnames\n\t\t\t'Noto Sans Myanmar', // BB14.5 bundled replaced by Pyidaungsu\n\t\t],\n\t\tlinuxfaces: [\n\t\t\t'Arimo', // Arimo without regular seems not to work, double check it.\n\t\t\t'Arial','Arial Regular','Courier New','Courier New Regular', // aliases, expected not to work!\n\t\t\t// common linux fonts\n\t\t\t'Cantarell Regular','DejaVu Sans', // fedora\n\t\t\t'Ubuntu', // ubuntu\n\t\t\t'Nimbus Sans','Nimbus Sans Regular', // debian\n\t\t\t'FreeSans','Liberation Sans', // popular\n\t\t\t// expand: ^ above the only NEW font tested is FreeSans\n\t\t\t'Symbola', // fedora\n\t\t\t'Jamrul','Kinnari', // ubuntu\n\t\t\t'OpenSymbol', // openoffice\n\t\t\t'Amiri', // libreoffice\n\t\t\t'Bitstream Charter' // debian\n\t\t],\n\t\tmac: [\n\t\t\t'Apple Symbols','Impact','Palatino','Rockwell', // system\n\t\t\t'Noto Serif Hmong Nyiakeng','STIX Math', // BB12 fontnames\n\t\t\t'Noto Sans Symbols2', // BB12 bundled: ToDo: in faces: maybe remove when that lands in TB\n\t\t\t'Noto Sans Myanmar', // BB14.5 bundled replaced by Pyidaungsu\n\t\t\t'.Helvetica Neue DeskInterface', // dot-prefixed font families on mac = hidden // tb#42377\n\t\t],\n\t\tmacfaces: [\n\t\t\t// weighted\n\t\t\t'Avenir Light','Charter Black','Damascus Medium','Gill Sans Light','Hiragino Sans W3',\n\t\t\t// other\n\t\t\t'Apple Braille','Trebuchet MS','Webdings','Wingdings','Zapfino', // system\n\t\t\t'Noto Sans Symbols2', // BB12 bundled\n\t\t],\n\t\twindows: [\n\t\t\t'Calibri','Candara','Microsoft JhengHei UI','MS UI Gothic','Microsoft YaHei UI','NSimSun','Segoe UI Emoji','SimSun-ExtB', // system\n\t\t\t'MS Shell Dlg', // system alias == Microsoft Sans Serif\n\t\t\t'Gill Sans','Gill Sans MT', // MS bundled\n\t\t\t// other\n\t\t\t'Noto Sans Symbols2', // BB12 bundled\n\t\t\t'Noto Sans Myanmar', // BB14.5 bundled replaced by Pyidaungsu\n\t\t\t'Noto Color Emoji', // bundled nightly: should be removed\n\t\t\t'Helv', // ToDo: this might need to move with font.vis\n\t\t],\n\t\twindowsfaces: [\n\t\t\t// weighted\n\t\t\t// 'Arial Narrow', // ToDo: uncomment once we block it\n\t\t\t'Calibri Light', // 8\n\t\t\t'Microsoft JhengHei UI Light','Nirmala UI Semilight', // 8.1\n\t\t\t'Candara Light','Corbel Light','Yu Gothic UI Light', // 10\n\t\t\t// other\n\t\t\t'MS UI Gothic','Microsoft YaHei UI','NSimSun','SimSun-ExtB', // system\n\t\t\t'Gill Sans','Gill Sans MT', // MS bundled\n\t\t\t'Noto Serif Hmong Nyiakeng Regular', // BB12 bundled\n\t\t\t'Arabic Transparent', // fontSubstitutes\n\t\t\t'Courier','MS Serif','Roman', // aliases\n\t\t],\n\t\twindowsoffscreen: [\n\t\t\t// proportional only: in BB we test once against monospace + fallbacks\n\t\t\t'Cambria','Comic Sans MS','Gabriola','Impact','Webdings', // system\n\t\t\t'MS Shell Dlg', // system alias == Microsoft Sans Serif\n\t\t\t'Gill Sans','Gill Sans MT', // MS bundled\n\t\t\t// other\n\t\t\t'Noto Sans Symbols2', // BB12 bundled\n\t\t],\n\t},\n\t// kBaseFonts: https://searchfox.org/mozilla-central/search?path=StandardFonts*.inc\n\tbase: {\n\t\tandroid: [],\n\t\tlinux: [],\n\t\tmac: [\n\t\t\t// always\n\t\t\t'-apple-system',\n\t\t\t//kBaseFonts\n\t\t\t'Al Bayan','Al Nile','Al Tarikh','American Typewriter','Andale Mono','Apple Braille','Apple Chancery',\n\t\t\t\t'Apple Color Emoji','Apple SD Gothic Neo','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t\t'Arial','Arial Hebrew','Arial Hebrew Scholar','Arial Unicode MS','Avenir Next','Ayuthaya',\n\t\t\t'Baghdad','Bangla MN','Bangla Sangam MN','Baskerville','Beirut','Bodoni Ornaments',\n\t\t\t\t'Bodoni 72','Bodoni 72 Oldstyle','Bodoni 72 Smallcaps', // bodoni 72 font-family we drop 'book'\n\t\t\t'Catamaran','Chalkboard','Chalkboard SE','Chalkduster','Cochin','Comic Sans MS','Copperplate',\n\t\t\t\t'Corsiva Hebrew','Courier New',\n\t\t\t'Damascus','DecoType Naskh','Devanagari MT','Devanagari Sangam MN','Didot','Diwan Kufi','Diwan Thuluth',\n\t\t\t'Euphemia UCAS',\n\t\t\t'Farah','Farisi',\n\t\t\t'GB18030 Bitmap','Galvji','Geeza Pro','Geneva','Georgia','Gill Sans','Gujarati MT','Gujarati Sangam MN',\n\t\t\t\t'Gurmukhi MN','Gurmukhi MT','Gurmukhi Sangam MN',\n\t\t\t'Helvetica','Helvetica Neue','Hoefler Text','Hiragino Maru Gothic ProN',\n\t\t\t'Impact','InaiMathi',\n\t\t\t'Kailasa','Kannada MN','Kannada Sangam MN','Kefa','Khmer MN','Khmer Sangam MN','Kohinoor Bangla',\n\t\t\t\t'Kohinoor Devanagari','Kohinoor Gujarati','Kohinoor Telugu','Kokonor','Krungthep','KufiStandardGK',\n\t\t\t'Lao MN','Lao Sangam MN','Lucida Grande','Luminari',\n\t\t\t'Malayalam MN','Malayalam Sangam MN','Menlo','Microsoft Sans Serif','Mishafi','Mishafi Gold',\n\t\t\t\t'Monaco','Mshtakan','Mukta Mahee','Muna','Myanmar MN','Myanmar Sangam MN',\n\t\t\t'Nadeem','New Peninim MT',\n\t\t\t'Noto Nastaliq Urdu',\n\t\t\t'Noto Sans Adlam','Noto Sans Armenian','Noto Sans Avestan','Noto Sans Bamum','Noto Sans Bassa Vah',\n\t\t\t\t'Noto Sans Batak','Noto Sans Bhaiksuki','Noto Sans Brahmi','Noto Sans Buginese','Noto Sans Buhid',\n\t\t\t\t'Noto Sans Canadian Aboriginal','Noto Sans Carian','Noto Sans Caucasian Albanian','Noto Sans Chakma',\n\t\t\t\t'Noto Sans Cham','Noto Sans Coptic','Noto Sans Cuneiform','Noto Sans Cypriot','Noto Sans Duployan',\n\t\t\t\t'Noto Sans Egyptian Hieroglyphs','Noto Sans Elbasan','Noto Sans Glagolitic','Noto Sans Gothic',\n\t\t\t\t'Noto Sans Gunjala Gondi','Noto Sans Hanifi Rohingya','Noto Sans Hanunoo','Noto Sans Hatran',\n\t\t\t\t'Noto Sans Imperial Aramaic','Noto Sans Inscriptional Pahlavi','Noto Sans Inscriptional Parthian',\n\t\t\t\t'Noto Sans Javanese','Noto Sans Kaithi','Noto Sans Kannada','Noto Sans Kayah Li','Noto Sans Kharoshthi',\n\t\t\t\t'Noto Sans Khojki','Noto Sans Khudawadi','Noto Sans Lepcha','Noto Sans Limbu','Noto Sans Linear A',\n\t\t\t\t'Noto Sans Linear B','Noto Sans Lisu','Noto Sans Lycian','Noto Sans Lydian','Noto Sans Mahajani',\n\t\t\t\t'Noto Sans Mandaic','Noto Sans Manichaean','Noto Sans Marchen','Noto Sans Masaram Gondi',\n\t\t\t\t'Noto Sans Meetei Mayek','Noto Sans Mende Kikakui','Noto Sans Meroitic','Noto Sans Miao','Noto Sans Modi',\n\t\t\t\t'Noto Sans Mongolian','Noto Sans Mro','Noto Sans Multani','Noto Sans Myanmar','Noto Sans Nabataean',\n\t\t\t\t'Noto Sans New Tai Lue','Noto Sans Newa','Noto Sans NKo','Noto Sans Ol Chiki','Noto Sans Old Hungarian',\n\t\t\t\t'Noto Sans Old Italic','Noto Sans Old North Arabian','Noto Sans Old Permic','Noto Sans Old Persian',\n\t\t\t\t'Noto Sans Old South Arabian','Noto Sans Old Turkic','Noto Sans Oriya','Noto Sans Osage','Noto Sans Osmanya',\n\t\t\t\t'Noto Sans Pahawh Hmong','Noto Sans Palmyrene','Noto Sans Pau Cin Hau','Noto Sans PhagsPa','Noto Sans Phoenician',\n\t\t\t\t'Noto Sans Psalter Pahlavi','Noto Sans Rejang','Noto Sans Samaritan','Noto Sans Saurashtra','Noto Sans Sharada',\n\t\t\t\t'Noto Sans Siddham','Noto Sans Sora Sompeng','Noto Sans Sundanese','Noto Sans Syloti Nagri','Noto Sans Syriac',\n\t\t\t\t'Noto Sans Tagalog','Noto Sans Tagbanwa','Noto Sans Tai Le','Noto Sans Tai Tham','Noto Sans Tai Viet',\n\t\t\t\t'Noto Sans Takri','Noto Sans Thaana','Noto Sans Tifinagh','Noto Sans Tirhuta','Noto Sans Ugaritic',\n\t\t\t\t'Noto Sans Vai','Noto Sans Wancho','Noto Sans Warang Citi','Noto Sans Yi','Noto Sans Zawgyi',\n\t\t\t'Noto Serif Ahom','Noto Serif Balinese','Noto Serif Hmong Nyiakeng','Noto Serif Myanmar','Noto Serif Yezidi',\n\t\t\t'Optima','Oriya MN','Oriya Sangam MN',\n\t\t\t'PT Mono','PT Sans','PT Sans Caption','PT Serif','PT Serif Caption','Palatino','Papyrus',\n\t\t\t\t'PingFang HK','PingFang SC','PingFang TC','Plantagenet Cherokee',\n\t\t\t'Raanana','Rockwell',\n\t\t\t'STIX Two Math', // FF133+ 1902570\n\t\t\t'STIX Two Math Regular', // 2000429\n\t\t\t'STIXGeneral','STIXIntegralsD','STIXIntegralsSm','STIXIntegralsUp','STIXIntegralsUpD','STIXIntegralsUpSm',\n\t\t\t\t'STIXNonUnicode','STIXSizeFiveSym','STIXSizeFourSym','STIXSizeOneSym','STIXSizeThreeSym','STIXSizeTwoSym',\n\t\t\t\t'STIXVariants','STSong','Sana','Sathu','Savoye LET','Shree Devanagari 714','SignPainter','Silom','Sinhala MN',\n\t\t\t\t'Sinhala Sangam MN','Skia','Snell Roundhand','Songti SC','Songti TC','Symbol',\n\t\t\t'Tahoma','Tamil MN','Tamil Sangam MN','Telugu MN','Telugu Sangam MN','Thonburi','Times New Roman',\n\t\t\t\t'Trattatello','Trebuchet MS',\n\t\t\t'Verdana',\n\t\t\t'Waseem','Webdings','Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Zapf Dingbats','Zapfino',\n\t\t\t// 11- | legacy\n\t\t\t'Courier','Times',\n\t\t\t// localized: just in case: ToDo: test JAP install\n\t\t\t'애플고딕', // AppleGothic\n\t\t\t'애플명조', // AppleMyungjo\n\t\t\t'ヒラギノ丸ゴ', // Hiragino Maru Gothic ProN\n\t\t],\n\t\tmacfaces: [\n\t\t\t// weighted/styled\n\t\t\t'Arial Black','Arial Rounded MT Bold','Arial Narrow','PT Sans Narrow',\n\t\t\t// without the weight W* it loads W3\n\t\t\t'Hiragino Sans W4',\n\t\t\t// works in font-family if we drop 'book'/'roman'\n\t\t\t'Avenir Book','Avenir Book Oblique','Avenir Roman','ITF Devanagari Book','ITF Devanagari Marathi Book',\n\t\t\t// legacy\n\t\t\t'Courier','Courier Bold','Courier Bold Oblique','Courier Oblique',\n\t\t\t'Times','Times Bold','Times Bold Italic','Times Italic','Times Roman',\n\t\t\t// these ones have no regular/normal\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light',\n\t\t\t\t'Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Oblique',\n\t\t\t'Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Demi Bold','Avenir Next Heavy',\n\t\t\t\t'Avenir Next Medium','Avenir Next Ultra Light',\n\t\t\t'Big Caslon Medium','Bradley Hand Bold','Brush Script MT Italic',\n\t\t\t'Charter Black','Charter Bold', //'Charter Black Italic','Charter Bold Italic','Charter Italic',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Futura Condensed ExtraBold','Futura Condensed Medium',\n\t\t\t'Futura Medium', //'Futura Bold','Futura Medium Italic',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Hiragino Mincho ProN W3','Hiragino Mincho ProN W6',\n\t\t\t'Hiragino Sans GB W3','Hiragino Sans GB W6',\n\t\t\t'ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Medium',//'ITF Devanagari Bold', \n\t\t\t'ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium',//'ITF Devanagari Marathi Bold',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Text','Sukhumvit Set Thin', //'Sukhumvit Set Bold','Sukhumvit Set Semi Bold',\n\t\t\t// SyntaxError: An invalid or illegal string was specified\n\t\t\t\t// 'Bodoni 72 Bold','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book Italic',\n\t\t],\n\t\twindows: [\n\t\t\t// 7\n\t\t\t'Arial','Calibri','Cambria','Cambria Math','Candara','Comic Sans MS','Consolas','Constantia','Corbel','Courier New',\n\t\t\t'Ebrima','Gabriola','Georgia','Impact','Lucida Console','Lucida Sans Unicode','MS Gothic','ＭＳ ゴシック',\n\t\t\t'MS PGothic','ＭＳ Ｐゴシック','MS UI Gothic','MV Boli','Malgun Gothic','맑은 고딕','Marlett','Microsoft Himalaya',\n\t\t\t'Microsoft JhengHei','微軟正黑體','Microsoft New Tai Lue','Microsoft PhagsPa','Microsoft Sans Serif',\n\t\t\t'Microsoft Tai Le','Microsoft YaHei','微软雅黑','Microsoft Yi Baiti','MingLiU-ExtB','細明體-ExtB',\n\t\t\t'MingLiU_HKSCS-ExtB','細明體_HKSCS-ExtB','Mongolian Baiti','NSimSun','新宋体','PMingLiU-ExtB','新細明體-ExtB',\n\t\t\t'Palatino Linotype','Segoe Print','Segoe Script','Segoe UI','Segoe UI Symbol','SimSun','宋体','SimSun-ExtB',\n\t\t\t'Sylfaen','Symbol','Tahoma','Times New Roman','Trebuchet MS','Verdana','Webdings','Wingdings',\n\t\t\t// 8\n\t\t\t'Gadugi','Nirmala Text','Nirmala UI','Microsoft JhengHei UI','Microsoft YaHei UI','Myanmar Text',\n\t\t\t// 8.1\n\t\t\t'Javanese Text','Leelawadee UI','Segoe UI Emoji','Sitka Banner','Sitka Display',\n\t\t\t'Sitka Heading','Sitka Small','Sitka Subheading','Sitka Text','Yu Gothic','游ゴシック',\n\t\t\t// 10\n\t\t\t'Bahnschrift','Segoe MDL2 Assets','Segoe UI Historic','Yu Gothic UI',\n\t\t\t// 11\n\t\t\t'SimSun-ExtG', // win11 24H2 see 1947960 | 1954265 FF139 adeed to base\n\t\t\t// fntPlatformFont\n\t\t\t'MS Shell Dlg \\\\32',\n\t\t\t// common FontSubstitutes that point to kBase fonts\n\t\t\t'MS Shell Dlg','MS Shell Dlg 2', // can differ\n\t\t\t// FontSubstitutes which can be or are aliased to kBaseFonts\n\t\t\t\t// 1845105: FF141+\n\t\t\t'Arial (Arabic)','Arial (Hebrew)','Arabic Transparent','Arial Baltic','Arial CE','Arial CYR','Arial Greek','Arial TUR',\n\t\t\t'Courier','Courier New (Arabic)','Courier New (Hebrew)','Courier New Baltic','Courier New CE','Courier New CYR',\n\t\t\t'Courier New Greek','Courier New TUR','Helv','Helvetica','MS Serif Greek','MS Sans Serif Greek','Small Fonts Greek',\n\t\t\t'Tahoma Armenian','Times','Times New Roman (Arabic)','Times New Roman (Hebrew)','Times New Roman Baltic',\n\t\t\t'Times New Roman CE','Times New Roman CYR','Times New Roman Greek','Times New Roman TUR','Tms Rmn',\n\t\t\t\t// jap\n\t\t\t'ﾍﾙﾍﾞﾁｶ', // arial\n\t\t\t'ｸｰﾘｴ', // Courier New\n\t\t\t'標準ゴシック','ゴシック','ｺﾞｼｯｸ', // ＭＳ ゴシック -> MS Gothic\n\t\t\t'ﾀｲﾑｽﾞﾛﾏﾝ', // TNR\n\t\t\t/* ignore\n\t\t\t// system aliases: should always be the same AFAICT\n\t\t\t\t// https://searchfox.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#1990\n\t\t\t\t'MS Sans Serif','MS Serif','Small Fonts','Roman', // Microsoft Sans Serif, TNR, Arial, TNR\n\t\t\t//'Franklin Gothic Medium', // 7 not detected if font-vis < 3: 1720408\n\t\t\t//*/\n\t\t],\n\t\twindowsfaces: [\n\t\t\t// weighted\n\t\t\t'Arial Black','Arial Narrow','Segoe UI Light','Segoe UI Semibold', // 7\n\t\t\t'Calibri Light','Segoe UI Semilight', // 8\n\t\t\t// 8.1\n\t\t\t'Leelawadee UI Semilight','Microsoft JhengHei Light','Microsoft JhengHei UI Light',\n\t\t\t'Microsoft YaHei Light','Microsoft YaHei UI Light','Nirmala UI Semilight','Segoe UI Black','Yu Gothic Light',\n\t\t\t// 10\n\t\t\t'Candara Light','Corbel Light','Malgun Gothic Semilight',\n\t\t\t'Yu Gothic Medium','Yu Gothic UI Light','Yu Gothic UI Semilight','Yu Gothic UI Semibold',\n\t\t\t/* ignore: not detected by font face\n\t\t\t\t'Bahnschrift Light','Bahnschrift SemiBold','Bahnschrift SemiLight',\n\t\t\t//*/\n\t\t],\n\t\twindowsoffscreen: [\n\t\t\t// vs MS Shell Dlg \\32\n\t\t\t'Calibri','Cambria','Georgia','MV Boli','Marlett','NSimSun','Sylfaen',\n\t\t]\n\t},\n\t// kLangPackFonts\n\tbaselang: {\n\t\tandroid: [], linux: [], mac: [],\n\t\twindows: [\n\t\t\t'Aharoni','Aldhabi','Andalus','Angsana New','AngsanaUPC','Aparajita','Arabic Typesetting','BIZ UDGothic','BIZ UDゴシック',\n\t\t\t'BIZ UDMincho','BIZ UD明朝','BIZ UDPGothic','BIZ UDPゴシック','BIZ UDPMincho','BIZ UDP明朝','Batang','바탕','BatangChe','바탕체',\n\t\t\t'Browallia New','BrowalliaUPC','Cordia New','CordiaUPC','DFKai-SB','DaunPenh','David','DengXian','等线','DilleniaUPC',\n\t\t\t'DokChampa','Dotum','돋움','DotumChe','돋움체','Estrangelo Edessa','EucrosiaUPC','Euphemia','FangSong','仿宋','FrankRuehl',\n\t\t\t'FreesiaUPC','Gautami','Gisha','Gulim','굴림','GulimChe','굴림체','Gungsuh','궁서','GungsuhChe','궁서체','IrisUPC','Iskoola Pota',\n\t\t\t'JasmineUPC','KaiTi','楷体','Kalinga','Kartika','Khmer UI','KodchiangUPC','Kokila','Lao UI','Latha','Leelawadee','Levenim MT',\n\t\t\t'LilyUPC','MS Mincho','ＭＳ 明朝','MS PMincho','ＭＳ Ｐ明朝','Mangal','Meiryo','メイリオ','Meiryo UI','Microsoft Uighur',\n\t\t\t'MingLiU','細明體','MingLiU_HKSCS','細明體_HKSCS','Miriam','Miriam Fixed','MoolBoran','Narkisim','Nyala','PMingLiU','新細明體',\n\t\t\t'Plantagenet Cherokee','Raavi','Rod','Sakkal Majalla','Sanskrit Text','Shonar Bangla','Shruti','SimHei','黑体',\n\t\t\t'Simplified Arabic','Traditional Arabic','Tunga','Urdu Typesetting','Utsaah','Vani','Vijaya','Vrinda','Yu Mincho','游明朝',\n\t\t\t// UD Digi\n\t\t\t\t// to save on code turmoil and maintenance, allow 24H2 in FF144 or lower: if FPP leaks it will show\n\t\t\t\t// with other fonts and if FPP doesn't leak then they simply will not show in the results\n\t\t\t'UD Digi Kyokasho N-R','UD Digi Kyokasho NK-R','UD Digi Kyokasho NP-R', // original (ignore weighted -B bold)\n\t\t\t'UD Digi Kyokasho N','UD Digi Kyokasho NK','UD Digi Kyokasho NP', // 24H2: FF145+ 1988407\n\t\t\t// 1954265: FF139+ win11 23H2 / win1022H2\n\t\t\t'Noto Sans HK','Noto Sans JP','Noto Sans KR','Noto Sans SC','Noto Sans TC',\n\t\t\t'Noto Serif HK','Noto Serif JP','Noto Serif KR','Noto Serif SC','Noto Serif TC',\n\t\t\t// FontSubstitutes which can be or are aliased to kLangPackFonts\n\t\t\t\t// 1845105: FF141+\n\t\t\t\t// hebrew\n\t\t\t'David Transparent', // David\n\t\t\t'Fixed Miriam Transparent', // Miriam Fixed\n\t\t\t'Miriam Transparent', // Miriam\n\t\t\t'Rod Transparent', // Rod\n\t\t\t\t// japanese\n\t\t\t'標準明朝', // ＭＳ 明朝 -> MS Mincho,\n\t\t\t\t// simplified chinese\n\t\t\t'FangSong_GB2312', // FangSong\n\t\t\t'KaiTi_GB2312','楷体_GB2312' // KaiTi + localized\n\t\t\t\t/* Fixedsys and system do not seem to be available to web content\n\t\t\t'Fixedsys Greek', // Fixedsys\n\t\t\t'System Greek', // System\n\t\t\t\t//*/\n\t\t],\n\t\twindowsfaces: [\n\t\t\t'BIZ UDMincho Medium','BIZ UDPMincho Medium','DengXian Light','Yu Mincho Demibold','Yu Mincho Light'\n\t\t],\n\t\twindowsoffscreen: [\n\t\t\t// to check if RFP/FPP leak, not determine RFP vs FPP: no gurantee of supplemental kLangPackFonts\n\t\t\t// add a token range of scripts in win10+ that were in win7/8 (more likely if user upgraded?)\n\t\t\t'Aldhabi','Aparajita','Batang','David','Estrangelo Edessa','Euphemia','FangSong','Gautami','Iskoola Pota',\n\t\t\t'Kalinga','Kartika','Khmer UI','Lao UI','Latha','Leelawadee','Meiryo','MingLiU','Nyala','Plantagenet Cherokee',\n\t\t\t'Raavi','Shonar Bangla','Shruti','Tunga',\n\t\t]\n\t},\n\t// NOT kBase/LangPack\n\tsystem: {\n\t\tandroid: [\n\t\t\t// common: note 'AndroidClock' ignored\n\t\t\t'Carrois Gothic SC','Cutive Mono','Dancing Script','Droid Sans Mono','Noto Color Emoji','Noto Naskh Arabic', // all\n\t\t\t'Coming Soon','Noto Naskh Arabic UI','Roboto', // 9+\n\t\t\t'Noto Color Emoji Flags','Source Sans Pro', // 12+\n\t\t\t// other\n\t\t\t'Droid Sans','Droid Sans Fallback','Droid Serif',\n\t\t\t'Noto Sans','Noto Sans CJK KR','Noto Sans CJK SC','Noto Sans CJK TC','Noto Sans SC','Noto Sans TC',\n\t\t\t'Noto Serif','Noto Serif CJK KR','Noto Serif CJK SC','Noto Serif CJK TC','Noto Serif SC','Noto Serif TC',\n\t\t\t// +samsung\n\t\t\t'One UI Sans APP VF','One UI Sans KR VF', // 1865238\n\t\t\t'SamsungColorEmoji', // 1872510\n\t\t\t'SamsungKorean_v2.0','SamsungKorean_v3.0', // 1674683\n\t\t\t'SamsungKhmerUI','SamsungMyanmarShan','SamsungMyanmarZawgyiUI',\n\t\t\t// +xiaomi: https://hyperos.mi.com/font/en/\n\t\t\t'MiSans','MiSans VF','MiSans TC','MiSans TC VF', // 1933410, 1954947\n\t\t\t'MiSans Arabic', // for all these maybe append VF as more likely? or both?\n\t\t\t'MiSans Devanagari',\n\t\t\t'MiSans Khmer',\n\t\t\t'MiSans Latin',\n\t\t\t'MiSans Lao',\n\t\t\t'MiSans Gujarati',\n\t\t\t'MiSans Gurmukhi',\n\t\t\t'MiSans Myanmar',\n\t\t\t'MiSans Thai',\n\t\t\t'MiSans Tibetan',\n\t\t\t// +SEC\n\t\t\t'New SEC Num Fixed VF','New SEC Num VF','SEC Bengali','SEC Bengali UI','SEC CJK Regular Extra',\n\t\t\t'SEC Devanagari','SEC Devanagari UI','SEC Lao','SEC Lao UI','SEC Malayalam','SEC Malayalam UI',\n\t\t\t'SEC Myanmar UI','SEC Naskh Arabic','SEC Naskh Arabic UI','SEC Serif Tibetan','SEC Tamil','SEC Tamil UI',\n\t\t\t'SECFallback','SECGujarati','SECGujarati UI','SECGurmukhi','SECGurmukhi UI','SECKannada','SECKannada UI',\n\t\t\t'SECTelugu','SECTelugu UI',\n\t\t\t// +SEC CJK\n\t\t\t'SEC CJK JP','SEC CJK KR','SEC CJK SC','SEC CJK TC','SEC Mono CJK JP','SEC Mono CJK KR','SEC Mono CJK SC',\n\t\t\t'SEC Mono CJK TC',\n\t\t],\n\t\tlinux: [\n\t\t\t// self\n\t\t\t'Noto Sans','Noto Serif',\n\t\t\t// +always\n\t\t\t'Arial','Courier','Courier New',\n\t\t\t// +common notos\n\t\t\t'Noto Emoji','Noto Sans Tibetan',\n\t\t\t// +selective kBase ubuntu or fedora\n\t\t\t\t// notos\n\t\t\t'Noto Color Emoji','Noto Mono','Noto Serif CJK JP','Noto Serif CJK KR','Noto Serif CJK SC','Noto Serif CJK TC',\n\t\t\t\t// western/symbols\n\t\t\t'Cantarell','DejaVu Sans','DejaVu Serif','Droid Sans','STIX','STIX Two Text','Symbola', // fedora\n\t\t\t'Dingbats','FreeMono','Jamrul','Kinnari','Ubuntu', // ubuntu\n\t\t\t\t// other\n\t\t\t'OpenSymbol', // openoffice\n\t\t\t'Amiri', // libreoffice\n\t\t\t'Liberation Mono','Liberation Sans','Liberation Serif',\n\t\t\t\t// scripts\n\t\t\t\t// ubuntu\n\t\t\t'KacstNaskh','PakType Naskh Basic', // arabic\n\t\t\t'Likhan','Mitra Mono','Mukti Narrow', // bengali\n\t\t\t'Chandas','Kalimati','Samanata', // devangari 'Gargi','Nakula','Rachana','Sahadeva','Sarai',\n\t\t\t'Rekha','Saab','Samyak Gujarati', // gujarati\n\t\t\t'Lohit Gurmukhi', // gurmukhi\n\t\t\t'Gubbi','Navilu', // kannada\n\t\t\t'Phetsarath OT', // lao\n\t\t\t'Chilanka','Gayathri','Suruma', // malayalam 'Dyuthi','Karumbi','Keraleeyam','Manjari','Uroob',\n\t\t\t'ori1Uni','utkal', // oriya\n\t\t\t'LKLUG','padmaa', // sinhala\n\t\t\t'Samyak Tamil', // tamil\n\t\t\t'Pothana2000','Vemana2000', // telugu\n\t\t\t'Laksaman','Purisa','Umpush', // thai 'Norasi','Tlwg Mono',\n\t\t\t'Jomolhari', // tibetan, also chrome os\n\t\t\t'Pagul','Rasa','Yrsa', // multiscript\n\t\t\t\t// fedora\n\t\t\t'Droid Arabic Kufi', // arabic\n\t\t\t'Droid Sans Devanagari', // devangari\n\t\t\t'Droid Sans Ethiopic', // ethiopic\n\t\t\t'Droid Sans Hebrew', // hebrew\n\t\t\t'Droid Sans Japanese', // jap\n\t\t\t'Khmer OS', // khmer\n\t\t\t'Droid Sans Tamil', // tamil\n\t\t\t'Droid Sans Thai', // thai\n\t\t\t'Nuosu SIL', // yi\n\t\t\t// debian\n\t\t\t'Bitstream Charter','C059','Courier 10 Pitch','D050000L',\n\t\t\t'DejaVu Math TeX Gyre','Nimbus Mono PS','Nimbus Roman',\n\t\t\t'Nimbus Sans','P052','Quicksand','Standard Symbols PS',\n\t\t\t'URW Bookman','URW Gothic','Z003',\n\t\t\t// ToDo: expand\n\t\t\t'Inter',\n\t\t],\n\t\tmac: [ //NOT kBase/LangPack\n\t\t\t'Academy Engraved LET','Adelle Sans Devanagari','AkayaKanadaka','AkayaTelivigala','Annai MN','Arima Koshi','Arima Madurai','Avenir Next Condensed',\n\t\t\t'Bai Jamjuree','Baloo 2','Baloo Bhai 2','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Chettan 2','Baloo Da 2','Baloo Paaji 2',\n\t\t\t\t'Baloo Tamma 2','Baloo Tammudu 2','Baloo Thambi 2','Baoli SC','Baoli TC','BiauKai','BiauKaiHK','BiauKaiTC','BIZ UDGothic',\n\t\t\t\t'BIZ UDMincho','BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Cambay Devanagari','Canela','Canela Deck','Canela Text','Chakra Petch','Charm','Charmonman',\n\t\t\t'Dash','Domaine Display',\n\t\t\t'Fahkwang','Founders Grotesk','Founders Grotesk Condensed','Founders Grotesk Text',\n\t\t\t'Gotu','Grantha Sangam MN','Graphik','Graphik Compact','GungSeo',\n\t\t\t'Hannotate SC','Hannotate TC','HanziPen SC','HanziPen TC','HeadLineA','Hei','Herculanum','Hubballi',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','Kai','Kaiti SC','Kaiti TC','Katari','Kavivanar','Kigelia','Kigelia Arabic','Kodchasan','KoHo','Krub',\n\t\t\t\t'Kefa III', // new tahoe 26\n\t\t\t'Lahore Gurmukhi','Lava Devanagari','Lava Kannada','Lava Telugu',\n\t\t\t\t'LiHei Pro','LiSong Pro','Libian SC','Libian TC',\n\t\t\t'Maku','Mali','Modak','Mukta','Mukta Malar','Mukta Vaani','Myriad Arabic',\n\t\t\t'Nanum Brush Script','Nanum Gothic','Nanum Myeongjo','나눔명조','Nanum Pen Script','Niramit','Nom Na Tong',\n\t\t\t\t'Noto Serif Kannada','November Bangla Traditional',\n\t\t\t'October Compressed Devanagari','October Compressed Gujarati','October Compressed Gurmukhi','October Compressed Kannada',\n\t\t\t\t'October Compressed Meetei Mayek','October Compressed Odia','October Compressed Ol Chiki','October Compressed Tamil','October Compressed Telugu',\n\t\t\t'October Condensed Devanagari','October Condensed Gujarati','October Condensed Gurmukhi','October Condensed Kannada',\n\t\t\t\t'October Condensed Meetei Mayek','October Condensed Odia','October Condensed Ol Chiki','October Condensed Tamil','October Condensed Telugu',\n\t\t\t'October Devanagari','October Gujarati','October Gurmukhi','October Kannada',\n\t\t\t\t'October Meetei Mayek','October Odia','October Ol Chiki','October Tamil','October Telugu',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'Padyakke Expanded One','Party LET','PCMyungjo','PilGi','PingFang MO','Produkt','Proxima Nova','PSL Ornanong Pro',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Sama Devanagari','Sama Gujarati','Sama Gurmukhi','Sama Kannada','Sama Malayalam','Sama Tamil','Sarabun','Sauber Script',\n\t\t\t\t'Shobhika','SimSong','Spot Mono','Srisakdi','STFangsong','STHeiti','STIX Two Text','STKaiti','华文楷体','STXihei',\n\t\t\t'Tiro Bangla','Tiro Devanagari Hindi','Tiro Devanagari Marathi','Tiro Devanagari Sanskrit','Tiro Gurmukhi','Tiro Kannada','Tiro Tamil',\n\t\t\t\t'Tiro Telugu','Toppan Bunkyu Gothic','Toppan Bunkyu Mincho','Tsukushi A Round Gothic','Tsukushi B Round Gothic',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Yuanti SC','Yuanti TC','Yuppy SC','Yuppy TC',\n\t\t\t// legacy\n\t\t\t\t// https://searchfox.org/mozilla-central/rev/f53c09a22edc700ab1a9eaaf4da0f0dd9f11bff3/gfx/thebes/CoreTextFontList.cpp#38-44\n\t\t\t'Athelas','Marion','Seravek','Superclarendon',\n\t\t\t// downloadable but I have it\n\t\t\t'Iowan Old Style',\n\t\t\t// NFI\n\t\t\t'BlinkMacSystemFont',\n\t\t],\n\t\tmacfaces: [\n\t\t\t'Avenir Next Condensed Bold','Avenir Next Condensed Demi Bold',\n\t\t\t\t'Avenir Next Condensed Heavy','Avenir Next Condensed Medium','Avenir Next Condensed Ultra Light',\n\t\t\t'Brill Roman Bold','Brill Roman Medium','Brill Roman Semibold',\n\t\t\t// works in font-family if we drop 'roman'\n\t\t\t'Brill Roman','Publico Headline Roman','Publico Text Roman',\n\t\t\t// weighted/styled: all these ones have no regular/normal\n\t\t\t'Apple LiGothic Medium','Apple LiSung Light',\n\t\t\t'Brill Italic Bold Italic','Brill Italic Medium Italic','Brill Italic Semibold Italic', //'Brill Italic Italic',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'Hiragino Sans CNS W3','Hiragino Sans CNS W6',\n\t\t\t'Hiragino Sans TC W3','Hiragino Sans TC W6',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy',\n\t\t\t'Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'Publico Headline Black','Publico Headline Bold', //'Publico Headline Black Italic','Publico Headline Bold Italic','Publico Headline Italic',\n\t\t\t'Publico Text Bold','Publico Text Semibold', //'Publico Text Bold Italic','Publico Text Italic','Publico Text Semibold Italic',\n\t\t\t'Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light',\n\t\t\t'Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium',\n\t\t\t'YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t// SyntaxError: An invalid or illegal string was specified\n\t\t\t\t// 'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium',\n\t\t],\n\t\twindows: [\n\t\t\t'Arial Nova','Georgia Pro','Gill Sans Nova','Ink Free','Neue Haas Grotesk Text Pro','Rockwell Nova',\n\t\t\t'Segoe Fluent Icons','Segoe UI Variable Display','Segoe UI Variable Small','Segoe UI Variable Text',\n\t\t\t'Simplified Arabic Fixed','Verdana Pro',\n\t\t\t'HoloLens MDL2 Assets', // removed from base FF136+ 1942883\n\t\t\t// 1957317: Noto Sans CJK HK/JP/KR/SC/TC and Noto Serif CJK HK/JP/KR/SC/TC\n\t\t\t\t// ^ not added: windows only installs region-specific e.g. Noto Sans TC, not language-specific e.g. Noto Sans CJK TC\n\t\t\t// other\n\t\t\t'Sans Serif Collection', // win11 only: 1858357\n\t\t\t'MingLiU_MSCS','細明體_MSCS','MingLiU_MSCS-ExtB','細明體_MSCS-ExtB', // win11 only? 2016678\n\t\t\t'Cascadia Code','Cascadia Mono', // MS downloads 11\n\t\t\t'Arial Unicode MS','MS Reference Specialty','MS Outlook','Gill Sans','Gill Sans MT', // MS products\n\t\t\t'OpenSymbol', // openoffice\n\t\t\t'Amiri', // libreoffice\n\t\t],\n\t\twindowsfaces: [\n\t\t\t'Arial Nova Cond','Arial Nova Light',\n\t\t\t'Georgia Pro Black','Georgia Pro Cond','Georgia Pro Light','Georgia Pro Semibold',\n\t\t\t'Gill Sans Nova Cond','Gill Sans Nova Light',\n\t\t\t//'Neue Haas Grotesk Text Pro UltraThin','Neue Haas Grotesk Text Pro Light',\n\t\t\t'Rockwell Nova Cond','Rockwell Nova Extra Bold','Rockwell Nova Light',\n\t\t\t'Verdana Pro Black','Verdana Pro Light',\n\t\t\t// the above are all supplemental, so to properly test font face is not leaking\n\t\t\t// we need to add some non-weighted fonts: not much to work with :-(\n\t\t\t'Ink Free','Sans Serif Collection',\n\t\t\t// MS products\n\t\t\t'Arial Unicode MS','MS Reference Specialty','MS Outlook','Gill Sans','Gill Sans MT',\n\t\t],\n\t\twindowsoffscreen: [\n\t\t\t'Arial Nova','Georgia Pro','Gill Sans Nova','Ink Free','Neue Haas Grotesk Text Pro','Rockwell Nova',\n\t\t\t'Segoe Fluent Icons','Segoe UI Variable Display','Segoe UI Variable Small','Segoe UI Variable Text',\n\t\t\t'Simplified Arabic Fixed','Verdana Pro',\n\t\t\t// win11\n\t\t\t'Sans Serif Collection',\n\t\t\t// MS products\n\t\t\t'Arial Unicode MS','MS Reference Specialty','MS Outlook','Gill Sans','Gill Sans MT',\n\t\t\t// MS downloads\n\t\t\t'Cascadia Code','Cascadia Mono', // 11\n\t\t],\n\t},\n\t// platform detection\n\tplatform: {\n\t\t// gecko\n\t\tgecko: [\n\t\t\t// note: '-apple-menu','-apple-status-bar' not detected in gecko\n\t\t\t'-apple-system',\n\t\t\t'Dancing Script', // android fallback // 'Roboto'\n\t\t\t'MS Shell Dlg \\\\32',\n\t\t],\n\t\t// non-gecko: all expected fonts that wouldn't likely be found on other platforms\n\t\tandroid: ['Dancing Script'],\n\t\tmac: [\n\t\t\t'-apple-menu','-apple-status-bar','-apple-system', // wekbit detects these 3, blink doesn't\n\t\t\t'Apple Color Emoji','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t],\n\t\twindows: [\n\t\t\t'MS Gothic','MS PGothic','MS UI Gothic','Microsoft JhengHei','Microsoft New Tai Lue',\n\t\t\t'Microsoft PhagsPa','Microsoft Tai Le','Microsoft YaHei','Microsoft Yi Baiti',\n\t\t],\n\t\t// combined non-gecko\n\t\tall: [],\n\t},\n}\n\nfunction set_fntList() {\n\tlet build = (gLoad || isFontSizesMore !== isFontSizesPrevious)\n\n\tif (build) {\n\t\tisFontSizesPrevious = isFontSizesMore\n\t\tfntData = {\n\t\t\tall: {},\n\t\t\tfaces: {base: [], baselang: [], fpp: [], full: [], unexpected: []},\n\t\t\tfamily: {\n\t\t\t\tbase: [], baselang: [], bundled: [], control: [], 'control_name': [],\n\t\t\t\tfpp: [], full: [], generic: [], 'generic_name': [], system: [], unexpected: []\n\t\t\t},\n\t\t\toffscreen: {base: [], baselang: [], fpp: [], full: [], unexpected: []},\n\t\t}\n\n\t\t// fntString\n\t\tif (isBB || 'android' == isOS || 'linux' == isOS) {\n\t\t\t//  isBB: all maxed: linux: 120/140 | windows 131/183 | mac 135/150\n\t\t\t// nonBB: seems to work well so far\n\t\t\tfntString = '-'\n\t\t} else {\n\t\t\tif ('windows' == isOS) {fntString = 'MōΩ' // 158/190 = max\n\t\t\t} else {fntString = 'Mō-'} // mac: 441/464 = almost max | Mōá?- + tofu = 443\n\t\t}\n\t\tfntString = fntString +'\\uffff'\n\n\t\t// baseSize: add fallback for misconfigured/missing\n\t\t// fntPlatformFont: expected + isn't/can't be blocked\n\t\t\t// when used forces a single fallback font to compare to instead of trying up to\n\t\t\t// three (monospace, sans-serif, serif). No entropy is lost as lack of aliases or\n\t\t\t// FontSubtitutes (e.g. Tahoma) is expected - we _know_ they are there\n\t\tlet baseSize = ['monospace','sans-serif','serif']\n\t\tfntPlatformFont = undefined // reset\n\t\tif ('windows' == isOS) {\n\t\t\tif (!isFontSizesMore) {\n\t\t\t\t// blink doesn't allow MS Shell Dlg \\\\32\n\t\t\t\tfntPlatformFont = isGecko ? 'MS Shell Dlg \\\\32' : 'Tahoma'\n\t\t\t}\n\t\t\tif (isBB) {fntPlatformFont = undefined} // force BB to detect all fonts for health\n\t\t\tbaseSize = [\n\t\t\t\t'monospace, Consolas, Courier, \\\"Courier New\\\", \\\"Lucida Console\\\"',\n\t\t\t\t'sans-serif, Arial',\n\t\t\t\t'serif, Times, Roman'\n\t\t\t]\n\t\t} else if ('mac' == isOS) {\n\t\t\tif (isGecko && !isFontSizesMore) {fntPlatformFont = '-apple-system'}\n\t\t\tbaseSize = [\n\t\t\t\t'monospace, Menlo, \\\"Courier New\\\", Monaco',\n\t\t\t\t'sans-serif',\n\t\t\t\t'serif'\n\t\t\t]\n\t\t} else if ('android' == isOS) {\n\t\t\t// Roboto is not guaranteed unless Android 9+\n\t\t\tif (!isFontSizesMore) {fntPlatformFont = 'Dancing Script'}\n\t\t}\n\n\t\t// control: 1-pass or 3-pass | control_name: remove fallbacks e.g. 'serif, X' -> 'serif'\n\t\tfntData.family.control = fntPlatformFont == undefined ? baseSize : [fntPlatformFont]\n\t\tfntData.family.control.forEach(function(name) {fntData.family['control_name'].push(name.split(',')[0])})\n\t\t\n\t\t// generic: expand baseSize\n\t\t\t// don't use isStyles as that will duplicate and complicate existing entries with fallbacks\n\t\t\t// note: 'fantasy' = not set in gecko (checked Feb 2026) see 536004#c2 but lets cover other engines\n\t\tbaseSize = baseSize.concat(['cursive','fantasy','fangsong','math','system-ui'])\n\t\tbaseSize = baseSize.concat(isSystemFont)\n\t\tif (fntPlatformFont !== undefined) {baseSize.push(fntPlatformFont)}\n\t\tfntData.family.generic = baseSize.sort()\n\t\tbaseSize.forEach(function(name) {fntData.family['generic_name'].push(name.split(',')[0])})\n\n\t\t// font-family\n\t\tif (isOS !== undefined) {\n\t\t\tfntFake = '--00'+ rnd_string()\n\t\t\tlet array = [], aExtraTests = ['faces','offscreen']\n\t\t\tif ('android' == isOS) {\n\t\t\t\t// notos\n\t\t\t\tfntMaster.android.notoboth.forEach(function(fnt) {array.push('Noto Sans '+ fnt, 'Noto Serif '+ fnt)})\n\t\t\t\tfntMaster.android.notosans.forEach(function(fnt) {array.push('Noto Sans '+ fnt)})\n\t\t\t\tfntMaster.android.notoserif.forEach(function(fnt) {array.push('Noto Serif '+ fnt)})\n\t\t\t\t// +extras\n\t\t\t\tarray = array.concat(fntMaster.system[isOS])\n\t\t\t\tfntData.family.full = array\n\t\t\t\tfntData.family.full.push(fntFake)\n\t\t\t} else if (isBB) {\n\t\t\t\t// TB44461: Segoe MDL2 Assets\n\t\t\t\tif (140 == isVer) {\n\t\t\t\t\tfntMaster.allowlist.windows = fntMaster.allowlist.windows.filter(x => !['Segoe MDL2 Assets'].includes(x))\n\t\t\t\t\tfntMaster.blocklist.windows.push('Segoe MDL2 Assets')\n\t\t\t\t}\n\t\t\t\t// desktop BB\n\t\t\t\tlet aBundled = []\n\t\t\t\tfntMaster.bundled.notoboth.forEach(function(fnt) {aBundled.push('Noto Sans '+ fnt, 'Noto Serif '+ fnt)})\n\t\t\t\tfntMaster.bundled.notosans.forEach(function(fnt) {aBundled.push('Noto Sans '+ fnt)})\n\t\t\t\tfntMaster.bundled.notoserif.forEach(function(fnt) {aBundled.push('Noto Serif '+ fnt)})\n\t\t\t\taBundled = aBundled.concat(fntMaster.bundled[isOS])\n\t\t\t\tarray = array.concat(aBundled)\n\t\t\t\tfntData.family.bundled = array\n\t\t\t\tfntData.family.system = fntMaster.allowlist[isOS]\n\t\t\t\tarray = array.concat(fntMaster.allowlist[isOS])\n\t\t\t\tfntData.family.base = array\n\t\t\t\tfntMaster.blocklist[isOS].push(fntFake)\n\t\t\t\tfntData.family.unexpected = fntMaster.blocklist[isOS]\n\t\t\t\tarray = array.concat(fntMaster.blocklist[isOS])\n\t\t\t\tfntData.family.full = array\n\t\t\t\t// faces.offscreen\n\t\t\t\taExtraTests.forEach(function(item) {\n\t\t\t\t\tlet key = isOS + item\n\t\t\t\t\tarray = fntMaster.allowlist[key]\n\t\t\t\t\tif (undefined !== array) {\n\t\t\t\t\t\tlet aUnexpected = fntMaster.blocklist[key]\n\t\t\t\t\t\tfntData[item].base = array.sort()\n\t\t\t\t\t\tfntData[item].unexpected = aUnexpected.sort()\n\t\t\t\t\t\tfntData[item].full = array.concat(aUnexpected).sort()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// desktop FF\n\t\t\t\tarray = fntMaster.base[isOS]\n\t\t\t\tfntData.family.base = array\n\t\t\t\tarray = array.concat(fntMaster.baselang[isOS])\n\t\t\t\tfntData.family.fpp = array // windows FPP (mac FPP = same as base)\n\t\t\t\tfntData.family.baselang = fntMaster.baselang[isOS]\n\t\t\t\tfntMaster.system[isOS].push(fntFake)\n\t\t\t\tarray = array.concat(fntMaster.system[isOS])\n\t\t\t\tfntData.family.unexpected = fntMaster.system[isOS]\n\t\t\t\tfntData.family.full = array\n\t\t\t\t// faces/offscreen\n\t\t\t\taExtraTests.forEach(function(item) {\n\t\t\t\t\tlet key = isOS + item, \n\t\t\t\t\tarray = fntMaster.base[key]\n\t\t\t\t\tif (undefined !== array) {\n\t\t\t\t\t\tlet aBaseLang = fntMaster.baselang[key]\n\t\t\t\t\t\tlet aFPP = undefined == aBaseLang ? array : array.concat(aBaseLang)\n\t\t\t\t\t\tlet aUnexpected = fntMaster.system[key]\n\t\t\t\t\t\tfntData[item].base = array.sort()\n\t\t\t\t\t\tif (undefined !== aBaseLang) {\n\t\t\t\t\t\t\tfntData[item].baselang = aBaseLang.sort()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (undefined !== aBaseLang) {fntData[item].fpp = aFPP.sort()}\n\t\t\t\t\t\tfntData[item].unexpected = aUnexpected.sort()\n\t\t\t\t\t\tfntData[item].full = aFPP.concat(aUnexpected).sort()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\t// -control from lists\n\t\t\tif (fntPlatformFont !== undefined) {\n\t\t\t\tlet fntKeys = ['base','full','fpp','system','bundled']\n\t\t\t\tfntKeys.forEach(function(key) {\n\t\t\t\t\tif (fntData.family[key] !== undefined) {\n\t\t\t\t\tlet array = fntData.family[key]\n\t\t\t\t\t fntData.family[key] = array.filter(x => ![fntPlatformFont].includes(x))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\t// dupes\n\t\t\tif (gLoad) {\n\t\t\t\tlet aCheck = fntData.family.full\n\t\t\t\taCheck = dedupeArray(aCheck)\n\t\t\t\tif (aCheck.length !== fntData.family.full.length) {\n\t\t\t\t\tlog_alert(12, 'set_fntList', 'dupes in '+ isOS, isScope, true) // persist since we only do this once\n\t\t\t\t\tfntData.family.full = aCheck\n\t\t\t\t}\n\t\t\t}\n\t\t\t// sort\n\t\t\tfntData.family.bundled.sort()\n\t\t\tfntData.family.system.sort()\n\t\t\tfntData.family.unexpected.sort()\n\t\t\tfntData.family.base.sort()\n\t\t\tfntData.family.baselang.sort()\n\t\t\tfntData.family.fpp.sort()\n\t\t\tfntData.family.full.sort()\n\n\t\t\t// fntBtn\n\t\t\tlet fntobj = {}, obj = fntData.family\n\t\t\tif (!isGecko || 'android' == isOS || !isBB && 'linux' == isOS) {\n\t\t\t\tfntobj = fntData.family.full\n\t\t\t} else if (isBB || 'windows' == isOS) {\n\t\t\t\tif (isBB) {\n\t\t\t\t\tfntobj = {'1. system': {count: obj.system.length, 'fonts': obj.system},\n\t\t\t\t\t\t'2. bundled': {count: obj.bundled.length, 'fonts': obj.bundled},\n\t\t\t\t\t\t'3. allowlist': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfntobj = {'1. kBaseFonts': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t'2. kLangPackFonts': {count: obj.baselang.length, 'fonts': obj.baselang},\n\t\t\t\t\t'3. FPP': {count: obj.fpp.length, 'fonts': obj.fpp},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfntobj['4. unexpected'] = {count: obj.unexpected.length, 'fonts': obj.unexpected}\n\t\t\t\tfntobj['5. tested'] = {count: obj.full.length, 'fonts': obj.full}\n\t\t\t} else {\n\t\t\t\t//mac\n\t\t\t\tfntobj = {'1. kBaseFonts': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t'2. unexpected': {count: obj.unexpected.length, 'fonts': obj.unexpected},\n\t\t\t\t\t'3. tested': {count: obj.full.length, 'fonts': obj.full},\n\t\t\t\t}\n\t\t\t}\n\t\t\tfntData.family['summary'] = fntobj\n\t\t\tfntBtn = addButton(12, 'fonts_'+ isOS, fntData.family.full.length +' fonts', 'btnc', 'lists')\n\n\t\t\t// faces/offscreen\n\t\t\taExtraTests.forEach(function(item) {\n\t\t\t\tlet obj = fntData[item]\n\t\t\t\tif (obj.full.length) {\n\t\t\t\t\tif (!isGecko) {\n\t\t\t\t\t\tfntobj = obj.full\n\t\t\t\t\t} else if (isBB) {\n\t\t\t\t\t\tfntobj = {'1. allowlist': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t\t\t'2. unexpected': {count: obj.unexpected.length, 'fonts': obj.unexpected},\n\t\t\t\t\t\t\t'3. tested': {count: obj.full.length, 'fonts': obj.full}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ('mac' == isOS) {\n\t\t\t\t\t\tfntobj = {'1. kBaseFonts': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t\t\t'2. unexpected': {count: obj.unexpected.length, 'fonts': obj.unexpected},\n\t\t\t\t\t\t\t'3. tested': {count: obj.full.length, 'fonts': obj.full}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfntobj = {'1. kBaseFonts': {count: obj.base.length, 'fonts': obj.base},\n\t\t\t\t\t\t\t'2. kLangPackFonts': {count: obj.baselang.length, 'fonts': obj.baselang},\n\t\t\t\t\t\t\t'3. FPP': {count: obj.fpp.length, 'fonts': obj.fpp},\n\t\t\t\t\t\t\t'4. unexpected': {count: obj.unexpected.length, 'fonts': obj.unexpected},\n\t\t\t\t\t\t\t'5. tested': {count: obj.full.length, 'fonts': obj.full}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfntData[item]['summary'] = fntobj\n\t\t\t\t\tfntBtn = addButton(12, 'font_' + item +'_'+ isOS, fntData[item].full.length +' '+ item, 'btnc', 'lists') + fntBtn \n\t\t\t\t}\n\t\t\t})\n\t\t\t// all: gecko only since non-gecko is an array and gecko is an object\n\t\t\tif (isGecko && fntData.faces.full.length) {\n\t\t\t\tlet total\n\t\t\t\tfor (const k of Object.keys(fntData.faces.summary)) {\n\t\t\t\t\tlet array = [], aFace = fntData.faces.summary[k].fonts\n\t\t\t\t\taFace.forEach(function(font){\n\t\t\t\t\t\t// strip out regular and other text needed for font face\n\t\t\t\t\t\tif (' Regular' == font.slice(-8)) {font = font.slice(0,-8)}\n\t\t\t\t\t\tarray.push(font)\n\t\t\t\t\t})\n\t\t\t\t\t// dedupe\n\t\t\t\t\tarray = dedupeArray(array)\n\t\t\t\t\tlet newkey = k\n\t\t\t\t\tif (isBB) {\n\t\t\t\t\t\tnewkey = ((k.slice(0,1)) * 1) + 2\n\t\t\t\t\t\tnewkey += k.slice(1)\n\t\t\t\t\t}\n\t\t\t\t\tarray = array.concat(fntData.family.summary[newkey].fonts)\n\t\t\t\t\ttry {array = array.concat(fntData.offscreen.summary[k].fonts)} catch {}\n\t\t\t\t\tarray = dedupeArray(array)\n\n\t\t\t\t\t// remove unexpected if they're in the allow/expected lot\n\t\t\t\t\tif (k.includes('unexpected')) {\n\t\t\t\t\t\tlet ignore = isBB ? '1. allowlist' : ('mac' == isOS ? '1. kBaseFonts'  :'3. FPP')\n\t\t\t\t\t\tarray = array.filter(x => !fntData.all[ignore].fonts.includes(x))\n\t\t\t\t\t} else if (k.includes('tested')) {\n\t\t\t\t\t\ttotal = array.length\n\t\t\t\t\t}\n\t\t\t\t\tfntData.all[k] = {count: array.length, 'fonts': array.sort()}\n\t\t\t\t}\n\t\t\t\tfntBtn = addButton(12, 'total_fonts_'+ isOS, total +' total', 'btnc', 'lists') + fntBtn \n\t\t\t}\n\t\t}\n\t}\n\t// bail\n\tif (isOS == undefined) {return}\n\n\t// fnt*Btn data\n\tif (gRun || build) {\n\t\taddDetail('fonts_'+ isOS, fntData.family.summary, 'lists')\n\t\taddDetail('font_faces_'+ isOS, fntData.faces.summary, 'lists')\n\t\taddDetail('font_offscreen_'+ isOS, fntData.offscreen.summary, 'lists')\n\t\taddDetail('total_fonts_'+ isOS, fntData.all, 'lists')\n\t}\n}\n\nfunction set_fntList_mini() {\n\t// populate fntMaster.platform.all for non-gecko\n\ttry {\n\t\tlet aTemp = fntMaster.platform.android\n\t\taTemp = aTemp.concat(fntMaster.platform.mac)\n\t\taTemp = aTemp.concat(fntMaster.platform.windows)\n\t\tfntMaster.platform.all = aTemp.sort()\n\t} catch(e) {}\n}\n\nfunction get_document_fonts(METRIC) {\n\tfntDocEnabled = false // reset\n\tlet value, data, notation = default_red, fntTest = '\\\"test font name\\\"'\n\ttry {\n\t\tif (runSE) {foo++}\n\t\t// dedicated div, hardcoded style\n\t\tlet font = getComputedStyle(dom.tzpDocFont).getPropertyValue('font-family'),\n\t\t\tfontnoquotes = font.slice(0, fntTest.length - 2) // ext may strip quotes marks\n\t\tfntDocEnabled = (font == fntTest || fontnoquotes == fntTest ? true : false)\n\t\t// test setting it: catches e.g. chameleon\n\t\tdom.tzpDiv.style.fontFamily = fntTest\n\t\tlet font2 = getComputedStyle(dom.tzpDiv).getPropertyValue('font-family')\n\t\t// tidy\n\t\tvalue = (fntDocEnabled ? zE : zD) +' | '+ font + (font !== font2 ? ' | '+ font2 : '')\n\t\t// notate: only default if exact match\n\t\tif ('enabled | \\\"test font name\\\"' == value) {notation = default_green}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(12, METRIC, value,'', notation, data)\n\treturn\n}\n\nfunction get_font_notation(METRIC, data) {\n\tif (!isGecko) {return ''}\n\n\tlet badnotation = isBB ? bb_red : rfp_red\n\tlet goodnotation = isBB ? bb_green : rfp_green\n\tlet isSizes = 'font_sizes' == METRIC\n\tlet obj = isSizes ? fntData.family : ('font_faces' == METRIC ? fntData.faces : fntData.offscreen)\n\tlet notation = goodnotation\n\n\tlet aNotInBase = data, aMissing = [], aMissingSystem = []\n\taNotInBase = aNotInBase.filter(x => !obj.base.includes(x))\n\tif (isBB) {\n\t\taMissing = isSizes ? obj.bundled : obj.base\n\t\taMissing = aMissing.filter(x => !data.includes(x))\n\t\tif (isSizes && obj.system.length) {\n\t\t\taMissingSystem = obj.system\n\t\t\taMissingSystem = aMissingSystem.filter(x => !data.includes(x))\n\t\t}\n\t}\n\n\tlet count = aNotInBase.length + aMissing.length + aMissingSystem.length\n\tif (count > 0) {\n\t\tlet tmpName = (isSizes ? 'font_names' : METRIC) +'_health', tmpobj = {}\n\t\tlet suffix = isSizes ? '_bundled' : ''\n\t\tif (aMissing.length) {tmpobj['missing' + suffix] = aMissing}\n\t\tif (aMissingSystem.length) {tmpobj['missing_system'] = aMissingSystem}\n\t\tif (aNotInBase.length) {\n\t\t\tif (isBB) {\n\t\t\t\ttmpobj['unexpected'] = aNotInBase\n\t\t\t} else {\n\t\t\t\t// FF: break into FPP vs non FPP\n\t\t\t\ttmpobj['unexpected'] = {}\n\t\t\t\tlet aLangPack = aNotInBase.filter(x => obj.baselang.includes(x))\n\t\t\t\tlet aOther = aNotInBase.filter(x => !aLangPack.includes(x))\n\t\t\t\tif (aLangPack.length) {tmpobj.unexpected['kLangPackFonts'] = aLangPack}\n\t\t\t\tif (aOther.length) {tmpobj.unexpected['other'] = aOther}\n\t\t\t}\n\t\t}\n\t\taddDetail(tmpName, tmpobj)\n\t\tlet brand = isTB ? (isMB ? 'MB' : 'TB') : 'RFP'\n\t\tnotation = addButton('bad', tmpName, \"<span class='health'>\"+ cross + '</span> '+ count +' '+ brand)\n\t\t// BB doesn't have baselang but check FPP fallback regardless\n\t\tif (isFPPFallback && obj.baselang.length) {\n\t\t\t// FFP if all unexpected are in baselang then we're fpp_green\n\t\t\tlet aNotInBaseLang = aNotInBase.filter(x => !obj.baselang.includes(x))\n\t\t\tif (aNotInBaseLang.length == 0) {notation = fpp_green}\n\t\t}\n\t}\n\treturn notation\n}\n\nfunction get_fonts_base(METRICB, selected) {\n\t// selected can be: 'unknown', 'n/a' or any of the domrect or perspective or pixel\n\n\t// if n/a: try to calculate selected: same logic as font_sizes\n\t\t// in order, exclude lies + errors, limit to domrect + perspective or pixel\n\tif (selected == zNA) {\n\t\tlet oDomList = {0: 'domrectbounding', 1: 'domrectclient', 2: 'domrectboundingrange', 3: 'domrectclientrange'}\n\t\tlet order = [\n\t\t\t'domrectbounding','domrectboundingrange','domrectclient','domrectclientrange','perspective','pixel'\n\t\t]\n\t\tif (isSmart) {\n\t\t\tfor (const k of Object.keys(oDomList)) {\n\t\t\t\tif (!aDomRect[k]) {order = order.filter(x => ![oDomList[k]].includes(x))} // remove from list\n\t\t\t}\n\t\t}\n\t\tfor (let i=0; i < order.length; i++) {\n\t\t\tlet value = order[i]\n\t\t\tif (!fntBaseInvalid.hasOwnProperty(order[i])) {selected = value; break}\n\t\t}\n\t}\n\tlet isSelected = selected !== zNA && selected !== 'unknown'\n\t// if we have fntBaseMin data _and_ nothing is invalid, output one of each method group\n\tlet useMin = fntBaseMin.length > 0 && Object.keys(fntBaseInvalid).length == 0\n\n\t// rebuild base fonts sizes: fntBase is already ordered: do first so hashes are correct\n\t// for each base combine w + h, replace with fntBaseInvalid errors (but not lies)\n\t// note: reported (non FP data) is grouped with all method data (and extensions can cause mayhem)\n\t\t// but select (a FP metric) should be grouped with only the method used, so we build them seperately\n\tlet reportedTemp = {}, reportedHash = {}, reportedBase = {}\n\tlet selectTemp = {}, selectHash = {}, selectBase = {}\n\n\tfor (const base in fntBase) { // for each base e.g. serif\n\t\treportedTemp[base] = {}\n\t\tfor (const m of Object.keys(fntBase[base])) { // for each method e.g. domrectbounding\n\t\t\tif ('Width' == m.slice(-5)) { // for each pair\n\t\t\t\tlet method = m.slice(0,-5), value\n\t\t\t\tif(zLIE !== fntBaseInvalid[method]) {value = fntBaseInvalid[method]}\n\t\t\t\tif (undefined == value) {value = [fntBase[base][m], fntBase[base][method +'Height']]}\n\t\t\t\tif (useMin && fntBaseMin.includes(method) || !useMin) {\n\t\t\t\t\treportedTemp[base][method] = value\n\t\t\t\t\tif (isSelected && method == selected) {selectTemp[base] = value}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// group by hash\n\tfor (const base in reportedTemp) { // reported and selected have the same object keys\n\t\t// reported\n\t\tlet tmphash = mini(reportedTemp[base])\n\t\tif (undefined == reportedHash[tmphash]) {\n\t\t\treportedHash[tmphash] = {'bases': [base], 'data': reportedTemp[base]}\n\t\t} else {\n\t\t\treportedHash[tmphash].bases.push(base)\n\t\t}\n\t\t// select\n\t\tif (isSelected) {\n\t\t\ttmphash = mini(selectTemp[base])\n\t\t\tif (undefined == selectHash[tmphash]) {\n\t\t\t\tselectHash[tmphash] = {'bases': [base], 'data': selectTemp[base]}\n\t\t\t} else {\n\t\t\t\tselectHash[tmphash].bases.push(base)\n\t\t\t}\n\t\t}\n\t}\n\t// use base as keys | bases are already sorted since fntData.family.generic is too\n\tfor (const h in reportedHash) {\n\t\tlet newhash = mini(reportedHash[h].data)\n\t\treportedBase[reportedHash[h].bases.join(' ')] = {'hash': newhash, 'metrics': reportedHash[h].data}\n\t}\n\tif (isSelected) {\n\t\tfor (const h in selectHash) {\n\t\t\tlet newhash = mini(selectHash[h].data)\n\t\t\tselectBase[selectHash[h].bases.join(' ')] = selectHash[h].data\n\t\t}\n\t}\n\t//* reported\n\t//console.log('reportedTemp', reportedTemp)\n\t//console.log('reportedHash', reportedHash)\n\t//console.log('reportedBase', reportedBase)\n\t//*/\n\t//* select\n\t//console.log('selectTemp', selectTemp)\n\t//console.log('selectHash', selectHash)\n\t//console.log('selectBase', selectBase)\n\t//*/\n\n\t// display all that hard work!!\n\t\t// unless we had an error which means we never end up here, we will have fntBase data\n\tlet btnAll = addButton(12, METRICB +'_reported', Object.keys(reportedBase).length +'/'+ fntData.family.generic_name.length)\n\taddDetail(METRICB +'_reported', reportedBase)\n\taddDisplay(12, METRICB+ '_reported', mini(reportedBase), btnAll)\n\n\t// add selected/unknown/n/a\n\tif (isSelected) {\n\t\tlet newobj = {}\n\t\tnewobj[selected] = selectBase\n\t\tlet hash = mini(newobj)\n\t\tlet btn = addButton(12, METRICB, Object.keys(selectBase).length +'/'+ fntData.family.generic_name.length)\n\t\taddBoth(12, METRICB, hash, btn,'', newobj)\n\t} else {\n\t\taddBoth(12, METRICB, selected)\n\t}\n}\n\nconst get_fonts_faces = (METRIC, METRICD, aFonts) => new Promise(resolve => {\n\t// testing non regular fonts + font face leaks (i.e not just light/black etc)\n\t\t// it is problematic to test weighted fonts because you don't know\n\t\t// if it's synthesized, a variable font, or an actual font(name)\n\t// blocking document fonts does not affect this test\n\n\tlet t0 = nowFn()\n\t// main test we don't pass an array of font names otherwise it's a test\n\tlet isMain = undefined == aFonts\n\n\tlet data ='', btn='', notation =''\n\tfunction exit(value, btn, notation, data) {\n\t\tif (isMain) {\n\t\t\taddBoth(12, METRIC, value, btn, notation, data)\n\t\t\tfntHealth.push(notation)\n\t\t\tlog_perf(12, METRIC, t0)\n\t\t}\n\t\treturn resolve(data)\n\t}\n\tlet typeCheck = typeFn(window.FontFace)\n\tif ('undefined' == typeCheck) {exit(typeCheck); return}\n\n\t// start with a letter or it throws \"SyntaxError: An invalid or illegal string was specified\"\n\tlet fntFaceFake = 'a'+ rnd_string()\n\tasync function testLocalFontFamily(font) {\n\t\ttry {\n\t\t\tconst fontFace = new FontFace(font, `local(\"${font}\")`)\n\t\t\tawait fontFace.load()\n\t\t\treturn fntFaceFake\n\t\t} catch(e) {\n\t\t\treturn e+''\n\t\t}\n\t}\n\tfunction getLocalFontFamily(font) {\n\t\treturn new FontFace(font, `local(\"${font}\")`)\n\t\t\t.load()\n\t\t\t.then((font) => font.family)\n\t\t\t//.then((font) => font.unicodeRange +': '+ font.family)\n\t\t\t.catch(() => null)\n\t}\n\tfunction loadFonts(fonts) {\n\t\treturn Promise.all(fonts.map(getLocalFontFamily))\n\t\t.then(list => list.filter(font => font !== null))\n\t}\n\n\tPromise.all([\n\t\ttestLocalFontFamily(fntFaceFake),\n\t]).then(function(res){\n\t\tlet value =''\n\t\tlet fntList = isMain ? fntData.faces.full : aFonts\n\t\tlet isNotate = fntList.length > 0\n\t\t// only notate if we're testing it\n\t\tlet badnotation = !isNotate ? '' : isBB ? bb_red : rfp_red\n\t\tlet goodnotation = !isNotate ? '' : isBB ? bb_green : rfp_green\n\n\t\ttry {\n\t\t\tlet test = res[0]\n\t\t\tif (fntFaceFake == test) {throw zErrInvalid +'fake font detected'\n\t\t\t} else if ('NetworkError: A network error occurred.' !== test) {throw test\n\t\t\t} else if (!isNotate) {\n\t\t\t\texit(zNA, btn, badnotation, data)\n\t\t\t} else {\n\t\t\t\tloadFonts(fntList).then(function(results){\n\t\t\t\t\tif (results.length) {\n\t\t\t\t\t\tdata = []\n\t\t\t\t\t\t// some engines record quotes: strip them out\n\t\t\t\t\t\tresults.forEach(function(item){\n\t\t\t\t\t\t\titem = item.replaceAll('\"',''); data.push(item)\n\t\t\t\t\t\t})\n\t\t\t\t\t\tvalue = mini(results)\n\t\t\t\t\t\tbtn = addButton(12, METRIC, results.length)\n\t\t\t\t\t\tif (isMain && fntList.length) {\n\t\t\t\t\t\t\tnotation = get_font_notation(METRIC, data)\n\t\t\t\t\t\t\t// enumerate fonts across all font tests\n\t\t\t\t\t\t\tsDetail[isScope][METRICD] = sDetail[isScope][METRICD].concat(data)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// ToDo: once we allow fontFace in BB this will always be badnotation\n\t\t\t\t\t\tnotation = isBB ? goodnotation : badnotation\n\t\t\t\t\t\tvalue = 'none'\n\t\t\t\t\t\tif (!isMain) {data = 'none'}\n\t\t\t\t\t}\n\t\t\t\t\texit(value, btn, notation, data)\n\t\t\t\t})\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tif (isMain) {exit(log_error(12, METRIC, e), btn, notation, zErr)} else {exit(zErr, btn, notation, zErr)}\n\t\t}\n\t})\n})\n\nfunction get_fonts_offscreen(METRIC, METRICD) {\n\t// test RFP/FPP do not leak\n\t\t// note: document fonts does not affect this test\n\tlet t0 = nowFn()\n\tlet fntList = fntData.offscreen.full\n\tif (0 == fntList.length) {\n\t\taddBoth(12, METRIC, zNA)\n\t\treturn\n\t}\n\n\t// do a single pass\n\t\t// use fntPlatformFont if possible otherwise use monospace (with fallbacks if possible)\n\tlet fntOffscreen = 'monospace'\n\tif (undefined !== fntPlatformFont) {\n\t\tfntOffscreen = fntPlatformFont\n\t} else if ('windows' == isOS) {\n\t\tfntOffscreen = 'monospace, Consolas, Courier, \\\"Courier New\\\", \\\"Lucida Console\\\"'\n\t} else if ('mac' == isOS) {\n\t\tfntOffscreen = 'monospace, Menlo, \\\"Courier New\\\", Monaco'\n\t}\n\n\tlet value = '', data ='', btn='', notation = rfp_red\n\tlet badnotation = isBB ? bb_red : rfp_red\n\tlet goodnotation = isBB ? bb_green : rfp_green\n\ttry {\n\t\t// set canvas\n\t\tlet canvas = new OffscreenCanvas(0,0)\n\t\tlet ctx = canvas.getContext('2d')\n\n\t\t// get base\n\t\tctx.font = 'normal normal normal 512px '+ fntOffscreen\n\t\tlet base = ctx.measureText(fntString).width\n\t\t// check base\n\t\tif (runST) {base = undefined}\n\t\tlet typeCheck = typeFn(base)\n\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t// check fake font\n\t\tctx.font = 'normal normal normal 512px '+ fntFake +', '+ fntOffscreen\n\t\tlet fake = ctx.measureText(fntString).width\n\t\tif (runSI) {fake = base}\n\t\tif (fake !== base) {throw zErrInvalid +'fake font detected'}\n\t\t// loop font list\n\t\tdata = []\n\t\tfntList.forEach(function(font){\n\t\t\tctx.font = 'normal normal normal 512px '+ font +', '+ fntOffscreen\n\t\t\tif (ctx.measureText(fntString).width !== base) {data.push(font)}\n\t\t})\n\t\tif (data.length) {\n\t\t\tvalue = mini(data)\n\t\t\tbtn = addButton(12, METRIC, data.length)\n\t\t\tif (fntData.offscreen.base.length) {notation = get_font_notation(METRIC, data)}\n\t\t\t// enumerate fonts across all font tests\n\t\t\tsDetail[isScope][METRICD] = sDetail[isScope][METRICD].concat(data)\n\t\t} else {\n\t\t\t// ToDo: once we allow fontFace in BB this will always be badnotation\n\t\t\tnotation = isBB ? goodnotation : badnotation\n\t\t\tvalue = 'none'\n\t\t}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t\t// tmp until we enable offscreen canvas in BB\n\t\tif (isBB && 'ReferenceError: OffscreenCanvas is not defined' == value) {notation = ''}\n\t}\n\tfntHealth.push(notation)\n\taddBoth(12, METRIC, value, btn, notation, data)\n\tlog_perf(12, METRIC, t0)\n\treturn\n}\n\nconst get_fonts_size = (isMain = true, METRIC = 'font_sizes') => new Promise(resolve => {\n\t/* getDimensions code based on https://github.com/abrahamjuliot/creepjs */\n\t// reset\n\tfntBaseInvalid = {}\n\tfntBaseMin = []\n\tconst id = 'element-fp'\n\t// note: element-fp has a transform: this only affects domrect\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tconst doc = document // or iframe.contentWindow.document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\t\tdom[id].innerHTML = `\n\t\t\t<style>\n\t\t\t#${id}-detector {\n\t\t\t\t--font: '';\n\t\t\t\tposition: absolute !important;\n\t\t\t\tleft: -9999px!important;\n\t\t\t\tfont-size: ` + fntSize + ` !important;\n\t\t\t\tfont-style: normal !important;\n\t\t\t\tfont-weight: normal !important;\n\t\t\t\tfont-stretch: normal !important;\n\t\t\t\tletter-spacing: normal !important;\n\t\t\t\tline-break: auto !important;\n\t\t\t\tline-height: normal !important;\n\t\t\t\ttext-transform: none !important;\n\t\t\t\ttext-align: left !important;\n\t\t\t\ttext-decoration: none !important;\n\t\t\t\ttext-shadow: none !important;\n\t\t\t\twhite-space: normal !important;\n\t\t\t\tword-break: normal !important;\n\t\t\t\tword-spacing: normal !important;\n\t\t\t\t/* in order to test scrollWidth, clientWidth, etc. */\n\t\t\t\tpadding: 0 !important;\n\t\t\t\tmargin: 0 !important;\n\t\t\t\t/* in order to test inlineSize and blockSize */\n\t\t\t\twriting-mode: horizontal-tb !important;\n\t\t\t\t/* for transform and perspective */\n\t\t\t\ttransform-origin: unset !important;\n\t\t\t\tperspective-origin: unset !important;\n\t\t\t}\n\t\t\t#${id}-detector::after {\n\t\t\t\tfont-family: var(--font);\n\t\t\t\tcontent: '`+ fntString +`';\n\t\t\t}\n\t\t\t</style>\n\t\t\t<span id=\"${id}-detector\"></span>`\n\n\t\tconst span = doc.getElementById(`${id}-detector`)\n\t\tconst pixelsToNumber = pixels => +pixels.replace('px','')\n\t\tconst originPixelsToNumber = pixels => 2*pixels.replace('px', '')\n\t\tconst style = getComputedStyle(span)\n\t\tconst range = document.createRange()\n\t\trange.selectNode(span)\n\n\t\t// set parameters\n\t\tlet fntGeneric = [], fntTest = [], fntControl = [], fntControlObj = {}, oTests = {}, aTests = []\n\t\tconst aSkipCheck = ['domrectbounding', 'domrectclient','domrectboundingrange','domrectclientrange','client','offset']\n\t\tlet oSkip = {}, isSkip = false\n\t\tfor (let i=0; i < aSkipCheck.length; i++) {oSkip[i] = false}\n\t\tif (isMain) {\n\t\t\tfntData.family.control.forEach(function(item) {\n\t\t\t\tlet key = item.split(',')[0]\n\t\t\t\tfntControl.push(key)\n\t\t\t\tfntControlObj[key] = item\n\t\t\t})\n\t\t\tfntGeneric = fntData.family.generic\n\t\t\tfntTest = fntData.family.full\n\t\t\t// match display order so btn links = first of each hash\n\t\t\toTests = {\n\t\t\t\t'client': {},\n\t\t\t\t'offset': {},\n\t\t\t\t'scroll': {},\n\t\t\t\t'pixel': {},\n\t\t\t\t'pixelsize': {},\n\t\t\t\t'perspective': {},\n\t\t\t\t'transform': {},\n\t\t\t\t'domrectbounding': {},\n\t\t\t\t'domrectboundingrange': {},\n\t\t\t\t'domrectclient': {},\n\t\t\t\t'domrectclientrange': {},\n\t\t\t}\n\t\t\t// if one dimension key throws e.g. a Reference Error then the whole lot thows\n\t\t\t// e.g. clientHeight: foo++, all three size metrics are errors: \"font_sizes/_base/_methods\": \"ReferenceError: foo is not defined\"\n\t\t\t// e.g. uBO's AOPR: *##+js(aopr, Range.prototype.getClientRects)\n\t\t\t// note:: we already catch TypeErrors in validity tests later\n\t\t\tlet el = dom.tzpRect\n\t\t\tfor (let i=0; i < aSkipCheck.length; i++) {\n\t\t\t\tlet name = aSkipCheck[i]\n\t\t\t\t// Hmmm: these still create a complete meltdown instead of trapping individual errors\n\t\t\t\t\t// 1: *##+js(aopr, Element.prototype.getClientRects)\n\t\t\t\t\t// 2: !*##+js(aopr, Range.prototype.getBoundingClientRect)\n\t\t\t\ttry {\n\t\t\t\t\t//foo++\n\t\t\t\t\tlet obj = {}\n\t\t\t\t\tif (0 == i) {\n\t\t\t\t\t\t//foo++\n\t\t\t\t\t\tobj = el.getBoundingClientRect()\n\t\t\t\t\t} else if (1 == i) {\n\t\t\t\t\t\tobj = el.getClientRects()[0] //\n\t\t\t\t\t} else if (i < 4) {\n\t\t\t\t\t\tlet range = document.createRange(); range.selectNode(el)\n\t\t\t\t\t\tif (2 == i) {\n\t\t\t\t\t\t\tobj = range.getBoundingClientRect() //\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tobj = range.getClientRects()[0] \n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ('client' == name) {\n\t\t\t\t\t\tobj = {'height': el.clientHeight, 'width': el.clientWidth}\n\t\t\t\t\t} else if ('offset' == name) {\n\t\t\t\t\t\tobj = {'height': el.offsetHeight, 'width': el.offsetWidth}\n\t\t\t\t\t}\n\t\t\t\t} catch(e) {\n\t\t\t\t\t// if we susccessfully complete _something_ then we can add\n\t\t\t\t\t// errors and display for each skipped item later\n\t\t\t\t\toSkip[i] = e+''\n\t\t\t\t\tdelete oTests[name]\n\t\t\t\t\tisSkip = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isSkip) {'skipped', console.log(oSkip)}\n\t\t} else {\n\t\t\tfntControl = ['monospace', \"sans-serif\", \"serif\"]\n\t\t\tfntControlObj = {\n\t\t\t\t\"monospace\": 'monospace, Consolas, Courier, \"Courier New\", \"Lucida Console\"',\n\t\t\t\t\"sans-serif\": 'sans-serif, Arial',\n\t\t\t\t\"serif\": 'serif, \"Times New Roman\\\"',\n\t\t\t}\n\t\t\tfntGeneric = fntControl\n\t\t\tfntTest = ['--00'+ rnd_string()]\n\t\t\tlet src = isGecko ? 'gecko' : 'all'\n\t\t\tfntTest = fntTest.concat(fntMaster.platform[src])\n\t\t\toTests = {'perspective': {}}\n\t\t}\n\n\t\tlet getDimensions = (span, style) => {\n\t\t\tconst transform = style.transformOrigin.split(' ')\n\t\t\tconst perspective = style.perspectiveOrigin.split(' ')\n\t\t\tconst dimensions = {\n\t\t\t\t// keep sorted for font_sizes_base_reported\n\t\t\t\tclientHeight: oSkip[4] ? '' : span.clientHeight,\n\t\t\t\tclientWidth: oSkip[4] ? '' : span.clientWidth,\n\t\t\t\tdomrectboundingHeight: oSkip[0] ? '' : span.getBoundingClientRect().height,\n\t\t\t\tdomrectboundingWidth: oSkip[0] ? '' : span.getBoundingClientRect().width,\n\t\t\t\tdomrectboundingrangeHeight: oSkip[1] ? '' : range.getBoundingClientRect().height,\n\t\t\t\tdomrectboundingrangeWidth: oSkip[1] ? '' : range.getBoundingClientRect().width,\n\t\t\t\tdomrectclientHeight: oSkip[2] ? '' : span.getClientRects()[0].height,\n\t\t\t\tdomrectclientWidth: oSkip[2] ? '' : span.getClientRects()[0].width,\n\t\t\t\tdomrectclientrangeHeight: oSkip[3] ? '' : range.getClientRects()[0].height,\n\t\t\t\tdomrectclientrangeWidth: oSkip[3] ? '' : range.getClientRects()[0].width,\n\t\t\t\toffsetHeight: oSkip[5] ? '' : span.offsetHeight,\n\t\t\t\toffsetWidth: oSkip[5] ? '' : span.offsetWidth,\n\t\t\t\tperspectiveHeight: originPixelsToNumber(perspective[1]),\n\t\t\t\tperspectiveWidth: originPixelsToNumber(perspective[0]),\n\t\t\t\tpixelHeight: pixelsToNumber(style.height),\n\t\t\t\tpixelWidth: pixelsToNumber(style.width),\n\t\t\t\tpixelsizeHeight: pixelsToNumber(style.blockSize),\n\t\t\t\tpixelsizeWidth: pixelsToNumber(style.inlineSize),\n\t\t\t\tscrollHeight: span.scrollHeight,\n\t\t\t\tscrollWidth: span.scrollWidth,\n\t\t\t\ttransformHeight: originPixelsToNumber(transform[1]),\n\t\t\t\ttransformWidth: originPixelsToNumber(transform[0]),\n\t\t\t}\n\t\t\treturn dimensions\n\t\t}\n\t\t// simulate errors: don't test isFontSizesMore not used in production\n\t\tif (runSF && isMain && !isFontSizesMore) {\n\t\t\tgetDimensions = (span, style) => {\n\t\t\t\tconst transform = style.transformOrigin.split(' ')\n\t\t\t\tconst perspective = style.perspectiveOrigin.split(' ')\n\t\t\t\tconst dimensions = {\n\t\t\t\t\tclientHeight: span.clientHeight, // same size: engineered below\n\t\t\t\t\tclientWidth: span.clientWidth,\n\t\t\t\t\tdomrectboundingHeight: null, // TypeError: empty string x null\n\t\t\t\t\tdomrectboundingWidth: '',\n\t\t\t\t\tdomrectboundingrangeHeight: range.getBoundingClientRect().height,\n\t\t\t\t\tdomrectboundingrangeWidth: range.getBoundingClientRect().width,\n\t\t\t\t\tdomrectclientHeight: span.getClientRects()[0].height, // fake font detected: engineered below\n\t\t\t\t\tdomrectclientWidth: span.getClientRects()[0].width,\n\t\t\t\t\tdomrectclientrangeHeight: 100, // none\n\t\t\t\t\tdomrectclientrangeWidth: 200,\n\t\t\t\t\toffsetHeight: NaN, // TypeError: NaN (same)\n\t\t\t\t\toffsetWidth: NaN,\n\t\t\t\t\tperspectiveHeight: undefined, // TypeError: Infinity x undefined (different)\n\t\t\t\t\tperspectiveWidth: Infinity,\n\t\t\t\t\tpixelHeight: pixelsToNumber(style.height),\n\t\t\t\t\tpixelWidth: pixelsToNumber(style.width),\n\t\t\t\t\tpixelsizeHeight: pixelsToNumber(style.blockSize),\n\t\t\t\t\tpixelsizeWidth: pixelsToNumber(style.inlineSize),\n\t\t\t\t\tscrollHeight: 0, // Invalid: width or height < 1\n\t\t\t\t\tscrollWidth: 50,\n\t\t\t\t\ttransformHeight: originPixelsToNumber(transform[1]) + ((Math.random() * 100) / 100), // all\n\t\t\t\t\ttransformWidth: originPixelsToNumber(transform[0]),\n\t\t\t\t}\n\t\t\t\treturn dimensions\n\t\t\t}\n\t\t}\n\n\t\t// base sizes\n\t\tfntBase = fntGeneric.reduce((acc, font) => {\n\t\t\tif (isSystemFont.includes(font)) { // not a family\n\t\t\t\tspan.style.setProperty('--font', '')\n\t\t\t\tspan.style.font = font\n\t\t\t} else {\n\t\t\t\tspan.style.font =''\n\t\t\t\tspan.style.setProperty('--font', font)\n\t\t\t}\n\t\t\tconst dimensions = getDimensions(span, style) // this line is where it errors\n\t\t\tacc[font.split(',')[0]] = dimensions // use only first name, i.e w/o fallback\n\t\t\treturn acc\n\t\t}, {})\n\t\tspan.style.font ='' // reset\n\n\t\t// test validity\n\t\tfor (const k of Object.keys(oTests)) {\n\t\t\t// assume we always have fntBase.monospace\n\t\t\tlet wValue = fntBase.monospace[k +'Width'], wType = typeFn(wValue),\n\t\t\t\thValue = fntBase.monospace[k +'Height'], hType = typeFn(hValue)\n\t\t\ttry {\n\t\t\t\tif ('number' !== wType || 'number' !== hType) {\n\t\t\t\t\tthrow zErrType + (wType == hType ? wType : wType +' x '+ hType)\n\t\t\t\t} else if (wValue < 1 || hValue < 1) {throw zErrInvalid + 'width or height < 1'\n\t\t\t\t} else if (wValue == hValue < 1) {throw zErrInvalid + 'width == height'}\n\t\t\t\taTests.push(k)\n\t\t\t} catch(e) {\n\t\t\t\tfntBaseInvalid[k] = zErr\n\t\t\t\toTests[k]['error'] = e+''\n\t\t\t\taddDisplay(12, METRIC +'_'+ k, log_error(12, METRIC +'_'+ k, e))\n\t\t\t}\n\t\t}\n\n\t\t// base only: after validity so we know what to use in lookup\n\t\tif (isMain) {\n\t\t\tif (!fntTest.length || false == fntDocEnabled) {\n\t\t\t\tremoveElementFn(id)\n\t\t\t\treturn resolve('baseonly')\n\t\t\t}\n\t\t}\n\n\t\t// measure\n\t\tif (aTests.length) {\n\t\t\tlet intDetected = 0, intDetectedMax = aTests.length\n\t\t\tfntTest.forEach(font => {\n\t\t\t\tintDetected = 0 // reset per font\n\t\t\t\tfor (const basefont of fntControl) {\n\t\t\t\t\tintDetected = 0 // reset per control\n\t\t\t\t\tspan.style.setProperty('--font', \"'\"+ font +\"', \"+ fntControlObj[basefont])\n\t\t\t\t\tconst style = getComputedStyle(span)\n\t\t\t\t\tconst dimensions = getDimensions(span, style)\n\t\t\t\t\taTests.forEach(function(method) {\n\t\t\t\t\t\tlet wName = method +'Width', hName = method +'Height'\n\t\t\t\t\t\tif (dimensions[wName] != fntBase[basefont][wName] || dimensions[hName] != fntBase[basefont][hName]) {\n\t\t\t\t\t\t\tif (isFontSizesMore) {\n\t\t\t\t\t\t\t\t// every basefont result\n\t\t\t\t\t\t\t\tif (undefined == oTests[method][font]) {oTests[method][font] = {}}\n\t\t\t\t\t\t\t\toTests[method][font][basefont] = [dimensions[wName], dimensions[hName]]\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// always one result per font\n\t\t\t\t\t\t\t\toTests[method][font] = [dimensions[wName], dimensions[hName]]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tintDetected++\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tif (intDetected == intDetectedMax && !isFontSizesMore) {break}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t// exit isOS check\n\t\tif (!isMain) {\n\t\t\tremoveElementFn(id)\n\t\t\treturn resolve(oTests['perspective'])\n\t\t}\n\n\t\t// sim fake font + same sizes\n\t\tif (runSF && !isFontSizesMore) {\n\t\t\toTests['domrectclient'][fntFake] = [700, 800]\n\t\t\tfor (const k of Object.keys(oTests['client'])) {oTests['client'][k] = [700, 800]}\n\t\t}\n\n\t\t// catch more errors\n\t\tfor (const k of Object.keys(oTests)) {\n\t\t\tlet obj = oTests[k],\n\t\t\t\tobjcount = Object.keys(obj).length\n\t\t\ttry {\n\t\t\t\tif (0 == objcount) {throw zErrInvalid +'none'\n\t\t\t\t} else if (objcount == fntData.family.full.length) {throw zErrInvalid +'all'\n\t\t\t\t} else if (obj.hasOwnProperty(fntFake)) {throw zErrInvalid +'fake font detected'}\n\t\t\t} catch(e) {\n\t\t\t\t// we don't have to emmpty it\n\t\t\t\tfor (const prop in obj) {if (obj.hasOwnProperty(prop)) {delete obj[prop]}} \n\t\t\t\tfntBaseInvalid[k] = zErr\n\t\t\t\toTests[k]['error'] = e+''\n\t\t\t\taddDisplay(12, METRIC +'_'+ k, log_error(12, METRIC +'_'+ k, e))\n\t\t\t}\n\t\t}\n\n\t\t// add domrect lies if not already an error\n\t\tif (isSmart) {\n\t\t\t// in order of aDomRect\n\t\t\tlet domrectnames = ['domrectbounding','domrectclient','domrectboundingrange','domrectclientrange']\n\t\t\tfor (let i=0; i < domrectnames.length; i++) {\n\t\t\t\tlet name = domrectnames[i]\n\t\t\t\tif (!aDomRect[i] && undefined == fntBaseInvalid[name]) {fntBaseInvalid[name] = zLIE}\n\t\t\t}\n\t\t}\n\t\t// if we got this far we can add oSkip\n\t\tif (isSkip) {\n\t\t\tfor (const k of Object.keys(oSkip)) {\n\t\t\t\tlet skipErr = oSkip[k], m = aSkipCheck[k]\n\t\t\t\tif ('string' === typeof skipErr) {addDisplay(12, METRIC +'_'+ m, log_error(12, METRIC +'_'+ m, skipErr))}\n\t\t\t}\n\t\t}\n\t\tremoveElementFn(id)\n\t\treturn resolve(oTests)\n\t} catch(e) {\n\t\tremoveElementFn(id)\n\t\tif (isMain) {\n\t\t\tlog_error(12, METRIC, e)\n\t\t\tlog_error(12, METRIC +'_methods', e)\n\t\t\tlog_error(12, METRIC +'_base', e)\n\t\t}\n\t\treturn resolve(zErr)\n\t}\n})\n\nfunction get_fonts(METRIC, METRICD) {\n\t/*\n\t- only notate font_names == not a metric but is picked up health\n\t- sizes we record all errors + lies per method. This is all we need for method\n\t\tresults/entropy - sizes is either something or unknown: so never notate or lies\n\t- sizes_base + sizes_methods: never notate or lies: it is simply a reflection\n\t\tof what happened in sizes\n\t*/\n\n\tlet t0 = nowFn()\n\tconst METRICM = METRIC +'_methods'\n\tconst METRICB = METRIC +'_base'\n\tconst METRICN = 'font_names'\n\tlet badnotation = isBB ? bb_red : rfp_red\n\tlet goodnotation = isBB ? bb_green : rfp_green\n\n\t// functions\n\tfunction exit(value) {\n\t\taddBoth(12, METRIC, value)\n\t\taddBoth(12, METRICM, value)\n\t\tadd_font_names(value)\n\t\tif (value == zNA) {\n\t\t\tget_fonts_base(METRICB, value)\n\t\t} else {\n\t\t\taddBoth(12, METRICB, value)\n\t\t}\n\t\tlog_perf(12, METRIC, t0)\n\t\treturn\n\t}\n\tfunction add_font_names(value) {\n\t\t// fontnames: always notate for health\n\t\t// display only: so always add a lookup\n\t\tsDetail[isScope].lookup[METRICN] = value\n\t\taddDisplay(12, METRICN, value,'', badnotation)\n\t\tfntHealth.push(badnotation)\n\t}\n\n\tget_fonts_size().then(res => {\n\t\t//console.log(\"res\", res)\n\t\t// quick exits\n\t\tlet typeCheck = typeFn(res)\n\t\tif ('string' === typeCheck) {exit(('baseonly' == res ? zNA : zErr)); return}\n\t\tif ('object' !== typeCheck) {log_error(12, METRIC, zErrType + typeCheck); exit(zErr); return}\n\n\t\t// organize oData: note: everything is already sorted\n\t\tlet oData = {}, oValid = {}\n\t\tfor (let name in res) {\n\t\t\tlet data = res[name]\n\t\t\tif (!data.hasOwnProperty('error')) {\n\t\t\t\t// group by hash\n\t\t\t\tlet hash = mini(data)\n\t\t\t\toValid[name] = hash\n\t\t\t\tif (oData[hash] == undefined) {oData[hash] = {'names': [name], 'data': data}\n\t\t\t\t} else {oData[hash].names.push(name)}\n\t\t\t}\n\t\t}\n\n\t\t// per hash: do stuff: font names, same size, handle isFontSizesMore\n\t\tfor (const h of Object.keys(oData)) {\n\t\t\toData[h].datacount = Object.keys(oData[h].data).length\n\t\t\toData[h].datafonts = []\n\t\t\tlet oTmpSize = {}, setSize = new Set(), oGroups = {}\n\t\t\tfor (const f of Object.keys(oData[h].data)) {\n\t\t\t\toData[h].datafonts.push(f)\n\t\t\t\t// only do size buckets if not isFontSizesMore\n\t\t\t\tif (!isFontSizesMore) {\n\t\t\t\t\tlet sizekey = oData[h].data[f].join('x')\n\t\t\t\t\tif (undefined == oTmpSize[sizekey]) {oTmpSize[sizekey] = [f], setSize.add(sizekey)\n\t\t\t\t\t} else {oTmpSize[sizekey].push(f)}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// use size buckets to detect more garbage\n\t\t\tif (isFontSizesMore || setSize.size > 1) {\n\t\t\t\toData[h].sizedata = [] // for detailed items\n\t\t\t\toData[h].sizecount = setSize.size\n\t\t\t\tfor (const k of Object.keys(oTmpSize)) {\n\t\t\t\t\tlet tmpFonts = oTmpSize[k]\n\t\t\t\t\tlet tmpSize = oData[h].data[tmpFonts[0]]\n\t\t\t\t\toData[h].sizedata.push([tmpFonts, tmpSize])\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t(oData[h].names).forEach(function(name) {\n\t\t\t\t\tlet lookup = oData[h].data[oData[h].datafonts[0]]\n\t\t\t\t\tlet error = zErrInvalid +'same size ['+ lookup.join(' x ') +']'\n\t\t\t\t\tfntBaseInvalid[name] = zErr\n\t\t\t\t\taddDisplay(12, METRIC +'_'+ name, log_error(12, METRIC +'_'+ name, error))\n\t\t\t\t})\n\t\t\t\tdelete oData[h]\n\t\t\t}\n\t\t}\n\n\t\t// sync fntBaseInvalid and oValid\n\t\tfor (name in fntBaseInvalid) {delete oValid[name]}\n\t\t\n\t\t// fallback: first valid domrect in order of display as that gets the btn\n\t\t\t// fntBaseInvalid: is errors, plus lies if not an error and isSmart\n\t\tlet selected\n\t\tlet aDomList = ['domrectbounding','domrectboundingrange','domrectclient','domrectclientrange']\n\t\tfor (let i=0; i < aDomList.length; i++) {\n\t\t\tlet domname = aDomList[i]\n\t\t\tif (undefined !== oValid[domname]) {selected = domname; break}\n\t\t}\n\n\t\t// font_size_methods: this gives us any tampering entropy\n\t\t\t// not to be confused with errors/lies which are already recorded\n\t\tlet oMethods = {}, oIndex = {}, counter = 0\n\t\tlet aNames = [ // sorted by expected group then name\n\t\t\t'client','offset','scroll','pixel','pixelsize','perspective','transform',\n\t\t\t'domrectbounding','domrectboundingrange','domrectclient','domrectclientrange',\n\t\t]\n\t\taNames.forEach(function(k) {\n\t\t\tif (undefined !== oValid[k]) {\n\t\t\t\tlet indexKey = oValid[k] // the method hash\n\t\t\t\tif (oIndex[indexKey] == undefined) {\n\t\t\t\t\toIndex[indexKey] = (counter+'').padStart(2,'0'); counter++\n\t\t\t\t}\n\t\t\t\tlet mKey = oIndex[indexKey]\n\t\t\t\tif (oMethods[mKey] == undefined) {\n\t\t\t\t\toMethods[mKey] = [k]\n\t\t\t\t\tfntBaseMin.push(k) // first of each\n\t\t\t\t} else {\n\t\t\t\t\toMethods[mKey].push(k)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\tlet mHash = 'unknown', mBtn ='', mData =''\n\t\tif (Object.keys(oMethods).length) {\n\t\t\tmHash = mini(oMethods); mBtn = addButton(12, METRICM); mData = oMethods\n\t\t}\n\t\taddBoth(12, METRICM, mHash, mBtn, '', mData)\n\n\t\t// fallbacks: matching valid *Number pairs\n\t\tif (selected == undefined) {\n\t\t\tlet items = [['perspective', 'transform'], ['pixel', 'pixelsize']]\n\t\t\tfor (let i=0; i < items.length; i++) {\n\t\t\t\tlet ctrlName = items[i][0], ctrlHash = oValid[ctrlName]\n\t\t\t\tlet testName = items[i][1], testHash = oValid[testName]\n\t\t\t\tif (ctrlHash !== undefined && ctrlHash == testHash) {\n\t\t\t\t\tselected = ctrlName; break // we have a valid *Number pair\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//console.log('oData', oData)\n\t\t//console.log('oValid', oValid)\n\t\t//console.log('fntBaseInvalid', fntBaseInvalid)\n\n\t\t// no more fallbacks\n\t\t// output oData = not-errors\n\t\tfor (const k of Object.keys(oData)) {\n\t\t\tlet aList = oData[k].names\n\t\t\tfor (let i=0; i < aList.length; i++) {\n\t\t\t\tlet method = aList[i]\n\t\t\t\tlet fntmethod = METRIC +'_'+ method\n\t\t\t\t// style + record lies to be consistent\n\t\t\t\tlet isLies = false, btn =''\n\t\t\t\tif ('domrect' == method.slice(0,7)) {\n\t\t\t\t\tisLies = fntBaseInvalid.hasOwnProperty(method)\n\t\t\t\t\tif (isLies) {\n\t\t\t\t\t\t// we don't need to record domrect lie data: just that its a lie\n\t\t\t\t\t\tlog_known(12, fntmethod, zLIE)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//add btn to first of each hash\n\t\t\t\tif (i == 0) {\n\t\t\t\t\taddDetail(fntmethod, oData[k].data)\n\t\t\t\t\tbtn = addButton(12, fntmethod, oData[k].datacount)\n\t\t\t\t\tif (!isFontSizesMore) {\n\t\t\t\t\t\taddDetail(fntmethod +'_grouped', oData[k].sizedata)\n\t\t\t\t\t\tbtn += addButton(12, fntmethod +'_grouped', oData[k].sizecount)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taddDisplay(12, fntmethod, k, btn,'', isLies)\n\n\t\t\t\t// FP data: not lies\n\t\t\t\tif (method == selected) {\n\t\t\t\t\tlet notation =''\n\t\t\t\t\tif (fntData.family.base.length) {notation = get_font_notation(METRIC, oData[k].datafonts)}\n\t\t\t\t\t// names\n\t\t\t\t\tlet btn = addButton(12, METRICN, oData[k].datacount)\n\t\t\t\t\tsDetail[isScope][METRICN] = oData[k].datafonts\n\t\t\t\t\taddDisplay(12, METRICN, mini(oData[k].datafonts), btn, notation)\n\t\t\t\t\tfntHealth.push(notation)\n\t\t\t\t\t// data\n\t\t\t\t\tbtn = addButton(12, METRIC, oData[k].datacount)\n\t\t\t\t\tif (!isFontSizesMore) {\n\t\t\t\t\t\taddDetail(METRIC +'_grouped', oData[k].sizedata)\n\t\t\t\t\t\tbtn += addButton(12, METRIC +'_grouped', oData[k].sizecount)\n\t\t\t\t\t}\n\t\t\t\t\taddBoth(12, METRIC, k, btn,'', oData[k].data)\n\t\t\t\t\t// enumerate fonts across all font tests\n\t\t\t\t\tsDetail[isScope][METRICD] = sDetail[isScope][METRICD].concat(oData[k].datafonts)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// nothing\n\t\tif (!Object.keys(oData).length || selected == undefined) {\n\t\t\taddBoth(12, METRIC, 'unknown')\n\t\t\tadd_font_names('unknown')\n\t\t\tget_fonts_base(METRICB, 'unknown')\n\t\t} else {\n\t\t\tget_fonts_base(METRICB, selected)\n\t\t}\n\n\t\tlog_perf(12, METRIC, t0)\n\t\treturn\n\t})\n}\n\nfunction get_fonts_max(METRIC, isLies) {\n\tlet t0 = nowFn()\n\tlet value = zNA, data ='', btn=''\n\tlet el = dom.tzpFontMax\n\ttry {\n\t\tdata = {}\n\t\tlet range, method\n\t\tisStylesAll.forEach(function(stylename) {\n\t\t\tel.innerHTML = '<span class=\"'+ stylename +'\" style=\"font-size: 20000px\">.</span>'\n\t\t\tlet target = el.children[0]\n\t\t\tmethod = measureFn(target, METRIC)\n\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\tvalue = method.height\n\t\t\tif (runST) {value += ''}\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tif ('number' !== typeCheck) {throw zErrInvalid + 'got '+ typeCheck}\n\t\t\tdata[stylename] = value\n\t\t})\n\t\tvalue = mini(data); btn = addButton(12, METRIC)\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\tel.innerHTML =''\n\taddBoth(12, METRIC, value, btn,'', data, isLies)\n\tlog_perf(12, METRIC, t0)\n\treturn\n}\n\nfunction get_formats() {\n\t// FF105+: layout.css.font-tech.enabled\n\tconst oList = {\n\t\t'font-format': ['collection','embeddedopentype','opentype','svg','truetype','woff','woff2'],\n\t\t'font-tech': ['color-CBDT','color-COLRv0','color-COLRv1','color-SVG','color-sbix',\n\t\t\t'features-aat','features-graphite','features-opentype','incremental','palettes','variations']\n\t}\n\tfor (const k of Object.keys(oList)) {\n\t\tlet list = oList[k]\n\t\tconst METRIC = k\n\t\tlet hash, btn ='', data = []\n\t\ttry {\n\t\t\tif (runSE) {foo++}\n\t\t\tlist.forEach(function(item) {if (CSS.supports(k +'('+ item + ')')) {data.push(item)}})\n\t\t\tif (data.length) {\n\t\t\t\thash = mini(data); btn = addButton(12, METRIC, data.length)\n\t\t\t} else {\n\t\t\t\thash = zNA; data =''\n\t\t\t}\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t\taddBoth(12, METRIC, hash, btn,'', data)\n\t}\n\treturn\n}\n\nfunction get_glyphs(METRIC, isLies) {\n\t/* NOTES\n\tFF131+ nightly: 1900175 + 1403931 ride the train\n\t\t- Enable USER_RESTRICTED for content processes on Nightly\n\t\t- security.sandbox.content.level > 7\n\t\t- this affected (FF win11 at least) clientrect\n\t\t\t- 0x3095 + 0x532D (2 CJK chars)\n\t\t\t- almost always both in every style except cursive never affected\n\t\t\t- only changed in http(s), file:// not affected\n\t\t- so reminder that generally we should always be using https for final testing/analysis\n\t*/\n\tlet t0 = nowFn()\n\t/* Notes: math added FF145+ nightly and FF149+\n\t\tuse isStyles currently = ['cursive','math','monospace','sans-serif','serif','system-ui']\n\n\t\tunique sizes: win11 all system fonts FF\n\t\tsans-serif = 34 + cursive = 66 + serif = 84 + system-ui = 102 + monospace = 112 + fantasy = 115\n\t- all the same: 'emoji','ui-monospace','ui-rounded','ui-sans-serif','ui-serif'\n\t\t- ui-* not added to gecko yet\n\t\t- emoji - we're not testing any emojis here\n\t\t- do not increase unique sizes\n\t- 'fangsong': does not add to unique sizes, we would need a different set of code points\n\t- 'fantasy': only added 3 more sizes\n\t*/\n\n\tconst id = 'element-fp'\n\tlet hash, btn ='', data = {}, strSizes =''\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\t\tdiv.innerHTML = '<span id=\"glyphs-span\" style=\"font-size: 22000px;\"><span id=\"glyphs-slot\"></span></span>'\n\t\tconst span = dom['glyphs-span'], slot = dom['glyphs-slot']\n\n\t\tlet oData = {}, tmpobj = {}, newobj = {}, setSize = new Set()\n\t\tlet methodW, methodH, width, height\n\t\t\n\t\tlet methoddiv, methodspan, rangeH, rangeW // current method\n\n\t\tisStyles.forEach(function(stylename) {\n\t\t\tslot.style.fontFamily = stylename\n\t\t\toData[stylename] = {}\n\t\t\tlet isFirst = stylename == isStyles[0]\n\t\t\tfntCodes.forEach(function(code) {\n\t\t\t\tlet codeString = String.fromCodePoint(code)\n\t\t\t\tslot.textContent = codeString\n\t\t\t\t// always get span width, div height\n\n\t\t\t\t/* use a dedicated function\n\t\t\t\tmethodW = measureFn(div, METRIC)\n\t\t\t\tmethodH = measureFn(span, METRIC)\n\t\t\t\twidth = methodW.width, height = methodH.height\n\t\t\t\t// only typecheck once: first char on first style\n\t\t\t\tif (code == fntCodes[0] && isFirst) {\n\t\t\t\t\tif (undefined !== methodW.error) {throw methodW.errorstring}\n\t\t\t\t\tif (undefined !== methodH.error) {throw methodH.errorstring}\n\t\t\t\t\tif (runST) {width = NaN, height = [1]}\n\t\t\t\t\tlet wType = typeFn(width), hType = typeFn(height)\n\t\t\t\t\tif ('number' !== wType || 'number' !== hType) {\n\t\t\t\t\t\tthrow zErrType + (wType == hType ? wType : wType +' x '+ hType)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//*/\n\n\t\t\t\t// current method\n\t\t\t\tif (isDomRect > 1) {\n\t\t\t\t\trangeH = document.createRange()\n\t\t\t\t\trangeH.selectNode(div)\n\t\t\t\t\trangeW = document.createRange()\n\t\t\t\t\trangeW.selectNode(span)\n\t\t\t\t}\n\t\t\t\tif (isDomRect < 1) { // get a result regardless\n\t\t\t\t\tmethoddiv = div.getBoundingClientRect()\n\t\t\t\t\tmethodspan = span.getBoundingClientRect()\n\t\t\t\t} else if (isDomRect == 1) {\n\t\t\t\t\tmethoddiv = div.getClientRects()[0]\n\t\t\t\t\tmethodspan = span.getClientRects()[0]\n\t\t\t\t} else if (isDomRect == 2) {\n\t\t\t\t\tmethoddiv = rangeH.getBoundingClientRect()\n\t\t\t\t\tmethodspan = rangeW.getBoundingClientRect()\n\t\t\t\t} else if (isDomRect > 2) {\n\t\t\t\t\tmethoddiv = rangeH.getClientRects()[0]\n\t\t\t\t\tmethodspan = rangeW.getClientRects()[0]\n\t\t\t\t}\n\t\t\t\twidth = methodspan.width, height = methoddiv.height\n\t\t\t\t// only typecheck once: first char on first style\n\t\t\t\tif (code == fntCodes[0] && isFirst) {\n\t\t\t\t\tif (runST) {width = NaN, height = [1]}\n\t\t\t\t\tlet wType = typeFn(width), hType = typeFn(height)\n\t\t\t\t\tif ('number' !== wType || 'number' !== hType) {\n\t\t\t\t\t\tthrow zErrType + (wType == hType ? wType : wType +' x '+ hType)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\toData[stylename][code] = [width, height]\n\t\t\t\tsetSize.add(width+'x'+height)\n\t\t\t})\n\t\t})\n\t\tfor (const k of Object.keys(oData)) {\n\t\t\tlet hash = mini(oData[k])\n\t\t\tif (tmpobj[hash] == undefined) {tmpobj[hash] = {'names': [k], 'data': oData[k]}\n\t\t\t} else {tmpobj[hash].names.push(k)}\n\t\t}\n\t\tfor (const k of Object.keys(tmpobj)) {newobj[tmpobj[k].names.join(' ')] = tmpobj[k].data}\n\t\tfor (const k of Object.keys(newobj).sort()) {data[k] = newobj[k]}\n\n\t\thash = mini(data), strSizes = gRun ? setSize.size + ' unique sizes' : ''\n\t\tbtn = addButton(12, METRIC)\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tremoveElementFn(id)\n\taddBoth(12, METRIC, hash, btn,'', data, isLies)\n\tlog_perf(12, METRIC, t0,'', strSizes)\n\treturn\n}\n\nfunction get_graphite(METRIC) {\n\tlet hash, data ='', notation = isBB ? bb_red : ''\n\ttry {\n\t\tif (!fntDocEnabled) {throw zErrInvalid + 'document fonts disabled'}\n\t\t// ToDo: handle when font face is blocked\n\t\tlet el = dom.tzpGraphite,\n\t\t\ttest = el.children[0].offsetWidth,\n\t\t\tcontrol = el.children[1].offsetWidth\n\t\tif (runST) {test = NaN; control = NaN}\n\t\tlet wType = typeFn(test), hType = typeFn(control)\n\t\tif ('number' !== wType || 'number' !== hType) {throw zErrType + wType +' | '+ hType}\n\t\thash = (control == test ? zF : zS)\n\t\tif (isBB) {notation = hash == zS ? bb_standard : bb_safer}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(12, METRIC, hash,'', notation, data)\n\treturn\n}\n\nfunction get_script_defaults(METRIC) {\n\t// this is zoom resistant\n\t\t// except with \"zoom text only\" and you zoom from default\n\n\t// note: isStyles|All doesn't add anything\n\tconst styles = ['monospace','sans-serif','serif']\n\tconst scripts = {\n\t\tarabic: 'ar', armenian: 'hy', bengali: 'bn', cyrillic: 'ru', devanagari: 'hi', ethiopic: 'gez',\n\t\tgeorgian: 'ka', greek: 'el', gujurati: 'gu', gurmukhi: 'pa', hebrew: 'he', japanese: 'ja',\n\t\tkannada: 'kn', khmer: 'km', korean: 'ko', latin: 'en', malayalam: 'ml', mathematics: 'x-math',\n\t\todia: 'or', other: 'my', 'simplified chinese': 'zh-CN', sinhala: 'si', tamil: 'ta', telugu: 'te',\n\t\tthai: 'th', tibetan: 'bo', 'traditional chinese (hong kong)': 'zh-HK',\n\t\t'traditional chinese (taiwan)': 'zh-TW', 'unified canadian syllabary': 'cr',\n\t}\n\n\tlet hash = zNA, btn ='', data ='', notation = default_red\n\t//if (isGecko) {\n\t\tdata = {}\n\t\ttry {\n\t\t\tconst el = dom.tzpScript\n\n\t\t\t// family typecheck\n\t\t\tlet test = getComputedStyle(el).getPropertyValue('font-family')\n\t\t\tif (runST) {test =''}\n\t\t\tlet typeCheck = typeFn(test)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + 'font-family: '+ typeCheck}\n\n\t\t\t// size typecheck\n\t\t\ttest = getComputedStyle(el).getPropertyValue('font-size').trim()\n\t\t\tif (runSI) {test = '16ppx'}\n\t\t\tlet originalvalue = test\n\t\t\ttypeCheck = typeFn(test)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (test.slice(-2) !== 'px') {throw zErrInvalid + 'got '+ originalvalue} // missing px\n\t\t\ttest = test.slice(0, -2)\n\t\t\tif (test.length > 0) {test = test * 1}\n\t\t\tif ('number' !== typeFn(test)) {throw zErrInvalid + 'got '+ originalvalue} // missing number\n\n\t\t\t// loop\n\t\t\tlet tmpdata = {}\n\t\t\tel.style.fontSize ='' // reset size\n\t\t\tfor (const k of Object.keys(scripts)) {\n\t\t\t\tlet lang = scripts[k]\n\t\t\t\tel.style.fontFamily ='' // each lang reset fanily\n\t\t\t\tel.setAttribute('lang', lang)\n\t\t\t\tlet font = getComputedStyle(el).getPropertyValue('font-family')\n\t\t\t\tlet tmp = [font]\n\t\t\t\tstyles.forEach(function(style) {\n\t\t\t\t\tel.style.fontFamily = style\n\t\t\t\t\tlet size = getComputedStyle(el).getPropertyValue('font-size').slice(0,-2)\n\t\t\t\t\ttmp.push(size)\n\t\t\t\t})\n\t\t\t\tlet key = tmp.join('-')\n\t\t\t\tif (tmpdata[key] == undefined) {tmpdata[key] = [k]} else {tmpdata[key].push(k)}\n\t\t\t}\n\t\t\tlet singleKey\n\t\t\tfor (const k of Object.keys(tmpdata).sort()) {data[k] = tmpdata[k]; singleKey = k} // sort obj\n\t\t\thash = mini(data); btn = addButton(12, METRIC)\n\t\t\t// notation\n\t\t\tif ('windows' == isOS && 'e5179dbb' == hash) {notation = default_green\n\t\t\t} else if ('linux' == isOS && 'a4253645' == hash) {notation = default_green\n\t\t\t} else if ('mac' == isOS && '884ca29d' == hash) {notation = default_green\n\t\t\t} else if ('android' == isOS && '632e080a' == hash) {notation = default_green\n\t\t\t}\n\t\t\t// single value\n\t\t\tif (1 === Object.keys(data).length) {hash = singleKey; btn = ''}\n\n\t\t} catch(e) {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t//}\n\taddBoth(12, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_system_fonts(METRIC) {\n\t// 1802957: FF109+: -moz no longer applied but keep for regression testing\n\t\t// add bogus '-default-font' to check they are falling back to actual default\n\tlet oList = {\n\t\t'fonts_moz': [\n\t\t\t'-default-font','-moz-bullet-font','-moz-button','-moz-button-group','-moz-desktop','-moz-dialog','-moz-document',\n\t\t\t'-moz-field','-moz-info','-moz-list','-moz-message-bar','-moz-pull-down-menu','-moz-window','-moz-workspace',\n\t\t],\n\t\t'fonts_system': ['caption','icon','menu','message-box','small-caption','status-bar']\n\t}\n\tlet aProps = ['font-size','font-style','font-weight','font-family']\n\tlet hash, btn ='', data = {}, notation = 'moz_fonts' == METRIC ? default_red : rfp_red\n\n\ttry {\n\t\tlet tmpdata = {}\n\t\tlet el = dom.tzpDiv\n\t\t// typecheck\n\t\tfor (const j of aProps) {\n\t\t\tlet test = getComputedStyle(el)[j]\n\t\t\tif (runST) {test =''}\n\t\t\tlet typeCheck = typeFn(test)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + j +': '+ typeCheck}\n\t\t}\n\t\toList[METRIC].forEach(function(name){\n\t\t\tlet aKeys = []\n\t\t\tel.style.font ='' // always clear in case a font is invalid/deprecated\n\t\t\tel.style.font = name\n\t\t\tfor (const j of aProps) {aKeys.push(getComputedStyle(el)[j])}\n\t\t\tlet key = aKeys.join(' ')\n\t\t\tif (tmpdata[key] == undefined) {tmpdata[key] = [name]} else {tmpdata[key].push(name)}\n\t\t})\n\t\tlet count = 0\n\t\tfor (const k of Object.keys(tmpdata).sort()) {data[k] = tmpdata[k]; count += tmpdata[k].length}\n\t\thash = mini(data)\n\t\t// moz: defaults since at least 115 on win/linux: assume android/mac the same: i.e switch to generic font-families\n\t\tif ('fonts_moz' == METRIC) {\n\t\t\tif (isDesktop) {\n\t\t\t\tif ('fe778289' == hash) {notation = default_green} // 16px normal 400 serif\n\t\t\t} else if ('android' == isOS) {\n\t\t\t\tif ('7e76c987' == hash) {notation = default_green} // 16px normal 400 sans-serif\n\t\t\t}\n\t\t} else {\n\t\t\t// RFP FF128+\n\t\t\tif ('windows' == isOS) {\n\t\t\t\tif ('a75e7a17' == hash) {notation = rfp_green} // 12px normal 400 sans-serif\n\t\t\t} else if ('mac' == isOS) {\n\t\t\t\tif ('0b6c0dbe' == hash) {notation = rfp_green}\n\t\t\t\t/* mac\n\t\t\t\t11px normal 400 -apple-system: [message-box, status-bar],\n\t\t\t\t11px normal 700 -apple-system: [small-caption],\n\t\t\t\t12px normal 400 -apple-system: [icon],\n\t\t\t\t13px normal 400 -apple-system: [caption, menu]\n\t\t\t\t*/\n\t\t\t} else if ('linux' == isOS) {\n\t\t\t\tif (isBB) {\n\t\t\t\t\t// BB14: due to font config aliases\n\t\t\t\t\tif ('ea0ea5d7' == hash) {notation = rfp_green} // 15px normal 400 Arimo\n\t\t\t\t} else {\n\t\t\t\t\tif ('48e3d1b4' == hash) {notation = rfp_green} // 15px normal 400 sans-serif\n\t\t\t\t}\n\t\t\t} else if ('android' == isOS) {\n\t\t\t\tif ('7e83ef35' == hash) {notation = rfp_green} // 12px normal 400 Roboto\n\t\t\t}\n\t\t}\n\t\tif (isSmart) {count = (count +'').padStart(2,' ')} // aesthetics: align the last three \"font\" health metrics\n\t\tbtn = addButton(12, METRIC, Object.keys(data).length +'/'+ count)\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(12, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_textmetrics(METRIC) {\n\t/* https://www.bamsoftware.com/talks/fc15-fontfp/fontfp.html#demo */\n\t/* NOTES\n\tFF86+: 1676966: gfx.font_rendering.fallback.async\n\t\t- set chars directly in HTML to force fallback ASAP\n\t*/\n\tlet t0 = nowFn()\n\tlet oMetrics = {\n\t\tactualboundingbox: ['actualBoundingBoxAscent','actualBoundingBoxDescent','actualBoundingBoxLeft','actualBoundingBoxRight'],\n\t\tbaseline: ['alphabeticBaseline','hangingBaseline','ideographicBaseline'],\n\t\temheight: ['emHeightAscent','emHeightDescent'],\n\t\tfontboundingbox: ['fontBoundingBoxAscent','fontBoundingBoxDescent'],\n\t\t// width is mostly identical to glyphs domrect width (untransformed). A handful of font widths\n\t\t// differ by a tiny amount e.g. ±0.0001220703125 or -0.00006103515625. This is not going to add\n\t\t// much entropy if any, so drop for now\n\t\t//width: ['width'],\n\t}\n\tlet oData = {}, aValid = []\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\t// check supported + type\n\t\tlet aNonsense = ['', Infinity,' ', [], true, undefined, {1:2}, null, 'a']\n\t\tlet canvas = dom.tzpTextmetrics, ctx = canvas.getContext('2d')\n\t\tlet tm = ctx.measureText('a')\n\t\tfor (const k of Object.keys(oMetrics)) {\n\t\t\toData[k] = {}\n\t\t\tlet oSet = new Set()\n\t\t\tfor (const j of oMetrics[k]) {\n\t\t\t\tlet isSupported = runST ? Math.random() < 0.5 : TextMetrics.prototype.hasOwnProperty(j)\n\t\t\t\tif (isSupported) {\n\t\t\t\t\tlet typeCheck = typeFn(tm[j])\n\t\t\t\t\tif (runST && Math.random() < 0.5) {\n\t\t\t\t\t\tlet x = aNonsense[Math.floor(Math.random() *10 )]\n\t\t\t\t\t\ttypeCheck = typeFn(x)\n\t\t\t\t\t}\n\t\t\t\t\tif ('number' !== typeCheck) {\n\t\t\t\t\t\tisSupported = zErr\n\t\t\t\t\t\tlet suffix = 'baseline' == k ? j.slice(0, j.length-8) : j.slice(k.length)\n\t\t\t\t\t\tif ('width' == k) {\n\t\t\t\t\t\t\taddBoth(12, METRIC +'_'+ k, log_error(12, METRIC +'_'+ k, zErrType + typeCheck))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog_error(12, METRIC +'_'+ k +'_'+ suffix.toLowerCase(), zErrType + typeCheck)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toData[k][j] = isSupported\n\t\t\t\toSet.add(isSupported)\n\t\t\t}\n\t\t\tif (1 == oSet.size && oSet.has(true)) {aValid.push(k) // test\n\t\t\t} else if (1 == oSet.size && oSet.has(false)) {addBoth(12, METRIC +'_'+ k, zNA) // not supported\n\t\t\t} else if ('width' == k) { // error: we already added width\n\t\t\t} else if (1 == oSet.size && oSet.has(zErr)) {addBoth(12, METRIC +'_'+ k, zErr +'s') // errors\n\t\t\t} else {\n\t\t\t\tlet summary = []\n\t\t\t\tfor (const j of oMetrics[k]) {summary.push(oData[k][j])}\n\t\t\t\taddBoth(12, METRIC +'_'+ k, summary.join(', ')) // mixed\n\t\t\t}\n\t\t}\n\t\tif (0 == aValid.length) {return}\n\n\t\tlet styles = isStyles\n\t\t// don't use 'none': this is default style + font per style for each language\n\t\t\t// and is already present in covering monospace/sans-serif/serif\n\t\t\t// fantasy vs sans-serif | fangsong vs serif both add very little\n\n\t\toData = {} // clear\n\t\taValid.forEach(function(k){\n\t\t\toData[k] = {}\n\t\t\tstyles.forEach(function(s){oData[k][s] = {}})\n\t\t})\n\t\tlet aSet = [], aList = ['actualboundingbox', 'width']\n\t\taList.forEach(function(m) {if (aValid.includes(m)) {aSet.push(m)}})\n\t\tlet bSet = [], bList = ['baseline', 'emheight', 'fontboundingbox']\n\t\tbList.forEach(function(m) {if (aValid.includes(m)) {bSet.push(m)}})\n\n\t\tstyles.forEach(function(s) { // each style\n\t\t\tctx.font = 'normal normal 22000px '+ s\n\t\t\tif (aSet.length) {\n\t\t\t\tfntCodes.forEach(function(code) { // each code\n\t\t\t\t\tlet codeString = String.fromCodePoint(code)\n\n\t\t\t\t\ttm = ctx.measureText(codeString)\n\t\t\t\t\t// textmetrics\n\t\t\t\t\taSet.forEach(function(k){\n\t\t\t\t\t\tlet data = [], props = oMetrics[k]\n\t\t\t\t\t\tprops.forEach(function(p) {data.push(tm[p])})\n\t\t\t\t\t\toData[k][s][code] = data\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (bSet.length) {\n\t\t\t\ttm = ctx.measureText('a')\n\t\t\t\tbSet.forEach(function(k){\n\t\t\t\t\tlet data = [], props = oMetrics[k]\n\t\t\t\t\tprops.forEach(function(p) {data.push(tm[p])})\n\t\t\t\t\toData[k][s] = data // we're only getting a single codepoint\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t\t//console.log(oData)\n\n\t\taValid.forEach(function(k) {\n\t\t\tlet tmpobj = {}, newobj = {}, data = {}, isLies = false\n\t\t\t// group hashes\n\t\t\tfor (const s of Object.keys(oData[k])) {\n\t\t\t\tlet hash = mini(oData[k][s])\n\t\t\t\tif (undefined == tmpobj[hash]) {tmpobj[hash] = {'data': oData[k][s], 'names': [s]}\n\t\t\t\t} else {tmpobj[hash].names.push(s)}\n\t\t\t}\n\t\t\tfor (const k of Object.keys(tmpobj)) {newobj[tmpobj[k].names.join(' ')] = tmpobj[k].data} // group by name\n\t\t\tfor (const k of Object.keys(newobj).sort()) {data[k] = newobj[k]} // sort by name\n\t\t\toMetrics[k].forEach(function(name){if (isProxyLie('TextMetrics.' + name)) {isLies = true}})\n\t\t\tlet hash = mini(data), btn = addButton(12, METRIC +'_'+ k)\n\t\t\taddBoth(12, METRIC +'_'+ k, hash, btn,'', data, isLies)\n\t\t})\n\t} catch(e) {\n\t\tfor (const k of Object.keys(oMetrics)) {\n\t\t\taddBoth(12, METRIC +'_'+ k, log_error(12, METRIC +'_'+ k, e))\n\t\t}\n\t}\n\tlog_perf(12, METRIC, t0)\n\ttry {dom.tzpTextmetrics.height = 0} catch {} // hide the fixed canvas after use\n\treturn\n}\n\nfunction get_widget_fonts(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input\n/*\n\tlet aList = [\n\t\t'button','checkbox','color','date','datetime-local','email','file','hidden','image','month','number',\n\t\t'password','radio','range','reset','search','select','submit','tel','text','textarea','time','url','week',\n\t]\n*/\n\tlet aProps = ['font-family','font-size']\n\tlet hash, btn='', data = {}, notation = rfp_red\n\ttry {\n\t\tlet tmpdata = {}\n\t\tlet target = dom.tzpWidget\n\t\tfor (let i=0; i < target.childElementCount; i++) {\n\t\t\tlet el = target.children[i], name = '' !== el.id ? el.id.slice(3) : el.type\n\t\t\tlet aKeys = []\n\t\t\tfor (const j of aProps) {\n\t\t\t\tlet value = getComputedStyle(el)[j]\n\t\t\t\t// type check first item\n\t\t\t\tif (0 == i) {\n\t\t\t\t\tif (runST) {value =''}\n\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\tif ('string' !== typeCheck) {throw zErrType + j +': '+ typeCheck}\n\t\t\t\t}\n\t\t\t\taKeys.push(value)\n\t\t\t}\n\t\t\tlet key = aKeys.join(' ')\n\t\t\tif (tmpdata[key] == undefined) {tmpdata[key] = [name]} else {tmpdata[key].push(name)}\n\t\t}\n\t\tlet count = 0\n\t\tfor (const k of Object.keys(tmpdata).sort()) {data[k] = tmpdata[k]; count += tmpdata[k].length}\n\t\thash = mini(data)\n\t\tbtn = addButton(12, METRIC, Object.keys(data).length +'/'+ count)\n\t\t// RFP FF128+\n\t\tif ('windows' == isOS && '24717aa8' == hash) {notation = rfp_green\n\t\t/*monospace 13.3333px: [date, datetime-local, time],\n\t\t\tmonospace 13px: [textarea],\n\t\t\tsans-serif 13.3333px: [19 items],\n\t\t\tsans-serif 13px: [image]*/\n\t\t} else if ('mac' == isOS && '12e7f88a' == hash) {notation = rfp_green\n\t\t/*-apple-system 13.3333px: [19 items],\n\t\t\tmonospace 13.3333px: [date, datetime-local, time],\n\t\t\tmonospace 13px: [textarea],\n\t\t\tsans-serif 13px: [image] */\n\t\t} else if ('linux' == isOS) {\n\t\t\tif (isBB) {\n\t\t\t// BB14: due to font config aliases\n\t\t\t/*Arimo 13.3333px: [19 items],\n\t\t\t\tmonospace 12px: [textarea],\n\t\t\t\tmonospace 13.3333px: [date, datetime-local, time],\n\t\t\t\tsans-serif 13px: [image]*/\n\t\t\t\tif ('edeba276' == hash) {notation = rfp_green}\n\t\t\t} else {\n\t\t\t/*monospace 12px: [textarea],\n\t\t\t\tmonospace 13.3333px: [date, datetime-local, time],\n\t\t\t\tsans-serif 13.3333px: [19 items],\n\t\t\t\tsans-serif 13px: [image]*/\n\t\t\t\tif ('99054729' == hash) {notation = rfp_green}\n\t\t\t}\n\t\t} else if ('android' == isOS && '0833dc19' == hash) {notation = rfp_green\n\t\t/*Roboto 13.3333px: [19 items],\n\t\t\tmonospace 12px: [textarea],\n\t\t\tmonospace 13.3333px: [date, datetime-local, time],\n\t\t\tsans-serif 13px: [image]*/\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(12, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nconst get_woff2 = (METRIC) => new Promise(resolve => {\n\t// check\n\tlet typeCheck = typeFn(window.FontFace)\n\tif ('undefined' == typeCheck) {\n\t\taddBoth(12, METRIC, typeCheck)\n\t\treturn resolve()\n\t} else {\n\t\ttry {\n\t\t\tconst supportsWoff2 = (function(){\n\t\t\t\tconst font = new FontFace('t', 'url(\"data:font/woff2;base64,d09GMgABAAAAAADwAAoAAAAAAiQAAACoAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAALAogOAE2AiQDBgsGAAQgBSAHIBuDAciO1EZ3I/mL5/+5/rfPnTt9/9Qa8H4cUUZxaRbh36LiKJoVh61XGzw6ufkpoeZBW4KphwFYIJGHB4LAY4hby++gW+6N1EN94I49v86yCpUdYgqeZrOWN34CMQg2tAmthdli0eePIwAKNIIRS4AGZFzdX9lbBUAQlm//f262/61o8PlYO/D1/X4FrWFFgdCQD9DpGJSxmFyjOAGUU4P0qigcNb82GAAA\") format(\"woff2\")', {});\n\t\t\t\tfont.load().catch(err => {\n\t\t\t\t\t// NetworkError: A network error occurred. < woff2 disabled/downloadable | fonts blocked e.g. uBO\n\t\t\t\t\t// ReferenceError: FontFace is not defined < layout.css.font-loading-api.enabled\n\t\t\t\t\taddDisplay(12, METRIC, log_error(12, METRIC, err))\n\t\t\t\t})\n\t\t\t\treturn font.status == 'loaded' || font.status == 'loading'\n\t\t\t})()\n\t\t\tlet value = (supportsWoff2 ? zS : zF)\n\t\t\taddBoth(12, METRIC, value)\n\t\t\treturn resolve()\n\t\t} catch(e) {\n\t\t\taddBoth(12, METRIC, e,'','', zErrLog)\n\t\t\treturn resolve()\n\t\t}\n\t}\n})\n\nconst outputFonts = () => new Promise(resolve => {\n\tif (gLoad) {\n\t\tlet strDisplay\n\t\ttry {\n\t\t\tlet aDisplay = []\n\t\t\tfntCodes.forEach(function(code) {\n\t\t\t\tlet codeString = String.fromCodePoint(code)\n\t\t\t\taDisplay.push('<span class=\"s99 monospace smaller\" dir=\"ltr\">'+ code.slice(2) + sc\n\t\t\t\t\t+'</span> <span class=\"bold bigger\"> '+ codeString +' </span>'\n\t\t\t\t)\n\t\t\t})\n\t\t\tstrDisplay = aDisplay.join('  ')\n\t\t} catch(e) {\n\t\t\tstrDisplay = '<span class=\"mono\">'+ e+'</span>'\n\t\t}\n\t\taddDisplay(12, 'glyphs_visual', strDisplay)\n\t}\n\tif (gRun && sectionIgnore.includes('fonts')) {return resolve()}\n\n\tlet METRICD = 'font_detection'\n\tsDetail[isScope][METRICD] = [] // reset/clear\n\tfntHealth = []\n\n\tset_fntList()\n\tPromise.all([\n\t\tget_document_fonts('document_fonts'), // sets fntDocEnabled\n\t\tget_script_defaults('script_defaults'),\n\t\tget_fonts('font_sizes', METRICD), // uses fntDocEnabled\n\t\tget_system_fonts('fonts_moz'),\n\t\tget_system_fonts('fonts_system'),\n\t\tget_widget_fonts('fonts_widget'),\n\t\tget_formats(),\n\t\tget_woff2('woff2'),\n\t\tget_graphite('graphite'), // uses fntDocEnabled\n\t]).then(function(){\n\t\t// allow more time for font async fallback\n\t\tlet isLies = isDomRect == -1\n\t\tPromise.all([\n\t\t\tget_fonts_max('font_sizes_max', isLies),\n\t\t\tget_fonts_faces('font_faces',METRICD),\n\t\t\tget_glyphs('glyphs', isLies),\n\t\t\tget_textmetrics('textmetrics'),\n\t\t\tget_fonts_offscreen('font_offscreen', METRICD),\n\t\t]).then(function(){\n\t\t\tif (fntBtn.length) {addDisplay(12, 'fntBtn', fntBtn)}\n\t\t\t// enumerated fonts over all font tests\n\t\t\tlet array = sDetail[isScope][METRICD]\n\t\t\tif (array.length) {\n\t\t\t\t//\n\t\t\t\tlet color = 12\n\t\t\t\tif (isGecko && isSmart && 3 == fntHealth.length) {\n\t\t\t\t\tcolor = 'bad'\n\t\t\t\t\t// if font_names, font_faces and font_offscreen are the same and green\n\t\t\t\t\t// i.e rfp_green x 3 or fpp_green x 3, bb_green x 3\n\t\t\t\t\t//dedupe\n\t\t\t\t\tfntHealth = dedupeArray(fntHealth)\n\t\t\t\t\tif (1 == fntHealth.length) {\n\t\t\t\t\t\tlet value = fntHealth[0]\n\t\t\t\t\t\tif (value == bb_green || value == rfp_green || value == fpp_green) {color = 'good'}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tarray = dedupeArray(array); array.sort()\n\t\t\t\tsDetail[isScope][METRICD] = array\n\t\t\t\taddDisplay(12, METRICD, addButton(color, METRICD, array.length +' fonts'))\n\t\t\t}\n\t\t\treturn resolve()\n\t\t})\n\t})\n})\n\ncountJS(12)\n"
  },
  {
    "path": "js/generic.js",
    "content": "'use strict';\ndom = getUniqueElements()\n\nfunction getUniqueElements() {\n\tconst dom = document.getElementsByTagName('*')\n\treturn new Proxy(dom, {\n\t\tget: function(obj, prop) {return obj[prop]},\n\t\tset: function(obj, prop, val) {obj[prop].textContent = `${val}`; return true}\n\t})\n}\n\n/*** GENERIC ***/\n\nfunction measureFn(target, metric) {\n\tlet range, method, type = isDomRect\n\t//type = 2 // test\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tif (type > 1) {range = document.createRange(); range.selectNode(target)}\n\t\tif (type < 1) {method = target.getBoundingClientRect() // get a result regardless\n\t\t} else if (type == 1) {method = target.getClientRects()[0]\n\t\t} else if (type == 2) {method = range.getBoundingClientRect()\n\t\t} else if (type > 2) {method = range.getClientRects()[0]\n\t\t}\n\t\treturn method\n\t} catch(e) {\n\t\treturn {'error': true, 'errorstring': e+''}\n\t}\n}\n\nconst newFn = x => typeof x != 'string' ? x : new Function(x)()\nfunction nowFn() {if (isPerf) {return performance.now()}; return}\nfunction rnd_string() {return Math.random().toString(36).substring(2, 15)}\nfunction rnd_number() {return Math.floor((Math.random() * (99999-10000))+10000)}\nfunction rnd_word(len = 5) {\n\tlet str = ''\n\tfor (let i=0; i < len; i++) {str += Math.floor(Math.random() * 25 + 10).toString(36)}\n\treturn str\n}\nfunction removeElementFn(id) {try {dom[id].remove()} catch {}}\nfunction addProxyLie(value) {sData[SECT99].push(value)}\nfunction isProxyLie(value) {\n\t// ensure we only _use_ tampering in gecko smart mode\n\t\t// sect99 is now populated based on isProtoProxy which includes all gecko and can include other select engines\n\t\t// isSmart and isSmartDataMode are reset each run in smartFn\n\tif (isSmart || isSmartDataMode) {return sData[SECT99].includes(value)}\n\treturn false\n}\nfunction smartFn(type) {\n\t// reset\n\tisFile = 'file:' == location.protocol\n\tisSmartDataMode = false\n\tisSmart = false\n\t// calculate\n\tif (isGecko && isVer >= isSmartMin) {\n\t\tif ('early' == type) {\n\t\t\t// we do not know isBB yet\n\t\t\tif (isSmartAllowed) {isSmart = true}\n\t\t} else {\n\t\t\t// must be smart: but we can create blocks, set criteria for what to allow\n\t\t\t\t// e.g. to block TB15 due to too much noise during development\n\t\t\t\t// make sure to cater for isSmartAllowed so it can be overriden via console for reruns\n\t\t\t//if (isSmartAllowed || !isTB) {isSmart = true} // example\n\t\t\tisSmart = true\n\t\t}\n\t\t//console.log(type, isSmart)\n\t\tisSmartDataMode = !isSmart // isSmartDataMode must be the opposite\n\t\tif ('final' == type && isSmartDataMode) {run_basic('data-only')}\n\t}\n}\n\nfunction typeFn(item, isSimple = false) {\n\t// return a more detailed result\n\tlet type = typeof item\n\tif ('string' === type) {\n\t\tif (!isSimple) {\n\t\t\tif ('' === item) {type = 'empty string'} else if ('' === item.trim()) {type = 'whitespace'}\n\t\t}\n\t} else if ('number' === type) {\n\t\tif (Number.isNaN(item)) {type = 'NaN'} else if (Infinity === item) {type = 'Infinity'}\n\t} else if ('object' === type) {\n\t\tif (Array.isArray(item)) {\n\t\t\ttype = 'array'\n\t\t\tif (!isSimple && !item.length) {type = 'empty array'}\n\t\t} else if (null === item) {type = 'null'\n\t\t} else {\n\t\t\tif (!isSimple) {\n\t\t\t\ttry {if (0 === Object.keys(item).length) {type = 'empty object'}} catch {}\n\t\t\t}\n\t\t}\n\t}\n\t// do nothing: undefined, bigint, boolean, function\n\treturn type +'' // make sure we return a string\n}\n\nfunction testtypeFn(isSimple = false) {\n\tlet bigint = 9007199254740991\n\ttry {bigint = BigInt(9007199254740991)} catch(e) {}\n\tlet data = ['a','','  ', 1, 1.2, Infinity, NaN, [], [1], {}, {a: 1}, null,\n\t\ttrue, false, bigint, undefined, function foobar() {},]\n\tdata.forEach(function(item) {\n\t\tlet type = typeFn(item, isSimple)\n\t\tconsole.log(typeof type, item, type)\n\t})\n}\n\nfunction dedupeArray(array, toString = false) {\n\tarray = array.filter(function(item, position) {return array.indexOf(item) === position})\n\tif (toString) {return array.join(', ')}\n\treturn array\n}\n\nfunction run_block(trace) {\n\tconsole.log(trace, 'blocking')\n\tlog_perf(SECTG, 'isBlock','')\n\tisStop = true // prevent further code\n\ttry {\n\t\tdom.tzpContent.style.display = 'none'\n\t\tdom.blockmsg.style.display = 'block'\n\t\tlet msg = 'TZP requires gecko '+ isBlockMin +'+'\n\t\tif ('iframe' == trace) {\n\t\t\tmsg = 'i\\'m in an iframe'\n\t\t} else if ('insecure' == trace) {\n\t\t\tmsg = 'i\\'m in an insecure context'\n\t\t} else if ('quirks' == trace) {\n\t\t\tmsg = 'i\\'m in quirks mode - try again'\n\t\t} else if (isAllowNonGecko) {\n\t\t\tif (undefined !== isEngine) {\n\t\t\t\tmsg = 'update your '+ isEngine +' browser'\n\t\t\t} else if (!isGecko) {\n\t\t\t\tmsg = 'TZP requires gecko '+ (isEngineStr.includes(' or ') ? ', ' : ' or ') + isEngineStr\n\t\t\t}\n\t\t}\n\t\tdom.blockmsg.innerHTML = \"<center><br><span style='font-size: 14px;'><b>\"+ (isGecko ? 'Gah.' : 'Aw, Snap!')\n\t\t\t+\"<br><br>\" + msg +'<b></span></center>'\n\t} catch(e) {}\n}\n\nfunction run_basic(str = 'basic') {\n\t// basic mode: colors: gecko only, let other engines have some color\n\tif (isGecko && 'basic' == str) {\n\t\tlog_perf(SECTG, 'isBasic','')\n\t\tfor (let i=1; i < 19; i++) {\n\t\t\tdocument.documentElement.style.setProperty('--test'+i, 'var(--txtbasic)')\n\t\t\tdocument.documentElement.style.setProperty('--bg'+i, 'var(--bg99)')\n\t\t}\n\t\tdocument.documentElement.style.setProperty('--testweight', 'normal')\n\t\tdocument.documentElement.style.setProperty('--bggood', 'none')\n\t\tdocument.documentElement.style.setProperty('--bgbad', 'none')\n\t\tdocument.body.style.setProperty('--testbad', 'var(--txtbasic)')\n\t}\n\t// basic/other modes: notation\n\tif (str.length) {\n\t\tif ('undefined' == isEngine) {str = 'experimental'} else {str += ' mode'}\n\t\tlet items = document.getElementsByClassName('nav-down')\n\t\tfor (let i=0; i < items.length; i++) {\n\t\t\t// find '<a href' to end, prepend span\n\t\t\t// e.g. '<a href=\"#uad\">▼</a>' -> '<span class=\"perf\">notation</span><a href=\"#uad\">▼</a>'\n\t\t\tlet link = items[i].innerHTML\n\t\t\tlink = link.slice(link.indexOf('<a href'), link.length)\n\t\t\titems[i].innerHTML = \"<span class='perf'>\"+ str +'</span> '+ link\n\t\t}\n\t}\n}\n\nfunction getElementProp(sect, id, name, pseudo = ':after') {\n\t// default none: https://www.w3.org/TR/CSS21/generate.html#content\n\t//console.log(sect, id, pseudo)\n\ttry {\n\t\tlet item = window.getComputedStyle(document.querySelector(id), pseudo).content\n\t\t// if supported but css blocked we get 'none' but if not supported (e.g. servo during development) we get ''\n\t\t\t// we want the FP css metrics to reflect what the css actually says, so match the defaults\n\t\tif ('undefined' == isEngine && '' == item) {\n\t\t\t// out of range: screen, inner, dpi: default is '' but we return '?'\n\t\t\tif ('#S' == id || '#D' == id || '#P' == id) {return '?'}\n\t\t\t// deviceposture, orientations, aspect ratios, display-mode\n\t\t\tlet aUndefined = ['#cssDP','#cssOm','#cssDAR','#cssO','#cssAR','#cssDM']\n\t\t\tif (aUndefined.includes(id)) {return 'undefined'}\n\t\t\t// everything else\n\t\t\treturn zNA\n\t\t}\n\n\t\tif (runSI && !runSL) {item = 'none'} // don't error if runSL\n\t\tlet typeCheck = typeFn(item, true)\n\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\titem = item.replace(/\"/g,'') // trim quote marks\n\t\t// screen(S) + window(D) + dpi(P:before) return none or ? when out of range\n\t\tif ('#S' == id || '#D' == id || '#P' == id) {\n\t\t\tif (':after' == pseudo && ' x ' == item.slice(0,3)) {item = item.slice(3)} // S/D remove leading ' x '\n\t\t\tif ('none' == item || '' == item) {item = '?'} // return consistent ? for out of range/blocked\n\t\t} else if ('#cssVS' == id) {\n\t\t\tif (':after' == pseudo && ' x ' == item.slice(0,3)) {item = item.slice(3)} // remove leading ' x '\n\t\t}\n\t\t// everything else should have a value: so \"none\" means css was blocked\n\t\tif ('none' == item) {throw zErrInvalid +\"got 'none'\"}\n\t\t// our css rules use \"none \" (trailing space) so we can detect when the css blocked\n\t\t\t// trim it for our return to compare to matchMedia\n\t\tif ('none ' == item) {item = 'none'} \n\t\t// return numbers\n\t\tif ('string' === typeCheck && !Number.isNaN(item * 1)) {item = item * 1}\n\t\treturn item\n\t} catch(e) {\n\t\tlog_error(sect, name, e)\n\t\treturn zErr\n\t}\n}\n\nfunction mini(str) {\n\t// https://stackoverflow.com/a/22429679\n\tconst json = `${JSON.stringify(str)}`\n\tlet len = json.length, hash = 0x811c9dc5\n\tfor (let i=0; i < len; i++) {\n\t\thash = Math.imul(31, hash) + json.charCodeAt(i) | 0\n\t}\n\treturn ('0000000' + (hash >>> 0).toString(16)).slice(-8)\n}\n\nconst promiseRaceFulfilled = async ({\n\tpromise,\n\tresponseType, // promise response type\n\tlimit = 1000 // default ms to fulfill\n}) => {\n\t// set up promise race\n\tconst slowPromise = new Promise(resolve => setTimeout(resolve, limit))\n\t// await promise race status\n\tconst response = await Promise.race([slowPromise, promise]) // fastest will win \n\t\t.then(response => response instanceof responseType ? response : 'pending')\n\t\t.catch(error => 'rejected')\n\treturn (\n\t\tresponse == 'rejected' || response == 'pending' ? undefined : response\n\t)\n}\n\n/*** GLOBAL ONCE ***/\n\nfunction get_isArch(METRIC) {\n\t// FYI: 32bit no longer supported for linux FF145+\n\tlet t0 = nowFn(), value\n\ttry {\n\t\tif (runSG) {foo++}\n\t\tlet test = new ArrayBuffer(Math.pow(2,32)) // 4294967296\n\t\tvalue = 64\n\t} catch(e) {\n\t\tif ('blink' == isEngine && 'RangeError: Array buffer allocation failed' == e+'') {\n\t\t\t// chrome limits ArrayBuffer to 2145386496: https://issues.chromium.org/issues/40055619\n\t\t\tisArch = zNA; value = zNA\n\t\t} else {\n\t\t\tisArch = log_error(3, 'browser_architecture', e, isScope, true) // persist sect3\n\t\t\tvalue = zErr\n\t\t}\n\t}\n\tlog_perf(SECTG, METRIC, t0,'', value)\n}\n\nfunction get_isAutoplay(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getAutoplayPolicy\n\t// get non-user-gesture values once\n\tlet t0 = nowFn()\n\ttry {\n\t\tif ('undefined' == typeof navigator.getAutoplayPolicy) {\n\t\t\tisAutoPlay = 'undefined'\n\t\t} else {\n\t\t\tlet aTest, mTest\n\t\t\tlet aPolicy = navigator.getAutoplayPolicy('audiocontext')\n\t\t\ttry {\n\t\t\t\tif (runSG) {foo++}\n\t\t\t\taTest = navigator.getAutoplayPolicy(dom.tzpAudio)\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(13, METRIC +'_audio', e, isScope, true) // persist sect13\n\t\t\t\taTest = zErr\n\t\t\t}\n\t\t\tlet mPolicy = navigator.getAutoplayPolicy('mediaelement')\n\t\t\ttry {\n\t\t\t\tif (runSG) {bar++}\n\t\t\t\tmTest = navigator.getAutoplayPolicy(dom.tzpVideo)\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(13, METRIC +'_media', e, isScope, true) // persist sect13\n\t\t\t\tmTest = zErr\n\t\t\t}\n\t\t\t// combine\n\t\t\tisAutoPlay = (aPolicy === aTest ? aPolicy : aPolicy +', '+ aTest) +' | '+ (mPolicy === mTest ? mPolicy : mPolicy +', '+ mTest)\n\t\t}\n\t} catch(e) {\n\t\tisAutoPlay = zErr\n\t\tisAutoPlayError = log_error(13, METRIC, e, isScope, true) // persist sect13\n\t}\n\tlog_perf(SECTG, 'isAutoPlay', t0,'', (isAutoPlay == zErr ? zErr : ''))\n\treturn\n}\n\nconst get_isBB = (METRIC) => new Promise(resolve => {\n\tif (!isGecko) {return resolve()}\n\n\tlet t0 = nowFn(), isDone = false\n\tsetTimeout(() => {\n\t\tif (!isDone) {\n\t\t\tlog_error(3, METRIC, zErrTime, isScope, true) // persist sect3\n\t\t\tlog_alert(SECTG, METRIC, zErrTime, isScope, true)\n\t\t\tlog_perf(SECTG, METRIC, t0,'', zErrTime)\n\t\t\tresolve()\n\t\t}\n\t}, 150)\n\n\tlet count = 0, expected = 1\n\tfunction exit(value, id) {\n\t\tcount++\n\t\tif ('aboutTor' == id) {removeElementFn(id)}\n\t\t//console.log(`${count} of ${expected}: ${value}, ${id}`)\n\t\t// return on first true\n\t\tif (!isDone && true === value) {\n\t\t\tisDone = true\n\t\t\t//console.log('resolving after first success: test return no.', count, id)\n\t\t\tisBB = true\n\t\t\tif (id.includes('mullvad')) {isMB = true} else {isTB = true}\n\t\t\t// tidy notation\n\t\t\tif (isMB) {\n\t\t\t\tbb_green = sgtick+'MB]'+sc\n\t\t\t\tbb_red = sbx+'MB]'+sc\n\t\t\t\tbb_slider_red = sbx+'MB Slider]'+sc\n\t\t\t\tbb_standard = sg+'[MB Standard]'+sc\n\t\t\t\tbb_safer = sg+'[MB Safer]'+sc\n\t\t\t}\n\t\t\tlog_perf(SECTG, METRIC, t0,'', (isMB ? 'mullvad': 'tor') +' browser | '+ id)\n\t\t\tresolve()\n\t\t}\n\t\t// otherwise if !isBB we exit false after expected number of test(s)\n\t\tif (!isBB && count == expected || zErr == value) {\n\t\t\tisDone = true\n\t\t\tlog_perf(SECTG, METRIC, t0,'', (zErr == value ? zErr : false))\n\t\t\tresolve()\n\t\t}\n\t}\n\t// FF121+: 1855861\n\tconst get_event = (el, id) => {\n\t\tel.onload = function() {exit(true, id)}\n\t\tel.onerror = function() {exit(false, id)}\n\t}\n\tif (!runSG) {\n\t\ttry {\n\t\t\t// min ver is 128 now\n\t\t\tlet list = [\n\t\t\t\t'content/torconnect/tor-connect.svg', // TB13.5\n\t\t\t\t'skin/icons/torbrowser.png', // TB14.5\n\t\t\t\t'skin/icons/mullvadbrowser.png', // MB14.5\n\t\t\t]\n\t\t\texpected = list.length\n\t\t\tlist.forEach(function(image) {\n\t\t\t\tlet parts = (image.slice(0,-4)).split('/')\n\t\t\t\tlet id = parts[parts.length - 1]\n\t\t\t\tlet el = new Image()\n\t\t\t\tel.src = 'chrome://global/' + image\n\t\t\t\tget_event(el, id)\n\t\t\t})\n\t\t} catch(e) {\n\t\t\t// catch any unexpected extension fuckery\n\t\t\tlog_error(3, METRIC, e, isScope, true) // persist sect3\n\t\t\tlog_alert(SECTG, METRIC, e.name, isScope, true)\n\t\t\texit(zErr)\n\t\t}\n\t}\n})\n\nconst get_isBrave = (METRIC) => new Promise(resolve => {\n\tif ('blink' !== isEngine) return resolve()\n\n\tlet t0 = nowFn()\n\tfunction exit(value = false) {\n\t\tlog_perf(SECTG, METRIC, t0, '', value)\n\t\treturn resolve()\n\t}\n\ttry {\n\t\t// first determine isBrave\n\t\t\t// keeping in mind that extensions etc could mess with these\n\t\tif ('object' == typeof opr) return resolve() // opera\n\t\t// navigator\n\t\tlet braveInN = 'brave' in navigator &&\n\t\t\tObject.getPrototypeOf(navigator.brave).constructor.name == 'Brave' &&\n\t\t\tnavigator.brave.isBrave.toString() == 'function isBrave() { [native code] }' &&\n\t\t\t'brave' in navigator ? Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype)).indexOf(\"brave\") < 20 : false\n\t\tif (!braveInN) {\n\t\t\texit()\n\t\t} else {\n\t\t\t// userAgentData\n\t\t\tPromise.all([\n\t\t\t\tget_agent_data('', isOS, false)\n\t\t\t]).then(function(res){\n\t\t\t\ttry {\n\t\t\t\t\t// we already have braveInN, now we want braveInU\n\t\t\t\t\tlet data = res[0]\n\t\t\t\t\tif ('object' !== typeof data) {exit()}\n\t\t\t\t\t// the position of 'Brave' can/might vary (seems like a patch not randomizing)\n\t\t\t\t\t// so we need to loop: assumes same position in brand + fullVersionList\n\t\t\t\t\tfor (let i = 0; i < data.brands.length; i++) {\n\t\t\t\t\t\tif ('Brave' == data.brands[i].brand && 'Brave' == data.fullVersionList[i].brand) {\n\t\t\t\t\t\t\tisBrave = true; break\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// if isBrave, we can check if FP protection is enabled: telltale signs we can easily check include\n\t\t\t\t\t\t// null keyboard\n\t\t\t\t\t\t// gibberish in plugins (if pdf enabled)\n\t\t\t\t\t\t// canvas\n\t\t\t\t\t// other possibles are: tiny chrome + tiny screen positions\n\t\t\t\t\t// others: extendedscreen can't be true\n\n\t\t\t\t\t// plugins gibberish\n\t\t\t\t\t\t// items are always in a set order (check each platform) - brave mixes this ip\n\t\t\t\t\t\t// one of the five is always missing - brave tends to overwrite one of them\n\t\t\t\t\t\t// non expected items have no spaces e.g. \n\t\t\t\t\t/* examples\n\t\t\t\t\t\t\"aZMOPuXT: qdWTwBAIjRnTRQnb: WyhQIMtePHDBnyCJMtWyhYrdWTw3j47d\",\t\t8, 16, 32\n\t\t\t\t\t\t\"4k5k5cO:  BMteXyhQQv268mb:  FCgYzCBn6dt15FCo7GDJEKFpct9mbse\",\t\t7, 15, 31\n\t\t\t\t\t*/\n\t\t\t\t\tlet aOrder = ['PDF Viewer','Chrome PDF Viewer','Chromium PDF Viewer','Microsoft Edge PDF Viewer','WebKit built-in PDF']\n\t\t\t\t\t// 2 out of 3 should be enough to determine gibberish\n\n\t\t\t\t\texit(isBrave + (isBrave ? ' '+ isBraveSmart : ''))\n\t\t\t\t} catch(e) {\n\t\t\t\t\tlog_error(3, METRIC, e, isScope, true) // persist sect3\n\t\t\t\t\texit(zErr)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\tlog_error(3, METRIC, e, isScope, true) // persist sect3\n\t\texit(zErr)\n\t}\n})\n\nfunction get_isDevices() {\n\tisDevices = undefined\n\tlet t0 = nowFn()\n\ttry {\n\t\tif (undefined !== navigator.mediaDevices) {return}\n\t\tif (runSG) {foo++}\n\t\tnavigator.mediaDevices.enumerateDevices().then(function(devices) {\n\t\t\tisDevices = devices\n\t\t\tif (gLoad) {log_perf(SECTG, 'isDevices', t0,'', nowFn())}\n\t\t}\n\t)} catch(e) {}\n}\n\nfunction get_isEngine(METRIC) {\n\tif (isGecko) {return}\n\tlet t0 = nowFn()\n\ttry {\n\t\tlet oEngines = {\n\t\t\tblink: [\n\t\t\t\t'number' === typeof TEMPORARY,\n\t\t\t\t'number' === typeof PERSISTENT,\n\t\t\t\t'object' === typeof onappinstalled,\n\t\t\t\t'object' === typeof onbeforeinstallprompt,\n\t\t\t\t'function' === typeof webkitResolveLocalFileSystemURL,\n\t\t\t],\n\t\t\twebkit: [\n\t\t\t\t'object' === typeof browser,\n\t\t\t\t'object' === typeof safari,\n\t\t\t\t'function' === typeof webkitConvertPointFromNodeToPage,\n\t\t\t\t'function' === typeof webkitCancelRequestAnimationFrame,\n\t\t\t\t'object' === typeof webkitIndexedDB,\n\t\t\t],\n\t\t\t/* ignore edgeHTML\n\t\t\tedgeHTML: [\n\t\t\t\t'function' === typeof clearImmediate,\n\t\t\t\t'function' === typeof msWriteProfilerMark,\n\t\t\t\t'object' === typeof oncompassneedscalibration,\n\t\t\t\t'object' === typeof onmsgesturechange,\n\t\t\t\t'object' === typeof onmsinertiastart,\n\t\t\t\t'object' === typeof onreadystatechange,\n\t\t\t\t'function' === typeof setImmediate,\n\t\t\t]\n\t\t\t//*/\n\t\t}\n\t\t// array engine matches, so subsequent results doesn't override prev\n\t\tlet aEngine = [], aAllowed = []\n\t\tfor (const engine of Object.keys(oEngines).sort()) {\n\t\t\taAllowed.push(engine)\n\t\t\tlet sumE = oEngines[engine].reduce((prev, current) => prev + current, 0)\n\t\t\tif (sumE > (oEngines[engine].length/2)) {aEngine.push(engine)}\n\t\t}\n\t\taAllowed.sort()\n\t\tisEngineStr = aAllowed.join(', ')\n\t\tif (aAllowed.length > 1) {\n\t\t\tisEngineStr = ' or '+ aAllowed[aAllowed.length - 1]\n\t\t\taAllowed = aAllowed.slice(0,-1)\n\t\t\tisEngineStr = aAllowed.join(',') + isEngineStr\n\t\t}\n\t\tif (aEngine.length == 1) {isEngine = aEngine[0]} // valid one result\n\t\t// set minimum\n\t\tif (undefined !== isEngine) {\n\t\t\t// approved engine detected, if not enforcing min, disable block\n\t\t\tisEngineBlocked = isAllowNonGeckoMin\n\t\t\t// if enforcing min, check\n\t\t\tif (isAllowNonGeckoMin) {\n\t\t\t\ttry {\n\t\t\t\t\tif ('blink' == isEngine) {\n\t\t\t\t\t\t// 109 is the last version supported on win7\n\t\t\t\t\t\t//if ('function' == typeof(Map.groupBy)) {isEngineBlocked = false} // 117 2023-Sept\n\t\t\t\t\t\tif ('function' == typeof(Document.parseHTMLUnsafe)) {isEngineBlocked = false} // 124 2024-Apr\n\t\t\t\t\t\t//if ('function' !== typeof(Intl.DurationFormat)) {isEngineBlocked = false} // 129 2024-Sep\n\t\t\t\t\t} else if ('webkit' == isEngine) {\n\t\t\t\t\t\t// https://en.wikipedia.org/wiki/Safari_(web_browser)#Version_compatibility\n\t\t\t\t\t\t// 15.6.1 2022-Aug = last version supported on macOS 10.15?\n\t\t\t\t\t\tif ('function' == typeof(Intl.DurationFormat)) {isEngineBlocked = false} // 16.4 2023-Mar\n\t\t\t\t\t\t//if ('function' == typeof(Map.groupBy)) {isEngineBlocked = false} // 17.4 2024-Mar\n\t\t\t\t\t}\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\t\t} else if (isAllowNonGeckoUndefined) {\n\t\t\tisEngine = 'undefined' // a string vs undefined typeof\n\t\t\tisEngineBlocked = false\n\t\t}\n\t} catch(e) {}\n\tlog_perf(SECTG, METRIC, t0,'', isEngine)\n}\n\nconst get_isFileSystem = (METRIC, isWarmup = false) => new Promise(resolve => {\n\t// meta: 1748667\n\t// note: pref change (dom.fs.enabled) requires new reload: so we can run once\n\tlet t0 = nowFn()\n\tfunction exit(value) {\n\t\tif (!isWarmup) {\n\t\t\tisFileSystem = value\n\t\t\tlog_perf(SECTG, METRIC, t0,'', value)\n\t\t}\n\t\treturn resolve()\n\t}\n\tif (navigator.storage == undefined || 'function' !== typeof navigator.storage.getDirectory) {\n\t\texit(zD)\n\t}\n\tPromise.all([\n\t\tnavigator.storage.getDirectory()\n\t]).then(function(){\n\t\texit(zE)\n\t})\n\t.catch(function(e){\n\t\tisFileSystemError = e+''\n\t\texit(zErr)\n\t})\n})\n\nconst get_isFontDelay = () => new Promise(resolve => {\n\tif (!isBB || !isGecko || isVer < 139 || 'android' == isOS) {return resolve()}\n\n\t//if ('windows' !== isOS && 'mac' !== isOS) {return resolve()}\n\t\t// ^ linux shouldn't have any delay since it's dets the font dir as system font dir?\n\t\t// ^ but for now lets treat all desktop as suspect and force TB tro address this perf cliff\n\t// BB: font.vis + bundled fonts\n\t\t// very slow to async fallback | on linux the bundled fonts IS the system font dir and is not affected\n\t\t// not the case when using font.system.whitelist | we should see if we can get this fixed upstream\n\t// currently only nightly uses font.vis but could change at any time and is not version specific\n\t\t// detect if we need a delay by testing if fontface is working\n\n\tisFontDelay = true // BB default delay in case of errors/fuckery\n\tPromise.all([\n\t\tget_fonts_faces('','', ['Arial','Courier','Times New Roman']), // all are allowed + expected in windows/mac\n\t]).then(function(res){\n\t\t// either 'error', 'none' or an array of detected fonts\n\t\t//console.log(res[0])\n\t\tif ('none' == res[0]) {isFontDelay = false} // only remove delay if 'none': errors/detected-fonts = force a delay\n\t\treturn resolve()\n\t})\n})\n\nfunction get_isGecko(METRIC) {\n\tlet t0 = nowFn(), value\n\ttry {\n\t\tlet list = [\n\t\t\t[DataTransfer, 'DataTransfer', 'mozSourceNode'],\n\t\t\t[Document, 'Document', 'mozFullScreen'],\n\t\t\t[HTMLCanvasElement, 'HTMLCanvasElement', 'mozPrintCallback'],\n\t\t\t[HTMLElement, 'HTMLElement', 'onmozfullscreenerror'],\n\t\t\t[HTMLVideoElement, 'HTMLVideoElement', 'mozDecodedFrames'],\n\t\t\t[IDBIndex, 'IDBIndex', 'mozGetAllKeys'],\n\t\t\t[IDBObjectStore, 'IDBObjectStore', 'mozGetAll'],\n\t\t\t[Screen, 'Screen', 'mozOrientation'], // 1325110: mozOrientation slated for deprecation\n\t\t\t[SVGElement, 'SVGElement', 'onmozfullscreenchange'] \n\t\t]\n\t\tlet obj, prop, aNo = []\n\t\tlist.forEach(function(array) {\n\t\t\tobj = array[0]\n\t\t\tprop = array[2]\n\t\t\tif ('function' === typeof obj\n\t\t\t\t&& ('object' === typeof Object.getOwnPropertyDescriptor(obj.prototype, prop))) {\n\t\t\t} else {\n\t\t\t\taNo.push(array[1])\n\t\t\t}\n\t\t})\n\t\tlet found = (list.length - aNo.length)\n\t\tif (found > 5) {\n\t\t\tisGecko = true\n\t\t\t// alert if any gecko checks fail\n\t\t\tif (aNo.length) {log_alert(SECTG, METRIC, aNo.join(', '), isScope, true)}\n\t\t}\n\t\tvalue = isGecko +' | '+ found +'/'+ list.length\n\t} catch(e) {\n\t\tvalue = zErr\n\t}\n\tlog_perf(SECTG, METRIC, t0,'', value)\n\treturn\n}\n\nfunction get_isPointerRawUpdate(event) {\n\tlet value = 'undefined'\n\ttry {\n\t\tif (event.getCoalescedEvents && event.getCoalescedEvents().length > 1) {\n\t\t\t//console.log(\"Coalesced events:\", event.getCoalescedEvents());\n\t\t} else {\n\t\t\tvalue = {'persistentDeviceId': event.persistentDeviceId, 'pointerType': event.pointerType}\n\t\t}\n\t} catch(e) {\n\t\tvalue = zErr\n\t}\n\tisPointerRawUpdate = value\n}\n\nconst get_isOS = (METRIC) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\n\t// 1. widget font: mac/linux\n\tfunction trywidget() {\n\t\ttry {\n\t\t\tif (runSG) {foo++}\n\t\t\tlet aIgnore = [\n\t\t\t\t'cursive','emoji','fangsong','fantasy','math','monospace','none','sans-serif',\n\t\t\t\t'serif','system-ui','ui-monospace','ui-rounded','ui-serif','undefined'\n\t\t\t]\n\t\t\tlet font = getComputedStyle(dom.tzpbutton).getPropertyValue('font-family')\n\t\t\tif ('string' !== typeFn(font) || aIgnore.includes(font)) {\n\t\t\t\tthrow zErr\n\t\t\t} else {\n\t\t\t\tif (isGecko) {\n\t\t\t\t\t// button\n\t\t\t\t\tif (font.slice(0,12) == \"MS Shell Dlg\") {exit('windows')\n\t\t\t\t\t} else if (font == '-apple-system') {exit('mac')\n\t\t\t\t\t} else {throw zErr}\n\t\t\t\t} else {\n\t\t\t\t// mac webkit\n\t\t\t\t\t// search and select return -apple-system\n\t\t\t\t\t// mozfonts (e.g. mozbutton) return webkit-standard\n\t\t\t\t\t// status-bar returns -apple-status-bar\n\t\t\t\t\t// menu returns -apple-menu\n\t\t\t\t\ttryfonts()\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\ttryfonts()\n\t\t}\n\t}\n\t// 2: fonts\n\tfunction tryfonts() {\n\t\t// check doc fonts\n\t\tlet fntEnabled = false\n\t\ttry {\n\t\t\tif (runSG) {foo++}\n\t\t\tlet fntTest = '\\\"test font name\\\"'\n\t\t\t//dom.tzpDocFont.style.fontFamily = fntTest\n\t\t\tlet font = getComputedStyle(dom.tzpDocFont).getPropertyValue('font-family'),\n\t\t\t\tfontnoquotes = font.slice(0, fntTest.length - 2) // ext may strip quotes marks\n\t\t\tfntEnabled = (font == fntTest || fontnoquotes == fntTest ? true : false)\n\t\t} catch {}\n\t\tif (!fntEnabled) {trysomethingelse(); return}\n\n\t\t// check fonts\n\t\tget_fonts_size(false).then(res => {\n\t\t\tif ('object' == typeFn(res, true)) {\n\t\t\t\tlet aDetected = [], found\n\t\t\t\tfor (const k of Object.keys(res)) {aDetected.push(k)}\n\t\t\t\tif (isGecko) {\n\t\t\t\t\tfound = aDetected[0]\n\t\t\t\t\tif (aDetected.length == 1) {\n\t\t\t\t\t\tif (found == 'MS Shell Dlg \\\\32') {exit('windows')\n\t\t\t\t\t\t} else if (found == '-apple-system') {exit('mac')\n\t\t\t\t\t\t} else if (found == 'Dancing Script') { exit('android')\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttrysomethingelse()\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//console.log('isOS font check', found, isOS)\n\t\t\t\t\t} else if (isGecko && aDetected.length == 0) {\n\t\t\t\t\t\texit('linux')\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttrysomethingelse()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(aDetected)\n\t\t\t\t\t// if we detected the fake font then ignore\n\t\t\t\t\t//aDetected.push('--00'+ rnd_string()) // test\n\t\t\t\t\tlet aFake = aDetected.filter(x => !fntMaster.platform.all.includes(x)) \n\t\t\t\t\tif (aFake.length) {trysomethingelse(); return}\n\t\t\t\t\t// get counts\n\t\t\t\t\tlet aWindows = aDetected.filter(x => fntMaster.platform.windows.includes(x)),\n\t\t\t\t\t\taMac = aDetected.filter(x => fntMaster.platform.mac.includes(x)),\n\t\t\t\t\t\taAndroid = aDetected.filter(x => fntMaster.platform.android.includes(x))\n\t\t\t\t\tlet intW = aWindows.length, intM = aMac.length, intA = aAndroid.length\n\t\t\t\t\t//console.log(aWindows, intW, aMac, intM, aAndroid, intA)\n\t\t\t\t\tif (intW > 0 && (intM + intA == 0)) {exit('windows')\n\t\t\t\t\t} else if (intM > 0 && (intW + intA == 0)) {exit('mac')\n\t\t\t\t\t} else if (aDetected.length == 0) {exit('linux')\n\t\t\t\t\t} else {\n\t\t\t\t\t\t//} else if (intA > 0 && (intM + intW == 0)) {exit('android')\n\t\t\t\t\t\t//} else {exit('linux')}\n\t\t\t\t\t\t// can't base android vs lionux on a single font\n\t\t\t\t\t\ttrysomethingelse()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttrysomethingelse()\n\t\t\t}\n\t\t})\n\t}\n\t// 3. now what? \n\tfunction trysomethingelse() {\n\t\texit()\n\t}\n\t// 4. exit\n\tfunction exit(value) {\n\t\tisOS = value\n\t\tisDesktop = 'android' !== isOS\n\t\tdom.tzpResource.style.backgroundImage = \"url('chrome://branding/content/\"\n\t\t\t+ (isDesktop ? '' : 'fav') + \"icon64.png')\" // set icon\n\t\tlog_perf(SECTG, METRIC, t0, '', isOS +'')\n\t\tif (undefined == isOS) {\n\t\t\t// for now only alert isGecko\n\t\t\tif (isGecko) {\n\t\t\t\tisOSErr = log_error(3, \"os\", zErrType +'undefined', isScope, true) // persist sect3\n\t\t\t\tlog_alert(SECTG, METRIC, \"undefined\", isScope, true)\n\t\t\t}\n\t\t}\n\t\treturn resolve()\n\t}\n\n\tif (isGecko) {\n\t\ttrywidget()\n\t} else {\n\t\tset_fntList_mini()\n\t\ttryfonts()\n\n\t\t/*\n\t\t// get svh and lvh: if they differ then you have a dynamic urlbar\n\t\t// this is fast - could we leverage it for gecko as well\n\t\t\t// maybe not since isBB might restrict it for FPing dynamic urlbar\n\t\t\t// also apps may allow disabling it\n\t\t\t// also maybe apps will enable it on other devices/tablets/platforms\n\t\t\t// or extensions might tamper with it\n\t\t// so for now just record the info for non-gecko\n\t\tlet aList = ['L','S']\n\t\ttry {\n\t\t\tlet data = {}\n\t\t\taList.forEach(function(k) {data[k] = dom['tzp'+ k +'V'].offsetHeight})\n\t\t\tlet diff = Math.abs(data['L'] - data['S'])\n\t\t\tif (diff > 20) { // allow some wriggle room\n\t\t\t\t//if ('blink' == isEngine) {isOS = 'android'}\n\t\t\t}\n\t\t\tlog_perf(SECTG, METRIC, t0, '', 'L: '+ data['L'] +' | S: '+ data['S'])\n\t\t} catch(e) {}\n\t\t//*/\n\t\treturn resolve()\n\t}\n})\n\nconst get_isRecursion = () => new Promise(resolve => {\n\t// 2nd test is more accurate/stable\n\tconst METRIC = \"isRecursion\"\n\tlet t0 = nowFn()\n\tlet level = 0\n\tfunction recurse() {level++; recurse()}\n\ttry {recurse()} catch {}\n\tlevel = 0\n\ttry {\n\t\trecurse()\n\t} catch(e) {\n\t\tlet stacklen = e.stack.toString().length\n\t\t// display value\n\t\tisRecursion = [level +\" [stack length: \"+ stacklen +']']\n\t\tlog_perf(SECTG, METRIC, t0, \"\", isRecursion.join())\n\t\t// metric values: only collect level\n\t\t\t// https://github.com/arkenfox/user.js/issues/1789: round down to 1000's\n\t\tisRecursion.push(Math.floor(level/1000))\n\t\treturn resolve()\n\t}\n})\n\nfunction get_isStylesheet(end = 7680, start = 200) {\n\t// currently the ranges for both screen (min-device-*) and\n\t// inner (min-*) are 400-2560 and the two css files total almost 500kb\n\tlet t0 = nowFn(), state = start +'-'+ end, METRIC = 'isStylesheet'\n\ttry {\n\t\tconst style = document.createElement('style')\n\t\tstyle.type = 'text/css'\n\t\tlet rules = []\n\t\tfunction add_blank(i) {\n\t\t\trules.push(\"@media (min-device-width:\"+ i +\"px){#S:before{content:'';}}\")\n\t\t\trules.push(\"@media (min-device-height:\"+ i +\"px){#S:after{content:'';}}\")\n\t\t\trules.push(\"@media (min-width:\"+ i +\"px){#D:before{content:'';}}\")\n\t\t\trules.push(\"@media (min-height:\"+ i +\"px){#D:after{content:'';}}\")\n\t\t}\n\t\tadd_blank(start - 1)\n\t\tfor (let i= start; i <= end; i++) {\n\t\t\trules.push(\"@media (min-device-width:\"+ i +\"px){#S:before{content:'\" + i +\"';}}\")\n\t\t\trules.push(\"@media (min-device-height:\"+ i +\"px){#S:after{content:' x \" + i +\"';}}\")\n\t\t\trules.push(\"@media (min-width:\"+ i +\"px){#D:before{content:'\" + i +\"';}}\")\n\t\t\trules.push(\"@media (min-height:\"+ i +\"px){#D:after{content:' x \" + i +\"';}}\")\n\t\t}\n\t\tadd_blank(end + 1)\n\t\tstyle.appendChild(document.createTextNode(rules.join(' ')))\n\t\tdocument.head.appendChild(style)\n\t\tisStylesheet = state\n\t} catch(e) {\n\t\tstate = zErr\n\t\tlog_error(SECTG, METRIC, e, isScope, true)\n\t}\n\t// ~65ms for 7680 (8k) cold | reload ~15ms\n\t\t// some/most of this time is spent awaiting more js files anyway\n\tlog_perf(SECTG, METRIC, t0, '', state)\n\treturn\n}\n\nconst get_isSystemFont = () => new Promise(resolve => {\n\t//if (!isGecko) {return resolve()}\n\tlet t0 = nowFn()\n\tfunction exit(value) {\n\t\tlog_perf(SECTG, 'isSystemFont', t0,'', value)\n\t\treturn resolve()\n\t}\n\tlet aMoz = [\n\t\t// -moz seem to always be the same\n\t\t'-moz-bullet-font','-moz-button','-moz-button-group','-moz-desktop','-moz-dialog','-moz-document',\n\t\t'-moz-field','-moz-info','-moz-list','-moz-message-bar','-moz-pull-down-menu','-moz-window','-moz-workspace',\n\t]\n\tlet aNonMoz = [\n\t\t// in gecko non -moz seem to always be the same: mac might differ  I seem to recall this\n\t\t\t// in the past - anyway we grab the first of each e.g. caption + menu differ in blink (windows)\n\t\t'caption','icon','menu','message-box','small-caption','status-bar',\n\t]\n\t// first aFont per computed family\n\t\t// add '-default-font' (alphabetically first) so it's easy to see what it pairs with in baseFonts\n\tlet aFonts = ['-default-font']\n\tif (isGecko) {aFonts = aFonts.concat(aMoz)}\n\taFonts = aFonts.concat(aNonMoz)\n\taFonts.sort()\n\n\ttry {\n\t\tlet el = dom.tzpDiv, data = []\n\t\taFonts.forEach(function(font){\n\t\t\tel.style.font ='' // always clear in case a font is invalid/deprecated\n\t\t\tel.style.font = font\n\t\t\tlet family = getComputedStyle(el)['font-family']\n\t\t\tif (!data.includes(family)) {\n\t\t\t\tdata.push(family)\n\t\t\t\tisSystemFont.push(font)\n\t\t\t}\n\t\t})\n\t\tif (isGecko) {\n\t\t// we use isSystemFont in fntSizes where -moz group doesn't match non -moz even though all of\n\t\t// aFonts have the same computedStyes: ensure we have one of each\n\t\t\tif (0 == isSystemFont.filter(x => aMoz.includes(x).length)) {isSystemFont.push(aMoz[0])}\n\n\t\t}\n\t\tisSystemFont.sort()\n\t\texit(isSystemFont.join(', '))\n\t} catch(e) {\n\t\texit(e.name) // log nothing: we run in fonts later\n\t}\n})\n\nfunction get_isVer(METRIC) {\n\tif (!isGecko) {return}\n\tlet t0 = nowFn()\n\n\tisVer = cascade()\n\tif (isVer == 152) {isVerExtra = '+'} else if (isVer == 127) {isVerExtra = ' or lower'}\n\tlog_perf(SECTG, METRIC, t0,'', isVer + isVerExtra)\n\t// gecko block mode\n\tisBlock = isVer < isBlockMin\n\tif (isBlock) {run_block('gecko'); return} // sets isStop\n\t// set smarts / modes\n\tsmartFn('early')\n\tif (!isSmart && isVer < isSmartMin) {run_basic()}\n\treturn\n\n\tfunction cascade() {\n\t\tlet test\n\t\ttry {\n\t\t\t// old-timey check: avoid false postives: must be 128 or higher\n\t\t\ttry {let test128 = (new Blob()).bytes()} catch {return 127} // 1896509\n\t\t\t// now cascade\n\t\t\ttry {if (SVGTextPathElement.prototype.hasOwnProperty('side')) return 152} catch(e) {} // 2034371\n\t\t\tif (CSSContainerRule.prototype.hasOwnProperty('conditions')) return 151 // 2022827\n\t\t\tif ('object' == typeof visualViewport.onscrollend) return 150 // 1801658\n\t\t\ttry {Temporal.PlainDate.from({calendar:'gregory', monthCode:'M12', month:13, year:2019, day:1})} catch(e) {if ('RangeError' == e.name) return 149} // 2009792\n\t\t\t// 148: fast-path: pref dom.location.ancestorOrigins.enabled: default true 148+\n\t\t\ttry {if (undefined !== location.ancestorOrigins) return 148} catch(e) {} // 1085214\n\t\t\ttry {let test148 = new Temporal.Duration(0).total({unit:'years', relativeTo:'-271821-04-19'}); return 148} catch(e) {} // 2004851\n\t\t\tif (Intl.supportedValuesOf('numberingSystem').includes('tols')) return 147 // 2000225 ?\n\t\t\ttry {throw new DOMException('a', 'b')} catch(e) {if (0 !== e.columnNumber) return 146} // 1997216\n\t\t\tif (undefined !== (new ToggleEvent('toggle', null)).source) return 145 // 1968987\n\t\t\tif (undefined == window.CSS2Properties) return 144 // 144: 1919582\n\t\t\t// 143: fast-path: pref: layout.css.moz-appearance.webidl.enabled: default false 143+\n\t\t\tif (!CSS2Properties.prototype.hasOwnProperty('-moz-appearance')) return 143 // 1977489\n\t\t\ttry {\n\t\t\t\tlet segmenter = new Intl.Segmenter('en', {granularity: 'word'})\n\t\t\t\ttest = Array.from(segmenter.segment('a:b')).map(({ segment }) => segment)\n\t\t\t\tif (3 == test.length) return 142 // 1960300\n\t\t\t} catch(e) {}\n\t\t\t// 141: fast-path: requires temporal default enabled FF139+ javascript.options.experimental.temporal\n\t\t\ttry {if (undefined == Temporal.PlainDate.from('2029-12-31[u-ca=gregory]').weekOfYear) return 141} catch(e) {} // 1950162\n\t\t\t// 141: fast-path: dom.intersection_observer.scroll_margin.enabled (default true)\n\t\t\ttry {if (window[\"IntersectionObserver\"].prototype.hasOwnProperty('scrollMargin')) return 141} catch(e) {} // 1860030\n\t\t\t// 140: fast-path: pref: dom.event.pointer.rawupdate.enabled : default true 140+\n\t\t\ttry {if (\"object\" === typeof onpointerrawupdate) return 140 } catch(e) {} // 1550462\n\t\t\t// 140: if < 141 there is only one paint entry \"PerformancePaintTiming\"\n\t\t\ttry {if (undefined !== performance.getEntriesByType(\"paint\")[0].presentationTime) return 140} catch(e) {} // 1963464\n\t\t\ttry {if ('' !== dom.tzpAudio.preload) return 140} catch(e) {} // 929890\n\t\t\t// 139\n\t\t\tif (HTMLDialogElement.prototype.hasOwnProperty('requestClose')) return 139 // 1960556\n\t\t\t// 138: fast-path: requires webrtc e.g. media.peerconnection.enabled | --disable-webrtc\n\t\t\ttry {if (RTCCertificate.prototype.hasOwnProperty('getFingerprints')) return 138} catch(e) {} // 1525241\n\t\t\t// 138: fast-path: dom.origin_agent_cluster.enabled\n\t\t\tif ('boolean' == typeof originAgentCluster) return 138 // 1665474\n\t\t\t// 138: must be FF134 or higher\n\t\t\ttry {\n\t\t\t\tif (HTMLScriptElement.prototype.hasOwnProperty('textContent')) { // FF135+\n\t\t\t\t\ttest = Intl.NumberFormat('yo-bj', {style: 'unit', unit: 'year', unitDisplay: 'narrow'}).format(1)\n\t\t\t\t\tif ('606d1046' == mini(test)) return 138 // 1954425\n\t\t\t\t}\n\t\t\t} catch(e) {}\n\t\t\t// 137 fast-path: javascript.options.experimental.math_sumprecise\n\t\t\tif ('function' == typeof Math.sumPrecise) return 137 // 1943120\n\t\t\t// 136 fast-path: FF132+ pref enabled javascript.options.experimental.regexp_modifiers\n\t\t\ttry {if ((new RegExp(\"(?i:[A-Z]{4})\")).test('abcd')) return 136} catch {} // 1939533\n\t\t\tif (HTMLScriptElement.prototype.hasOwnProperty('textContent')) return 135 // 1905706\n\t\t\t// 134: may be affected by --with-system-icu\n\t\t\t\t// ToDo: replace, fallbacks?\n\t\t\tif ('lij' == Intl.PluralRules.supportedLocalesOf('lij').join()) return 134 // 1927706\n\t\t\ttry {\n\t\t\t\tlet parser = (new DOMParser).parseFromString(\"<select><option name=''></option></select>\", 'text/html')\n\t\t\t\tif (null === parser.body.firstChild.namedItem('')) return 133 // 1837773\n\t\t\t} catch {}\n\t\t\ttry {\n\t\t\t\tconst re = new RegExp('(?:)', 'gv');\n\t\t\t\ttest = RegExp.prototype[Symbol.matchAll].call(re, '𠮷')\n\t\t\t\tfor (let i=0; i < 3; i++) {if (true == test.next().done) return 132} // 1899413\n\t\t\t} catch {}\n\t\t\ttry {\n\t\t\t\ttest = new Intl.DateTimeFormat('zh', {calendar: 'chinese', dateStyle: 'medium'}).format(new Date(2033, 9, 1))\n\t\t\t\tif ('2033' == test.slice(0,4)) return 131 // 1900196\n\t\t\t} catch {}\n\t\t\ttry {new RegExp('[\\\\00]','u')} catch(e) {if (e+'' == 'SyntaxError: invalid decimal escape in regular expression') return 130} // 1907236\n\t\t\tif (CSS2Properties.prototype.hasOwnProperty('WebkitFontFeatureSettings')) return 129 // 1595620\n\t\t\treturn 128\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t\treturn 0\n\t\t}\n\t}\n}\n\nconst get_isXML = () => new Promise(resolve => {\n\t// get once ASAP +clear console: not going to change between tests\n\t\t// gecko change app lang and it requires closing and a new tab\n\t\t// blink changing app lang also asks for a relaunch\n\t\t\t// note: blink doesn't seem to translate these, or it's not tied to either app or web-content language\n\t\t// assume webkit requires an app restart\n\n\tlet t0 = nowFn(), delimiter = ':'\n\tconst list = {\n\t\tn02: 'a', n03: '', n04: '<>', n05: '<', n07: '<x></X>', n08: '<x x:x=\"\" x:x=\"\">',\n\t\tn09: '<x></x><x>', n11: '<x>&x;', n14: '<x>&#x0;', n20: '<x><![CDATA[', n27: '<x:x>',\n\t\tn28: '<x xmlns:x=\"\"></x>', n30: '<?xml v=\"\"?>'\n\t}\n\ttry {\n\t\tlet parser = new DOMParser\n\t\tlet isMethod = isGecko ? 'gecko' : 'other'\n\t\t// determine method to get values if engine is 'undefined'\n\t\tif ('undefined' == isEngine) {\n\t\t\t// servo fails in both\n\t\t\t\t//'gecko': can't access property \"firstChild\", target is undefined\n\t\t\t\t//'other': can't access property \"innerText\", target is undefined\n\t\t\ttry {\n\t\t\t\tlet testdoc = parser.parseFromString('a', 'application/xml')\n\t\t\t\tlet testtarget = testdoc.getElementsByTagName('parsererror')[0]\n\t\t\t\tlet teststr = testtarget.innerText\n\t\t\t} catch(e) {\n\t\t\t\t// failed 'other' method, so use 'gecko' method\n\t\t\t\tisMethod = 'gecko'\n\t\t\t}\n\t\t}\n\n\t\tfor (const k of Object.keys(list)) {\n\t\t\tlet doc = parser.parseFromString(list[k], 'application/xml')\n\t\t\tlet target = doc.getElementsByTagName('parsererror')[0]\n\t\t\t//if ('n02' == k && !isGecko) {console.log(doc.getElementsByTagName('parsererror')[0])} //debug\n\n\t\t\tlet str, value, parts\n\t\t\tif ('gecko' == isMethod) {str = target.firstChild.textContent} else {str = target.innerText}\n\t\t\tif (runST) {str =''}\n\t\t\tlet typeCheck = typeFn(str)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\n\t\t\tif ('gecko' == isMethod) {\n\t\t\t\t// gecko\n\t\t\t\t//split into parts: works back to FF52 and works with LTR\n\t\t\t\tparts = str.split('\\n')\n\t\t\t\tif ('n02' == k) {\n\t\t\t\t\t// ensure 3 parts: e.g. hebrew only has 2 lines\n\t\t\t\t\tlet tmpStr = parts[1]\n\t\t\t\t\tlet loc = window.location+'', locLen = loc.length, locStart = tmpStr.indexOf(loc)\n\t\t\t\t\tif (undefined == parts[2]) {\n\t\t\t\t\t\tlet position = locLen+ locStart\n\t\t\t\t\t\tparts[1] = (tmpStr.slice(0, position)).trim()\n\t\t\t\t\t\tparts.push((tmpStr.slice(-(tmpStr.length - position))).trim())\n\t\t\t\t\t}\n\t\t\t\t\t// set delimiter: should aways be the last item in parts[1] after we strip location\n\t\t\t\t\t\t// usually = \":\" (charCode 58) but zh-Hans-CN = \"：\" (charCode 65306) and my = \" -\"\n\t\t\t\t\tlet strLoc = (parts[1].slice(0, locStart)).trim() // trim\n\t\t\t\t\tdelimiter = strLoc.slice(-1) // last char\n\t\t\t\t\t// concat some bits\n\t\t\t\t\t\t// don't trim strName prior to +delimiter (which is length 1)\n\t\t\t\t\t\t// e.g. 'fr','my' have a preceeding space, so capture that\n\t\t\t\t\tlet strName = parts[0].split(delimiter)[0] + delimiter\n\t\t\t\t\t// use an object as joining for a string can get weird with RTL\n\t\t\t\t\tlet oData = {\n\t\t\t\t\t\t'delimiter': delimiter +' (' + delimiter.charCodeAt(0) +')', // redundant but record it for debugging\n\t\t\t\t\t\t'error': strName,\n\t\t\t\t\t\t'line': parts[2].trim(),\n\t\t\t\t\t\t'location': strLoc,\n\t\t\t\t\t}\n\t\t\t\t\tisXML['n00'] = oData\n\t\t\t\t}\n\t\t\t\t// parts[0] is always the error message\n\t\t\t\tvalue = parts[0]\n\t\t\t\tlet trimLen = parts[0].split(delimiter)[0].length + 1\n\t\t\t\tvalue = value.slice(trimLen).trim()\n\t\t\t} else {\n\t\t\t\t// blink + webkit\n\t\t\t\tlet newtarget = target.children\n\t\t\t\t// [0] \"This page contains the following errors:\"\n\t\t\t\t// [1] \"error on line X at column Y: \" + actual error\n\t\t\t\t// [2] \"Below is a rendering of the page up to the first error.\"\n\t\t\t\tstr = newtarget[1].textContent\n\t\t\t\t// cleanup english XML messages: I don't think blink or webkit translate these\n\t\t\t\tlet isErrorStr = '', isError = 'error on line' == str.slice(0,13)\n\t\t\t\tif (isError) {\n\t\t\t\t\tlet position = str.indexOf(\": \")\n\t\t\t\t\tif (position > 0) {\n\t\t\t\t\t\tisErrorStr = str.slice(0,28)\n\t\t\t\t\t\tstr = str.slice(position + 2)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvalue = str.replace(/\\n/g,'')\n\t\t\t\tif ('n02' == k) {\n\t\t\t\t\tif (isError) {\n\t\t\t\t\t\tisXML['n00'] = {0: newtarget[0].textContent, 1: isErrorStr, 2: newtarget[2].textContent}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tisXML['n00'] = {0: newtarget[0].textContent, 2: newtarget[2].textContent}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//if ('n02' == k) {console.log(str, '\\n', newtarget)} //debug\n\t\t\t}\n\t\t\tisXML[k] = value\n\t\t}\n\t} catch(e) {\n\t\tisXML = e+''\n\t}\n\tif (isGecko && gClear) {console.clear()}\n\tlog_perf(SECTG, 'isXML', t0,'', ('string' == typeof isXML ? zErr : ''))\n\treturn resolve()\n})\n\nfunction get_isXSLT() {\n\t// FF151: dom.xslt.enabled\n\t\t// no need to record the error, returning n/a catches the entropy/change\n\t\t// the boolean can be used to determine health\n\n\t// note: this does not reflect the preference on reruns but rather the error\n\t\t// e.g. once enabled, the error is not thrown even if the pref is flipped to false\n\tisXSLT = isGecko ? true : false\n\tif (isGecko) {\n\t\ttry {\n\t\t\tlet x = new XSLTProcessor()\n\t\t} catch(e) {\n\t\t\tif ('ReferenceError: XSLTProcessor is not defined' == e+'') {isXSLT = false}\n\t\t}\n\t}\n\treturn\n}\n\n/*** PREREQ ***/\n\nfunction get_isDomRect() {\n\tif (!isGecko) {return}\n\t// like canvas: this is only testing for protection, so always run in gecko including basic mode\n\t// determine valid domrect methods + grab data for analysis\n\tlet t0 = nowFn()\n\tconst names = ['element_getbounding', 'element_getclient','range_getbounding','range_getclient']\n\tconst props = ['bottom','height','left','right','top','width','x','y']\n\t// reset: assume lies\n\tisDomRect = -1\n\taDomRect = [false, false, false, false]\n\toDomRect = {}\n\n\tlet el = dom.tzpRect\n\tfor (let i=0; i < 4; i++) {\n\t\tlet METRIC = names[i]\n\t\tlet tmpobj = {}, hash\n\t\ttry {\n\t\t\tlet obj\n\t\t\tif (0 == i) {\n\t\t\t\tobj = el.getBoundingClientRect()\n\t\t\t} else if (1 == i) {\n\t\t\t\tobj = el.getClientRects()[0]\n\t\t\t} else {\n\t\t\t\tlet range = document.createRange()\n\t\t\t\trange.selectNode(el)\n\t\t\t\tobj = (2 == i ? range.getBoundingClientRect() : range.getClientRects()[0] )\n\t\t\t}\n\t\t\tprops.forEach(function(prop){\n\t\t\t\tlet value = obj[prop]\n\t\t\t\tif (runST && i == 0) {value = 's'}\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (runSL) {value += 0.1}\n\t\t\t\ttmpobj[prop] = value\n\t\t\t})\n\t\t\thash = mini(tmpobj)\n\t\t\taDomRect[i] = ('642e7ef0' == hash)\n\t\t} catch(e) {\n\t\t\tlog_error(15, 'domrect_'+ METRIC, e)\n\t\t\taDomRect[i] = zErr\n\t\t\ttmpobj = zErr\n\t\t\thash = zErr\n\t\t}\n\t\tif (undefined == oDomRect[hash]) {\n\t\t\toDomRect[hash] = {'data': tmpobj, 'methods': [METRIC]}\n\t\t} else {\n\t\t\toDomRect[hash]['methods'].push(METRIC)\n\t\t}\n\t}\n\t//aDomRect = [false, false, false, false]\n\tisDomRect = aDomRect.indexOf(true)\n\t//console.log(isDomRect, aDomRect)\n\tlog_perf(SECTP, 'isDomRect', t0,'', aDomRect.join(', '))\n\treturn\n}\n\nfunction get_isPerf() {\n\tisPerf = false\n\tfor (let i=1; i < 50 ; i++) {\n\t\ttry {\n\t\t\tlet value = Math.trunc(performance.now() - performance.now())\n\t\t\tif (0 !== value && -1 !== value) {return}\n\t\t} catch {return}\n\t}\n\tisPerf = true\n}\n\n/** CLICKING **/\n\nfunction copyclip(element) {\n\tif ('clipboard' in navigator) {\n\t\ttry {\n\t\t\tlet content = dom[element].innerHTML\n\t\t\tif ('metricsDisplay' == element) {\n\t\t\t\tcontent = dom['metricsTitle'].innerHTML +'\\n\\n'+ content\n\t\t\t}\n\t\t\t// remove spans, change linebreaks\n\t\t\tlet regex = /<br\\s*[\\/]?>/gi\n\t\t\tcontent = content.replace(regex, '\\r\\n')\n\t\t\tcontent = content.replace(/<\\/?span[^>]*>/g,'')\n\t\t\t// get it\n\t\t\tnavigator.clipboard.writeText(content).then(function() {\n\t\t\t\t// indicate it\n\t\t\t\ttry {\n\t\t\t\t\tlet target = dom.metricsBtnCopy\n\t\t\t\t\ttarget.classList.add('indicate')\n\t\t\t\t\ttarget.classList.remove('btn0')\n\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\ttarget.classList.add('btn0')\n\t\t\t\t\t\ttarget.classList.remove('indicate')\n\t\t\t\t\t}, 500)\t\n\t\t\t\t} catch(e) {}\n\t\t\t}, function() {\n\t\t\t\t// clipboard write failed\n\t\t\t})\n\t\t} catch {}\n\t}\n}\n\nfunction showhide(id, style) {\n\tlet items = document.getElementsByClassName('tog'+ id)\n\tfor (let i=0; i < items.length; i++) {items[i].style.display = style}\n}\n\nfunction togglerows(id, word) {\n\tlet items = document.getElementsByClassName('tog'+ id)\n\tlet\tstyle = items[0].style.display == 'table-row' ? 'none' : 'table-row'\n\tfor (let i=0; i < items.length; i++) {items[i].style.display = style}\n\tif ('btn' == word) {\n\t\tword = '['+ ('none' == style ? '+' : '-') +']'\n\t} else {\n\t\tword = ('none' == style ? '&#9660; show ' : '&#9650; hide ') + ('' == word || word === undefined ? 'details' : word)\n\t}\n\ttry {dom['label'+ id].innerHTML = word} catch {}\n}\n\n/*** METRICS DISPLAY ***/\n\nfunction json_highlight(json, clrValues = false) {\n\tlet clrSymbols = false\n\tif ('health' == overlayName) {\n\t\tclrValues = false\n\t\tif ('_summary' == overlayHealth) {clrSymbols = true}\n\t}\n\tif ('string' !== typeof json) {\n\t\t// get the overlay width and use that to calculate a json maxlength\n\t\t\t// old hardcoded code: linux 88, android 68, windows/mac incl. BB = 95\n\t\tlet minLen = 50, len = isDesktop ? 95 : minLen\n\t\toverlayInfo = ''\n\t\ttry {\n\t\t\t// we use the table width because it is visible\n\t\t\t\t// overlaycontent available width is 100px less\n\t\t\tlet contentWidth = dom.tbfp.clientWidth - (100 + isScrollbar) \n\t\t\tlen = (contentWidth/overlayCharLen) - 2 // give us some wiggle room\n\t\t\tif (len < minLen) {len = minLen} else {len = Math.floor(len)}\n\t\t\toverlayInfo = contentWidth +' | '+ overlayCharLen +' | '+ len\n\t\t\tlet strLast = '1234567890', strSpacer = (' ').repeat(len -(overlayInfo.length + strLast.length))\n\t\t\toverlayInfo += strSpacer + strLast\n\t\t} catch(e) {}\n\t\tjson = json_stringify(json, len);\n\t}\n\tjson = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\treturn json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {\n\t\tvar cls = 'number';\n\t\tif (/^\"/.test(match)) {\n\t\t\tif (/:$/.test(match)) {\n\t\t\t\tcls = 'key';\n\t\t\t} else {\n\t\t\t\tif (clrValues) {\n\t\t\t\t\tcls = 'string';\n\t\t\t\t} else if (clrSymbols) {\n\t\t\t\t\tcls =''\n\t\t\t\t\tmatch = match.replace(tick, green_tick)\n\t\t\t\t\tmatch = match.replace(cross, red_cross)\n\t\t\t\t} else {\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (/true|false/.test(match)) {\n\t\t\tcls = 'boolean';\n\t\t} else if (/null/.test(match)) {\n\t\t\tcls = 'null';\n\t\t}\n\t\treturn '<span class=\"'+ cls +'\">'+ match +'</span>';\n\t})\n}\n\nfunction json_stringify(passedObj, overlayMaxLength = 95, options = {}) {\n\t/* https://github.com/lydell/json-stringify-pretty-compact */\n\tconst stringOrChar = /(\"(?:[^\\\\\"]|\\\\.)*\")|[:,]/g;\n\tconst indent = JSON.stringify(\n\t\t[1],\n\t\tundefined,\n\t\toptions.indent === undefined ? 2 : options.indent\n\t).slice(2, -3);\n\tconst maxLength =\n\t\tindent === ''\n\t\t\t? Infinity\n\t\t\t: options.maxLength === undefined\n\t\t\t? overlayMaxLength\n\t\t\t: options.maxLength;\n\tlet { replacer } = options;\n\n\treturn (function _stringify(obj, currentIndent, reserved) {\n\t\tif (obj && 'function' === typeof obj.toJSON) {\n\t\t\tobj = obj.toJSON()\n\t\t}\n\t\tconst string = JSON.stringify(obj, replacer);\n\t\tif (string === undefined) {\n\t\t\treturn string\n\t\t}\n\t\tconst length = maxLength - currentIndent.length - reserved;\n\t\tif (string.length <= length) {\n\t\t\tconst prettified = string.replace(\n\t\t\t\tstringOrChar,\n\t\t\t\t(match, stringLiteral) => {\n\t\t\t\t\treturn stringLiteral || `${match} `;\n\t\t\t\t}\n\t\t\t);\n\t\t\tif (prettified.length <= length) {\n\t\t\t\treturn prettified;\n\t\t\t}\n\t\t}\n\t\tif (replacer != null) {\n\t\t\tobj = JSON.parse(string);\n\t\t\treplacer = undefined;\n\t\t}\n\t\tif ('object' == typeof obj && obj !== null) {\n\t\t\tconst nextIndent = currentIndent + indent;\n\t\t\tconst items = [];\n\t\t\tlet index = 0;\n\t\t\tlet start;\n\t\t\tlet end;\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tstart = '[';\n\t\t\t\tend = ']';\n\t\t\t\tconst { length } = obj;\n\t\t\t\tfor (; index < length; index++) {\n\t\t\t\t\titems.push(\n\t\t\t\t\t\t_stringify(obj[index], nextIndent, index === length - 1 ? 0 : 1) ||\n\t\t\t\t\t\t'null'\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstart = '{';\n\t\t\t\tend = '}';\n\t\t\t\tconst keys = Object.keys(obj);\n\t\t\t\tconst { length } = keys;\n\t\t\t\tfor (; index < length; index++) {\n\t\t\t\t\tconst key = keys[index];\n\t\t\t\t\tconst keyPart = `${JSON.stringify(key)}: `;\n\t\t\t\t\tconst value = _stringify(\n\t\t\t\t\t\tobj[key],\n\t\t\t\t\t\tnextIndent,\n\t\t\t\t\t\tkeyPart.length + (index === length - 1 ? 0 : 1)\n\t\t\t\t\t);\n\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\titems.push(keyPart + value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (items.length > 0) {\n\t\t\t\treturn [start, indent + items.join(`,\\n${nextIndent}`), end].join(\n\t\t\t\t`\\n${currentIndent}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\treturn string;\n\t})(passedObj, '', 0);\n}\n\nfunction metricsAction(type) {\n\tif ('close' == type) {\n\t\tdom.modaloverlay.style.display = 'none'\n\t\tdom.overlay.style.display = 'none'\n\t\tdom.metricsDisplay.innerHTML = '' // clear so we always start at the top\n\t\tdom.overlayInfo.innerHTML = ''\n\t} else if ('console' == type) {\n\t\tif (metricsData !== undefined) {console.log(metricsTitle, metricsData)}\n\t} else if ('download' == type) {\n\tif (metricsData == undefined) {return}\n\t\ttry {\n\t\t\tlet name = metricsPrefix + (metricsTitle.replaceAll(': ', '_')).toLowerCase()\n\t\t\tvar file = new Blob([JSON.stringify(metricsData, null, 2)], {type: 'application/json'})\n\t\t\tvar a = document.createElement('a')\n\t\t\ta.href = URL.createObjectURL(file)\n\t\t\ta.download = name\n\t\t\ta.click()\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t}\n\t} else {\n\t\t// user changed settings\n\t\tdom.metricsDisplay.innerHTML = ''\n\t\t// force delay so reflow = always start at the top\n\t\tsetTimeout(function() {\n\t\t\tmetricsShow(overlayName, overlayScope)\n\t\t}, 0)\n\t}\n}\n\nfunction metricsEvent(evt) {\n\tif ('block' !== dom.modaloverlay.style.display) {return}\n\tevt = evt || window.event\n\tvar isEscape = false\n\tif ('key' in evt) {\n\t\tisEscape = ('Escape' == evt.key || 'Esc' == evt.key)\n\t} else {\n\t\tisEscape = (27 == evt.keyCode)\n\t}\n\tif (isEscape) {metricsAction('close')\n\t} else if ((evt.ctrlKey || evt.metaKey) && 67 == evt.keyCode) {\n\t\tcopyclip('metricsDisplay')\n\t}\n}\n\nfunction metricsShow(name, scope) {\n\toverlayName = name\n\n\tlet isKit = false\n\tif (isGecko & isSmart) {\n\t\t// atm this is pretty much limited to health, pixels_match, timezone_offsets_data\n\t\t// and fonts_detected\n\t\ttry {\n\t\t\tlet btn = dom[scope+name]\n\t\t\tif (undefined == btn) {btn = dom[name]}\n\t\t\tisKit = 'btngood' == btn.children[0].classList[0]\n\t\t} catch(e) {}\n\t}\n\tif (isKit) {dom.overlaykit.classList.remove('hidden')} else {dom.overlaykit.classList.add('hidden')}\n\n\tlet isVisible = dom.modaloverlay.style.display == 'block'\n\tlet aShowFormat = ['fingerprint','health'] // lies need to be made into an object with metrics as key\n\tlet isSection = sectionNames.includes(name)\n\tlet isShowFormat = aShowFormat.includes(name) || isSection\n\tlet isHealth = name == 'health'\n\n\tlet target = name, overlayScope = scope\n\tif (isShowFormat) {target = metricsUI(target, isVisible, isSection, isHealth)}\n\tlet data, color = 99, filter = ''\n\tif (name == SECT97 || name == SECT98 || name == SECT99) {\n\t\t// prototype/proxy\n\t\tdata = gData[name]\n\t} else if (scope+'_health_metrics' == name) {\n\t\tdata = gData.health[scope+'_metrics']\n\t\ttarget = 'health_metrics'\n\t} else if (zFP+'_metrics' == name) {\n\t\tdata = gData[zFP][scope+'_metrics']\n\t} else if (aShowFormat.includes(name)) {\n\t\t// FP/health\n\t\tif (isHealth) {\n\t\t\tif (!dom.healthAll.checked) {filter = dom.healthPass.checked ? '_pass' : '_fail'}\n\t\t\tif (overlayHealth == '_detail') {overlayHealth = ''; target = name}\n\t\t} else {\n\t\t\tif (overlayFP == '_detail') {overlayFP = ''; target = name}\n\t\t}\n\t\tdata = gData[name][scope + (isHealth ? overlayHealth + filter : overlayFP)]\n\t} else if (name == 'alerts' || name == 'errors' || name == 'lies') {\n\t\t// global alerts/errors/lies\n\t\tdata = gData[name][scope]\n\t} else if (isSection) {\n\t\t// section\n\t\tif (overlaySection == '_detail') {overlaySection = ''; target = name}\n\t\tdata = sData[zFP][scope + overlaySection][name]\n\t} else {\n\t\t// section alerts/errors/lies\n\t\tlet nameslice = name.slice(0,4)\n\t\tif (nameslice == 'erro' || nameslice == 'aler' || nameslice == 'lies') {\n\t\t\tlet slicelen = nameslice == 'lies' ? 4 : 6\n\t\t\ttarget = name.slice(0, slicelen)\n\t\t\tname = name.slice(slicelen)\n\t\t\tdata = sData[target][scope][name]\n\t\t\ttarget = target.toUpperCase() +': '+ name\n\t\t} else {\n\t\t\t// detail data\n\t\t\tdata = sDetail[scope][name]\n\t\t}\n\t}\n\tmetricsData = data\n\tlet aCached = ['fingerprint','fingerprint_flat','misc']\n\tlet isCache = aCached.includes(target)\n\tlet cTarget = scope + ('misc' == target ? '_'+ target : overlayFP)\n\n\tlet isColor = !(target == 'window_functions')\n\tlet\tdisplay = data !== undefined ? (isCache ? sDataTemp['cache'][cTarget] : json_highlight(data, isColor)): ''\n\t// dev: no need to display overlayInfo everywhere, limit\n\tif ('feature' == name) {dom.overlayInfo.innerHTML = overlayInfo}\n\n\t//add btn, show/hide options, display\n\tlet hash = mini(data)\n\tmetricsTitle = (scope == undefined ? '' : scope.toUpperCase() +': ') + target + filter +': '+ hash\n\tdom.metricsTitle.innerHTML = metricsTitle\n\tif (isVisible) {\n\t\t// avoid reflow\n\t\tdom.metricsDisplay.innerHTML = display\n\t} else {\n\t\t//dom.metricDownload.style.display = isShowFormat ? 'inline' : 'none'\n\t\t//^allow download everwhere\n\n\t\tdom.metricOptions.style.display = isShowFormat ? 'block' : 'none'\n\t\tdom.modaloverlay.style.display = 'block'\n\t\tdom.overlay.style.display = 'block'\n\t\t// delay so overlay is painted\n\t\tsetTimeout(function() {dom.metricsDisplay.innerHTML = display}, 0)\n\t}\n}\n\nfunction metricsUI(target, isVisible, isSection, isHealth) {\n\tif (!isVisible) {\n\t\t// tidy up options\n\t\tdom.optFlat.style.display = target == 'fingerprint' ? 'block': 'none'\n\t\tdom.optList.style.display = target == 'fingerprint' ? 'block': 'none'\n\t\tdom.groupHealth.style.display = isHealth ? 'block' : 'none'\n\n\t\t// ensure suitable options\n\t\tlet selected = ''\n\t\tif (isSection) {\n\t\t\toverlaySection = ('' == overlaySection ? '_detail' : '_summary')\n\t\t\tselected = overlaySection\n\t\t} else if (isHealth) {\n\t\t\toverlayHealth = ('' == overlayHealth ? '_detail' : '_summary')\n\t\t\tselected = overlayHealth\n\t\t} else {\n\t\t\tif (overlayFP == '') {overlayFP = '_detail'}\n\t\t\tselected = overlayFP\n\t\t}\n\t\tdom['optFormat'+ selected].checked = true\n\t}\n\n\t// update final target to match checked item\n\tlet items = document.getElementsByName('optOverlay')\n\tfor (let i=0; i < items.length; i++) {\n\t\tif (items[i].checked) {\n\t\t\ttarget += items[i].value\n\t\t\tlet check = items[i].value\n\t\t\tif (isSection) {overlaySection = check} else if (isHealth) {overlayHealth = check} else {overlayFP = check}\n\t\t}\n\t}\n\t// return final target\n\treturn target\n}\n\n/*** OUTGOING ***/\n\nfunction lookup_health(sect, metric, scope, isPass) {\n\t// return summary 'error/untrustworthy/str/hash' + detail (underlying data)\n\tif ('window.caches' == metric) {metric = 'caches'}\n\tlet data ='', hash =''\n\t// error?\n\ttry {data = gData['errors'][scope][sect][metric]; if (undefined !== data) {return([zErr, data])}} catch {}\n\tif ('pixels_match' == metric) {\n\t\tdata = sDetail[scope][metric]\n\t\tif ('string' == typeof data) {return([zErr, data])}\n\t}\n\t// lie?\n\ttry {data = gData['lies'][scope][sect][metric]; if (undefined !== data) {return([zLIE, zLIE])}} catch {}\n\t// nested, lookups, FP|detail data\n\ttry {\n\t\tlet nested ='', tmpdata, sDetailTemp\n\t\tif ('pixels_match' !== metric && 'pixels_' == metric.slice(0,7)) {nested = 'pixels'; metric = metric.replace('pixels_','')}\n\t\tif ('useragent_' == metric.slice(0,10)) {nested = 'useragent'; metric = metric.replace('useragent_','')}\n\t\tif ('media_' == metric.slice(0,6)) {nested = 'media'; metric = metric.replace('media_','')}\n\n\t\tif ('' !== nested) {\n\t\t\tdata = gData[zFP][scope][sect]['metrics'][nested]['metrics'][metric]\n\t\t} else if (sDetail[scope].lookup[metric] !== undefined) {\n\t\t\tdata = sDetail[scope].lookup[metric]\n\t\t} else if ('font_names' == metric || 'pixels_match' == metric) {\n\t\t\t// special case font names: not in FP / hash = full enumeration\n\t\t\tdata = sDetail[scope][metric]\n\t\t\thash = mini(data)\n\t\t} else {\n\t\t\tdata = gData[zFP][scope][sect]['metrics'][metric]\n\t\t}\n\t\tif (undefined !== data) {\n\t\t\tlet typeCheck = typeFn(data, true)\n\t\t\thash = '' == hash ? data : hash\n\t\t\t// handle sDetailTemp: copy per run so it doesn't change in gData\n\t\t\tif (undefined !== sDetail[scope][metric]) {\n\t\t\t\tsDetailTemp = sDetail[scope][metric]\n\t\t\t\tif (!isPass) {\n\t\t\t\t\t// special case font names/faces: detail should reflect isPass: can't just check for !== undefined\n\t\t\t\t\t// e.g. windows FPP will still have unexpected data (for RFP)\n\t\t\t\t\tif ('font_faces' == metric || 'font_names' == metric || 'font_offscreen' == metric) {\n\t\t\t\t\t\tsDetailTemp = sDetail[scope][metric +'_health']\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet tmpCheck = typeFn(sDetailTemp)\n\t\t\t\tif ('object' == tmpCheck) {\n\t\t\t\t\tdata = {}\n\t\t\t\t\tfor (const k of Object.keys(sDetailTemp)) {data[k] = sDetailTemp[k]}\n\t\t\t\t} else if ('array' == tmpCheck) {\n\t\t\t\t\tdata = []\n\t\t\t\t\tsDetailTemp.forEach(function(item){data.push(item)})\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ('object' === typeCheck) {\n\t\t\t\ttry {hash = gData[zFP][scope][sect]['metrics'][metric].hash} catch {}\n\t\t\t}\n\t\t\treturn([hash, data])\n\t\t}\n\t} catch(e) {\n\t\tconsole.log(metric,e)\n\t}\n\treturn(['',''])\n}\n\nfunction output_health(scope) {\n\t// done after populating global FP, errors, lies\n\tif (!isSmart) {return}\n\tlet h = \"health\", countPass = 0, countTotal = Object.keys(gData.health[scope +'_collect']).length\n\tgData[h][scope] = {}\n\tgData[h][scope +'_metrics'] = []\n\tgData[h][scope +'_fail'] = {}\n\tgData[h][scope +'_pass'] = {}\n\tgData[h][scope +'_summary'] = {}\n\tgData[h][scope +'_summary_fail'] = {}\n\tgData[h][scope +'_summary_pass'] = {}\n\n\tlet target = gData.health[scope +'_collect']\n\ttry {\n\t\tfor (const metric of Object.keys(target).sort()) {\n\t\t\ttry {\n\t\t\t\tlet sect = target[metric][0]\n\t\t\t\tlet isPass = target[metric][1]\n\t\t\t\tif (isPass) {countPass++}\n\t\t\t\tlet symbol = isPass ? tick : cross\n\t\t\t\tlet sub = isPass ? '_pass' : '_fail'\n\t\t\t\t// lookup\n\t\t\t\tlet data = lookup_health(sect, metric, scope, isPass)\n\t\t\t\tlet summary = data[0], detail = data[1]\n\t\t\t\tif ('' !== summary) {summary = ' '+ summary}\n\n\t\t\t\t// populate detail\n\t\t\t\tif ('' == detail) {detail = symbol}\n\t\t\t\tgData[h][scope][metric] = detail\n\t\t\t\tgData[h][scope + sub][metric] = detail\n\t\t\t\t// populate summary + metriclist\n\t\t\t\tgData[h][scope +'_metrics'].push(metric)\n\t\t\t\tgData[h][scope +'_summary'][metric] = symbol + summary\n\t\t\t\tgData[h][scope +'_summary'+ sub][metric] = symbol + summary\n\t\t\t} catch(e) {\n\t\t\t\tconsole.log(metric, e)\n\t\t\t}\n\t\t}\n\t\tif (countTotal > 0) {\n\t\t\tlet isAll = countPass == countTotal\n\t\t\tlet overlayHealthCount = countPass +'/'+ countTotal\n\t\t\tlet btnPart1 = addButton((isAll ? 'good' : 'bad'), h, countPass)\n\t\t\tbtnPart1 = btnPart1.replace(']','')\t+ '<span style=\"letter-spacing: -0.2em\"> | </span>'\n\t\t\tdom[scope + h].innerHTML = btnPart1\n\t\t\t\t+ addButton(0,'document_health_metrics', countTotal).replace('[','')\n\t\t\tif (isAll) {dom.healthAll.checked = true} else {dom.healthFail.checked = true}\n\t\t}\n\t\tdelete gData[h][scope +'_collect']\n\t} catch(e) {\n\t\tconsole.log(e)\n\t}\n}\n\nfunction output_perf(id, click = false) {\n\tif (!isPerf) {return}\n\n\tlet array = id == \"all\" ? gData[\"perf\"] : sDataTemp[\"perf\"]\n\tlet target = id == \"all\" ? \"perfG\" : \"perfS\"\n\tlet btn = id == \"all\" ? dom.perfGBtn : dom.perfSBtn\n\n\t// toggle perf depth\n\tlet isMore = dom[target +\"Btn\"].innerHTML === \"less\"\n\tif (click) {\n\t\tisMore = !isMore // user toggled it\n\t\tdom[target +\"Btn\"] = isMore ? \"less\" : \"more\"\n\t}\n\n\tlet aPretty = [],\n\t\tcolor1 = \" <span class='s14'>\",\n\t\tcolor2 = \" <span class='s7'>\",\n\t\tcolor3 = \" <span class='s12'>\",\n\t\ts98 = \"<span class='s99'>\", // trimmed\n\t\tpageLoad = false,\n\t\tisStart = false,\n\t\tnFix = isDecimal ? 2 : 0,\n\t\tpad = isDecimal ? 18 : 15,\n\t\tpadFix = isDecimal ? 3 : 0\n\tif (isMore) {pad = 32}\n\n\ttry {\n\t\tarray.forEach(function(array) {\n\t\t\tlet type = array[0],\n\t\t\t\tname = array[1],\n\t\t\t\ttime1 = array[2],\n\t\t\t\ttime2 = array[3],\n\t\t\t\textra = array[4]\n\t\t\tif ('string' == typeof extra) {extra = extra.trim()}\n\t\t\textra = undefined == extra  ? '' : extra !== '' ? ' | '+ extra : ''\n\n\t\t\t// header\n\t\t\tif (isMore && 1 === type) {\n\t\t\t\tif (\"IMMEDIATE\" == name) {pageLoad = true}\n\t\t\t\tif (\"DOCUMENT START\" == name) {isStart = true}\n\t\t\t\ttime1 = pageLoad ? \" \"+ time1 : \"\"\n\t\t\t\taPretty.push(color1 + name +\":\"+ time1 + sc)\n\t\t\t}\n\t\t\t// section/detail\n\t\t\tif (type > 1) {\n\t\t\t\tif (id == 'all') {\n\t\t\t\t\ttime1 = time2 - time1\n\t\t\t\t\ttime2 = time2 - gt1 // use gt1: only reset on global runs\n\t\t\t\t}\n\t\t\t\ttime1 = Number(time1).toFixed(nFix)\n\t\t\t\ttime2 = Number(time2).toFixed(nFix)\n\t\t\t}\n\t\t\t// section\n\t\t\tif (2 === type) {\n\t\t\t\ttime2 = id == \"all\" ? \" |\"+ color3 + time2.padStart(4 + padFix) +\" ms</span>\" : \"\"\n\t\t\t\tlet pretty = name.padStart(pad) +\":\"+ color2 + time1.padStart(4 + padFix) +\"</span> ms\"\n\t\t\t\t\t+ time2 + extra\n\t\t\t\taPretty.push(pretty)\n\t\t\t\tif (sectionNames.includes(name)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdom[\"perf\"+ name] = \" \"+ time1 +\" ms\"\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tconsole.error('perf'+ name +' element is missing')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// detail\n\t\t\tif (isMore && 3 === type) {\n\t\t\t\tname = name.replace(\": \"+ SECTNF,\"\")\n\t\t\t\tif (name.length > pad) {\n\t\t\t\t\tlet parts = name.split(\":\")\n\t\t\t\t\tlet newlen = pad - (parts[1].length + 1)\n\t\t\t\t\tname = name.slice(0, newlen) +\":\"+ parts[1]\n\t\t\t\t}\n\t\t\t\tif (id !== \"all\") {isStart = true}\n\t\t\t\ttime2 = isStart ? \" |\"+ s98 + time2.padStart(5 + padFix) +\" ms</span>\" : ''\n\t\t\t\tlet pretty = s98 + name.padStart(pad) + sc +\":\" + s98 + time1.padStart(5 + padFix) +\" ms</span>\"\n\t\t\t\t\t+ time2 + extra\n\t\t\t\taPretty.push(pretty)\n\t\t\t}\n\t\t})\n\t\tdom[target].innerHTML = aPretty.join(\"<br>\")\n\t} catch(e) {\n\t\tconsole.error(e)\n\t}\n}\n\nfunction output_section(section, scope) {\n\t// ToDo: SANITY CHECKS\n\tlet aSection = section == \"all\" ? sectionOrder : [section]\n\n\t// propagate onces to sDataTemp\n\t\t// don't worry about all vs section: sDataTemp is temp\n\ttry {\n\t\tbtnList.forEach(function(item) {\n\t\t\tlet once = item+\"once\"\n\t\t\tif (gData[once] !== undefined && gData[once][scope] !== undefined) {\n\t\t\t\tfor (const s of Object.keys(gData[once][scope])) {\n\t\t\t\t\tif (!sectionNames.includes(s)) {\n\t\t\t\t\t\t// non-section: straight to sData: sorted\n\t\t\t\t\t\tif (sData[item][scope] == undefined) {sData[item][scope] = {}}\n\t\t\t\t\t\tif (sData[item][scope][s] == undefined) {sData[item][scope][s] = {}}\n\t\t\t\t\t\tfor (const m of Object.keys(gData[once][scope][s]).sort()) {\n\t\t\t\t\t\t\tsData[item][scope][s][m] = gData[once][scope][s][m]\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// section: to sDataTemp: no sort\n\t\t\t\t\t\t\t// this is just section onces: _all_ sections to sData etc are looped below\n\t\t\t\t\t\tif (sDataTemp[item][scope] == undefined) {sDataTemp[item][scope] = {}}\n\t\t\t\t\t\tif (sDataTemp[item][scope][s] == undefined) {sDataTemp[item][scope][s] = {}}\n\t\t\t\t\t\tfor (const m of Object.keys(gData[once][scope][s])) {\n\t\t\t\t\t\t\tsDataTemp[item][scope][s][m] = gData[once][scope][s][m]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t} catch(e) {\n\t\tconsole.error(e)\n\t}\n\n\t// propagate data\n\tlet gList = {}, gFlat = {}\n\tlet summary = scope+'_summary', metriclist = scope+'_metrics'\n\tif (gRun) {\n\t\tgData[zFP][summary] = {}\n\t\tsData[zFP][summary] = {}\n\t\tgData[zFP][metriclist] = []\n\t}\n\n\taSection.forEach(function(number) {\n\t\tlet data = {}, datasummary = {}, hash, count, hashsummary\n\t\tlet name = sectionMap[number]\n\t\tsData[zFP][summary][name] = {}\n\t\t// section\n\t\ttry {\n\t\t\tdata = {}, datasummary = {}\n\t\t\tlet obj = sDataTemp[zFP][scope][number]\n\t\t\tfor (const k of Object.keys(obj).sort()) {\n\t\t\t\tdata[k] = obj[k]\n\t\t\t\tlet value = (\"object\" == typeof data[k] && data[k] !== null ? data[k][\"hash\"] : data[k])\n\t\t\t\tdatasummary[k] = value\n\t\t\t\tif (gRun) {\n\t\t\t\t\tgFlat[k] = obj[k]\n\t\t\t\t\tgList[k] = datasummary[k]\n\t\t\t\t}\n\t\t\t}\n\t\t\tsData[zFP][scope][name] = data\n\t\t\thash = mini(data)\n\t\t\tcount = Object.keys(data).length\n\n\t\t\tsData[zFP][summary][name] = datasummary\n\t\t\thashsummary = mini(datasummary)\n\t\t\t// global\n\t\t\tif (gRun) {\n\t\t\t\t// FP\n\t\t\t\tgData[zFP][scope][name] = {}\n\t\t\t\tgData[zFP][scope][name][\"hash\"] = hash\n\t\t\t\tgData[zFP][scope][name][\"metrics\"] = data\n\t\t\t\t// summary\n\t\t\t\tgData[zFP][summary][name] = {}\n\t\t\t\tgData[zFP][summary][name][\"hash\"] = hashsummary\n\t\t\t\tgData[zFP][summary][name][\"metrics\"] = datasummary\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t}\n\t\t// display\n\t\ttry {\n\t\t\tfor (const d of Object.keys(sDataTemp[\"display\"][scope][number])) {\n\t\t\t\tlet str\n\t\t\t\ttry {\n\t\t\t\t\tstr = sDataTemp[\"display\"][scope][number][d]\n\t\t\t\t\tdom[d].innerHTML = str\n\t\t\t\t} catch(e) {\n\t\t\t\t\tconsole.error(d, str, e.name, e.emssage)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t}\n\t\tlet sHash = hash + addButton(0, name, count +\" metric\"+ (count == 1 ? \"\" : \"s\"), \"btns\")\n\n\t\t// section buttons\n\t\t\t// sDataTemp sorted to sData\n\t\tlet aBtns = []\n\t\ttry {\n\t\t\tbtnList.forEach(function(item) {\n\t\t\t\tlet btn =''\n\t\t\t\tif (sDataTemp[item][scope] !== undefined && sDataTemp[item][scope][name] !== undefined) {\n\t\t\t\t\tif (sData[item][scope] == undefined) {sData[item][scope] = {}}\n\t\t\t\t\tif (sData[item][scope][name] == undefined) {sData[item][scope][name] = {}}\n\t\t\t\t\tlet typeCheck = typeFn(sDataTemp[item][scope][name], true)\n\t\t\t\t\tfor (const m of Object.keys(sDataTemp[item][scope][name]).sort()) {\n\t\t\t\t\t\tsData[item][scope][name][m] = sDataTemp[item][scope][name][m]\n\t\t\t\t\t}\n\t\t\t\t\tlet count = Object.keys(sData[item][scope][name]).length\n\t\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tlet btnText = count + ' '+ (count == 1 ? item.slice(0,-1) : item) // single/plural\n\t\t\t\t\t\tlet color = ('alerts' === item) ? 'bad' : 0\n\t\t\t\t\t\tbtn = addButton(color, item + name, btnText, 'btns', scope)\n\t\t\t\t\t\taBtns.push(btn.trim())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\tdom[name +'hash'].innerHTML = sHash + aBtns.join('')\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t}\n\t})\n\tif (gRun) {\n\t\t// flat + list\n\t\tlet flat = scope+'_flat', list = scope+'_list'\n\t\tgData[zFP][flat] = {}\n\t\tgData[zFP][list] = {}\n\t\tfor (const k of Object.keys(gFlat).sort()) {\n\t\t\tgData[zFP][flat][k] = gFlat[k]\n\t\t\tgData[zFP][metriclist].push(k) // metric list\n\t\t}\n\t\tfor (const k of Object.keys(gList).sort()) {gData[zFP][list][k] = gList[k]}\n\t\t// recalculate overlayCharLen for cached big jsons\n\t\ttry {\n\t\t\t// at a minimum this is 10 chars \"0, 0, 0, 0\" all platforms/engines\n\t\t\tlet target = dom.position_screen\n\t\t\toverlayCharLen = target.offsetWidth/target.innerText.length\n\t\t} catch(e) {\n\t\t\toverlayCharLen = 7\n\t\t\tconsole.error(e)\n\t\t}\n\t\t// cache big json displays\n\t\tsDataTemp['cache'] = {}\n\t\tsDataTemp['cache'][scope] = json_highlight(gData[zFP][scope])\n\t\tsDataTemp['cache'][flat] = json_highlight(gData[zFP][flat])\n\t}\n\tif (gRun || 18 == section) {\n\t\t// cache misc\n\t\tsDataTemp['cache'][scope +'_misc'] = json_highlight(gData[zFP][scope]['misc'])\n\t}\n}\n\n/*** RECORD ***/\n\nfunction addButton(color, name, text = 'details', btn = 'btnc', scope = isScope) {\n\tif ('' == text) {text = 'details'}\n\treturn \" <span class='btn\"+ color +' '+ btn +\"' onClick='metricsShow(`\"+ name +\"`,`\" + scope +\"`)'>[\"+ text +\"]</span>\"\n}\n\nfunction addBoth(section, metric, str, btn ='', notation ='', data ='', isLies = false, donotuse ='x') {\n\t//if ('x' !== donotuse) {console.log(metric, 'extra paramater passed')}\n\tif (undefined == str) {str += ''}\n\tlet display = str\n\t// check: errors can't be lies\n\tif (data == zErr || data == zErrLog || data == zErrShort) {\n\t\tisLies = false\n\t\tlet sectionName = sectionMap[section]\n\t\t// instead of logging errors in each function add them here\n\t\tif (data == zErrLog || data == zErrShort) {\n\t\t\t// 186 errors: 82e9678e (runST + runSI)\n\t\t\t// 190 errors: 4440a20a (+ runSE)\n\t\t\tdisplay = log_error(section, metric, str)\n\t\t\tif (data == zErrShort) {display = zErr}\n\t\t\tdata = zErr\n\t\t}\n\t}\n\t// check: non obj can't have btns\n\tif ('object' !== typeof data && '' !== btn) {\n\t\tlet typeCheck = typeFn(data, true), value\n\t\tif ('object' !== typeCheck && 'array' !== typeCheck) {btn =''}\n\t}\n\taddDisplay(section, metric, display, btn, notation, isLies)\n\n\t// data = FP: if missing we use str/ which also doubles as our hash for objects\n\tif ('' == data || undefined == data) {data = str}\n\taddData(section, metric, data, str, isLies)\n}\n\nfunction addData(section, metric, data, hash ='', isLies = false, donotuse ='x') {\n\t//if ('x' !== donotuse) {console.log(metric, 'extra paramater passed')}\n\t// check: basic mode/errors can't be lies\n\tif (!isSmart || data == zErr) {isLies = false}\n\tlet typeCheck = typeFn(data, true), value\n\tif ('object' === typeCheck || 'array' === typeCheck) {\n\t\taddDetail(metric, data)\n\t\tvalue = {'hash': hash, 'metrics': data}\n\t} else {\n\t\tvalue = data\n\t}\n\tsDataTemp[zFP][isScope][section][metric] = isLies ? zLIE : value\n\tif (isLies) {\n\t\t// don't add spoofed domrect data\n\t\tlet aIgnore = ['element_font','element_forms','element_mathml','element_other','glyphs']\n\t\tif (aIgnore.includes(metric)) {value = zLIE}\n\t\tlog_known(section, metric, value)\n\t}\n}\n\nfunction addDisplay(section, metric, str ='', btn ='', notation ='', isLies = false, donotuse ='x') {\n\t//if ('x' !== donotuse) {console.log(metric, 'extra paramater passed')}\n\t// check: basic mode can't be lies or have notation\n\tif (!isSmart) {isLies = false; notation =''}\n\t// style lies + ensure lies cannot be good health\n\tif (isLies) {\n\t\tstr = \"<span class='lies'>\"+ str +\"</span>\"\n\t\tnotation = notation.replace(\"class='good'\", \"class='bad'\")\n\t\tnotation = notation.replace(tick, cross)\n\t}\n\tstr += ''\n\tif (isSmart && !isLies && 8 == str.length && !str.includes(' ')) { // limit to hopefully just hashes\n\t\tif (str.includes('6') && str.includes('7')) {\n\t\t\tif (str.indexOf('6') < str.indexOf('7')) {\n\t\t\t\tstr = str.replace('7','<span class=\"s67 s' + section +'\">7</span>')\n\t\t\t\tstr = str.replace('6','<span class=\"s67 s' + section +'\">6</span>')\n\t\t\t}\n\t\t}\n\t}\n\tif ('manual' == isScope) {\n\t\tsDataTemp['display'][isScope][metric] = str + btn + notation\n\t\treturn\n\t}\n\tsDataTemp['display'][isScope][section][metric] = str + btn + notation\n\t// global health: just grab pass/fail\n\tif (gRun && '' !== notation && notation.includes(\"class='health'\")) {\n\t\tlet isPass\n\t\tif (notation.includes('>'+ tick +'<')) {isPass = true} else if (notation.includes('>'+ cross +'<')) {isPass = false}\n\t\tif (isPass !== undefined) {\n\t\t\tgData['health'][isScope +'_collect'][metric] = [sectionMap[section], isPass]\n\t\t}\n\t}\n}\n\nfunction addDetail(metric, data, scope = isScope) {\n\tif (sDetail[scope] == undefined) {sDetail[scope] = {}}\n\tsDetail[scope][metric] = data\n\tif (gRun) {addTiming(metric)}\n}\n\nfunction addTiming(metric) {\n\tlet remainder = gCountTiming % 8, key, value\n\tif (0 == gCountTiming % 5) {\n\t\t// get extra dates\n\t\ttry {gData.timing['date'].push((new Date())[Symbol.toPrimitive]('number'))} catch {}\n\t}\n\ttry {\n\t\tif (0 == remainder) {\n\t\t\tkey = 'exslt'\n\t\t\tif (!isXSLT) {throw zSKIP}\n\t\t\tconst xslText = '<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"'\n\t\t\t\t+' xmlns:date=\"http://exslt.org/dates-and-times\" extension-element-prefixes=\"date\"><xsl:output method=\"html\"/>'\n\t\t\t\t+' <xsl:template match=\"/\"><xsl:value-of select=\"date:date-time()\" /></xsl:template></xsl:stylesheet>'\n\t\t\tconst doc = (new DOMParser).parseFromString(xslText, \"text/xml\")\n\t\t\tlet xsltProcessor = new XSLTProcessor\n\t\t\txsltProcessor.importStylesheet(doc)\n\t\t\tlet fragment = xsltProcessor.transformToFragment(doc, document)\n\t\t\tvalue = (fragment.childNodes[0].nodeValue).slice(0,-6)\n\t\t} else if (1 == remainder || 5 == remainder) {\n\t\t\tkey = 'now'; value = performance.now()\n\t\t} else if (2 == remainder) {\n\t\t\tkey = 'currenttime'; value = gTimeline.currentTime\n\t\t} else if (3 == remainder) {\n\t\t\tkey = 'timestamp'; value = new Event('').timeStamp\n\t\t} else if (4 == remainder) {\n\t\t\tkey = 'date'\n\t\t\tvalue = (new Date())[Symbol.toPrimitive]('number')\n\t\t} else if (6 == remainder) {\n\t\t\tperformance.mark('a')\n\t\t} else if (7 == remainder) {\n\t\t\tkey = 'instant'\n\t\t\tvalue = Temporal.Now.instant().toString()\n\t\t}\n\t\tif (undefined !== key) {\n\t\t\tif (runST) {value = undefined}\n\t\t\tgData.timing[key].push(value)\n\t\t}\n\t} catch(e) {\n\t\tgData.timing[key] = e+''\n\t}\n\tgCountTiming++\n}\n\nfunction addTimings() {\n\t// get first and final values for each to ensure a max diff\n\ttry {gData.timing['now'].push(performance.now())} catch {}\n\ttry {gData.timing['timestamp'].push(new Event('').timeStamp)} catch {}\n\ttry {gData.timing['date'].push((new Date())[Symbol.toPrimitive]('number'))} catch {}\n\ttry {gData.timing['instant'].push(Temporal.Now.instant().toString())} catch {}\n\ttry {\n\t\tif (0 == gCountTiming) {gTimeline = new DocumentTimeline()}\n\t\tgData.timing['currenttime'].push(gTimeline.currentTime)\n\t} catch {}\n\ttry {\n\t\tif (0 == gCountTiming) {performance.clearMarks('a')}\n\t\tperformance.mark('a')\n\t} catch {}\n\tif (0 == gCountTiming) {\n\t\taddTiming('start') // adds first exslt\n\t}\n}\n\nfunction log_alert(section, metric, alert, scope = isScope, isOnce = false) {\n\tif ('string' !== typeof section) {section = sectionMap[section]}\n\tlet key = 'alerts'\n\tif (gRun && isOnce) {\n\t\tkey += 'once'\n\t\t//if (gData[key][scope] == undefined) {gData[key][scope] = {}}\n\t\tif (gData[key][scope][section] == undefined) {gData[key][scope][section] = {}}\n\t\tgData[key][scope][section][metric] = alert\n\t} else {\n\t\tif (sDataTemp[key][scope] == undefined) {sDataTemp[key][scope] = {}}\n\t\tif (sDataTemp[key][scope][section] == undefined) {sDataTemp[key][scope][section] = {}}\n\t\tsDataTemp[key][scope][section][metric] = alert\n\t}\n}\n\nfunction log_error(section, metric, error = zErr, scope = isScope, isOnce = false) {\n\tif ('string' !== typeof section) {section = sectionMap[section]}\n\tif ('' == error || null == error || undefined == error) {error = zErr} else {error += ''}\n\tlet aLen25 = [\n\t\t'canPlayType','isTypeSuppo','font-format','font-tech','textmetrics',\n\t]\n\tlet len = isDesktop ? 50 : 25\n\tif (aLen25.includes(metric.slice(0,11))) {len = 25}\n\tlet key = 'errors'\n\t// collect\n\tif (gRun && isOnce) {\n\t\tkey += 'once'\n\t\tif (gData[key][scope] == undefined) {gData[key][scope] = {}}\n\t\tif (gData[key][scope][section] == undefined) {gData[key][scope][section] = {}}\n\t\tgData[key][scope][section][metric] = error\n\t} else {\n\t\tif (sDataTemp[key] == undefined) {sDataTemp[key] = {}}\n\t\tif (sDataTemp[key][scope] == undefined) {sDataTemp[key][scope] = {}}\n\t\tif (sDataTemp[key][scope][section] == undefined) {sDataTemp[key][scope][section] = {}}\n\t\tsDataTemp[key][scope][section][metric] = error\n\t}\n\t// trim if required + return\n\t// is aLen25 and android, just display zErr\n\tif (!isDesktop && aLen25.includes(metric.slice(0,11))) {\n\t\terror = zErr\n\t} else if (error.length > len) {\n\t\terror = error.slice(0,len-3) + \"...\"\n\t}\n\treturn error\n}\n\nfunction log_known(section, metric, data, scope = isScope) {\n\tif (!isSmart) {return data}\n\tlet key = 'lies'\n\tif ('string' !== typeof section) {section = sectionMap[section]}\n\tif (sDataTemp[key][scope] == undefined) {sDataTemp[key][scope] = {}}\n\tif (sDataTemp[key][scope][section] == undefined) {sDataTemp[key][scope][section] = {}}\n\tif (undefined == data) {\n\t\tif (undefined !== sDetail[scope][metric]) {data = sDetail[scope][metric]}\n\t}\n\tsDataTemp[key][scope][section][metric] = data\n\t// color\n\treturn \"<span class='lies'>\"+ data +\"</span>\"\n}\n\nfunction log_perf(section, metric ='', time1, time2, extra) {\n\tif (!isPerf) {return}\n\tif ('string' !== typeof section) {section = sectionMap[section]}\n\tlet tEnd = performance.now()\n\tlet str = '' === metric ? section : metric +': '+ section\n\n\t// GLOBAL\n\tif (gRun || str.includes(SECTNF)) {\n\t\tgData.perf.push([3, str, time1, tEnd, extra])\n\t\treturn\n\t}\n\t// SECTION RERUNS\n\tif (str.includes(SECTP)) {return} // ignore prereq\n\tlet type = sectionNames.includes(str) ? 2 : 3\n\ttime2 = tEnd - gt0\n\ttime1 = (2 === type) ? time2 : tEnd - time1\n\tsDataTemp.perf.push([type, str, time1, time2, extra])\n}\n\nfunction log_section(name, time, scope = isScope) {\n\tlet t0 = nowFn()\n\tlet nameStr = \"number\" === typeof name ? sectionMap[name] : name\n\tif (gRun) {gData[\"perf\"].push([2, nameStr, time, t0])}\n\tif (nameStr == SECTP) {return}\n\t//console.log(name, nameStr)\n\n\t// SECTION RERUNS\n\tif (!gRun) {\n\t\tlog_perf(nameStr,'', time)\n\t\toutput_section(name, scope)\n\t\toutputPostSection(name) // trigger nonFP\t\n\t\tgClick = true\n\t\treturn\n\t}\n\n\t// GLOBAL\n\tgCount++\n\n\t//console.log(sectionMap[name], gCount ,\"/\", gSectionsExpected)\n\tif (gCount == gSectionsExpected) {\n\t\t// NoScript can be slow - check quirks mode at the end - we only need do this once\n\t\tif (gLoad) {\n\t\t\ttry {if ('CSS1Compat' !== document.compatMode) {run_block('quirks'); return}} catch(e) {}\n\t\t}\n\n\t\tgt1 = gt0\n\t\tif (isPerf) {dom.perfAll = \" \"+ (performance.now()-gt0).toFixed(isDecimal ? 2 : 0) +\" ms\"}\n\t\toutput_section(\"all\", scope)\n\n\t\t// FP\n\t\ttry {\n\t\t\tlet metricCount = Object.keys(gData[zFP][scope +\"_flat\"]).length\n\t\t\tlet color = (metricCount == expectedMetrics || sectionIgnore.length) ? 0 : 'red' // use red to override color in basic mode\n\t\t\t\n\t\t\tlet btnPart1 = addButton(color, zFP, metricCount)\n\t\t\tbtnPart1 = btnPart1.replace(']','')\t+ '<span style=\"letter-spacing: -0.2em\"> </span>'\n\t\t\tdom[scope + 'hash'].innerHTML = mini(gData[zFP][scope]) + btnPart1\n\t\t\t\t+ addButton(0,'fingerprint_metrics', 'metrics').replace('[','')\n\t\t} catch(e) {\n\t\t\tconsole.log(e)\n\t\t}\n\n\t\tlet aBtns = []\n\t\ttry {\n\t\t\tbtnList.forEach(function(item) {\n\t\t\t\tlet total = 0, oFlat = {}, oSummary = {}\n\t\t\t\t// propagate sData to gData\n\t\t\t\tif (sData[item][scope] !== undefined) {\n\t\t\t\t\tif (gData[item][scope] == undefined) {\n\t\t\t\t\t\tgData[item][scope] = {}\n\t\t\t\t\t}\n\t\t\t\t\tfor (const s of Object.keys(sData[item][scope]).sort()) {\n\t\t\t\t\t\t// everything is already sorted\n\t\t\t\t\t\tgData[item][scope][s] = sData[item][scope][s]\n\t\t\t\t\t\ttotal += Object.keys(sData[item][scope][s]).length\n\t\t\t\t\t\tfor (const k of Object.keys(sData[item][scope][s])) {\n\t\t\t\t\t\t\tlet tmpData = sData[item][scope][s][k]\n\t\t\t\t\t\t\tlet value = ('object' == typeof tmpData && tmpData !== null ? sData[item][scope][s][k]['hash'] : sData[item][scope][s][k])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (total > 0) {\n\t\t\t\t\tlet btnText = total +\" \"+ (total == 1 ? item.slice(0,-1) : item) // single/plural\n\t\t\t\t\tlet color = ('alerts' === item) ? 'bad' : 0\n\t\t\t\t\taBtns.push(addButton(color, item, btnText, 'btnc', scope))\n\t\t\t\t}\n\t\t\t})\n\t\t} catch(e) {\n\t\t\tconsole.error(e)\n\t\t}\n\t\tdom[scope +\"btns\"].innerHTML = aBtns.join(\"\")\n\n\t\t// prototype/proxy\n\t\t\t// ToDo: isTB health\n\t\tlet protoDisplay = zNA\n\t\tif (isProtoProxy) {\n\t\t\tprotoDisplay = 'none'\n\t\t\tlet propsCount = gData[SECT97].length\n\t\t\tlet protoCount = (Object.keys(gData[SECT98]).length)\n\t\t\tlet proxyCount = gData[SECT99].length\n\t\t\tif (protoCount + proxyCount > 0) {\n\t\t\t\tlet aStr = []\n\t\t\t\tif (protoCount > 0) {aStr.push(mini(gData[SECT98]) + addButton(0, SECT98, protoCount))}\n\t\t\t\tif (proxyCount > 0) {aStr.push(mini(gData[SECT99]) + addButton(0, SECT99, proxyCount))}\n\t\t\t\tprotoDisplay = aStr.join(\" \")\n\t\t\t}\n\t\t\tprotoDisplay += ' from '+ addButton(0, SECT97, propsCount)\n\t\t}\n\t\tdom.protohash.innerHTML = protoDisplay\n\n\t\toutput_health(scope)\n\n\t\t// trigger nonFP\n\t\toutputPostSection(\"all\")\n\t\tgLoad = false\n\t\tgClick = true\n\t}\n}\n\n/*** RUN ***/\n\nfunction countJS(item) {\n\tjsFiles++\n\tif (1 == jsFiles) {\n\t\t// block quirks mode e.g. caused by NoScript\n\t\ttry {if ('CSS1Compat' !== document.compatMode) {run_block('quirks'); return}} catch(e) {}\n\t\t// block if iframed\n\t\tif (window.location !== window.parent.location) {run_block('iframe'); return}\n\t\t// block if insecure as this produces very different results e.g. some APIs require secure\n\t\t\t// gecko diffs include 14 navigator keys, 100+ window props, and 7 permissions\n\t\tif (!isFile && 'https:' !== location.protocol) {run_block('insecure'); return}\n\t\t// non-gecko\n\t\tif (!isGecko) {\n\t\t\tif (isEngineBlocked) {run_block('upgrade'); return}\n\t\t\tif (isAllowNonGecko && undefined !== isEngine) {run_basic()} else {run_block(isEngine+' engine'); return}\n\t\t}\n\t\t// update tooltip\n\t\tif (undefined !== isStylesheet) {\n\t\t\ttry {\n\t\t\t\tlet items = document.getElementsByClassName('cssrange')\n\t\t\t\tfor (let i=0; i < items.length; i++) {items[i].innerHTML = 'range '+ isStylesheet}\n\t\t\t} catch(e) {}\n\t\t}\n\t\t// set src's for our l10n iframe tests\n\t\t\t// setting these inline can cause the wrong contentDocument in the wrong iframes\n\t\t\t// it's almost random like some sort of race with different results in android vs windows - WTF!!\n\t\t\t// Switching the element order in html (with inline src and not by JS) I can replicate these\n\t\t\t// mismatched contents in both windows and android. Only setting them via JS are we always correct\n\t\t\t// jesus says: WT actual F\n\t\tif (isGecko) {\n\t\t\ttry {dom.tzpInvalidImage.src = 'images/InvalidImage.png'} catch {}\n\t\t\ttry {dom.tzpScaledImage.src = 'images/ScaledImage.png'} catch {}\n\t\t\ttry {dom.tzpXMLunstyled.src = 'xml/xmlunstyled.xml'} catch {}\n\t\t\ttry {dom.tzpXSLT.src='xml/xslterror.xml'} catch {} // in FF134 or lower this breaks devtools: oh dear, what a shame\n\t\t}\n\n\t\tget_isVer('isVer') // if PoCs don't touch the dom this is fine here: required for isTB\n\t\tget_isSystemFont()\n\t\treturn\n\t} else if (jsFiles === jsFilesExpected) {\n\t\t// block: quirks, iframe, insecure, upgrade required, !isAllowNonGecko, undefined isEngine | also if gecko is below min version\n\t\tif (isStop) {return}\n\t\t// otherwise not blocked\n\t\tisBlock = false\n\t\t// tidy up metric overlay symbols to match global symbol used\n\t\tdom.overlay_tick.innerHTML = tick +' '\n\t\tdom.overlay_cross.innerHTML = cross +' '\n\t\tgData['perf'].push([1, 'RUN ONCE', nowFn()])\n\t\tPromise.all([\n\t\t\tget_isBB('isBB'),\n\t\t\tget_isBrave('isBrave'),\n\t\t\tget_isFileSystem('isFileSystem'),\n\t\t\tget_isAutoplay('getAutoplayPolicy'),\n\t\t]).then(function(){\n\t\t\t// 140+ notations: if isBB then block FPP notations & vice versa\n\t\t\tisFPPFallback = !isBB\n\t\t\tPromise.all([\n\t\t\t\tget_isOS('isOS')\n\t\t\t]).then(function(){\n\t\t\t\t// tweak monospace size\n\t\t\t\t\t// ToDo: this is bad design: we need a better way to get nice consistent sizes across\n\t\t\t\t\t// TB vs linux vs other linux vs other platforms\n\t\t\t\t/*\n\t\t\t\tif ('windows' == isOS) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdocument.body.style.setProperty('--txtSize', '12px')\n\t\t\t\t\t\tdocument.body.style.setProperty('--txtSizeBigger', '24px')\n\t\t\t\t\t} catch(e) {console.log(e)}\n\t\t\t\t}\n\t\t\t\t//*/\n\t\t\t\t// do once\n\t\t\t\tdom.tzpPointer.addEventListener('pointerdown', (event) => {outputUser('pointer_event', event)})\n\t\t\t\tdom.tzpPointer.addEventListener('pointerrawupdate', (event) => {get_isPointerRawUpdate(event)})\n\n\t\t\t\tif (isDesktop) {\n\t\t\t\t\tdocument.addEventListener('keydown', metricsEvent)\n\t\t\t\t} else {\n\t\t\t\t\tshowhide('A','table-row')\n\t\t\t\t\t// A1 inner_document: html class hidden - only used by android\n\t\t\t\t\t// add class togS so it shows when expanding, remove hidden class\n\t\t\t\t\tdom.A1.classList.add('togS')\n\t\t\t\t\tdom.A1.classList.remove('hidden')\n\t\t\t\t\t// hide and remove togS on the entire viewport section + also window.inner - not used by android\n\t\t\t\t\t// + visualViewportScale + window_scrollbar\n\t\t\t\t\tlet items = document.getElementsByClassName('A2')\n\t\t\t\t\tfor (let i=0; i < items.length; i++) {\n\t\t\t\t\t\titems[i].classList.remove('togS')\n\t\t\t\t\t\titems[i].classList.add('hidden')\n\t\t\t\t\t}\n\t\t\t\t\t// hide console button in overlay: width is a premium\n\t\t\t\t\tdom.metricsConsole.classList.add('hidden')\n\t\t\t\t}\n\t\t\t\t// set isBBESR: some health checks we only want to do if it's worthwhile\n\t\t\t\t// android and alpha are moving to RR and it's not ffeasible to keep up with per release changes\n\t\t\t\tif (isBB && 'android' !== isOS && isVer == 140) {isBBESR = true}\n\t\t\t\tPromise.all([\n\t\t\t\t\tget_isFontDelay() // determine if we need to delay BB for font.vis and async font fallback\n\t\t\t\t]).then(function(){\n\t\t\t\t\toutputSection('load')\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunction outputPostSection(id) {\n\tif (\"all\" !== id) {\n\t\toutput_perf(id)\n\t}\n\tif (\"number\" === typeof id) {id = sectionMap[id]}\n\tif (gRun) {gData[\"perf\"].push([1, SECTNF, nowFn()])}\n\tlet isLog = gRun // push perf\n\tgRun = false // stop collecting\n\n\tif (id == \"storage\") {\n\t\ttest_worker(isLog)\n\t\ttest_worker_service(isLog) // doesn't return\n\t\ttest_worker_shared(isLog)\n\t\ttest_idb(isLog)\n\t} else if (id == \"agent\") {\n\t\tget_agent_iframes(isLog)\n\t\tget_agent_workers()\n\t} else if (id == \"all\") {\n\t\ttest_worker_service(isLog) // doesn't return\n\t\tPromise.all([\n\t\t\ttest_worker(isLog),\n\t\t\ttest_worker_shared(isLog),\n\t\t\ttest_idb(isLog),\n\t\t\tget_agent_iframes(isLog),\n\t\t\tget_agent_workers(),\n\t\t]).then(function(){\n\t\t\toutput_perf(id)\n\t\t})\n\t}\n}\n\nfunction outputSection(id, isResize = false) {\n\tif (isBlock || !gClick) {\n\t\toutput_perf('all')\n\t\treturn\n\t}\n\t// set the onion skin pattern background if TB and dark\n\tif (gLoad && isTB && 'android' !== isOS) {\n\t\ttry {\n\t\t\tlet target = document.body\n\t\t\tlet bgcolor = window.getComputedStyle(target).getPropertyValue('background-color')\n\t\t\tif ('rgb(22, 27, 34)' == bgcolor) {\n\t\t\t\ttarget.classList.add('tzpBody')\n\t\t\t} else {\n\t\t\t\ttarget.classList.remove('tzpBody')\n\t\t\t}\n\t\t} catch(e) {}\n\t}\n\n\t// reset scope\n\tisScope = zDOC\n\tif ('load' == id) {\n\t\t// set sectionOrder/Names/Nos\n\t\tlet tmpObj = {}\n\t\tfor (const k of Object.keys(sectionMap)) {sectionNos[sectionMap[k]] = k; tmpObj[sectionMap[k]] = k; sectionNames.push(sectionMap[k])}\n\t\tfor (const n of Object.keys(tmpObj).sort()) {sectionOrder.push(tmpObj[n])}\n\t\tsectionNames.sort()\n\t}\n\n\tgClick = false\n\tlet delay = 100\n\t// reset\n\tif ('load' == id || 'all' == id) {\n\t\t// show hide sections\n\t\tlet sState = mini(sectionIgnore)\n\t\tif (sState !== sectionState) {\n\t\t\tconsole.log('hiding these sections', sectionIgnore)\n\t\t\tsState = sectionState\n\t\t\tfor (const n of Object.keys(sectionNos)) {\n\t\t\t\tif (undefined !== sectionNos[n]) {\n\t\t\t\t\tlet tbltarget = dom['tb'+ sectionNos[n]]\n\t\t\t\t\tif (sectionIgnore.includes(n)) {\n\t\t\t\t\t\ttbltarget.classList.add('hidden')\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttbltarget.classList.remove('hidden')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// gData\n\t\tif (runSG) {\n\t\t\tlog_error('a', 'd', '4', isScope, true)\n\t\t\tlog_error('_a', 'a', '1', isScope, true)\n\t\t\tlog_error(2, 'z', '9', isScope, true)\n\t\t\tlog_error(2, 'y', '8', isScope, true)\n\t\t\tlog_error(SECTG, 'c', '3', isScope, true)\n\t\t\tlog_error(SECTG, 'b', '2', isScope, true)\n\t\t\tlog_alert(5, 'z', \"p\", isScope, true)\n\t\t\tlog_alert(5, '_a', \"t\", isScope, true)\n\t\t\tlog_alert(5, 'm', \"z\", isScope, true)\n\t\t}\n\t\tgData[zFP] = {'document': {}}\n\t\tgData.health = {'document_collect': {}}\n\t\tgTiming.forEach(function(item){gData.timing[item] = []})\n\t\tbtnList.forEach(function(item){gData[item] = {}})\n\t\tif (!gLoad) { // don't wipe gLoad perf\n\t\t\tgData['perf'] = []\n\t\t}\n\t\t// sData\n\t\tsData = {\n\t\t\t'fingerprint': {'document': {}}\n\t\t}\n\t\t// sDataTemp\n\t\tsDataTemp = {\n\t\t\t'display': {'document': {}},\n\t\t\t'fingerprint': {\"document\": {}},\n\t\t\t'perf': [],\n\t\t}\n\t\tbtnList.forEach(function(item){\n\t\t\tsData[item] = {'document': {}}\n\t\t\tsDataTemp[item] = {'document': {}}\n\t\t})\n\t\tfor (const name of Object.keys(sectionMap)) {\n\t\t\tlet sectionName = sectionMap[name]\n\t\t\tsDataTemp[zFP][isScope][name] = {}\n\t\t\tsDataTemp['display'][isScope][name] = {}\n\t\t}\n\t\t// sDetail\n\t\tsDetail = {'document': {'lookup': {}}, 'manual': {}}\n\t}\n\n\tif ('load' == id) {\n\t\t// skip clear/reset\n\t\tid = 'all'\n\t\tdelay = 0\n\t} else if ('all' == id) {\n\t\tgRun = true\n\t\t// clear\n\t\tlet items = document.getElementsByClassName('c')\n\t\tfor (let i=0; i < items.length; i++) {items[i].innerHTML = '&nbsp'}\n\t\titems = document.getElementsByClassName('cssc') // inline css notations we don't want to add an empty space\n\t\tfor (let i=0; i < items.length; i++) {items[i].innerHTML = ''}\n\t\titems = document.getElementsByClassName('gc') // user actions\n\t\tfor (let i=0; i < items.length; i++) {items[i].innerHTML = '&nbsp'}\n\t\t// reset global\n\t\tgCount = 0\n\t\tget_isDevices() // non gLoad warmup\n\t} else {\n\t\t// clear section data\n\t\tlet name = sectionMap[id]\n\t\ttry {sData[zFP][isScope][name] = {}} catch {}\n\t\ttry {sDataTemp[zFP][isScope][id] = {}} catch {}\n\t\ttry {sDataTemp['display'][isScope][id] = {}} catch {}\n\t\tbtnList.forEach(function(item){\n\t\t\ttry {sData[item][isScope][name] = {}} catch {}\n\t\t\ttry {sDataTemp[item][isScope][name] = {}} catch {}\n\t\t})\n\t\tif (!isResize) {\n\t\t\tlet tbl = dom['tb'+ id]\n\t\t\ttbl.querySelectorAll(`.c`).forEach(e => {e.innerHTML = '&nbsp'})\n\t\t\ttbl.querySelectorAll('span.cssc').forEach(e => {e.innerHTML = ''})\n\t\t}\n\t\tgRun = false\n\t}\n\n\tvar promiseSection = async function(x) {\n\t\tlet n = Number.isInteger(x) ? x : sectionNos[x]\n\t\tif (n == 1) { return(outputScreen(isResize))}\n\t\tif (n == 2) { return(outputAgent())}\n\t\tif (n == 3) { return(outputFD())}\n\t\tif (n == 4) { return(outputRegion())}\n\t\tif (n == 5) { return(outputHeaders())}\n\t\tif (n == 6) { return(outputStorage())}\n\t\tif (n == 7) { return(outputDevices())}\n\t\tif (n == 9) { return(outputCanvas())}\n\t\tif (n == 10) { return(outputWebGL())}\n\t\tif (n == 11) { return(outputAudio())}\n\t\tif (n == 12) { return(outputFonts())}\n\t\tif (n == 13) { return(outputMedia())}\n\t\tif (n == 14) { return(outputCSS())}\n\t\tif (n == 15) { return(outputElements())}\n\t\tif (n == 17) { return(outputTiming())}\n\t\tif (n == 18) { return(outputMisc())}\n\t}\n\n\tfunction output() {\n\t\tif ('all' == id) {\n\t\t\tif (!gLoad) {\n\t\t\t\tgCountTiming = 0 // reset\n\t\t\t\taddTimings()\n\t\t\t}\n\t\t\t// run sequentially awaiting each before running the next\n\t\t\t// order: use number or section name\n\t\t\tlet order = [\n\t\t\t\t3, // first: sets isMB (legacy method)\n\t\t\t\t2, 1, 5, 14, 13, // fast\n\t\t\t\t'canvas',\n\t\t\t\t'storage', // little slow: cache + permissions\n\t\t\t\t'misc', // cold on load: iframe props\n\t\t\t\t'elements', // cold on load: mathml\n\t\t\t\t'audio',\n\t\t\t\t'webgl',\n\t\t\t\t'devices','fonts', // near last: allow time for isDevices, font fallback\n\t\t\t\t'region', // next to last: allow time for iframes and any reporting API items to manifest on first run\n\t\t\t\t17 // last: uses data collected during gRun\n\t\t\t]\n\t\t\tconst forEachSection = async (iterable, action) => {\n\t\t\t\tfor (const n of iterable) {\n\t\t\t\t\tlet t0 = nowFn()\n\t\t\t\t\tawait promiseSection(n)\n\t\t\t\t\tlet x = Number.isInteger(n) ? n : sectionNos[n] * 1\n\t\t\t\t\tlog_section(x, t0)\n\t\t\t\t}\n\t\t\t}\n\t\t\tforEachSection(order, promiseSection)\n\t\t} else {\n\t\t\tgt0 = nowFn() // single section timer\n\t\t\tPromise.all([\n\t\t\t\tpromiseSection(id)\n\t\t\t]).then(function(){\n\t\t\t\tlog_section(id, gt0, isScope, isResize)\n\t\t\t})\n\t\t}\n\t}\n\n\t// set isXSLT which we need before we start any timing measurements\n\tget_isXSLT()\n\t// reset smarts\n\tsmartFn('final')\n\tlet enforcedDelay = 0\n\tif (gLoad) {\n\t\t// initiate timings\n\t\tgCountTiming = 0\n\t\taddTimings()\n\t\t// force an initial delay regardless | moreso if it it's BB with font.vis\n\t\t// e.g. some extensions can be slow to inject etc\n\t\t// e.g. will help with resources such as XML/images and\n\t\tenforcedDelay = isFontDelay ? 3000 : (isFile ? 0 : 1200)\n\t}\n\t//if (gLoad) {enforcedDelay = 1200}\n\tif (enforcedDelay > 0) {\n\t\tdelay = 0\n\t\tlet msg = isFontDelay ? 'async font fallback' : ''\n\t\tlet padLength = (enforcedDelay+'').length\n\t\tif (isFontDelay) {dom.protohash.innerHTML = '<span class=\"spaces\">     awaiting '+ msg}\n\t\tlet t0 = nowFn(), increment = 16 // 5 is too fast to read\n\t\tfunction countdown() {\n\t\t\taddTiming() // add loads of various timing measurements during our delay\n\t\t\tlet timetaken = Math.floor(nowFn() - t0)\n\t\t\tif (timetaken > enforcedDelay) {\n\t\t\t\tdom.protohash.innerHTML = ''\n\t\t\t\tclearInterval(timer)\n\t\t\t\tlog_perf(SECTG, 'enforced delay', t0,'', msg)\n\t\t\t\tproceed()\n\t\t\t} else {\n\t\t\t\tlet remainder = enforcedDelay - timetaken\n\t\t\t\tif (remainder > (increment * 2)) {\n\t\t\t\t\tremainder ='<span class=\"spaces\">     running in ... ' + (remainder+'').padStart(padLength) +' ms</span>'\n\t\t\t\t} else (remainder = '')\n\t\t\t\tdom.documenthash.innerHTML = remainder\n\t\t\t}\n\t\t}\n\t\tlet timer = setInterval(countdown, increment)\n\t} else {\n\t\tproceed()\n\t}\n\n\tfunction proceed() {\n\t\tsetTimeout(function() {\n\t\t\tget_isPerf()\n\t\t\tif (gRun) {gData['perf'].push([1, 'DOCUMENT START', nowFn()])}\n\t\t\tgt0 = nowFn()\n\t\t\tPromise.all([\n\t\t\t\tget_isDomRect(),\n\t\t\t\toutputPrototypeLies(isResize),\n\t\t\t]).then(function(){\n\t\t\t\tif (isBB && gClear && 'all' == id) {console.clear()}\n\t\t\t\tif (isSmart) {log_section(SECTP, gt0)}\n\t\t\t\t// WTF NoScript! sometimes we have to catch this later\n\t\t\t\ttry {if ('CSS1Compat' !== document.compatMode) {run_block('quirks'); return}} catch(e) {}\n\t\t\t\toutput()\n\t\t\t})\n\t\t}, delay)\n\t}\n}\n\nfunction run_immediate() {\n\tget_isPerf()\n\tlet t00 = nowFn()\n\tzErrLog = rnd_string()\n\tzErrShort = rnd_string()\n\tgData['perf'].push([1, 'IMMEDIATE', t00])\n\tisFile = 'file:' == location.protocol\n\tPromise.all([\n\t\tget_isGecko('isGecko')\n\t]).then(function(){\n\t\tif (!isGecko) {\n\t\t\t// get engine regardless\n\t\t\tget_isEngine('isEngine')\n\t\t\t// return if not supported\n\t\t\tif (!isAllowNonGecko || undefined === isEngine) {return}\n\t\t} else {\n\t\t\t// search https://searchfox.org/firefox-main/source/dom/locales/en-US/chrome/dom/dom.properties for 'is deprecated'\n\t\t\t// trigger some gecko deprecated items. They are recorded regardless of API. The API pref simply allows access to read them\n\t\t\tlet aItems = [\n\t\t\t\t'InstallTrigger', // X is deprecated and will be removed in the future. | FF100 1754441\n\t\t\t\t'fullScreen', // X is deprecated. Use Y instead. | FF65 1504946\n\t\t\t\t'onmozfullscreenchange', 'onmozfullscreenerror' // X is deprecated\n\t\t\t]\n\t\t\taItems.forEach(function(n) {try {window[n]} catch(e) {}})\n\t\t\ttry {document.releaseCapture()} catch(e) {} // FF90 973604 mark set/releaseCapture() as deprecated\n\t\t\t\t// X is deprecated. Use Y instead. For more help\n\t\t\ttry {screen.mozOrientation} catch(e) {} // FF147 2003169 deprecate mozOrientation\n\t\t\t\t// X attribute is deprecated and will be removed in the future.\n\t\t}\n\t\t// set isProtoProxy on known engines\n\t\t\t// we already returned if isEngine == undefined just above\n\t\tisProtoProxy = 'undefined' == isEngine ? false : true\n\t\t// expand css, record stylesheet info\n\t\tget_isStylesheet(7680)\n\t\t// recursion\n\t\tget_isRecursion()\n\t\t// storage warm ups\n\t\tget_isFileSystem('isFileSystem', true)\n\t\ttry {window.caches.keys()} catch {}\n\t\t// other warm ups\n\t\tget_isDevices()\n\t\ttry {let w = speechSynthesis.getVoices()} catch {}\n\t\ttry {\n\t\t\tconst config = {initDataTypes: ['cenc'], videoCapabilities: [{contentType: 'video/mp4;codecs=\"avc1.4D401E\"'}]}\n\t\t\tnavigator.requestMediaKeySystemAccess('org.w3.clearkey', [config]).then((key) => {}).catch(function(e){})\n\t\t} catch {}\n\t\ttry {\n\t\t\tlet warm = Intl.DateTimeFormat().resolvedOptions()\n\t\t\twarm = Intl.DateTimeFormat(undefined, {timeZone: 'Europe/London', timeZoneName: 'shortGeneric'}).format(new Date)\n\t\t\twarm = new Intl.NumberFormat(undefined, {notation: 'compact'}).format(1)\n\t\t\twarm = new Intl.NumberFormat(undefined, {style: 'unit', unit: 'hectare'}).format(1)\n\t\t} catch {}\n\t\tget_isXML()\n\t\tget_isArch('isArch')\n\t\ttry {\n\t\t\t// ensure hevc correctness e.g. see 1972902 fixed by 1974881\n\t\t\t\t// 1st query on a new session hevc are false positives: a recheck fixes it\n\t\t\tlet vCodecs = ['\"hev1.1.6.L93.B0\"','\"hev1.2.4.L120.B0\"','\"hvc1.1.6.L93.B0\"','\"hvc1.2.4.L120.B0\"']\n\t\t\tlet vObj = document.createElement('video')\n\t\t\tvCodecs.forEach(function(item) {let vTest = vObj.canPlayType('video/mp4; codecs='+ item)})\n\t\t} catch(e) {}\n\t})\n}\n\nrun_immediate()\n"
  },
  {
    "path": "js/globals.js",
    "content": "'use strict';\n\nvar dom;\n\nconst SECTG = '_global', SECTP = '_prereq', SECTNF = 'NON-FP',\n\tSECT97 = 'properties', SECT98 = 'prototype', SECT99 = 'proxy'\n\nconst sectionMap = {\n\t1: 'screen', 2: 'agent', 3: 'feature', 4: 'region', 5: 'headers', 6: 'storage',\n\t7: 'devices', 9: 'canvas', 10: 'webgl', 11: 'audio', 12: 'fonts', 13: 'codecs',\n\t14: 'css', 15: 'elements', 17: 'timing', 18: 'misc',\n}\nlet sectionOrder = [], // numerical order for objects\n\tsectionNames = [], // lookup names by number\n\tsectionNos = {}, // lookup numbers by name\n\tsectionIgnore = [], // show/hide on dom't run if gRun\n\tsectionState = 'ac6c4be7' // default empty array: sectionIgnore hash so we know to trigger show/hide\n\n// ToDo: expand: some info can go into lies but we could create new items e.g methods/tampered-data\n\t// some 'methods/entropy' are in the FP: e.g. canvas/domrect or errors e.g. font sizes\nconst btnList = ['alerts', 'errors', 'lies']\n\nconst jsFilesExpected = 15,\n\tgSectionsExpected = 16,\n\texpectedMetrics = 135\nlet jsFiles = 0, gCount = 0, gCountTiming = 0\n\n// global\nlet gData = { // from sData\n\t'alertsonce': {'document': {}},\n\t'errorsonce': {'document': {}},\n\t'health': {'document': {}},\n\t'perf': [],\n\t'timing': {},\n}\nlet gTiming = ['currenttime','date','exslt','instant','mark','navigation','now','performance','reducetimer','resource','timestamp']\nlet gTimeline\n\n// section\nlet sData = {}, // final sorted section data: from sDataTemp\n\tsDataTemp = {}, // unsorted section data\n\tsDetail = {} // all clickables: lies, fake, valid etc\n\n// scopes\nconst zFP = 'fingerprint',\n\tzDOC = 'document',\n\tzIFRAME = 'iframe'\nlet isScope = zDOC\n\n// styles\nlet s0 = \" <span class='\",\n\tsb = s0+\"bad'>\",\n\tsg = s0+\"good'>\",\n\ts1 = s0+\"s1'>\", // s1+s3+s99: used in perf details\n\ts3 = s0+\"s3'>\",\n\ts99 = s0+\"s99'>\",\n\tsc = '</span>'\n\n// common\nconst zD = 'disabled',\n\tzE = 'enabled',\n\tzErr = 'error',\n\tzErrType = 'TypeError: ',\n\tzErrTime = 'timed out',\n\tzErrInvalid = 'Invalid: ',\n\tzNA = 'n/a',\n\tzS = 'success',\n\tzF = 'failed',\n\tzNEW = sb+'[NEW]'+sc,\n\tzLIE = 'untrustworthy',\n\tzSKIP = 'skipped'\n\nlet zErrLog = '', // log error in add/Both\n\tzErrShort = '' // log error in add/Both but display zErr in add/Display\n\n// grab as soon as possible\nlet isInitial = {height: {}, width: {}}\nfunction get_scr_initial() {\n\t// we don't need any error entropy: we get these properties again later\n\tlet x, oList = {height: ['innerHeight','outerHeight'], width: ['innerWidth','outerWidth']}\n\tfor (const axis of Object.keys(oList)) {\n\t\tlet aList = oList[axis]\n\t\taList.forEach(function(k){\n\t\t\ttry {\n\t\t\t\tx = window[k]\n\t\t\t\tif ('number' !== typeof x || Number.isNaN(x)) {x = zErr}\n\t\t\t} catch {\n\t\t\t\tx = zErr\n\t\t\t}\n\t\t\tif (k.includes('inner')) {k = 'inner'} else {k = 'outer'}\n\t\t\tisInitial[axis][k] = x\n\t\t})\n\t}\n}\nget_scr_initial()\n\n// notation\n\t// https://en.wikipedia.org/wiki/Check_mark\n\t// https://en.wikipedia.org/wiki/X_mark\nconst tick = '✓', // ✓ u2713, 🗸 u1F5F8\n\tcross = '✗', // ✗ u2717, 🗴 u!F5F4, 🞩 u1F7A9\n\tgreen_tick = sg+\"<span class='health'>\"+ tick +'</span>'+sc,\n\tgreen_benign = sg+\"[<span class='health'>\"+ tick +'</span> benign]'+sc,\n\tred_cross = sb+\"<span class='health'>\"+ cross +'</span>'+ sc,\n\tred_benign = sb+\"[<span class='health'>\"+ cross +'</span> benign]'+ sc,\n\tsgtick = sg +\"[<span class='health'>\"+ tick +'</span> ', \n\tsbx = sb +\"[<span class='health'>\" + cross +'</span> ',\n\trfp_green = sgtick+'RFP]'+sc,\n\trfp_red = sbx+'RFP]'+sc,\n\tsilent_green = sg +\"[<span class='healthsilent'>\"+ tick +'</span>]'+ sc,\n\tsilent_red = sb +\"[<span class='healthsilent'>\" + cross +'</span>]'+ sc,\n\tsilent_rfp_green = sg +\"[<span class='healthsilent'>\"+ tick +'</span> RFP]'+ sc,\n\tsilent_rfp_red = sb +\"[<span class='healthsilent'>\" + cross +'</span> RFP]'+ sc,\n\tnw_green = sgtick+'RFP newwin]'+sc,\n\tnw_red = sbx+'RFP newwin]'+sc,\n\tdefault_green = sgtick+'default]'+sc,\n\tdefault_red = sbx+'default]'+sc,\n\tmatch_green = sgtick+'match]'+sc,\n\tmatch_red = sbx+'match]'+sc,\n\tfpp_green = sgtick+'FPP]'+sc,\n\tlang_green = sgtick+' languages]'+sc,\n\tlang_red = sbx+' languages]'+sc,\n\tlocale_green = sgtick+' locale]'+sc,\n\tlocale_red = sbx+' locale]'+sc,\n\tlocaletz_green = sgtick+' locale + timezone]'+sc,\n\tlocaletz_red = sbx+' locale + timezone]'+sc,\n\tintl_green = sgtick+' intl]'+sc,\n\tintl_red = sbx+' intl]'+sc,\n\ttz_green = sgtick+' timezone]'+sc,\n\ttz_red = sbx+' timezone]'+sc,\n\tdesktopmode_green = sgtick+'RFP desktop mode]'+sc\n\n// dynamic BB notation\nlet bb_green = sgtick+'TB]'+sc,\n\tbb_red = sbx+'TB]'+sc,\n\tbb_slider_red = sbx+'TB Slider]'+sc,\n\tbb_standard = sg+'[TB Standard]'+sc,\n\tbb_safer = sg+'[TB Safer]'+sc // don't tick/cross slider\n\n// run once\nlet isArch = true,\n\tisAutoPlay,\n\tisAutoPlayError,\n\tisBrave = false,\n\tisBraveSmart = false, // only if FP protection is on\n\tisDesktop = true,\n\tisDevices,\n\tisEngine,\n\tisEngineBlocked = true,\n\tisEngineStr = '',\n\tisFile = false,\n\tisFileSystem,\n\tisFileSystemError,\n\tisFontDelay = false, // BB win/mac require a delay for async font fallback if font.vis used\n\tisGecko = false,\n\tisOS,\n\tisOSErr,\n\tisProps, // window properties\n\tisProtoProxy = false,\n\tisRecursion,\n\tisReporting, // ReportingAPI\n\tisScrollbar,\n\tisStyles = ['cursive','math','monospace','sans-serif','serif','system-ui'],\n\t\t// FF145+ nightly 1788937 math | 2014703 FF149+\n\t\t// 'emoji','ui-monospace','ui-rounded','ui-serif' = currently at least gecko + blink redundant (windows)\n\t\t// 'emoji' = better covered in special metric/test targeting emojis/unicode\n\t\t// 'fantasy' = not set in gecko (checked Feb 2026) see 536004#c2\n\tisStylesAll = [\n\t\t'cursive','emoji','fangsong','fantasy','math','monospace',\n\t\t'sans-serif','serif','system-ui','ui-monospace','ui-rounded','ui-serif'\n\t],\n\tisStylesheet,\n\tisSystemFont = [],\n\tisVer = 0,\n\tisVerExtra = '',\n\tisViewportUnits = {},\n\tisXML = {},\n\tisXSLT // dom.xslt.enabled\n\nlet isBB = false,\n\tisBBESR = false,\n\tisMB = false,\n\tisTB = false,\n\tisFPPFallback = false // helps track FPP health, block BB giving passes to FPP protections\n\n// region\nlet languagesSupported = {},\n\tlocalesSupported = {},\n\tisLanguageSmart = false,\n\tisLanguagesNav = [], // lowercase sorted to compare to systemLanguages\n\tisLocaleValid,\n\tisLocaleValue,\n\tisLocaleAlt, // allow variants in checks e.g. en-CA checks en-US values\n\tisTimeZoneValid,\n\tisTimeZoneValue,\n\t// intl.locale\n\toIntlLocale = {},\n\toIntlLocaleKeys = {},\n\toIntlLocalePerf = {},\n\t// intl.date\n\toIntlDate = {},\n\toIntlDateKeys = {},\n\t// test dates\n\toIntlDates = {}\n\n// other\nlet aDomRect = [true, true, true, true],\n\tisDomRect = 0, // default non-gecko\n\toDomRect = {},\n\tisDecimal = false,\n\tisPerf = false,\n\tisPointerRawUpdate = 'undefined'\n\n// overlay metrics\nlet overlayScope = 'document',\n\toverlayFP = '_list',\n\toverlayHealth = '_summary',\n\toverlaySection = '',\n\toverlayName = '',\n\toverlayCharLen, // length per monospace character\n\toverlayInfo = '',\n\tmetricsData,\n\tmetricsTitle,\n\tmetricsPrefix = ''\n\n// runtypes\nlet gt0, gt1,\n\tgLoad = true,\n\tgRun = true,\n\tgClick = true,\n\tgFS = false, // don't run FS measurements if already tiggered\n\tgClear = true, // clear console of xml and BB's prototype/proxy errors\n\tisAllowNonGecko = true, // allow some other engines\n\tisAllowNonGeckoMin = true, // enforce min requirements on those other engines\n\tisAllowNonGeckoUndefined = true, // allow undefined engines\n\tisBlock = true,\n\tisFontSizesMore = false, // when true: force 3-pass and group/order by name then generic-font-family\n\tisFontSizesPrevious = false, \n\tisSmart = false,\n\tisSmartDataMode = false, // when in data-only mode we still want to run proxy/prototype lies\n\tisSmartAllowed = false, // data-only mode - do not give off false health signals if not maintained\n\tisStop = false\n\nconst isBlockMin = 128,\n\tisSmartMin = 140\n\n/** DEV **/\n// simulate errors\nlet runSG = false, // break globals\n\trunST = false, // throw type\n\trunSI = false, // throw invalid\n\trunSE = false, // generic if not thrown\n\trunTE = false, // cause timeout\n\trunSF = false, // font enumeration tests\n\trunSL = false // lies\n"
  },
  {
    "path": "js/iframes.js",
    "content": "'use strict';\n\nconst getDynamicIframeWindow = ({\n\tcontext,\n\tsource ='',\n\ttest ='',\n\tcontentWindow = false,\n\tnestIframeInContainerDiv = false,\n\tviolateSOP = true, // SameOriginPolicy\n\tdisplay = false\n}) => new Promise(resolve => {\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tconst elementName = nestIframeInContainerDiv ? 'div' : 'iframe'\n\t\tconst length = context.length\n\t\tconst element = document.createElement(elementName)\n\t\tdocument.body.appendChild(element)\n\t\tif (!display) {\n\t\t\telement.setAttribute('style', 'display:none')\n\t\t}\n\t\tif (nestIframeInContainerDiv) {\n\t\tconst attributes = `\n\t\t\t${source ? `src=${source}` : ''}\n\t\t\t\t${violateSOP ? '' : `sandbox=\"allow-same-origin\"`}\n\t\t\t`\n\t\t\telement.innerHTML = `<iframe ${attributes}></iframe>`\n\t\t} else if (!violateSOP) {\n\t\t\telement.setAttribute('sandbox', 'allow-same-origin')\n\t\t\tif (source) {\n\t\t\t\telement.setAttribute('src', source)\n\t\t\t}\n\t\t} else if (source) {\n\t\t\telement.setAttribute('src', source)\n\t\t}\n\t\tconst iframeWindow = contentWindow ? element.contentWindow : context[length]\n\n\t\tif ('agent' == test) {\n\t\t\tlet newNav = iframeWindow.navigator\n\t\t\tlet data = {}\n\n\t\t\tfunction exit(value) {\n\t\t\t\ttry {document.body.removeChild(element)} catch(e) {}\n\t\t\t\tdata['useragentdata'] = value\n\t\t\t\treturn resolve({'data': data, 'hash': mini(data)})\n\t\t\t}\n\n\t\t\t// useragent\n\t\t\tlet list = ['appCodeName','appName','appVersion','buildID','oscpu',\n\t\t\t\t'platform','product','productSub','userAgent','vendor','vendorSub']\n\t\t\tlet tmpData = {}, r\n\t\t\tlist.forEach(function(p) {\n\t\t\t\ttry {\n\t\t\t\t\tr = newNav[p]\n\t\t\t\t\tlet typeCheck = typeFn(r, true), expectedType = 'string'\n\t\t\t\t\tif (!isGecko) {\n\t\t\t\t\t\t// type check will throw an error for a string \"undefined\"\n\t\t\t\t\t\tif ('buildID' == p || 'oscpu' == p) {expectedType = 'undefined'}\n\t\t\t\t\t}\n\t\t\t\t\tif (expectedType !== typeCheck) {throw zErr}\n\t\t\t\t\tif ('' == r) {r = 'empty string'}\n\t\t\t\t} catch(e) {\n\t\t\t\t\tr = e\n\t\t\t\t}\n\t\t\t\ttmpData[p] = r+''\n\t\t\t})\n\t\t\tdata['useragent'] = {'hash': mini(tmpData), 'metrics': tmpData}\n\n\t\t\t// useragentdata\n\t\t\ttry {\n\t\t\t\tlet k = newNav.userAgentData\n\t\t\t\tlet typeCheck = typeFn(k, true)\n\t\t\t\tif ('undefined' == typeCheck) {\n\t\t\t\t\texit(typeCheck)\n\t\t\t\t} else {\n\t\t\t\t\tif ('object' !== typeCheck) {throw zErr}\n\t\t\t\t\tif ('[object NavigatorUAData]' !== k+'') {throw zErr}\n\t\t\t\t\tnavigator.userAgentData.getHighEntropyValues([\n\t\t\t\t\t\t'architecture','bitness','brands','formFactors','fullVersionList','mobile',\n\t\t\t\t\t\t'model','platform','platformVersion','uaFullVersion','wow64'\n\t\t\t\t\t]).then(res => {\n\t\t\t\t\t\texit({'hash': mini(res), 'metrics': res})\n\t\t\t\t\t}).catch(function(err){\n\t\t\t\t\t\texit(zErr)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\texit(zErr)\n\t\t\t}\n\t\t} else {\n\t\t\ttry {document.body.removeChild(element)} catch(e) {}\n\t\t\treturn resolve('not supported')\n\t\t}\n\t} catch(e) {\n\t\treturn resolve(e+'')\n\t}\n})\n\nfunction get_agent_iframes(log = false) {\n\tif (gRun && sectionIgnore.includes('agent')) {return}\n\n\t// runs post FP\n\tlet t0 = nowFn()\n\n\tlet aNames = ['content_docroot', 'content_with_url', 'window_docroot',\n\t\t'window_with_url', 'iframe_access', 'nested', 'window_access']\n\n\tlet METRIC = 'agent'\n\t// get data\n\tPromise.all([\n\t\tgetDynamicIframeWindow({context: window, contentWindow: true, violateSOP: false, test: METRIC}), // docroot contentWindow\n\t\tgetDynamicIframeWindow({context: window, contentWindow: true, source: '?', violateSOP: false, test: METRIC}), // with URL contentWindow\n\t\tgetDynamicIframeWindow({context: window, violateSOP: false, test: METRIC}), // docroot\n\t\tgetDynamicIframeWindow({context: window, source: '?', violateSOP: false, test: METRIC}), // with URL\n\t\tgetDynamicIframeWindow({context: frames, test: METRIC}), // iframe access\n\t\tgetDynamicIframeWindow({context: window, nestIframeInContainerDiv: true, test: METRIC}), // nested\n\t\tgetDynamicIframeWindow({context: window, test: METRIC}), // window access\n\t]).then(function(results){\n\t\tconst ctrlHash = mini(sDetail.document.agent_reported)\n\t\t/* test some errors\n\t\tresults[0] = 'i am not groot'\n\t\tresults[2] = 'i am groot'\n\t\t//*/\n\t\t/* test some different hashes\n\t\tresults[4].data.appCodeName = 'Godzilla'\n\t\tlet tmpHash = mini(results[4].data)\n\t\tresults[4].hash = tmpHash\n\t\tresults[6].data.appName = 'Navigator'\n\t\ttmpHash = mini(results[6].data)\n\t\tresults[6].hash = tmpHash\n\t\t//*/\n\n\t\tlet oData = {}, oDisplay = {}, oHashes = {}, countErrors = 0\n\t\tfor (let i=0; i < results.length; i++) {\n\t\t\tlet item = results[i]\n\t\t\tlet name = 'agent_'+ aNames[i]\n\t\t\tif ('string' == typeof item) {\n\t\t\t\tdom[name].innerHTML = item\n\t\t\t\tcountErrors++\n\t\t\t} else {\n\t\t\t\tif (oHashes[item.hash] == undefined) {\n\t\t\t\t\toHashes[item.hash] = {'group': [name], 'data': item.data}\n\t\t\t\t} else {\n\t\t\t\t\toHashes[item.hash]['group'].push(name)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet summary ='', btn ='', errString =''\n\t\tif (countErrors > 0 && countErrors < results.length) {\n\t\t\terrString += \" <span class='s2'>[\"+ countErrors +' error'+ (countErrors > 1 ? 's' : '') +']</span>'+ sc\n\t\t}\n\t\tif (countErrors == results.length) {\n\t\t\t// all errors\n\t\t\tsummary = zErr\n\t\t} else if (Object.keys(oHashes).length == 1) {\n\t\t\t// single hash\n\t\t\tlet singleHash, notation = match_green\n\t\t\tfor (const k of Object.keys(oHashes)) {\n\t\t\t\tsingleHash = k\n\t\t\t\tif (k !== ctrlHash) {\n\t\t\t\t\tnotation = match_red\n\t\t\t\t\tbtn = addButton(2, 'agent_iframe', 'details', 'btnc', zIFRAME)\n\t\t\t\t\taddDetail('agent_iframe', oHashes[k].data, zIFRAME)\n\t\t\t\t}\n\t\t\t\tlet items = oHashes[k].group\n\t\t\t\titems.forEach(function(item) {oDisplay[item] = k})\n\t\t\t}\n\t\t\tsummary = singleHash + btn + notation\n\t\t} else {\n\t\t\t// multiple hashes\n\t\t\tfor (const k of Object.keys(oHashes)) {\n\t\t\t\tlet items = oHashes[k].group\n\t\t\t\tfor (let i=0; i < items.length; i++) {\n\t\t\t\t\t// reset\n\t\t\t\t\tbtn = ''\n\t\t\t\t\tlet name = items[i]\n\t\t\t\t\t// 1st of each non-match: add details\n\t\t\t\t\tif (i == 0 && k !== ctrlHash) {\n\t\t\t\t\t\tbtn = addButton(2, name, 'details', 'btnc', zIFRAME)\n\t\t\t\t\t\taddDetail(name, oHashes[k].data, zIFRAME)\n\t\t\t\t\t}\n\t\t\t\t\toDisplay[name] = k + btn\n\t\t\t\t}\n\t\t\t\tsummary = 'mixed'+ match_red\n\t\t\t}\n\t\t}\n\t\tfor (const k of Object.keys(oDisplay)) {dom[k].innerHTML = oDisplay[k]}\n\t\tdom.uaIframes.innerHTML = summary + errString\n\t\tif (log) {log_perf(SECTNF, 'agent iframes', t0)}\n\t})\n}\n\ncountJS('iframes')\n"
  },
  {
    "path": "js/misc.js",
    "content": "'use strict';\n\n/* TIMING */\n\nfunction check_timing(type) {\n\tlet aAllow = ['currenttime', 'date', 'mark', 'now', 'timestamp']\n\tif (!aAllow.includes(type)) {return true}\n\n\tlet setTiming = new Set(), value, result = true\n\tlet aIgnore = [0, -0, -1, -16, -17]\n\tlet max = isPerf ? 10 : 500\n\tfor (let i=1; i < max ; i++) {\n\t\ttry {\n\t\t\tif ('now' == type) {value = performance.now() - performance.now()\n\t\t\t} else if ('timestamp' == type) {\n\t\t\t\tvalue = new Event('').timeStamp - new Event('').timeStamp\n\t\t\t} else if ('date' == type) {\n\t\t\t\tvalue = (new Date())[Symbol.toPrimitive]('number') - (new Date())[Symbol.toPrimitive]('number')\n\t\t\t} else if ('mark' == type) {\n\t\t\t\tvalue = performance.mark('a').startTime - performance.mark('a').startTime\n\t\t\t} else if ('currenttime' == type) {\n\t\t\t\tvalue = (gTimeline.currentTime) - (gTimeline.currentTime)\n\t\t\t}\n\t\t\tvalue = Math.trunc(value)\n\t\t\t// we're subtracting the second measurement from the first so any value !== 0/-0 would be negative\n\t\t\tif (!aIgnore.includes(value)) {result = false}\n\t\t\tsetTiming.add(value)\n\t\t} catch {\n\t\t\t// we would have already captured errors\n\t\t\treturn true\n\t\t}\n\t}\n\t// note: sometimes mark + timestamp \"noise\" mode are not caught\n\t// ToDo: if !isPerf and return is true, perhaps we can try something else eg with a known delay\n\tif (false == result) {console.log(type, max, setTiming)}\n\treturn result\n}\n\nfunction get_timing_mark() {\n\ttry {\n\t\tlet entries = performance.getEntriesByName(\"a\",\"mark\")\n\t\tif (undefined === entries) {\n\t\t\tthrow zD\n\t\t} else {\n\t\t\tlet tmpSet = new Set()\n\t\t\tentries.forEach(function(obj){\n\t\t\t\tlet value = obj.startTime\n\t\t\t\tif (undefined !== value) {\n\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\ttmpSet.add(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet data = Array.from(tmpSet)\n\t\t\tdata = data.sort(function (a,b) { return a-b})\n\t\t\tgData.timing.mark = data\n\t\t}\n\t} catch(e) {\n\t\tgData.timing.mark = e+''\n\t}\n}\n\nfunction get_timing_navigation() {\n\t// dom.enable_performance_navigation_timing\n\ttry {\n\t\tlet entries = performance.getEntries().find(({entryType})=>entryType==='navigation')\n\t\tif (undefined === entries) {\n\t\t\tthrow zD\n\t\t} else {\n\t\t\tlet aList = ['connectEnd','connectStart','domComplete','domContentLoadedEventEnd','domContentLoadedEventStart',\n\t\t\t\t'domInteractive','domainLookupEnd','domainLookupStart','duration','loadEventEnd','loadEventStart',\n\t\t\t\t'requestStart','responseEnd','responseStart','secureConnectionStart','startTime','unloadEventEnd',\n\t\t\t\t'unloadEventStart','workerStart']\n\t\t\tlet tmpSet = new Set()\n\t\t\taList.forEach(function(k){\n\t\t\t\tlet value = entries[k]\n\t\t\t\tif (undefined !== value) {\n\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\ttmpSet.add(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet data = Array.from(tmpSet)\n\t\t\tdata = data.sort(function (a,b) { return a-b})\n\t\t\tgData.timing.navigation = data\n\t\t}\n\t} catch(e) {\n\t\tgData.timing.navigation = e+''\n\t}\n}\n\nfunction get_timing_performance() {\n\t// dom.enable_performance\n\ttry {\n\t\tlet entries = performance.timing\n\t\tif (0 === entries.loadEventEnd && 0 == entries.navigationStart) {\n\t\t\tthrow zD\n\t\t} else {\n\t\t\tlet aList = ['connectStart','domComplete','domContentLoadedEventEnd','domContentLoadedEventStart',\n\t\t\t\t'domInteractive','domLoading','domainLookupEnd','domainLookupStart','fetchStart','loadEventEnd',\n\t\t\t\t'loadEventStart','navigationStart','redirectEnd','redirectStart','requestStart','responseEnd',\n\t\t\t\t'responseStart','secureConnectionStart','unloadEventEnd','unloadEventStart']\n\t\t\tlet tmpSet = new Set()\n\t\t\taList.forEach(function(k){\n\t\t\t\tlet value = performance.timing[k]\n\t\t\t\tif (undefined !== value && 0 !== value) {\n\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\ttmpSet.add(value)\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet data = Array.from(tmpSet)\n\t\t\tdata = data.sort(function (a,b) { return a-b})\n\t\t\tgData.timing.performance = data\n\t\t}\n\t} catch(e) {\n\t\tgData.timing.performance = e+''\n\t}\n}\n\nfunction get_timing_resource() {\n\t// dom.enable_resource_timing\n\ttry {\n\t\tlet entries = performance.getEntriesByType('resource')\n\t\tif (0 === entries.length) {\n\t\t\tif (isFile) {throw zSKIP} else {throw zD}\n\t\t} else {\n\t\t\tlet aList = ['duration','fetchStart','requestStart','responseEnd','responseStart','secureConnectionStart','startTime']\n\t\t\tlet tmpSet = new Set()\n\t\t\tentries.forEach(function(obj){\n\t\t\t\taList.forEach(function(item){\n\t\t\t\t\tlet value = obj[item]\n\t\t\t\t\tif (undefined !== value) {\n\t\t\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t// fix to 3 decimal places: otherwise e.g.\n\t\t\t\t\t\t// 33.33399999999999, 33.333999999999996 has a diff of 7.105427357601002e-15\n\t\t\t\t\t\t// and diff (calc1) becomes 7.1 instead of basically 0\n\t\t\t\t\t\t// don't allow huge drift\n\t\t\t\t\t\tif (value < 200000) {\n\t\t\t\t\t\t\tvalue = value.toFixed(3) * 1\n\t\t\t\t\t\t\ttmpSet.add(value)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t\tlet data = Array.from(tmpSet)\n\t\t\tdata = data.sort(function (a,b) { return a-b})\n\t\t\tgData.timing.resource = data\n\t\t}\n\t} catch(e) {\n\t\tgData.timing.resource = e+''\n\t}\n}\n\nfunction get_timing(METRIC) {\n\tlet aLoop = ['contexttime','performancetime']\n\tlet strZero = 'not enough data'\n\tif ('timing_precision' == METRIC) {\n\t\taLoop = gTiming\n\t\t// check isPerf again\n\t\tif (isPerf) {get_isPerf()}\n\t\t// get a last value for each to ensure a max diff\n\t\taddTimings()\n\t\t// poulate some data\n\t\tget_timing_mark()\n\t\tget_timing_navigation()\n\t\tget_timing_performance()\n\t\tget_timing_resource()\n\t\t/* testing\n\t\tgData.timing.date = [1723240561321]\n\t\tgData.timing.exslt = ['2024-08-09T20:23:10.000','2024-08-09T20:23:11.000']\n\t\tgData.timing.currenttime = [83.34, 116.72, 150, 233.4] // 60FPS but no 3 decimal places\n\t\tgData.timing.currenttime = [966.686] // only a single RFP entry\n\t\tgData.timing.currenttime = [962.486] // only a single non-RFP entry\n\n\t\t//ToDo: cleanup rogue is10's and is100's\n\t\t\t// looking at these they aren't actually 100's but also 50's: but I use divided by 2\n\t\tgData.timing.currenttime = [86.4, 136.44, 236.48] // real world examples that causes a 100ms entry\n\t\tgData.timing.currenttime = [75.12, 125.16, 225.24] // another one\n\t\tgData.timing.currenttime = [71.12, 121.14, 171.16] // another one\n\n\t\tgData.timing.exslt = [\"2025-07-14T03:53:46.047\",\"2025-07-14T03:53:46.058\"]\n\t\t//*/\n\n\t\t// isDecimal\n\t\tisDecimal = false\n\t\tif (isPerf) {\n\t\t\ttry {\n\t\t\t\tfor (let i=0; i < gData.timing.now.length; i++) {\n\t\t\t\t\tif (!Number.isInteger(gData.timing.now[i])) {isDecimal = true; break}\n\t\t\t\t}\n\t\t\t} catch(e) {console.log(e+'')}\n\t\t}\n\t}\n\tlet oGood = {\n\t\t'date': [0, 1, 16, 17, 33, 34],\n\t\t'instant': [0, 1, 16, 17, 33, 34],\n\t\t'resource': [0, 1, 2, 3, 16, 17, 18, 19, 33, 34, 35, 36], // resource can have large drift, use integers: e.g. wait ages then rerun\n\t\t'performance': [0, 1, 16, 17, 33, 34],\n\t\t'exslt': [0], // 1912129: exslt diffs must be 1000, and all end in .000\n\t\t'other': [\n\t\t\t// tested 20/10mn timestamps over ~12s/6s = ~750/370 unique times\n\t\t\t// the longer since the first time, the more decimal points drift\n\t\t\t// so 0's become 0.1's then 0.2's etc: 6s seems to limit drift to 1 decimal point\n\t\t\t0, 0.1, 0.2, 0.3, 0.4,\n\t\t\t16.6, 16.7, 16.8, 16.9, 17,\n\t\t\t33.3, 33.4, 33.5, 33.6, 33.7,\n\t\t],\n\t\t'ten': [0, 10, 20, 30, 40],\n\t}\n\tlet aNotInteger = ['mark','now','timestamp']\n\tlet calc1 = new RegExp('^-?\\\\d+(?:\\.\\\\d{0,' + (1 || -1) + '})?')\n\tlet str, data, notation, oData = {}, countFail = 0, countErr = 0\n\n\tsDetail[isScope][METRIC +'_data'] = {}\n\tlet isDateNoise = false\n\n\taLoop.forEach(function(k){\n\t\tlet aGood = oGood[k]\n\t\tif (undefined == aGood) {aGood = oGood.other}\n\t\t// don't add to health, we do that with the parent metric\n\t\tstr ='', notation = silent_red\n\t\ttry {\n\t\t\tlet aTimes = gData.timing[k]\n\t\t\tif ('string' == typeof aTimes) {throw aTimes}\n\t\t\taTimes = dedupeArray(aTimes)\n\t\t\tif (aTimes.length) {sDetail[isScope][METRIC +'_data'][k] = {'data': aTimes}}\n\t\t\t// type check\n\t\t\tlet start = aTimes[0]\n\t\t\tlet expected = ('exslt' == k || 'instant' == k) ? 'string' : 'number'\n\t\t\tlet typeCheck = typeFn(start)\n\t\t\tif (expected !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t// check noise\n\t\t\tlet isNoise = 'exslt' == k ? false : !check_timing(k)\n\t\t\tlet isMatch = true\n\t\t\tif (aNotInteger.includes(k) && Number.isInteger(start)) {isMatch = false}\n\t\t\tif ('date' == k) {\n\t\t\t\tisDateNoise = isNoise\n\t\t\t} else if ('exslt' == k) {\n\t\t\t\tisNoise = isDateNoise\n\t\t\t\tif ('.000' !== start.slice(-4)) {isMatch = false}\n\t\t\t\t// we use epoch time so each entry is always moving forward in time | remove leading 0 in ms\n\t\t\t\tstart = start.slice(0,20) + start.slice(-2)+ '0'\n\t\t\t\tstart = (new Date(start))[Symbol.toPrimitive]('number')\n\t\t\t} else if ('instant' == k) {\n\t\t\t\tstart = start.slice(0,-1) // remove trailing Z\n\t\t\t\tstart = (new Date(start))[Symbol.toPrimitive]('number')\n\t\t\t} else if ('resource' == k) {\n\t\t\t\tstart = Math.floor(start)\n\t\t\t}\n\t\t\tlet startIncremental = start\n\n\t\t\t// get diffs\n\t\t\tlet isZero = false, is10 = true, is100 = true\n\t\t\tif ('exslt' == k) {is100 = false} // exslt can only be 10 or RFP\n\t\t\tlet setDiffs = new Set(), setIncremental = new Set(), aTotal = []\n\t\t\tif (1 == aTimes.length) {\n\t\t\t\taTotal.push(0) // make sure we display something\n\t\t\t\t// all non-exslt we expect multiple values\n\t\t\t\tif ('exslt' !== k) {isMatch = false}\n\t\t\t\tisZero = true\n\t\t\t}\n\t\t\tfor (let i=1; i < aTimes.length ; i++) {\n\t\t\t\tlet end = aTimes[i]\n\t\t\t\t// type check\n\t\t\t\ttypeCheck = typeFn(end)\n\t\t\t\tif (expected !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t// clean up\n\t\t\t\tif ('exslt' == k) {\n\t\t\t\t\tif ('.000' !== end.slice(-4)) {isMatch = false}\n\t\t\t\t\tend = end.slice(0,20) + end.slice(-2)+ '0'\n\t\t\t\t\tend = (new Date(end))[Symbol.toPrimitive]('number')\n\t\t\t\t} else if ('instant' == k) {\n\t\t\t\t\tend = end.slice(0,-1) // remove trailing Z\n\t\t\t\t\tend = (new Date(end))[Symbol.toPrimitive]('number')\n\t\t\t\t} else if ('resource' == k) {\n\t\t\t\t\tend = Math.floor(end)\n\t\t\t\t}\n\t\t\t\t// truncate to 1 decimal place\n\t\t\t\t// diffs since start\n\t\t\t\tlet totaldiff = ((end - start).toString().match(calc1)[0]) * 1\n\t\t\t\taTotal.push(totaldiff)\n\t\t\t\tlet diff = (totaldiff % 50).toFixed(2) * 1 // drop 50s\n\t\t\t\tsetDiffs.add(diff)\n\t\t\t\t// incremental diffs: helps reduce drift\n\t\t\t\ttotaldiff = ((end - startIncremental).toString().match(calc1)[0]) * 1\n\t\t\t\tdiff = (totaldiff % 50).toFixed(2) * 1 // drop 50s\n\t\t\t\tsetIncremental.add(diff)\n\t\t\t\tstartIncremental = end // set this end value for the next incremental start value\n\t\t\t}\n\n\n\t\t\t// diff arrays\n\t\t\tlet aDiffs = Array.from(setDiffs)\n\t\t\tlet aIncremental = Array.from(setIncremental)\n\t\t\tsDetail[isScope][METRIC +'_data'][k]['diffs'] = aDiffs\n\t\t\tsDetail[isScope][METRIC +'_data'][k]['incremental'] = aIncremental\n\n\t\t\t// using incremental: test intervals\n\t\t\tif (aIncremental.length) {\n\t\t\t\tfor (let i=0; i < aIncremental.length; i++) {\n\t\t\t\t\tif (isMatch && !aGood.includes(aIncremental[i])) {isMatch = false}\n\t\t\t\t\tif (is10 && !oGood.ten.includes(aIncremental[i])) {is10 = false}\n\t\t\t\t\tif (is100 && 0 !== aIncremental[i]) {is100 = false}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// a single data entry means zero diffs/incremental == so we can never get isMatch\n\t\t\t\t\t// and is10/is100 can be false positives (we can ignore - seems rare)\n\n\t\t\t\t// don't assume 10 or 100 if only 1 sample size\n\t\t\t\t// ToDo: I do not like this: rare? and a false positive is ok when you look at the rest of the data\n\t\t\t\t\t// we could analayse and cleanup data afterwards\n\t\t\t}\n\n\t\t\t// some tests we can rely on non-integer\n\t\t\t\t// but others we measure enough to not all land on 0's (or 50's and 100s)\n\t\t\tlet a100 = ['date','performance','contexttime','performancetime']\n\t\t\tif (a100.includes(k)) {if (is100 || is10) {isMatch = false}}\n\t\t\t// clean up exslt\n\t\t\tif ('exslt' == k) {\n\t\t\t\tif (isNoise) {\n\t\t\t\t\tis100 = false\n\t\t\t\t\tif ('10ms' !== oData['date']) {is10 = false}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// currenttime: 60FPS false positives: check for 3 decimal places\n\t\t\tif (isMatch && 'currenttime' == k) {\n\t\t\t\tisMatch = false\n\t\t\t\tfor (let i=0; i < aTimes.length; i++) {\n\t\t\t\t\tlet check = Math.floor(aTimes[i]) === aTimes[i] ? 0 : (aTimes[i]).toString().split(\".\")[1].length\n\t\t\t\t\tif (3 === check) {isMatch = true; break}\n\t\t\t\t}\n\t\t\t}\n\t\t\t//console.log(k, isNoise)\n\t\t\tlet value =''\n\t\t\tif (isMatch && !isNoise && isGecko) {\n\t\t\t\tnotation = silent_green\n\t\t\t\tvalue = 'RFP'\n\t\t\t} else {\n\t\t\t\t// add entropy e.g. jShelter 10ms or 100ms or noise\n\t\t\t\t// order is 100+, 100, 10, noise, nothing\n\t\t\t\t// isZero could be is100: sometimes we just don't get enough data\n\t\t\t\t// so it can be a little unstable with e.g. extension fuckery - that's OK\n\t\t\t\tif (isZero) {value = strZero\n\t\t\t\t} else if (is100) {value = '100ms'\n\t\t\t\t} else if (is10) {value = '10ms'\n\t\t\t\t} else if (isNoise) {value = 'noise'\n\t\t\t\t}\n\t\t\t\tcountFail++\n\t\t\t}\n\t\t\toData[k] = value\n\n\t\t\t// display\n\t\t\tstr = aTotal.join(', ')\n\t\t\tlet strLen = str.length\n\t\t\tif (strLen > 60) {\n\t\t\t\tlet len = aTotal.length, unitLen = strLen/(aTotal.length)\n\t\t\t\tlet reduce = Math.floor((str.length - 60)/unitLen)\n\t\t\t\tlet lasttwo = ' &#x2026 '+ aTotal[len-2] +', '+aTotal[len-1]\n\t\t\t\tlet newTotal = aTotal.slice(0, len - (reduce + 2))\n\t\t\t\tif ((newTotal.join(', ') + lasttwo).length > 60) {newTotal = newTotal.slice(0, len - (reduce + 3))}\n\t\t\t\tstr = newTotal.join(', ') + lasttwo\n\t\t\t}\n\t\t\t//console.log(k, is10, is100, data, aTotal)\n\t\t\t//console.log(k, '\\naDiffs', aDiffs, '\\naIncremental', aIncremental)\n\t\t} catch(e) {\n\t\t\toData[k] = ''\n\t\t\tif ('reducetimer' !== k) {\n\t\t\t\tif ('instant' == k) {\n\t\t\t\t\t// gecko 139+ we expect temporal to work\n\t\t\t\t\tif (!isGecko || isGecko && isVer < 139) {\n\t\t\t\t\t\tif ('ReferenceError: Temporal is not defined' == e || 'ReferenceError: Can\\'t find variable: Temporal' == e) {e = zSKIP}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstr = (zD == e || zSKIP == e) ? e : log_error(17, METRIC +'_'+ k, e)\n\t\t\t\toData[k] = (zD == e || zSKIP == e) ? e : zErr\n\t\t\t\tif (zSKIP !== e) {\n\t\t\t\t\tcountFail++\n\t\t\t\t\tcountErr++\n\t\t\t\t} else {\n\t\t\t\t\tnotation = ''\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//sDetail[isScope][METRIC][k] = data\n\t\tif ('timing_precision' == METRIC) {\n\t\t\tif ('reducetimer' !== k) {\n\t\t\t\taddDisplay(17, METRIC +'_'+ k, str,'', notation)\n\t\t\t}\n\t\t} else {\n\t\t\taddDisplay(17, METRIC +'_'+ k, str,'', notation)\n\t\t}\n\t})\n\n\t// display\n\tlet btn = ''\n\t// data\n\tif ('timing_precision' == METRIC) {\n\t\t// we didn't countFail reducetimer or skipped Temporal so RFP will have zero fails\n\n\t\tlet rtvalue // reducetimer: privacy.reduceTimerPrecision\n\t\tnotation = silent_green\n\t\tlet isProtected = (aLoop.length - countFail) == aLoop.length\n\n\t\t// currenttime: handle a single data entry\n\t\tif (1 == countFail && oData.currenttime == strZero) {\n\t\t\trtvalue = zNA\n\t\t\t// with RFP is always oGood\n\t\t\ttry {\n\t\t\t\tlet singledata = (gData.timing.currenttime[0].toString().match(calc1)[0]) * 1\n\t\t\t\tlet singletest = (singledata % 50).toFixed(2) * 1\n\t\t\t\tif (oGood.other.includes(singletest)) {\n\t\t\t\t\toData.currenttime = 'RFP'\n\t\t\t\t\taddDisplay(17, METRIC +'_currenttime', singledata,'', notation)\n\t\t\t\t\tcountFail = countFail - 1\n\t\t\t\t\tisProtected = (aLoop.length - countFail) == aLoop.length // recalc\n\t\t\t\t}\n\t\t\t} catch(e) {}\n\t\t}\n\n\t\t// if _some_ RFP timing fails (i.e isProtected !== aLoop.length), then rtvalue\n\t\t\t// can end up false when it isn't really - because we have decimals but cannot\n\t\t\t// know for sure it's RFP (not worth checking everything non-error for 60FPS)\n\t\t\t// but what we can do is exclude all errors\n\t\tisProtected = (aLoop.length - countFail) + countErr == aLoop.length\n\t\t//console.log(aLoop.length - countFail, countErr, aLoop.length, isProtected)\n\t\tif (isProtected && isDecimal || !isGecko) {\n\t\t\t// non-Gecko || if RFP which is also isDecimal, then we can't tell\n\t\t\trtvalue = zNA\n\t\t\t// RFP with decimals looks silly\n\t\t\tif (isGecko) {isDecimal = false}\n\t\t} else {\n\t\t\trtvalue = !isDecimal\n\t\t\tif (isDecimal) {countFail++; notation = silent_red}\n\t\t}\n\t\t// reducetimer data/display\n\t\taddDisplay(17, METRIC +'_reducetimer', rtvalue,'', notation)\n\t\toData['reducetimer'] = rtvalue\n\n\t\t// recalc\n\t\tisProtected = (aLoop.length - countFail) == aLoop.length\n\t\tnotation = isProtected ? rfp_green : rfp_red\n\t\tstr = (aLoop.length - countFail) +'/' + aLoop.length\n\t\t// add\n\t\tbtn = addButton(17, METRIC, str) + addButton(17, METRIC +'_data', 'data')\n\t\taddBoth(17, METRIC, mini(oData), btn, notation, oData)\n\t\t// cleanup\n\t\t//performance.clearMeasures()\n\n\t} else {\n\t\tnotation = (aLoop.length - countFail) == aLoop.length ? rfp_green : rfp_red\n\t\tstr = (aLoop.length - countFail) +'/' + aLoop.length\n\t\tbtn = addButton(17, METRIC, str) + addButton(17, METRIC +'_data', 'data')\n\t\tsDetail[isScope][METRIC] = oData\n\t\taddDisplay(17, METRIC, mini(oData), btn, notation)\n\t}\n\treturn\n}\n\n/* MISC */\n\nfunction check_mathLies(type) {\n\tlet mathList = ['Math.cos','Math.sin','Math.tan'] // trig\n\tif ('other' == type) {\n\t\tmathList = ['Math.cosh','Math.exp','Math.log','Math.pow']\n\t}\n\t/*\n\t// not used\n\t'Math.acos','Math.acosh','Math.asinh','Math.atan','Math.atan2','Math.atanh',\n\t'Math.cbrt','Math.expm1','Math.log10','Math.log1p','Math.sinh','Math.sqrt','Math.tanh'\n\t*/\n\treturn mathList.some(lie => sData[SECT99].indexOf(lie) >= 0)\n}\n\nfunction get_component_shims(METRIC) {\n\tif (!isGecko) {addBoth(18, METRIC, zNA); return}\n\n\t// 960392: dom.use_components_shim\n\tlet hash, btn ='', data, notation = isBB ? bb_red: ''\n\ttry {\n\t\tdata = Object.keys(Object.getOwnPropertyDescriptors(Components.interfaces))\n\t\thash = mini(data); btn = addButton(18, METRIC, data.length)\n\t} catch(e) {\n\t\tif (e+'' == 'ReferenceError: Components is not defined') {\n\t\t\tif (isBB) {notation = bb_green}\n\t\t\thash = 'undefined'\n\t\t} else {\n\t\t\thash = e; data = zErrLog\n\t\t}\n\t}\n\taddBoth(18, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_math(METRIC, isLies) {\n\tlet hash, btn='', data = {}, notation = 'math_trig' == METRIC ? rfp_red : ''\n\ttry {\n\t\tif ('math_trig' == METRIC) {\n\t\t\tconst oMath = {\n\t\t\t\tcos: [\n\t\t\t\t\t['-1',-1],['17*Math.LOG10E',17*Math.LOG10E],['1e12',1e12],['1e130',1e130],\n\t\t\t\t\t['1e140',1e140],['1e251',1e251],['1e272',1e272],['1e284',1e284],['1e75',1e75],\n\t\t\t\t\t['21*Math.LN2',21*Math.LN2],['21*Math.LOG2E',21*Math.LOG2E],['25*Math.SQRT2',25*Math.SQRT2],\n\t\t\t\t\t['50*Math.SQRT1_2',50*Math.SQRT1_2],['51*Math.LN2',51*Math.LN2],['57*Math.E',57*Math.E],\n\t\t\t\t],\n\t\t\t\tsin: [\n\t\t\t\t\t['35*Math.SQRT1_2',35*Math.SQRT1_2],['7*Math.LOG10E',7*Math.LOG10E],\n\t\t\t\t],\n\t\t\t\ttan: [\n\t\t\t\t\t['10*Math.LOG10E',10*Math.LOG10E],['10*Math.LOG2E',10*Math.LOG2E],\n\t\t\t\t\t['17*Math.SQRT2',17*Math.SQRT2],['34*Math.SQRT1_2',34*Math.SQRT1_2],\n\t\t\t\t\t['6*Math.E',6*Math.E],['6*Math.LN2',6*Math.LN2],\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst keys = ['cos','sin','tan']\n\t\t\tfor (let x = 0; x < keys.length; x++){\n\t\t\t\tlet k = keys[x]\n\t\t\t\toMath[k].forEach(function(item) {data['Math.'+ k +'('+ item[0] +')'] = Math[k](item[1])})\n\t\t\t}\n\t\t} else {\n\t\t\tdata = {\n\t\t\t\t'(Math.E - 1 / Math.E) / 2': (Math.E - 1 / Math.E) / 2, // sinh(1)\n\t\t\t\t'(Math.exp(1) + Math.exp(-1)) / 2': (Math.exp(1) + Math.exp(-1)) / 2, // cosh(1)\n\t\t\t\t'(Math.exp(1) - Math.exp(-1)) / 2': (Math.exp(1) - Math.exp(-1)) / 2, // sinh(1) alt\n\t\t\t\t'Math.E - 1': Math.E - 1, // expm1(1)\n\t\t\t\t'Math.cosh(1)': Math.cosh(1),\n\t\t\t\t'Math.exp(1) - 1': Math.exp(1) - 1, // expm1(1) alt\n\t\t\t\t'Math.log((1.5)/(0.5))/2': Math.log((1.5) / (0.5)) / 2,\n\t\t\t\t'Math.pow(Math.abs(Math.PI), 1 / 3)': Math.pow(Math.abs(Math.PI), 1 / 3), // polyfill cbrt(Math.PI)\n\t\t\t}\n\t\t}\n\t\tif (runST) {if ('math_trig' == METRIC) {data['Math.cos(-1)'] = NaN} else {data['Math.E - 1'] = Infinity}}\n\t\tfor (const k of Object.keys(data)) {\n\t\t\tlet typeCheck = typeFn(data[k])\n\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t}\n\t\thash = mini(data); btn = addButton(18, METRIC)\n\t\tif (METRIC == 'math_trig' && 'd240b02e' == hash) {notation = rfp_green}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(18, METRIC, hash, btn, notation, data, isLies)\n\treturn\n}\n\nfunction get_math_css(METRIC) {\n\t// 1881277: ToDo: expand this\n\t\t// This should just be equivalancy of Math but IDK what libraries CSS uses\n\t\t// and Math has entropy beyond platform architecture\n\t// We could also try to replicate\n\t\t// - math_trig: RFP doesn't cover this IIUIC so a leak?\n\t\t// - math_other\n\t\t// both would add more methods and expose lies/leaks\n\tlet hash, btn='', data = {}, isLies = isDomRect == -1\n\tlet aList = ['A']\n\ttry {\n\t\tlet wrapper = dom.tzpCalc, target = dom.tzpCalcTarget, method\n\t\taList.forEach(function(k){\n\t\t\ttarget.className = '' // remove any classes\n\t\t\ttarget.classList.add('tzpCalc' +k) // add class\n\t\t\tmethod = measureFn(wrapper, METRIC)\n\t\t\tif (k == aList[0]) { // typeCheck first itemn\n\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\tlet value = method.width\n\t\t\t\tif (runST) {value = undefined}\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t}\n\t\t\tdata[k] = method.width\n\t\t})\n\t\thash = mini(data); btn = addButton(18, METRIC)\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(18, METRIC, hash, btn, '', data, isLies)\n}\n\nfunction get_navigator_keys(METRIC) {\n\tlet hash, btn='', aNav = [], notation = isBBESR ? bb_red : '', isLies = false\n\ttry {\n\t\tif (runST) {foo++}\n\t\t// navigator\n\t\tfor (const key in navigator) {aNav.push(key)}\n\t\tlet typeCheck = typeFn(aNav)\n\t\tif ('array' !== typeCheck) {throw zErrType + typeCheck}\n\n\t\tif (isSmart) {\n\t\t\t// navigator.prototype: should match navigator\n\t\t\tlet aProto = Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype))\n\t\t\tlet typeCheck = typeFn(aProto)\n\t\t\tif ('array' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t// ToDo: check/expand these\n\t\t\tlet expected = [\n\t\t\t\t'appCodeName','appName','appVersion','buildID','oscpu','platform','product','productSub','userAgent','cookieEnabled',\n\t\t\t\t'vendor','vendorSub','hardwareConcurrency','language','languages','mimeTypes','onLine','plugins','webdriver',\n\t\t\t\t'taintEnabled','javaEnabled','doNotTrack','cookieEnabled','pdfViewerEnabled','requestMediaKeySystemAccess',\n\t\t\t\t'locks', // 1851539\n\t\t\t\t'userActivation', // 1791079\n\t\t\t\t// ToDo: FF144+? 1983296 functionality pref deprecated\n\t\t\t\t//'globalPrivacyControl', // 1983296\n\t\t\t]\n\t\t\t// test\n\t\t\tif (runSL) {\n\t\t\t\texpected = ['a','b','javaEnabled']\n\t\t\t\taNav = ['a','javaEnabled','c','d','f','g'] // pre a, missing b, a+d not-in-proto\n\t\t\t\taProto = ['javaEnabled','b','c','e','constructor','f','g'] // missing a, post f+g, e not in nav\n\t\t\t}\n\t\t\t// compare hashes\n\t\t\tlet navhash = mini(aNav.concat('constructor')), protohash = mini(aProto) // do I need this\n\t\t\t// tampering: don't dedupe, just collect\n\t\t\tlet missing = {}, post = [], pre = [], diffs = {}, oTampered = {}\n\t\t\t// aProto: post constructor\n\t\t\tlet position = aProto.indexOf('constructor')\n\t\t\tpost = aProto.slice(position +1)\n\t\t\t// aNav: pre javaEnabled\n\t\t\tposition = aNav.indexOf('javaEnabled')\n\t\t\tif (position > 0) {\n\t\t\t\tpre = aNav.slice(0, position)\n\t\t\t\tif (isVer < 129) {pre = pre.filter(x => !['vibrate'].includes(x))} // ignore vibrate in 128 or lower\n\t\t\t}\n\t\t\t// missing\n\t\t\tlet missingNav = expected.filter(x => !aNav.includes(x))\n\t\t\tlet missingProto = expected.filter(x => !aProto.includes(x))\n\t\t\tif (missingNav.length) {missing['navigator'] = missingNav.sort()}\n\t\t\tif (missingProto.length) {missing['prototype'] = missingProto.sort()}\n\t\t\t// diffs\n\t\t\t// in prototype but not in nav\n\t\t\tlet notNav = aProto.filter(x => !aNav.includes(x))\n\t\t\tnotNav = notNav.filter(x => !['constructor'].includes(x)) // ignore constructor\n\t\t\tif (notNav.length) {diffs['not_in_navigator'] = notNav.sort()}\n\t\t\t// in nav but not in prototype\n\t\t\tlet notProto = aNav.filter(x => !aProto.includes(x))\n\t\t\tif (notProto.length) {diffs['not_in_prototype'] = notProto.sort()}\n\t\t\t// \n\t\t\tisLies = (post.length + pre.length + Object.keys(missing).length + Object.keys(diffs).length) > 0\n\t\t\tif (isLies) {\n\t\t\t\tif (Object.keys(missing).length) {oTampered['missing_expected'] = missing}\n\t\t\t\tif (post.length) {oTampered['post_constructor'] = post.sort()}\n\t\t\t\tif (pre.length) {oTampered['pre_javaEnabled'] = pre.sort()}\n\t\t\t\tif (Object.keys(diffs).length) {oTampered['prototype_vs_navigator'] = diffs}\n\t\t\t\taddDetail(METRIC +'_tampered', oTampered)\n\t\t\t}\n\t\t}\n\t\t// always return aNav\n\t\thash = mini(aNav); btn = addButton(18, METRIC, aNav.length)\n\t\t// health: BB only if ESR\n\t\tif (isBBESR) {\n\t\t\tif (isMB) { // MB\n\t\t\t\tif ('a389214b' == hash) {notation = bb_green} // MB15 41\n\t\t\t} else { // TB\n\t\t\t\tif ('8d3dd2a1' == hash) {notation = bb_green} // TB15\n\t\t\t}\n\t\t}\n\t\t// if tampered use notation to fail health\n\t\tif (isLies) {notation += addButton('bad', METRIC +'_tampered', \"<span class='health'>\"+ cross + '</span> tampered')}\n\t} catch(e) {\n\t\thash = e; aNav = zErrLog\n\t}\n\taddBoth(18, METRIC, hash, btn, notation, aNav, isLies)\n\treturn\n}\n\nfunction get_pdf(METRIC) {\n\t// FF99+ none/hardcoded: all three are expected nav keys\n\t// https://html.spec.whatwg.org/multipage/system-state.html#dom-plugin\n\t\t// the order is alphabetical with 'PDF Viewer' inserted in the 0th popsition\n\n\tlet data = {}\n\tfunction get_obj(item) {\n\t\tlet res = 'none'\n\t\ttry {\n\t\t\tlet obj = navigator[item]\n\t\t\tif (runST) {obj = []} else if (runSI) {obj = {}}\n\t\t\tlet typeCheck = typeFn(obj, true)\n\t\t\tif ('object' !== typeCheck) {throw zErrType + typeFn(obj)}\n\t\t\tlet expected = '[object '+ item.charAt(0).toUpperCase() + (item.slice(1)).slice(0, -1) +'Array]'\n\t\t\tif (expected !== obj+'') {throw zErrInvalid +'expected '+ expected +': got '+ obj+''}\n\t\t\tlet cyclicTest = mini(obj) // TypeError: cyclic object\n\t\t\tif (obj.length) {\n\t\t\t\tres = []\n\t\t\t\tfor (let i=0; i < obj.length; i++) {\n\t\t\t\t\tif ('mimeTypes' == item) {\n\t\t\t\t\t\tres.push( obj[i].type + (obj[i].description == '' ? ': * ' : ': '+ obj[i].type)\n\t\t\t\t\t\t\t+ (obj[i].suffixes == '' ? ': *' : ': '+ obj[i].suffixes))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tres.push(obj[i].name + (obj[i].filename == '' ? ': * ' : ': '+ obj[i].filename)\n\t\t\t\t\t\t\t+ (obj[i].description == '' ? ': *' : ': '+ obj[i].description))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(18, METRIC +'_'+ item, e); res = zErr\n\t\t}\n\t\tdata[item] = res\n\t\treturn\n\t}\n\tfunction get_pdfViewer(item) {\n\t\tlet res\n\t\ttry {\n\t\t\tres = navigator[item]\n\t\t\tif (runST) {res = undefined}\n\t\t\tlet typeCheck = typeFn(res)\n\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t} catch(e) {\n\t\t\tlog_error(18, METRIC +'_'+ item, e); res = zErr\n\t\t}\n\t\tdata[item] = res\n\t\treturn\n\t}\n\tPromise.all([\n\t\tget_obj('mimeTypes'), // do in sorted order\n\t\tget_pdfViewer('pdfViewerEnabled'),\n\t\tget_obj('plugins'),\n\t]).then(function() {\n\t\t// FF116 1838415 dropped RFP protection\n\t\t// FF147 1999126 re-added: just notate as RFP regardless of version\n\t\tlet notation = rfp_red, isLies = false\n\t\tif (runSL) {data = {'mimeTypes': 'none', 'pdfViewerEnabled': true, 'plugins': 'none'}}\n\t\tlet hash = mini(data)\n\t\tif (!['91073152','beccb452'].includes(hash) || isProxyLie('Navigator.pdfViewerEnabled')) {\n\t\t\tisLies = true\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tlet keys = Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype))\n\t\t\t\tif (keys.indexOf('pdfViewerEnabled') > keys.indexOf('constructor')) {isLies = true}\n\t\t\t} catch {}\n\t\t}\n\t\tif ('91073152' == hash) {notation = rfp_green}\n\t\taddBoth(18, METRIC, hash, addButton(18, METRIC), notation, data, isLies)\n\t\treturn\n\t})\n}\n\nconst get_speech_engines = (METRIC) => new Promise(resolve => {\n\t// media.webspeech.synth.enabled\n\tlet t0 = nowFn(), notation = rfp_red, isLies = false\n\tfunction exit(display, value) {\n\t\taddBoth(18, METRIC, display,'', notation, value, isLies)\n\t\treturn resolve()\n\t}\n\n\tfunction populateVoiceList() {\n\t\tlet res = [], ignoreLen, ignoreStr\n\t\t/* examples\n\t\t\tmoz-tts:android:hr_HR\n\t\t\turn:moz-tts:sapi:Microsoft David - English (United States)?en-US\n\t\t\turn:moz-tts:osx:com.apple.eloquence.en-US.Eddy\n\t\t*/\n\t\tlet aStrip = [\n\t\t\t'moz-tts:android:', // android\n\t\t\t'urn:moz-tts:osx:com.apple.eloquence.', // mac\n\t\t\t'urn:moz-tts:sapi:', // windows\n\t\t]\n\t\ttry {\n\t\t\tlet v = speechSynthesis.getVoices()\n\t\t\tif (runST) {v = null} else if (runSI) {v = [{}]} else if (runSL) {addProxyLie('speechSynthesis.getVoices')}\n\t\t\tlet typeCheck = typeFn(v, true)\n\t\t\tif ('array' !== typeCheck) {throw zErrType + typeFn(v)}\n\t\t\tif (v.length) {\n\t\t\t\tlet expected = '[object SpeechSynthesisVoice]'\n\t\t\t\tif ((v +'').slice(0,29) !== '[object SpeechSynthesisVoice]') {throw zErrInvalid +'expected '+ expected}\n\t\t\t}\n\t\t\tif (v.length == 0) {\n\t\t\t\tnotation = rfp_green\n\t\t\t\texit('none','none')\n\t\t\t} else {\n\t\t\t\t// enumerate: reduce redundancy/noise\n\t\t\t\t\t// only record default if true and localService if false | ignore voiceURI if it matches expected\n\t\t\t\tv.forEach(function(i) {\n\t\t\t\t\tlet uriStr = i.voiceURI, skipURI = false\n\t\t\t\t\t// replace useless strings\n\t\t\t\t\taStrip.forEach(function(str){uriStr = uriStr.replace(str, '')})\n\t\t\t\t\turiStr.trim()\n\t\t\t\t\tif (isGecko) {\n\t\t\t\t\t\tskipURI = (uriStr = i.name +'?'+ i.lang) // windows\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// blink\n\t\t\t\t\t\tskipURI = (uriStr == i.name) // dedupe repetitive crap\n\t\t\t\t\t}\n\t\t\t\t\tres.push(\n\t\t\t\t\t\ti.name +' | '+ i.lang + (i['default'] ? ' | default' : '') + (i.localService ? '' : ' | false') + (skipURI ? '' : ' | '+ uriStr)\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t\tlet hash = mini(res)\n\t\t\t\taddBoth(18, METRIC, hash, addButton(18, METRIC, res.length), notation, res, isProxyLie('speechSynthesis.getVoices'))\n\t\t\t\tlog_perf(18, METRIC, t0)\n\t\t\t\treturn resolve()\n\t\t\t}\n\t\t} catch(e) {\n\t\t\texit(e, zErrLog)\n\t\t}\n\t}\n\ttry {\n\t\tpopulateVoiceList()\n\t\tif (speechSynthesis.onvoiceschanged !== undefined) {\n\t\t\tspeechSynthesis.onvoiceschanged = populateVoiceList;\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\nfunction get_svg(METRIC) {\n\tlet hash, data ='', target = dom.tzpSVG\n\ttry {\n\t\tif (runSE) {foo++}\n\t\ttarget.innerHTML =''\n\t\tconst svgns = 'http://www.w3.org/2000/svg'\n\t\tlet shape = document.createElementNS(svgns,'svg')\n\t\tlet rect = document.createElementNS(svgns,'rect')\n\t\trect.setAttribute('width',20)\n\t\trect.setAttribute('height',20)\n\t\tshape.appendChild(rect)\n\t\ttarget.appendChild(shape)\n\t\thash = target.offsetHeight > 0 ? zE : zD\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(18, METRIC, hash,'','', data)\n\treturn\n}\n\nfunction get_webdriver(METRIC) {\n\t// expected FF60+\n\tlet value, data =''\n\ttry {\n\t\tvalue = navigator[METRIC]\n\t\tif (runST) {value = null}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(18, METRIC, value,'','', data, isProxyLie('Navigator.'+ METRIC))\n\treturn\n}\n\nfunction get_window_functions(METRIC) {\n\t// isProps was generated n+ sorted in get_window_props\n\tlet t0 = nowFn()\n\tlet hash, btn='', tamperBtn ='', data = {}, oPost = (isGecko ? {} : ''), notation = isBBESR ? bb_red : ''\n\tlet typeCheck = typeFn(isProps)\n\t// propagate errors\n\tif ('string' == typeCheck) {\n\t\taddBoth(18, METRIC, isProps, '', notation, zErrLog)\n\t\treturn\n\t}\n\t// isProps _should_ be an error string of a populated array, but let's cover it\n\tif ('array' !== typeCheck) {\n\t\taddBoth(18, METRIC, zErrType + typeCheck, '', notation, zErrLog)\n\t\treturn\n\t}\n\t// remove Navigator, CSSStyleProperties (+ CSS2Properties Gecko 143 or lower)\n\tisProps = isProps.filter(x => !['CSS2Properties','CSSStyleProperties','Navigator'].includes(x)) \n\n\t// reduce to functions only\n\ttry {\n\t\tlet intPost = 0\n\t\tisProps.forEach(function(f){\n\t\t\tif (window[f] !== undefined) {\n\t\t\t\ttry {\n\t\t\t\t\tlet array = Object.getOwnPropertyNames(window[f].prototype)\n\t\t\t\t\t// ignore constructor only\n\t\t\t\t\tif (1 == array.length && 'constructor' == array[0]) {\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdata[f] = array\n\t\t\t\t\t\tif (isGecko && 'constructor' !== array[array.length -1]) {\n\t\t\t\t\t\t\toPost[f] = array.slice(array.indexOf('constructor')+1)\n\t\t\t\t\t\t\tintPost += oPost[f].length\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\t\t})\n\t\thash = mini(data); btn = addButton(18, METRIC, Object.keys(data).length)\n\n\t\t// tampered (or rather post constructor items)\n\t\tlet intKeys = Object.keys(oPost).length\n\t\tif (intKeys > 0) {\n\t\t\tlet postName = '_post_constructor'\n\t\t\tsDetail[isScope][METRIC + postName] = oPost\n\t\t\ttamperBtn = addButton(18, METRIC + postName, intKeys +'/'+ intPost)\n\t\t}\n\t\t// notation: safer vs standard doesn't seem to affect this\n\t\t\t// but we need to check webgl click to play\n\t\tif (isBBESR) {\n\t\t\t// hashes must be calculated on HTTPS not file schema\n\t\t\tlet oHashes = {\n\t\t\t\t// key: [standard, safer]\n\t\t\t\tMB : {\n\t\t\t\t\t'linux': ['c99980b4','12add862'],\n\t\t\t\t\t'mac': ['f862f015','df5f9ac3'],\n\t\t\t\t\t'windows': ['c99980b4','12add862']\n\t\t\t\t},\n\t\t\t\tTB : {\n\t\t\t\t\t'linux': ['0c9aaf28','cdde2f4c'],\n\t\t\t\t\t'mac': ['efef2c31','d2e0c655'],\n\t\t\t\t\t'windows': ['0c9aaf28', 'cdde2f4c']\n\t\t\t\t},\n\t\t\t}\n\t\t\tlet key = isTB ? 'TB' : 'MB'\n\t\t\tif (undefined !== oHashes[key][isOS]) {\n\t\t\t\tif (oHashes[key][isOS].includes(hash)) {notation = bb_green}\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(18, METRIC, hash, btn + tamperBtn, notation, data)\n\tlog_perf(18, METRIC, t0)\n\treturn\n}\n\nfunction get_window_prop(METRIC) {\n\t// BB: display only: wasm\n\tlet str, notation = isBB ? bb_slider_red : ''\n\ttry {\n\t\tstr = window.WebAssembly\n\t\tif (runST) {str = null}\n\t\tlet typeCheck = typeFn(str)\n\t\tif ('undefined' !== typeCheck && 'object' !== typeCheck) {throw zErrType + typeCheck}\n\t\tstr = ('object' === typeCheck) ? zE : zD\n\t\tif (isBB) {notation = str == zE ? bb_standard : bb_safer}\n\t} catch(e) {\n\t\tstr = log_error(18, METRIC, e)\n\t}\n\taddDisplay(18, METRIC, str,'', notation)\n\treturn\n}\n\nfunction get_window_props(METRIC) {\n\t/* https://github.com/abrahamjuliot/creepjs */\n\tlet t0 = nowFn(), iframe\n\tlet hash, btn='', data, dataLen, dataSorted, dataOriginal, notation = isBBESR ? bb_red : ''\n\tlet tamperHash = zNA, tamperBtn ='', aTampered =''\n\tlet oIndex = {}, isConsoleOpen = false, allowConsole = false, isAlert = false, skipAlert = false\n\tisProps = [] // reset: used to build function proprties\n\n\tlet id = 'iframe-window-version'\n\n\ttry {\n\t\t// create & append\n\t\tlet el = document.createElement('iframe')\n\t\tel.setAttribute('id', id)\n\t\tel.setAttribute('style', 'display: none')\n\t\t/* ToDo: investigate\n\t\tif ('blink' == isEngine) {\n\t\t\t// content scripts never run in frames like this\n\t\t\t// javascript:undefined, javascript:null as long as it's not a string\n\t\t\tel.setAttribute('src', 'javascript:false')\n\t\t}\n\t\t//*/\n\t\tif (!runSE) {document.body.appendChild(el)}\n\t\t// get props\n\t\tiframe = dom[id]\n\t\tlet contentWindow = iframe.contentWindow\n\t\t// data to manipulate\n\t\tdata = Object.getOwnPropertyNames(contentWindow)\n\t\tdataLen = data.length\n\t\t// original: useful for analysis\n\t\tdataOriginal = Object.getOwnPropertyNames(contentWindow)\n\t\tsDetail[isScope][METRIC +'_original'] = dataOriginal\n\t\t// sorted: we use this in function_props\n\t\tisProps = Object.getOwnPropertyNames(contentWindow)\n\t\tisProps.sort()\n\n\t\tif (isGecko) {\n\t\t\t// get index positions\n\t\t\tlet aIndex = ['Event','PageTransitionEvent','Performance','PerformanceTiming','console']\n\t\t\taIndex.forEach(function(item){let x = data.indexOf(item); oIndex[item] = [x, dataLen - x]})\n\t\t\toIndex['total_count'] = dataLen\n\t\t\t// all the properties that can be tampered with by NS/uBO\n\t\t\t\t// ultimately we move those present (sorted) to the end of the data so we can get a stable hash across security levels in Base Barowser\n\t\t\t\t// FF148+ 543435 changed things up\n\t\t\tlet isExpanded = isVer > 147\n\t\t\tlet aExpanded = ['PerformanceTiming','console','Promise','PageTransitionEvent','NodeList'] // nodelist from android\n\t\t\tlet aPossible = [\n\t\t\t\t'Audio','Blob','Crypto','CustomEvent','Element','Error','HTMLAudioElement','HTMLCanvasElement',\n\t\t\t\t'HTMLElement','HTMLFrameElement','HTMLHtmlElement','HTMLIFrameElement','HTMLImageElement',\n\t\t\t\t'HTMLMediaElement','HTMLObjectElement','HTMLVideoElement','Image','MediaSource','NodeList',\n\t\t\t\t'OffscreenCanvas','Promise','Proxy','SecurityPolicyViolationEvent','SharedWorker','String','URL',\n\t\t\t\t'WebAssembly','Worker','XMLHttpRequest','XMLHttpRequestEventTarget','decodeURI','decodeURIComponent',\n\t\t\t\t'encodeURI','encodeURIComponent','escape','unescape','webkitURL',\n\t\t\t]\n\t\t\tif (isExpanded) {\n\t\t\t\taPossible.push(\n\t\t\t\t\t'JSON','MutationObserver','WebSocket','XMLHttpRequest','XMLHttpRequestEventTarget', // 543435 uBO exposes these 5\n\t\t\t\t\t'Navigator', // NoScript aded this around 148alpha\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif (isSmart) {\n\t\t\t\t/* determine console state before we start messing around with the array\n\t\t\t\tFF147 and lower 'Event' and FF148+ 'console' is low=closed high=open\n\t\t\t\t- e.g. FF141 event moves from ~175 to ~935, FF145 from ~417 to ~944\n\t\t\t\t- e.g. FF148 console moves from ~241 to ~962\n\t\t\t\tSo checking if it is within 150 or so from the end is a loose ranged check that allows\n\t\t\t\tfor loads of tampered items. Unfortunately NS + webgl-click-to-play (i.e no exception)\n\t\t\t\tand probably other extensions break this rule, so we have to stick with the more rigid\n\t\t\t\tposition is off-by-one diff checks, and use isAlert to add a 'likely' qualifier\n\t\t\t\t*/\n\t\t\t\tlet threshold = dataLen - 150\n\t\t\t\tif (!isExpanded) {\n\t\t\t\t\tif (oIndex.Event[0] !== -1 && oIndex.Performance[0] !== -1) {\n\t\t\t\t\t\t// if event hasn't moved (no tampering) then you are definitely closed\n\t\t\t\t\t\tif (oIndex.Event[0] < threshold) {\n\t\t\t\t\t\t\tisConsoleOpen = false; skipAlert = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tisConsoleOpen = oIndex.Event[0] == oIndex.Performance[0] + 1; allowConsole = true // old method\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (oIndex.console[0] !== -1 && oIndex.PerformanceTiming[0] !== -1) {\n\t\t\t\t\t\tallowConsole = true\n\t\t\t\t\t\t// if console or PT haven't moved (no tampering) then you are definitely closed\n\t\t\t\t\t\tif (oIndex.PerformanceTiming[0] < threshold || oIndex.console[0] < threshold) {\n\t\t\t\t\t\t\tisConsoleOpen = false; skipAlert = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tisConsoleOpen = oIndex.console[0] == oIndex.PerformanceTiming[0] + 1 // new method\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// tampered: filter items for console open etc\n\t\t\t\t//    safer closed: Performance ... more items then Event\n\t\t\t\t// standard closed: Performance + no Event...\n\t\t\t\t//  BB/FF/ALL open: Performance then Event...\n\t\t\t\tif (runSL) {data.push('fake')}\n\t\t\t\taTampered = data.slice(oIndex.Performance[0] +1)\n\t\t\t\tlet aIgnore = ['Event','Location']\n\t\t\t\tif (isExpanded) {aIgnore = aIgnore.concat(aExpanded)}\n\t\t\t\taTampered = aTampered.filter(x => !aIgnore.includes(x))\n\t\t\t\tif (aTampered.length) {\n\t\t\t\t\taddDetail(METRIC +'_tampered', aTampered.sort())\n\t\t\t\t\ttamperBtn = addButton(18, METRIC +'_tampered', aTampered.length + ' tampered')\n\t\t\t\t\ttamperHash = mini(aTampered)\n\t\t\t\t} else {\n\t\t\t\t\taTampered = ''\n\t\t\t\t\ttamperHash = 'none'\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet aHas = aPossible.filter(x => data.includes(x))\n\t\t\taHas = dedupeArray(aHas)\n\t\t\taHas.sort()\n\t\t\t// always move all these to the end\n\t\t\t\t// this gives us a much more stable hash with and without NS tampering, but also allows false positives\n\t\t\tdata = data.filter(x => !aHas.includes(x))\n\t\t\tdata = data.concat(aHas)\n\n\t\t\tif (isSmart) {\n\t\t\t\t// now we check tampered items not in possible\n\t\t\t\tif (aTampered.length) {\n\t\t\t\t\tlet aTamperedNotInPossible = aTampered.filter(x => !aPossible.includes(x))\n\t\t\t\t\tif (aTamperedNotInPossible.length) {\n\t\t\t\t\t\tisAlert = true\n\t\t\t\t\t\t// don't record untrustworthy/lies - just collect tampered items\n\t\t\t\t\t\t\t// maintaining is too burdensome: also many extensions can add extra tampered items\n\t\t\t\t\t\t\t// e.g. AutocopySelection2Clipboard can trigger 'HTMLBodyElement','HTMLHeadElement','Selection'\n\t\t\t\t\t\tconsole.log(mini(aTamperedNotInPossible), aTamperedNotInPossible)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// notate console: mark as likely if additional tampering\n\t\t\t\tif (allowConsole && isDesktop) {\n\t\t\t\t\tlet strConsole = ' [devtools ' + (isAlert && !skipAlert ? ' likely ': '') + (isConsoleOpen ? 'open' : 'closed') +']'\n\t\t\t\t\taddDisplay(18, 'consolestatus', strConsole)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// move expected Performance, Event, Location to the end\n\t\t\t\t// these affect the order if console open and various tabs selected\n\t\t\tlet aCheck = ['Location','Performance','Event']\n\t\t\tif (isExpanded) {aCheck = aCheck.concat(aExpanded)}\n\t\t\tlet aItems = data.filter(x => aCheck.includes(x))\n\t\t\taItems.sort() // because an open console can change the order\n\t\t\tdata = data.filter(x => !aItems.includes(x))\n\t\t\tdata = data.concat(aItems)\n\t\t} else {\n\t\t\tdata.sort()\n\t\t}\n\t\thash = mini(data); btn = addButton(18, METRIC, data.length)\n\t\tif (isGecko) {\n\t\t\tbtn += addButton(18, METRIC +'_sorted', 'sorted')\n\t\t\t\t+ (isDesktop ? addButton(18, METRIC +'_original', 'original') : '')\n\t\t\t\t+ addButton(18, METRIC +'_index', 'index')\n\t\t\tsDetail[isScope][METRIC +'_sorted'] = isProps\n\t\t\tsDetail[isScope][METRIC +'_index'] = oIndex\n\t\t\t/* recored original order for analysis\n\t\t\tbtn += \n\t\t\t\n\t\t\t//*/\n\t\t} else {\n\t\t\tbtn += addButton(18, METRIC +'_original', 'original')\n\t\t}\n\t\t// health: BB only if ESR\n\t\tif (isBBESR) {\n\t\t\t// hashes are: standard (has WebAssembly) | safer (should be identical w/ and w/o webgl clicktoplay)\n\t\t\t// funfact: 1419501 (backported from FF144) radically altered the order of items in the unordered list\n\t\t\t// NOTE: hashes can not be computed via file schema as NS does weird shit now\n\t\t\tlet oHashes = {\n\t\t\t\tMB : {\n\t\t\t\t\t'linux': ['0a2537c8','3c3cf46a'], // 860, 859\n\t\t\t\t\t'mac': ['cb4fa24b','8be8b02d'], // 857, 856\n\t\t\t\t\t'windows': ['0a2537c8','3c3cf46a'] // 860, 859\n\t\t\t\t},\n\t\t\t\tTB : {\n\t\t\t\t\t'linux': ['f3fd6bb5','0698db17'], // 837, 836\n\t\t\t\t\t'mac': ['bd279b0a','27f5532c'], // 834, 833\n\t\t\t\t\t'windows': ['f3fd6bb5','0698db17'] // 837, 836\n\t\t\t\t},\n\t\t\t}\n\t\t\tlet key = isTB ? 'TB' : 'MB'\n\t\t\tif (undefined !== oHashes[key][isOS]) {\n\t\t\t\tif (oHashes[key][isOS].includes(hash)) {notation = bb_green}\n\t\t\t}\n\t\t}\n\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t\tif (isProps.length == 0) {isProps = e+''}\n\t}\n\tremoveElementFn(id)\n\taddBoth(18, METRIC +'_tampered', tamperHash, tamperBtn, '', aTampered)\n\tif (isAlert) {notation += sb +' *'+ sc}\n\taddBoth(18, METRIC, hash, btn, notation, data)\n\tlog_perf(18, METRIC, t0)\n\n\treturn\n}\n\nconst outputTiming = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('timing')) {return resolve()}\n\n\tif (!gRun) {return resolve()}\n\t/* other perf prefs are in window properties\n\t\tdom.enable_performance_observer: PerformanceObserver, PerformanceObserverEntryList\n\t\tdom.enable_performance_navigation_timing: PerformanceNavigationTiming\n\t\tdom.enable_event_timing: EventCounts, PerformanceEventTiming\n\t\tdom.performance.time_to_contentful_paint.enabled: ?\n\t\tdom.enable_performance: ?\n\t\tdom.enable_resource_timing: doesn't remove property PerformanceResourceTiming\n\t*/\n\tPromise.all([\n\t\tget_timing('timing_precision'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\nconst outputMisc = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('misc')) {return resolve()}\n\n\tif (runSL) {\n\t\taddProxyLie('Math.sin')\n\t\taddProxyLie('Math.log')\n\t}\n\tlet isMathTrigLies = check_mathLies('trig')\n\tlet isMathOtherLies = check_mathLies('other')\n\n\tlet notation = '', value = zNA\n\tif (isGecko) {\n\t\t// 1259822: FF74+ | 1965165: javascript.options.property_error_message_fix FF140+ default enabled\n\t\ttry {null.bar} catch(e) {\n\t\t\tif (isBB) {\n\t\t\t\tnotation = (e+'' == 'TypeError: can\\'t access property \"bar\" of null' ? bb_green : bb_red)\n\t\t\t}\n\t\t\tvalue = e.message\n\t\t}\n\t}\n\taddBoth(18, 'error_message_fix', value,'', notation)\n\n\tPromise.all([\n\t\tget_svg('svg_enabled'),\n\t\tget_math('math_trig', isMathTrigLies),\n\t\tget_math('math_other', isMathOtherLies),\n\t\tget_math_css('math_css'),\n\t\tget_component_shims('component_interfaces'),\n\t\tget_window_prop('wasm'),\n\t\tget_window_props('window_properties'),\n\t\tget_navigator_keys('navigator_keys'),\n\t\tget_webdriver('webdriver'),\n\t\tget_pdf('pdf'),\n\t\tget_speech_engines('speech_engines'),\n\t]).then(function(){\n\t\tPromise.all([\n\t\t\tget_window_functions('window_functions')\n\t\t]).then(function(){\n\t\t\treturn resolve()\n\t\t})\n\t})\n})\n\ncountJS(18)\n"
  },
  {
    "path": "js/prototypeLies.js",
    "content": "'use strict';\n\n/*\nhttps://github.com/abrahamjuliot/creepjs\n- https://abrahamjuliot.github.io/creepjs/tests/prototype.html\n- https://github.com/abrahamjuliot/creepjs/blob/master/docs/tests/prototype.js\n*/\n\nconst outputPrototypeLies = (isResize = false) => new Promise(resolve => {\n\tif (isResize) {return resolve()}\n\tsData[SECT98] = {}\n\tsData[SECT99] = []\n\tif (!isProtoProxy) {return resolve()}\n\tlet t0 = nowFn()\n\n\tconst getIframe = () => {\n\t\ttry {\n\t\t\tconst numberOfIframes = window.length\n\t\t\tconst frag = new DocumentFragment()\n\t\t\tconst div = document.createElement('div')\n\t\t\tfrag.appendChild(div)\n\t\t\tconst ghost = () => `\n\t\t\t\theight: 100vh;\n\t\t\t\twidth: 100vw;\n\t\t\t\tposition: absolute;\n\t\t\t\tleft:-10000px;\n\t\t\t\tvisibility: hidden;\n\t\t\t`\n\t\t\tdiv.innerHTML = `<div style=\"${ghost()}\"><iframe></iframe></div>`\n\t\t\tdocument.body.appendChild(frag)\n\t\t\tconst iframeWindow = window[numberOfIframes]\n\t\t\treturn {\n\t\t\t\tiframeWindow,\n\t\t\t\tdiv\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tiframeWindow: window,\n\t\t\t\tdiv: undefined\n\t\t\t}\n\t\t}\n\t}\n\tconst {\n\t\tiframeWindow,\n\t\tdiv: iframeContainerDiv\n\t} = getIframe()\n\n\tconst getPrototypeLies = scope => {\n\t\t// engine\n\t\tconst IS_BLINK = 'blink' == isEngine\n\t\tconst IS_GECKO = isGecko\n\t\tconst IS_WEBKIT = 'webkit' == isEngine\n\n\t\tconst getRandomValues = () => (\n\t\t\tString.fromCharCode(Math.random() * 26 + 97) +\n\t\t\tMath.random().toString(36).slice(-7)\n\t\t)\n\t\tconst randomId = getRandomValues()\n\t\t// Lie Tests\n\t\t// object constructor descriptor should return undefined properties\n\t\tconst getUndefinedValueLie = (obj, name) => {\n\t\t\tconst objName = obj.name\n\t\t\tconst objNameUncapitalized = self[objName.charAt(0).toLowerCase() + objName.slice(1)]\n\t\t\tconst hasInvalidValue = !!objNameUncapitalized && (\n\t\t\t\ttypeof Object.getOwnPropertyDescriptor(objNameUncapitalized, name) != 'undefined' ||\n\t\t\t\ttypeof Reflect.getOwnPropertyDescriptor(objNameUncapitalized, name) != 'undefined'\n\t\t\t)\n\t\t\treturn hasInvalidValue\n\t\t}\n\n\t\t// accessing the property from the prototype should throw a TypeError\n\t\tconst getIllegalTypeErrorLie = (obj, name) => {\n\t\t\tconst proto = obj.prototype\n\t\t\ttry {\n\t\t\t\tproto[name]\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\treturn error.constructor.name != 'TypeError' ? true : false\n\t\t\t}\n\t\t}\n\n\t\t// calling the interface prototype on the function should throw a TypeError\n\t\tconst getCallInterfaceTypeErrorLie = (apiFunction, proto) => {\n\t\t\ttry {\n\t\t\t\tnew apiFunction()\n\t\t\t\tapiFunction.call(proto)\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\treturn error.constructor.name != 'TypeError'\n\t\t\t}\n\t\t}\n\n\t\t// applying the interface prototype on the function should throw a TypeError\n\t\tconst getApplyInterfaceTypeErrorLie = (apiFunction, proto) => {\n\t\t\ttry {\n\t\t\t\tnew apiFunction()\n\t\t\t\tapiFunction.apply(proto)\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\treturn error.constructor.name != 'TypeError'\n\t\t\t}\n\t\t}\n\n\t\t// creating a new instance of the function should throw a TypeError\n\t\tconst getNewInstanceTypeErrorLie = apiFunction => {\n\t\t\ttry {\n\t\t\t\tnew apiFunction()\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\treturn error.constructor.name != 'TypeError'\n\t\t\t}\n\t\t}\n\n\t\t// extending the function on a fake class should throw a TypeError and message \"not a constructor\"\n\t\tconst getClassExtendsTypeErrorLie = apiFunction => {\n\t\t\ttry {\n\t\t\t\tconst shouldExitInSafari13 = (\n\t\t\t\t\t/version\\/13/i.test((navigator || {}).userAgent) && IS_WEBKIT\n\t\t\t\t)\n\t\t\t\tif (shouldExitInSafari13) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\t// begin tests\n\t\t\t\tclass Fake extends apiFunction { }\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\t// Native has TypeError and 'not a constructor' message in FF & Chrome\n\t\t\t\treturn (\n\t\t\t\t\terror.constructor.name != 'TypeError' ||\n\t\t\t\t\t!/not a constructor/i.test(error.message)\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// setting prototype to null and converting to a string should throw a TypeError\n\t\tconst getNullConversionTypeErrorLie = apiFunction => {\n\t\t\tconst nativeProto = Object.getPrototypeOf(apiFunction)\n\t\t\ttry {\n\t\t\t\tObject.setPrototypeOf(apiFunction, null) + ''\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\treturn error.constructor.name != 'TypeError'\n\t\t\t} finally {\n\t\t\t\t// restore proto\n\t\t\t\tObject.setPrototypeOf(apiFunction, nativeProto)\n\t\t\t}\n\t\t}\n\n\t\t// toString() and toString.toString() should return a native string in all frames\n\t\tconst getToStringLie = (apiFunction, name, scope) => {\n\t\t\t/*\n\t\t\tAccepted strings:\n\t\t\t'function name() { [native code] }'\n\t\t\t'function name() {\\n    [native code]\\n}'\n\t\t\t'function get name() { [native code] }'\n\t\t\t'function get name() {\\n    [native code]\\n}'\n\t\t\t'function () { [native code] }'\n\t\t\t`function () {\\n    [native code]\\n}`\n\t\t\t*/\n\t\t\tlet scopeToString, scopeToStringToString\n\t\t\ttry {\n\t\t\t\tscopeToString = scope.Function.prototype.toString.call(apiFunction)\n\t\t\t} catch (e) { }\n\t\t\ttry {\n\t\t\t\tscopeToStringToString = scope.Function.prototype.toString.call(apiFunction.toString)\n\t\t\t} catch (e) { }\n\n\t\t\tconst apiFunctionToString = (\n\t\t\t\tscopeToString ?\n\t\t\t\t\tscopeToString :\n\t\t\t\t\t\tapiFunction.toString()\n\t\t\t)\n\t\t\tconst apiFunctionToStringToString = (\n\t\t\t\tscopeToStringToString ?\n\t\t\t\t\tscopeToStringToString :\n\t\t\t\t\t\tapiFunction.toString.toString()\n\t\t\t)\n\t\t\tconst trust = name => ({\n\t\t\t\t[`function ${name}() { [native code] }`]: true,\n\t\t\t\t[`function get ${name}() { [native code] }`]: true,\n\t\t\t\t[`function () { [native code] }`]: true,\n\t\t\t\t[`function ${name}() {${'\\n'}    [native code]${'\\n'}}`]: true,\n\t\t\t\t[`function get ${name}() {${'\\n'}    [native code]${'\\n'}}`]: true,\n\t\t\t\t[`function () {${'\\n'}    [native code]${'\\n'}}`]: true\n\t\t\t})\n\t\t\treturn (\n\t\t\t\t!trust(name)[apiFunctionToString] ||\n\t\t\t\t!trust('toString')[apiFunctionToStringToString]\n\t\t\t)\n\t\t}\n\n\t\t// \"prototype\" in function should not exist\n\t\tconst getPrototypeInFunctionLie = apiFunction => 'prototype' in apiFunction\n\n\t\t// \"arguments\", \"caller\", \"prototype\", \"toString\" should not exist in descriptor\n\t\tconst getDescriptorLie = apiFunction => {\n\t\t\tconst hasInvalidDescriptor = (\n\t\t\t\tObject.getOwnPropertyDescriptor(apiFunction, 'arguments') ||\n\t\t\t\tReflect.getOwnPropertyDescriptor(apiFunction, 'arguments') ||\n\t\t\t\tObject.getOwnPropertyDescriptor(apiFunction, 'caller') ||\n\t\t\t\tReflect.getOwnPropertyDescriptor(apiFunction, 'caller') ||\n\t\t\t\tObject.getOwnPropertyDescriptor(apiFunction, 'prototype') ||\n\t\t\t\tReflect.getOwnPropertyDescriptor(apiFunction, 'prototype') ||\n\t\t\t\tObject.getOwnPropertyDescriptor(apiFunction, 'toString') ||\n\t\t\t\tReflect.getOwnPropertyDescriptor(apiFunction, 'toString')\n\t\t\t)\n\t\t\treturn hasInvalidDescriptor\n\t\t}\n\n\t\t// \"arguments\", \"caller\", \"prototype\", \"toString\" should not exist as own property\n\t\tconst getOwnPropertyLie = apiFunction => {\n\t\t\tconst hasInvalidOwnProperty = (\n\t\t\t\tapiFunction.hasOwnProperty('arguments') ||\n\t\t\t\tapiFunction.hasOwnProperty('caller') ||\n\t\t\t\tapiFunction.hasOwnProperty('prototype') ||\n\t\t\t\tapiFunction.hasOwnProperty('toString')\n\t\t\t)\n\t\t\treturn hasInvalidOwnProperty\n\t\t}\n\n\t\t// descriptor keys should only contain \"name\" and \"length\"\n\t\tconst getDescriptorKeysLie = apiFunction => {\n\t\t\tconst descriptorKeys = Object.keys(Object.getOwnPropertyDescriptors(apiFunction))\n\t\t\tconst hasInvalidKeys = '' + descriptorKeys != 'length,name' && '' + descriptorKeys != 'name,length'\n\t\t\treturn hasInvalidKeys\n\t\t}\n\n\t\t// own property names should only contain \"name\" and \"length\"\n\t\tconst getOwnPropertyNamesLie = apiFunction => {\n\t\t\tconst ownPropertyNames = Object.getOwnPropertyNames(apiFunction)\n\t\t\tconst hasInvalidNames = !(\n\t\t\t\t'' + ownPropertyNames == 'length,name' ||\n\t\t\t\t'' + ownPropertyNames == 'name,length'\n\t\t\t)\n\t\t\treturn hasInvalidNames\n\t\t}\n\n\t\t// own keys names should only contain \"name\" and \"length\"\n\t\tconst getOwnKeysLie = apiFunction => {\n\t\t\tconst ownKeys = Reflect.ownKeys(apiFunction)\n\t\t\tconst hasInvalidKeys = !(\n\t\t\t\t'' + ownKeys == 'length,name' ||\n\t\t\t\t'' + ownKeys == 'name,length'\n\t\t\t)\n\t\t\treturn hasInvalidKeys\n\t\t}\n\n\t\t// calling toString() on an object created from the function should throw a TypeError\n\t\tconst getNewObjectToStringTypeErrorLie = apiFunction => {\n\t\t\ttry {\n\t\t\t\tconst you = () => Object.create(apiFunction).toString()\n\t\t\t\tconst cant = () => you()\n\t\t\t\tconst hide = () => cant()\n\t\t\t\thide()\n\t\t\t\t// error must throw\n\t\t\t\treturn true\n\t\t\t} catch (error) {\n\t\t\t\tconst stackLines = error.stack.split('\\n')\n\t\t\t\tconst validScope = !/at Object\\.apply/.test(stackLines[1])\n\t\t\t\t// Stack must be valid\n\t\t\t\tconst validStackSize = (\n\t\t\t\t\terror.constructor.name == 'TypeError' && stackLines.length >= 5\n\t\t\t\t)\n\t\t\t\t// Chromium must throw error 'at Function.toString'... and not 'at Object.apply'\n\t\t\t\tif (validStackSize && IS_BLINK && (\n\t\t\t\t\t!validScope ||\n\t\t\t\t\t!/at Function\\.toString/.test(stackLines[1]) ||\n\t\t\t\t\t!/at you/.test(stackLines[2]) ||\n\t\t\t\t\t!/at cant/.test(stackLines[3]) ||\n\t\t\t\t\t!/at hide/.test(stackLines[4])\n\t\t\t\t)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn !validStackSize\n\t\t\t}\n\t\t}\n\n\t\t/* Proxy Detection */\n\t\t// arguments or caller should not throw 'incompatible Proxy' TypeError\n\t\tconst tryIncompatibleProxy = fn => {\n\t\t\ttry {\n\t\t\t\tfn()\n\t\t\t\treturn true // failed to throw\n\t\t\t} catch (error) {\n\t\t\t\treturn (\n\t\t\t\t\terror.constructor.name != 'TypeError' || (IS_GECKO && /incompatible\\sProxy/.test(error.message))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst getIncompatibleProxyTypeErrorLie = apiFunction => {\n\t\t\treturn (\n\t\t\t\ttryIncompatibleProxy(() => apiFunction.arguments) ||\n\t\t\t\ttryIncompatibleProxy(() => apiFunction.caller)\n\t\t\t)\n\t\t}\n\t\tconst getToStringIncompatibleProxyTypeErrorLie = apiFunction => {\n\t\t\treturn (\n\t\t\t\ttryIncompatibleProxy(() => apiFunction.toString.arguments) ||\n\t\t\t\ttryIncompatibleProxy(() => apiFunction.toString.caller)\n\t\t\t)\n\t\t}\n\n\t\t// checking proxy instanceof proxy should throw a valid TypeError\n\t\tconst getInstanceofCheckLie = apiFunction => {\n\t\t\tconst proxy = new Proxy(apiFunction, {})\n\t\t\tif (!IS_BLINK) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst hasValidStack = (error, type = 'Function') => {\n\t\t\t\tconst { message, name, stack } = error\n\t\t\t\tconst validName = name == 'TypeError'\n\t\t\t\tconst validMessage = message == `Function has non-object prototype 'undefined' in instanceof check`\n\t\t\t\tconst targetStackLine = ((stack || '').split('\\n') || [])[1]\n\t\t\t\tconst validStackLine = (\n\t\t\t\t\ttargetStackLine.startsWith(`    at ${type}.[Symbol.hasInstance]`) ||\n\t\t\t\t\ttargetStackLine.startsWith('    at [Symbol.hasInstance]') // Chrome 102\n\t\t\t\t)\n\t\t\t\treturn validName && validMessage && validStackLine\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tproxy instanceof proxy\n\t\t\t\treturn true // failed to throw\n\t\t\t}\n\t\t\tcatch (error) {\n\t\t\t\t// expect Proxy.[Symbol.hasInstance]\n\t\t\t\tif (!hasValidStack(error, 'Proxy')) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tapiFunction instanceof apiFunction\n\t\t\t\t\treturn true // failed to throw\n\t\t\t\t}\n\t\t\t\tcatch (error) {\n\t\t\t\t\t// expect Function.[Symbol.hasInstance]\n\t\t\t\t\treturn !hasValidStack(error)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// defining properties should not throw an error\n\t\tconst getDefinePropertiesLie = (apiFunction) => {\n\t\t\tif (!IS_BLINK) {\n\t\t\t\treturn false // chrome only test\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tObject.defineProperty(apiFunction, '', { configurable: true })+''\n\t\t\t\tReflect.deleteProperty(apiFunction, '')\n\t\t\t\treturn false\n\t\t\t} catch (error) {\n\t\t\t\treturn true // failed at Error\n\t\t\t}\n\t\t}\n\n\t\t// setPrototypeOf error tests\n\t\tconst spawnError = (apiFunction, method) => {\n\t\t\tif (method == 'setPrototypeOf') {\n\t\t\t\treturn Object.setPrototypeOf(apiFunction, Object.create(apiFunction)) + ''\n\t\t\t} else {\n\t\t\t\tapiFunction.__proto__ = apiFunction\n\t\t\t\treturn apiFunction++\n\t\t\t}\n\t\t}\n\t\tconst hasValidError = error => {\n\t\t\tconst { name, message } = error\n\t\t\tconst hasRangeError = name == 'RangeError'\n\t\t\tconst hasInternalError = name == 'InternalError'\n\t\t\tconst chromeLie = IS_BLINK && (\n\t\t\t\tmessage != `Maximum call stack size exceeded` || !hasRangeError\n\t\t\t)\n\t\t\tconst firefoxLie = IS_GECKO && (\n\t\t\t\tmessage != `too much recursion` || !hasInternalError\n\t\t\t)\n\t\t\treturn (hasRangeError || hasInternalError) && !(chromeLie || firefoxLie)\n\t\t}\n\n\t\tconst getTooMuchRecursionLie = ({ apiFunction, method = 'setPrototypeOf' }) => {\n\t\t\tconst nativeProto = Object.getPrototypeOf(apiFunction)\n\t\t\tconst proxy = new Proxy(apiFunction, {})\n\t\t\ttry {\n\t\t\t\tspawnError(proxy, method)\n\t\t\t\treturn true // failed to throw\n\t\t\t} catch (error) {\n\t\t\t\treturn !hasValidError(error)\n\t\t\t} finally {\n\t\t\t\tObject.setPrototypeOf(proxy, nativeProto) // restore\n\t\t\t}\n\t\t}\n\n\t\tconst getChainCycleLie = ({ apiFunction, method = 'setPrototypeOf' }) => {\n\t\t\tconst nativeProto = Object.getPrototypeOf(apiFunction)\n\t\t\ttry {\n\t\t\t\tspawnError(apiFunction, method)\n\t\t\t\treturn true // failed to throw\n\t\t\t} catch (error) {\n\t\t\t\tconst { name, message, stack } = error\n\t\t\t\tconst targetStackLine = ((stack || '').split('\\n') || [])[1]\n\t\t\t\tconst hasTypeError = name == 'TypeError'\n\t\t\t\tconst chromeLie = IS_BLINK && (\n\t\t\t\t\tmessage != `Cyclic __proto__ value` || (\n\t\t\t\t\t\tmethod == '__proto__' && (\n\t\t\t\t\t\t\t!targetStackLine.startsWith(`    at set __proto__ (<anonymous>)`) && // Chrome ~117+\n\t\t\t\t\t\t\t!targetStackLine.startsWith(`    at set __proto__ [as __proto__]`) && // Chrome 102+\n\t\t\t\t\t\t\t!targetStackLine.startsWith(`    at Function.set __proto__ [as __proto__]`)\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\tconst firefoxLie = IS_GECKO && (\n\t\t\t\t\tmessage != `can't set prototype: it would cause a prototype chain cycle`\n\t\t\t\t)\n\t\t\t\tif (!hasTypeError || chromeLie || firefoxLie) {\n\t\t\t\t\treturn true // failed Error\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tObject.setPrototypeOf(apiFunction, nativeProto) // restore\n\t\t\t}\n\t\t}\n\n\t\tconst getReflectSetProtoLie = ({ apiFunction, randomId }) => {\n\t\t\tif (!randomId) {\n\t\t\t\trandomId = getRandomValues()\n\t\t\t}\n\t\t\tconst nativeProto = Object.getPrototypeOf(apiFunction)\n\t\t\ttry {\n\t\t\t\tif (Reflect.setPrototypeOf(apiFunction, Object.create(apiFunction))) {\n\t\t\t\t\treturn true // failed value (expected false)\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\trandomId in apiFunction\n\t\t\t\t\t\treturn false\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn true // failed at Error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treturn true // failed at Error\n\t\t\t} finally {\n\t\t\t\tObject.setPrototypeOf(apiFunction, nativeProto) // restore\n\t\t\t}\n\t\t}\n\n\t\tconst getReflectSetProtoProxyLie = ({ apiFunction, randomId }) => {\n\t\t\tif (!randomId) {\n\t\t\t\trandomId = getRandomValues()\n\t\t\t}\n\t\t\tconst nativeProto = Object.getPrototypeOf(apiFunction)\n\t\t\tconst proxy = new Proxy(apiFunction, {})\n\t\t\ttry {\n\t\t\t\tif (!Reflect.setPrototypeOf(proxy, Object.create(proxy))) {\n\t\t\t\t\treturn true // failed value (expected true)\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\trandomId in apiFunction\n\t\t\t\t\t\treturn true // failed to throw\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn !hasValidError(error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treturn true // failed at Error\n\t\t\t} finally {\n\t\t\t\tObject.setPrototypeOf(proxy, nativeProto) // restore\n\t\t\t}\n\t\t}\n\n\t\t// API Function Test\n\t\tconst getLies = ({ apiFunction, proto, obj = null, lieProps }) => {\n\t\t\tif ('function' != typeof apiFunction) {\n\t\t\t\treturn {\n\t\t\t\t\tlied: false,\n\t\t\t\t\tlieTypes: []\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst name = apiFunction.name.replace(/get\\s/, '')\n\t\t\tlet lies = {\n\t\t\t\t// custom lie string names\n\t\t\t\t[`a: failed illegal error`]: obj ? getIllegalTypeErrorLie(obj, name) : false,\n\t\t\t\t[`b: failed undefined properties`]: obj ? getUndefinedValueLie(obj, name) : false,\n\t\t\t\t[`c: failed call interface error`]: getCallInterfaceTypeErrorLie(apiFunction, proto),\n\t\t\t\t[`d: failed apply interface error`]: getApplyInterfaceTypeErrorLie(apiFunction, proto),\n\t\t\t\t[`e: failed new instance error`]: getNewInstanceTypeErrorLie(apiFunction),\n\t\t\t\t[`f: failed class extends error`]: getClassExtendsTypeErrorLie(apiFunction),\n\t\t\t\t[`g: failed null conversion error`]: getNullConversionTypeErrorLie(apiFunction),\n\t\t\t\t[`h: failed toString`]: getToStringLie(apiFunction, name, scope),\n\t\t\t\t[`i: failed \"prototype\" in function`]: getPrototypeInFunctionLie(apiFunction),\n\t\t\t\t[`j: failed descriptor`]: getDescriptorLie(apiFunction),\n\t\t\t\t[`k: failed own property`]: getOwnPropertyLie(apiFunction),\n\t\t\t\t[`l: failed descriptor keys`]: getDescriptorKeysLie(apiFunction),\n\t\t\t\t[`m: failed own property names`]: getOwnPropertyNamesLie(apiFunction),\n\t\t\t\t[`n: failed own keys names`]: getOwnKeysLie(apiFunction),\n\t\t\t\t[`o: failed object toString error`]: getNewObjectToStringTypeErrorLie(apiFunction),\n\t\t\t\t// Proxy Detection\n\t\t\t\t[`p: failed at incompatible proxy error`]: getIncompatibleProxyTypeErrorLie(apiFunction),\n\t\t\t\t[`q: failed at toString incompatible proxy error`]: getToStringIncompatibleProxyTypeErrorLie(apiFunction),\n\t\t\t\t[`r: failed at too much recursion error`]: getChainCycleLie({ apiFunction })\n\t\t\t}\n\t\t\t// conditionally use advanced detection\n\t\t\tconst detectProxies = (\n\t\t\t\tname == 'toString' || !!lieProps['Function.toString']\n\t\t\t)\n\t\t\tif (detectProxies) {\n\t\t\t\tlies = Object.assign(\n\t\t\t\t\t{},\n\t\t\t\t\tlies,\n\t\t\t\t\t// Advanced Proxy Detection\n\t\t\t\t\t{\n\t\t\t\t\t\t[`s: failed at too much recursion __proto__ error`]: getChainCycleLie({ apiFunction, method: '__proto__' }),\n\t\t\t\t\t\t[`t: failed at chain cycle error`]: getTooMuchRecursionLie({ apiFunction }),\n\t\t\t\t\t\t[`u: failed at chain cycle __proto__ error`]: getTooMuchRecursionLie({ apiFunction, method: '__proto__' }),\n\t\t\t\t\t\t[`v: failed at reflect set proto`]: getReflectSetProtoLie({ apiFunction, randomId }),\n\t\t\t\t\t\t[`w: failed at reflect set proto proxy`]: getReflectSetProtoProxyLie({ apiFunction, randomId }),\n\t\t\t\t\t\t[`x: failed at instanceof check error`]: getInstanceofCheckLie(apiFunction),\n\t\t\t\t\t\t[`y: failed at define properties`]: getDefinePropertiesLie(apiFunction)\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst lieTypes = Object.keys(lies).filter(key => !!lies[key])\n\t\t\treturn {\n\t\t\t\tlied: lieTypes.length,\n\t\t\t\tlieTypes\n\t\t\t}\n\t\t}\n\n\t\t// Lie Detector\n\t\tconst createLieDetector = () => {\n\t\t\tconst isSupported = obj => typeof obj != 'undefined' && !!obj\n\t\t\tconst props = {} // lie list and detail\n\t\t\tlet propsSearched = [] // list of properties searched\n\t\t\treturn {\n\t\t\t\tgetProps: () => props,\n\t\t\t\tgetPropsSearched: () => propsSearched,\n\t\t\t\tsearchLies: (fn, {\n\t\t\t\t\ttarget = [],\n\t\t\t\t\tignore = []\n\t\t\t\t} = {}) => {\n\t\t\t\t\tlet obj\n\t\t\t\t\t// check if api is blocked or not supported\n\t\t\t\t\ttry {\n\t\t\t\t\t\tobj = fn()\n\t\t\t\t\t\tif (!isSupported(obj)) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tconst interfaceObject = !!obj.prototype ? obj.prototype : obj\n\t\t\t\t\t;[...new Set([\n\t\t\t\t\t\t...Object.getOwnPropertyNames(interfaceObject),\n\t\t\t\t\t\t...Object.keys(interfaceObject) // backup\n\t\t\t\t\t])].sort().forEach(name => {\n\t\t\t\t\t\tconst skip = (\n\t\t\t\t\t\t\tname == 'constructor' ||\n\t\t\t\t\t\t\t(target.length && !new Set(target).has(name)) ||\n\t\t\t\t\t\t\t(ignore.length && new Set(ignore).has(name))\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (skip) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst objectNameString = /\\s(.+)\\]/\n\t\t\t\t\t\tconst apiName = `${\n\t\t\t\t\t\t\tobj.name ? obj.name : objectNameString.test(obj) ? objectNameString.exec(obj)[1] : undefined\n\t\t\t\t\t\t\t}.${name}`\n\t\t\t\t\t\tpropsSearched.push(apiName)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst proto = obj.prototype ? obj.prototype : obj\n\t\t\t\t\t\t\tlet res // response from getLies\n\n\t\t\t\t\t\t\t// search if function\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst apiFunction = proto[name] // may trigger TypeError\n\t\t\t\t\t\t\t\tif ('function' == typeof apiFunction) {\n\t\t\t\t\t\t\t\t\tres = getLies({\n\t\t\t\t\t\t\t\t\t\tapiFunction: proto[name],\n\t\t\t\t\t\t\t\t\t\tproto,\n\t\t\t\t\t\t\t\t\t\tlieProps: props\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\tif (res.lied) {\n\t\t\t\t\t\t\t\t\t\treturn (props[apiName] = res.lieTypes)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// since there is no TypeError and the typeof is not a function,\n\t\t\t\t\t\t\t\t// handle invalid values and ignore name, length, and constants\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tname != 'name' &&\n\t\t\t\t\t\t\t\t\tname != 'length' &&\n\t\t\t\t\t\t\t\t\tname[0] !== name[0].toUpperCase()) {\n\t\t\t\t\t\t\t\t\tconst lie = [`z: failed descriptor.value undefined`]\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\tprops[apiName] = lie\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) { }\n\t\t\t\t\t\t\t// else search getter function\n\t\t\t\t\t\t\tconst getterFunction = Object.getOwnPropertyDescriptor(proto, name).get\n\t\t\t\t\t\t\tres = getLies({\n\t\t\t\t\t\t\t\tapiFunction: getterFunction,\n\t\t\t\t\t\t\t\tproto,\n\t\t\t\t\t\t\t\tobj,\n\t\t\t\t\t\t\t\tlieProps: props\n\t\t\t\t\t\t\t}) // send the obj for special tests\n\n\t\t\t\t\t\t\tif (res.lied) {\n\t\t\t\t\t\t\t\treturn (props[apiName] = res.lieTypes)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst lie = `aa: failed prototype test execution`\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\tprops[apiName] = [lie]\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst lieDetector = createLieDetector()\n\t\tconst {\n\t\t\tsearchLies\n\t\t} = lieDetector\n\n\t\t// search lies: remove target to search all properties\n\t\t// test Function.toString first to determine the depth of the search\n\t\tsearchLies(() => Function, {\n\t\t\ttarget: [\n\t\t\t\t'toString',\n\t\t\t],\n\t\t\tignore: [\n\t\t\t\t'caller',\n\t\t\t\t'arguments'\n\t\t\t]\n\t\t})\n\t\t// other APIs\n\t\tsearchLies(() => AnalyserNode)\n\t\tsearchLies(() => AudioBuffer, {\n\t\t\ttarget: [\n\t\t\t\t'copyFromChannel',\n\t\t\t\t'getChannelData'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => BiquadFilterNode, {\n\t\t\ttarget: [\n\t\t\t\t'getFrequencyResponse'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => CanvasRenderingContext2D, {\n\t\t\ttarget: [\n\t\t\t\t'getImageData',\n\t\t\t\t'getLineDash',\n\t\t\t\t'isPointInPath',\n\t\t\t\t'isPointInStroke',\n\t\t\t\t'measureText',\n\t\t\t\t'quadraticCurveTo',\n\t\t\t\t'font'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => CSSStyleDeclaration, {\n\t\t\ttarget: [\n\t\t\t\t'removeProperty',\n\t\t\t\t'setProperty'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => CSS2Properties, { // Gecko 143 or lower\n\t\t\ttarget: [\n\t\t\t\t'setProperty'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => CSSStyleProperties, { // Gecko 144+\n\t\t\ttarget: [\n\t\t\t\t'setProperty'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Date, {\n\t\t\ttarget: [\n\t\t\t\t'getDate',\n\t\t\t\t'getDay',\n\t\t\t\t'getFullYear',\n\t\t\t\t'getHours',\n\t\t\t\t'getMinutes',\n\t\t\t\t'getMonth',\n\t\t\t\t'getTime',\n\t\t\t\t'getTimezoneOffset',\n\t\t\t\t'setDate',\n\t\t\t\t'setFullYear',\n\t\t\t\t'setHours',\n\t\t\t\t'setMilliseconds',\n\t\t\t\t'setMonth',\n\t\t\t\t'setSeconds',\n\t\t\t\t'setTime',\n\t\t\t\t'toDateString',\n\t\t\t\t'toJSON',\n\t\t\t\t'toLocaleDateString',\n\t\t\t\t'toLocaleString',\n\t\t\t\t'toLocaleTimeString',\n\t\t\t\t'toString',\n\t\t\t\t'toTimeString',\n\t\t\t\t'valueOf'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Intl.DateTimeFormat, {\n\t\t\ttarget: [\n\t\t\t\t'format',\n\t\t\t\t'formatRange',\n\t\t\t\t'formatToParts',\n\t\t\t\t'resolvedOptions'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Document, {\n\t\t\ttarget: [\n\t\t\t\t'adoptedStyleSheets',\n\t\t\t\t'createElement',\n\t\t\t\t'createElementNS',\n\t\t\t\t'getElementById',\n\t\t\t\t'getElementsByClassName',\n\t\t\t\t'getElementsByName',\n\t\t\t\t'getElementsByTagName',\n\t\t\t\t'getElementsByTagNameNS',\n\t\t\t\t'referrer',\n\t\t\t\t'styleSheets',\n\t\t\t\t'write',\n\t\t\t\t'writeln'\n\t\t\t],\n\t\t\tignore: [\n\t\t\t\t// Firefox returns undefined on getIllegalTypeErrorLie test\n\t\t\t\t'onreadystatechange',\n\t\t\t\t'onmouseenter',\n\t\t\t\t'onmouseleave'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => DOMRect)\n\t\tsearchLies(() => DOMRectReadOnly)\n\t\tsearchLies(() => Element, {\n\t\t\ttarget: [\n\t\t\t\t'append',\n\t\t\t\t'appendChild',\n\t\t\t\t'attachShadow',\n\t\t\t\t'getBoundingClientRect',\n\t\t\t\t'getClientRects',\n\t\t\t\t'insertAdjacentElement',\n\t\t\t\t'insertAdjacentHTML',\n\t\t\t\t'insertAdjacentText',\n\t\t\t\t'insertBefore',\n\t\t\t\t'prepend',\n\t\t\t\t'replaceChild',\n\t\t\t\t'replaceWith',\n\t\t\t\t'setAttribute'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => FontFace, {\n\t\t\ttarget: [\n\t\t\t\t'family',\n\t\t\t\t'load',\n\t\t\t\t'status'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => HTMLCanvasElement)\n\t\tsearchLies(() => HTMLElement, {\n\t\t\ttarget: [\n\t\t\t\t'clientHeight',\n\t\t\t\t'clientWidth',\n\t\t\t\t'offsetHeight',\n\t\t\t\t'offsetWidth',\n\t\t\t\t'scrollHeight',\n\t\t\t\t'scrollWidth'\n\t\t\t],\n\t\t\tignore: [\n\t\t\t\t// Firefox returns undefined on getIllegalTypeErrorLie test\n\t\t\t\t'onmouseenter',\n\t\t\t\t'onmouseleave'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => HTMLIFrameElement, {\n\t\t\ttarget: [\n\t\t\t\t'contentDocument',\n\t\t\t\t'contentWindow',\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => IntersectionObserverEntry, {\n\t\t\ttarget: [\n\t\t\t\t'boundingClientRect',\n\t\t\t\t'intersectionRect',\n\t\t\t\t'rootBounds'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Math, {\n\t\t\ttarget: [\n\t\t\t\t'acos',\n\t\t\t\t'acosh',\n\t\t\t\t'asinh',\n\t\t\t\t'atan',\n\t\t\t\t'atan2',\n\t\t\t\t'atanh',\n\t\t\t\t'cbrt',\n\t\t\t\t'cos',\n\t\t\t\t'cosh',\n\t\t\t\t'exp',\n\t\t\t\t'expm1',\n\t\t\t\t'log',\n\t\t\t\t'log10',\n\t\t\t\t'log1p',\n\t\t\t\t'pow',\n\t\t\t\t'sin',\n\t\t\t\t'sinh',\n\t\t\t\t'sqrt',\n\t\t\t\t'tan',\n\t\t\t\t'tanh'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => MediaDevices, {\n\t\t\ttarget: [\n\t\t\t\t'enumerateDevices',\n\t\t\t\t'getDisplayMedia',\n\t\t\t\t'getUserMedia'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Navigator, {\n\t\t\ttarget: [\n\t\t\t\t'appCodeName',\n\t\t\t\t'appName',\n\t\t\t\t'appVersion',\n\t\t\t\t'buildID',\n\t\t\t\t'connection',\n\t\t\t\t'deviceMemory',\n\t\t\t\t'getBattery',\n\t\t\t\t'getGamepads',\n\t\t\t\t'getVRDisplays',\n\t\t\t\t'globalPrivacyControl',\n\t\t\t\t'hardwareConcurrency',\n\t\t\t\t'language',\n\t\t\t\t'languages',\n\t\t\t\t'maxTouchPoints',\n\t\t\t\t'mimeTypes',\n\t\t\t\t'oscpu',\n\t\t\t\t'pdfViewerEnabled',\n\t\t\t\t'platform',\n\t\t\t\t'plugins',\n\t\t\t\t'product',\n\t\t\t\t'productSub',\n\t\t\t\t'sendBeacon',\n\t\t\t\t'serviceWorker',\n\t\t\t\t'userAgent',\n\t\t\t\t'userAgentData',\n\t\t\t\t'vendor',\n\t\t\t\t'vendorSub',\n\t\t\t\t'webdriver',\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Node, {\n\t\t\ttarget: [\n\t\t\t\t'appendChild',\n\t\t\t\t'insertBefore',\n\t\t\t\t'replaceChild'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => OffscreenCanvas, {\n\t\t\ttarget: [\n\t\t\t\t'convertToBlob',\n\t\t\t\t'getContext'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => OffscreenCanvasRenderingContext2D, {\n\t\t\ttarget: [\n\t\t\t\t'getImageData',\n\t\t\t\t'getLineDash',\n\t\t\t\t'isPointInPath',\n\t\t\t\t'isPointInStroke',\n\t\t\t\t'measureText',\n\t\t\t\t'quadraticCurveTo',\n\t\t\t\t'font'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Range, {\n\t\t\ttarget: [\n\t\t\t\t'getBoundingClientRect',\n\t\t\t\t'getClientRects',\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Intl.RelativeTimeFormat, {\n\t\t\ttarget: [\n\t\t\t\t'resolvedOptions'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => Screen)\n\t\tsearchLies(() => speechSynthesis, {\n\t\t\ttarget: [\n\t\t\t\t'getVoices'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => StorageManager, {\n\t\t\ttarget: [\n\t\t\t\t'estimate',\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => String, {\n\t\t\ttarget: [\n\t\t\t\t'fromCodePoint'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => SVGRect)\n\t\tsearchLies(() => TextMetrics)\n\t\tsearchLies(() => WebGLRenderingContext, {\n\t\t\ttarget: [\n\t\t\t\t'bufferData',\n\t\t\t\t'getParameter',\n\t\t\t\t'readPixels'\n\t\t\t]\n\t\t})\n\t\tsearchLies(() => WebGL2RenderingContext, {\n\t\t\ttarget: [\n\t\t\t\t'bufferData',\n\t\t\t\t'getParameter',\n\t\t\t\t'readPixels'\n\t\t\t]\n\t\t})\n\t\t/* potential targets:\n\t\t\tRTCPeerConnection\n\t\t\tPlugin\n\t\t\tPluginArray\n\t\t\tMimeType\n\t\t\tMimeTypeArray\n\t\t\tWorker\n\t\t\tHistory\n\t\t*/\n\n\t\t// disregard Function.prototype.toString lies to filter direct API tampering\n\t\tconst getCountOfNonFunctionToStringLies = x => !x ? x : x.filter(x => !/o:|q:/.test(x)).length\n\n\t\t// return lies list and detail\n\t\tconst props = lieDetector.getProps()\n\t\tconst propsSearched = lieDetector.getPropsSearched()\n\t\treturn {\n\t\t\tlieList: Object.keys(props).sort(),\n\t\t\tlieDetail: props,\n\t\t\tlieCount: Object.keys(props).reduce((acc, key) => acc + props[key].length, 0),\n\t\t\tpropsSearched,\n\t\t\t// filter out lies on Function.prototype.toString\n\t\t\ttamperingList: (props => {\n\t\t\t\treturn Object.keys(props).filter(key => {\n\t\t\t\t\tconst totalTamperingLies = getCountOfNonFunctionToStringLies(props[key])\n\t\t\t\t\tif (!totalTamperingLies) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t})(props)\n\t\t}\n\t}\n\n\t// start\n\tconst {\n\t\tlieList,\n\t\tlieDetail,\n\t\tlieCount,\n\t\tpropsSearched,\n\t\ttamperingList\n\t} = getPrototypeLies(iframeWindow) // execute and destructure the list and detail\n\tif (iframeContainerDiv) {\n\t\tiframeContainerDiv.parentNode.removeChild(iframeContainerDiv)\n\t}\n\n\t// navigator\n\t\t// prototype items that are not navigator\n\t\t// this is lie 'b: failed undefined properties'? but here we cover all navigator keys\n\ttry {\n\t\tlet aNav = []\n\t\tfor (const key in navigator) {aNav.push(key)}\n\t\tlet aProto = Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype))\n\t\taProto = aProto.filter(x => !['constructor'].includes(x)) // ignore constructor\n\t\tlet aNotInNav = aProto.filter(x => !aNav.includes(x)) //\n\t\t//let aNotInProto = aNav.filter(x => !aProto.includes(x)) // this would catch made up shit?\n\t\t//console.log(aNav, aProto, aNotInNav, aNotInProto)\n\t\taNotInNav.forEach(function(item) {\n\t\t\titem = 'Navigator.'+ item\n\t\t\tif (lieDetail[item] == undefined) {lieDetail[item] = []}\n\t\t\tlieDetail[item].push('zz: failed getOwnPropertyDescriptors')\n\t\t\tif (!tamperingList.includes(item)) {tamperingList.push(item)}\n\t\t})\n\t} catch(e) {console.log(e)}\n\n\t// sData\n\tsData[SECT98] = {}\n\tfor (const k of Object.keys(lieDetail).sort()) {sData[SECT98][k] = lieDetail[k]}\n\tsData[SECT99] = tamperingList.sort()\n\tif (!gRun) {return resolve()}\n\n\t// gData\n\tgData[SECT99] = sData[SECT99]\n\tgData[SECT98] = {}\n\tif (Object.keys(sData[SECT98]).length) {\n\t\tlet newObj = {}\n\t\tfor (const k of Object.keys(sData[SECT98])) {newObj[k] = sData[SECT98][k]}\n\t\tgData[SECT98] = newObj\n\t}\n\tgData[SECT97] = propsSearched.sort()\n\n\tlog_perf(SECTP, SECT98 +\"/\"+ SECT99, t0)\n\treturn resolve()\n})\n\ncountJS(SECTP)\n"
  },
  {
    "path": "js/region.js",
    "content": "'use strict';\n\n/* HEADERS */\nfunction get_nav_connection(METRIC) {\n\tlet hash, btn ='', data ='', notation = default_red\n\ttry {\n\t\thash = navigator.connection\n\t\tif (runST) {hash = null} else if (runSI) {hash = {}} else if (runSL) {addProxyLie('Navigator.'+ METRIC)}\n\t\tif (undefined === hash) {\n\t\t\t\thash = hash+''; notation = default_green\n\t\t} else {\n\t\t\tlet typeCheck = typeFn(hash, true)\n\t\t\tif ('object' !== typeCheck) {throw zErrType + typeFn(hash)}\n\t\t\tlet expected = '[object NetworkInformation]'\n\t\t\tif (hash+'' !== expected) {throw zErrInvalid + 'expected '+ expected +': got '+ hash}\n\t\t\t// https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation\n\t\t\tlet keyTypes = {\n\t\t\t\t// gecko only\n\t\t\t\taddEventListener: 'function',\n\t\t\t\tdispatchEvent: 'function',\n\t\t\t\tontypechange: 'null',\n\t\t\t\tremoveEventListener: 'function',\n\t\t\t\ttype: 'string',\n\t\t\t\t// also in blink\n\t\t\t\tdownlink: 'number',\n\t\t\t\tdownlinkMax: 'null',\n\t\t\t\teffectiveType: 'string',\n\t\t\t\tonchange: 'null',\n\t\t\t\trtt: 'number',\n\t\t\t\tsaveData: 'boolean',\n\t\t\t\twhen: 'function',\n\t\t\t}\n\t\t\tlet oGood = {\n\t\t\t\teffectiveType: ['slow-2g','2g','3g','4g'],\n\t\t\t\ttype: ['bluetooth','cellular','ethernet','none','wifi','wimax','other','unknown']\n\t\t\t}\n\t\t\tlet oTemp = {}\n\t\t\tfor (let k in hash) {\n\t\t\t\ttry {\n\t\t\t\t\tlet x = navigator.connection[k]\n\t\t\t\t\tif (runSI) {x = undefined}\n\t\t\t\t\t// type check\n\t\t\t\t\tlet typeCheck = typeFn(x), expectedType = keyTypes[k]\n\t\t\t\t\tif (typeCheck !== expectedType) {\n\t\t\t\t\t\tlet isInvalid = true\n\t\t\t\t\t\t// https://groups.google.com/a/chromium.org/g/blink-dev/c/tU_Hqqytx8g/m/HTJebzVHBAAJ\n\t\t\t\t\t\t// \"WiFi on Android reports Infinity for downlinkMax as Chrome recently dropped the required permission to get Wifi linkSpeed\n\t\t\t\t\t\tif ('blink' == isEngine && 'downlinkMax' == k && 'Infinity' == typeCheck) {isInvalid = false}\n\t\t\t\t\t\tif (isInvalid) {throw zErrInvalid +'expected '+ expectedType +': got '+ typeCheck}\n\t\t\t\t\t}\n\t\t\t\t\t// valid string\n\t\t\t\t\tif ('type' == k || 'effectiveType' == k) {\n\t\t\t\t\t\tif (runSI) {x = '1g'}\n\t\t\t\t\t\tlet aGood = oGood[k]\n\t\t\t\t\t\tif (!aGood.includes(x)) {throw zErrInvalid + ': got ' + x}\n\t\t\t\t\t\tif ('slow-2g' == x) {x = '2g'} // treat slow-2g as 2g\n\t\t\t\t\t}\n\t\t\t\t\t// cleanup\n\t\t\t\t\tif ('function' === typeCheck) {x = typeCheck}\n\t\t\t\t\tif (null == x || Infinity == x) {x += ''} // record null/Infinity as strings | note: 'null'/'Infinity' are caught as errors\n\t\t\t\t\t// stability\n\t\t\t\t\tif ('rtt' == k) {x = zNA} else if ('downlink' == k) {\tx = Math.floor(x)}\n\t\t\t\t\toTemp[k] = x\n\t\t\t\t} catch(e) {\n\t\t\t\t\toTemp[k] = zErr\n\t\t\t\t\tlog_error(5, METRIC +'_'+ k, e)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdata = {}\n\t\t\tfor (const k of Object.keys(oTemp).sort()) {data[k] = oTemp[k]}\n\t\t\thash = mini(data); btn = addButton(5, METRIC)\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(5, METRIC, hash, btn, notation, data, isProxyLie('Navigator.'+ METRIC))\n\treturn\n}\n\nfunction get_nav_dnt(METRIC) {\n\t// ignored\n\t\t// navigator.msDoNotTrack = IE9 + 10\n\t\t// window.doNotTrack = IE11 and Edge 16- and old Safari\n\n\t// gecko: this is an expected property\n\t// nonGecko\n\t\t// blink: string vs null: i.e a string of \"null\" will be an error\n\t\t// webkit: undefined vs null: i.e a string of \"undefined\" will be an error\n\tlet hash, data ='', expectedType = isGecko ? 'string' : 'undefined'\n\ttry {\n\t\thash = navigator[METRIC]\n\t\tif (runST) {hash = 1} else if (runSI) {hash = '2'}\n\t\tlet typeCheck = typeFn(hash)\n\t\tif ('blink' == isEngine) {\n\t\t\t// blink can be pnly be \"1\" or null\n\t\t\tif ('1' !== hash && null !== hash) {throw zErrInvalid + 'expected 1 or null: got ' + hash}\n\t\t} else {\n\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (isGecko) {\n\t\t\t\tif ('1' !== hash && 'unspecified' !== hash) {throw zErrInvalid + 'expected 1 or unspecified: got ' + hash}\n\t\t\t}\n\t\t}\n\t\thash += '' // gecko is a string, otherwise we can only be null/undefined, so convert to a string\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(5, METRIC, hash,'','', data)\n\treturn\n}\n\nfunction get_nav_online(METRIC) {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine\n\tlet value, data ='', notation = rfp_red\n\ttry {\n\t\tvalue = navigator.onLine\n\t\tif (runST) {value = undefined}\n\t\tlet typeCheck = typeFn(value)\n\t\t// we expect blink, gecko, webkit to return a boolean\n\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\tif (value) {notation = rfp_green} // 1975851: FF142+\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(5, METRIC, value,'', notation, data)\n\treturn\n}\n\nfunction get_nav_gpc(METRIC) {\n\t// GPC: 1670058\n\t\t// privacy.globalprivacycontrol.functionality.enabled = navigator\n\t\t// privacy.globalprivacycontrol.enabled = true/false\n\t// FF120+ desktop (?android): gpc enabled: false but true in pb mode\n\n\t// ToDo: FF144+? 1983296 functionality pref deprecated\n\tlet hash, data ='', notation = isBB ? default_red : ''\n\ttry {\n\t\thash = navigator[METRIC]\n\t\tif (runST) {hash = null} else if (runSL) {addProxyLie('Navigator.'+ METRIC)}\n\t\tif (undefined === hash) {\n\t\t\thash = hash+''\n\t\t} else {\n\t\t\tlet typeCheck = typeFn(hash)\n\t\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t// expected boolean but could be true or false, so don't notate\n\t\t\t// except BB where we expect true due to pb mode\n\t\t\tif (isBB && true === hash) {notation = default_green}\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(5, METRIC, hash,'', notation, data, isProxyLie('Navigator.'+ METRIC))\n\treturn\n}\n\n/* REGION */\n\nfunction add_microperf_intl(m, countC, tsub0, isIntl) {\n\tif (undefined == oIntlLocalePerf[m]) {oIntlLocalePerf[m] = {}}\n\tif (isIntl) {oIntlLocalePerf[m]['constructors'] = countC}\n\tlet subname = (isIntl ? 'intl' : 'string')\n\toIntlLocalePerf[m][subname] = nowFn() - tsub0\n}\n\nfunction set_isLanguageSmart() {\n\t// set once: ignore android for now\n\tif (!gLoad || !isSmart && !isSmartDataMode || !isDesktop) {return}\n\n\t// BB if ESR\n\t\t// resource://gre/res/multilocale.txt\n\tisLanguageSmart = isBB\n\n\tconst en = 'en-US, en'\n\tlanguagesSupported = {\n\t\t// language = existing key | languages = key + value[0] | locale = key unless value[1] !== undefined\n\t\t'ar': [en],\n\t\t'be': [en],\n\t\t'bg': [en],\n\t\t'ca': [en],\n\t\t'cs': ['sk, '+ en],\n\t\t'da': [en],\n\t\t'de': [en],\n\t\t'el-GR': ['el, '+ en, 'el'],\n\t\t'en-US': ['en'],\n\t\t'es-ES': ['es, '+ en],\n\t\t'fa-IR': ['fa, '+ en, 'fa'],\n\t\t'fi-FI': ['fi, '+ en, 'fi'],\n\t\t'fr': ['fr-FR, '+ en],\n\t\t'ga-IE': ['ga, en-IE, en-GB, '+ en],\n\t\t'he': ['he-IL, '+ en],\n\t\t'hu-HU': ['hu, '+ en, 'hu'],\n\t\t'id': [en],\n\t\t'is': [en],\n\t\t'it-IT': ['it, '+ en, 'it'],\n\t\t'ja': [en],\n\t\t'ka-GE': ['ka, '+ en, 'ka'],\n\t\t'ko-KR': ['ko, '+ en, 'ko'],\n\t\t'lt': [en +', ru, pl'],\n\t\t'mk-MK': ['mk, '+ en, 'mk'],\n\t\t'ms': [en],\n\t\t'my': ['en-GB, en'],\n\t\t'nb-NO': ['nb, no-NO, no, nn-NO, nn, '+ en],\n\t\t'nl': [en],\n\t\t'pl': [en],\n\t\t'pt-BR': ['pt, '+ en],\n\t\t'pt-PT': ['pt, en, en-US'],\n\t\t'ro-RO': ['ro, en-US, en-GB, en', 'ro'],\n\t\t'ru-RU': ['ru, '+ en, 'ru'],\n\t\t'sq': ['sq-AL, '+ en],\n\t\t'sv-SE': ['sv, '+ en],\n\t\t'th': [en],\n\t\t'tr-TR': ['tr, '+ en, 'tr'],\n\t\t'uk-UA': ['uk, '+ en, 'uk'],\n\t\t'vi-VN': ['vi, '+ en, 'vi'],\n\t\t'zh-CN': ['zh, zh-TW, zh-HK, '+ en, 'zh-Hans-CN'],\n\t\t'zh-TW': ['zh, '+ en, 'zh-Hant-TW'],\n\t}\n\t// these are current stable BB hashes since last checked\n\t\t// note: upstream ESR seems to pick up stable l10n changes\n\t// last checked TB15.06\n\n\t// NOTE: these hashes are only designed to work with BB ESR (stable) and FF en-US\n\t// we can use an array if necessary so as to not get false positives in FF\n\t// using an array (test is .includes) means we're not super tight on our health check i.e per isVer\n\n\tlet xsEN = '6cc5a8b4'\n\tlocalesSupported = {\n\t\t// v hashes are with localized NumberRangeOver/Underflow\n\t\t// c = css | m = media | v = verification | x = xml | xs = xslt | xsort = xslt sort\n\t\t// r = reporting (if blank we use the english hash)\n\t\t'ar': {   m: '1f9a06e3', v: '7262bcc6', x: '71982b47', xs: '5cee96ec', xsort: '352c4e34', r: ''},\n\t\t'be': {   m: '076d68e6', v: '4edeafab', x: '42583d22', xs: 'c28dba41', xsort: '74053574', r: '6de2f0b7'},\n\t\t'bg': {   m: '2da6c02e', v: 'ce892c88', x: 'c4f06f98', xs: 'b964cfe0', xsort: '7d747674', r: ''},\n\t\t'ca': {   m: 'd856d812', v: '6b3bb3d8', x: '77a62a49', xs: 'ad2e7060', xsort: xsEN,       r: ''},\n\t\t'cs': {   m: 'c92accb0', v: 'de3ab0ad', x: '81c91d49', xs: '7c010d86', xsort: 'a7ddfef4', r: '39eac55d'},\n\t\t'da': {   m: '39169214', v: '479797a1', x: 'a30818e8', xs: '8654b0f1', xsort: '88f55cfa', r: '266a324b'},\n\t\t'de': {   m: '298d11c6', v: 'f9e2eae6', x: 'c1ce6571', xs: '5ab0cbb9', xsort: xsEN,       r: '8c11ce07'},\n\t\t'el': {   m: '39712e09', v: 'fb391308', x: '493f7225', xs: '33a4584c', xsort: 'cae41bf4', r: '71191fa1'},\n\t\t'en-US': {m: '05c30936', v: '41310558', x: '544e1ae8', xs: 'bcb04adc', xsort: xsEN,       r: ['8c954475','4a9afc22']},\n\t\t'es-ES': {m: '96b78cbd', v: '97c3f5a9', x: 'ed807f70', xs: 'd9a6e947', xsort: '32fce55a', r: '7fc42e10'},\n\t\t'fa': {   m: '6648d919', v: '8ef57409', x: '1ed34bca', xs: '47876cea', xsort: 'ff0f7334', r: ''},\n\t\t'fi': {   m: '82d079c7', v: '3e29e6e7', x: '859efc32', xs: '67b222db', xsort: '26f7a3f8', r: ''},\n\t\t'fr': {   m: '024d0fce', v: '34e28fa2', x: '1d2050d3', xs: 'f09eacaa', xsort: xsEN,       r: '7ebbf4b3'},\n\t\t'ga-IE': {m: '97fca229', v: '2bf1321d', x: 'd3af2cd8', xs: '021b6b57', xsort: xsEN,       r: ''},\n\t\t'he': {   m: 'cdde832b', v: 'e47dbb82', x: 'c7274a3e', xs: '35d1f35c', xsort: 'a0fcc2b4', r: 'cadeed05'},\n\t\t'hu': {   m: 'db7366e6', v: 'b72d316d', x: 'e4f85168', xs: 'ffae360e', xsort: '2fe650b4', r: '4b0d44d0'},\n\t\t'id': {   m: '1e275882', v: '5dda18f3', x: 'a70cd23c', xs: '26e6e4fb', xsort: xsEN,       r: '93c32eba'},\n\t\t'is': {   m: '204c8f73', v: '6bbe7a8f', x: 'edb8b212', xs: '3d227a5a', xsort: '93b575f8', r: 'ce6ce0a6'},\n\t\t'it': {   m: '716e7242', v: '3b781f09', x: 'c567f479', xs: '7d0eba5c', xsort: xsEN,       r: '514ebfe9'},\n\t\t'ja': {   m: 'ab56d7cb', v: '48645d06', x: 'a58f6165', xs: 'a0fa98ad', xsort: '22ec9486', r: '64e8a6a1'},\n\t\t'ka': {   m: '6961b7e4', v: '40feb44f', x: '765afcb4', xs: '460ae32f', xsort: '7a65b6b4', r: '778bc94a'},\n\t\t'ko': {   m: 'c758b027', v: 'd3b54047', x: '1235e26d', xs: '1d314216', xsort: '9c39494c', r: 'c81a1027'},\n\t\t'lt': {   m: 'c36fbafb', v: 'd5f9b95d', x: 'b0e8a3bc', xs: 'ca28b814', xsort: 'f26c6ff4', r: 'e58fc47e'},\n\t\t'mk': {   m: '78274f1b', v: '333aae58', x: 'b6020ec1', xs: '36e30ccb', xsort: 'f9e81474', r: ''},\n\t\t'ms': {   m: '3e26c6be', v: '9dadbc64', x: '15e6148f', xs: '421d606a', xsort: xsEN,       r: '411351e5'},\n\t\t'my': {   m: '939f2013', v: '43cc3aa3', x: 'a6571ec7', xs: 'bfc734fe', xsort: 'fbfb1d8c', r: ''},\n\t\t'nb-NO': {m: '1d496fea', v: '84ce54eb', x: 'e0d34e04', xs: '19e8e2a5', xsort: '88f55cfa', r: 'b32738cf'},\n\t\t'nl': {   m: 'e1d3b281', v: '326cbfd2', x: 'caef95fc', xs: '8a47ae1a', xsort: xsEN,       r: '2a725fb7'},\n\t\t'pl': {   m: '0bd88e98', v: '95ad4851', x: '2a45177d', xs: '4740c17a', xsort: '01902794', r: '2678c528'},\n\t\t'pt-BR': {m: '39835e93', v: 'de2c3569', x: '68f80c66', xs: 'e710618b', xsort: xsEN,       r: '21ee14c6'},\n\t\t'pt-PT': {m: '6ae9a13a', v: 'b21f3984', x: '0aa2a309', xs: '025ca23b', xsort: xsEN,       r: 'e6a7d6ff'},\n\t\t'ro': {   m: '3e321768', v: 'd72a350b', x: 'a9da3416', xs: '61b5e498', xsort: '2a01a4d8', r: '9b675c63'},\n\t\t'ru': {   m: '8e9b7945', v: '2391fbec', x: '26f663da', xs: '4445d36a', xsort: '7d747674', r: '0bf2516d'},\n\t\t'sq': {   m: '91943e67', v: 'e0259277', x: '4e0bbdcd', xs: '569be7bb', xsort: 'f45c6af8', r: 'a75d2c6f'},\n\t\t'sv-SE': {m: 'bc792ce2', v: 'd9d7828b', x: '4af3452f', xs: '701cd8c7', xsort: '1ca25322', r: '3ed80374'},\n\t\t'th': {   m: 'a32d70a7', v: '07358a87', x: '2a04071a', xs: '7e968207', xsort: 'a0bff3b4', r: '65ade427'},\n\t\t'tr': {   m: '4217ef80', v: '5048d312', x: '55daef93', xs: 'd8e92945', xsort: 'e9fda72a', r: 'd62d2c72'},\n\t\t'uk': {   m: '4bea2a13', v: '0163f51d', x: '4f817ea3', xs: 'e62ccf4f', xsort: 'ae65fe74', r: '2049852a'},\n\t\t'vi': {   m: 'bba6c980', v: 'b8137d59', x: '80da1efb', xs: '959b2e31', xsort: '2a01a4d8', r: 'ef6841d7'},\n\t\t'zh-Hans-CN': {m: '550ea53e', v: '0e58f82a', x: '536abb21', xs: '1feed45e', xsort: '42d5bac6', r: '135f1290'},\n\t\t'zh-Hant-TW': {m: '66b515a4', v: '8e4cfa0e', x: '9ad3338c', xs: '8aa6bfbf', xsort: '6d106412', r: '62cefab7'},\n\t}\n\t// mac: japanese languages are the same but the locale is 'ja-JP' not 'ja'\n\tif ('mac' == isOS) {\n\t\tlanguagesSupported['ja'].push('ja-JP')\n\t\tlocalesSupported['ja-JP'] = localesSupported.ja\n\t\tdelete localesSupported['ja']\n\t}\n\tif (isMB) {\n\t\t// 22 of 38 supported\n\t\tlet notSupported = [\n\t\t\t// lang\n\t\t\t'be','bg','ca','cs','el-GR','ga-IE','he','hu-HU','id','is','ka-GE','lt','mk-MK','ms','pt-PT','ro-RO','sq','uk-UA','vi-VN',\n\t\t\t// + locales\n\t\t\t'el','hu','ka','mk','ro','uk','vi',\n\t\t]\n\t\tnotSupported.forEach(function(key){\n\t\t\tdelete languagesSupported[key]\n\t\t\tdelete localesSupported[key]\n\t\t})\n\t}\n\treturn\n}\n\nfunction set_oIntlDate() {\n\tlet d = oIntlDates\n\toIntlDate = {\n\t\tdate_timestyle : {\n\t\t\t\"default\": {\n\t\t\t\t'full_medium': [d.JanA, d.JanB, d.JulA, d.JulB, d.SepA, d.SepB],\n\t\t\t\t'medium_long': [d.JanA, d.JanB, d.JulA, d.JulB, d.NovA, d.NovB],\n\t\t\t\t'short_full': [d.SepA, d.SepB],\n\t\t\t},\n\t\t\t'ethiopic': {'full': [d.JanA], 'medium': [d.JanA]},\n\t\t\t'japanese': {'medium': [d.SepA, d.NovA]}, // NovA required for blink (147)\n\t\t},\n\t}\n\t// build keys\n\tfor (const k of Object.keys(oIntlDate)) {\n\t\toIntlDateKeys[k] = []\n\t\tfor (const j of Object.keys(oIntlDate[k]).sort()) {oIntlDateKeys[k].push(j)}\n\t}\n}\n\nfunction set_oIntlDates() {\n\t/*\n\n\tintl.dates\n\tall dates (days/months/am-pm) must account for timezones\n\t\t- that way everyone covers the specific targets (e.g. am, friday, single digit day, etc)\n\t\t- timezone entropy is in the actual timezonename (we're confirming that here)\n\twe use UTC so we can check the original date hasn't been altered\n\t\t- which means we will nd up testing more dates to cover specific days\n\t\t- at this point AM/PM doesn't seem to be a factor\n\t\t- this makes the PoC's max entropy easier to verify\n\ttimezones can be 14 hrs less or 12 hrs more but (IIUIC) our selected dates aren't hiting those instances\n\t\twhere it exceeds ±12 (or I lucked out) and we end up only needing two identical times on subsequent days\n\ttests/PoCs need to cover all possible combos of locales x timezonenames because those\n\t\tare the TWO variables that I cannot control (oIntlLocale only have ONE variable: locale)\n\t\tand not all locales handle timezonenames to the same degree: e.g.\n\t\t- America/Los_Angles has 343 possible outcomes, Europe/Vatican has 344: this is because\n\t\t- pt-ao + pt-ch vary for the vatican but not for los angeles\n\t  - tl;dr: locale + timezonename PoCs cover a range\n\n\tintl.locale\n\tall dates (days/months/am-pm) must be timezone resistent: we are checking locale only\n\t\t- reported timezonename (and locale) is tested see oIntlDate section\n\t\t- thus we use UTC time so everyone uses the exact same dates, and then we pass\n\t\tUTC as the timezone so nothing shifts, preserving our specific datetimes\n\tthe tests that expose day/time are datetimeformat's relatedYear + components + timezonename | and dayperiods\n\t*/\n\n\toIntlDates = {\n\t\t//intl.dates\n\t\tJanA: new Date('2024-01-04T04:12:34.000Z'),\n\t\tJanB: new Date('2024-01-05T04:12:34.000Z'),\n\t\tJulA: new Date('2024-07-04T14:12:34.000Z'),\n\t\tJulB: new Date('2024-07-05T14:12:34.000Z'),\n\t\tSepA: new Date('2024-09-03T04:12:34.000Z'),\n\t\tSepB: new Date('2024-09-04T04:12:34.000Z'),\n\t\tNovA: new Date('2024-11-03T14:12:34.000Z'),\n\t\tNovB: new Date('2024-11-04T14:12:34.000Z'),\n\t\t//intl.locale\n\t\t// fractionalSecondDigits: we only ever reveal the seconds\n\t\tFSD: new Date('2023-06-10T01:12:34.567Z'),\n\t\t// month (x4) + year (xJan): we only ever reveal the month or year\n\t\tJan: new Date('2023-01-15T00:00:00.000Z'),\n\t\tJun: new Date('2023-06-15T00:00:00.000Z'),\n\t\tSep: new Date('2023-09-15T00:00:00.000Z'),\n\t\tNov: new Date('2023-11-15T00:00:00.000Z'),\n\t\t// days (x2) + hrs (xFri) + era: expose day/hr\n\t\tWed: new Date('2023-01-18T01:00:00.000Z'), // doubles as hour 1\n\t\tFri: new Date('2023-01-20T13:00:00.000Z'), // doubles as hour 13\n\t\tEra: new Date('-000002-01-15T01:00:00.000Z'),\n\t\t// relatedyear exposes day\n\t\tRY1: new Date('-000002-01-15T01:00:00.000Z'),\n\t\tRY2: new Date('2023-01-15T00:00:00.000Z'),\n\t\t// timezonename exposes day but we pass the timezone itself so it's relative (i.e stable per timezone)\n\t\tTZN1: new Date('2019-08-15T00:00:00.000Z'),\n\t\t// dayperiod: exposes hr\n\t\tDP8: new Date('2019-01-30T08:00:00Z'),\n\t\tDP12: new Date('2019-01-30T12:00:00Z'),\n\t\tDP15: new Date('2019-01-30T15:00:00Z'),\n\t\tDP18: new Date('2019-01-30T18:00:00Z'),\n\t\tDP22: new Date('2019-01-30T22:00:00Z'),\n\t}\n}\n\nfunction set_oIntlLocale() {\n\tlet d = oIntlDates\n\tlet tzLG = {'longGeneric': [d.TZN1]}, tzSG = {'shortGeneric': [d.TZN1]}\n\tlet unitN = {'narrow': [1]}, unitL = {'long': [1]}, unitB = {'long': [1], 'narrow': [1]}\n\tlet curAN = {\"accounting\": [-1000], \"name\": [-1]},\n\t\tcurN = {\"name\": [-1]},\n\t\tcurS = {\"symbol\": [1000]}\n\n\toIntlLocale = {\n\t\tcollation: {\n\t\t\tsearch: ['\\u0107','\\u0109','\\u1ED9','\\u00F6'],\n\t\t\tsort: [\n\t\t\t\t'A','a','aa','ch','ez','kz','ng','ph','ts','tt','y','\\u00E2','\\u00E4','\\u00E7\\a','\\u00EB','\\u00ED','\\u00EE','\\u00F0',\n\t\t\t\t'\\u00F1','\\u00F6','\\u0107','\\u0109','\\u0137\\a','\\u0144','\\u0149','\\u01FB','\\u025B','\\u03B1','\\u040E','\\u0439','\\u0453',\n\t\t\t\t'\\u0457','\\u04F0','\\u0503','\\u0561','\\u05EA','\\u0627','\\u0649','\\u06C6','\\u06C7','\\u06CC','\\u06FD','\\u0934','\\u0935',\n\t\t\t\t'\\u09A4','\\u09CE','\\u0A85','\\u0B05','\\u0B85','\\u0C05','\\u0C85','\\u0D85','\\u0E24','\\u0E9A','\\u10350','\\u10D0','\\u1208',\n\t\t\t\t'\\u1780','\\u1820','\\u1D95','\\u1DD9','\\u1ED9','\\u1EE3','\\u311A','\\u3147','\\u4E2D','\\uA647','\\uFB4A'\n\t\t\t]\n\t\t},\n\t\t// DTF\n\t\t'datetimeformat.components': {\n\t\t\tera: {\n\t\t\t\t// we need to control the date part so toLocaleString matches\n\t\t\t\t'long': [{era: 'long', year: 'numeric', month: 'numeric', day: 'numeric'}, [d.Era]]\n\t\t\t},\n\t\t\tfractionalSecondDigits: {\n\t\t\t\t'1': [{minute: 'numeric', second: 'numeric', fractionalSecondDigits: 1}, [d.FSD]]\n\t\t\t},\n\t\t\thour: {\n\t\t\t\t'numeric': [{hour: 'numeric'}, [d.Wed]],\n\t\t\t},\n\t\t\thourCycle: {\n\t\t\t\t'h11-2-digit': [{hour: '2-digit', hourCycle: 'h11'}, [d.Wed]]\n\t\t\t},\n\t\t\tmonth: {\n\t\t\t\t'narrow': [{month: 'narrow'}, [d.Nov] ],\n\t\t\t\t'short': [{month: 'short'}, [d.Jan, d.Jun, d.Sep, d.Nov]],\n\t\t\t},\n\t\t\tweekday: {\n\t\t\t\t'long': [{weekday: 'long'}, [d.Wed, d.Fri]],\n\t\t\t\t'narrow': [{weekday: 'narrow'}, [d.Wed, d.Fri]],\n\t\t\t\t'short': [{weekday: 'short'}, [d.Fri]],\n\t\t\t},\n\t\t\tyear: {\n\t\t\t\t'2-digit': [{year: \"2-digit\"}, [d.Jan]]\n\t\t\t},\n\t\t},\n\t\t'datetimeformat.dayperiod': {\n\t\t\t'long': [d.DP8, d.DP22],\n\t\t\t'narrow': [d.DP8, d.DP15],\n\t\t\t'short': [d.DP12, d.DP15, d.DP18]\n\t\t},\n\t\t'datetimeformat.listformat': {\n\t\t\t'narrow': ['conjunction','disjunction','unit'],\n\t\t\t'short': ['unit'],\n\t\t\t'long': ['conjunction','unit']\n\t\t},\n\t\t'datetimeformat.relatedyear': {\n\t\t\t// these are all long\n\t\t\tbuddhist: [d.RY1],\n\t\t\tchinese: [d.RY1],\n\t\t\tcoptic: [d.RY2],\n\t\t\t'default': [d.RY1],\n\t\t\tgregory: [d.RY1],\n\t\t\thebrew: [d.RY1],\n\t\t\tindian: [d.RY1],\n\t\t\t'islamic-tbla': [d.RY1],\n\t\t\tjapanese: [d.RY1, d.RY2],\n\t\t\troc: [d.RY1],\n\t\t},\n\t\t'datetimeformat.timezonename': {\n\t\t\t'Africa/Douala': tzLG,\n\t\t\t'America/Montevideo': tzSG,\n\t\t\t'America/Winnipeg': tzLG,\n\t\t\t'Asia/Hong_Kong': tzSG,\n\t\t\t'Asia/Seoul': tzLG,\n\t\t\t'Europe/London': tzSG,\n\t\t\t'Asia/Muscat': tzSG,\n\t\t},\n\t\t// DN\n\t\tdisplaynames: {\n\t\t\tcalendar: {\n\t\t\t\t'short': ['chinese','dangi','ethiopic','gregory','islamic-tbla','islamic-umalqura','japanese','roc'],\n\t\t\t},\n\t\t\tcurrency: {'long': ['JPY','NIO','SEK','SZL','TZS','XAF']},\n\t\t\tdatetimefield: {\n\t\t\t\t'narrow': ['day','dayPeriod','weekOfYear','weekday'],\n\t\t\t\t'short': ['era','month','second','timeZoneName'],\n\t\t\t},\n\t\t\tlanguage: {'dialect': ['bn-in','en','fr-ch','gu','kl','sr-ba','zh-hk']},\n\t\t\tregion: {'narrow': ['CM','FR','TL','US','VC','VI','ZZ']},\n\t\t\tscript: {\n\t\t\t\t// blink is case sensitive\n\t\t\t\t'short': ['Arab','Beng','Cyrl','Deva','Guru','Hans','Hrkt','Latn','Mong','Mymr','Orya','Zxxx','Zzzz'],\n\t\t\t},\n \t\t},\n\t\t// DF\n\t\tdurationformat: {\n\t\t\t'digital': {'a': {'milliseconds': 1}},\n\t\t\t'long': {'a': {'years': 1, 'microseconds': 1}, 'b': {'seconds': 2}},\n\t\t\t'narrow': {'a': {'years': 1, 'months': 2, 'seconds': 1, 'microseconds': 1000}},\n\t\t\t'short': {'a': {'days': 2, 'seconds': 2, 'nanoseconds': 1}},\n\t\t},\n\t\t// NF\n\t\t'numberformat.compact': {'long': [0/0, 1000, 2e6, 6.6e12, 7e15],'short': [-1100000000, -1000],},\n\t\t'numberformat.currency': {\"KES\": curS, 'ETB': curN, \"GBP\": curS, \"USD\": curAN, \"XXX\": curN},\n\t\t'numberformat.formattoparts': {\n\t\t\t'decimal': [1.2],'group': [1000, 99999],'infinity': [Infinity],'minusSign': [-5],'nan': ['a']\n\t\t},\n\t\t'numberformat.notation': {\n\t\t\tscientific: {'decimal': []},\n\t\t\tstandard: {'decimal': [0/0, -1000, 987654], 'percent': [1000]},\n\t\t},\n\t\t'numberformat.sign': {always: [-1, 0/0]},\n\t\t'numberformat.unit': {\n\t\t\t'byte': unitN, // ICU 74\n\t\t\t'fahrenheit': unitB,\n\t\t\t'foot': unitL,\n\t\t\t'hectare': {'long': [1], 'short': [987654]},\n\t\t\t'kilometer-per-hour': unitN,\n\t\t\t'millimeter': unitN,\n\t\t\t'month': unitB,\n\t\t\t'nanosecond': unitN,\n\t\t\t'percent': {\"long\": [1], \"narrow\": [1], \"short\": [987654]},\n\t\t\t'second': {'long': [1], 'narrow': [1], 'short': [987654]},\n\t\t\t'terabyte': unitL,\n\t\t},\n\t\t// PR\n\t\t'pluralrules.select': {\n\t\t\tcardinal: [0, 1, 2, 3, 7, 21, 100],\n\t\t\tordinal: [1, 2, 3, 4, 5, 6, 8, 10, 81]\n\t\t},\n\t\t'pluralrules.selectrange': {\n\t\t\tcardinal: [[0,0],[1,1],[2,1],[2,4]],\n\t\t\tordinal: [[0,0],[0,1],[0,6],[1,1],[1,3],[1,5],[3,3]],\n\t\t},\n\t\t// other\n\t\trelativetimeformat: {\n\t\t\talways: {'narrow': [[1, 'day'], [0, 'year']]},\n\t\t\tauto: {\n\t\t\t\t'long': [[1, 'second']],\n\t\t\t\t'narrow': [[0,'second'],[1,'second'],[3,'second'],[0,'day'],[1,'day'], [3,'day'],[1,'week'],[0,'quarter'],[1,'year']]\n\t\t\t},\n\t\t},\n\t\tresolvedoptions: {\n\t\t\tcollator: ['caseFirst'],\n\t\t\tdatetimeformat: ['calendar','day','hourcycle','month','numberingSystem'],\n\t\t\tpluralrules: ['pluralCategories'],\n\t\t},\n\t}\n\ttry {oIntlLocale['numberformat.compact']['long'].push(BigInt('987354000000000000'))} catch {}\n\tlet nBig = 987654\n\ttry {nBig = BigInt('987354000000000000')} catch {}\n\toIntlLocale['numberformat.notation']['scientific']['decimal'].push(nBig)\n\t// build keys\n\tfor (const k of Object.keys(oIntlLocale)) {\n\t\toIntlLocaleKeys[k] = []\n\t\tfor (const j of Object.keys(oIntlLocale[k]).sort()) {oIntlLocaleKeys[k].push(j)}\n\t}\n}\n\nfunction get_geo(METRIC) {\n\t// nav/window are redundant: display only\n\tlet res = [], value, notation = default_red, isLies = false\n\t// nav\n\ttry {\n\t\tlet keys = Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype))\n\t\tif (runSL) {\n\t\t\tkeys = keys.filter(x => !['geolocation'].includes(x))\n\t\t\tkeys.push('geolocation')\n\t\t}\n\t\tvalue = keys.includes(METRIC) ? zE : zD\n\t\t// this only detects enabled as untrustworthy\n\t\tif (keys.indexOf(METRIC) > keys.indexOf('constructor')) {\n\t\t\tlog_known(4, METRIC +'_navigator', value)\n\t\t\tisLies = true\n\t\t}\n\t} catch(e) {\n\t\tlog_error(4, METRIC +'_navigator', e); value = zErr\n\t}\n\taddDisplay(4, METRIC +'_navigator', value,'','', isLies) // display separate for notating lies\n\tres.push(isLies? zLIE : value)\n\t// window\n\ttry {\n\t\tvalue = 'Geolocation' in window ? true : false\n\t\tif (value == true) {\n\t\t\tlet typeCheck = typeFn(window.Geolocation)\n\t\t\tif (runST) {typeCheck = 'string'}\n\t\t\tif ('function' !== typeCheck) {throw zErrType + typeCheck}\n\t\t}\n\t} catch(e) {\n\t\tlog_error(4, METRIC +'_window', e); value = zErr\n\t}\n\tres.push(value)\n\t// summary\n\tlet hash = mini(res)\n\tif (isBB && hash == 'feacff5d') {\n\t\tnotation = default_green // BB ESR78+: disabled, true\n\t} else if (!isBB && hash == '23d43ed0') {\n\t\tnotation = default_green // FF72+: enabled, true\n\t}\n\t// health lookup\n\tif (gRun) {sDetail[isScope].lookup[METRIC] = res.join(' | ')}\n\taddDisplay(4, METRIC, res[1],'', notation)\n\treturn\n}\n\nfunction get_language_locale() {\n\t// reset\n\tisLocaleValid = false\n\tisLocaleValue = undefined\n\tisLocaleAlt = undefined\n\tisLanguagesNav = []\n\n\t// LANGUAGES\n\tfunction get_langmetric(m) {\n\t\ttry {\n\t\t\tlet value = navigator[m]\n\t\t\tlet expected = ('language' == m ? 'string' : 'array')\n\t\t\tif (runST) {value = ('language' == m ? null : [])}\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tif (expected !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif ('languages' == m) {\n\t\t\t\tvalue.forEach(function(l){\n\t\t\t\t\tisLanguagesNav.push(l.toLowerCase())\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn ('language' == m ? value : value.join(', '))\n\t\t} catch(e) {\n\t\t\treturn [e]\n\t\t}\n\t}\n\tlet oData = {}, metrics = ['language','languages'], notation =''\n\tmetrics.forEach(function(m) {oData[m] = get_langmetric(m)})\n\tObject.keys(oData).forEach(function(METRIC){\n\t\tif (isLanguageSmart && isBB) { // only notate BB\n\t\t\tnotation = bb_red\n\t\t\tif (languagesSupported[oData.language] !== undefined) {\n\t\t\t\tif ('language' == METRIC) {notation = bb_green\n\t\t\t\t} else {if (oData[METRIC] == oData.language +', '+ languagesSupported[oData.language][0]) {notation = bb_green}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet value = oData[METRIC], data =''\n\t\tif ('array' == typeFn(value)) {value = value[0]; data = zErrLog}\n\t\taddBoth(4, METRIC, value,'', notation, data, isProxyLie('Navigator.'+ METRIC))\n\t})\n\n\t// LOCALES\n\tfunction get_locmetric(m) {\n\t\tlet METRIC = 'locale_'+ m, r\n\t\ttry {\n\t\t\tif ('collator' == m) {if (runSL) {r = 'en-FAKE'} else {r = Intl.Collator().resolvedOptions().locale}\n\t\t\t} else if ('datetimeformat' == m) {r = Intl.DateTimeFormat().resolvedOptions().locale\n\t\t\t} else if ('displaynames' == m) {r = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\n\t\t\t} else if ('durationformat' == m) {r = new Intl.DurationFormat().resolvedOptions().locale\n\t\t\t} else if ('listformat' == m) {r = new Intl.ListFormat().resolvedOptions().locale\n\t\t\t} else if ('numberformat' == m) {r = new Intl.NumberFormat().resolvedOptions().locale\n\t\t\t} else if ('pluralrules' == m) {r = new Intl.PluralRules().resolvedOptions().locale\n\t\t\t} else if ('relativetimeformat' == m) {r = new Intl.RelativeTimeFormat().resolvedOptions().locale\n\t\t\t} else if ('segmenter' == m) {r = new Intl.Segmenter().resolvedOptions().locale\n\t\t\t}\n\t\t\tif (runST) {r = undefined} else if (runSI) {r = 'collator' !== m ? 'en-USA' : 'tzp'}\n\t\t\tlet typeCheck = typeFn(r)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (!Intl.DateTimeFormat.supportedLocalesOf([r]).length) {throw zErrInvalid + 'locale '+ r +' not supported'}\n\t\t\toRes[m] = r\n\t\t\treturn r\n\t\t} catch(e) {\n\t\t\toRes[m] = e+''\n\t\t\tlog_error(4, METRIC, e)\n\t\t\toErr[m] = e+''\n\t\t\treturn zErr\n\t\t}\n\t}\n\t// LOCALES\n\tlet METRIC = 'locale', value ='', res = [], oRes = {}, oErr = {}\n\tmetrics = [\n\t\t'collator','datetimeformat','displaynames','durationformat','listformat',\n\t\t'numberformat','pluralrules','relativetimeformat','segmenter',\n\t]\n\tmetrics.forEach(function(m) {res.push(get_locmetric(m))})\n\tsDetail[isScope][METRIC] = oRes\n\tlet btn = addButton(4, METRIC)\n\n\t// LOCALE\n\t// remove errors + dupes\n\tres = res.filter(x => ![zErr].includes(x))\n\tres = dedupeArray(res)\n\tlet isLies = false\n\tif (res.length == 1) {\n\t\tvalue = res[0]\n\t\tisLocaleValue = value\n\t\t// reduce en health false positives\n\t\t// but only for isBB since as it only ships with en-US\n\t\t\t// use isLocaleAlt in validation checks: allow e.g. en-CA to use en-US for lookup\n\t\t\t// ^ we already have a health check for wrong locale\n\t\tisLocaleAlt = (isBB && 'en-' == isLocaleValue.slice(0,3) ? 'en-US' : isLocaleValue)\n\t\tif (isSmart) {isLocaleValid = true} // only set if smart\n\t} else if (res.length == 0) {\n\t\tvalue = zErr\n\t} else {\n\t\tvalue = 'mixed'; isLies = true\n\t}\n\tif (isLanguageSmart && isBB) { // only notate BB\n\t\tnotation = bb_red\n\t\tlet errHash = mini(oErr)\n\t\tif (Object.keys(oErr).length == 0) {\n\t\t\t// BB15: no errors\n\t\t\t// only green if BB supported\n\t\t\tlet key = oData.language\n\t\t\tif (languagesSupported[key] !== undefined) {\n\t\t\t\tlet expected = languagesSupported[key][1] == undefined ? key : languagesSupported[key][1]\n\t\t\t\tif (value === expected) {notation = bb_green}\n\t\t\t}\n\t\t}\n\t}\n\taddDisplay(4, METRIC, value, btn, notation, isLies)\n\taddData(4, METRIC, value, '', isLies)\n\treturn\n}\n\nfunction get_language_system(METRIC) {\n\t/* systemLanguages: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/systemLanguage\n\tpopulate svg with nav entries to detect if anything added. To detect removals would mean\n\tpopulating with all supported BCPs (lots) = not worth it: perf and it is unlikely _only_ removal\n\thappens, i.e we already detect added. Also prior to FF127 = false positives with prefixs e.g. if\n\tyou were 'en-US, en', all en-* would be be true. Not worth the footgun or hasssle\n\t*/\n\tlet t0 = nowFn()\n\tlet value, data =''\n\ttry {\n\t\tisLanguagesNav.sort() // so results are sorted\n\t\t// populate\n\t\tlet aText = ['<switch id=\"switch\">']\n\t\tisLanguagesNav.forEach(function(l){aText.push('<text systemLanguage=\"'+ l +'\">' + l +'</text>')})\n\t\taText.push('<text systemLanguage=\"groot\">groot</text>')\n\t\taText.push('<text>unknown</text></switch>')\n\t\tlet el = dom.tzpSwitch\n\t\tel.innerHTML = aText.join('')\n\t\t// walk nodes\n\t\tlet aDetected = []\n\t\tconst walker = document.createTreeWalker(dom['switch'], NodeFilter.SHOW_TEXT, null);\n\t\twhile(walker.nextNode() && walker.currentNode) {\n\t\t\tlet target = walker.currentNode\n\t\t\t//* important: we check range.getClientRects DOMRectList length so only real nav items are detected\n\t\t\t\t// we use range due to selectNode (I think)\n\t\t\t\t// we can't use range.getBoundingClientRect's DOMRect object (can't get obj keys length)\n\t\t\t\t// THIS IS THE WAY: range.getClientRects()\n\t\t\t// e.g. if isLanguagesNav has a fake 'fr' (e.g. extension) it won't be detected as it\n\t\t\t\t// isn't a \"rendered\" node with a range (cuz it's fake) - IIUIC\n\t\t\tlet range = new Range()\n\t\t\trange.selectNode(target)\n\t\t\tif (range.getClientRects().length) {aDetected.push(target.textContent)}\n\t\t}\n\t\t// remove unknown\n\t\taDetected = aDetected.filter(x => !['unknown'].includes(x))\n\t\tif (0 == aDetected.length) {throw zErrType + 'empty array'}\n\t\tvalue = aDetected.join(', ')\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\t// tidy nav string to compare to\n\tisLanguagesNav = isLanguagesNav.join(', ')\n\taddBoth(4, METRIC, value,'', (value == isLanguagesNav ? lang_green : lang_red), data)\n\tlog_perf(4, METRIC, t0)\n}\n\nfunction get_dates_intl() {\n\tfunction get_metric(m, isIntl) {\n\t\tlet tsub0 = nowFn(), countC = 0\n\t\ttry {\n\t\t\tlet obj = {}, objcheck = {}, tests = oIntlDate[m], testkeys = oIntlDateKeys[m], value\n\t\t\tlet formatter, checker\n\t\t\tif ('date_timestyle' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) {\n\t\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\t\tlet styles = s.split('_'), cal = 'default' == key ? undefined : key\n\t\t\t\t\t\tif (1 == styles.length) {styles.push(styles[0])} // ensure we have two styles\n\t\t\t\t\t\t// test\n\t\t\t\t\t\tlet options = {dateStyle: styles[0], timeStyle: styles[1], timeZone: tzTest}\n\t\t\t\t\t\tif ('default' !== key) {options['calendar'] = key}\n\t\t\t\t\t\tformatter = Intl.DateTimeFormat(locTest, options); countC++\n\t\t\t\t\t\t// check\n\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\toptions = {calendar: cal, dateStyle: styles[0], timeStyle: styles[1], timeZone: tzCheck}\n\t\t\t\t\t\t\tchecker = Intl.DateTimeFormat(locCheck, options); countC++\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttests[key][s].forEach(function(n) {\n\t\t\t\t\t\t\tvalue = formatter.format(n); data.push(value)\n\t\t\t\t\t\t\tif (isCheck) {value = checker.format(n); datacheck.push(value)}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// microperf\n\t\t\tadd_microperf_intl('datetimeformat.'+ m, countC, tsub0, isIntl)\n\t\t\t// return\n\t\t\treturn [\n\t\t\t\t{'hash': mini(obj), 'metrics': obj},\n\t\t\t\t(isCheck ? {'hash': mini(objcheck), 'metrics': objcheck} : undefined)\n\t\t\t]\n\t\t} catch(e) {\n\t\t\tadd_microperf_intl('datetimeformat.'+ m, countC, tsub0, isIntl)\n\t\t\tlog_error(4, METRIC +'_'+ m, e)\n\t\t\treturn [zErr, zErr]\n\t\t}\n\t}\n\n\tconst oMetrics = {\n\t\tintl : ['date_timestyle',],\n\t\t'to-string': [],\n\t}\n\tlet METRIC, oStringExpected = {}, isCheck = isLocaleValid && isTimeZoneValid\n\tlet locTest = undefined, locCheck = isLocaleValue // use variables so I can test them\n\tlet tzTest = undefined, tzCheck = isTimeZoneValue // use variables so I can test them\n\n\tObject.keys(oMetrics).forEach(function(list){\n\t\tMETRIC = 'dates_'+ list\n\t\tlet t0 = nowFn(), isIntl = 'intl' == list, notation = localetz_red\n\t\tlet oData = {}, oCheck = {} // data from each intl/string loop\n\n\t\toMetrics[list].forEach(function(m) {\n\t\t\tlet res = get_metric(m, isIntl) \n\t\t\toData[m] = res[0]\n\t\t\toCheck[m] = res[1]\n\t\t\tlet isString = (isIntl && oMetrics['to-string'].includes(m))\n\t\t\tif (isString) {oStringExpected[m] = res[0]} // intl version of to*string to compare to\n\t\t\t// console.log(list, res); console.log('test', oData); console.log('check', oCheck); console.log('expected string', oStringExpected)\n\t\t})\n\t\tlet hash = mini(oData)\n\t\t// on string loop (we have an empty tostring list hence the extra check)\n\t\tif (!isIntl && oMetrics[list].length) {\n\t\t\t// does the undefined string data match the undefined intl data\n\t\t\taddDisplay(4, METRIC +'_matches_intl','','', (hash == mini(oStringExpected) ? intl_green : intl_red))\n\t\t}\n\t\tif (isCheck) {\n\t\t\tif (hash == mini(oCheck)) {\n\t\t\t\tnotation = localetz_green\n\t\t\t} else {\n\t\t\t\taddDetail(METRIC +'_expected', oCheck)\n\t\t\t\tnotation = addButton('bad', METRIC +'_expected', \"<span class='health'>\"+ cross +\"</span> locale + timezone\")\n\t\t\t}\n\t\t}\n\t\tif (oMetrics[list].length) { // temp check until we start building string tests\n\t\t\taddBoth(4, METRIC, hash, addButton(4, METRIC), notation, oData)\n\t\t\tlog_perf(4, METRIC, t0)\n\t\t}\n\t\tif (!isIntl) {return}\n\t})\n}\n\nfunction get_locale_intl() {\n\tfunction get_metric(m, isIntl) {\n\t\tlet tsub0 = nowFn(), countC = 0\n\t\ttry {\n\t\t\tlet obj = {}, objcheck = {}, tests = oIntlLocale[m], testkeys = oIntlLocaleKeys[m], value\n\t\t\tlet formatter, checker\n\n\t\t\tif ('collation' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet testdata = tests[key].sort() // always resort\n\t\t\t\t\t// trim leading/trailing spacesto help LTR/RTL\n\t\t\t\t\tobj[key] = testdata.sort(Intl.Collator(locTest, {usage: key}).compare).join(' , ').trim(); countC++\n\t\t\t\t\tif (isCheck) {objcheck[key] = testdata.sort(Intl.Collator(locCheck, {usage: key}).compare).join(' , ').trim(); countC++}\n\t\t\t\t}\n\t\t\t} else if ('datetimeformat.components' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) {\n\t\t\t\t\t\tlet option = tests[key][s][0]\n\t\t\t\t\t\t// our dates are specifically UTC to get specific days/hrs\n\t\t\t\t\t\t// to preserve that we pass UTC as the timeZone\n\t\t\t\t\t\toption['timeZone'] = 'UTC'\n\t\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\t\tformatter = new Intl.DateTimeFormat(locTest, option); countC++\n\t\t\t\t\t\t\tif (isCheck) {checker = new Intl.DateTimeFormat(locCheck, option); countC++}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttests[key][s][1].forEach(function(n){\n\t\t\t\t\t\t\tvalue = (isIntl ? formatter.format(n) : (n).toLocaleString(strTest, option)); data.push(value)\n\t\t\t\t\t\t\tif (isCheck) {value = (isIntl ? checker.format(n) : (n).toLocaleString(strCheck, option)); datacheck.push(value)}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if ('datetimeformat.dayperiod' == m) {\n\t\t\t\t// our dates are specifically UTC to get specific times\n\t\t\t\t// to preserve that we pass UTC as the timeZone\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tformatter = new Intl.DateTimeFormat(locTest, {hourCycle: 'h12', timeZone: 'UTC', dayPeriod: key}); countC++\n\t\t\t\t\tif (isCheck) {checker = new Intl.DateTimeFormat(locCheck, {hourCycle: 'h12', timeZone: 'UTC', dayPeriod: key}); countC++}\n\t\t\t\t\ttests[key].forEach(function(item) {\n\t\t\t\t\t\tdata.push(formatter.format(item))\n\t\t\t\t\t\tif (isCheck) {datacheck.push(checker.format(item))}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('datetimeformat.listformat' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\ttests[key].forEach(function(item) {\n\t\t\t\t\t\tdata.push(new Intl.ListFormat(locTest, {style: key, type: item}).format(['a','b','c'])); countC++\n\t\t\t\t\t\tif (isCheck) {datacheck.push(new Intl.ListFormat(locCheck, {style: key, type: item}).format(['a','b','c'])); countC++}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('datetimeformat.relatedyear' == m) {\n\t\t\t\t// our dates are specifically UTC as we expose the day and\n\t\t\t\t// to preserve that we pass UTC as the timeZone\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet cal = 'default' == key ? undefined : key\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\tformatter = Intl.DateTimeFormat(locTest, {calendar: cal, relatedYear: 'long', timeZone: 'UTC'}); countC++\n\t\t\t\t\t\tif (isCheck) {checker = Intl.DateTimeFormat(locCheck, {calendar: cal, relatedYear: 'long', timeZone: 'UTC'}); countC++}\n\t\t\t\t\t}\n\t\t\t\t\ttests[key].forEach(function(d) {\n\t\t\t\t\t\tlet stroptions = {calendar: cal, day: 'numeric', month: 'numeric', year: 'numeric', timeZone: 'UTC'}\n\t\t\t\t\t\tvalue = (isIntl ? formatter.format(d) : (d).toLocaleString(strTest, stroptions)); data.push(value)\n\t\t\t\t\t\tif (isCheck) {value = (isIntl ? checker.format(d) : (d).toLocaleString(strCheck, stroptions)); datacheck.push(value)}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('datetimeformat.timezonename' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(tzn){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// use y+m+d numeric so toLocaleString matches\n\t\t\t\t\t\t\t// use hour12 in case - https://bugzilla.mozilla.org/show_bug.cgi?id=1645115#c9\n\t\t\t\t\t\t\t// key: e.g. Africa/Douala | tzn: e.g. longGeneric\n\t\t\t\t\t\t\tlet option = {year: 'numeric', month: 'numeric', day: 'numeric', hour12: true, timeZone: key, timeZoneName: tzn}\n\t\t\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\t\t\tformatter = Intl.DateTimeFormat(locTest, option); countC++\n\t\t\t\t\t\t\t\tif (isCheck) {checker = Intl.DateTimeFormat(locCheck, option); countC++}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttests[key][tzn].forEach(function(dte){\n\t\t\t\t\t\t\t\tvalue = (isIntl ? formatter.format(dte) : (dte).toLocaleString(strTest, option)); data.push(value)\n\t\t\t\t\t\t\t\tif(isCheck) {value = (isIntl ? checker.format(dte) : (dte).toLocaleString(strCheck, option)); datacheck.push(value)}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} catch {} // ignore invalid\n\t\t\t\t\t\tif (data.length) {obj[key] = data}\n\t\t\t\t\t\tif (datacheck.length) {objcheck[key] = datacheck}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tif (!Object.keys(obj).length) {let trap = Intl.DateTimeFormat(locTest, {timeZoneName: 'longGeneric'})} // trap error\n\t\t\t} else if ('displaynames' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) { // for each style\n\t\t\t\t\t\tlet optkey = 'datetimefield' == key ? 'dateTimeField' : key // fix key case\n\t\t\t\t\t\tlet options = {type: optkey, style: s}\n\t\t\t\t\t\tif ('language' == key) {options = {type: key, languageDisplay: s}}\n\t\t\t\t\t\tlet data = {}, datacheck = {}\n\t\t\t\t\t\t// displaynames takes an empty array for undefined, but allow oour override for testing\n\t\t\t\t\t\tlet locIntl = undefined == locTest ? [] : locTest\n\t\t\t\t\t\tformatter = new Intl.DisplayNames(locIntl, options); countC++\n\t\t\t\t\t\tif (isCheck) {checker = new Intl.DisplayNames(locCheck, options); countC++}\n\t\t\t\t\t\ttests[key][s].forEach(function(item) {\n\t\t\t\t\t\t\tdata[item] = formatter.of(item)\n\t\t\t\t\t\t\tif (isCheck) {datacheck[item] = checker.of(item)}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if ('durationformat' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet yearformat = ('long' == key || 'short' == key) ? 'always' : 'auto' // long we want to force 0 for years\n\t\t\t\t\tformatter = new Intl.DurationFormat(locTest, {style: key, yearsDisplay: yearformat}); countC++\n\t\t\t\t\tif (isCheck) {checker = new Intl.DurationFormat(locCheck, {style: key, yearsDisplay: yearformat}); countC++}\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tfor (const item of Object.keys(tests[key])) {\n\t\t\t\t\t\tdata.push(formatter.format(tests[key][item]))\n\t\t\t\t\t\tif (isCheck) {datacheck.push(checker.format(tests[key][item]))}\n\t\t\t\t\t}\n\t\t\t\t\tobj[key] = data.join(' | '); objcheck[key] = datacheck.join(' | ')\n\t\t\t\t}\n\t\t\t} else if ('numberformat.compact' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet option = {notation: 'compact', compactDisplay: key, useGrouping: true}\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\tformatter = new Intl.NumberFormat(locTest, option); countC++\n\t\t\t\t\t\tif (isCheck) {checker = new Intl.NumberFormat(locCheck, option); countC++}\n\t\t\t\t\t}\n\t\t\t\t\ttests[key].forEach(function(n) {\n\t\t\t\t\t\tvalue = (isIntl ? formatter.format(n) : (n).toLocaleString(strTest, option)); data.push(value)\n\t\t\t\t\t\tif (isCheck) {value = (isIntl ? checker.format(n) : (n).toLocaleString(strCheck, option)); datacheck.push(value)}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('numberformat.currency' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) {\n\t\t\t\t\t\tlet option = 'accounting' == s ? {style: 'currency', currency: key, currencySign: s} : {style: 'currency', currency: key, currencyDisplay: s}\n\t\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\t\ttests[key][s].forEach(function(n) {\n\t\t\t\t\t\t\tvalue = (isIntl ? Intl.NumberFormat(locTest, option).format(n) : (n).toLocaleString(strTest, option))\n\t\t\t\t\t\t\tdata.push(value); countC++\n\t\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\t\tvalue = (isIntl ? Intl.NumberFormat(locCheck, option).format(n) : (n).toLocaleString(strCheck, option))\n\t\t\t\t\t\t\t\tdatacheck.push(value); countC++\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if ('numberformat.formattoparts' == m) {\n\t\t\t\tfunction get_value(type, aParts) {\n\t\t\t\t\tfor (let i=0; i < aParts.length; i++) {\n\t\t\t\t\t\tif (aParts[i].type === type) {str = aParts[i].value; return (str.length == 1 ? str.charCodeAt(0) : str)}\n\t\t\t\t\t}\n\t\t\t\t\treturn 'none'\n\t\t\t\t}\n\t\t\t\tformatter = Intl.NumberFormat(locTest); countC++\n\t\t\t\tif (isCheck) {checker = Intl.NumberFormat(locCheck); countC++}\n\t\t\t\tlet str\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\ttests[key].forEach(function(num){\n\t\t\t\t\t\tdata.push(get_value(key, formatter.formatToParts(num)))\n\t\t\t\t\t\tif (isCheck) {datacheck.push(get_value(key, checker.formatToParts(num)))}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('numberformat.notation' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) {\n\t\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\t\tformatter = Intl.NumberFormat(locTest, {notation: key, style: s}); countC++\n\t\t\t\t\t\t\tif (isCheck) {checker = Intl.NumberFormat(locCheck, {notation: key, style: s}); countC++}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttests[key][s].forEach(function(n){\n\t\t\t\t\t\t\tvalue = (isIntl ? formatter.format(n) : (n).toLocaleString(strTest, {notation: key, style: s})); data.push(value)\n\t\t\t\t\t\t\tif (isCheck) {value = (isIntl ? checker.format(n) : (n).toLocaleString(strCheck, {notation: key, style: s})); datacheck.push(value)}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if ('numberformat.sign' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\tformatter = new Intl.NumberFormat(locTest, {signDisplay: key}); countC++\n\t\t\t\t\t\tif (isCheck) {checker = new Intl.NumberFormat(locCheck, {signDisplay: key}); countC++}\n\t\t\t\t\t}\n\t\t\t\t\ttests[key].forEach(function(n){\n\t\t\t\t\t\tvalue = (isIntl ? formatter.format(n) : (n).toLocaleString(strTest, {signDisplay: key})); data.push(value)\n\t\t\t\t\t\tif (isCheck) {value = (isIntl ? checker.format(n) : (n).toLocaleString(strCheck, {signDisplay: key})); datacheck.push(value)}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('numberformat.unit' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(ud){\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (isIntl) {\n\t\t\t\t\t\t\t\tformatter = Intl.NumberFormat(locTest, {style: 'unit', unit: key, unitDisplay: ud}); countC++\n\t\t\t\t\t\t\t\tif (isCheck) {checker = Intl.NumberFormat(locCheck, {style: 'unit', unit: key, unitDisplay: ud}); countC++}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttests[key][ud].forEach(function(n){\n\t\t\t\t\t\t\t\tvalue = (isIntl ? formatter.format(n) : (n).toLocaleString(strTest, {style: 'unit', unit: key, unitDisplay: ud}))\n\t\t\t\t\t\t\t\tdata.push(value)\n\t\t\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\t\t\tvalue = (isIntl ? checker.format(n) :\t(n).toLocaleString(strCheck, {style: 'unit', unit: key, unitDisplay: ud}))\n\t\t\t\t\t\t\t\t\tdatacheck.push(value)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} catch {} // ignore invalid\n\t\t\t\t\t})\n\t\t\t\t\tif (data.length) {obj[key] = data}\n\t\t\t\t\tif (datacheck.length) {objcheck[key] = datacheck}\n\t\t\t\t}\n\t\t\t\tif (!Object.keys(obj).length) {let trap = Intl.NumberFormat(locTest, {style: 'unit', unit: 'day'})} // trap error\n\t\t\t} else if ('pluralrules.select' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\tformatter = new Intl.PluralRules(locTest, {type: key}); countC++\n\t\t\t\t\tif (isCheck) {checker = new Intl.PluralRules(locCheck, {type: key}); countC++}\n\t\t\t\t\tlet prev='', current='', prevchk='', currentchk=''\n\t\t\t\t\ttests[key].forEach(function(n) {\n\t\t\t\t\t\tcurrent = formatter.select(n); if (prev !== current) {data.push(n +': '+ current); prev = current}\n\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\tcurrentchk = checker.select(n); if (prevchk !== currentchk) {datacheck.push(n +': '+ currentchk); prevchk = currentchk}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tobj[key] = data; objcheck[key] = datacheck\n\t\t\t\t}\n\t\t\t} else if ('pluralrules.selectrange' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tlet data = {}, datacheck = {}\n\t\t\t\t\tformatter = new Intl.PluralRules(locTest, {type: key}); countC++\n\t\t\t\t\tif (isCheck) {checker = new Intl.PluralRules(locCheck, {type: key}); countC++}\n\t\t\t\t\tlet prev='', current='', prevchk='', currentchk=''\n\t\t\t\t\ttests[key].forEach(function(n) {\n\t\t\t\t\t\tcurrent = formatter.selectRange(n[0], n[1])\n\t\t\t\t\t\tif (prev !== current) {\n\t\t\t\t\t\t\tlet datakey =  formatter.select(n[0]) +'-'+ formatter.select(n[1])\n\t\t\t\t\t\t\tif (undefined == data[datakey]) {data[datakey] = current}\n\t\t\t\t\t\t\tprev = current\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\tcurrentchk = checker.selectRange(n[0], n[1])\n\t\t\t\t\t\t\tif (prevchk !== currentchk) {\n\t\t\t\t\t\t\t\tlet checkkey =  checker.select(n[0]) +'-'+ checker.select(n[1])\n\t\t\t\t\t\t\t\tif (undefined == datacheck[checkkey]) {datacheck[checkkey] = currentchk}\n\t\t\t\t\t\t\t\tprevchk = currentchk\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t// sort obj keys\n\t\t\t\t\tlet newdata = {}, newdatacheck = {}\n\t\t\t\t\tfor (const k of Object.keys(data).sort()) {newdata[k] = data[k]}\n\t\t\t\t\tfor (const k of Object.keys(datacheck).sort()) {newdatacheck[k] = datacheck[k]}\n\t\t\t\t\tobj[key] = newdata; objcheck[key] = newdatacheck\n\t\t\t\t}\n\t\t\t} else if ('relativetimeformat' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\tObject.keys(tests[key]).forEach(function(s) {\n\t\t\t\t\t\tlet data = [], datacheck = []\n\t\t\t\t\t\tformatter = new Intl.RelativeTimeFormat(locTest, {style: s, numeric: key}); countC++\n\t\t\t\t\t\tif (isCheck) {checker = new Intl.RelativeTimeFormat(locCheck, {style: s, numeric: key}); countC++}\n\t\t\t\t\t\ttests[key][s].forEach(function(pair){\n\t\t\t\t\t\t\tdata.push(formatter.format(pair[0], pair[1]))\n\t\t\t\t\t\t\tif (isCheck) {datacheck.push(checker.format(pair[0], pair[1]))}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tobj[key][s] = data; objcheck[key][s] = datacheck\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if ('resolvedoptions' == m) {\n\t\t\t\tfor (let i=0; i < testkeys.length; i++) {\n\t\t\t\t\tlet key = testkeys[i]\n\t\t\t\t\tif ('collator' == key) {formatter = Intl.Collator(locTest).resolvedOptions(); countC++\n\t\t\t\t\t} else if ('datetimeformat' == key) {formatter = Intl.DateTimeFormat(locTest).resolvedOptions(); countC++\n\t\t\t\t\t} else if ('pluralrules' == key) {formatter = new Intl.PluralRules(locTest).resolvedOptions(); countC++\n\t\t\t\t\t}\n\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\tif ('collator' == key) {checker = Intl.Collator(locCheck).resolvedOptions(); countC++\n\t\t\t\t\t\t} else if ('datetimeformat' == key) {checker = Intl.DateTimeFormat(locCheck).resolvedOptions(); countC++\n\t\t\t\t\t\t} else if ('pluralrules' == key) {checker = new Intl.PluralRules(locCheck).resolvedOptions(); countC++\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tobj[key] = {}; objcheck[key] = {}\n\t\t\t\t\ttests[key].forEach(function(s){\n\t\t\t\t\t\tif ('hourcycle' == s) {value = Intl.DateTimeFormat(locTest, {hour: 'numeric'}).resolvedOptions().hourCycle; countC++\n\t\t\t\t\t\t} else if ('pluralCategories' == s) {value = formatter[s].join(', ')\n\t\t\t\t\t\t} else {value = formatter[s]}\n\t\t\t\t\t\tobj[key][s] = value\n\n\t\t\t\t\t\tif (isCheck) {\n\t\t\t\t\t\t\tif ('hourcycle' == s) {value = Intl.DateTimeFormat(locCheck, {hour: 'numeric'}).resolvedOptions().hourCycle; countC++\n\t\t\t\t\t\t\t} else if ('pluralCategories' == s) {value = checker[s].join(', ')\n\t\t\t\t\t\t\t} else {value = checker[s]}\n\t\t\t\t\t\t\tobjcheck[key][s] = value\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// microperf\n\t\t\tadd_microperf_intl(m, countC, tsub0, isIntl)\n\t\t\t// return\n\t\t\treturn [\n\t\t\t\t//{'hash': mini(obj), 'metrics': obj},\n\t\t\t\t//(isCheck ? {'hash': mini(objcheck), 'metrics': objcheck} : undefined)\n\t\t\t\tobj, (isCheck ? objcheck : undefined)\n\t\t\t]\n\t\t} catch(e) {\n\t\t\tadd_microperf_intl(m, countC, tsub0, isIntl)\n\t\t\tlog_error(4, METRIC +'_'+ m.replace('.','_'), e)\n\t\t\treturn [zErr, zErr]\n\t\t}\n\t}\n\n\tconst oMetrics = {\n\t\tintl : [\n\t\t\t'collation',\n\t\t\t'datetimeformat.components','datetimeformat.dayperiod','datetimeformat.listformat',\n\t\t\t\t'datetimeformat.relatedyear','datetimeformat.timezonename',\n\t\t\t'displaynames','durationformat',\n\t\t\t'numberformat.compact','numberformat.currency','numberformat.formattoparts',\n\t\t\t\t'numberformat.notation','numberformat.sign','numberformat.unit',\n\t\t\t'pluralrules.select','pluralrules.selectrange','relativetimeformat','resolvedoptions',\n\t\t],\n\t\ttolocalestring: [\n\t\t\t'datetimeformat.components','datetimeformat.relatedyear','datetimeformat.timezonename',\n\t\t\t'numberformat.compact','numberformat.currency','numberformat.notation','numberformat.sign','numberformat.unit',\n\t\t],\n\t}\n\tlet METRIC, isCheck = isLocaleValid\n\tlet oStringExpected = {}, oStringExpectedChildren = {}\n\tlet locTest = undefined, locCheck = isLocaleValue // use variables so I can test them\n\tlet strTest = undefined, strCheck = isLocaleValue\n\t//locTest = 'de'; locCheck = 'de' // should be the same\n\t//locTest = 'it'; locCheck = 'ko' // everything should be different\n\n\t//strTest = 'fr', strCheck = 'fr' // should be the same\n\t//strTest = 'pl', strCheck = 'es' // everything should be different\n\n\tObject.keys(oMetrics).forEach(function(list){\n\t\tMETRIC = 'locale_'+ list\n\t\tlet t0 = nowFn(), isIntl = 'intl' == list, notation = locale_red\n\t\tlet oData = {}, oCheck = {} // data from each intl/string loop\n\t\tlet oDataChildren = {}, oCheckChildren = {}\n\n\t\toMetrics[list].forEach(function(m) {\n\t\t\tlet res = get_metric(m, isIntl)\n\t\t\tlet isParent = m.includes('.')\n\t\t\tlet isString = (isIntl && oMetrics['tolocalestring'].includes(m))\t\t\t\n\t\t\tif (isParent) {\n\t\t\t\tlet parent = m.split('.')[0], child = m.split('.')[1]\n\t\t\t\t// placeholders (so sorted order is kelp)\n\t\t\t\toData[parent] = {}\n\t\t\t\toCheck[parent] = {}\n\n\t\t\t\t// children\n\t\t\t\tif (undefined == oDataChildren[parent]) {oDataChildren[parent] = {}}\n\t\t\t\t\toDataChildren[parent][child] = res[0]\n\t\t\t\tif (undefined == oCheckChildren[parent]) {oCheckChildren[parent] = {}}\n\t\t\t\t\toCheckChildren[parent][child] = res[1]\n\t\t\t\tif (isString) {\n\t\t\t\t\toStringExpected[parent] = {}\n\t\t\t\t\tif (undefined == oStringExpectedChildren[parent]) {oStringExpectedChildren[parent] = {}}\n\t\t\t\t\toStringExpectedChildren[parent][child] = res[0]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// direct: don't hash zErr\n\t\t\t\toData[m] = zErr == res[0] ? zErr : {'hash': mini(res[0]), 'metrics': res[0]}\n\t\t\t\toCheck[m] = zErr == res[1] ? zErr: {'hash': mini(res[1]), 'metrics': res[1]}\n\t\t\t\tif (isString) {\n\t\t\t\t\toStringExpected[m] = zErr == res[0] ? zErr : {'hash': mini(res[0]), 'metrics': res[0]}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t// update placeholders\n\t\tfor (const k of Object.keys(oDataChildren)) {\n\t\t\toData[k] = {'hash': mini(oDataChildren[k]), 'metrics': oDataChildren[k]}\n\t\t\toCheck[k] = {'hash': mini(oCheckChildren[k]), 'metrics': oCheckChildren[k]}\n\t\t}\n\t\tlet hash = mini(oData)\n\n\t\t// update expected string placeholder on the intl loop\n\t\tif (isIntl) {\n\t\t\tfor (const k of Object.keys(oStringExpectedChildren)) {\n\t\t\t\toStringExpected[k] = {'hash': mini(oStringExpectedChildren[k]), 'metrics': oStringExpectedChildren[k]}\n\t\t\t}\n\t\t} else {\n\t\t\t// on string loop compare it\n\t\t\t// does the undefined string data match the undefined intl data\n\t\t\taddDisplay(4, METRIC +'_matches_intl','','', (hash == mini(oStringExpected) ? intl_green : intl_red))\n\t\t}\n\t\tif (isCheck) {\n\t\t\tif (hash == mini(oCheck)) {\n\t\t\t\tnotation = locale_green\n\t\t\t} else {\n\t\t\t\taddDetail(METRIC +'_expected', oCheck)\n\t\t\t\tnotation = addButton('bad', METRIC +'_expected', \"<span class='health'>\"+ cross +\"</span> locale\")\n\t\t\t}\n\t\t}\n\t\taddBoth(4, METRIC, hash, addButton(4, METRIC), notation, oData)\n\t\tlog_perf(4, METRIC, t0)\n\t\tif (!isIntl) {return}\n\t})\n}\n\nfunction get_timezone(METRIC) {\n\t// reset\n\tisTimeZoneValid = false\n\tisTimeZoneValue = undefined\n\n\tlet tzo = get_timezone_offset(METRIC +'_offset')\n\tlet offsets = get_timezone_offsets(METRIC +'_offsets', tzo.nowValue, tzo.utcValue)\n\n\t// timezone: we can use tzo.tampered items to return if isLies\n\tlet aMethods = ['timeZone','timeZoneId','zonedDateTimeISO']\n\tlet aTemporal = ['plainDateISO','plainDateTimeISO','plainTimeISO','zonedDateTimeISO']\n\tlet errCount = 0, lieCount = 0, tzData = {'data': [], 'valid': []}, notation = rfp_red, isLies = false\n\taMethods.forEach(function(k) {\n\t\tlet tz\n\t\tisLies = false\n\t\ttry {\n\t\t\tif ('timeZone' == k) {\n\t\t\t\ttz = Intl.DateTimeFormat().resolvedOptions().timeZone\n\t\t\t\tif (tzo.tampered.includes('timeZone')) {isLies = true}\n\t\t\t\t//tz = 'Asia/Tokyo' // test mixed but no lies detected\n\t\t\t} else {\n\t\t\t\tif (tzo.tampered.filter(x => aTemporal.includes(x)).length) {isLies = true}\n\t\t\t\tif ('timeZoneId' == k) {\n\t\t\t\t\ttz = Temporal.Now.timeZoneId()\n\t\t\t\t} else {\n\t\t\t\t\ttz = Temporal.Now.zonedDateTimeISO().toString()\n\t\t\t\t\ttz = tz.slice(tz.indexOf('[') + 1, tz.length - 1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (runST) {tz = undefined} else if (runSI) {tz = 'tzp'}\n\t\t\tlet typeCheck = typeFn(tz)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tlet tztest = (new Date('January 1, 2018 13:00:00 UTC')).toLocaleString('en', {timeZone: tz})\n\t\t\ttzData.data.push(tz)\n\t\t\tif (isLies) {lieCount++; log_known(4, METRIC +'_'+ k, tz)} else {tzData.valid.push(tz)}\n\t\t\taddDisplay(4, METRIC +'_'+ k, tz, '','', isLies)\n\t\t} catch(e) {\n\t\t\terrCount++\n\t\t\taddDisplay(4, METRIC +'_'+ k, log_error(4, METRIC +'_'+ k, e))\n\t\t}\n\t})\n\t// all errors\n\tif (errCount == aMethods.length) {addBoth(4, METRIC, zErr +'s', '', notation, zErr); return}\n\n\t// notation: 3 x truthful Atlantic/Reykjavik, and whilst we already have health checks\n\t// on offsets(s) but we need to confirm the actual results\n\tif ('80724dcd' == mini(tzData) && 0 === tzo.nowValue && '031b56a9' == offsets.hash) {notation = rfp_green}\n\n\t// summary\n\t\t// if we have a single valid value, use that\n\t\t// if valid is mixed then data is also mixed\n\t\t// if valid is empty then we have to use data anyway\n\t\t// data will always have at least one value (we returned earlier if all errors)\n\tlet value = '', aValid = dedupeArray(tzData.valid), aData = dedupeArray(tzData.data)\n\tlet isMixed = aData.length > 1, isValid = 1 == aValid.length \n\tisLies = !isValid\n\tif (1 == aValid.length) {value = aValid[0]} else {if (isMixed) {value = 'mixed'} else {value = aData[0]}}\n\t// isTimeZoneValue\n\tif ('mixed' !== value)\t{isTimeZoneValue = value}\n\t// health lookup\n\tif (notation == rfp_red && 'Atlantic/Reykjavik' == value) {\n\t\t// then we must have had lies and/or errors\n\t\tlet aHealth = []\n\t\tif (errCount > 0) {aHealth.push(errCount + ' error' + (errCount == 1 ? '' : 's'))}\n\t\tif (lieCount > 0) {aHealth.push(lieCount + ' mismatch' + (lieCount == 1 ? '' : 'es'))}\n\t\tif (gRun) {sDetail[isScope].lookup[METRIC] = aHealth.join(' | ')}\n\t}\n\t// display\n\taddBoth(4, METRIC, value, '', notation, '', isLies)\t\n\n\t// set isTimeZoneValid\n\t\t// offset: no tampering ignore errors | offsets : no tampering or errors (I might need to revisit this logic later)\n\t\t// ^ this means anything date/temporal or to*string hasn't been tampered with\n\t\t// timezone: can't be any lies and can't be mixed\n\tlet isTZValidSoFar = 0 == tzo.tampered.length && true === offsets.health\n\tif (isTZValidSoFar) {\n\t\tif (0 == lieCount && 1 == aValid.length) {isTimeZoneValid = true}\n\t}\n\treturn\n}\n\nfunction get_timezone_offset(METRIC) {\n\t// this is good test to catch + record various temporal/date/toString lies even\n\t\t// if they are ultimately duplicitous. This requires the spoofed offset to differ\n\t\t// from lastModified and real-time real-world world offsets only number 65-70\n\t\t// IIUIC. So not definitive, but multiple exposure of tampering is good. also,\n\t\t// fuck extensions trying to resist or solutions that create mismatches :)\n\tlet t0 = nowFn()\n\t// setup\n\tconst xslText = '<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"'\n\t\t\t+' xmlns:date=\"http://exslt.org/dates-and-times\" extension-element-prefixes=\"date\"><xsl:output method=\"html\"/>'\n\t\t\t+' <xsl:template match=\"/\"><xsl:value-of select=\"date:date-time()\" /></xsl:template></xsl:stylesheet>'\n\tconst\tdoc = (new DOMParser).parseFromString(xslText, 'text/xml')\n\tlet oData = {}, notation = tz_red\n\tlet methods = [\n\t\t'timeZone', // intl.DTF\n\t\t'iframe','parseFromString','parseHTMLUnsafe', // last modified, also exslt\n\t\t'plainDateISO','plainDateTimeISO','zonedDateTimeISO','plainTimeISO', // temporal\n\t\t// to*string\n\t\t'toDateString','toLocaleString','toLocaleDateString',\n\t\t'toLocaleTimeString','toString','toTimeString',\n\t\t//'date',\n\t]\n\t// non-gecko: skip exslt\n\t\t// 1990759: ToDo: add isXSLT + isVer when the pref flips: dom.xslt.enabled\n\tif (isGecko) {methods.push('exslt')} else {addDisplay(4, METRIC +'_exslt', zNA)}\n\tmethods.sort()\n\tlet aLastMods = ['exslt','iframe','parseFromString','parseHTMLUnsafe'] // is lastModified source\n\tlet testdate\n\tlet tznShort = {\n\t\tAKST: '-09:00', AKDT: '-08:00',\n\t\tAST: '-04:00', ADT: '-03:00',\n\t\tCST: '-06:00', CDT: '-05:00',\n\t\tEST: '-05:00', EDT: '-04:00',\n\t\tHAST: '-10:00', HADT: '-09:00',\n\t\tHST: '-10:00', HDT: '-09:00',\n\t\tMST: '-07:00', MDT: '-06:00',\n\t\tPST: '-08:00', PDT: '-07:00',\n\t\tUTC: '+00:00', GMT: '+00:00', GMT0: '+00:00',\n\t}\n\n\tfunction create_offset() {\n\t\t// if we don't have a minutekey but we do have a non-exslt lastmod, we can\n\t\t// check timezones and compare/calulate to determine a minutekey/offset\n\t\tlet key = oData.hasLastMod[0]\n\t\tif (undefined == key) {return}\n\t\tlet aTZs = [\n\t\t\t// these are timezones, not short timezonenames\n\t\t\t'GMT','GMT+1','GMT+2','GMT+3','GMT+4','GMT+5','GMT+6','GMT+7','GMT+8','GMT+9','GMT+10','GMT+11','GMT+12',\n\t\t\t'GMT-1','GMT-2','GMT-3','GMT-4','GMT-5','GMT-6','GMT-7','GMT-8','GMT-9','GMT-10','GMT-11','GMT-12','GMT-13','GMT-14',\n\t\t]\n\t\tlet option = {\n\t\t\tday: '2-digit', month: '2-digit', year: 'numeric',\n\t\t\thour12: false, hour: '2-digit', minute: 'numeric', second: 'numeric',\n\t\t\ttimeZoneName: 'short'\n\t\t}\n\t\t// note: testdate was already set in get_values so it's identical here\n\t\t// note: aTZs covers the hardcoded values in tznShort\n\t\t\t// note: this only covers full hours if an exact match: we will use 10's of minutes accuracy\n\t\t\t// to reduce chances of a digit having ticked over\n\t\t\t// lastMods iframe/parse* raw format's datetime components matches formatter\n\t\tlet exactmatch = oData.raw[key].slice(0,15),\n\t\t\thourmatch = oData.raw[key].slice(0,13)\n\t\tlet isPartial = false, offset, value\n\t\tfor (let i = 0; i < aTZs.length; i++) {\n\t\t\ttry {\n\t\t\t\toption.timeZone = 'Etc/'+ aTZs[i]\n\t\t\t\tlet formatter = new Intl.DateTimeFormat('en', option)\n\t\t\t\tvalue = formatter.format(testdate).replace(',','')\n\t\t\t\toffset = value.split(' ')[2]\n\t\t\t\tif (value.slice(0,13) == hourmatch) {\n\t\t\t\t\tif (value.slice(0,15) == exactmatch) {\n\t\t\t\t\t\t//console.log(value.slice(0,15), 'exact match', offset)\n\t\t\t\t\t\t// exact match\n\t\t\t\t\t\toData.minutekey = key\n\t\t\t\t\t\toData.offset[key] = offset\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else {\n\t\t\t\t\t\t//console.log(value.slice(0,15), 'hour match', offset)\n\t\t\t\t\t\t// hour match\n\t\t\t\t\t\t// this works because we use the extremes of +12/-14 which means we cover all\n\t\t\t\t\t\t// possible day + hour combos (partials would be inside those extremes), so one\n\t\t\t\t\t\t// of them must match: we just need to add or subtract from it\n\t\t\t\t\t\tisPartial = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tconsole.log(e+'')\n\t\t\t}\n\t\t}\n\t\tif (isPartial) {\n\t\t\t// calculate minute diff in 15's, ignore seconds\n\t\t\tif ('UTC' == offset) {offset = 'GMT+0'}\n\t\t\tlet sign = offset.slice(3,4)\n\t\t\tlet offsetHrs = offset.slice(4,offset.length) * 1\n\t\t\tlet expectedMins = oData.raw[key].slice(14,16) * 1\n\t\t\tlet partialMins = value.slice(14,16) * 1\n\t\t\tlet diff = Math.round((expectedMins - partialMins) / 15) * 15\n\t\t\tlet newoffset\n\t\t\t/*\n\t\t\tconsole.log(oData.raw[key], key)\n\t\t\tconsole.log(value)\n\t\t\tconsole.log(sign, offsetHrs, expectedMins, partialMins, diff)\n\t\t\t//*/\n\t\t\tif ('+' == sign) {\n\t\t\t\tif (diff < 0) {\n\t\t\t\t\tnewoffset = 'GMT' + sign + ((offsetHrs - 1)+'').padStart(2, '0') +':' + (60 + diff)\n\t\t\t\t} else {\n\t\t\t\t\tnewoffset = 'GMT' + sign + (offsetHrs+'').padStart(2, '0') +':' + diff\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// negative sign we only have ±30 diffs. The code below _should_ handle ±15/±45\n\t\t\t\tif (diff < 0) {\n\t\t\t\t\tnewoffset = 'GMT' + sign + (offsetHrs+'').padStart(2, '0') +':' + Math.abs(diff)\n\t\t\t\t} else {\n\t\t\t\t\tnewoffset = 'GMT' + sign + ((offsetHrs - 1)+'').padStart(2, '0') +':' + (60 - diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (undefined !== newoffset) {\n\t\t\t\toData.minutekey = key\n\t\t\t\toData.offset[key] = newoffset\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tfunction format_offset(str) {\n\t\tif (!str.includes('GMT')) {return str}\n\t\t// format en short timezonename into ±xx:xx format\n\t\tstr = str.slice(3)\n\t\tlet sign = str.slice(0,1), time = str.slice(1)\n\t\t/* toString example GMT1300 */\n\t\t/* other examples ['GMT+9:30','GMT+12','GMT-1','GMT+0']\t*/\n\t\tif (!time.includes(':') && 4 == time.length) {\n\t\t\ttime = time.slice(0,2)+':'+time.slice(2)\n\t\t}\n\t\tlet parts = time.split(':')\n\t\tlet hrs = parts[0].padStart(2,'0')\n\t\tlet mins = (undefined == parts[1] ? '00' : parts[1])\n\t\treturn sign + hrs +':'+ mins\n\t}\n\n\tfunction get_minutes(str) {\n\t\tif (undefined !== str) {\n\t\t\tif (undefined !== tznShort[str]) {\n\t\t\t\tstr = tznShort[str]\n\t\t\t} else if (str.includes('GMT')) {\n\t\t\t\tstr = format_offset(str)\n\t\t\t}\n\t\t\tlet minutes = ((str.slice(1,3) * 1)*60) + (str.slice(4,6)*1)\n\t\t\tlet sign = (str[0] == '+' ? (minutes == 0 ? '': '-') : '')\n\t\t\tminutes = minutes * ('-' == sign ? -1 : 1)\n\t\t\treturn [minutes, (str == '+00:00' ? '' : '['+ str +']')]\n\t\t} else {\n\t\t\treturn ''\n\t\t}\n\t}\n\n\tfunction get_month(src) {\n\t\tlet oMonths = {\n\t\t\tJan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06', \n\t\t\tJul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12', \n\t\t}\n\t\tlet month = oMonths[src]\n\t\treturn (undefined == month ? 'xx' : month)\n\t}\n\n\tfunction get_values(runNo) {\n\t\t// short timeZoneName exposes a GMT string in ~75% of timezones\n\t\t\t// which allows us more truthy offsets to fall back to\n\t\toData = {'_runNo': runNo, 'errors' : {}, 'format': {}, 'offset': {}, 'tampered': [], 'raw': {}} // reset\n\t\tlet option = {\n\t\t\tday: '2-digit', month: '2-digit', year: 'numeric',\n\t\t\thour12: false, hour: '2-digit', minute: 'numeric', second: 'numeric',\n\t\t\ttimeZoneName: 'short', timeZone: undefined,\n\t\t}\n\t\tlet formatter = new Intl.DateTimeFormat('en', option) // changing option does not affect our formatter\n\n\t\tlet id = 'iframelastmod'\n\t\ttestdate = new Date()\n\t\t// get values\n\t\tmethods.forEach(function(k){\n\t\t\tlet value\n\t\t\ttry {\n\t\t// DTF\n\t\t\t\tif ('timeZone' == k) {\n\t\t\t\t\tvalue = formatter.format(testdate).replace(',','')\n\t\t// last modified\n\t\t\t\t} else if ('exslt' == k) {\n\t\t\t\t\tlet xsltProcessor = new XSLTProcessor\n\t\t\t\t\txsltProcessor.importStylesheet(doc) // fragment sticky datetime is set here\n\t\t\t\t\tlet fragment = xsltProcessor.transformToFragment(doc, document) // toFragment is faster than toDocument\n\t\t\t\t\tvalue = fragment.childNodes[0].nodeValue\n\t\t\t\t} else if ('iframe' == k) {\n\t\t\t\t\tlet el = document.createElement('iframe')\n\t\t\t\t\tel.setAttribute('id', id)\n\t\t\t\t\tdocument.body.appendChild(el)\n\t\t\t\t\tlet target = el.contentDocument\n\t\t\t\t\tvalue = target.lastModified // contentWindow.document\n\t\t\t\t} else if ('parseFromString' == k) {\n\t\t\t\t\tvalue = (new DOMParser).parseFromString('','text/html').lastModified\n\t\t\t\t} else if ('parseHTMLUnsafe' == k) {\n\t\t\t\t\tvalue = Document.parseHTMLUnsafe('').lastModified\n\t\t// temporal\n\t\t\t\t} else if ('plainDateISO' == k) {\n\t\t\t\t\tvalue = Temporal.Now.plainDateISO().toString()\n\t\t\t\t} else if ('plainDateTimeISO' == k) {\n\t\t\t\t\tvalue = Temporal.Now.plainDateTimeISO().toString()\n\t\t\t\t} else if ('plainTimeISO' == k) {\n\t\t\t\t\tvalue = Temporal.Now.plainTimeISO().toString()\n\t\t\t\t\tvalue = value.slice(0,8)\n\t\t\t\t} else if ('zonedDateTimeISO' == k) {\n\t\t\t\t\tvalue = Temporal.Now.zonedDateTimeISO().toString()\n\t\t// to*string standalone\n\t\t\t\t} else if ('toLocaleDateString' == k) {\n\t\t\t\t\toption.timeZone = undefined\n\t\t\t\t\tvalue = testdate.toLocaleDateString('en', option).replace(',','')\n\t\t\t\t} else if ('toLocaleString' == k) {\n\t\t\t\t\toption.timeZone = undefined\n\t\t\t\t\tvalue = testdate.toLocaleString('en', option).replace(',','')\n\t\t\t\t} else if ('toLocaleTimeString' == k) {\n\t\t\t\t\tvalue = testdate.toLocaleTimeString('en', option).replace(',','')\n\t\t\t\t} else if ('toTimeString' == k) {\n\t\t\t\t\tlet parts = testdate.toTimeString().split(' ')\n\t\t\t\t\tvalue = parts[0] +' '+ parts[1]\n\t\t// no formatting\n\t\t\t\t} else if ('toDateString' == k) {\n\t\t\t\t\t// https://searchfox.org/firefox-main/source/js/src/tests/test262/built-ins/Date/prototype/toDateString/format.js\n\t\t\t\t\t// dateRegExp = /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{2} [0-9]{4}$/\n\t\t\t\t\t// gecko format: e.g. \"Fri Mar 20 2026\" | blink seems to be the same, tested a few locales+timezone mixes\n\t\t\t\t\tlet parts = testdate.toDateString().split(' ')\n\t\t\t\t\tvalue = get_month(parts[1]) +'/'+ parts[2] +'/'+ parts[3]\n\t\t\t\t} else if ('toString' == k) {\n\t\t\t\t\t// note: contents of the string from toString() are implementation-dependent\n\t\t\t\t\t\t// using formatter (DTF) defeats the purpose of the test\n\t\t\t\t\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString\n\t\t\t\t\t// \"it joins the string representation specified in toDateString() and toTimeString()\"\n\t\t\t\t\tlet parts = testdate.toString().replace(',','').split(' ')\n\t\t\t\t\tvalue = get_month(parts[1]) +'/'+ parts[2] +'/'+ parts[3] +' '+ parts[4]\n\t\t\t\t\tif (undefined !== parts[5]) {value += ' '+ parts[5]}\n\t\t\t\t}\n\t\t\t\t//if ('iframe' !== k) {foo++} // simulate only getting offset from a non-exslt lastMod\n\t\t\t\t// typecheck\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('string' !== typeCheck) {\n\t\t\t\t\tthrow zErrType + typeCheck\n\t\t\t\t} else {\n\t\t\t\t\t/* test\n\t\t\t\t\tif (!aLastMods.includes(k)) {\n\t\t\t\t\t\tlet index = value.indexOf(':')\n\t\t\t\t\t\tif (-1 !== index) {\n\t\t\t\t\t\t\t// shift minutes\n\t\t\t\t\t\t\tlet mins = value.slice(index +1, index +3) * 1\n\t\t\t\t\t\t\tmins = mins + (mins < 30 ? 1 : -1) // we only need move 1 minute: always 2 digits\n\t\t\t\t\t\t\tvalue = value.slice(0, index +1) + mins + value.slice(index +3, value.length)\n\t\t\t\t\t\t\t// shift seconds\n\t\t\t\t\t\t\tlet secs = value.slice(index +4, index +6) * 1\n\t\t\t\t\t\t\tlet secShift = 11\n\t\t\t\t\t\t\tsecs = secs + (secs < 30 ? secShift : secShift * -1) // always 2 digits\n\t\t\t\t\t\t\tsecs = (secs+'').padStart(2,'0') // just in case\n\t\t\t\t\t\t\tvalue = value.slice(0, index +4) + secs + value.slice(index +6, value.length)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t//*/\n\t\t\t\t\toData.raw[k] = value\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\t// the error differs if the console is open vs closed\n\t\t\t\tif ('parseHTMLUnsafe' == k) {\n\t\t\t\t\tif (e.name == 'NS_ERROR_UNEXPECTED') {e = 'Error: Permission denied to access property \\\"lastModified\\\"'}\n\t\t\t\t}\n\t\t\t\toData.errors[k] = e+''\n\t\t\t}\n\t\t})\n\t\tremoveElementFn(id)\n\t\t// partials (date or time only) need the missing corresponding part\n\t\t// we use lastModified items so if partials are legit they can match\n\t\ttry {\n\t\t\tlet partialkey, dateString, timeString\n\t\t\tfor (let i=0; i < aLastMods.length; i++) {\n\t\t\t\tlet key = aLastMods[i]; if (undefined !== oData.raw[key]) {partialkey = key; break}\n\t\t\t}\n\t\t\tif (undefined !== partialkey) {\n\t\t\t\toData['partialkey'] = partialkey\n\t\t\t\tdateString = oData.raw[partialkey]\n\t\t\t\tif ('exslt' !== partialkey) {\n\t\t\t\t\tdateString = dateString.replace(/(\\d{2})\\/(\\d{2})\\/(\\d{4})/, '$3-$1-$2')\n\t\t\t\t}\n\t\t\t\ttimeString = dateString.slice(11,19)\n\t\t\t\tdateString = dateString.slice(0,10)\n\t\t\t\tlet aPartial = ['toTimeString','plainTimeISO','toDateString','plainDateISO']\n\t\t\t\taPartial.forEach(function(item) {\n\t\t\t\t\t// if undefined we must have had an error already\n\t\t\t\t\tif (undefined !== oData.raw[item]) {\n\t\t\t\t\t\tif (item.includes('Time')) {\n\t\t\t\t\t\t\toData.raw[item] = dateString +' '+ oData.raw[item]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toData.raw[item] = oData.raw[item] +' '+ timeString\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tconsole.log(e)\n\t\t}\n\t\t// check validity and format\n\t\tfor (const k of Object.keys(oData.raw)) {\n\t\t\tlet formatted, src = oData.raw[k]\n\t\t\tif ('exslt' == k) {\n\t\t\t\toData.offset[k] = src.slice(-6)\n\t\t\t\tformatted = src.slice(0,-10).replace('T',' ')\n\t\t\t} else if ('zonedDateTimeISO' == k || 'plainDateTimeISO' == k) {\n\t\t\t\t// we only want the first 19 chars\n\t\t\t\tformatted = src.slice(0,19).replace('T',' ')\n\t\t\t\t// remember offsets\n\t\t\t\tif ('zonedDateTimeISO' == k) {\n\t\t\t\t\tlet end = src.indexOf('[')\n\t\t\t\t\toData.offset[k] = src.slice(end - 6, end)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tformatted = src.replace(/(\\d{2})\\/(\\d{2})\\/(\\d{4})/, '$3-$1-$2')\n\t\t\t\t// leverage short timezonename\n\t\t\t\tlet shortname = formatted.split(' ')[2]\n\t\t\t\tformatted = formatted.slice(0,19)\n\t\t\t\tif (undefined !== shortname) {\n\t\t\t\t\tif (shortname.includes('GMT') || undefined !== tznShort[shortname]) {oData.offset[k] = shortname}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (checkValidDate(k, formatted)) {oData.format[k] = formatted}\n\t\t}\n\t}\n\n\tfunction checkValidDate(method, value) {\n\t\ttry {\n\t\t\tif (new Date(value) +'' == 'Invalid Date') {\n\t\t\t\toData.errors[method] = 'Invalid Date: ' + value\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t} catch(e) {\n\t\t\toData.errors[method] = e+''\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfunction checkMatch(runNo) {\n\t\t// set minutekey\n\t\t\t// if we have one at least one lastModified exslt/iframe/parseFromString/parseHTMLUnsafe\n\t\t\t// and we have an offset that matches, then we know the real value\n\t\tlet minutekey, checkkey\n\t\tlet oCheck = {hasLastMod: [], hasOffset: []}\n\t\taLastMods.forEach(function(item){if (undefined !== oData.format[item]) {oCheck.hasLastMod.push(item)}})\n\t\tfor (const k of Object.keys(oData.offset)) {oCheck.hasOffset.push(k)}\n\t\tfor (const j of Object.keys(oCheck)) {\n\t\t\toData[j] = []\n\t\t\tlet lookup = 'hasLastMod' == j ? 'format' : 'offset'\n\t\t\tlet array = oCheck[j]\n\t\t\tarray.forEach(function(item){if (undefined !== oData[lookup][item]) {oData[j].push(item)}})\n\t\t}\n\n\t\tif (Object.keys(oData.hasLastMod).length && Object.keys(oData.hasOffset).length) {\n\t\t\t// last mods can't be tampered with\n\t\t\tif (oData.hasOffset.includes('exslt')) {\n\t\t\t\tminutekey = 'exslt' // if we have exslt offset we also have the format\n\t\t\t} else {\n\t\t\t\tlet aOff = oData.hasOffset, aLast = oData.hasLastMod\n\t\t\t\t// loop valid items which have an offset\n\t\t\t\tfor (let i = 0; i < aOff.length; i++) {\n\t\t\t\t\tif (undefined !== minutekey) {break}\n\t\t\t\t\tlet offsetkey = aOff[i], got = oData.format[offsetkey]\n\t\t\t\t\t// loop valid lastModified items which should all be truthy\n\t\t\t\t\tfor (let j = 0; j < aLast.length; j++) {\n\t\t\t\t\t\tlet lastkey = aLast[j], expected = oData.format[lastkey]\n\t\t\t\t\t\tif (expected == got) {minutekey = offsetkey; break}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// are they all the same\n\t\tlet aTmp = []\n\t\tfor (const k of Object.keys(oData.format)) {aTmp.push(oData.format[k])}\n\t\taTmp = dedupeArray(aTmp)\n\t\tif (undefined !== minutekey) {\n\t\t\toData['minutekey'] = minutekey\n\t\t\tcheckkey = minutekey\n\t\t}\n\t\tif (aTmp.length == 1) {return true}\n\t\tif (undefined == checkkey) {\n\t\t\t// just because we don't have a minutekey, doesn't mean we can't compare to a lastmodifed\n\t\t\tcheckkey = oCheck.hasLastMod[0]\n\t\t}\n\t\tif (undefined == checkkey) {return aTmp.length == 1}\n\t\toData['checkkey'] = checkkey\n\n\t\t// here is where we catch the tampering\n\t\t\t// some diffs + we have a checkkey which we consider truthy\n\t\t\t// compare the rest to checkkey (which we treat as truthy)\n\t\t\t// get checkkey parts\n\t\tlet mValue = oData.format[checkkey],\n\t\t\tmParts = mValue.split(' '),\n\t\t\tmDate = mParts[0],\n\t\t\tmTime = mParts[1].split(':')\n\t\tfor (const k of Object.keys(oData.format)) {\n\t\t\tlet isDiff = false // reset each check, assume false\n\t\t\tif (k !== checkkey) { // exempt checkkey\n\t\t\t\ttry {\n\t\t\t\t\tlet kValue = oData.format[k],\n\t\t\t\t\t\tkParts = kValue.split(' '),\n\t\t\t\t\t\tkDate = kParts[0],\n\t\t\t\t\t\tkTime = kParts[1].split(':')\n\t\t\t\t\t// compare k to m(inutekey)\n\t\t\t\t\tif (kValue == mValue) {\n\t\t\t\t\t\t// perfect match\n\t\t\t\t\t} else if (kDate !== mDate) {\n\t\t\t\t\t\t// different date\n\t\t\t\t\t\tisDiff = true // this covers toDateString and plainDateISO\n\t\t\t\t\t\t//if (isFile && 2 == runNo) {console.log('date changed', k, kDate)}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// time diff only\n\t\t\t\t\t\tlet tmpDiff = {\n\t\t\t\t\t\t\th: (kTime[0] * 1) - (mTime[0] * 1),\n\t\t\t\t\t\t\tmin: (kTime[1] * 1) - (mTime[1] * 1),\n\t\t\t\t\t\t\ts: (kTime[2] * 1) - (mTime[2] * 1),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// allow 10 seconds: jank, also leap seconds\n\t\t\t\t\t\t// abs !important to cover being ahead or behind\n\t\t\t\t\t\tlet secondsDiff = Math.abs((tmpDiff.h * 3600) + (tmpDiff.min * 60) + tmpDiff.s)\n\t\t\t\t\t\tif (secondsDiff > 10) {\n\t\t\t\t\t\t\tisDiff = true\n\t\t\t\t\t\t\t//if (isFile && 2 == runNo) {console.log('secondsDiff', secondsDiff, k)}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (isDiff) {oData.tampered.push(k)} //METRIC +'_'+ k)}\n\t\t\t\t} catch(e) {\n\t\t\t\t\tconsole.log(e+'')\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn 0 == oData.tampered.length\n\t}\n\n\tfunction display_values() {\n\t\t// all valid dates are in format, everything else is in errors\n\t\tfor (const k of Object.keys(oData.format)) {\n\t\t\tlet n = METRIC +'_'+ k, isLies = false, extra = ''\n\t\t\tlet value = oData.format[k]\n\t\t\t// style + record lies\n\t\t\tif (oData.tampered.includes(k)) {\n\t\t\t\tisLies = true\n\t\t\t\tlet tmpvalue = value\n\t\t\t\t// tidy up partial\n\t\t\t\tif ('toTimeString' == k || 'plainTimeISO' == k) {tmpvalue = tmpvalue.slice(-8)\n\t\t\t\t} else if ('toDateString' == k || 'plainDateISO' == k) {tmpvalue = tmpvalue.slice(0,10)\n\t\t\t\t}\n\t\t\t\tlog_known(4, n, tmpvalue)\n\t\t\t}\n\t\t\t// tidy up partial\n\t\t\tif ('toTimeString' == k || 'plainTimeISO' == k) {\n\t\t\t\tvalue = value.slice(-8)\n\t\t\t\tdom[k +'spaces'] = ' '.repeat(10)\n\t\t\t} else if ('toDateString' == k || 'plainDateISO' == k) {value = value.slice(0,10)}\n\t\t\t// add extra display info\n\t\t\tlet offsetStr = oData.offset[k]\n\t\t\tif (undefined !== offsetStr & 'string' == typeof offsetStr) {extra = ' '+ offsetStr}\n\t\t\tvalue += extra\n\t\t\taddDisplay(4, n, value,'','', isLies)\n\t\t}\n\t\tfor (const k of Object.keys(oData.errors)) {\n\t\t\taddDisplay(4, METRIC +'_'+ k, log_error(4, METRIC +'_'+ k, oData.errors[k]))\n\t\t}\n\t}\n\n\t// run, try to get isMatch\n\tget_values(1)\n\tlet isMatch = checkMatch(1)\n\tif (!isMatch) {\n\t\t// ~0.5 ms to grab our mods: 1 in 86,400 seconds tick over a day\n\t\t// so we'd have to be really unlucky to hit this: try again\n\t\tget_values(2)\n\t\tisMatch = checkMatch(2)\n\t}\n\toData['isMatch'] = isMatch\n\tdisplay_values()\n\tif (undefined == oData.minutekey) {create_offset()} // after display so we don't add offsets to a lastmod\n\n\tlet finalvalue, finaldisplay, utcValue\n\tlet errCount = Object.keys(oData.errors).length\n\tlet tamperCount = oData.tampered.length\n\tif (undefined !== oData.minutekey) {\n\t\tlet finaldata = get_minutes(oData.offset[oData.minutekey])\n\t\tfinalvalue = finaldata[0]\n\t\tfinaldisplay = finaldata.join(' ')\n\t\tutcValue = oData.format[oData.minutekey]\n\n\t\tif (0 == tamperCount && 0 == errCount) {\n\t\t\t// no lies + no errors\n\t\t\tnotation = tz_green\n\t\t} else if (gRun) {\n\t\t\t// health lookup\n\t\t\tlet aHealth = []\n\t\t\tif (errCount > 0) {aHealth.push(errCount + ' error' + (errCount == 1 ? '' : 's'))}\n\t\t\tif (tamperCount > 0) {aHealth.push(tamperCount + ' mismatch' + (tamperCount == 1 ? '' : 'es'))}\n\t\t\tif (gRun) {sDetail[isScope].lookup[METRIC] = aHealth.join(' | ')}\n\t\t}\n\t\taddDisplay(4, METRIC, finaldisplay,'', notation)\n\t\taddData(4, METRIC, finalvalue)\n\t} else {\n\t\t// all errors\n\t\tif (methods.length == errCount) {\n\t\t\taddBoth(4, METRIC, zErr +'s','', notation, zErr)\n\t\t} else {\n\t\t\t// 4 scenarios with isMatch/isTamper\n\t\t\t\t// if no checkkey then we never compared and isMatch is default false\n\t\t\tlet isTamper = tamperCount > 0\n\t\t\tfinalvalue = (isMatch || undefined == oData.checkkey) ? 'unknown' : 'mixed'\n\t\t\taddBoth(4, METRIC, finalvalue,'', notation,'', isTamper)\n\t\t}\n\t}\n\t//if (isFile) {console.log('timezone offset\\n', oData)}\n\tlog_perf(4, METRIC, t0)\n\treturn {'nowValue': finalvalue, 'utcValue': utcValue, 'tampered': oData.tampered}\n}\n\nfunction get_timezone_offsets(METRIC, nowValue, utcValue) {\n\tlet t0 = nowFn(), notation = tz_red\n\n\tlet years = [1879, 1952, 1976, 2025]\n\tlet days = {\n\t\t// to make sure we don't change years or months when a day or two ticks over\n\t\t// use the 15th - this makes get* and getUTC* PoCs possible\n\t\t'January 15': {numbers: [1,15], str :'01-15'},\n\t\t'July 15': {numbers: [7,15], str: '07-15'}\n\t}\n\tlet aMethods = [\n\t\t//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_components_and_time_zones\n\t\t'components','components_utc','date','date.parse','date.valueOf','getTime',\n\t\t'getTimezoneOffset','offsetNanoseconds','timeZoneName','Symbol.toPrimitive',\n\t]\n\tlet oMultiplier = {\n\t\t// year + month: our test dates are middle of the month: legit components will never differ\n\t\t// fake values that differ we don't need to be precise (because they're garbage) so no nee to\n\t\t// computer variable days in years and months, we can just use constants 365 + 31\n\t\t'1': 60000 * 60 * 24 * 365, // year\n\t\t'2': 60000 * 60 * 24 * 31, // month\n\t\t'3': 60000 * 60 * 24, // day\n\t\t'4': 60000 * 60, // hour\n\t\t'5': 60000, // minute\n\t\t'6': 1000, // second\n\t\t'7': 1, // ms\n\t}\n\tlet tznShort = {\n\t\t// hardcoded\n\t\tAKST: 540, AKDT: 480,\n\t\tAST: 240, ADT: 180,\n\t\tCST: 360, CDT: 300,\n\t\tEST: 300, EDT: 240,\n\t\tHAST: 600, HADT: 540,\n\t\tHST: 600, HDT: 540,\n\t\tMST: 420, MDT: 360,\n\t\tPST: 480, PDT: 420,\n\t\tUTC: 0, GMT: 0, 'GMT+0': 0,\n\t}\n\tlet oList = {}\n\tyears.forEach(function(year) {oList[year] = days})\t\n\t// if we know the real current offset (nowValue) we can add a silent non-collected\n\t// now datetime and compare it's value to nowValue to determine if tampered\n\tif ('number' == typeFn(nowValue)) {oList['now'] = {'now': ''}}\n\tlet oData = {'calc': {}, 'display': {}, 'errors': {}, 'hashes': {'all': {}, 'valid': {}},\n\t\t'lies': {}, 'math': {'1.utc': {}, '2.timezone': {}}, 'now': {}, 'numbers': {}, 'utc': {}\n\t}\n\taMethods.forEach(function(method) {\n\t\toData.calc[method] = {}\n\t\tyears.forEach(function(year) {oData.calc[method][year] = []})\n\t})\n\tlet tznOption = {\n\t\tday: '2-digit', month: '2-digit', year: 'numeric',\n\t\thour12: false, hour: '2-digit', minute: 'numeric', second: 'numeric',\n\t\ttimeZoneName: 'short',\n\t}\n\tlet oExpected = {\n\t\t'1879-01-15': {'components': '1879 0 15', 'other': -2870420400000},\n\t\t'1879-07-15': {'components': '1879 6 15', 'other': -2854782000000},\n\t\t'1952-01-15': {'components': '1952 0 15', 'other': -566823600000},\n\t\t'1952-07-15': {'components': '1952 6 15', 'other': -551098800000},\n\t\t'1976-01-15': {'components': '1976 0 15', 'other': 190558800000},\n\t\t'1976-07-15': {'components': '1976 6 15', 'other': 206283600000},\n\t\t'2025-01-15': {'components': '2025 0 15', 'other': 1736946000000},\n\t\t'2025-07-15': {'components': '2025 6 15', 'other': 1752584400000},\n\t}\n\n\ttry {\n\t\tlet formatter = new Intl.DateTimeFormat('en', tznOption)\n\t\tfor (const year of Object.keys(oList)) {\n\t\t\tlet isNow = 'now' == year\n\t\t\tObject.keys(oList[year]).forEach(function(day) {\n\t\t\t\tlet isFirst = (year == years[0] && day == 'January 15')\n\t\t\t\tlet test, control, key\n\t\t\t\tif (!isNow) {\n\t\t\t\t\tlet datetime = day +', '+ year +' 13:00:00'\n\t\t\t\t\tcontrol = new Date(datetime +' UTC')\n\t\t\t\t\ttest = new Date(datetime)\n\t\t\t\t\tkey = year +'-'+ days[day].str\n\t\t\t\t\toData.math['1.utc'][key] = {};  oData.math['2.timezone'][key] = {}; \n\t\t\t\t} else {\n\t\t\t\t\t// if we have a nowValue, we had a minutekey and a formatted string\n\t\t\t\t\ttest = new Date(utcValue)\n\t\t\t\t\tcontrol = new Date(utcValue +' UTC')\n\t\t\t\t}\n\t\t\t\tif (runSE) {foo++} else if (runST) {test = NaN}\n\t\t\t\taMethods.forEach(function(method) {\n\t\t\t\t\tif (undefined == oData.errors[method]) {\n\t\t\t\t\t\tlet offset, k = 60000, oDiffs, utc, time\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif ('getTimezoneOffset' == method) {\n\t\t\t\t\t\t\t\toffset = test.getTimezoneOffset()\n\t\t\t\t\t\t\t\tk = 1\n\t\t\t\t\t\t\t} else if ('timeZoneName' == method) {\n\t\t\t\t\t\t\t\t// it doesn't really matter what method we use since they're all exposed elsewhere\n\t\t\t\t\t\t\t\tlet tznDate = formatter.format(test).replace(',','')\n\t\t\t\t\t\t\t\ttime = tznDate.split(' ')[2]\n\t\t\t\t\t\t\t\tk = 1\n\t\t\t\t\t\t\t\tif (undefined !== tznShort[time]) {\n\t\t\t\t\t\t\t\t\toffset = tznShort[time]\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif ('GMT' !== time.slice(0,3)) {throw zErrInvalid + time}\n\t\t\t\t\t\t\t\t\t// hrs, minutes, seconds\n\t\t\t\t\t\t\t\t\tlet sign = time.includes('-') ? 1 : -1\n\t\t\t\t\t\t\t\t\tlet value = time.replace('GMT','')\n\t\t\t\t\t\t\t\t\tvalue = value.replace('-','')\n\t\t\t\t\t\t\t\t\tvalue = value.replace('+','')\n\t\t\t\t\t\t\t\t\tlet parts = value.split(':')\n\t\t\t\t\t\t\t\t\toffset = parts[0] * 60\n\t\t\t\t\t\t\t\t\tif (undefined !== parts[1]) { offset += parts[1] * 1} // minutes\n\t\t\t\t\t\t\t\t\tif (undefined !== parts[2]) { offset += (parts[2] * 1)/60} // seconds\n\t\t\t\t\t\t\t\t\toffset = offset * sign\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif ('date.parse' == method) {\n\t\t\t\t\t\t\t\t\ttime = Date.parse(test); utc = Date.parse(control)\n\t\t\t\t\t\t\t\t} else if ('date.valueOf' == method) {\n\t\t\t\t\t\t\t\t\ttime = test.valueOf(); utc = control.valueOf()\n\t\t\t\t\t\t\t\t} else if ('Symbol.toPrimitive' == method) {\n\t\t\t\t\t\t\t\t\ttime = test[Symbol.toPrimitive]('number')\n\t\t\t\t\t\t\t\t\tutc = control[Symbol.toPrimitive]('number')\n\t\t\t\t\t\t\t\t\toffset = time - utc\n\t\t\t\t\t\t\t\t} else if ('getTime' == method) {\n\t\t\t\t\t\t\t\t\ttime = test.getTime(); utc = control.getTime()\n\t\t\t\t\t\t\t\t} else if ('date' == method) {\n\t\t\t\t\t\t\t\t\tutc = control * 1; time = test * 1\n\t\t\t\t\t\t\t\t} else if ('offsetNanoseconds' == method) {\n\t\t\t\t\t\t\t\t\t// instant: YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone_id]\n\t\t\t\t\t\t\t\t\t// e.g. 1879-01-01T13:00Z\n\t\t\t\t\t\t\t\t\tlet tzid = Temporal.Now.timeZoneId()\n\t\t\t\t\t\t\t\t\tlet instantStr = isNow ? utcValue +'Z' : year +'-'+ days[day].str +'T13:00Z'\n\t\t\t\t\t\t\t\t\tlet instant = Temporal.Instant.from(instantStr)\n\t\t\t\t\t\t\t\t\ttime = instant.toZonedDateTimeISO(tzid).offsetNanoseconds\n\t\t\t\t\t\t\t\t\t// UTC is always zero so we could hard-code this\n\t\t\t\t\t\t\t\t\t// BUT it's nice to catch any fuckery caqused by extensions\n\t\t\t\t\t\t\t\t\tutc = instant.toZonedDateTimeISO('UTC').offsetNanoseconds\n\t\t\t\t\t\t\t\t\toffset = (utc - time) / 1e6\n\t\t\t\t\t\t\t\t\t//if (!isNow) (offset = offset * 2) // mixed but no lies\n\t\t\t\t\t\t\t\t} else if ('components' == method) {\n\t\t\t\t\t\t\t\t\toDiffs = {\n\t\t\t\t\t\t\t\t\t\t'1': [test.getUTCFullYear(), control.getUTCFullYear()],\n\t\t\t\t\t\t\t\t\t\t'2': [test.getUTCMonth(), control.getUTCMonth()],\n\t\t\t\t\t\t\t\t\t\t'3': [test.getUTCDate(), control.getUTCDate()],\n\t\t\t\t\t\t\t\t\t\t'4': [test.getUTCHours(), control.getUTCHours()],\n\t\t\t\t\t\t\t\t\t\t'5': [test.getUTCMinutes(), control.getUTCMinutes()],\n\t\t\t\t\t\t\t\t\t\t'6': [test.getUTCSeconds(), control.getUTCSeconds()],\n\t\t\t\t\t\t\t\t\t\t'7': [test.getUTCMilliseconds(), control.getUTCMilliseconds()],\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\toffset = 0, utc = [], time = []\n\t\t\t\t\t\t\t\t\tfor (const k of Object.keys(oDiffs)) {\n\t\t\t\t\t\t\t\t\t\toffset += (oMultiplier[k] * (oDiffs[k][0] - oDiffs[k][1]))\n\t\t\t\t\t\t\t\t\t\tutc.push(oDiffs[k][1]) // control\n\t\t\t\t\t\t\t\t\t\ttime.push(oDiffs[k][0]) // test\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tutc = utc.join(' '); time = time.join(' ')\n\t\t\t\t\t\t\t\t} else if ('components_utc' == method) {\n\t\t\t\t\t\t\t\t\toDiffs = {\n\t\t\t\t\t\t\t\t\t\t'1': [test.getFullYear(), control.getFullYear()],\n\t\t\t\t\t\t\t\t\t\t'2': [test.getMonth(), control.getMonth()],\n\t\t\t\t\t\t\t\t\t\t'3': [test.getDate(), control.getDate()],\n\t\t\t\t\t\t\t\t\t\t'4': [test.getHours(), control.getHours()],\n\t\t\t\t\t\t\t\t\t\t'5': [test.getMinutes(), control.getMinutes()],\n\t\t\t\t\t\t\t\t\t\t'6': [test.getSeconds(), control.getSeconds()],\n\t\t\t\t\t\t\t\t\t\t'7': [test.getMilliseconds(), control.getMilliseconds()],\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\toffset = 0, utc = [], time = []\n\t\t\t\t\t\t\t\t\tfor (const k of Object.keys(oDiffs)) {\n\t\t\t\t\t\t\t\t\t\t// this is reversed: we subtract time from utc\n\t\t\t\t\t\t\t\t\t\toffset += (oMultiplier[k] * (oDiffs[k][0] - oDiffs[k][1]))\n\t\t\t\t\t\t\t\t\t\tutc.push(oDiffs[k][0]) // reversed so we use test\n\t\t\t\t\t\t\t\t\t\ttime.push(oDiffs[k][1]) // control\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tutc = utc.join(' '); time = time.join(' ')\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet isTZN = 'timeZoneName' == method\n\t\t\t\t\t\t\tif (undefined == offset) {offset = time - utc}\n\t\t\t\t\t\t\tif (isNow) {\n\t\t\t\t\t\t\t\toData.now[method] = offset/k\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (undefined !== utc) {\n\t\t\t\t\t\t\t\t\tlet expected, isUTCMatch = true, isPartial = false\n\t\t\t\t\t\t\t\t\toData.math['1.utc'][key][method] = utc\n\t\t\t\t\t\t\t\t\toData.math['2.timezone'][key][method] = time\n\t\t\t\t\t\t\t\t\t// check for utc tampering\n\t\t\t\t\t\t\t\t\tif ('1879' == year &&'date.parse' == method) {\n\t\t\t\t\t\t\t\t\t\texpected = oExpected[key].other\n\t\t\t\t\t\t\t\t\t\t// only old-timey years have partial minutes and only partial minutes are offset from expected\n\t\t\t\t\t\t\t\t\t\tif (Number.isInteger(offset/k)) {\n\t\t\t\t\t\t\t\t\t\t\tisUTCMatch = utc == expected\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t// can't match expected (0 diff) and diff within 60000\n\t\t\t\t\t\t\t\t\t\t\tisPartial = true\n\t\t\t\t\t\t\t\t\t\t\tlet diff = Math.abs(expected - utc)\n\t\t\t\t\t\t\t\t\t\t\tisUTCMatch = diff < 60000 && diff !== 0\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif ('offsetNanoseconds' == method) {expected = 0\n\t\t\t\t\t\t\t\t\t\t} else if (method.includes('components')) {expected = oExpected[key].components +' 13 0 0 0'\n\t\t\t\t\t\t\t\t\t\t} else {expected = oExpected[key].other}\n\t\t\t\t\t\t\t\t\t\tif (undefined !== expected) {isUTCMatch = utc == expected}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t// log tampering\n\t\t\t\t\t\t\t\t\tif (!isUTCMatch) {\n\t\t\t\t\t\t\t\t\t\toData.lies[method] = ['utc']\n\t\t\t\t\t\t\t\t\t\tif (undefined == oData.utc[method]) {oData.utc[method] = {}}\n\t\t\t\t\t\t\t\t\t\toData.utc[method][key] = ['expected'+ (isPartial ? ' ±60000' : ''), expected, 'got', utc]\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (isTZN) {\n\t\t\t\t\t\t\t\t\toData.math['2.timezone'][key][method] = time\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (isFirst) {\n\t\t\t\t\t\t\t\t\tlet typeCheck = typeFn(offset)\n\t\t\t\t\t\t\t\t\t//console.log(method, typeCheck, offset)\n\t\t\t\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\toData.calc[method][year].push(offset/k)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\toData.errors[method] = log_error(4, METRIC +'_'+ method, e)\n\t\t\t\t\t\t\tdelete oData.calc[method]\n\t\t\t\t\t\t\tdelete oData.lies[method]\n\t\t\t\t\t\t\tdelete oData.utc[method]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\taddBoth(4, METRIC, log_error(4, METRIC, e),'', notation, zErr)\n\t\treturn {'health': false, 'hash': zErr}\n\t}\n\t// display errors\n\tfor (const k of Object.keys(oData.errors)) {addDisplay(4, METRIC +'_'+ k, oData.errors[k])}\n\t// exit if all errors\n\tif (Object.keys(oData.errors).length == aMethods.length) {\n\t\taddBoth(4, METRIC, zErr +'s', '', notation, zErr)\n\t\treturn {'health': false, 'hash': zErr}\n\t}\n\n\tfor (const k of Object.keys(oData.calc)) {\n\t\tlet tmpDisplay = []\n\t\tfor (const y of Object.keys(oData.calc[k])) {\n\t\t\toData.calc[k][y] = dedupeArray(oData.calc[k][y])\n\t\t\ttmpDisplay.push(oData.calc[k][y].join(', '))\n\t\t}\n\t\t// out of 339 unique results: 1 = 57 chars, 1 = 52 chars .. the rest are all 50 and under\n\t\t// will likely cause line overflow on android but it's cleaner to manage and visually see\n\t\tlet str = ''\n\t\tif (isDesktop && undefined !== oData.now[k]) {str = s99 +' ('+ oData.now[k] +')'+ sc}\n\t\toData.display[k] = tmpDisplay.join(' | ') + str\n\t\tif (undefined !== oData.now[k]) {\n\t\t\tif (oData.now[k] !== nowValue) {\n\t\t\t\tif (undefined == oData.lies[k]) {oData.lies[k] = []}\n\t\t\t\toData.lies[k].push('now')\n\t\t\t\tlog_known(4, METRIC +'_'+ k, tmpDisplay.join(' | '))\n\t\t\t}\n\t\t}\n\t\taddDisplay(4, METRIC +'_'+ k, oData.display[k], '','', undefined !== oData.lies[k])\n\t\tlet hash = mini(oData.calc[k])\n\t\tif (undefined == oData.hashes.all[hash]) {oData.hashes.all[hash] = [k]} else {oData.hashes.all[hash].push(k)}\n\t\tif (undefined == oData.lies[k]) {\n\t\t\tif (undefined == oData.hashes.valid[hash]) {oData.hashes.valid[hash] = [k]} else {oData.hashes.valid[hash].push(k)}\n\t\t}\n\t}\n\n\t// summarize oData.math etc\n\t// add mismatches\n\tif (Object.keys(oData.utc).length) {\n\t\toData.numbers['0.utc_tampered'] = {}\n\t\tfor (const k of Object.keys(oData.utc).sort()) {\n\t\t\toData.numbers['0.utc_tampered'][k] = oData.utc[k]\n\t\t}\n\t}\n\tfor (const type of Object.keys(oData.math)) {\n\t\t// don't include any data from items that eventually errored\n\t\tlet tmpobj = {}, newobj = {}\n\t\tfor (const d of Object.keys(oData.math[type])) {\n\t\t\tnewobj[d] = {}\n\t\t\tfor (const k of Object.keys(oData.math[type][d])) {\n\t\t\t\tif (undefined == oData.errors[k]) {\n\t\t\t\t\tlet itemdata = oData.math[type][d][k], itemhash = mini(itemdata) +' '\n\t\t\t\t\tif (undefined == newobj[d][itemhash]) {\n\t\t\t\t\t\tnewobj[d][itemhash] = {'data': itemdata, 'group': [k]}\n\t\t\t\t\t} else {newobj[d][itemhash].group.push(k)}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const d of Object.keys(newobj)) {\n\t\t\ttmpobj[d] = {}\n\t\t\tfor (const k of Object.keys(newobj[d])) {tmpobj[d][newobj[d][k].group.join(' ')] = newobj[d][k].data}\n\t\t}\n\t\toData.numbers[type] = tmpobj\n\t}\n\n\t// OFFSETS math data\n\t\t// we only need to check for any utc methods: note: math data and lies + utc are only recorded for methods that didn't error\n\tlet btnColor = 4\n\tif (isGecko) {btnColor = Object.keys(oData.utc).length ? 'bad' : 'good'}\n\taddDisplay(4, METRIC +'_data', addButton(btnColor, METRIC +'_data', 'data'))\n\tsDetail[isScope][METRIC +'_data'] = oData.numbers\n\n\t// health lookup\n\tlet errCount = Object.keys(oData.errors).length\n\tlet tamperCount = Object.keys(oData.lies).length\n\tif (gRun && errCount + tamperCount > 0) {\n\t\tlet aHealth = []\n\t\tif (errCount > 0) {aHealth.push(errCount + ' error' + (errCount == 1 ? '' : 's'))}\n\t\tif (tamperCount > 0) {aHealth.push(tamperCount + ' mismatch' + (tamperCount == 1 ? '' : 'es'))}\n\t\tif (gRun) {sDetail[isScope].lookup[METRIC] = aHealth.join(' | ')}\n\t}\n\n\t// summarize\n\tlet hash, data ='', btn ='', isLies = false\n\tlet isMixed = Object.keys(oData.hashes.all).length > 1\n\tlet isValid = Object.keys(oData.hashes.valid).length == 1\n\tif (isValid) {\n\t\t// we may have lies, but we also have a single valid (non-lie) hash\n\t\tfor (const h of Object.keys(oData.hashes.valid)) { // there's only one hash\n\t\t\tlet m = oData.hashes.valid[h][0] // get first method listed\n\t\t\thash = h; data = oData.calc[m]\n\t\t\tbtn = addButton(4, METRIC)\n\t\t\t// notation: no errors, no lies, i.e our single valid hash holds all methods\n\t\t\tif (oData.hashes.valid[h].length == aMethods.length) {notation = tz_green}\n\t\t}\n\t} else {\n\t\t// it may be feasible no lies detected but we have mixed results == clearly someone is lying\n\t\tisLies = tamperCount > 0 || isMixed\n\t\tif (isMixed) {\n\t\t\thash = 'mixed'\n\t\t} else {\n\t\t\tfor (const h of Object.keys(oData.hashes.all)) { // there's only one hash\n\t\t\t\tlet m = oData.hashes.all[h][0] // get first method listed\n\t\t\t\thash = h; data = oData.calc[m]\n\t\t\t\tbtn = addButton(4, METRIC)\n\t\t\t}\n\t\t}\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data, isLies)\n\n\t//if (isFile) {console.log('timezone offsets\\n', oData)}\n\tlog_perf(4, METRIC, t0)\n\t// return health as true if no errors and no lies and only one valid hash for all methods\n\treturn {'health': notation == tz_green, 'hash': hash}\n}\n\n/* l10n */\n\nfunction get_l10n_css(METRIC) {\n\tif (!isGecko) {addBoth(4, METRIC, zNA); return}\n\tlet hash, data = '', notation = '' //isLanguageSmart ? locale_red : ''\n\ttry {\n\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(4, METRIC, hash,'', notation)\n\treturn\n}\n\nconst get_l10n_media_messages = (METRIC) => new Promise(resolve => {\n\tif (!isGecko) {addBoth(4, METRIC, zNA); return resolve()}\n\n\t// https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/MediaDocument.properties\n\tlet hash, btn='', data = {}, notation = isLanguageSmart ? locale_red : ''\n\tlet aList = ['InvalidImage','ScaledImage']\n\tfor (const k of aList) {\n\t\tlet target = dom['tzp'+ k], title =''\n\t\ttry {\n\t\t\tif ('ScaledImage' == k) {\n\t\t\t\ttitle = target.contentWindow.document.title\n\t\t\t\ttitle = title.replace(k +'.png', '') // strip image name to reduce noise\n\t\t\t} else {\n\t\t\t\tconst image = target.contentWindow.document.querySelector('img')\n\t\t\t\ttitle = image.alt\n\t\t\t\ttitle = title.replace(target.src, '') // remove noise\n\t\t\t}\n\t\t\tdata[k] = title.trim()\n\t\t} catch(err) {\n\t\t\tlog_error(4, METRIC +'_'+ k, err)\n\t\t\tdata[k] = zErr\n\t\t}\n\t}\n\thash = mini(data); btn = addButton(4, METRIC)\n\tif (isLanguageSmart) {\n\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\tif (localesSupported[isLocaleAlt].m.includes(hash)) {notation = locale_green}\n\t\t}\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data)\n\treturn resolve()\n})\n\nfunction get_l10n_parsererror_direction(METRIC) {\n\t//if (!isGecko) {addBoth(4, METRIC, zNA); return}\n\t// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#error_handling\n\t\t// 1954813: \n\t\t// 1666613: currently relies on chrome://global/locale/intl.css\n\tlet value, data = '', notation = isLanguageSmart ? locale_red : ''\n\ttry {\n\t\tif (isGecko && isVer > 146) {\n\t\t\t// 1666613: no need to touch the dom in gecko: 0.17ms\n\t\t\tlet parser = (new DOMParser()).parseFromString('INVALID', 'text/xml')\n\t\t\tvalue = parser.firstChild.attributes[0].nodeValue\n\t\t} else {\n\t\t\t// 0.23ms\n\t\t\tlet target = dom.tzpDirection\n\t\t\ttarget.innerHTML = '<parsererror></parsererror>'\n\t\t\tvalue = getComputedStyle(target.children[0]).direction\n\t\t}\n\t\t// check\n\t\tif (runST) {value = ''} else if (runSI) {value = 'upsidedown'}\n\t\tlet typeCheck = typeFn(value)\n\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\tlet aGood = ['ltr','rtl']\n\t\tif (!aGood.includes(value)) {throw zErrInvalid +'expected '+ aGood.join(', ') +': got '+ value}\n\t\t// notation\n\t\t\t// since this is just BB (or FF en-US), we know only three locales are rtl: ar, fa, he\n\t\tif (isLanguageSmart && isLocaleValid) {\n\t\t\tlet aRTL = ['ar','fa','he']\n\t\t\tlet expected = aRTL.includes(isLocaleValue) ? 'rtl' : 'ltr'\n\t\t\tif (expected == value) {notation = locale_green}\n\t\t}\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\taddBoth(4, METRIC, value,'', notation, data)\n\treturn\n}\n\nconst get_l10n_reporting_messages = (METRIC) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API\n\t// dom.reporting.enabled\n\n\t// note: if/when the API is enabled, BB alpha can differ as deprecation warnings change\n\t// since we use isLanguageSmart (which can include non isBBESR), it's not worth coding\n\t// around that to remove false positives - we don't care about BB alpha health\n\tlet t0 = nowFn()\n\tfunction exit(res) {\n\t\ttry {observer.disconnect()} catch(e) {}\n\t\tif ('string' == typeFn(res)) {\n\t\t\t// undefined, n/a, errors\n\t\t\thash = res\n\t\t} else {\n\t\t\tif (hasReporting) {\n\t\t\t\tdata = isReporting\n\t\t\t} else {\n\t\t\t\t//console.log(res)\n\t\t\t\t// get up to x unique deprecated messages\n\t\t\t\tlet max = 10\n\t\t\t\tlet aSet = new Set()\n\t\t\t\tfor (let i=0; i < res.length; i++) {\n\t\t\t\t\tlet msg = res[i].body.message\n\t\t\t\t\tmsg = msg.replace('https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture','').trim()\n\t\t\t\t\taSet.add(msg)\n\t\t\t\t\tif (max == aSet.size) {break} // reruns accrue messages so break\n\t\t\t\t}\n\t\t\t\tdata = Array.from(aSet).sort()\n\t\t\t\tisReporting = data // cache the result for reruns\n\t\t\t}\n\t\t\tif (data.length) {\n\t\t\t\thash = mini(data); btn = addButton(4, METRIC) // + (hasReporting ? ' [cached]' : ' [generated]')\n\t\t\t} else {\n\t\t\t\thash = 'none'\n\t\t\t}\n\t\t\t// notate\n\t\t\tif (isLanguageSmart) {\n\t\t\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\t\t\tlet check = localesSupported[isLocaleAlt].r\n\t\t\t\t\t// if blank then it hasn't been translated yet\n\t\t\t\t\tif ('' == check) {check = localesSupported['en-US'].r}\n\t\t\t\t\tif (check.includes(hash)) {notation = locale_green}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\taddBoth(4, METRIC, hash, btn, notation, data)\n\t\tif (!hasReporting) {log_perf(4, METRIC, t0)}\n\t\treturn resolve()\n\t}\n\n\t// shipped in FF149+ 1976074\n\t// note: we don't need to notate for BB if the API is enabled or not, as that's covered by window properties\n\tlet hash, data ='', btn ='', notation = '', observer\n\tlet hasReporting = 'array' == typeFn(isReporting, true)\n\tif (!isGecko) {\n\t\texit(zNA)\n\t} else if (undefined == isReporting && undefined == window.ReportingObserver) {\n\t\texit('undefined')\n\t} else {\n\t\t// but we do notate when it is on to match locale\n\t\tnotation = isLanguageSmart ? locale_red : ''\n\t\tif (hasReporting) {\n\t\t\texit()\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tif (runSE) {foo++}\n\t\t\t\tobserver = new ReportingObserver((reports, observer) => {exit(reports)}, {types: ['deprecation'], buffered: true})\n\t\t\t\tobserver.observe()\n\t\t\t} catch(e) {\n\t\t\t\tdata = zErrLog; exit(e+'')\n\t\t\t}\n\t\t}\n\t}\n})\n\nfunction get_l10n_validation_messages(METRIC) {\n\t// https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/dom/dom.properties\n\n\tconst aNames = ['BadInputNumber','CheckboxMissing','DateTimeRangeOverflow','DateTimeRangeUnderflow',\n\t\t'FileMissing','InvalidEmail','InvalidURL','NumberRangeOverflow','NumberRangeUnderflow',\n\t\t'PatternMismatch','RadioMissing','SelectMissing','StepMismatch','ValueMissing',]\n\tconst input = \"<input type='number' required>\"\n\t\t+ \"<input type='checkbox' required>\"\n\t\t+ \"<input type='date' value='2024-01-01' max='2023-12-31'>\"\n\t\t+ \"<input type='date' value='2022-01-01' min='2023-12-31'>\"\n\t\t+ \"<input type='file' required>\"\n\t\t+ \"<input type='email' value='a'>\"\n\t\t+ \"<input type='url' value='a'>\"\n\t\t+ \"<input type='number' max='1974.3' value='2000'>\"\n\t\t+ \"<input type='number' min='8026.5' value='1'>\"\n\t\t+ \"<input type='tel' pattern='[0-9]{1}' value='a'>\"\n\t\t+ \"<input type='radio' required name='radiogroup'>\"\n\t\t+ \"<select required><option></option></select>\"\n\t\t+ \"<input type='number' min='1.2345' step='1005.5545' value='2'>\"\n\t\t+ \"<input type='text' required>\"\n\n\tlet hash, btn ='', data = {}, notation = isLanguageSmart ? locale_red : ''\n\ttry {\n\t\tlet collection = ((new DOMParser).parseFromString(input, 'text/html')).body.children\n\t\tlet cType = typeFn(collection)\n\t\tif ('object' !== cType) {throw zErrType + cType}\n\t\tfor (const k of Object.keys(collection)) {\n\t\t\tlet msg = collection[k].validationMessage\n\t\t\tif (runST) {msg = undefined}\n\t\t\tlet typeCheck = typeFn(msg)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tdata[aNames[k]] = msg\n\t\t}\n\t\thash = mini(data)\n\t\tlet count = Object.keys(data).length\n\t\tlet details = count === aNames.length ? 'details' : count +'/' + aNames.length\n\t\tbtn = addButton(4, METRIC, details)\n\t\tif (isLanguageSmart) {\n\t\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\t\tif (localesSupported[isLocaleAlt].v.includes(hash)) {notation = locale_green}\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_l10n_xml_messages(METRIC) {\n\t// https://searchfox.org/firefox-main/source/dom/locales/en-US/chrome/layout/xmlparser.properties\n\n\tlet hash, btn ='', data = isXML, notation = isLanguageSmart ? locale_red : ''\n\tif ('string' == typeof isXML) {\n\t\thash = isXML; data = isXML == zNA ? '' : zErrLog\n\t} else {\n\t\thash = mini(isXML); btn = addButton(4, METRIC)\n\t\tif (isLanguageSmart) {\n\t\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\t\tif (localesSupported[isLocaleAlt].x.includes(hash)) {notation = locale_green}\n\t\t\t}\n\t\t}\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data)\n\treturn\n}\n\nfunction get_l10n_xml_prettyprint(METRIC, isLies) {\n\tif (!isGecko) {addBoth(4, METRIC, zNA); return}\n\n\t// https://searchfox.org/firefox-main/source/dom/locales/en-US/dom = XMLPrettyPrint\n\t\t// note file schema errors due to CORS\n\t// by using a narrow iframe width, word segmentation line breaks determine the height,\n\t\t// and the content varies per app locale. It's imperative that the iframe be very\n\t\t// narrow (TZP uses 20px) as this ensure all scripts return the maximum number of lines\n\t// Deterministic health checks can't be hardcoded due to subpixels (system + other scaling)\n\t\t\t// and fonts (per platform + language), but we could simulate + compare\n\tlet value, data ='', notation=''\n\ttry {\n\t\tif (gRun) {dom.tzpXMLunstyled.width = 20} // ensure narrow width for max lines\n\t\tlet target = dom.tzpXMLunstyled.contentDocument.firstChild\n\t\tlet method = measureFn(target, METRIC)\n\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\tvalue = method.height\n\t} catch(e) {\n\t\tvalue = e; data = zErrLog\n\t}\n\t// if the xml isn't loaded in time we will get a low default value (e.g. 0 latin, 8 arabic, 18 privacyX)\n\t\t// notate this as well as unexpected errors (i.e value is a string)\n\t\t// don't notate if file schema (it makes file vs http have different health counts which is not good)\n\t\t// this test is gecko only, so we don't need to check isLanguageSmart\n\tif (!isFile) {\n\t\tif ('number' !== typeof value || value < 50) {notation = isLanguageSmart ? locale_red : default_red}\n\t}\n\taddBoth(4, METRIC, value,'', notation, data, isLies)\n}\n\nfunction get_l10n_xslt_messages(METRIC) {\n\tif (!isGecko) {addBoth(4, METRIC, zNA); return}\n\n\t// https://searchfox.org/firefox-main/source/dom/locales/en-US/dom/xslt.ftl\n\t// note file schema errors due to CORS\n\t\t// we only need the one test for max entropy (tested Base Browser)\n\t\t// but we need an object to create a btn, and this also allows future expansion\n\n\t// FF151: dom.xslt.enabled\n\t\t// we're only loading the iframe once (on page load). The pref dictates if the xslt was\n\t\t// parsed and an error displayed or not - and that's not going to change if the pref is\n\t\t// toggled a rerun done\n\n\tlet hash, data ='', btn='', notation = isLanguageSmart ? locale_red : ''\n\ttry {\n\t\tlet msg = dom.tzpXSLT.contentDocument.children[0].textContent\n\t\tif ('a' == msg) {\n\t\t\t// ToDo: cleanup notation when this becomes the standard\n\t\t\thash = zD // XSLT disabled on page load\n\t\t} else {\n\t\t\tdata = {'xslt-parse-failure': msg} \n\t\t\thash = mini(data); btn = addButton(4, METRIC)\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\tif (isLanguageSmart) {\n\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\tif (localesSupported[isLocaleAlt].xs.includes(hash)) {notation = locale_green}\n\t\t}\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data)\n}\n\nfunction get_l10n_xslt_sort(METRIC) {\n\tif (!isGecko) {addBoth(4, METRIC, zNA); return}\n\n\t// 1978383: xsl:sort uses only base sensitivity / primary strength\n\t// so we can't use plural rules to get a determinsitic result\n\tlet hash, btn ='', data = {}, notation = isLanguageSmart ? locale_red : ''\n\tif (!isXSLT) {\n\t\taddBoth(4, METRIC, zD,'', notation); return\n\t}\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\t// get characters\n\t\tlet aSource = oIntlLocale.collation.sort, aChars = [], aData = []\n\t\taSource.forEach(function(item){aChars.push(item)})\n\t\taChars.sort() // always sort to match char array same as collation poc\n\t\t// build xslt\n\t\taChars.forEach(function(item){aData.push('<a>'+ item +'</a>')})\n\t\tconst xData = '<?xml version=\"1.0\" encoding=\"UTF-8\"?><doc>'+ aData.join('') +'</doc>'\n\t\tconst xslText = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n\t\t\t+'<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">'\n\t\t\t+'<xsl:template match=\"/\"><xsl:for-each select=\"doc/a\"><xsl:sort select=\"text()\"/>'\n\t\t\t+'<xsl:value-of select=\"text()\"/>,</xsl:for-each></xsl:template></xsl:stylesheet>'\n\t\t// run xslt\n\t\tconst parser = new DOMParser()\n\t\tconst xsltProcessor = new XSLTProcessor()\n\t\tconst xslStylesheet = parser.parseFromString(xslText, \"application/xml\")\n\t\txsltProcessor.importStylesheet(xslStylesheet)\n\t\tconst xmlDoc = parser.parseFromString(xData, \"application/xml\")\n\t\tconst styledDoc = xsltProcessor.transformToDocument(xmlDoc)\n\t\tlet aTmp = styledDoc.firstChild.textContent.split(/[\\s,\\n]+/);\n\t\taTmp = aTmp.slice(0, -1)\n\t\tlet dataStr = (aTmp.join(' , ')).trim()\n\t\tdata = {'sort': dataStr}\n\t\thash = mini(data); btn = addButton(4, METRIC)\n\t\tif (isLanguageSmart) {\n\t\t\tif (isLocaleValid && localesSupported[isLocaleAlt] !== undefined) {\n\t\t\t\t// compare the string hash\n\t\t\t\tif (localesSupported[isLocaleAlt].xsort.includes(mini(dataStr))) {notation = locale_green}\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\thash = e; data = zErrLog\n\t}\n\taddBoth(4, METRIC, hash, btn, notation, data)\n\treturn\n}\n\n/* TODO */\n\nconst get_dates = () => new Promise(resolve => {\n\tlet d = new Date(Date.UTC(2023, 0, 1, 0, 0, 1)) //\n\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat\n\t// locale options\n\tlet o = {\n\t\t// numeric or 2-digit: always use numeric\n\t\tyear: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric',\n\t\t// true or false: true override hourCycle and forces h11 or h12 (locale dependent) == AM/PM\n\t\thour12: true,\n\t\t// long short narrow\n\t\tera: 'long',\n\t\tmonth: 'long', // also numeric or 2-digit but we already have that covered\n\t\tweekday: 'long',\n\t\t// long short\n\t\t\t// NOTE: FF91+ longGeneric, longOffset, shortGeneric, shortOffset\n\t\t\t// see timezonename PoC: tested july 2025: long, longOffset, shortOffset add nothing\n\t\t\t// short vs shortGeneric is a single extra unique hash\n\t\t\t// long matches longGeneric in terms of entropy\n\t\t\t// use long/short for old-timey support\n\t\ttimeZoneName: 'long' \n\t}\n\t// max option combos would be 3 x 3 x 3 x 2 = 54\n\t// then x dates and x calendars\n\t// that's a lot of tests\n\n\tlet localecode = undefined\n\tlet DTFo\n\ttry {DTFo = Intl.DateTimeFormat(undefined, o)} catch {}\n\n\tfunction get_item(item) {\n\t\tlet itemPad = 'item '+ item\n\t\ttry {\n// STRINGS\n\t\t\tif (item == 1) {return d.toTimeString()\n\t\t\t} else if (item == 2) {return d // a date object: default format?\n\t\t\t} else if (item == 3) {return d.toString() // redundant?\n\n\t\t\t// options\n\t\t\t} else if (item == 4) {return d.toLocaleString(localecode, o)\n\t\t\t} else if (item == 5) {return d.toLocaleDateString(localecode, o)\n\t\t\t} else if (item == 6) {return d.toLocaleTimeString(localecode, o)\n\t\t\t// no options\n\t\t\t} else if (item == 7) {return d.toLocaleString(localecode)\n\t\t\t} else if (item == 8) {return [d].toLocaleString(localecode) // typed array\n\t\t\t} else if (item == 9) {return d.toLocaleDateString(localecode)\n\t\t\t} else if (item == 10) {return d.toLocaleTimeString(localecode)\n\n// DTF\n\t\t\t} else if (item == 11) {return DTFo.format(d)\n\t\t\t} else if (item == 12) {\n\t\t\t\tlet f = Intl.DateTimeFormat(localecode, o)\n\n\t\t\t\tlet temp = f.formatToParts(d)\n\t\t\t\treturn temp.map(function(entry){return entry.value}).join('')\n\t\t\t} else if (item == 13) {return Intl.DateTimeFormat().format(d)\n\t\t\t} else if (item == 14) {\n\t\t\t\t// FF91+: 1710429\n\t\t\t\t// note: use hour12 - https://bugzilla.mozilla.org/show_bug.cgi?id=1645115#c9\n\t\t\t\t// FF91: extended TZNs are type \"unknown\"\n\t\t\t\tlet tzRes = []\n\t\t\t\ttry {\n\t\t\t\t\tlet tzNames = ['longGeneric','shortGeneric']\n\t\t\t\t\tlet tzDays = [d]\n\t\t\t\t\tlet tz\n\t\t\t\t\ttzDays.forEach(function(day) {\n\t\t\t\t\t\ttzNames.forEach(function(name) {\n\t\t\t\t\t\t\ttz =''\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tlet formatter = Intl.DateTimeFormat(localecode, {hour12: true, timeZoneName: name})\n\t\t\t\t\t\t\t\ttz = formatter.format(day)\n\t\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\t\tif (day == tzDays[0]) {\n\t\t\t\t\t\t\t\t\tlog_error(4, itemPad +': '+ name, e)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttz = zErr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttzRes.push(tz)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\treturn tzRes.join(' | ')\n\t\t\t\t} catch(e) {\n\t\t\t\t\tlog_error(4, itemPad +': timeZoneName', e)\n\t\t\t\t\treturn zErr\n\t\t\t\t}\n\t\t\t} else if (item == 15) {\n\t\t\t\t// FF91+: 1653024: formatRange\n\t\t\t\tlet date1 = new Date(Date.UTC(2020, 0, 15, 11, 59, 59)),\n\t\t\t\t\tdate2 = new Date(Date.UTC(2020, 0, 15, 12, 0, 1)),\n\t\t\t\t\tdate3 = new Date(Date.UTC(2020, 8, 19, 23, 15, 30))\n\t\t\t\treturn DTFo.formatRange(date1, date2) +' | '+ DTFo.formatRange(date1, date3)\n\t\t\t} else {\n\t\t\t\treturn zSKIP\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(4, itemPad, e)\n\t\t\treturn zErr\n\t\t}\n\t}\n\n\tfor (let i=1; i < 16; i++) {\n\t\tlet result = get_item(i)\n\t\tif (result !== zSKIP) {\n\t\t\tlet typeExpected = 2 == i ? 'empty object' : 'string'\n\t\t\tlet typeCheck = typeFn(result)\n\t\t\tif (typeExpected !== typeCheck) {result = zErrType + typeCheck}\n\t\t\taddDisplay(4, 'ldt'+ i, result)\n\t\t}\n\t}\n\treturn resolve()\n})\n\nconst outputRegion = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('region')) {return resolve()}\n\n\toIntlLocalePerf = {} // reset\n\n\tset_isLanguageSmart() // required for BB health in get_language_locale()\n\tPromise.all([\n\t\tget_geo('geolocation'),\n\t\tget_language_locale(), // sets isLocaleValid/Value, isLanguagesNav\n\t]).then(function(){\n\t\t// add smarts if locale matches: i.e we can notate messages for FF\n\t\t// isLanguageSmart controls health for l10n (and language/locale but we also check isBB in those)\n\t\tif (isGecko && !isLanguageSmart && isSmart && isDesktop) {\n\t\t\t// this becomes problematic to maintain for all those 40+ locales over a full ESR cycle as translations\n\t\t\t// change or deprecated warnings etc come and go: the health check only really matters if you're spoofing\n\t\t\t// en-US anyway, so let's limit to en-US for non-BB to avoid non-BB false positivess\n\t\t\tif ('en-US' == isLocaleValue) {\n\t\t\t\tif (localesSupported[isLocaleValue] !== undefined) {isLanguageSmart = true}\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet isLies = isDomRect == -1\n\t\tPromise.all([\n\t\t\tget_language_system('languages_system'), // uses isLanguagesNav\n\t\t\tget_locale_intl(),\n\t\t\tget_timezone('timezone'), // sets isTimeZoneValid/Value\n\t\t\tget_l10n_validation_messages('l10n_validation_messages'),\n\t\t\tget_l10n_xml_messages('l10n_xml_messages'),\n\t\t\tget_l10n_parsererror_direction('l10n_parsererror_direction'),\n\t\t\tget_l10n_xslt_sort('l10n_xslt_sort'),\n\t\t\tget_l10n_xml_prettyprint('l10n_xml_prettyprint', isLies),\n\t\t\tget_l10n_xslt_messages('l10n_xslt_messages'),\n\t\t\t//get_l10n_css('l10n_css'),\n\t\t]).then(function(){\n\t\t\tPromise.all([\n\t\t\t\tget_dates_intl(), // uses isTimeZoneValid/Value + isLocaleValid/Value\n\t\t\t\tget_dates(), // to migrate to get_dates_intl\n\t\t\t\tget_l10n_reporting_messages('l10n_reporting_messages'),\n\t\t\t\tget_l10n_media_messages('l10n_media_messages'),\n\t\t\t]).then(function(){\n\t\t\t\t// microperf: add totals, re-order into anew obj\n\t\t\t\tlet btn = '', count = 0, newobj = {'all': {}}\n\n\t\t\t\tlet iTime = 0, sTime = 0 // running totals\n\t\t\t\tlet countInteger = 0 \n\t\t\t\tfor (const k of Object.keys(oIntlLocalePerf).sort()) {\n\t\t\t\t\tnewobj[k] = oIntlLocalePerf[k]\n\t\t\t\t\tlet kTime = 0 // running sub total\n\t\t\t\t\tfor (const j of Object.keys(oIntlLocalePerf[k]).sort()) {\n\t\t\t\t\t\tlet value = oIntlLocalePerf[k][j]\n\t\t\t\t\t\tif ('constructors' == j) {\n\t\t\t\t\t\t\tcount += value\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif ('intl' == j) {iTime += value} else {sTime += value}\n\t\t\t\t\t\t\tkTime += value // sum time for this metric k\n\t\t\t\t\t\t\tif (Number.isInteger(value)) {countInteger++} // track integers\n\t\t\t\t\t\t\tif (isGecko && 16.67 == value.toFixed(2)) {countInteger++} // add RFP \"integers\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// add a subtotal if more than expected constructors|intl\n\t\t\t\t\tif (Object.keys(oIntlLocalePerf[k]).length > 2) {newobj[k]['total'] = kTime}\n\t\t\t\t}\n\t\t\t\t// we currently have 26 times\n\t\t\t\t\t// gecko noRFP + RFP = 26 | noRFP but reduceTimer + chrome = 0 to 5\n\t\t\t\tif (countInteger < 13) {\n\t\t\t\t\tnewobj.all = {'constructors': count, 'intl': iTime}\n\t\t\t\t\tif (sTime > 0) {newobj.all['string'] = sTime}\n\t\t\t\t\tnewobj.all['total'] = iTime + sTime\n\t\t\t\t\taddDetail('intl', newobj, 'microperf')\n\t\t\t\t\tbtn = addButton(99, 'intl','perf','btnc','microperf') //+' '+ countInteger\n\t\t\t\t}\n\t\t\t\tdom['intl_perf'].innerHTML = btn\n\t\t\t\treturn resolve()\n\t\t\t})\n\t\t})\n\t})\n})\n\nconst outputHeaders = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('headers')) {return resolve()}\n\n\tPromise.all([\n\t\tget_nav_dnt('doNotTrack'),\n\t\tget_nav_gpc('globalPrivacyControl'),\n\t\tget_nav_connection('connection'),\n\t\tget_nav_online('onLine'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\nset_oIntlDates()\nset_oIntlDate()\nset_oIntlLocale()\ncountJS(4)\n\n"
  },
  {
    "path": "js/screen.js",
    "content": "'use strict';\n\n/* SCREEN */\n\nfunction return_lb(w,h) {\n\t// LB\n\tlet wstep = 200, hstep = 200, bw = false, bh = false\n\tif (w < 501) {wstep = 50} else if (w < 1601) {wstep = 200}\n\tif (h < 501) {hstep = 50} else if (h < 1601) {hstep = 100}\n\tbw = Number.isInteger(w/wstep)\n\tbh = Number.isInteger(h/hstep)\n\treturn (bw && bh) ? true : false\n}\n\nfunction return_nw(w,h) {\n\t// NW\n\tlet wstep = w < 601 ? 50 : 200, hstep = h < 501 ? 50 : 100\n\tlet bw = false, bh = false\n\tif (w < 1401) {bw = Number.isInteger(w/wstep)}\n\tif (h < 901) {bh = Number.isInteger(h/hstep)}\n\treturn (bw && bh) ? nw_green : nw_red\n}\n\nfunction get_scr_fs_measure() {\n\t// F11: triggered by resize events if in FS\n\t// fullscreenElement: called on android by outputUserFS\n\tif (gFS) {return} // don't run if already running\n\tgFS = true // set running state\n\tlet delay = 25, max = 40, n = 1 // 40 x 25 = 1sec\n\tlet w, h, firstW, firstH, lastW, lastH\n\tlet isElementFS = document.fullscreen || document.webkitIsFullscreen || false\n\tlet target = document.fullscreenElement //, range, data\n\tlet output = isElementFS ? 'fullscreenElement' : 'fsSize'\n\tdom[output].innerHTML ='' // clear\n\n\tfunction measure() {\n\t\tif (isElementFS) {\n\t\t\t// use domrect but fallback to client\n\t\t\tif (isDomRect == -1) {\n\t\t\t\tw = document.fullscreenElement.clientWidth\n\t\t\t\th = document.fullscreenElement.clientHeight\n\t\t\t} else if (isDomRect < 1) {\n\t\t\t\tlet method = measureFn(target, output)\n\t\t\t\tw = method.width; h = method.height\n\t\t\t}\n\t\t} else {\n\t\t\tw = window.innerWidth; h = window.innerHeight\n\t\t}\n\t\tif (firstW == undefined) {firstW = w; firstH = h} // remember first values\n\t\tlastW = w; lastH = h\n\t\treturn w +' x '+ h\n\t}\n\t// initial size\n\tlet size = measure()\n\tlet notation = rfp_red, strSteps = ''\n\t\n\tfunction check_size() {\n\t\tclearInterval(checking)\n\t\tlet len = oDiffs.length\n\t\tif (len > 0) {\n\t\t\tlet lastValue = oDiffs[len-1]\n\t\t\tlet lastSize = lastW +' x '+ lastH\n\t\t\tlet stepsTaken = ((lastValue.split(':')[0]) * 1)\n\t\t\tlet timeTaken = stepsTaken * delay\n\t\t\tlet diff = '[diff: '+ (w - firstW) +' x '+ (h - firstH) +']'\n\t\t\ttimeTaken = Math.ceil(timeTaken/50) * 50 // round up in 50s\n\t\t\tsize = size + s1 +' &#9654 '+ sc + lastSize\n\t\t\tstrSteps = s1 +' <b>[~'+ timeTaken +' ms]</b> '+ sc + diff\n\t\t}\n\t\t// notate\n\t\tif (return_lb(lastW, lastH)) {notation = rfp_green}\n\t\tdom[output].innerHTML = size + (isSmart ? notation : '') + strSteps\n\t\tif (isElementFS) {document.exitFullscreen()} // only android can be isElementFS\n\t\tgFS = false // reset\n\t}\n\n\tlet current = size, oDiffs = [], nochange = 0\n\tfunction build_sizes() {\n\t\tif (n >= max) {\n\t\t\tcheck_size()\n\t\t} else {\n\t\t\t// grab changes\n\t\t\ttry {\n\t\t\t\tlet newsize = measure()\n\t\t\t\tif (newsize !== current) {\n\t\t\t\t\tnochange = 0\n\t\t\t\t\toDiffs.push(n +':'+ newsize)\n\t\t\t\t\tcurrent = newsize\n\t\t\t\t} else {\n\t\t\t\t\tnochange++\n\t\t\t\t\tif (nochange > 25) {check_size()} // exit\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tcheck_size()\n\t\t\t}\n\t\t}\n\t\tn++\n\t}\n\tlet checking = setInterval(build_sizes, delay)\n}\n\nconst get_scr_fullscreen = (METRIC) => new Promise(resolve => {\n\tlet oRes = {}\n\tlet cssvalue = getElementProp(1, '#cssDM', METRIC +'_display-mode_css')\n\tlet isErrCss = cssvalue == zErr\n\n\tfunction get_display_mode(item) {\n\t\t// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode\n\t\tconst item2 = item +'_css'\n\t\tlet data, isLies = false\n\t\ttry {\n\t\t\tlet aValid = ['browser','fullscreen','miniumal-ui','standalone']\n\t\t\tif (!isGecko) {aValid.push('picture-in-picture','window-controls-overlay')}\n\t\t\tfor (let i=0; i < aValid.length; i++) {\n\t\t\t\tif (window.matchMedia('(display-mode:'+ aValid[i] +')').matches) {data = aValid[i]; break}\n\t\t\t}\n\t\t\tif (runST) {data = undefined} else if (runSL) {data += '_fake'}\n\t\t\tlet typeCheck = typeFn(data)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (isSmart && !isErrCss && data !== cssvalue) {\n\t\t\t\tlog_known(1, METRIC +'_'+ item, data); isLies = true\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(1, METRIC +'_'+ item, e); data = zErr\n\t\t}\n\t\taddDisplay(1, item, data,'','', isLies)\n\t\toRes[item] = isLies ? zLIE : data\n\t\toRes[item2] = cssvalue\n\t\t// get FS measurments if in FS\n\t\tlet isElementFS = document.fullscreen || document.webkitIsFullscreen || false\n\t\tif (!isElementFS && 'fullscreen' == data && isDesktop) {\n\t\t\tget_scr_fs_measure()\n\t\t} else {\n\t\t\tgFS = false // cancel run state\n\t\t}\n\t}\n\n\t// nonGecko boolean vs undefined: i.e a string of \"undefined\" will be an error\n\t\t// note: FF145+ 1989559 marked as deprecated\n\tlet expectedType = isGecko ? 'boolean' : 'undefined'\n\t// fullScreen\n\tfunction get_fullScreen(item) {\n\t\tlet data, isLies = false\n\t\ttry {\n\t\t\tdata = window.fullScreen\n\t\t\tif (runST) {data = undefined}\n\t\t\tlet typeCheck = typeFn(data)\n\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif ('undefined' == typeCheck) {data += ''}\n\t\t\t// lies\n\t\t\tlet boolCss = 'fullscreen' == cssvalue ? true : false\n\t\t\tif (runSL) {data = !boolCss}\n\t\t\tif (isSmart && !isErrCss && boolCss !== data) {\n\t\t\t\tlog_known(1, METRIC +'_'+ item, data)\n\t\t\t\tisLies = true\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tlog_error(1, METRIC +'_'+ item, e); data = zErr\n\t\t}\n\t\t// can't use fullScreen because blink returns window.fullScreen as the element id='fullScreen'\n\t\taddDisplay(1, 'window'+ item, data,'','', isLies)\n\t\toRes[item] = isLies ? zLIE : data\n\t}\n\t// full-screen-api.enabled\n\tfunction get_mozFullScreenEnabled(item) {\n\t\tlet data\n\t\ttry {\n\t\t\tdata = document.mozFullScreenEnabled\n\t\t\tif (runST) {data = undefined}\n\t\t\tlet typeCheck = typeFn(data)\n\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif ('undefined' == typeCheck) {data += ''}\n\t\t} catch(e) {\n\t\t\tlog_error(1, METRIC +'_'+ item, e); data = zErr\n\t\t}\n\t\taddDisplay(1, item, data)\n\t\toRes[item] = data\n\t}\n\n\t// in order so oRes keys = sorted\n\tget_display_mode('display-mode')\n\tget_fullScreen('fullScreen')\n\tget_mozFullScreenEnabled('mozFullScreenEnabled')\n\taddData(1, METRIC, oRes, mini(oRes))\n\treturn resolve(oRes)\n})\n\nconst get_scr_measure = () => new Promise(resolve => {\n\tPromise.all([\n\t\tget_scr_mm('measure'),\n\t\tget_scr_viewport('sizes_viewport'),\n\t\tget_scr_fullscreen('fullscreen'),\n\t]).then(function(res){\n\t\t// get FS status\n\t\tlet isDisplayFS = 'fullscreen' == res[2]['display-mode']\n\n\t\tlet oTmp = {\n\t\t\tscreen: {height: {}, width: {}},\n\t\t\tavailable: {height: {}, width: {}},\n\t\t\tinner: {height: {}, width: {}},\n\t\t\touter: {height: {}, width: {}},\n\t\t}\n\t\t// matchmedia\n\t\toTmp.screen.height.media = res[0]['device-height']\n\t\toTmp.screen.width.media = res[0]['device-width']\n\t\toTmp.inner.height.media = res[0].height\n\t\toTmp.inner.width.media = res[0].width\n\t\t// test: css/media value is up to 1px higher: we should only allow a lower value\n\t\t//oTmp.screen.width.media = res[0]['device-width'] + 1\n\n\t\t// small viewport units\n\t\t\t// desktop: same as viewport element + clientrect but it ignores scollbars, so can\n\t\t\t// add information and is a more precise measurement of matchMedia\n\t\t\t// android: used to calculate dynamic toolbar\n\t\tlet vpWidth = isViewportUnits.width, vpHeight = isViewportUnits.height\n\t\toTmp.inner.width['svw'] = vpWidth.svw\n\t\toTmp.inner.height['svh'] = vpHeight.svh\n\n\t\tif (isDesktop) {\n\t\t\t// add desktop viewport so it's summarized/notated\n\t\t\toTmp['viewport'] = res[1]\n\t\t} else {\n\t\t\t// android \"document\" is an inner size not viewport || calculate get dynamic toolbar\n\t\t\toTmp.inner.width['document'] = vpWidth.document\n\t\t\toTmp.inner.height['document'] = vpHeight.document\n\t\t\t// dynamic toolbar: display only - let it NaN for I care\n\t\t\tlet strToolbar = (vpWidth.lvw - vpWidth.svw) +' x '+ (vpHeight.lvh - vpHeight.svh)\n\t\t\taddDisplay(1, 'dynamic_toolbar', strToolbar)\n\t\t\t// large viewport units \n\t\t\taddDisplay(1, 'viewport_large', vpWidth.lvw +' x '+ vpHeight.lvh)\n\t\t\t// sizes_viewport metric (small is already under sizes_inner)\n\t\t\tlet newobj = {'height': {'lvh': vpHeight.lvh}, 'width': {'lvw': vpWidth.lvw}}\n\t\t\taddData(1, 'sizes_viewport', newobj, mini(newobj))\n\t\t}\n\n\t\t// screen/window\n\t\t// order matters: so property targets are correct\n\t\tlet aList = ['iframe','doc'] // iframe first\n\t\tlet oList = {\n\t\t\t// screens first\n\t\t\tscreen: ['height','width'],\n\t\t\tavailable: ['availHeight','availWidth'],\n\t\t\t// then windows with inner last\n\t\t\touter: ['outerHeight','outerWidth'],\n\t\t\tinner: ['innerHeight','innerWidth'],\n\t\t}\n\t\t// window.inner on android is dynamic and also redudnant with document + small viewport units\n\t\tif (!isDesktop) {delete oList.inner}\n\t\tlet iTarget, target\n\t\ttry {iTarget = dom.tzpIframe.contentWindow} catch {}\n\t\ttry {target = iTarget.screen} catch {} // initial iframe target\n\t\taList.forEach(function(name) {\n\t\t\tif ('iframe' !== name) {target = screen; name = 'screen'} // initial target post iframe\n\t\t\tfor (const k of Object.keys(oList)) {\n\t\t\t\tif ('iframe' !== name) {\n\t\t\t\t\tif ('outer' == k) {target = window; name = 'window'} // switch non-iframe target\n\t\t\t\t}\n\t\t\t\tlet aItems = oList[k]\n\t\t\t\tfor (let i=0; i < aItems.length; i++) {\n\t\t\t\t\tlet p = aItems[i], x, isSkip = false\n\t\t\t\t\tlet axis = p.includes('idth') ? 'width' : 'height'\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif ('iframe' == name && 'inner' == k) {isSkip = true} // skip iframe inner\n\t\t\t\t\t\tif (!isSkip) {\n\t\t\t\t\t\t\tif ('iframe' == name && 'outer' == k) {target = iTarget.window} // switch iframe target\n\t\t\t\t\t\t\tx = target[p]\n\t\t\t\t\t\t\tif (runST) {x = undefined}\n\t\t\t\t\t\t\t/* cause one error\n\t\t\t\t\t\t\tif (name == 'screen' && k == 'screen' && axis == 'width') {x = undefined} // fail one screen\n\t\t\t\t\t\t\t//*/\n\t\t\t\t\t\t\t/* change one value: a little moot once we compare to css for zLIEs etc\n\t\t\t\t\t\t\tif (name == 'window' && k == 'outer' && axis == 'width') {x = x - 30} // fail one outer\n\t\t\t\t\t\t\t//*/\n\t\t\t\t\t\t\tlet typeCheck = typeFn(x)\n\t\t\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\t\t\t// only matchmedia can be non Integer\n\t\t\t\t\t\t\tif (!Number.isInteger(x)) {throw zErrInvalid + 'expected Integer: got '+ typeCheck}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tlog_error(1, 'sizes_'+ k +'_'+ axis +'_'+ name, e)\n\t\t\t\t\t\tx = zErr\n\t\t\t\t\t}\n\t\t\t\t\tif (!isSkip) {oTmp[k][axis][name] = x}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\t// css\n\t\tlet cssList = [['#S',':before'],['#S',':after'],['#D',':before'],['#D',':after']]\n\t\tcssList.forEach(function(array) {\n\t\t\tlet cssID = array[0], pseudo = array[1]\n\t\t\tlet axis = ':before' == pseudo ? 'width' : 'height'\n\t\t\tlet metric = '#S' == cssID ? 'screen' : 'inner'\n\t\t\tlet value = getElementProp(1, cssID, 'sizes_'+ metric +'_'+ axis +'_css', pseudo)\n\t\t\tif (value !== zErr && '?' !== value) {\n\t\t\t\tlet cType = typeFn(value)\n\t\t\t\tif ('number' !== cType) {\n\t\t\t\t\tlog_error(1, 'sizes_'+ metric +'_'+ axis +'_css', zErrType + cType)\n\t\t\t\t\tvalue = zErr\n\t\t\t\t} else if (!Number.isInteger(value)) {\n\t\t\t\t\t// only matchmedia can be non integer\n\t\t\t\t\tlog_error(1, 'sizes_'+ metric +'_'+ axis +'_css', zErrInvalid + 'expected Integer: got '+ cType)\n\t\t\t\t\tvalue = zErr\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ('#S' == array[0]) {oTmp.screen[axis].css = value} else {oTmp.inner[axis].css = value}\n\t\t})\n\n\t\t// sort into new obj, build default display\n\t\tlet oData = {}, oDisplay = {}, oSummary = {}\n\t\tfor (const k of Object.keys(oTmp)) {\n\t\t\toData[k] = {}\n\t\t\toSummary[k] = {}\n\t\t\tfor (const j of Object.keys(oTmp[k])) {\n\t\t\t\toData[k][j] = {}\n\t\t\t\toSummary[k][j] = undefined\n\t\t\t\tfor (const m of Object.keys(oTmp[k][j]).sort()) {\n\t\t\t\t\tlet value = oTmp[k][j][m]\n\t\t\t\t\toData[k][j][m] = value\n\t\t\t\t\tif ('width' == j && 'css' !== m) {\n\t\t\t\t\t\tif ('svw' == m) {oDisplay[k +'_viewport'] = value +' x '+ oTmp[k]['height']['svh']\n\t\t\t\t\t\t} else {oDisplay[k +'_'+ m] = value +' x '+ oTmp[k]['height'][m]}\n\t\t\t\t\t}\n\t\t\t\t\t// any error to oSummary, but ignore out of range css\n\t\t\t\t\tif ('string' == typeof value) {\n\t\t\t\t\t\tif ('css' == m && '?' == value) {} else {oSummary[k][j] = zErr}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// initial\n\t\toDisplay['initial_inner'] = isInitial.width.inner + ' x ' + isInitial.height.inner\n\t\toDisplay['initial_outer'] = isInitial.width.outer + ' x ' + isInitial.height.outer\n\n\t\t// notation\n\t\tlet notation ='', initData = zNA, initHash =''\n\t\tlet innerw = oData.inner.width.window, innerh = oData.inner.height.window\n\t\tlet screenw = oData.screen.width.screen, screenh = oData.screen.height.screen\n\n\t\t// controls: we want integers so we know what to match to\n\t\tlet controlw = innerw, controlh = innerh\n\n\t\tif (isDesktop) {\n\t\t\taddDisplay(1, 'size_newwin','','', return_nw(innerw, innerh)) // newwin\n\t\t} else {\n\t\t\t/* on android\n\t\t\theight\n\t\t\t\t- window.inner height can differ due to dynamic toolbar, so we use doc\n\t\t\t\t- documentElement height = clientHeight because we want the window, not the entire page length\n\t\t\t\tand is thus always our preferred integer to match to\n\t\t\twidth\n\t\t\t\t- documentElement width = domrect and not an integer\n\t\t\t\t- but TZP uses a fixed width <meta name=\"viewport\" content=\"width=800\"> so width should be constant\n\t\t\t\t- but window.inner width is also not affected by dynamic toolbar\n\t\t\t\t- so use window in case a phone's native res exceeds or we drop (or lower) the fixed width\n\t\t\t*/\n\t\t\tcontrolh = oData.inner.height.document\n\t\t\t// controlw is undefined, we need to grab it\n\t\t\ttry {\n\t\t\t\tcontrolw = window.innerWidth\n\t\t\t\tif ('number' !== typeFn(controlw)) {controlw = zErr} else if (!Number.isInteger(controlw)) {control = zErr}\n\t\t\t} catch {\n\t\t\t\tcontrolw = zErr\n\t\t\t}\n\t\t\t// initial_sizes\n\t\t\t// ToDo: add notation\n\t\t\tinitData = isInitial; initHash = mini(isInitial)\n\t\t}\n\t\tlet isCompareValid = 'number' == typeFn(controlw) && 'number' == typeFn(controlh)\n\n\t\t// desktop: if in fullscreenElement mode, use the svh element to measure\n\t\t\t// we don't have a resize event in android\n\t\tif (isDesktop) {\n\t\t\tlet isElementFS = document.fullscreen || document.webkitIsFullscreen || false\n\t\t\tif (isElementFS) {\n\t\t\t\taddDisplay(1, 'fullscreenElement', oData.inner.width.svw +' x '+ oData.inner.height.svh)\n\t\t\t}\n\t\t\ttry {dom.btnFS.style.display = (isElementFS ? 'block' : 'none')} catch {}\n\t\t}\n\n\t\t// RFP/match\n\t\tlet aRound = ['media','css','svh','svw','document','element','visualViewport']\n\t\tfor (const k of Object.keys(oData)) {\n\t\t\tlet isSame = true\n\t\t\t// for each axis\n\t\t\tfor (const j of Object.keys(oData[k])) {\n\t\t\t\tlet tmpSet = new Set\n\t\t\t\tfor (const n of Object.keys(oData[k][j])) {\n\t\t\t\t\tlet value = oData[k][j][n]\n\t\t\t\t\tlet original = value\n\t\t\t\t\t//console.log(k, j, n, value)\n\t\t\t\t\t// ignore css out of range\n\t\t\t\t\tlet isIgnore = '?' == value && 'css' == n\n\t\t\t\t\tif (zErr == value || zLIE == value) {\n\t\t\t\t\t\tisSame = false // errors and lies = fail\n\t\t\t\t\t\tisIgnore = true // don't add errors or lies to our set\n\t\t\t\t\t\tif (zErr == value) {oSummary[k][j] = zErr}\n\t\t\t\t\t}\n\t\t\t\t\t/* android window.inner\n\t\t\t\t\t\ta non-match doesn't matter for notation: we don't notate RFP inner on android yet\n\t\t\t\t\t\tit does cause a \"mixed\" in summary which with a green RFP is misleading, so ignore\n\t\t\t\t\t\tNote: once we add lies logic to our data, handled just above, we also won't be\n\t\t\t\t\t\tletting a possible genuine mismatch thru by not checking it for sameness\n\t\t\t\t\t*/\n\t\t\t\t\tif (!isDesktop && 'inner' == k && 'window' == n) {isIgnore = true}\n\t\t\t\t\tlet control\n\t\t\t\t\tif (!isIgnore) {\n\t\t\t\t\t\t// *vw/h can be non-integer in inner\n\t\t\t\t\t\t// media can be non-integer | css can be off by 1 | both only screen + inner metrics\n\t\t\t\t\t\t// document (android) width uses domrect\n\t\t\t\t\t\t// viewport (desktop) ucan be non-integer\n\t\t\t\t\t\t// match them to our inner or screen if within 1\n\t\t\t\t\t\tif (aRound.includes(n)) {\n\t\t\t\t\t\t\tvalue = Math.floor(value) // to remove non-integers + ensure valid diffs are positive\n\t\t\t\t\t\t\tcontrol = 'width' == j ? screenw : screenh // if these are invalid diff == NaN\n\t\t\t\t\t\t\tif ('inner' == k || 'viewport' == k) {control = 'width' == j ? controlw : controlh}\n\t\t\t\t\t\t\t// we floored so any valid diff must be 1 or 0 because we substract value from control\n\t\t\t\t\t\t\tif (1 == control - value) {value = control} // match control\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//if ('viewport' == k) {console.log(k, j, n, '\\norig', original, '\\ncontrol', control, '\\nfinal value for sameness', value)}\n\t\t\t\t\t\ttmpSet.add(value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet aSet = Array.from(tmpSet)\n\n\t\t\t\t// summary\n\t\t\t\t// if the array is empty, isSame should already be false\n\t\t\t\t// we already rounded + matched non-integers, there should only be one\n\t\t\t\tif (aSet.length !== 1) {\n\t\t\t\t\tif (undefined == oSummary[k][j]) {oSummary[k][j] = 'mixed'}\n\t\t\t\t\tisSame = false\n\t\t\t\t} else {\n\t\t\t\t\tif (undefined == oSummary[k][j]) {oSummary[k][j] = aSet[0]}\n\t\t\t\t}\n\t\t\t\t// notation\n\t\t\t\t\t// if all the same then does it match _based_ on inner\n\t\t\t\tif (isSame && isCompareValid) {\n\t\t\t\t\t// inner + viewport: does it match LBing: check once and pass both width + height\n\t\t\t\t\tif ('inner' == k) {\n\t\t\t\t\t\tif ('width' == j) {isSame = return_lb(controlw, controlh)}\n\t\t\t\t\t} else if ('viewport' == k) {\n\t\t\t\t\t\t// viewport RFP must match letterboxing AND inner\n\t\t\t\t\t\tif ('width' == j) {\n\t\t\t\t\t\t\tisSame = return_lb(oSummary.viewport.width, oSummary.viewport.height)\n\t\t\t\t\t\t\tif (isSame) {\n\t\t\t\t\t\t\t\t// it must also match inner\n\t\t\t\t\t\t\t\tif (oSummary.viewport.width !== controlw) {isSame = false}\n\t\t\t\t\t\t\t\tif (oSummary.viewport.height !== controlh) {isSame = false}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// we can refine these rules later per key/OS: currently does it == inner\n\t\t\t\t\t\tlet match = 'width' == j ? controlw : controlh\n\t\t\t\t\t\tif (aSet[0] !== match) {isSame = false}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet notation = isSame ? rfp_green : rfp_red\n\t\t\tif ('inner' == k && !isDesktop) {notation = ''}\n\t\t\taddDisplay(1, 'sizes_'+ k, '','', notation)\n\t\t}\n\t\t//console.log('viewport', res[1])\n\t\t//console.log('data', oData)\n\t\t//console.log('sum', oSummary)\n\t\t//console.log('display', oDisplay)\n\n\t\t// health lookups\n\t\tif (gRun) {\n\t\t\tlet strInner = oTmp.inner.width.window +' x '+ oTmp.inner.height.window\n\t\t\tlet initInner = isInitial.width.inner +' x '+ isInitial.height.inner\n\t\t\tlet initOuter = isInitial.width.outer +' x '+ isInitial.height.outer\n\t\t\tlet initMatch = initInner == initOuter ? initInner : 'inner: '+ initInner +' | outer: '+ initOuter\n\t\t\tsDetail[isScope].lookup['size_newwin'] = strInner\n\t\t\tsDetail[isScope].lookup['sizes_initial'] = initMatch\n\t\t}\n\n\t\t/* ToDo: update oData/oDisplay/oSummary with lies\n\t\t\ti.e detect them, change oData to zLIE, color them\n\t\t\tsData[SECT99] covers \"Screen.width\",\"Screen.height\",\"Screen.availWidth\",\"Screen.availHeight\"\n\t\t\tand we have if css is valid and it's not \"isSame\" i.e it matches within 1\n\t\t\tabout the only one we really can't tell is outer\n\t\t*/\n\n\t\t// display only: taskbar/dock + chrome\n\t\t\t// on android there is no dock and we set a minimum width which means chrome is non-sensical\n\t\t\t// and can be negative: e.g. outer 427 - inner 500, also display space is at a premium\n\t\tlet arW = oData.screen.width.screen,\n\t\t\tarH = oData.screen.height.screen,\n\t\t\tdockH = arH - oData.available.height.screen,\n\t\t\tdockW = arW - oData.available.width.screen,\n\t\t\tchromeW = oData.outer.width.window - oData.inner.width.window,\n\t\t\tchromeH = oData.outer.height.window - oData.inner.height.window,\n\t\t\taspect = Math.trunc((arW/arH) * 1000)/1000\n\t\tif (isDesktop) {\n\t\t\t// ToDo: check each platform e.g. mac behavior FS Element vs F11\n\t\t\t// chrome\n\t\t\t\t// note: non-gecko does not resize non-inner (e.g. outer) when zooming, so chrome sizes can get ridiculous, display anyway\n\t\t\tnotation = red_benign\n\t\t\tlet isChromeZero = '00' == chromeW +''+chromeH\n\t\t\tif (isDisplayFS) {\n\t\t\t\tif (isChromeZero) {notation = green_benign}\n\t\t\t} else {\n\t\t\t\tif (!isChromeZero) {notation = green_benign}\n\t\t\t}\n\t\t\tsDetail[isScope].lookup['size_chrome'] = chromeW +' x '+ chromeH\n\t\t\taddDisplay(1, 'size_chrome', '[chrome: '+ chromeW +' x '+ chromeH +']','', notation)\n\n\t\t\t// docker\n\t\t\tnotation = red_benign\n\t\t\tlet isDockZero = '00' == dockW +''+dockH\n\t\t\tif (isDisplayFS) {\n\t\t\t\tif (isDockZero) {notation = green_benign}\n\t\t\t} else {\n\t\t\t\tif (!isDockZero) {notation = green_benign}\n\t\t\t}\n\n\t\t\tlet dockStr = ('windows' == isOS ? 'taskbar' : ('mac' == isOS ? 'menu bar/dock' : 'panel'))\n\t\t\tif (isOS == undefined) {dockStr = 'taskbar/dock/panel'}\n\t\t\tsDetail[isScope].lookup['size_dock'] = dockW +' x '+ dockH\n\t\t\taddDisplay(1, 'size_dock', '['+ dockStr +': '+ dockW +' x '+ dockH +']','', notation)\n\t\t}\n\n\t\t// aspect ratio\n\t\t\t// AR is one measurement that would be a finite list: that if not known would mean tampering (or a new AR)\n\t\t\t// https://en.wikipedia.org/wiki/List_of_common_display_resolutions\n\t\t\t\t// we can likely easily cover 95%+ of non android ARs, but the last 5% is a massive long tail. The data itself\n\t\t\t\t// redundant: and RFP checks in future will be set sizes e.g. 2k,4k,8k and will have their own halth checks\n\t\t\t// for now just display the AR\n\t\tnotation = ''\n\t\t/*\n\t\tnotation = isDesktop ? red_benign : ''\n\t\tsDetail[isScope].lookup['screen_aspect_ratio'] = aspect\n\t\tif ('NaN' !== aspect) {\n\t\t\t// always get highest over lowest to reduce checks\n\t\t\tlet ar = (arW < arH ? Math.trunc((arH/arW) * 1000)/1000 : aspect)\n\t\t\tlet aGood = [1.6, 1.777, 1.778]\n\t\t\tif (aGood.includes(ar)) {notation = green_benign}\n\t\t}\n\t\t//*/\n\t\taddDisplay(1, 'screen_aspect_ratio', '[aspect ratio: '+ aspect +']','', notation)\n\n\t\t// data\n\t\tfor (const k of Object.keys(oData)) {addData(1, 'sizes_'+ k, oData[k], mini(oData[k]))}\n\t\taddData(1, 'sizes_initial', initData, initHash)\n\t\t// display\n\t\tfor (const k of Object.keys(oSummary)) {oDisplay[k +'_summary'] = oSummary[k].width +' x '+ oSummary[k].height}\n\t\tfor (const k of Object.keys(oDisplay)) {addDisplay(1, k, oDisplay[k])}\n\t\treturn resolve()\n\t})\n})\n\nconst get_scr_mm = (datatype) => new Promise(resolve => {\n\tconst unable = 'unable to find upper bound'\n\tconst oList = {\n\t\tmeasure: [\n\t\t\t['sizes_screen', 'device-width', 'device-width', 'max-device-width', 'px', 512, 0.01],\n\t\t\t['sizes_screen', 'device-height', 'device-height', 'max-device-height', 'px', 512, 0.01],\n\t\t\t['sizes_inner', 'width', 'width', 'max-width', 'px', 512, 0.01],\n\t\t\t['sizes_inner', 'height', 'height', 'max-height', 'px', 512, 0.01],\n\t\t],\n\t\tpixels: [\n\t\t\t['pixels', '-moz-device-pixel-ratio', '-moz-device-pixel-ratio', 'max--moz-device-pixel-ratio', '', 4, 0.0000001],\n\t\t\t['pixels', '-webkit-device-pixel-ratio', '-webkit-device-pixel-ratio', '-webkit-max-device-pixel-ratio', '', 4, 0.01],\n\t\t\t['pixels', 'dpcm', 'resolution', 'max-resolution', 'dpcm', 1e-5, 0.0000001],\n\t\t\t['pixels', 'dpi', 'resolution', 'max-resolution', 'dpi', 1e-5, 0.0000001],\n\t\t\t['pixels', 'dppx', 'resolution', 'max-resolution', 'dppx', 1e-5, 0.0000001],\n\t\t]\n\t}\n\tconst oPrefixes = {\n\t\t'device-width': 'sizes_screen_width',\n\t\t'device-height': 'sizes_screen_height',\n\t\twidth: 'sizes_inner_width',\n\t\theight: 'sizes_inner_height',\n\t\t'-moz-device-pixel-ratio': 'pixels',\n\t\t'-webkit-device-pixel-ratio': 'pixels',\n\t\tdpcm: 'pixels',\n\t\tdpi: 'pixels',\n\t\tdppx: 'pixels',\n\t}\n\n\tlet list = oList[datatype], maxCount = oList[datatype].length, count = 0, oData = {}\n\tfunction exit(id, value) {\n\t\tif (value == unable) {\n\t\t\tif (!isGecko && '-moz-device-pixel-ratio' == id) {\n\t\t\t\tvalue = zNA\n\t\t\t} else {\n\t\t\t\tlet suffix = (id.includes('width') || id.includes('height')) ? 'media' : id\n\t\t\t\tlet metric = oPrefixes[id] +'_'+ suffix\n\t\t\t\tlog_error(1, metric, unable)\n\t\t\t\tvalue = zErr\n\t\t\t}\n\t\t}\n\t\toData[id] = value\n\t\tcount++\n\t\tif (count == maxCount) {\n\t\t\treturn resolve(oData)\n\t\t}\n\t}\n\tfunction runTest(callback){\n\t\tlist.forEach(function(k){\n\t\t\tlet group = k[0], metric = k[1], lower = k[2], upper = k[3],\n\t\t\t\tsuffix = k[4], epsilon = k[5], precision = k[6]\n\t\t\tPromise.all([\n\t\t\t\tcallback(group, lower, upper, suffix, epsilon, precision),\n\t\t\t]).then(function(result){\n\t\t\t\tif (runST) {\n\t\t\t\t\texit(metric, unable)\n\t\t\t\t} else {\n\t\t\t\t\texit(metric, result[0])\n\t\t\t\t}\n\t\t\t}).catch(function(err){\n\t\t\t\texit(metric, err)\n\t\t\t})\n\t\t})\n\t}\n\tfunction searchValue(tester, maxValue, precision){\n\t\tlet minValue = 0\n\t\tlet ceiling = Math.pow(2, 32)\n\t\tfunction stepUp(){\n\t\t\tif (maxValue > ceiling || runST){\n\t\t\t\treturn Promise.reject(unable)\n\t\t\t}\n\t\t\treturn tester(maxValue).then(function(testResult){\n\t\t\t\tif (testResult === searchValue.isEqual){\n\t\t\t\t\treturn maxValue\n\t\t\t\t}\n\t\t\t\telse if (testResult === searchValue.isBigger){\n\t\t\t\t\tminValue = maxValue\n\t\t\t\t\tmaxValue *= 2\n\t\t\t\t\treturn stepUp()\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tfunction binarySearch() {\n\t\t\tif (maxValue - minValue < precision) {\n\t\t\t\treturn tester(minValue).then(function(testResult) {\n\t\t\t\t\tif (testResult.isEqual) {return minValue\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn tester(maxValue).then(function(testResult) {\n\t\t\t\t\t\t\tif (testResult.isEqual) {return maxValue\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn Promise.resolve(minValue) // +' to '+ maxValue // just return min\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlet pivot = (minValue + maxValue) / 2\n\t\t\t\treturn tester(pivot).then(function(testResult) {\n\t\t\t\t\tif (testResult === searchValue.isEqual) {return pivot\n\t\t\t\t\t} else if (testResult === searchValue.isBigger) {\n\t\t\t\t\t\tminValue = pivot\n\t\t\t\t\t\treturn binarySearch()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmaxValue = pivot\n\t\t\t\t\t\treturn binarySearch()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn stepUp().then(function(stepUpResult) {\n\t\t\tif (stepUpResult){return stepUpResult\n\t\t\t} else {return binarySearch()}\n\t\t})\n\t}\n\tsearchValue.isSmaller = -1\n\tsearchValue.isEqual = 0\n\tsearchValue.isBigger = 1\n\n\trunTest(function(group, prefix, maxPrefix, suffix, maxValue, precision) {\n\t\treturn searchValue(function(valueToTest) {\n\t\t\ttry {\n\t\t\t\tif (runSE) {foo++}\n\t\t\t\tif (window.matchMedia('('+ prefix +': '+ valueToTest + suffix+')').matches){\n\t\t\t\t\treturn Promise.resolve(searchValue.isEqual)\n\t\t\t\t} else if (window.matchMedia('('+ maxPrefix +': '+ valueToTest + suffix+')').matches){\n\t\t\t\t\treturn Promise.resolve(searchValue.isSmaller)\n\t\t\t\t} else {\n\t\t\t\t\treturn Promise.resolve(searchValue.isBigger)\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tlet metric = oPrefixes[prefix] +'_media'\n\t\t\t\tif ('pixels' == group) {metric = group +'_'+ ('resolution' == prefix ? suffix : prefix)}\n\t\t\t\tlog_error(1, metric, e, isScope)\n\t\t\t\treturn Promise.reject(zErr)\n\t\t\t}\n\t\t}, maxValue, precision)\n\t})\n})\n\nconst get_scr_orientation = (METRIC) => new Promise(resolve => {\n\t// NOTE: a screen.orientation.addEventListener('change'.. event\n\t\t// does not detect css changes, but a resize event does, which\n\t\t// is the only one we use, so treat css as truthy\n\tlet oData = {'device': {}, 'window': {}}, oDisplay = {}\n\t// matchmedia: sorted names\n\tlet oTests = {\n\t\t'device': {'-moz-device-orientation': '#cssOm', 'device-aspect-ratio': '#cssDAR'},\n\t\t'window': {'aspect-ratio': '#cssAR', 'orientation': '#cssO'}\n\t}\n\tlet l = 'landscape', p = 'portrait', q = '(orientation: ', s = 'square', a = 'aspect-ratio'\n\n\tfor (const type of Object.keys(oTests)) {\n\t\tfor (const item of Object.keys(oTests[type])) {\n\t\t\tlet value, isErr = false\n\t\t\tlet cssitem = item +'_css', cssID = oTests[type][item]\n\t\t\ttry {\n\t\t\t\tif ('-moz-device-orientation' == item) {\n\t\t\t\t\tif (window.matchMedia('(-moz-device-orientation:'+ l +')').matches) value = l\n\t\t\t\t\tif (window.matchMedia('(-moz-device-orientation:'+ p +')').matches) value = p\n\t\t\t\t} else if ('device-aspect-ratio' == item) {\n\t\t\t\t\tif (window.matchMedia('(device-'+ a +':1/1)').matches) value = s\n\t\t\t\t\tif (window.matchMedia('(min-device-'+ a +':10000/9999)').matches) value = l\n\t\t\t\t\tif (window.matchMedia('(max-device-'+ a +':9999/10000)').matches) value = p\n\t\t\t\t} else if ('aspect-ratio' == item) {\n\t\t\t\t\tif (window.matchMedia('('+ a +':1/1)').matches) value = s\n\t\t\t\t\tif (window.matchMedia('(min-'+ a +':10000/9999)').matches) value = l\n\t\t\t\t\tif (window.matchMedia('(max-'+ a +':9999/10000)').matches) value = p\n\t\t\t\t} else {\n\t\t\t\t\tif (window.matchMedia(q + p +')').matches) value = p\n\t\t\t\t\tif (window.matchMedia(q + l +')').matches) value = l\n\t\t\t\t}\n\t\t\t\tif (runST) {value = undefined} else if (runSL) {value += '_fake'}\n\t\t\t\t// can only be undefined (default) or a string (which we set)\n\t\t\t\tif (!isGecko && '-moz-device-orientation' == item) {\n\t\t\t\t\tif (value !== undefined) {throw zErrType + typeFn(value)} // undefined in nonGecko\n\t\t\t\t\tvalue += ''\n\t\t\t\t} else {\n\t\t\t\t\tif (value == undefined) {throw zErrType +'undefined'} // we expect values (in gecko)\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(1, METRIC +'_'+ type +'_'+ item, e)\n\t\t\t\tvalue = zErr\n\t\t\t\tisErr = true\n\t\t\t}\n\t\t\t// css\n\t\t\t// check matchmedia matches css\n\t\t\tlet cssvalue = getElementProp(1, cssID, METRIC +'_'+ cssitem)\n\t\t\tlet isErrCss = cssvalue == zErr\n\t\t\tlet isLies = (!isErr && !isErrCss && value !== cssvalue)\n\t\t\toDisplay[METRIC +'_'+ item] = {'value': value +'', 'lies': isLies}\n\t\t\tif (isSmart && isLies) {\n\t\t\t\tlog_known(1, METRIC +'_'+ type +'_'+ item, value)\n\t\t\t\tvalue = zLIE\n\t\t\t}\n\t\t\toData[type][item] = value\n\t\t\toData[type][cssitem] = cssvalue\n\t\t}\n\t}\n\n\t// device: try and get a valid css value\n\tlet check = oData.device['-moz-device-orientation_css']\n\tif (zErr == check) {check = oData.device['device-aspect-ratio_css']}\n\tif ('square' == check) {check = 'portrait'}\n\n\t// screen\n\t// 1325110: mozOrientation slated for deprecation\n\tlet items = ['mozOrientation', 'orientation.angle', 'orientation.type']\n\tlet targets = ['screen','iframe'], iscreen\n\ttry {iscreen = dom.tzpIframe.contentWindow.screen} catch {}\n\ttargets.forEach(function(k) {\n\t\tlet strIframe = 'iframe' == k ? '_iframe' : ''\n\t\tlet target = 'screen' == k ? screen : iscreen\n\t\titems.forEach(function(item) {\n\t\t\tlet value, expectedType = 'string', isAngle = 'orientation.angle' == item, isLies = false\n\t\t\ttry {\n\t\t\t\tif ('mozOrientation' == item) {\n\t\t\t\t\tvalue = target.mozOrientation\n\t\t\t\t\t// gecko: undefined throws an error, 'undefined' returns the string (or a lie if isSmart)\n\t\t\t\t\tif (!isGecko) {expectedType = 'undefined'}\n\t\t\t\t} else if (isAngle) {\n\t\t\t\t\tvalue = target.orientation.angle; expectedType = 'number'\n\t\t\t\t} else {value = target.orientation.type\n\t\t\t\t}\n\t\t\t\tif (runST) {value = isAngle ? value +'' : true\n\t\t\t\t} else if (runSI && isAngle) {value = 45\n\t\t\t\t} else if (runSL) {value = isAngle ? 90 : 'portrait-primary'\n\t\t\t\t}\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (isAngle) {\n\t\t\t\t\tlet aGood = [0, 90, 180, 270]\n\t\t\t\t\tif (!aGood.includes(Math.abs(value))) {\n\t\t\t\t\t\tthrow zErrInvalid + 'expected 0, 90, 180 or 270: got '+ value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// lies\n\t\t\t\tif (isSmart && zErr !== check) {\n\t\t\t\t\t// check mozOrientation + .type matches css\n\t\t\t\t\t// note: we can't check the angle, it could be anything - see Piero tablet tests\n\t\t\t\t\tif ('string' == expectedType && value.split('-')[0] !== check) {\n\t\t\t\t\t\tlog_known(1, METRIC +'_device_'+ item + strIframe, value)\n\t\t\t\t\t\tisLies = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(1, METRIC +'_device_'+ item + strIframe, e)\n\t\t\t\tvalue = zErr\n\t\t\t}\n\t\t\tif ('mozOrientation' == item && undefined == value) {value += ''} // only nonGecko mozOrientation can be undefined, we already threw\n\t\t\toDisplay[METRIC +'_'+ item + strIframe] = {'value': value, 'lies': isLies}\n\t\t\toData['device'][item + strIframe] = isLies ? zLIE : value\n\t\t})\n\t})\n\t// sort device object\n\tlet tmpObj = {}\n\tfor (const k of Object.keys(oData.device).sort()) {tmpObj[k] = oData.device[k]}\n\toData.device = tmpObj\n\t// https://searchfox.org/mozilla-central/source/testing/web-platform/tests/screen-orientation/orientation-reading.html\n\t// see expectedAnglesLandscape + expectedAnglesPortrait\n\n\t// display, data\n\tfor (const k of Object.keys(oDisplay)) {addDisplay(1, k, oDisplay[k]['value'],'','', oDisplay[k]['lies'])}\n\tfor (const k of Object.keys(oData)) {\n\t\t// objects are already sorted\n\t\tlet data = oData[k]\n\t\tlet hash = mini(data)\n\t\taddData(1, METRIC +'_'+ k, oData[k], hash)\n\t\tif ('device' == k) {\n\t\t\t// create a summary\n\t\t\t// type: note: we already type checked mozOrientation on all engines and threw\n\t\t\tlet aTemp = [data['orientation.type'], data['orientation.type_iframe']]\n\t\t\tif (isGecko) {aTemp.push(data.mozOrientation, data.mozOrientation_iframe)\n\t\t\t} else {\n\t\t\t\tif ('undefined' !== data.mozOrientation) {aTemp.push(data.mozOrientation)}\n\t\t\t\tif ('undefined' !== data.mozOrientation_iframe) {aTemp.push(data.mozOrientation_iframe)}\n\t\t\t}\n\t\t\taTemp = dedupeArray(aTemp)\n\t\t\tlet summary = aTemp.length > 1 ? 'mixed': aTemp[0]\n\t\t\t// angle\n\t\t\taTemp = [data['orientation.angle'], data['orientation.angle_iframe']]\n\t\t\taTemp = dedupeArray(aTemp)\n\t\t\tsummary += ' | ' + (aTemp.length > 1 ? 'mixed': aTemp[0])\n\t\t\t// orientation\n\t\t\t\t// note: aspect ratio can be square since we return that from css rather than portrait\n\t\t\taTemp = [data['-moz-device-orientation'], data['-moz-device-orientation_css']]\n\t\t\taTemp = dedupeArray(aTemp)\n\t\t\tlet strOrientation = (aTemp.length > 1 ? 'mixed': aTemp[0])\n\t\t\tif (!isGecko && 'undefined' == strOrientation) {strOrientation = ''}\n\t\t\t// aspect-ratio\n\t\t\tif ('mixed' !== strOrientation) {\n\t\t\t\taTemp = [data['device-aspect-ratio'], data['device-aspect-ratio_css']]\n\t\t\t\taTemp = dedupeArray(aTemp)\n\t\t\t\tlet strAspect = (aTemp.length > 1 ? 'mixed': aTemp[0])\n\t\t\t\tif (strOrientation !== strAspect) {strOrientation += (strOrientation.length ? ' + ': '') + strAspect}\n\t\t\t}\n\t\t\tsummary += ' | ' + strOrientation\n\t\t\taddDisplay(1, METRIC +'_'+ k +'_summary', summary)\n\n\t\t\t// notation: use our summary\n\t\t\t\t// FF132+: 1607032 + 1918202 | FF133+: 1922204 | backported to BB\n\t\t\t\t// RFP is always primary | on android the angle of 0 vs 90 is reversed\n\t\t\t\t// type | angle | orientation (css) + aspect ratio (css)\n\t\t\tlet oGood = {\n\t\t\t\t'true': [ // desktop\n\t\t\t\t\t'landscape-primary | 0 | landscape',\n\t\t\t\t\t'portrait-primary | 90 | portrait',\n\t\t\t\t\t'portrait-primary | 90 | portrait + square'\n\t\t\t\t],\n\t\t\t\t'false': [ // android\n\t\t\t\t\t'landscape-primary | 90 | landscape',\n\t\t\t\t\t'portrait-primary | 0 | portrait',\n\t\t\t\t\t'portrait-primary | 0 | portrait + square',\n\t\t\t\t]\n\t\t\t}\n\t\t\tlet notation = oGood[isDesktop].includes(summary) ? rfp_green : rfp_red\n\t\t\taddDisplay(1, METRIC +'_'+ k,'','', notation)\n\t\t}\n\t}\n\treturn resolve()\n})\n\nconst get_scr_pixels = (METRIC) => new Promise(resolve => {\n\n\tfunction get_dpr() {\n\t\t// DPR window\n\t\tlet value, display, item = 'devicePixelRatio'\n\t\tlet targets = ['window','iframe']\n\t\ttargets.forEach(function(k) {\n\t\t\tvalue = undefined\n\t\t\tlet strIframe = 'iframe' == k ? '_iframe' : ''\n\t\t\ttry {\n\t\t\t\tlet target = 'window' == k ? window : dom.tzpIframe.contentWindow.window\n\t\t\t\tvalue = target.devicePixelRatio\n\t\t\t\tif (runST) {value = NaN} // this will also trigger dpi_div as varDPI is not set\n\t\t\t\tlet typeCheck = typeFn(value)\n\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tdisplay = value\n\t\t\t\tvarDPR = value\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(1, METRIC +'_'+ item + strIframe, e)\n\t\t\t\tdisplay = zErr\n\t\t\t\tvalue = zErr\n\t\t\t}\n\t\t\t// FF127: 1554751\n\t\t\tlet notation = value == 2 ? rfp_green : rfp_red\n\t\t\taddDisplay(1, METRIC +'_'+ item + strIframe, display, '', notation)\n\t\t\toData[item + strIframe] = value\n\t\t})\n\n\t\t// DPR border: 477157: don't notate this for health\n\t\tvalue = undefined, display = undefined, item = 'devicePixelRatio_border'\n\t\ttry {\n\t\t\tvalue = getComputedStyle(dom.tzpDPR).borderTopWidth // e.g. '1px'\n\t\t\tif (runST) {value = undefined} else if (runSI) {value = '123'}\n\t\t\tlet originalvalue = value\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tif ('string' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tif (value.slice(-2) !== 'px') {throw zErrInvalid + 'got '+ originalvalue} // missing px\n\t\t\tvalue = value.slice(0, -2)\n\t\t\tif (value.length > 0) {value = value * 1}\n\t\t\tif ('number' !== typeFn(value)) {throw zErrInvalid + 'got '+ originalvalue} // missing number\n\t\t\tif (value > 0) {\n\t\t\t\tvalue = 1/value\n\t\t\t\tdisplay = value\n\t\t\t\tvarDPR = value\n\t\t\t} else {\n\t\t\t\tthrow zErrInvalid + 'got '+ (1/value) // negative/Infinity\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tdisplay = log_error(1, METRIC +'_'+ item, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\taddDisplay(1, item, display)\n\t\toData[item] = value\n\t\treturn\n\t}\n\n\t// DPI CSS\n\tfunction get_dpi_css(item) {\n\t\tlet value = getElementProp(1, '#P', METRIC +'_'+ item, ':before')\n\t\tlet typeCheck = typeFn(value)\n\t\t// ignore errors (already caught) and of out of range (entirely possible?)\n\t\tif (value !== '?' && value !== zErr) {\n\t\t\tif ('number' !== typeCheck) {\n\t\t\t\tlog_error(1, METRIC +'_'+ item, zErrType + typeCheck), value = zErr\n\t\t\t}\n\t\t}\n\t\t// why did I allow a ? for css\n\t\t\t// was: 192 == value || '?' == value ? rfp_green : rfp_red\n\t\taddDisplay(1, METRIC +'_'+ item,'','', (192 == value ? rfp_green : rfp_red)) // css notate\n\t\toData[item] = value\n\t}\n\n\t// DPI DIV\n\tfunction get_dpi_div(item) {\n\t\t/* this FP value is redundant: it's essentially 96 * our DPR PoC (IIUIC)\n\t\tIIUIC: monitors always have a native resolution of 96 dpi\n\t\t\t- our div element should always have a height of 96\n\t\t\t- regardless of zoom + system scaling + layout.css.devPixelsPerPx (which combined == devicePixelRatio)\n\t\t\t- but IDK about e.g. QLED/Quantum \"dots\" and other emerging standards\n\t\t// tested with zooming levels: it's always 96 (domrect, offset, client)\n\t\t\t- system scaling 100% | 125%\n\t\t\t- layout.css.devPixelsPerPx 1.1 (equivalent to 110% zoom)\n\t\t\t- system scaling 125% + layout.css.devPixelsPerPx 1.1 combined\n\t\t*/\n\t\tlet display, value\n\t\ttry {\n\t\t\tlet target = dom.tzpDPI\n\t\t\t// domrect will not give us any greater precision AFAICT, but why not\n\t\t\tlet targetValue = 0 == isDomRect ? target.getBoundingClientRect().height : target.offsetHeight\n\t\t\t// the final \"dpi\" value comes from multiplying by DPR (our poc leak one)\n\t\t\tvalue = targetValue * varDPR\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tdisplay = value\n\t\t} catch(e) {\n\t\t\tdisplay = log_error(1, METRIC +'_'+ item, e), value = zErr\n\t\t}\n\t\taddDisplay(1, item, display)\n\t\toData[item] = value\n\t}\n\n\t// visualViewport scale\n\tfunction get_vv_scale(item) {\n\t\tlet value, display\n\t\ttry {\n\t\t\tvalue = visualViewport.scale\n\t\t\tif (runST) {value = undefined}\n\t\t\tlet typeCheck = typeFn(value)\n\t\t\tdisplay = value\n\t\t\tif ('number' !== typeof value) {throw zErrType + typeCheck}\n\t\t} catch(e) {\n\t\t\tdisplay = log_error(1, METRIC +'_'+ item, e)\n\t\t\tvalue = zErr\n\t\t}\n\t\taddDisplay(1, item, display)\n\t\toData[item] = value\n\t\treturn\n\t}\n\n\t// run\n\tlet varDPR, oData = {}\n\tPromise.all([\n\t\tget_scr_mm('pixels')\n\t]).then(function(results){\n\t\tfor (const k of Object.keys(results[0])) {\n\t\t\t// expected 100% zoom values\n\t\t\tlet oMatch = {\n\t\t\t\t'-moz-device-pixel-ratio': 2,\n\t\t\t\t'-webkit-device-pixel-ratio': 2,\n\t\t\t\t'dpcm': 75.59054999999998,\n\t\t\t\t'dpi': 192.00000000000006,\n\t\t\t\t'dppx': 2,\n\t\t\t}\n\t\t\tlet value = results[0][k]\n\t\t\toData[k] = value\n\t\t\taddDisplay(1, METRIC +'_'+ k, value,'', (value == oMatch[k] ? rfp_green : rfp_red))\n\t\t}\n\t\tget_dpr() // sets varDPR used in dpi_div\n\t\tget_dpi_css('dpi_css')\n\t\tget_dpi_div('dpi_div')\n\t\tif (isDesktop) {\n\t\t\t// android: useless, not stable as it is affected by zoom\n\t\t\tget_vv_scale('visualViewport_scale')\n\t\t}\n\t\tlet newobj = {}\n\t\tfor (const k of Object.keys(oData).sort()) {newobj[k] = oData[k]}\n\t\taddData(1, METRIC, newobj, mini(newobj))\n\t\t// pixel matches\n\t\tif (isSmart) {\n\t\t\tget_scr_pixels_match('pixels_match', oData)\n\t\t}\n\t\treturn resolve()\n\t})\n})\n\nfunction get_scr_pixels_match(METRIC, oData) {\n\tif (!isSmart) {return}\n\n\t// media pixels vs window devicePixelRatio\n\tlet isPixelMatch = true, oPixels = {}, oSummary = {'false': [], 'true': []}, controlPx, testPx\n\t// remove items we don't compare\n\tlet aIgnore = ['devicePixelRatio_border','dpi_div','visualViewport_scale']\n\taIgnore.forEach(function(item){delete oData[item]})\n\n\ttry {\n\t\t// typecheck\n\t\tfor (const k of Object.keys(oData).sort()) {\n\t\t\tlet typeCheck = typeFn(oData[k])\n\t\t\tif ('number' !== typeCheck) {\n\t\t\t\t// ignore out-of-range css\n\t\t\t\tif ('dpi_css' == k && '?' == oData[k]) {} else {throw zErrInvalid + k +' expected number: got '+ typeCheck}\n\t\t\t}\n\t\t}\n\t\tlet dprValue = oData.devicePixelRatio, dprStr = 'devicePixelRatio'\n\t\tlet oControls = {\n\t\t\t'-moz-device-pixel-ratio': [dprValue, dprStr],\n\t\t\t'-webkit-device-pixel-ratio': [dprValue, dprStr],\n\t\t\t//'devicePixelRatio': it's the control\n\t\t\t'devicePixelRatio_iframe': [dprValue, dprStr +''],\n\t\t\t'dpcm': [dprValue * 96 / 2.54, dprStr +' * 96 / 2.54'],\n\t\t\t'dpi': [dprValue * 96, dprStr +' * 96'],\n\t\t\t'dpi_css': [dprValue * 96, dprStr +' * 96'],\n\t\t\t'dppx': [dprValue, dprStr],\n\t\t}\n\t\tlet oLists = {\n\t\t\t'-moz-device-pixel-ratio': ['-moz-device-pixel-ratio','max--moz-device-pixel-ratio','min--moz-device-pixel-ratio'],\n\t\t\t'-webkit-device-pixel-ratio': ['-webkit-device-pixel-ratio','-webkit-max-device-pixel-ratio','-webkit-min-device-pixel-ratio'],\n\t\t\t'dppx': ['max-resolution','min-resolution','resolution'],\n\t\t}\n\t\t\n\t\t// b3e9e3c6 200% zoom dpr 1 === 100% zoom drp 1 with RFP\n\t\t//console.log(mini(oData), oData)\n\t\tfor (const k of Object.keys(oData).sort()) {\n\t\t\toPixels[k] = {}\n\t\t\tif (undefined !== oControls[k]) {\n\t\t\t\tcontrolPx = oControls[k][0]\n\t\t\t\toPixels[k].control = oControls[k]\n\t\t\t}\n\t\t\tif ('devicePixelRatio_iframe' == k) {\n\t\t\t\ttestPx = oData[k] == controlPx\n\t\t\t\toPixels[k]['match'] = testPx\n\t\t\t\tif (false === testPx) {isPixelMatch = false}\n\t\t\t\toSummary[testPx].push(k)\n\t\t\t} else if ('dpcm' == k || 'dpi' == k) {\n\t\t\t\tlet diff = Math.abs(oData[k] - controlPx)\n\t\t\t\toPixels[k].diff = diff\n\t\t\t\tlet testPx = diff < 0.0001\n\t\t\t\toPixels[k].match = testPx\n\t\t\t\tif (false === testPx) {isPixelMatch = false}\n\t\t\t\toSummary[testPx].push(k)\n\t\t\t} else if ('dpi_css' == k) {\n\t\t\t\t// ignore out-of-range dpi_css\n\t\t\t\tif ('?' !== oData[k]) {\n\t\t\t\t\ttestPx = oData[k] == Math.floor(controlPx) // css is min-resolution\n\t\t\t\t\toPixels[k]['match'] = testPx\n\t\t\t\t\tif (false === testPx) {isPixelMatch = false}\n\t\t\t\t\toSummary[testPx].push(k)\n\t\t\t\t}\n\t\t\t} else if (oLists[k] !== undefined) {\n\t\t\t\t// ToDo: is max actually needed, so we need min?\n\t\t\t\tlet unit = 'dppx' == k ? 'dppx' : ''\n\t\t\t\toLists[k].forEach(function(item){\n\t\t\t\t\ttestPx = window.matchMedia('('+ item +':'+ controlPx + unit +')').matches\n\t\t\t\t\toPixels[k]['match '+item] = testPx\n\t\t\t\t\tif (false === testPx) {isPixelMatch = false}\n\t\t\t\t\toSummary[testPx].push(k +'_'+ item)\n\t\t\t\t})\n\t\t\t}\n\t\t\toPixels[k].value = oData[k]\n\t\t}\n\t\t// make the notification clickable\n\t\tsDetail[isScope][METRIC] = oPixels\n\t\tlet btncolor = isPixelMatch ? 'good' : 'bad'\n\t\tlet btnsymbol = isPixelMatch ? tick : cross\n\t\taddDisplay(1, METRIC,'','', addButton(btncolor, METRIC, \"<span class='health'>\"+ btnsymbol +\"</span> RFP pixels\"))\n\t} catch(e) {\n\t\tsDetail[isScope][METRIC] = e+''\n\t\taddDisplay(1, METRIC,'','', sbx+' RFP pixels]'+sc)\n\t}\n}\n\nconst get_scr_position_screen = (METRIC) => new Promise(resolve => {\n\t// left/top = 0 depends on secondary monitor | availLeft/availTop = 0 depends on dock/taskbar\n\tlet tmpObj = {}, aList = ['availLeft','availTop','left','top']\n\t// nonGecko: number vs undefined: i.e a string of \"undefined\" will be an error\n\tlet aNonGecko = ['left','top']\n\tlet targets = ['screen','iframe'], iscreen, display = []\n\ttry {iscreen = dom.tzpIframe.contentWindow.screen} catch {}\n\ttargets.forEach(function(k) {\n\t\tlet strIframe = 'iframe' == k ? '_iframe' : ''\n\t\tlet target = 'screen' == k ? screen : iscreen, x\n\t\taList.forEach(function(item){\n\t\t\ttry {\n\t\t\t\tx = target[item]\n\t\t\t\tif (runST) {x = 'undefined'}\n\t\t\t\tlet typeCheck = typeFn(x), expectedType = 'number'\n\t\t\t\tif (!isGecko && aNonGecko.includes(item)) {expectedType = 'undefined'}\n\t\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (undefined == x) {x += ''}\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(1, METRIC +'_'+ item + strIframe, e); x = zErr\n\t\t\t}\n\t\t\ttmpObj[item + strIframe] = x\n\t\t})\n\t})\n\t// sort object\n\tlet oData = {}, isMixed = false, btn =''\n\tfor (const k of Object.keys(tmpObj).sort()) {oData[k] = tmpObj[k]}\n\t//console.log(oData, mini(oData))\n\tlet hash = mini(oData)\n\tlet notation = '4963ac89' == hash ? rfp_green : rfp_red\n\taddData(1, METRIC, oData, hash)\n\taList.forEach(function(item){\n\t\tlet isMatch = oData[item] == oData[item +'_iframe']\n\t\tif (!isMatch) {isMixed = true}\n\t\tdisplay.push(isMatch ? oData[item] : 'mixed')\n\t})\n\tif (isMixed) {btn = addButton(1, METRIC)}\n\taddDisplay(1, METRIC, display.join(', '), btn, notation)\n\treturn resolve()\n})\n\nconst get_scr_position_window = (METRIC) => new Promise(resolve => {\n\t// FS = all 0 except sometimes mozInnerScreenY | maximized can include negatives for screenX/Y\n\tlet oData = {}, aList = ['mozInnerScreenX','mozInnerScreenY','screenX','screenY']\n\t// nonGecko: number vs undefined: i.e a string of \"undefined\" will be an error\n\tlet aNonGecko = ['mozInnerScreenX','mozInnerScreenY']\n\tlet display = [], x\n\taList.forEach(function(k){\n\t\ttry {\n\t\t\tx = window[k]\n\t\t\tif (runST) {x = 'undefined'}\n\t\t\tlet typeCheck = typeFn(x), expectedType = 'number'\n\t\t\t\tif (!isGecko && aNonGecko.includes(k)) {expectedType = 'undefined'}\n\t\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\tif (undefined == x) {x += ''}\n\t\t} catch(e) {\n\t\t\tlog_error(1, METRIC +'_'+ k, e); x = zErr\n\t\t}\n\t\toData[k] = x; display.push(x)\n\t})\n\tlet hash = mini(oData)\n\tlet notation = '66a7ee25' == hash ? rfp_green : rfp_red\n\taddDisplay(1, METRIC, display.join(', '), '', notation)\n\taddData(1, METRIC, oData, hash)\n\treturn resolve()\n})\n\nfunction get_scr_viewport_units() {\n\t// https://developer.mozilla.org/en-US/docs/Web/CSS/length\n\t// large, dynamic, small unit support: FF101, Safari 15.4, blink 108\n\n\t// desktop + android use small in inner section\n\t// android uses large as a standalone\n\tlet aList = isDesktop ? ['S'] : ['L','S']\n\tlet data = {'height': {}, 'width': {}}\n\n\taList.forEach(function(k) {\n\t\tlet METRIC = 'L' == k ? 'sizes_viewport' : 'sizes_inner'\n\t\tlet target\n\t\ttry {target = dom['tzp'+ k +'V']} catch {}\n\t\tlet prefix = k.toLowerCase() + 'v'\n\t\tfor (const p of Object.keys(data)) {\n\t\t//aItems.forEach(function(p) {\n\t\t\tlet name = prefix + p.slice(0,1)\n\t\t\ttry {\n\t\t\t\tlet x\n\t\t\t\tif (isDomRect == -1) {\n\t\t\t\t\tx = p == 'width' ? target.offsetWidth : target.offsetHeight\n\t\t\t\t} else {\n\t\t\t\t\tlet method = measureFn(target, METRIC +'_'+ prefix)\n\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\tx = 'width' == p ? method.width : method.height\n\t\t\t\t\t//type check\n\t\t\t\t\tif (runST) {x = p == 'width' ? undefined : '' }\n\t\t\t\t\tlet typeCheck = typeFn(x)\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\tdata[p][name] = x\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tlog_error(1, METRIC +'_'+ p + '_' + prefix + p.slice(0,1), e)\n\t\t\t\tdata[p][name] = zErr\n\t\t\t}\n\t\t}\n\t})\n\treturn data\n}\n\nconst get_scr_viewport = (METRIC) => new Promise(resolve => {\n\t// get viewport units\n\tisViewportUnits = get_scr_viewport_units()\n\n\tlet oData = {height: {}, width: {}}\n\tconst id= 'vp-element', aMETRIC = 'sizes_inner'\n\n\tfunction get_viewport(type) {\n\t\tlet w, h, method, target\n\t\tlet metric = isDesktop ? aMETRIC : METRIC\n\n\t\ttry {\n\t\t\tif ('element' == type) {\n\t\t\t\ttarget = document.createElement('div')\n\t\t\t\ttarget.setAttribute('id', id)\n\t\t\t\ttarget.style.cssText = 'position:fixed;top:0;left:0;bottom:0;right:0;'\n\t\t\t\tdocument.documentElement.insertBefore(target,document.documentElement.firstChild)\n\t\t\t\tif (isDomRect == -1) {\n\t\t\t\t\tw = target.offsetWidth\n\t\t\t\t\th = target.offsetHeight\n\t\t\t\t} else {\n\t\t\t\t\tmethod = measureFn(target, METRIC +'_'+ type)\n\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\tw = method.width\n\t\t\t\t\th = method.height\n\t\t\t\t}\n\t\t\t} else if ('document' == type) {\n\t\t\t\t// using document.documentElement + domrect = the full web content dimensions: we can\n\t\t\t\t// use domrect width as we know that is fixed | height we use clientHeight as this reports inner\n\t\t\t\ttarget = document.documentElement\n\t\t\t\th = target.clientHeight\n\t\t\t\tif (isDomRect == -1) {\n\t\t\t\t\tw = target.clientWidth\n\t\t\t\t} else {\n\t\t\t\t\tmethod = measureFn(target, METRIC +'_'+ type)\n\t\t\t\t\tif (undefined !== method.error) {throw method.errorstring}\n\t\t\t\t\tw = method.width\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tw = window.visualViewport.width \n\t\t\t\th = window.visualViewport.height\n\t\t\t}\n\n\t\t\tif (runST) {w = NaN, h = undefined}\n\t\t\tlet wType = typeFn(w), hType = typeFn(h)\n\t\t\tif ('number' !== wType) {\n\t\t\t\tlog_error(1, metric +'_width_'+ type, zErrType + wType)\n\t\t\t\tw = zErr\n\t\t\t}\n\t\t\tif ('number' !== hType) {\n\t\t\t\tlog_error(1, metric +'_height_'+ type, zErrType + hType)\n\t\t\t\th = zErr\n\t\t\t}\n\t\t} catch(e) {\n\t\t\th = zErr; w = zErr\n\t\t\tif (isDesktop) {\n\t\t\t\tlog_error(1, metric +'_'+ type, e)\n\t\t\t} else {\n\t\t\t\tlog_error(1, metric +'_width_'+ type, e)\n\t\t\t\tlog_error(1, metric +'_height_'+ type, e)\n\t\t\t}\n\t\t}\n\t\toData.height[type] = h //+ 100\n\t\toData.width[type] = w //+ 200\n\n\t\t// android only calls document and uses it in inner section\n\t\t// we can just store this in isViewportUnits\n\t\tif (!isDesktop) {\n\t\t\tisViewportUnits.height['document'] = h\n\t\t\tisViewportUnits.width['document'] = w\n\t\t}\n\t}\n\n\t// ToDo: we could also use size observer / IntersectionObserverEntry\n\t// all\n\tget_viewport('document')\n\t// android: there is no viewport section: document becomes part of inner section.\n\t\t// element + visualViewport are redundant with a TZP clean load (new tab etc) and\n\t\t// can be or are unstable with dynamic urlbar/toolbar and pinch to zoom/reruns combos etc\n\tif (isDesktop) {\n\t\tget_viewport('element')\n\t\tget_viewport('visualViewport')\n\t\tremoveElementFn(id)\n\t}\n\t// resolve\n\treturn resolve(oData) // return the data for use in the parent function\n})\n\n/* AGENT */\n\nconst get_agent = (METRIC, os = isOS) => new Promise(resolve => {\n\tlet oReported = {'useragent': {}, 'useragentdata': {}}\n\tlet oComplex = {}, oData = {}, countFail = 0, countSuccess = 0\n\t/*\n\twindows:\n\t- FF116+ 1841425: windows hardcoded to 10.0 (patched 117 but 115 was last version for < win10)\n\tmac:\n\t- FF116+ 1841215: mac hardcoded to 10.15 (patched 117 but 115 was last release for < 10.15)\n\tandroid:\n\t- FF122+ 1865766: hardcod to 10.0 - partially backed out\n\t- FF123+ 1861847: hardcod oscpu/platform to 'Linux armv81'\n\t- FF126+ [pending: they shipped an intervention instead] 1860417: Linux added to appVersion + ua_os\n\tlinux:\n\t- FF123+ 1861847: hardcode oscpu/platform to \"Linux x86_64\" (backed out? on hold? these are RFP's values anyway)\n\t- FF127+ 1873273: report non-x86_64 CPUs (including 32-bit x86) as \"x86_64\"\n\t*/\n\n\t/*\n\t- FF132+ 1711835 SPOOFED_PLATFORM dropped, is now hardcoded for all\n\t*/\n\n\t// RFP notation: nsRFPService.h\n\tlet oRFP = {\n\t\tandroid: {\n\t\t\tappVersion: '5.0 (Android 10)', oscpu: 'Linux armv81', platform: 'Linux armv81', ua_os: 'Android 10; Mobile'\n\t\t},\n\t\tlinux: {\n\t\t\tappVersion: '5.0 (X11)', oscpu: 'Linux x86_64', platform: 'Linux x86_64', ua_os: 'X11; Linux x86_64'\n\t\t},\n\t\tmac: {\n\t\t\tappVersion: '5.0 (Macintosh)', oscpu: 'Intel Mac OS X 10.15', platform: 'MacIntel', ua_os: 'Macintosh; Intel Mac OS X 10.15'\n\t\t},\n\t\twindows: {\n\t\t\tappVersion: '5.0 (Windows)', oscpu: 'Windows NT 10.0; Win64; x64', platform: 'Win32', ua_os: 'Windows NT 10.0; Win64; x64'\n\t\t}\n\t}\n\tif (os !== undefined) {\n\t\tfor (const k of Object.keys(oRFP)) {\n\t\t\t// important: only add next version to array if we are open ended ('+')\n\t\t\tlet uaVer = isVer, isDroid = 'android' == k, nxtVer = uaVer + 1\n\t\t\t// userAgent\n\t\t\tlet uaRFP = 'Mozilla/5.0 (' + oRFP[k].ua_os +'; rv:', uaNext = uaRFP // base\n\t\t\tuaRFP += uaVer +'.0) Gecko/' + (isDroid ? uaVer +'.0' : '20100101') +' Firefox/'+ uaVer +'.0'\n\t\t\toRFP[k].userAgent = [uaRFP]\n\t\t\t// next userAgent\n\t\t\tif ('+' == isVerExtra) {\n\t\t\t\tuaNext += nxtVer +'.0) Gecko/'+ (isDroid ? nxtVer +'.0' : '20100101') +' Firefox/'+ nxtVer +'.0'\n\t\t\t\toRFP[k].userAgent.push(uaNext)\n\t\t\t}\n\t\t\t// desktop mode: 1727775\n\t\t\tif (isDroid) {\n\t\t\t\tuaRFP = 'Mozilla/5.0 (' + oRFP.linux.ua_os +'; rv:', uaNext = uaRFP // base\n\t\t\t\tuaRFP += uaVer +'.0) Gecko/20100101 Firefox/'+ uaVer +'.0'\n\t\t\t\toRFP[k].userAgent.push(uaRFP)\n\t\t\t\tif ('+' == isVerExtra) {\n\t\t\t\t\tuaNext += nxtVer +'.0) Gecko/20100101 Firefox/'+ nxtVer +'.0'\n\t\t\t\t\toRFP[k].userAgent.push(uaNext)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t//console.log(oRFP)\n\n\tlet list = {\n\t\t// static\n\t\tappCodeName: ['Mozilla', true],\n\t\tappName: ['Netscape', [1]],\n\t\tproduct: ['Gecko', null],\n\t\tbuildID: ['20181001000000', 1],\n\t\tproductSub: ['20100101', 1/0],\n\t\tvendor: ['empty string', {1:1}],\n\t\tvendorSub: ['empty string'],\n\t\t// more complex\n\t\tappVersion: ['skip', []],\n\t\tplatform: ['skip', {}],\n\t\toscpu: ['skip', NaN],\n\t\tuserAgent: ['skip'],\n\t}\n\tfor (const p of Object.keys(list).sort()) {\n\t\toData[p] = ''; oReported[METRIC][p] = '' // preset ordered objects\n\t\tlet expected = list[p][0], sim = list[p][1]\n\t\tlet isErr = false, str =''\n\t\ttry {\n\t\t\tstr = navigator[p]\n\t\t\tif (runST) {str = sim} else if (runSL) {\n\t\t\t\t// note: proxy lies are is only used on complex\n\t\t\t\taddProxyLie('Navigator.'+ p)\n\t\t\t\t// static we only check against expected: tampering is already recorded in\n\t\t\t\t// prototype lies and there is no need to record untrustworthy if it's expected\n\t\t\t\tif ('skip' !== expected) {\n\t\t\t\t\tstr = ('FAKE '+ str).trim()\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet typeCheck = typeFn(str, true), expectedType = 'string'\n\t\t\tif (!isGecko) {\n\t\t\t\t// type check will throw an error for a string \"undefined\"\n\t\t\t\tif ('buildID' == p || 'oscpu' == p) {expectedType = 'undefined'}\n\t\t\t}\n\t\t\tif (expectedType !== typeCheck) {throw zErrType + typeFn(str)}\n\t\t\tif ('' == str) {str = 'empty string'}\n\t\t} catch(e) {\n\t\t\tisErr = true\n\t\t\tstr = log_error(2, METRIC +'_'+ p, e)\n\t\t}\n\t\tif ('skip' !== expected) {\n\t\t\toutputStatic(p, str+'', expected, isErr)\n\t\t} else {\n\t\t\toComplex[p] = [str+'', isErr]\n\t\t}\n\t}\n\n\tfunction outputStatic(property, reported, expected, isErr) {\n\t\toReported[METRIC][property] = (isErr ? zErr : reported)\n\t\t//let isLies = isProxyLie('Navigator.'+ property)\n\t\t// prototypeLies doesn't pick everything up all the time: instead use expected\n\t\t\t// and because non-expected is lies\n\t\t// still notate slent fails so our count makes sense\n\t\tlet isLies = reported !== expected\n\t\tlet notation = (isErr || isLies) ? rfp_red : ''\n\t\taddDisplay(2, METRIC +'_'+ property, reported, '', notation, (isErr ? false : isLies))\n\t\tlet fpvalue = isErr ? zErr : (isSmart && isLies ? zLIE : reported)\n\t\tif (zLIE == fpvalue) {log_known(2, METRIC +'_'+ property, reported)}\n\t\toData[property] = fpvalue\n\t}\n\n\tfor (const k of Object.keys(oComplex)) {\n\t\tlet reported = oComplex[k][0], isErr = oComplex[k][1]\n\t\toReported[METRIC][k] = (isErr ? zErr : reported)\n\t\tlet isLies = isProxyLie('Navigator.'+ k)\n\t\tif (!isLies) {\n\t\t\tlet aFlags = []\n\t\t\t// prototypeLies doesn't pick everything up all the time: add some basic checks\n\t\t\t\t// note: may be valid, e.g. a fork uses a custom values\n\t\t\tif ('userAgent' == k | 'appVersion' == k) {\n\t\t\t\t// userAgent: e.g. Chameleon, Chrome Mask\n\t\t\t\t// appVersion: User-Agent Switcher\n\t\t\t\taFlags = [' like','Chrome','WebKit','KHTML','Apple','Safari']\n\t\t\t\tfor (let i=0; i < aFlags.length; i++) {\n\t\t\t\t\tif (reported.includes(aFlags[i])) {isLies = true; break}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!isLies) {\n\t\t\t\tif ('userAgent' == k) {\n\t\t\t\t\t// check version: all platforms contain '; rv:' + version + '.0)'\n\t\t\t\t\taFlags = [isVer]\n\t\t\t\t\tif ('+' == isVerExtra) {aFlags.push(isVer + 1)}\n\t\t\t\t\tlet isVerCheck = false\n\t\t\t\t\taFlags.forEach(function(item) {\n\t\t\t\t\t\tif (reported.includes('; rv:'+ item +'.0)')) {isVerCheck = true}\n\t\t\t\t\t})\n\t\t\t\t\tif (!isVerCheck) {isLies = true}\n\t\t\t\t} else if ('appVersion' == k) {\n\t\t\t\t\t// User-Agent Switcher\n\t\t\t\t\tif ('windows' == os) {isLies = reported !== '5.0 (Windows)'\n\t\t\t\t\t}\n\t\t\t\t} else if ('platform' == k) {\n\t\t\t\t\t// User-Agent Switcher\n\t\t\t\t\tif ('windows' == os) {isLies = reported !== 'Win32'\n\t\t\t\t\t}\n\t\t\t\t} else if ('oscpu' == k) {\n\t\t\t\t\t// User-Agent Switcher\n\t\t\t\t\tif ('windows' == os) {isLies = !reported.includes('Windows NT 10.0')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet notation = isLies ? rfp_red : '' // in case os is undefined\n\t\tif (os !== undefined) {\n\t\t\tlet rfpvalue = oRFP[os][k], isMatch = false\n\t\t\tisMatch = (k == 'userAgent' ? rfpvalue.includes(reported) : rfpvalue === reported)\n\t\t\tnotation = isMatch ? rfp_green : rfp_red\n\t\t\t// notate good desktopmode\n\t\t\tif (k == 'userAgent' && isMatch && !isDesktop) {\n\t\t\t\tif (reported.includes('Linux')) {notation = desktopmode_green}\n\t\t\t}\n\t\t\t// catch non-errors and non-lies health failures\n\t\t\tif (notation.includes(cross)) {countFail++} else if (notation.includes(tick)) {countSuccess++}\n\t\t}\n\t\taddDisplay(2, METRIC +'_'+ k, reported, '', notation, (isErr ? false : isLies))\n\t\t// record value in oData\n\t\tlet fpvalue = isErr ? zErr : (isSmart && isLies ? zLIE : reported)\n\t\tif (zLIE == fpvalue) {log_known(2, METRIC +'_'+ k, reported)}\n\t\toData[k] = fpvalue\n\t}\n\t// add lookup\n\taddDetail('agent_reported', oReported) // add reported for matching/compares: e.g. iframes\n\t// add metric\n\tfor (const k of Object.keys(oData)) {if (zLIE == oData[k] || zErr == oData[k]) {countFail++}} // count failures\n\tlet strCount = (0 == countFail ? sg : sb) +'['+ countSuccess +'/'+ (countFail + countSuccess) +']'+ sc\n\tlet agentnotation = (0 == countFail ? silent_rfp_green : silent_rfp_red)\n\taddBoth(2, METRIC, mini(oData), addButton(2, METRIC), agentnotation + strCount, oData)\n\treturn resolve()\n})\n\nconst get_agent_data = (METRIC, os = isOS, isMain = true) => new Promise(resolve => {\n\n\tfunction exit(hash, data ='', btn ='') {\n\t\tif (isMain) {\n\t\t\tsDetail[isScope]['agent_reported'][METRIC] = ('object' == typeof data ? data : hash)\n\t\t\taddBoth(2, METRIC, hash, btn,'', data)\n\t\t}\n\t\treturn resolve(data)\n\t}\n\ttry {\n\t\tlet k = navigator.userAgentData\n\t\tif (runSE) {foo++} else if (runST) {k = 1} else if (runSI) {k = {}}\n\t\tlet typeCheck = typeFn(k, true)\n\t\tif ('undefined' == typeCheck) {\n\t\t\texit(typeCheck)\n\t\t} else {\n\t\t\t// type check\n\t\t\tif ('object' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\tlet expected = '[object NavigatorUAData]'\n\t\t\tif (expected !== k+'') {throw zErrInvalid +'expected '+ expected +' got '+ k+''}\n\t\t\t// https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues\n\t\t\tnavigator.userAgentData.getHighEntropyValues([\n\t\t\t\t'architecture','bitness','brands','formFactors','fullVersionList','mobile',\n\t\t\t\t'model','platform','platformVersion','uaFullVersion','wow64'\n\t\t\t]).then(res => {\n\t\t\t\t//let data = res\n\t\t\t\t// new object: merge versions + check for mismatches\n\t\t\t\t\t// e.g. brands, fullVersionList, uaFullVersion\n\t\t\t\t// keep order: e.g. opera vs chrome differs in order of array items\n\t\t\t\t// only blink so no smarts, for now just add the object\n \t\t\t\texit(mini(res), res, addButton(2, METRIC))\n\t\t\t}).catch(function(err){\n\t\t\t\texit(err, zErrLog)\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\texit(e, zErrLog)\n\t}\n})\n\nfunction get_agent_workers() {\n\tif (gRun && sectionIgnore.includes('agent')) {return}\n\t// control\n\tlet list = ['appCodeName','appName','appVersion','platform','product','userAgent']\n\tlet oCtrl = {}, r\n\tlist.forEach(function(prop) {\n\t\ttry {\n\t\t\tr = navigator[prop]\n\t\t\tif ('string' !== typeof r) {throw zErr}\n\t\t\tif ('' ==r) {r = 'empty string'}\n\t\t} catch(e) {\n\t\t\tr = zErr\n\t\t}\n\t\toCtrl[prop] = r\n\t})\n\tlet control = mini(oCtrl)\n\n\t// web\n\tlet scope0 = 'worker', metric0 = 'agent_'+ scope0, target0 = dom[metric0], test0 =''\n\tif (isFile) {\n\t\ttarget0.innerHTML = zSKIP\n\t} else {\n\t\ttry {\n\t\t\tlet workernav = new Worker('js/'+ scope0 +'_agent.js')\n\t\t\ttarget0.innerHTML = zF\n\t\t\tworkernav.addEventListener('message', function(e) {\n\t\t\t\t//console.log(scope0, e.data)\n\t\t\t\ttest0 = mini(e.data)\n\t\t\t\ttarget0.innerHTML = test0 + (test0 == control ? match_green : match_red)\n\t\t\t\tworkernav.terminate\n\t\t\t}, false)\n\t\t\tworkernav.postMessage('')\n\t\t} catch(e) {\n\t\t\ttarget0.innerHTML = log_error(2, metric0, e, scope0)\n\t\t}\n\t}\n\t// shared\n\tlet scope1 = 'worker_shared', metric1 = 'agent_'+ scope1, target1 = dom[metric1], test1 =''\n\ttry {\n\t\tlet sharednav = new SharedWorker('js/'+ scope1 +'_agent.js')\n\t\ttarget1.innerHTML = zF\n\t\tsharednav.port.addEventListener('message', function(e) {\n\t\t\t//console.log('scope1', e.data)\n\t\t\ttest1 = mini(e.data)\n\t\t\ttarget1.innerHTML = test1 + (test1 == control ? match_green : match_red)\n\t\t\tsharednav.port.close()\n\t\t}, false)\n\t\tsharednav.port.start()\n\t\tsharednav.port.postMessage('')\n\t} catch(e) {\n\t\ttarget1.innerHTML = log_error(2, metric1, e, scope1)\n\t}\n\t// service\n\tlet scope2 = 'worker_service', metric2 = 'agent_'+ scope2, target2 = dom[metric2], test2 =''\n\ttarget2.innerHTML = zF // assume failure\n\ttry {\n\t\t// register\n\t\tnavigator.serviceWorker.register('js/'+ scope2 +'_agent.js').then(function(swr) {\n\t\t\tlet sw\n\t\t\tif (swr.installing) {sw = swr.installing}\n\t\t\telse if (swr.waiting) {sw = swr.waiting}\n\t\t\telse if (swr.active) {sw = swr.active}\n\t\t\tsw.addEventListener('statechange', function(e) {\n\t\t\t\tif (e.target.state == 'activated') {\n\t\t\t\t\tsw.postMessage('')\n\t\t\t\t}\n\t\t\t})\n\t\t\tif (sw) {\n\t\t\t\t// listen\n\t\t\t\tlet channel = new BroadcastChannel('sw-agent')\n\t\t\t\tchannel.addEventListener('message', event => {\n\t\t\t\t\t//console.log('agent service', event.data.msg)\n\t\t\t\t\ttest2 = mini(event.data.msg)\n\t\t\t\t\ttarget2.innerHTML = test2 + (test2 == control ? match_green : match_red)\n\t\t\t\t\t// unregister & close\n\t\t\t\t\tswr.unregister().then(function(boolean) {})\n\t\t\t\t\tchannel.close()\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\ttarget2.innerHTML = zF +' ['+ sw +']'\n\t\t\t}\n\t\t},\n\t\tfunction(e) {\n\t\t\ttarget2.innerHTML = log_error(2, metric2, e, scope2)\n\t\t})\n\t} catch(e) {\n\t\ttarget2.innerHTML = log_error(2, metric2, e, scope2)\n\t}\n}\n\n/* OUTPUT */\n\nconst outputFD = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('feature')) {return resolve()}\n\n\tlet METRIC = 'infinity_architecture', value, data =''\n\ttry {\n  \tconst f = new Float32Array([Infinity - Infinity])\n  \tvalue = new Uint8Array(f.buffer)[3]\n  } catch(e) {\n  \tvalue = e; data = zErrLog\n  }\n\taddBoth(3, METRIC, value,'','', data)\n\n\t// arch: FF110+ pref removed: error means 32bit\n\tlet str = '64bit'; data = 64\n\tif (isArch !== true) {\n\t\tif ('RangeError: invalid array length' == isArch) {\n\t\t\tstr = '32bit'; data = 32\n\t\t} else {\n\t\t\tstr = isArch; data = zErr\n\t\t\t// blink we set zNA if expected error, so propigate that\n\t\t\tif ('blink' == isEngine && zNA == isArch) {data = zNA}\n\t\t}\n\t}\n\taddBoth(3, 'browser_architecture', str,'','', data)\n\n\tif (!isGecko) {\n\t\tlet aList = ['logo','wordmark','version']\n\t\tif (undefined == isOS) {aList.push('os')}\n\t\taList.forEach(function(item) {addBoth(3, item, zNA)})\n\t\taList = ['tzpWordmark','tzpResource']\n\t\taList.forEach(function(item) {addDisplay(3, item, zNA)})\n\t\t// browser\n\t\taddBoth(3, 'browser', isBrave ? 'Brave' : isEngine+'')\n\t\t// os\n\t\tif (undefined !== isOS) {addBoth(3, 'os', isOS)}\n\t\treturn resolve()\n\t}\n\n\t// logo\n\tlet wType, hType, w, h, isLogo, isLogoData ='', isWordmark, isWordData =''\n\ttry {\n\t\tw = dom.tzpAbout.width, h = dom.tzpAbout.height\n\t\tif (runST) {w += '', h = null}\n\t\twType = typeFn(w), hType = typeFn(h)\n\t\tif ('number' !== wType || 'number' !== hType) {throw zErrType + wType +' x '+ hType}\n\t\tisLogo = w +' x '+ h\n\t} catch(e) {\n\t\tisLogo = e; isLogoData = zErrShort\n\t}\n\n\t// about-wordmark.svg\n\t\t// we were using width/height but in beta/dev 148 (may have started earlier) offscreen would\n\t\t// produce different and unstable results (see below) so instead we will use naturalW/H\n\t// ToDo: if I can work ouot how to make w/h stable and different this may help expose other methods\n\t\t// to differentiate channels and PB mode\n\ttry {\n\t\tlet isHidden, isOffscreen\n\t\t// hidden\n\t\tw = dom.tzpBrandHidden.naturalWidth, h = dom.tzpBrandHidden.naturalHeight\n\t\tif (runST) {w = true, h += ''}\n\t\twType = typeFn(w), hType = typeFn(h)\n\t\tif ('number' !== wType || 'number' !== hType) {throw zErrType + wType +' x '+ hType}\n\t\tisHidden = w +' x '+ h\n\t\t// offscreen\n\t\t/*\n\t\tstable is one line \"Firefox Browser\" 336 x 48 - stable and matches\n\t\tnightly is two lines \"Firefox | Nightly\" 300 x 109 - stable and matches\n\t\tdev is two lines \"Firefox | Developer\"\n\t\t\thidden\t\t\t300 x 109\n\t\t\toffscreen\t\t628 x 227 <-- 1st time in a session and wtf ALWAYS THIS in PB windows\n\t\t\toffscreen\t\t615 x 223 <-- thereafter\n\t\tbeta is one line: \"Firefox\"\n\t\t\thidden\t\t\t300 x 67\t\n\t\t\toffscreen\t\t628 x 140 <-- 1st time in a session and wtf ALWAYS THIS in PB windows\n\t\t\toffscreen\t\t615 x 137 <-- thereafter\n\t\t\tbut offscreen should be 300 x 67\n\n\t\tnote: 1 1sec + pause before testing on gLoad solves stability but keeps the nonm-match\n\t\tbut I don't think this adds anything entropy wise\n\t\t*/\n\t\tisOffscreen = dom.tzpBrand.naturalWidth +' x '+ dom.tzpBrand.naturalHeight\n\t\tif (isHidden !== isOffscreen) {isHidden += ', ' + isOffscreen}\n\t\tisWordmark = isHidden\n\t} catch(e) {\n\t\tisWordmark = e; isWordData = zErrShort\n\t}\n\n\t// set isMB: legacy: older 128's still need detection\n\tif (gLoad && !isBB && isDesktop) {\n\t\tif (128 == isVer && isWordmark + isLogo == '400 x 32300 x 236') {\n\t\t\tisMB = true\n\t\t\tisBB = true\n\t\t}\n\t}\n\t// browser\n\tlet notation = isBB ? bb_red : ''\n\taddBoth(3, 'browser', (isMB ? 'Mullvad Browser' : (isTB ? 'Tor Browser' : 'Firefox')))\n\taddBoth(3, 'logo', isLogo,'', (isBB && '24 x 24' == isLogo ? bb_green : notation), isLogoData)\n\taddBoth(3, 'wordmark', isWordmark,'', (isBB && '0 x 0' == isWordmark ? bb_green : notation), isWordData)\n\n\t// eval\n\tMETRIC = 'eval.toString'\n\ttry {\n\t\tlet len = eval.toString().length\n\t\tif (runST) {len = 43}\n\t\tif (len !== 37) {throw zErrInvalid + 'expected 37: got '+ len}\n\t} catch(e) {\n\t\tlog_error(3, METRIC, e)\n\t}\n\t// os, version\n\taddBoth(3, 'os', (isOS == undefined ? (isOSErr !== undefined ? isOSErr : zErr) : isOS))\n\taddBoth(3, 'version', (isVerExtra !== '' ? isVer + isVerExtra : isVer))\n\t// set metricsPrefix\n\tif (isGecko && isSmart) {\n\t\tmetricsPrefix = (isMB ? 'MB' : (isTB ? 'TB': 'FF')) + isVer + isVerExtra +'-'+ (isOS !== undefined ? isOS : 'unknown') +'-'\n\t}\n\treturn resolve()\n})\n\nconst outputScreen = (isResize = false) => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('screen')) {return resolve()}\n\n\tPromise.all([\n\t\tget_scr_position_screen('position_screen'),\n\t\tget_scr_position_window('position_window'),\n\t\tget_scr_pixels('pixels'),\n\t\tget_scr_orientation('orientation'),\n\t\tget_scr_measure(),\n\t]).then(function(){\n\t\t// add listeners once\n\t\tif (gLoad && isDesktop) {\n\t\t\twindow.addEventListener('resize', function(){outputSection(1, true)})\n\t\t}\n\t\treturn resolve()\n\t})\n})\n\nconst outputAgent = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('agent')) {return resolve()}\n\n\tPromise.all([\n\t\t// keep order: useragent creates agent_reported lookup, and the others add to it\n\t\tget_agent('useragent'), // \n\t\tget_agent_data('useragentdata'),\n\t]).then(function(){\n\t\t// make agent_reported same structure as section\n\t\tlet newobj = {}, data = sDetail[isScope].agent_reported\n\t\tfor (const k of Object.keys(data).sort()) {\n\t\t\tif ('object' == typeof data[k]) {newobj[k] = {'hash': mini(data[k]), 'metrics': data[k]}} else {newobj[k] = data[k]}\n\t\t}\n\t\tsDetail[isScope].agent_reported = newobj\n\t\treturn resolve()\n\t})\n})\n\ncountJS(1)\n"
  },
  {
    "path": "js/storage.js",
    "content": "'use strict';\n\n/* Web SQL Database API / openDatabase\n\tdon't test for this: it was implemented only in blink and safari\n\t- https://developer.chrome.com/blog/deprecating-web-sql\n\t- blink: API default disabled 119 (oct 2023) removed 124 (april 2024)\n\t- safari: removed in 2019\n*/\n\n/*\n\tToDo: leverage expires to get real date/time (substract our constant - e.g 2 days)\n\tcookieStore.getAll().then(cookies=>console.log(cookies))\n\tsome engines return more information\n\te.g. chrome includes\n\t\texpires: 1765940558000\n*/\n\nfunction lookup_cookie(name) {\n\ttry {\n\t\tname += '='\n\t\tlet decodedCookie = decodeURIComponent(document.cookie)\n\t\tlet ca = decodedCookie.split(';')\n\t\tfor (let i=0; i < ca.length; i++) {\n\t\t\tlet c = ca[i]\n\t\t\twhile (c.charAt(0) == ' ') {c = c.substring(1)}\n\t\t\tif (c.indexOf(name) == 0) {return c.substring(name.length, c.length)}\n\t\t}\n\t} catch {}\n\treturn ''\n}\n\nconst lookup_cookiestore = async function(rndStr, k) {\n\ttry {\n\t\tlet cookie = await cookieStore.get(rndStr + k)\n\t\treturn cookie.value\n\t} catch(e) {\n\t\treturn ''\n\t}\n}\n\nconst lookup_permission = (item) => new Promise(resolve => {\n\ttry {\n\t\tnavigator.permissions.query({name: item}).then(function(r) {\n\t\t\treturn resolve(r.state)\n\t\t}).catch(e => {\n\t\t\treturn resolve()\n\t\t})\n\t} catch(e) {\n\t\treturn resolve()\n\t}\n})\n\nfunction lookup_storage_bucket(type, bytes, granted = false) {\n\tconst GiB = 1073741824\n\t// test\n\t//bytes = Math.floor(((32/5) * GiB))    // = 6.4 exact\n\t//bytes = Math.floor(((32/5) * GiB)) -1 // = 6-7 range\n\t//bytes = Math.floor(((32/5) * GiB)) +1 // = 6-7 range\n\t//bytes = 5368709119 // 5GiB minus 1 byte = 4-5 range\n\n\tlet value = (bytes/GiB) // in GiBs\n\tlet isExact = Number.isInteger(value)\n\tif (!isExact) {\n\t\t// catch obvious floating points: i.e a part byte difference :)\n\t\t// e.g. 32GiB * 20% (gecko's %) = 6.4GiB = but we get 6.3999999994412065\n\t\t// 6.4 * GiB = 6871947673.6 (gecko floors)\n\t\tlet upper = (Math.ceil(value *10)/10) // e.g. 6.4\n\t\tlet diff = (upper * GiB) - bytes\n\t\t//console.log('bytes', bytes,'\\nvalue', value,'\\nupper', upper,'\\ndiff', diff)\n\t\tif (diff < 1) {\n\t\t\tisExact = true\n\t\t\tvalue = upper\n\t\t}\n\t}\n\tif (!isExact) {\n\t\t// still not exact, floor it\n\t\tvalue = Math.floor(bytes/(GiB) * 10)/10\n\t}\n\n\tif ('quota' == type && !isExact) {\n\t\t// bucketize quota more\n\t\t\t// if persistent-storage is granted\n\t\t\t// if gecko and under 10GB\n\t\t\t// if blink which doesn't protect this | webkit IDK it seems to provide precise values\n\t\tlet isBucket = (isGecko && value < 10 || !isGecko || granted)\n\t\tif (isBucket) {\n\t\t\tif (value < 10) {\n\t\t\t\t// more precision\n\t\t\t\tvalue = Math.floor(value)\n\t\t\t\tvalue = value +'-'+ (value + 1)\n\t\t\t} else {\n\t\t\t\t// round to next 100, return range\n\t\t\t\tvalue = Math.ceil(value/100) * 100\n\t\t\t\tvalue = value-100 +'-'+ value\n\t\t\t}\n\t\t}\n\t}\n\t// webkit private window returns 1048576000 bytes = 1000MB\n\tif ('webkit' == isEngine && 1048576000 == bytes) {value = '1000 MB'} else {value += ' GiB'}\n\t// blink incognito returns 1819735497 bytes = some reduced calculation?\n\treturn value\n}\n\nconst get_caches = (METRIC) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\t// PB mode: DOMException: The operation is insecure.\n\t\t// FF122: 1864684: dom.cache.privatebrowsing.enabled\n\t\t// also see 1742344 / 1714354\n\n\t// type check first\n\t\t// e.g. insecure parent on http://www.raymondhill.net/ublock/pageloadspeed.html\n\tlet typeCheck = typeFn(window.self.caches, true)\n\ttry {\n\t\tif ('object' !== typeCheck) {\n\t\t\tthrow zErrType + typeCheck\n\t\t} else {\n\t\t\tPromise.all([\n\t\t\t\twindow.self.caches.keys()\n\t\t\t]).then(function(){\n\t\t\t\texit(zE)\n\t\t\t}).catch(function(e){\n\t\t\t\texit(log_error(6, METRIC, e))\n\t\t\t})\n\t\t}\n\t} catch(err) {\n\t\texit(log_error(6, METRIC, err))\n\t}\n\tfunction exit(str) {\n\t\taddBoth(6, METRIC, str,'','', (str = zE ? str : zErr))\n\t\tlog_perf(6, METRIC, t0)\n\t\treturn resolve()\n\t}\n})\n\nfunction get_cookies(METRIC, rndStr) {\n\tlet value\n\ttry {\n\t\tlet test = navigator.cookieEnabled\n\t\tif (runST) {test = undefined}\n\t\tlet typeCheck = typeFn(test)\n\t\tif ('boolean' !== typeCheck) {throw zErrType + typeCheck}\n\t\tvalue = test ? zE : zD\n\t} catch(e) {\n\t\tlog_error(6, METRIC, e); value = zErr\n\t}\n\n\tlet aTests = ['_session','_persistent']\n\taTests.forEach(function(k){\n\t\ttry {\n\t\t\tlet expires =''\n\t\t\tif ('_persistent' == k) {\n\t\t\t\tlet d = new Date()\n\t\t\t\td.setTime(d.getTime() + 172800000) // 2 days\n\t\t\t\texpires = '; expires='+ d.toUTCString()\n\t\t\t}\n\t\t\tdocument.cookie = rndStr + k +'='+ rndStr +'; SameSite=Strict' + expires\n\t\t\tvalue += ' | '+ (lookup_cookie(rndStr + k) == rndStr ? zS : zF)\n\t\t} catch(e) {\n\t\t\tlog_error(6, METRIC + k, e); value += ' | '+ zErr\n\t\t}\n\t})\n\t// don't use cookie in element names == adblockers might block display\n\taddDisplay(6, 'ctest', value)\n\taddData(6, METRIC, value)\n\treturn\n}\n\nconst get_cookiestore = (METRIC, rndStr) => new Promise(resolve => {\n\t// https://developer.mozilla.org/en-US/docs/Web/API/CookieStore\n\tfunction exit() {\n\t\t// don't use cookie in element names == adblockers might block display\n\t\taddDisplay(6, 'cstest', value)\n\t\taddData(6, METRIC, value)\n\t\treturn resolve()\n\t}\n\n\tlet value, obj = window[METRIC]\n\ttry {\n\t\tvalue = 'object' == typeFn(obj, true) ? zE : zD\n\t} catch(e) {\n\t\tlog_error(6, METRIC, e); value = zErr\n\t}\n\tif (isFile) {\n\t\tvalue += ' | '+ zSKIP + ' | '+ zSKIP\n\t\texit()\n\t} else {\n\t\t// use a different suffix than cookies\n\t\tlet aTests = ['_session_store','_persistent_store']\n\t\taTests.forEach(function(k){\n\t\t\ttry {\n\t\t\t\tlet options = {name: rndStr + k, value: rndStr}\n\t\t\t\tif ('_persistent_store' == k) {\n\t\t\t\t\toptions['expires'] = Date.now() + 172800000 // 2 days\n\t\t\t\t}\n\t\t\t\tcookieStore.set(options)\n\t\t\t\tPromise.all([\n\t\t\t\t\tlookup_cookiestore(rndStr, k),\n\t\t\t\t]).then(function(res){\n\t\t\t\t\tvalue += ' | ' + (res[0] == rndStr ? zS : zF)\n\t\t\t\t\tif ('_persistent_store' == k) {exit()}\n\t\t\t\t})\n\t\t\t} catch(e) {\n\t\t\t\t// slice \"_store\": consistent style to match cookies\n\t\t\t\t// redundant to use \"cookieStore_session_store\"\n\t\t\t\tlog_error(6, METRIC + k.slice(0,-6), e); value += ' | '+ zErr\n\t\t\t\tif ('_persistent_store' == k) {exit()}\n\t\t\t}\n\t\t})\n\t}\n})\n\nfunction get_filesystem(METRIC) {\n\tlet display = isFileSystem, notation =''\n\tif (isFileSystem === zErr) {\n\t\tdisplay = log_error(6, METRIC, isFileSystemError)\n\t}\n\t// PBmode: SecurityError: Security error when calling GetDirectory\n\tif (isBB) {\n\t\tnotation = ('SecurityError: Security error when calling GetDirectory' == isFileSystemError ? bb_green : bb_red)\n\t} else {\n\t\t// FF111: 1811001: dom.fs.enabled = true\n\t\tif (isFileSystem == zD) {notation = default_red}\n\t}\n\taddBoth(6, METRIC, display,'', notation, isFileSystem)\n\treturn\n}\n\nfunction get_idb(METRIC) {\n\tlet value = zE\n\ttry {\n\t\tlet test = window[METRIC]\n\t\tif (runST) {test = []}\n\t\tlet typeCheck = typeFn(test, true)\n\t\tif ('undefined' == typeCheck) {value = typeCheck\n\t\t} else if ('object' !== typeCheck) {throw zErrType +typeCheck}\n\t} catch(e) {\n\t\tlog_error(6, METRIC, e); value = zErr\n\t}\n\taddBoth(6, METRIC, value)\n\treturn\n}\n\nfunction get_storage(METRIC, rndStr) {\n\t// dom.storage.enabled\n\tlet value, type = ('localStorage' == METRIC ? 'local' : 'session')\n\tlet obj\n\ttry {\n\t\tobj = window[type +'Storage']\n\t\tvalue = 'object' == typeFn(obj, true) ? zE : zD\n\t} catch(e) {\n\t\tlog_error(6, METRIC, e); value = zErr\n\t}\n\n\ttry {\n\t\tif (runSE) {foo++}\n\t\tobj.setItem(rndStr +'_'+ type, rndStr)\n\t\tvalue += ' | '+ (obj.getItem(rndStr +'_'+ type) == rndStr ? zS : zF)\n\t} catch(e) {\n\t\tlog_error(6, METRIC +'_test', e); value += ' | '+ zErr\n\t}\n\taddBoth(6, METRIC, value)\n\treturn\n}\n\nconst get_storage_quota = (METRIC) => new Promise(resolve => {\n\tlet isLies = false, notation = rfp_red\n\tlet isAuto = false\n\tPromise.all([\n\t\tlookup_permission('persistent-storage')\n\t]).then(function(res){\n\t\tif ('granted' == res[0] || 'denied' == res[0]) {isAuto = true} // no prompt\n\t\ttry {\n\t\t\tlet test = 'storage_quota' == METRIC ? navigator.storage : navigator.webkitTemporaryStorage\n\t\t\tif (undefined == test) {\n\t\t\t\texit('undefined')\n\t\t\t} else {\n\t\t\t\tnavigator.storage.estimate().then(estimate => {\n\t\t\t\t\tlet bytes = estimate.quota\n\t\t\t\t\tif (runST) {bytes = undefined} else if (runSL) {addProxyLie('StorageManager.estimate')}\n\t\t\t\t\tlet typeCheck = typeFn(bytes)\n\t\t\t\t\tif ('number' !== typeCheck && !Number.isInteger(bytes)) {throw zErrType + typeCheck}\n\t\t\t\t\tlet value = lookup_storage_bucket('quota', bytes, isAuto)\n\t\t\t\t\tlet display = value +' ['+ bytes +' bytes]'\n\t\t\t\t\tif (isProxyLie('StorageManager.estimate')) {isLies = true}\n\t\t\t\t\t// 1781277 RFP can only be exactly 10GB or 50GB\n\t\t\t\t\tif (10737418240 == bytes || 53687091200 == bytes) {notation = rfp_green}\n\t\t\t\t\tsDetail[isScope].lookup[METRIC] = display\n\t\t\t\t\texit(display, value)\n\t\t\t\t}).catch(function(e){\n\t\t\t\t\texit(log_error(6, METRIC, e), zErr)\n\t\t\t\t})\n\t\t\t}\n\t\t} catch(e) {\n\t\t\texit(log_error(6, METRIC, e), zErr)\n\t\t}\n\t})\n\tfunction exit(display, value) {\n\t\taddBoth(6, METRIC, display,'', notation, value, isLies)\n\t\t// silent run manager to force granted quota when run\n\t\tif (isAuto) {\n\t\t\tPromise.all([outputUserStorageManager()]).then(function(){return resolve()})\n\t\t} else {\n\t\t\treturn resolve()\n\t\t}\n\t}\n})\n\nfunction get_workers(METRIC) {\n\t// these are kinda redundant because we have them in window properties metric, and in future\n\t// we will type check and use their scopes in the overall fingerptint: until then ...\n\tlet aList = ['ServiceWorker','SharedWorker','Worker']\n\tlet data = {}, aStr = []\n\taList.forEach(function(k){\n\t\tlet value = zE\n\t\ttry {\n\t\t\tlet test = window[k]\n\t\t\tif (runST) {test = false}\n\t\t\tlet typeCheck = typeFn(test)\n\t\t\tif ('undefined' == typeCheck) {value = typeCheck\n\t\t\t} else if ('function' !== typeCheck) {throw zErrType + typeCheck}\n\t\t} catch(e) {\n\t\t\tlog_error(6, METRIC +'_'+ k, e); value = zErr\n\t\t}\n\t\tdata[k] = value\n\t\taStr.push(value)\n\t})\n\taddDisplay(6, METRIC, aStr.join(' | '))\n\taddData(6, METRIC, data, mini(data))\n\treturn\n}\n\nconst test_idb = (log = false) => new Promise(resolve => {\n\tlet t0 = nowFn(), rndStr = rnd_string()\n\tconst METRIC = 'indexedDB_test'\n\tfunction exit(value) {\n\t\tdom[METRIC] = value\n\t\tif (log) {log_perf(SECTNF, METRIC, t0,'', value)}\n\t\treturn resolve()\n\t}\n\ttry {\n\t\tlet openIDB = indexedDB.open(rndStr +'_idb')\n\t\t// create\n\t\topenIDB.onupgradeneeded = function(event){\n\t\t\tlet dbObject = event.target.result\n\t\t\tlet dbStore = dbObject.createObjectStore(rndStr, {keyPath:'id'})\n\t\t}\n\t\topenIDB.onsuccess = function(event) {\n\t\t\tlet dbObject = event.target.result\n\t\t\t// start\n\t\t\tlet dbTx = dbObject.transaction(rndStr, 'readwrite')\n\t\t\tlet dbStore = dbTx.objectStore(rndStr)\n\t\t\t// add\n\t\t\tdbStore.put({id: rndStr, value: rndStr})\n\t\t\t// query\n\t\t\tlet getStr = dbStore.get(rndStr)\n\t\t\tgetStr.onsuccess = function() {\n\t\t\t\texit(getStr.result.value == rndStr ? zS : zF)\n\t\t\t}\n\t\t\t// close\n\t\t\tdbTx.oncomplete = function() {dbObject.close()}\n\t\t}\n\t\topenIDB.onerror = function(event) {exit(zF)}\n\t} catch {\n\t\texit(zErr)\n\t}\n})\n\nconst test_worker = (log = false) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\tlet METRIC = 'worker_test'\n\tfunction exit(value) {\n\t\tdom[METRIC].innerHTML = value\n\t\tif (log) {log_perf(SECTNF, METRIC, t0,'', value)}\n\t\treturn resolve()\n\t}\n\tif ('undefined' == typeof Worker) {\n\t\texit('undefined')\n\t} else {\n\t\ttry {\n\t\t\tconst workerScript = `self.postMessage('eek')`\n\t\t\tconst workerBlob = new Blob([workerScript], {type: 'application/javascript'})\n\t\t\tconst workerURL = URL.createObjectURL(workerBlob)\n\t\t\tconst worker = new Worker(workerURL)\n\t\t\tworker.onmessage = function(e) {worker.terminate; exit(zS)} // receive\n\t\t\tworker.onerror = function(e) {exit(zErr)} // error\n\t\t\tworker.onterminate = function() {URL.revokeObjectURL(workerURL)} // cleanup\n\t\t} catch {\n\t\t\texit(zErr)\n\t\t}\n\t}\n})\n\nconst test_worker_service = (log = false) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\tconst METRIC = 'worker_service_test'\n\tfunction exit(value) {\n\t\tdom[METRIC] = value\n\t\tif (log) {log_perf(SECTNF, METRIC, t0,'', value)}\n\t}\n\tif ('undefined' == typeof ServiceWorker) {\n\t\t\texit('undefined')\n\t} else {\n\t\ttry {\n\t\t\tnavigator.serviceWorker.register('js/storage_service_worker.js').then((registration) => {\n\t\t\t\texit(zS)\n\t\t\t\tregistration.unregister().then(function(boolean) {})\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\texit(zErr)\n\t\t\t})\n\t\t} catch {exit(zErr)}\n\t}\n})\n\nconst test_worker_shared = (log = false) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\tconst METRIC = 'worker_shared_test'\n\tfunction exit(value) {\n\t\tdom[METRIC] = value\n\t\tif (log) {log_perf(SECTNF, METRIC, t0,'', value)}\n\t\treturn resolve()\n\t}\n\tif ('undefined' == typeof SharedWorker) {\n\t\texit('undefined')\n\t} else {\n\t\ttry {\n\t\t\tlet shared = new SharedWorker('js/storage_shared_worker.js')\n\t\t\tlet rndStr2 = rnd_string()\n\t\t\tshared.port.addEventListener('message', function(e) {\n\t\t\t\tlet value = ('TZP-'+ rndStr2 === e.data) ? zS : zF\n\t\t\t\tshared.port.close()\n\t\t\t\texit(value)\n\t\t\t}, false)\n\t\t\tshared.onerror = function (err) {exit(zErr)}\n\t\t\tshared.port.start()\n\t\t\tshared.port.postMessage(rndStr2)\n\t\t} catch {\n\t\t\texit(zErr)\n\t\t}\n\t}\n})\n\nconst test_worker_shared_new = (log = false) => new Promise(resolve => {\n\tlet t0 = nowFn()\n\tlet METRIC = 'worker_shared_test'\n\tfunction exit(value) {\n\t\tdom[METRIC].innerHTML = value +' TEST'\n\t\tif (log) {log_perf(SECTNF, METRIC, t0,'', value)}\n\t\treturn resolve()\n\t}\n\tif ('undefined' == typeof SharedWorker) {\n\t\texit('undefined')\n\t} else {\n\t\ttry {\n\t\t\tconst workerScript = 'var ports = []; onconnect = function(e) {let port = e.ports[0]; ports.push(port); '\n\t\t\t\t+ 'port.start(); port.onmessage = function(e) {port.postMessage(\"eek\")}'\n\t\t\tconst workerBlob = new Blob([workerScript], {type: 'application/javascript'})\n\t\t\tconst workerURL = URL.createObjectURL(workerBlob)\n\t\t\tconst shared = new SharedWorker(workerURL)\n\t\t\tshared.port.postMessage('eek') // ping\n\t\t\tshared.onmessage = function(e) {port.close(); shared.terminate; exit(zS)} // receive\n\t\t\tshared.onerror = function(e) {\n\t\t\t\tconsole.log('onerror', e, e.message)\n\t\t\t\texit(zErr)\n\t\t\t} // error\n\t\t\tshared.onterminate = function() {URL.revokeObjectURL(workerURL)} // cleanup\n\t\t} catch(e) {\n\t\t\tconsole.log('trycatch', e)\n\t\t\texit(zErr)\n\t\t}\n\t}\n})\n\nconst outputStorage = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('storage')) {return resolve()}\n\n\tlet rndStr = rnd_string()\n\tPromise.all([\n\t\tget_idb('indexedDB'),\n\t\tget_workers('workers'),\n\t\tget_cookies('cookies', rndStr),\n\t\tget_storage('localStorage', rndStr),\n\t\tget_storage('sessionStorage', rndStr),\n\t\tget_cookiestore('cookieStore', rndStr),\n\t\tget_caches('caches'),\n\t\tget_filesystem('filesystem'),\n\t\tget_storage_quota('storage_quota'),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(6)\n"
  },
  {
    "path": "js/storage_service_worker.js",
    "content": "'use strict';\n"
  },
  {
    "path": "js/storage_shared_worker.js",
    "content": "'use strict';\n\n// shared\nvar ports = []\nonconnect = function(e) {\n\tlet port = e.ports[0]\n\tports.push(port)\n\tport.start()\n\tport.onmessage = function(e) {port.postMessage(\"TZP-\"+e.data)}\n}\n"
  },
  {
    "path": "js/user.js",
    "content": "'use strict';\n\n/* USER */\n\nfunction exitUserFS() {\n\ttry {document.exitFullscreen()} catch {}\n}\n\nconst outputUserAgentOpen = (METRIC) => new Promise(resolve => {\n\tlet list = ['appCodeName','appName','appVersion','buildID','oscpu',\n\t\t'platform','product','productSub','userAgent','vendor','vendorSub']\n\t\n\tlet data = {'useragent': {}, 'useragentdata': {}}, r\n\tlet newWin = window.open()\n\tlet newNavigator = newWin.navigator\n\n\tfunction exit(value) {\n\t\tnewWin.close()\n\t\tdata['useragentdata'] = value\n\t\t// make agent_reported same structure as section\n\t\tlet newobj = {}\n\t\tfor (const k of Object.keys(data).sort()) {\n\t\t\tif ('object' == typeof data[k]) {newobj[k] = {'hash': mini(data[k]), 'metrics': data[k]}} else {newobj[k] = data[k]}\n\t\t}\n\t\tdata = newobj\n\t\t// hash\n\t\tlet hash = mini(data)\n\t\tconst ctrlHash = mini(sDetail.document.agent_reported)\n\t\t// output\n\t\tif (hash == ctrlHash) {\n\t\t\thash += match_green\n\t\t} else {\n\t\t\taddDetail(METRIC, data)\n\t\t\thash += addButton(2, METRIC) + match_red\n\t\t}\n\t\taddDisplay(2, METRIC, hash)\n\t\treturn resolve()\n\t}\n\n\t// useragent\n\tlist.forEach(function(p) {\n\t\ttry {\n\t\t\tr = newNavigator[p]\n\t\t\tlet typeCheck = typeFn(r, true), expectedType = 'string'\n\t\t\tif (!isGecko) {\n\t\t\t\t// type check will throw an error for a string \"undefined\"\n\t\t\t\tif ('buildID' == p || 'oscpu' == p) {expectedType = 'undefined'}\n\t\t\t}\n\t\t\tif (expectedType !== typeCheck) {throw zErr}\n\t\t\tif ('' == r) {r = 'empty string'}\n\t\t} catch(e) {\n\t\t\tr = e\n\t\t}\n\t\tdata['useragent'][p] = r+''\n\t})\n\t// useragentdata\n\ttry {\n\t\tlet k = navigator.userAgentData\n\t\tlet typeCheck = typeFn(k, true)\n\t\tif ('undefined' == typeCheck) {\n\t\t\texit(typeCheck)\n\t\t} else {\n\t\t\tif ('object' !== typeCheck) {throw zErr}\n\t\t\tif ('[object NavigatorUAData]' !== k+'') {throw zErr}\n\t\t\tnavigator.userAgentData.getHighEntropyValues([\n\t\t\t\t'architecture','bitness','brands','formFactors','fullVersionList','mobile',\n\t\t\t\t'model','platform','platformVersion','uaFullVersion','wow64'\n\t\t\t]).then(res => {\n \t\t\t\texit(res)\n\t\t\t}).catch(function(err){\n\t\t\t\texit(zErr)\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\texit(zErr)\n\t}\n})\n\nconst outputUserAudio = (METRIC) => new Promise(resolve => {\n\t// oscillator\n\tconst get_oscillator = (metric) => new Promise(resolve => {\n\t\tlet btn =''\n\t\tfunction exit(value, data) {\n\t\t\tif (undefined !== data) {\n\t\t\t\tsDetail[isScope][metric] = data\n\t\t\t\tbtn = addButton(11, metric)\n\t\t\t}\n\t\t\taddDisplay(11, metric, value, btn)\n\t\t\treturn resolve([metric, value])\n\t\t}\n\t\ttry {\n\t\t\tif (runSE) {foo++}\n\t\t\tlet results = [],\n\t\t\t\taudioCtx = new window.AudioContext\n\t\t\tlet oscillator = audioCtx.createOscillator(),\n\t\t\t\tanalyser = audioCtx.createAnalyser(),\n\t\t\t\tgain = audioCtx.createGain(),\n\t\t\t\tscriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1)\n\n\t\t\tgain.gain.value = 0\n\t\t\toscillator.type = 'triangle'\n\t\t\toscillator.connect(analyser)\n\t\t\tanalyser.connect(scriptProcessor)\n\t\t\tscriptProcessor.connect(gain)\n\t\t\tgain.connect(audioCtx.destination)\n\n\t\t\tscriptProcessor.onaudioprocess = function(bins) {\n\t\t\t\ttry {\n\t\t\t\t\tbins = new Float32Array(analyser.frequencyBinCount)\n\t\t\t\t\tanalyser.getFloatFrequencyData(bins) // JSShelter errors here\n\t\t\t\t\tif ('object' !== typeFn(bins)) {throw zErrType +'Float32Array: '+ typeFn(bins)}\n\t\t\t\t\tfor (let i=0; i < bins.length; i++) {results.push(bins[i])}\n\t\t\t\t\tanalyser.disconnect()\n\t\t\t\t\tscriptProcessor.disconnect()\n\t\t\t\t\tgain.disconnect()\n\t\t\t\t\t// output\n\t\t\t\t\tif (runSL) {results = []}\n\t\t\t\t\tlet typeCheck = typeFn(results[0])\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\tlet hash = mini(results)\n\t\t\t\t\texit(hash, results)\n\t\t\t\t} catch(e) {\n\t\t\t\t\texit(log_error(11, metric, e), e+'')\n\t\t\t\t}\n\t\t\t}\n\t\t\toscillator.start(0)\n\t\t} catch(e) {\n\t\t\texit(log_error(11, metric, e), e+'')\n\t\t}\n\t})\n\t// hybrid\n\tconst get_oscillator_compressor = (metric) => new Promise(resolve => {\n\t\tlet btn =''\n\t\tfunction exit(value, data) {\n\t\t\tif (undefined !== data) {\n\t\t\t\tsDetail[isScope][metric] = data\n\t\t\t\tbtn = addButton(11, metric)\n\t\t\t}\n\t\t\taddDisplay(11, metric, value, btn)\n\t\t\treturn resolve([metric, value])\n\t\t}\n\t\ttry {\n\t\t\tlet results = []\n\t\t\tlet audioCtx = new window.AudioContext,\n\t\t\t\toscillator = audioCtx.createOscillator(),\n\t\t\t\tanalyser = audioCtx.createAnalyser(),\n\t\t\t\tgain = audioCtx.createGain(),\n\t\t\t\tscriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1)\n\n\t\t\t// compressor\n\t\t\tlet compressor = audioCtx.createDynamicsCompressor()\n\t\t\tcompressor.threshold && (compressor.threshold.value = -50)\n\t\t\tcompressor.knee && (compressor.knee.value = 40)\n\t\t\tcompressor.ratio && (compressor.ratio.value = 12)\n\t\t\tcompressor.reduction && (compressor.reduction.value = -20)\n\t\t\tcompressor.attack && (compressor.attack.value = 0)\n\t\t\tcompressor.release && (compressor.release.value = .25)\n\n\t\t\tgain.gain.value = 0 // 0 volume\n\t\t\toscillator.type = 'triangle' // wave\n\t\t\toscillator.connect(compressor)\n\t\t\tcompressor.connect(analyser)\n\t\t\tanalyser.connect(scriptProcessor)\n\t\t\tscriptProcessor.connect(gain)\n\t\t\tgain.connect(audioCtx.destination)\n\n\t\t\tscriptProcessor.onaudioprocess = function(bins) {\n\t\t\ttry {\n\t\t\t\t\tbins = new Float32Array(analyser.frequencyBinCount)\n\t\t\t\t\tanalyser.getFloatFrequencyData(bins) // JSShelter errors here\n\t\t\t\t\tif ('object' !== typeFn(bins)) {throw zErrType +'Float32Array: '+ typeFn(bins)}\n\t\t\t\t\tfor (let i=0; i < bins.length; i++) {results.push(bins[i])}\n\t\t\t\t\tanalyser.disconnect()\n\t\t\t\t\tscriptProcessor.disconnect()\n\t\t\t\t\tgain.disconnect()\n\t\t\t\t\t// check\n\t\t\t\t\tif (runSE) {foo++} else if (runSL) {results = []}\n\t\t\t\t\tlet typeCheck = typeFn(results[0])\n\t\t\t\t\tif ('number' !== typeCheck) {throw zErrType + typeCheck}\n\t\t\t\t\tlet hash = mini(results)\n\t\t\t\t\texit(hash, results)\n\t\t\t\t} catch(e) {\n\t\t\t\t\texit(log_error(11, metric, e), e+'') // user test: reflect error entropy\n\t\t\t\t}\n\t\t\t}\n\t\t\toscillator.start(0)\n\t\t} catch(e) {\n\t\t\texit(log_error(11, metric, e), e+'') // user test: reflect error entropy\n\t\t}\n\t})\n\t// run\n\tlet section = {}\n\tfunction run() {\n\t\tlet notation = rfp_red\n\t\ttry {\n\t\t\tlet tStart = nowFn()\n\t\t\tlet test = new window.AudioContext\n\t\t\tPromise.all([\n\t\t\t\tget_oscillator(METRIC +'_oscillator'),\n\t\t\t\tget_oscillator_compressor(METRIC +'_oscillator_compressor'),\n\t\t\t]).then(function(results){\n\t\t\t\tsection[results[0][0]] = results[0][1] // oscillator\n\t\t\t\tsection[results[1][0]] = results[1][1] // oscillator_compressor\n\t\t\t\tlet obj = {}\n\t\t\t\tfor (const k of Object.keys(section).sort()) {obj[k.replace('audio_test_', '')] = section[k]}\n\t\t\t\tlet hash = mini(obj)\n\t\t\t\taddDetail(METRIC, obj)\n\t\t\t\tif (true === isArch) {\n\t\t\t\t\tif ('e2bbb839' == hash) {\n\t\t\t\t\t\t// {\"oscillator\": \"5b3956a9\", \"oscillator_compressor\": \"e08487bf\"}\n\t\t\t\t\t\tnotation = sgtick+'x86_64/amd_64]'+sc\n\t\t\t\t\t} else if ('011d0e6e' == hash) {\n\t\t\t\t\t\t// {\"oscillator\": \"f263f055\", \"oscillator_compressor\": \"1f38e089\"}\n\t\t\t\t\t\tnotation = sgtick+'ARM64/aarch64]'+sc\n\t\t\t\t\t}\n\t\t\t\t} else if ('e9f98e24' == hash) {\n\t\t\t\t\t// {\"oscillator\": \"e9f98e24\", \"oscillator_compressor\": \"bafe56d6\"}\n\t\t\t\t\tnotation = sgtick+'x86/i686/ARMv7]'+sc\n\t\t\t\t}\n\t\t\t\taddDisplay(11, METRIC, hash, addButton(0, METRIC, Object.keys(section).length +' metrics'), notation)\n\t\t\t\tif (isPerf) {\n\t\t\t\t\tsDataTemp['perf'].push([2, METRIC, performance.now() - tStart, performance.now()])\n\t\t\t\t\toutput_perf(METRIC)\n\t\t\t\t}\n\t\t\t\treturn resolve()\n\t\t\t})\n\t\t} catch(e) {\n\t\t\taddDisplay(11, METRIC, log_error(11,'audio2', e), '', notation)\n\t\t\treturn resolve()\n\t\t}\n\t}\n\t// start\n\tPromise.all([\n\t\toutputPrototypeLies(),\n\t]).then(function(){\n\t\trun()\n\t})\n})\n\nconst outputUserFS = (METRIC) => new Promise(resolve => {\n\tgFS = false\n\ttry {\n\t\tif (isDesktop) {\n\t\t\t// desktop: use documentElement\n\t\t\t\t// we can scroll, click, view everything\n\t\t\t\t// let the resize event trigger running the section\n\t\t\t\t// let get_scr_measure check for document.fullscreen and fill in the display\n\t\t\t\t// use svh because otherwise the height is the full document height\n\t\t\tdocument.documentElement.requestFullscreen()\n\t\t\treturn resolve()\n\t\t} else {\n\t\t\tlet element = dom.tzpFS\n\t\t\tPromise.all([\n\t\t\t\telement.requestFullscreen()\n\t\t\t]).then(function(){\n\t\t\t\tget_scr_fs_measure()\n\t\t\t\treturn resolve()\n\t\t\t})\n\t\t}\n\t} catch(e) {\n\t\taddDisplay(1, METRIC, log_error(1, METRIC, e))\n\t\treturn resolve()\n\t}\n})\n\nconst outputUserNewWin = (METRIC) => new Promise(resolve => {\n\tlet sizesi = [], // inner history\n\t\tsizeso = [], // outer history\n\t\tn = 1, // setInterval counter\n\t\tnewWinLeak =''\n\n\t// open\n\t\t// was: tests/newwin.html\n\t\t// use about:blank (same as forcing a delay with a non-existant website)\n\tlet newWin = window.open('about:blank','width=9000,height=9000')\n\t//let newWin = window.open('tests/newwin.html','width=9000,height=9000')\n\tlet iw = newWin.innerWidth,\n\t\tih = newWin.innerHeight,\n\t\tow = newWin.outerWidth,\n\t\toh = newWin.outerHeight\n\tsizesi.push(iw +' x '+ ih)\n\tsizeso.push(ow +' x '+ oh)\n\t// default output\n\tnewWinLeak = iw +' x '+ ih +' [inner] '+ ow +' x '+ oh +' [outer]'\n\n\tfunction check_newwin() {\n\t\tlet changesi = 0,\n\t\t\tchangeso = 0\n\t\t// detect changes\n\t\tlet prev = sizesi[0]\n\t\tlet strInner = s1 +'inner: '+ sc + iw +' x '+ ih\n\t\tfor (let k=0; k < sizesi.length; k++) {\n\t\t\tif (sizesi[k] !== prev ) {\n\t\t\t\tchangesi++;\tstrInner += s1 +' &#9654 <b>['+ k +']</b> '+ sc + sizesi[k]\n\t\t\t}\n\t\t\tprev = sizesi[k]\n\t\t}\n\t\tprev = sizeso[0]\n\t\tlet strOuter = s1 +'outer: '+ sc + ow +' x '+ oh\n\t\tfor (let k=0; k < sizeso.length; k++) {\n\t\t\tif (sizeso[k] !== prev ) {\n\t\t\t\tchangeso++;\tstrOuter += s1 +' &#9654 <b>['+ k +']</b> '+ sc + sizeso[k]\n\t\t\t}\n\t\t\tprev = sizeso[k]\n\t\t}\n\t\t// one or two lines\n\t\tif (changesi > 0 || changeso > 0) {\n\t\t\tnewWinLeak = strInner +'<br>'+ strOuter\n\t\t}\n\t\t// output\n\t\taddDisplay(1, METRIC, newWinLeak)\n\t\treturn resolve()\n\t}\n\tfunction build_newwin() {\n\t\t// check n times as fast as we can/dare\n\t\tif (n == 150) {\n\t\t\tclearInterval(checking)\n\t\t\tcheck_newwin()\n\t\t} else {\n\t\t\t// grab metrics\n\t\t\ttry {\n\t\t\t\tsizesi.push(newWin.innerWidth +' x '+ newWin.innerHeight)\n\t\t\t\tsizeso.push(newWin.outerWidth +' x '+ newWin.outerHeight)\n\t\t\t} catch {\n\t\t\t\tclearInterval(checking)\n\t\t\t\t// if not 'permission denied', eventually we always get\n\t\t\t\t// NS_ERROR_UNEXPECTED which we can ignore. Always output\n\t\t\t\t//console.log(e)\n\t\t\t\t//console.log(n, sizesi, sizeso)\n\t\t\t\tcheck_newwin()\n\t\t\t}\n\t\t}\n\t\tn++\n\t}\n\tlet checking = setInterval(build_newwin, 3)\n})\n\nconst outputUserPointer = (METRIC, event) => new Promise(resolve => {\n\t// ToDo: also look at radiusX/Y, screenX/Y, clientX/Y\n\t// https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/28535#note_2906361\n\tif (window.PointerEvent === undefined) {\n\t\taddDisplay(7, METRIC, 'undefined')\n\t\treturn resolve()\n\t}\n\tlet oData = {'pointerdown': {}, 'pointerrawupdate': isPointerRawUpdate}\n\tlet oList = {\n\t\tisPrimary: 'boolean', // RFP true\n\t\tpressure: 'number', // RFP: 0 if not active, 0.5 if active\n\t\tmozPressure: 'number',\n\t\tpointerType: 'string', // RFP mouse\n\t\tmozInputSource: 'number', // mouse = 1, pen = 2, touch = 5\n\t\ttangentialPressure: 'number', // RFP 0\n\t\ttiltX: 'number', // RFP 0\n\t\ttiltY: 'number', // RFP 0\n\t\ttwist: 'number', // RFP 0\n\t\twidth: 'number', // RFP 1\n\t\theight: 'number', // RFP 1\n\t\taltitudeAngle: 'number',\n\t\tazimuthAngle: 'number',\n\t}\n\tif (!isGecko) {\n\t\toList.mozPressure = 'undefined'\n\t\toList.mozInputSource = 'undefined'\n\t}\n\tfor (const k of Object.keys(oList).sort()) {\n\t\tlet value = event[k], expected = oList[k], typeCheck = typeFn(value)\n\t\tif (typeCheck !== expected) {\n\t\t\tvalue = zErrType + typeCheck\n\t\t}\n\t\tif ('undefined' == typeCheck) {value += ''}\n\t\toData['pointerdown'][k] = value\n\t}\n\tlet hash = mini(oData), btn = addButton(7, METRIC)\n\tsDetail[isScope][METRIC] = oData\n\taddDisplay(7, METRIC, hash, btn)\n\treturn resolve()\n})\n\nconst outputUserStorageManager = (isUserTest = false, METRIC = 'storage_manager') => new Promise(resolve => {\n\t// note: delay = 0 and !isUSerTest = silent run if permission granted on main TZP test\n\tlet notation = rfp_red\n\tfunction exit(value) {\n\t\taddDisplay(6, METRIC, value,'', notation)\n\t\treturn resolve()\n\t}\n\ttry {\n\t\tif (undefined == navigator.storage) {\n\t\t\texit('undefined')\n\t\t} else {\n\t\t\tnavigator.storage.persist().then(function(persistent) {\n\t\t\t\tnavigator.storage.estimate().then(estimate => {\n\t\t\t\t\t// we don't care about estimate.usage\n\t\t\t\t\tlet bytes = estimate.quota // bytes\n\t\t\t\t\tlet typeCheck = typeFn(bytes)\n\t\t\t\t\tif ('number' === typeCheck && Number.isInteger(bytes)) {\n\t\t\t\t\t\tlet value = lookup_storage_bucket('manager', bytes)\n\t\t\t\t\t\tvalue += ' ['+ bytes +' bytes]'\n\t\t\t\t\t\tif (isProxyLie('StorageManager.estimate')) {\n\t\t\t\t\t\t\tvalue = log_known(6, METRIC, value)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// 1781277 RFP can only be exactly 10GiB or 50GiB\n\t\t\t\t\t\t\tif (10737418240 == bytes || 53687091200 == bytes) {notation = rfp_green}\n\t\t\t\t\t\t}\n\t\t\t\t\t\texit(value)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow zErrType + typeCheck\n\t\t\t\t\t}\n\t\t\t\t}).catch(function(e){exit(log_error(6, METRIC, e))})\n\t\t\t}).catch(function(e){exit(log_error(6, METRIC, e))})\n\t\t}\n\t} catch(e) {exit(log_error(6, METRIC, e))}\n})\n\nconst outputUserTimingAudio = (METRIC) => new Promise(resolve => {\n\t// contexttime: geckoview\n\t\t// TypeError: undefined (with and with and w/out RFP)) on first run sometimes (and sometimes subsequent runs)\n\t\t// seen in FF139 stable, 141 beta, 142 nightly\n\n\tlet aList = ['contexttime','performancetime'], oTime = {}, audioCtx, source, rAF \n\taList.forEach(function(k){\n\t\tgData.timing[k] = []\n\t\toTime[k] = []\n\t})\n\n\t// collect\n\tfunction collectTimestamps() {\n\t\tconst ts = audioCtx.getOutputTimestamp();\n\t\toTime.contexttime.push(ts.contextTime * 1000)\n\t\toTime.performancetime.push(ts.performanceTime)\n\t\trAF = requestAnimationFrame(collectTimestamps); // Reregister itself\n\t\tif (oTime.contexttime.length > 20) {stop()}\n\t}\n\n\t// record\n\ttry {\n\t\taudioCtx = new AudioContext()\n\t\tsource = new AudioBufferSourceNode(audioCtx);\n\t\tsource.start(0);\n\t\trAF = requestAnimationFrame(collectTimestamps)\n\t} catch(e) {\n\t\taddDisplay(17, METRIC, log_error(17, METRIC, e),'', rfp_red)\n\t\treturn resolve()\n\t}\n\n\t// finish\n\tfunction stop() {\n\t\tsource.stop(0)\n\t\tcancelAnimationFrame(rAF)\n\t\taList.forEach(function(k){\n\t\t\tlet data = oTime[k]\n\t\t\tdata = dedupeArray(data)\n\t\t\t// contextTime: if the first value (we deduped) is 0 then we need to drop it\n\t\t\t\t// otherwise the first diff causes an offset to our 60FPS timing as rAF catches up: e.g.\n\t\t\t\t// 0, 10, 26.6, 43.3, 76.6, 110, 143.3, 160, 176.6, 193.3, 210, 243.3\n\t\t\t\t// 0, 10, 26.6, 43.3\n\t\t\t\t// ^ should be 0, 16.6, 33.3: i.e the [0, 10, 26.6...] we drop the start point of 0\n\t\t\t\t// after that everythng is in sync\n\t\t\tif ('contexttime' == k && 0 == data[0]) {data = data.slice(1)}\n\t\t\tgData.timing[k] = data\n\t\t})\n\t\tPromise.all([\n\t\t\tget_timing(METRIC)\n\t\t]).then(function(){\n\t\t\treturn resolve()\n\t\t})\n\t}\n})\n\nfunction outputUser(x, event) {\n\t// manual tests: require user initiated, permissions, transient activity\n\n\t// do nothing\n\tif (isBlock || !gClick) {return}\n\t// if already in fullscreenElement, nothing to do\n\t\t// we already did it when entering and resize picks up changes\n\tif ('fullscreenElement' == x) {\n\t\tif (document.fullscreen || document.webkitIsFullscreen) {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsDataTemp.display.manual = {} // reset display data\n\tgClick = false // prevent other tests\n\tgRun = false // reset\n\tget_isPerf() // reset\n\tisScope = 'manual'\n\n\t// promise\n\tvar promiseTest = async function(x) {\n\t\tif ('agent_open' == x) { return(outputUserAgentOpen(x))}\n\t\tif ('audio_test' == x) { return(outputUserAudio(x))}\n\t\tif ('fullscreenElement' == x) { return(outputUserFS(x))}\n\t\tif ('newwin' == x) { return(outputUserNewWin(x))}\n\t\tif ('pointer_event' == x) { return(outputUserPointer(x, event))}\n\t\tif ('storage_manager' == x) { return(outputUserStorageManager(true))}\n\t\tif ('timing_audio' == x) { return(outputUserTimingAudio(x))}\n\t}\n\n\t// ToDo: add an x option to run all (except FS)\n\tif ('all' == x) {\n\n\t} else {\n\t\ttry {dom[x] = ''} catch {} // clear\n\t\t// clear additional\n\t\ttry {\n\t\t\tlet items = document.getElementsByClassName('u'+x)\n\t\t\tfor (let i=0; i < items.length; i++) {items[i].innerHTML = '&nbsp'}\n\t\t} catch {}\n\t\tlet noDelay = ['audio','newwin', 'timing_audio']\n\t\tlet delay = noDelay.includes(x) ? 0 : 170\n\t\tsetTimeout(function() {\n\t\t\tPromise.all([\n\t\t\t\tpromiseTest(x)\n\t\t\t]).then(function(){\n\t\t\t\tgClick = true\n\t\t\t\tlet target = sDataTemp.display.manual\n\t\t\t\tfor (const k of Object.keys(target)) {dom[k].innerHTML = target[k]}\t\t\n\t\t\t})\n\t\t}, delay)\n\t}\n}\n\ncountJS('user')\n"
  },
  {
    "path": "js/webgl.js",
    "content": "'use strict';\n\n/* modifed from https://gist.github.com/abrahamjuliot/7baf3be8c451d23f7a8693d7e28a35e2 */\n\nfunction get_webgl() {\n\t/* ToDo:\n\t\tview-source:https://privacy-test-pages.glitch.me/privacy-protections/fingerprinting/helpers/tests.js\n\t\tMOAR stuff to be recorded here\n\t*/\n\n\tconst WebGLConstants = [\n\t\t'ALIASED_LINE_WIDTH_RANGE',\n\t\t'ALIASED_POINT_SIZE_RANGE',\n\t\t'ALPHA_BITS',\n\t\t'BLUE_BITS',\n\t\t'DEPTH_BITS',\n\t\t'GREEN_BITS',\n\t\t'MAX_COMBINED_TEXTURE_IMAGE_UNITS',\n\t\t'MAX_CUBE_MAP_TEXTURE_SIZE',\n\t\t'MAX_FRAGMENT_UNIFORM_VECTORS',\n\t\t'MAX_RENDERBUFFER_SIZE',\n\t\t'MAX_TEXTURE_IMAGE_UNITS',\n\t\t'MAX_TEXTURE_SIZE',\n\t\t'MAX_VARYING_VECTORS',\n\t\t'MAX_VERTEX_ATTRIBS',\n\t\t'MAX_VERTEX_TEXTURE_IMAGE_UNITS',\n\t\t'MAX_VERTEX_UNIFORM_VECTORS',\n\t\t'MAX_VIEWPORT_DIMS',\n\t\t'RED_BITS',\n\t\t'RENDERER',\n\t\t'SHADING_LANGUAGE_VERSION',\n\t\t'STENCIL_BITS',\n\t\t'VENDOR',\n\t\t'VERSION'\n\t]\n\tconst WebGL2Constants = [\n\t\t'MAX_VARYING_COMPONENTS',\n\t\t'MAX_VERTEX_UNIFORM_COMPONENTS',\n\t\t'MAX_VERTEX_UNIFORM_BLOCKS',\n\t\t'MAX_VERTEX_OUTPUT_COMPONENTS',\n\t\t'MAX_PROGRAM_TEXEL_OFFSET',\n\t\t'MAX_3D_TEXTURE_SIZE',\n\t\t'MAX_ARRAY_TEXTURE_LAYERS',\n\t\t'MAX_COLOR_ATTACHMENTS',\n\t\t'MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS',\n\t\t'MAX_COMBINED_UNIFORM_BLOCKS',\n\t\t'MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS',\n\t\t'MAX_DRAW_BUFFERS',\n\t\t'MAX_ELEMENT_INDEX',\n\t\t'MAX_FRAGMENT_INPUT_COMPONENTS',\n\t\t'MAX_FRAGMENT_UNIFORM_COMPONENTS',\n\t\t'MAX_FRAGMENT_UNIFORM_BLOCKS',\n\t\t'MAX_SAMPLES',\n\t\t'MAX_SERVER_WAIT_TIMEOUT',\n\t\t'MAX_TEXTURE_LOD_BIAS',\n\t\t'MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS',\n\t\t'MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS',\n\t\t'MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS',\n\t\t'MAX_UNIFORM_BLOCK_SIZE',\n\t\t'MAX_UNIFORM_BUFFER_BINDINGS',\n\t\t'MIN_PROGRAM_TEXEL_OFFSET',\n\t\t'UNIFORM_BUFFER_OFFSET_ALIGNMENT'\n\t]\n\tconst Categories = {\n\t\tdata: [\n\t\t\t//uniformBuffers\n\t\t\t'MAX_UNIFORM_BUFFER_BINDINGS',\n\t\t\t'MAX_UNIFORM_BLOCK_SIZE',\n\t\t\t'UNIFORM_BUFFER_OFFSET_ALIGNMENT',\n\t\t\t'MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS',\n\t\t\t'MAX_COMBINED_UNIFORM_BLOCKS',\n\t\t\t'MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS',\n\t\t\t//fragmentShader\n\t\t\t'MAX_FRAGMENT_UNIFORM_VECTORS',\n\t\t\t'MAX_TEXTURE_IMAGE_UNITS',\n\t\t\t'MAX_FRAGMENT_INPUT_COMPONENTS',\n\t\t\t'MAX_FRAGMENT_UNIFORM_COMPONENTS',\n\t\t\t'MAX_FRAGMENT_UNIFORM_BLOCKS',\n\t\t\t'FRAGMENT_SHADER_BEST_FLOAT_PRECISION',\n\t\t\t'MIN_PROGRAM_TEXEL_OFFSET',\n\t\t\t'MAX_PROGRAM_TEXEL_OFFSET',\n\t\t\t//frameBuffer\n\t\t\t'MAX_DRAW_BUFFERS',\n\t\t\t'MAX_COLOR_ATTACHMENTS',\n\t\t\t'MAX_SAMPLES',\n\t\t\t'RGBA_BITS',\n\t\t\t'DEPTH_STENCIL_BITS',\n\t\t\t'MAX_RENDERBUFFER_SIZE',\n\t\t\t'MAX_VIEWPORT_DIMS',\n\t\t\t//rasterizer\n\t\t\t'ALIASED_LINE_WIDTH_RANGE',\n\t\t\t'ALIASED_POINT_SIZE_RANGE',\n\t\t\t//textures\n\t\t\t'MAX_TEXTURE_SIZE',\n\t\t\t'MAX_CUBE_MAP_TEXTURE_SIZE',\n\t\t\t'MAX_COMBINED_TEXTURE_IMAGE_UNITS',\n\t\t\t'MAX_TEXTURE_MAX_ANISOTROPY_EXT',\n\t\t\t'MAX_3D_TEXTURE_SIZE',\n\t\t\t'MAX_ARRAY_TEXTURE_LAYERS',\n\t\t\t'MAX_TEXTURE_LOD_BIAS',\n\t\t\t//transformFeedback\n\t\t\t'MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS',\n\t\t\t'MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS',\n\t\t\t'MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS',\n\t\t\t//vertexShader\n\t\t\t'MAX_VARYING_VECTORS',\n\t\t\t'MAX_VERTEX_ATTRIBS',\n\t\t\t'MAX_VERTEX_TEXTURE_IMAGE_UNITS',\n\t\t\t'MAX_VERTEX_UNIFORM_VECTORS',\n\t\t\t'MAX_VERTEX_UNIFORM_COMPONENTS',\n\t\t\t'MAX_VERTEX_UNIFORM_BLOCKS',\n\t\t\t'MAX_VERTEX_OUTPUT_COMPONENTS',\n\t\t\t'MAX_VARYING_COMPONENTS',\n\t\t\t'VERTEX_SHADER_BEST_FLOAT_PRECISION',\n\t\t\t// was info\n\t\t\t'ANTIALIAS',\n\t\t],\n\t\tinfo: [\n\t\t\t//'CONTEXT',\n\t\t\t//'DIRECT_3D',\n\t\t\t'MAJOR_PERFORMANCE_CAVEAT',\n\t\t\t'RENDERER',\n\t\t\t'SHADING_LANGUAGE_VERSION',\n\t\t\t'VENDOR',\n\t\t\t'VERSION',\n\t\t\t'UNMASKED_VENDOR_WEBGL',\n\t\t\t'UNMASKED_RENDERER_WEBGL',\n\t\t],\n\t}\n\n\t/* parameter helpers */\n\t// https://developer.mozilla.org/en-US/docs/Web/API/EXT_texture_filter_anisotropic\n\tconst getMaxAnisotropy = (context) => {\n\t\ttry {\n\t\t\tconst extension = (\n\t\t\t\tcontext.getExtension('EXT_texture_filter_anisotropic') ||\n\t\t\t\tcontext.getExtension('WEBKIT_EXT_texture_filter_anisotropic') ||\n\t\t\t\tcontext.getExtension('MOZ_EXT_texture_filter_anisotropic')\n\t\t\t)\n\t\t\treturn context.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT)\n\t\t} catch (error) {\n\t\t\tconsole.error(error)\n\t\t\treturn undefined\n\t\t}\n\t}\n\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_draw_buffers\n\tconst getMaxDrawBuffers = (context) => {\n\t\ttry {\n\t\t\tconst extension = (\n\t\t\t\tcontext.getExtension('WEBGL_draw_buffers') ||\n\t\t\t\tcontext.getExtension('WEBKIT_WEBGL_draw_buffers') ||\n\t\t\t\tcontext.getExtension('MOZ_WEBGL_draw_buffers')\n\t\t\t)\n\t\t\treturn context.getParameter(extension.MAX_DRAW_BUFFERS_WEBGL)\n\t\t} catch (error) {\n\t\t\treturn undefined\n\t\t}\n\t}\n\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/precision\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/rangeMax\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/rangeMin\n\tconst getShaderData = (shader) => {\n\t\tconst shaderData = {}\n\t\ttry {\n\t\t\tfor (const prop in shader) {\n\t\t\t\tconst shaderPrecisionFormat = shader[prop]\n\t\t\t\tshaderData[prop] = {\n\t\t\t\t\tprecision: shaderPrecisionFormat.precision,\n\t\t\t\t\trangeMax: shaderPrecisionFormat.rangeMax,\n\t\t\t\t\trangeMin: shaderPrecisionFormat.rangeMin\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn shaderData\n\t\t} catch (error) {\n\t\t\treturn undefined\n\t\t}\n\t}\n\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat\n\tconst getShaderPrecisionFormat = (context, shaderType) => {\n\t\tconst props = ['LOW_FLOAT', 'MEDIUM_FLOAT', 'HIGH_FLOAT']\n\t\tconst precisionFormat = {}\n\t\ttry {\n\t\t\tprops.forEach(prop => {\n\t\t\t\tprecisionFormat[prop] = context.getShaderPrecisionFormat(context[shaderType], context[prop])\n\t\t\t\treturn\n\t\t\t})\n\t\t\treturn precisionFormat\n\t\t} catch (error) {\n\t\t\treturn undefined\n\t\t}\n\t}\n\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_debug_renderer_info\n\tconst getUnmasked = (contextType, context, constant) => {\n\t\ttry {\n\t\t\tconst extension = context.getExtension('WEBGL_debug_renderer_info')\n\t\t\tconst unmasked = context.getParameter(extension[constant])\n\t\t\treturn unmasked\n\t\t} catch (e) {\n\t\t\tlog_error(10, contextType +'_'+ constant, e)\n\t\t\treturn zErr\n\t\t}\n\t}\n\n\n\t/* get WebGLRenderingContext or WebGL2RenderingContext */\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext\n\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext\n\tfunction getWebGL(contextType) {\n\t\tconst errors = []\n\t\tlet data = {}\n\t\tconst isWebGL = /^(experimental-)?webgl$/ \n\t\tconst isWebGL2 = /^(experimental-)?webgl2$/\n\t\tconst supportsWebGL = isWebGL.test(contextType) && 'WebGLRenderingContext' in window\n\t\tconst supportsWebGL2 = isWebGL2.test(contextType) && 'WebGLRenderingContext' in window\n\n\t\t// detect support\n\t\tif (!supportsWebGL && !supportsWebGL2) {\n\t\t\terrors.push('not supported')\n\t\t\treturn [data, errors]\n\t\t}\n\n\t\t// get canvas context\n\t\tlet canvas\n\t\tlet context\n\t\tlet hasMajorPerformanceCaveat\n\t\ttry {\n\t\t\tcanvas = document.createElement('canvas')\n\t\t\tcontext = canvas.getContext(contextType, { failIfMajorPerformanceCaveat: true })\n\t\t\tif (!context) {\n\t\t\t\thasMajorPerformanceCaveat = true\n\t\t\t\tcontext = canvas.getContext(contextType)\n\t\t\t\tif (!context) {\n\t\t\t\t\tthrow new Error(`context of type ${typeof context}`)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\terrors.push(['context', e+'']) // 'context blocked'\n\t\t\treturn [data, errors]\n\t\t}\n\n\t\t// get supported extensions\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getSupportedExtensions\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions\n\t\tlet webGLExtensions\n\t\ttry {\n\t\t\twebGLExtensions = context.getSupportedExtensions()\n\t\t} catch (e) {\n\t\t\terrors.push(['extensions', e+'']) // 'extensions blocked'\n\t\t}\n\n\t\t// get parameters\n\t\tlet parameters\n\t\ttry {\n\t\t\tconst VERTEX_SHADER = getShaderData(getShaderPrecisionFormat(context, 'VERTEX_SHADER'))\n\t\t\tconst FRAGMENT_SHADER = getShaderData(getShaderPrecisionFormat(context, 'FRAGMENT_SHADER'))\n\n\t\t\tparameters = {\n\t\t\t\tANTIALIAS: context.getContextAttributes().antialias,\n\t\t\t\t//CONTEXT: contextType,\n\t\t\t\tMAJOR_PERFORMANCE_CAVEAT: hasMajorPerformanceCaveat,\n\t\t\t\tMAX_TEXTURE_MAX_ANISOTROPY_EXT: getMaxAnisotropy(context),\n\t\t\t\tMAX_DRAW_BUFFERS_WEBGL: getMaxDrawBuffers(context),\n\t\t\t\tVERTEX_SHADER,\n\t\t\t\tVERTEX_SHADER_BEST_FLOAT_PRECISION: Object.values(VERTEX_SHADER.HIGH_FLOAT),\n\t\t\t\tFRAGMENT_SHADER,\n\t\t\t\tFRAGMENT_SHADER_BEST_FLOAT_PRECISION: Object.values(FRAGMENT_SHADER.HIGH_FLOAT),\n\t\t\t\tUNMASKED_VENDOR_WEBGL: getUnmasked(contextType, context, 'UNMASKED_VENDOR_WEBGL'),\n\t\t\t\tUNMASKED_RENDERER_WEBGL: getUnmasked(contextType, context, 'UNMASKED_RENDERER_WEBGL')\n\t\t\t}\n        \n\t\t\tconst glConstants =  [...WebGLConstants, ...(supportsWebGL2 ? WebGL2Constants : [])]\n\t\t\tglConstants.forEach(key => {\n\t\t\t\tconst result = context.getParameter(context[key])\n\t\t\t\tconst typedArray = result && (\n\t\t\t\t\tresult.constructor === Float32Array ||\n\t\t\t\t\tresult.constructor === Int32Array\n\t\t\t\t)\n\t\t\t\tparameters[key] = typedArray ? [...result] : result\n\t\t\t})\n\n\t\t\tparameters.RGBA_BITS = [\n\t\t\t\tparameters.RED_BITS,\n\t\t\t\tparameters.GREEN_BITS,\n\t\t\t\tparameters.BLUE_BITS,\n\t\t\t\tparameters.ALPHA_BITS,\n\t\t\t]\n\t\t\tparameters.DEPTH_STENCIL_BITS = [\n\t\t\t\tparameters.DEPTH_BITS,\n\t\t\t\tparameters.STENCIL_BITS,\n\t\t\t]\n\t\t\t// redundant\n\t\t\t//parameters.DIRECT_3D = /Direct3D|D3D(\\d+)/.test(parameters.UNMASKED_RENDERER_WEBGL)\n\n\t\t} catch (e) {\n\t\t\tlog_error(10, contextType, e)\n\t\t\terrors.push(['parameters', e+'']) // 'parameters blocked'\n\t\t}\n\n\t\t// Structure parameter data\n\t\tlet components = {}\n\t\tif (parameters) {\n\t\t\tObject.keys(Categories).forEach((name) => {\n\t\t\t\tconst componentData = Categories[name].reduce((acc, key) => {\n\t\t\t\t\tif (parameters[key] !== undefined) {\n\t\t\t\t\t\tacc[key] = parameters[key]\n\t\t\t\t\t}\n\t\t\t\t\treturn acc\n\t\t\t\t}, {})\n\t\t\t\t// compile if data exists\n\t\t\t\tif (Object.keys(componentData).length) {\n\t\t\t\t\tcomponents[name] = componentData\n\t\t\t\t}\n\t\t\t}) \n\t\t}\n\n\t\tdata = {\n\t\t\t...components, webGLExtensions\n\t\t}\n\t\treturn [data, errors]\n\t}\n\n\tPromise.all([\n\t\tgetWebGL('webgl'),\n\t\tgetWebGL('webgl2'),\n\t\tgetWebGL('experimental-webgl'),\n\t]).then((response) => {\n\t\t//console.log(response)\n\t\tconst [webGL, webGL2, experimentalWebGL] = response\n\t\tconst [webGLData, webGLErrors] = webGL\n\t\tconst [webGL2Data, webGL2Errors] = webGL2\n\t\tconst [experimentalWebGLData, experimentalWebGLErrors] = experimentalWebGL\n\n\t\t// NS click to play: not entropy: only guaranteed on FIRST session page load assuming the\n\t\t\t// exception hasn't been permanently saved. We already have entropy on safer vs standard\n\t\t\t// and NS, so a Safer with allowed webgl implies clickedToPlay\n\t\t//let isClickToPlay = !!document.querySelector('.__ns__pop2top [data-policy-type=\"webgl\"]')\n\n\t\t//*\n\t\tif (!isFile) {\n\t\t\tconsole.debug('WebGL: ', mini(webGLData), webGLData)\n\t\t\tif (webGLErrors.length) {console.log('webGL Errors',webGLErrors)}\n\t\t\tconsole.debug('WebGL2: ', mini(webGL2Data), webGL2Data)\n\t\t\tif (webGL2Errors.length) {console.log('webGL2 Errors',webGL2Errors)}\n\t\t\tconsole.debug('Experimental: ', mini(experimentalWebGLData), experimentalWebGLData)\n\t\t\tif (experimentalWebGLErrors.length) {console.log('Experimental Errors',experimentalWebGLErrors)}\n\t\t}\n\t\t//*/\n\n\t\t// do something with the erorrs...\n\t\treturn\n\t}).catch(error => {\n\t\tconsole.error(error)\n\t\treturn\n\t})\n\n}\n\nconst outputWebGL = () => new Promise(resolve => {\n\tif (gRun && sectionIgnore.includes('webgl')) {return resolve()}\n\n\t// ToDo: readPixels, webGPU\n\tPromise.all([\n\t\tget_webgl(),\n\t]).then(function(){\n\t\treturn resolve()\n\t})\n})\n\ncountJS(10)\n"
  },
  {
    "path": "js/worker_agent.js",
    "content": "'use strict';\n\naddEventListener('message', function(e) {\n\tlet list = ['appCodeName','appName','appVersion','platform','product','userAgent']\n\tlet data = {}, r\n\tlist.forEach(function(p) {\n\t\ttry {\n\t\t\tr = navigator[p]\n\t\t\tif ('string' !== typeof r) {throw 'error'}\n\t\t\tif ('' == r) {r = 'empty string'}\n\t\t} catch(e) {\n\t\t\tr = e\n\t\t}\n\t\tdata[p] = r\n\t})\n\tself.postMessage(data)\n}, false)\n"
  },
  {
    "path": "js/worker_service_agent.js",
    "content": "'use strict';\n\naddEventListener('message', function(e) {\n\tlet list = ['appCodeName','appName','appVersion','platform','product','userAgent']\n\tlet data = {}, r\n\tlist.forEach(function(p) {\n\t\ttry {\n\t\t\tr = navigator[p]\n\t\t\tif ('string' !== typeof r) {throw 'error'}\n\t\t\tif ('' == r) {r = 'empty string'}\n\t\t} catch(e) {\n\t\t\tr = e\n\t\t}\n\t\tdata[p] = r\n\t})\n\tlet channel = new BroadcastChannel('sw-ua')\n\tchannel.postMessage({msg: data})\n}, false)\n"
  },
  {
    "path": "js/worker_shared_agent.js",
    "content": "'use strict';\n\nvar ports = []\nonconnect = function(e) {\n\tlet port = e.ports[0]\n\tports.push(port)\n\tport.start()\n\tport.onmessage = function(e) {\n\t\tlet list = ['appCodeName','appName','appVersion','platform','product','userAgent']\n\t\tlet data = {}, r\n\t\tlist.forEach(function(p) {\n\t\t\ttry {\n\t\t\t\tr = navigator[p]\n\t\t\t\tif ('string' !== typeof r) {throw \"error\"}\n\t\t\t\tif ('' == r) {r = 'empty string'}\n\t\t\t} catch(e) {\n\t\t\t\tr = e\n\t\t\t}\n\t\t\tdata[p] = r\n\t\t})\n\t\tport.postMessage(data)\n\t}\n}\n"
  },
  {
    "path": "tests/applang-xslterror.xml",
    "content": "<?xml version=\"1.0\"?><?xml-stylesheet type=\"text/xsl\" href=\"\"?><a>a</a>\n"
  },
  {
    "path": "tests/applang.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>app language</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<link rel=\"stylesheet\" href=\"chrome://global/locale/intl.css\">\n\t<link rel=\"preload\" href=\"applang-invalid.png\" as=\"image\">\n\t<link rel=\"preload\" href=\"applang-image.png\" as=\"image\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 580px; max-width: 780px}\n\t\t.appfixed {\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tpadding: 0px;\n\t\t\tposition: fixed;\n\t\t\tdisplay: inline;\n\t\t\twidth:fit-content;\n\t\t\theight:fit-content;\n\t\t}\n\t\t.appbigger {\n\t\t\tfont-size: 250px;\n\t\t}\n\t</style>\n</head>\n\n<body>\n\t<div class=\"appfixed normalized\"><div class=\"appbigger skew\" id=\"elementsdiv\"></div></div>\n\t<!-- offscreen -->\n\t<div class=\"offscreen\">\n\t\t<div>\n\t\t\t<!-- applang-image.png can't be hidden otherwise \"Scaled...\" is not in the title -->\n\t\t\t<iframe id=\"InvalidImage\" width=\"100\" height=\"30\" src=\"applang-invalid.png\"></iframe>\n\t\t\t<iframe id=\"ScaledImage\" width=\"100\" height=\"30\" src=\"applang-image.png\"></iframe>\n\t\t\t<iframe id=\"tzpXSLT\" width=\"40\" height=\"30\" src=\"applang-xslterror.xml\"></iframe>\n\t\t\t<div id='tzpDirection'></div>\n\t\t\t<div><input id=\"enumber\" type=\"number\" min=\"6\" step=\"2\"></div>\n\t\t</div>\n\t</div>\n\n\t<div class=\"hidden\">\n\t\t<div>\n\t\t\t<input type=\"text\" required id=\"widgettext\">\n\t\t\t<input type=\"checkbox\" required id=\"widgetcheckbox\">\n\t\t\t<input type=\"date\" id=\"widgetdatetime\" value=\"2024-01-01\" max=\"2023-12-31\">\n\t\t\t<input type=\"date\" id=\"widgetdatetimeunder\" value=\"2022-01-01\" min=\"2023-12-31\">\n\t\t\t<input type=\"email\" id=\"widgetemail\" value=\"a\">\n\t\t\t<input type=\"file\" required id=\"widgetfile\">\n\t\t\t<input type=\"number\" required id=\"widgetnumber\">\n\t\t\t<input type=\"number\" id=\"widgetmax\" max=\"1974.3\" value=\"2000\">\n\t\t\t<input type=\"number\" id=\"widgetmin\" min=\"8026.5\" value=\"1\">\n\t\t\t<input type=\"number\" id=\"widgetstep\" min=\"1.2345\" step=\"1005.5545\" value=\"2\">\n\t\t\t<input type=\"radio\" required name=\"radiogroup\" id=\"widgetradio\">\n\t\t\t<select required id=\"widgetselect\"><option></option></select>\n\t\t\t<input type=\"url\" id=\"widgeturl\" value=\"a\">\n\t\t\t<input type=\"tel\" id=\"widgettel\" pattern=\"[0-9]{1}\" value=\"a\">\n\t\t\t<!-- the rest not covered by the visually displayed ones -->\n\t\t\t<input type=\"hidden\" id=\"ehidden\">\n\t\t\t<input type=\"image\" id=\"eimage\">\n\t\t\t<input type=\"month\" id=\"emonth\">\n\t\t\t<input type=\"password\" id=\"epassword\">\n\t\t\t<input type=\"range\" id=\"erange\">\n\t\t\t<input type=\"search\" id=\"esearch\">\n\t\t\t<textarea id=\"etextarea\"></textarea>\n\t\t\t<input type=\"week\" id=\"eweek\">\n\t\t</div>\n\t</div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<!-- app lang -->\n\t<table id=\"tb4\">\n\t\t<col width=\"25%\"><col width=\"75%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">application language &hellip; &ldquo; &rdquo; &lsquo; &rsquo; \n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"locale\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"btn4 btnfirst\" onClick=\"run()\">[ run ]</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"><hr><br></td></tr>\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<div>CSS &nbsp;\n\t\t\t\t<span class='blue'>chrome://global/locale/intl.css</span>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"css\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>DIRECTION\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"parsererror_direction\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>MEDIA MESSAGES <sup>1</sup> &nbsp; \n\t\t\t\t<a class='blue' href='https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/MediaDocument.properties' target='blank'>searchfox MediaDocument</a>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"media_messages\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>REPORTING MESSAGES\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"reporting_messages\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>VALIDATION MESSAGES &nbsp; \n\t\t\t\t<a class='blue' href='https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/dom/dom.properties' target='blank'>searchfox dom.properties</a>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"validation_messages\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>XML MESSAGES &nbsp; \n\t\t\t\t<a class='blue' href='https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/xmlparser.properties' target='blank'>searchfox xmlparser</a>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"xml_messages\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>XSLT MESSAGES\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"xslt_messages\"> &nbsp; </p>\n\t\t\t</div>\n\t\t\t<div>XSLT SORT &nbsp; \n\t\t\t\t<a class='blue' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1978383' target='blank'>1978383: xsl:sort uses base sensitivity</a>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"xslt_sort\"> &nbsp; </p>\n\t\t\t</div>\n\n\t\t</td></tr>\n\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<div>NUMERIC INPUT &nbsp; <span class=\"c mono no_color\" id=\"numericinputhash\"></span>\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"numeric_input\"> &nbsp; </p>\n\t\t\t</div>\n\t\t</td></tr>\n\n\t\t<!--credits-->\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"no_color\">code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://trac.torproject.org/projects/tor/ticket/30683\">z3t on HackerOne</a> <sup>1</sup>\n\t\t\t</td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\nlet isLocale\n\n// trigger some gecko deprecated items\nfunction set_reporting_items() {\n\tlet aItems = ['InstallTrigger', 'fullScreen', 'onmozfullscreenchange', 'onmozfullscreenerror']\n\taItems.forEach(function(n) {try {window[n]} catch(e) {}})\n\ttry {screen.mozOrientation} catch(e) {}\n\ttry {document.releaseCapture()} catch(e) {}\n}\n\nfunction get_css() {\n\t// ToDo: we also have chrome://global/content/widgets.css\n\t\t// e.g. chrome://global/skin/richlistbox.css\n\tconst parent = dom.elementsdiv\n\tlet oList = {\n\t\t\"quote-fr\": \"<q lang='fr'></q>\",\n\t}\n\ttry {\n\t\tlet oData = {}, target\n\t\tfor (const k of Object.keys(oList).sort()) {\n\t\t\t// important to clear the div so no other elements can affect measurements\n\t\t\tparent.innerHTML = \"\"\n\t\t\tlet data = []\n\t\t\ttry {\n\t\t\t\tparent.innerHTML = oList[k] //+\" \"+ k\n\t\t\t\ttarget = parent.children[0]\n\t\t\t\toData[k] = [target.getBoundingClientRect().width, target.getBoundingClientRect().height]\n\t\t\t} catch(e) {\n\t\t\t\tconsole.log(e)\n\t\t\t\toData[k] = zErr\n\t\t\t}\n\t\t}\n\t\tparent.innerHTML = \"\"\n\t\tlet hash = mini(oData), notation = \"\"\n\t\tdom.css.innerHTML = hash + notation +\"<br>\"+ json_highlight(oData)\n\t\treturn\n\t} catch(e) {\n\t\tparent.innerHTML = \"\"\n\t\tdom.css = e+\"\"\n\t\treturn\n\t}\n}\n\nfunction get_direction() {\n\tlet value\n\ttry {\n\t\tlet target = dom.tzpDirection\n\t\ttarget.innerHTML = '<parsererror></parsererror>'\n\t\tvalue = getComputedStyle(target.children[0]).direction\n\t} catch(e) {\n\t\tvalue = e\n\t}\n\tdom.parsererror_direction.innerHTML = value\n}\n\nfunction get_locale() {\n\ttry {\n\t\tisLocale = Intl.DateTimeFormat().resolvedOptions().locale\n\t} catch(e) {\n\t\t// leave undefined\n\t}\n\tdom.locale.innerHTML = isLocale\n}\n\nfunction get_media_messages() {\n\t// https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/MediaDocument.properties\n\ttry {\n\t\tlet mList = {\n\t\t\t\"InvalidImage\": \"applang-invalid.png\", // 0-byte file\n\t\t\t\"ScaledImage\": \"applang-image.png\",\n\t\t\t//\"Unsupported\": \"applang-unsupported.png\",\n\t\t}\n\t\tlet mData = {}\n\t\tfor (const k of Object.keys(mList)) {\n\t\t\tlet target = dom[k], title = \"\"\n\t\t\tif (k === \"ScaledImage\") {\n\t\t\t\ttitle = target.contentWindow.document.title\n\t\t\t\ttitle = title.replace(mList[k], \"\") //strip image name to reduce noise\n\t\t\t} else {\n\t\t\t\tconst image = target.contentWindow.document.querySelector('img')\n\t\t\t\ttitle = image.alt\n\t\t\t\ttitle = title.replace(target.src, \"\") // remove noise\n\t\t\t}\n\t\t\ttitle = title.trim()\n\t\t\tmData[k] = title\n\t\t}\n\t\tlet hash = mini(mData)\n\t\tdom.media_messages.innerHTML = hash +\"<br>\"+ json_highlight(mData)\n\t\treturn\n\t} catch(e) {\n\t\tdom.media_messages = e+\"\"\n\t\treturn\n\t}\n}\n\nfunction get_reporting_messages() {\n\tif (!isFF) {return}\n\tlet observer\n\tfunction exit(res) {\n\t\ttry {observer.disconnect()} catch(e) {}\n\t\tif ('string' == typeof res) {\n\t\t\tdom.reporting_messages.innerHTML = res\n\t\t\treturn\n\t\t}\n\t\tlet max = 10\n\t\tlet aSet = new Set()\n\t\tfor (let i=0; i < res.length; i++) {\n\t\t\tlet msg = res[i].body.message\n\t\t\tmsg = msg.replace('https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture','').trim()\n\t\t\taSet.add(msg)\n\t\t\tif (max == aSet.size) {break} // reruns accrue messages so break\n\t\t}\n\t\tlet data = Array.from(aSet).sort()\n\t\tif (data.length) {\n\t\t\tlet hash = mini(data)\n\t\t\tdom.reporting_messages.innerHTML = hash +\"<br>\"+ json_highlight(data)\n\t\t} else {\n\t\t\tdom.reporting_messages.innerHTML = 'none'\n\t\t}\n\t}\n\ttry {\n\t\tobserver = new ReportingObserver((reports, observer) => {exit(reports)}, {types: ['deprecation'], buffered: true})\n\t\tobserver.observe()\n\t} catch(e) {\n\t\texit(e+'')\n\t}\n}\n\nfunction get_numeric_inputs() {\n\tlet target = dom.enumber\n\tlet oTests = {\n\t\tarab: ['٤','٦'],\n\t\tarabext: ['۴','۶'],\n\t\tlatn: ['4',4,'6',6,],\n\t\tmymr: ['၄','၆'],\n\t\tx: ['x']\n\t}\n\tlet oData = {}\n\tfor (const k of Object.keys(oTests)) {\n\t\toData[k] = {}\n\t\toTests[k].forEach(function(n) {\n\t\t\ttarget.value = n\n\t\t\tlet value = target.value\n\t\t\tlet charName = \"char\", charValue, j = n\n\t\t\tif (\"string\" === typeof n) {\n\t\t\t\tcharValue = n.charCodeAt(0)\n\t\t\t} else {\n\t\t\t\tcharName = \"number\"; charValue = n; j = n +\"n\"\n\t\t\t}\n\t\t\tlet validity = target.checkValidity()\n\t\t\toData[k][j] = {}\n\t\t\toData[k][j][charName] = charValue\n\t\t\toData[k][j][\"value\"] = value\n\t\t\toData[k][j][\"validity\"] = validity\n\t\t})\n\t}\n\tlet toggle = \"num\"\n\tdom.numericinputhash.innerHTML = mini(oData)\n\t\t+\" <span id='labelhidden\"+ toggle +\"' class='btnfirst btn0' onClick=\\\"togglerows('hidden\"+ toggle +\"','expand')\\\">[ expand ]</span>\"\n\tdom.numeric_input.innerHTML = \"<span class='toghidden\" + toggle +\" hidden'>\"+ json_highlight(oData) +\"</span><br>\"\n}\n\nfunction get_validation_messages() {\n\t// https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/dom/dom.properties\n\t\t// https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation\n\t\t// https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/ValidityState\n\t\t// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input\n/*\n\tInvalidDate=Please enter a valid date.\n\tInvalidDateMonth=Please enter a valid month.\n\tInvalidDateTime=Please enter valid date and time.\n\tInvalidDateWeek=Please enter a valid week.\n\tInvalidTime=Please enter a valid time.\n\tPatternMismatchWithTitle=Please match the requested format: %S.\n\tStepMismatchOneValue=Please select a valid value. The nearest valid value is %S.\n\tTextTooLong=Please shorten this text to %S characters or less (you are currently using %S characters).\n\tTextTooShort=Please use at least %S characters (you are currently using %S characters).\n\tTimeReversedRangeUnderflowAndOverflow=Please select a value between %1$S and %2$S.\n*/\n\n\t// tooLong + tooShort constraints require the user: https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/tooShort\n\t\t// ^ but we should add manual tests for those\n\t// InvalidDate/Time/Week/Month - can't set invalid values via JS\n\n\tconst dList = {\n\t\tBadInputNumber: 'number', // this returns ValueMissing on chrome\n\t\tCheckboxMissing: 'checkbox',\n\t\tDateTimeRangeOverflow: 'datetime',\n\t\tDateTimeRangeUnderflow: 'datetimeunder',\n\t\tFileMissing: 'file',\n\t\tInvalidEmail: 'email',\n\t\tInvalidURL: \"url\",\n\t\tNumberRangeOverflow: 'max',\n\t\tNumberRangeUnderflow: 'min',\n\t\tPatternMismatch: 'tel',\n\t\tRadioMissing: 'radio',\n\t\tSelectMissing: 'select',\n\t\tStepMismatch: 'step',\n\t\tValueMissing: 'text',\n\t}\n\n\t// dom\n\tlet domvalue, domhash\n\tlet dData = {}\n\ttry {\n\t\tfor (const k of Object.keys(dList).sort()) {\n\t\t\ttry {\n\t\t\t\tlet msg = dom[\"widget\"+ dList[k]].validationMessage\n\t\t\t\tdData[k] = msg\n\t\t\t} catch(e) {\n\t\t\t\tdData[k] = zErr\n\t\t\t}\n\t\t}\n\t\tdomhash = mini(dData)\n\t\tdomvalue = domhash +\" [DOM]<br>\"+ json_highlight(dData)\n\t} catch(e) {\n\t\tdomvalue = \"DOM: \"+ e+\"\"\n\t}\n\n\t// domparser\n\tlet dommatch_green = sg+\"[✓ matches DOM]\"+sc\n\tlet dommatch_red = sb+\"[✗ matches DOM]\"+sc\n\tlet parservalue, parserhash\n\tconst pList = {\n\t\tBadInputNumber: \"<input type='number' required>\",\n\t\tCheckboxMissing: \"<input type='checkbox' required>\",\n\t\tDateTimeRangeOverflow: \"<input type='date' value='2024-01-01' max='2023-12-31'>\",\n\t\tDateTimeRangeUnderflow: \"<input type='date' value='2022-01-01' min='2023-12-31'>\",\n\t\tFileMissing: \"<input type='file' required>\",\n\t\tInvalidEmail: \"<input type='email' value='a'>\",\n\t\tInvalidURL: \"<input type='url' value='a'>\",\n\t\tNumberRangeOverflow: \"<input type='number' max='1974.3' value='2000'>\",\n\t\tNumberRangeUnderflow: \"<input type='number' min='8026.5' value='1'>\",\n\t\tPatternMismatch: \"<input type='tel' pattern='[0-9]{1}' value='a'>\",\n\t\tRadioMissing: \"<input type='radio' required name='radiogroup'>\",\n\t\tSelectMissing: \"<select required><option></option></select>\",\n\t\tStepMismatch: \"<input type='number' min='1.2345' step='1005.5545' value='2'>\",\n\t\tValueMissing: \"<input type='text' required>\",\n\t}\n\tlet pData = {}\n\ttry {\n\t\tlet parser = new DOMParser\n\t\tfor (const k of Object.keys(pList)) {\n\t\t\ttry {\n\t\t\t\tlet doc = parser.parseFromString(pList[k], 'text/html')\n\t\t\t\tlet msg = doc.activeElement.children[0].validationMessage\n\t\t\t\tpData[k] = msg // +\"b\" // test alter hash\n\t\t\t} catch(e) {\n\t\t\t\toData[k] = zErr\n\t\t\t}\n\t\t}\n\t\tparserhash = mini(pData)\n\t} catch(e) {\n\t\tparservalue = \"DOMParser: \"+ e+\"\"\n\t}\n\n\tif (parservalue == undefined) { // not an error\n\t\tif (parserhash == domhash) {\n\t\t\tparservalue = parserhash +\" [DOMParser] \"+ dommatch_green\n\t\t} else {\n\t\t\tparservalue = parserhash +\" [DOMParser] \"+ dommatch_red\n\t\t\tconsole.log(\"DOMParser\\n\", pData)\n\t\t\tparservalue += \" check the console\"\n\t\t}\n\t}\n\tdom.validation_messages.innerHTML = parservalue +\"<br><br>\"+ domvalue\n}\n\nfunction get_xml_messages() {\n\tif (!isFF) {return}\n\t// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString\n\t\t// https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction\n\t\t// https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/xmlparser.properties\n\t\t// https://www.w3.org/TR/xml/\n\tconsole.clear()\n\tconst xmlList = {\n\t\tn02: \"a\", // syntax error\n\t\tn03: \"\", // no root element found\n\t\tn04: \"<>\", // not well-formed\n\t\tn05: \"<\", // unclosed token\n\t\tn07: \"<x></X>\", // mismatched tag\n\t\tn08: \"<x x:x='' x:x=''>\", // duplicate attribute\n\t\tn09: \"<x></x><x\", // junk after document element\n\t\tn11: \"<x>&x;\", // undefined entity\n\t\tn14: \"<x>&#x0;\", // reference to invalid character number\n\t\tn20: \"<x><![CDATA[\", // unclosed CDATA section\n\t\tn27: \"<x:x>\", // prefix not bound to a namespace\n\t\tn28: \"<x xmlns:x=''></x>\", // must not undeclare prefix\n\t\tn30: \"<\"+\"?xml v=''?>\", // XML declaration not well-formed\n\t\t\t// split \"<\"+\"?\" othersize notepad++s gets uposet with collapsing\n\t}\n\n\tlet oMimeTypes = ['application/xml','application/xhtml+xml','image/svg+xml','text/xml',] // 'text/html',\n\n\ttry {\n\t\tlet xmlData = {}, delimiter = \":\"\n\t\toMimeTypes.forEach(function(mimetype) {\n\t\t\tlet parser = new DOMParser\n\t\t\tlet oTemp = {}\n\t\t\tfor (const k of Object.keys(xmlList)) {\n\t\t\t\ttry {\n\t\t\t\t\tlet doc = parser.parseFromString(xmlList[k], mimetype)\n\t\t\t\t\tlet str = (doc.getElementsByTagName('parsererror')[0].firstChild.textContent)\n\t\t\t\t\tlet parts = str.split(\"\\n\")\n\t\t\t\t\tif ('n02' == k) {\n\t\t\t\t\t\t// ensure 3 parts: e.g. hebrew only has 2 lines\n\t\t\t\t\t\tlet tmpStr = parts[1]\n\t\t\t\t\t\tlet loc = window.location+'', locLen = loc.length, locStart = tmpStr.indexOf(loc)\n\t\t\t\t\t\tif (undefined == parts[2]) {\n\t\t\t\t\t\t\tlet position = locLen+ locStart\n\t\t\t\t\t\t\tparts[1] = (tmpStr.slice(0, position)).trim()\n\t\t\t\t\t\t\tparts.push((tmpStr.slice(-(tmpStr.length - position))).trim())\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// set delimiter: should aways be the last item in parts[1] after we strip location\n\t\t\t\t\t\t\t// usually = \":\" (charCode 58) but zh-Hans-CN = \"：\" (charCode 65306) and my = \" -\"\n\t\t\t\t\t\tlet strLoc = (parts[1].slice(0, locStart)).trim() // trim\n\t\t\t\t\t\tdelimiter = strLoc.slice(-1) // last char\n\t\t\t\t\t\t// concat some bits\n\t\t\t\t\t\t\t// don't trim strName prior to +delimiter (which is length 1)\n\t\t\t\t\t\t\t// e.g. 'fr','my' have a preceeding space, so capture that\n\t\t\t\t\t\tlet strName = parts[0].split(delimiter)[0] + delimiter\n\t\t\t\t\t\t// use an object as joining for a string can get weird with RTL\n\t\t\t\t\t\tlet oData = {\n\t\t\t\t\t\t\t'delimiter': delimiter +' (' + delimiter.charCodeAt(0) +')', // redundant but record it for debugging\n\t\t\t\t\t\t\t'error': strName,\n\t\t\t\t\t\t\t'line': parts[2].trim(),\n\t\t\t\t\t\t\t'location': strLoc,\n\t\t\t\t\t\t}\n\t\t\t\t\t\toTemp['n00'] = oData\n\t\t\t\t\t}\n\t\t\t\t\t// parts[0] is always the error message\n\t\t\t\t\tlet value = parts[0], trimLen = parts[0].split(delimiter)[0].length + 1\n\t\t\t\t\toTemp[k] = value.slice(trimLen).trim()\n\t\t\t\t} catch(err) {\n\t\t\t\t\toTemp[k] = err+\"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsole.log(mimetype, oTemp)\n\t\t\tlet hashTemp = mini(oTemp)\n\t\t\tif (xmlData[hashTemp] == undefined) {\n\t\t\t\txmlData[hashTemp] = {\n\t\t\t\t\t\"mimeTypes\": [mimetype],\n\t\t\t\t\t\"metrics\": oTemp,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txmlData[hashTemp][\"mimeTypes\"].push(mimetype)\n\t\t\t}\n\t\t})\n\t\tlet hash = mini(xmlData)\n\t\tdom.xml_messages.innerHTML = hash +\"<br>\"+ json_highlight(xmlData)\n\t\tconsole.clear()\n\t\treturn\n\t} catch(e) {\n\t\tdom.xml_messages = e+\"\"\n\t\tconsole.clear()\n\t\treturn\n\t}\n}\n\nfunction get_xslt_messages() {\n\tif (!isFF) {return}\n\tlet hash, data =''\n\ttry {\n\t\tlet data = {'xslt-parse-failure': dom.tzpXSLT.contentDocument.children[0].textContent} \n\t\thash = mini(data)\n\t\tdom.xslt_messages.innerHTML = hash + '<br>'+ json_highlight(data)\n\t} catch(e) {\n\t\tdom.xslt_messages.innerHTML = e+''\n\t}\n}\n\nfunction get_xslt_sort(aChars = []) {\n\tif (!isFF) {return}\n\n\tlet data = {}, notation = ''\n\tlet collatormatch_green = sg+\"[✓ matches Intl.Collator]\"+sc\n\tlet collatormatch_red = sb+\"[✗ matches Intl.Collator]\"+sc\n\ttry {\n\t\t// get characters\n\t\tlet aData = [], oData = {}\n\t\tif (!aChars.length) {\n\t\t\taChars = [ // already sorted\n\t\t\t\t'A','a','aa','ch','ez','kz','ng','ph','ts','tt','y','\\u00E2','\\u00E4','\\u00E7\\a','\\u00EB','\\u00ED','\\u00EE','\\u00F0',\n\t\t\t\t'\\u00F1','\\u00F6','\\u0107','\\u0109','\\u0137\\a','\\u0144','\\u0149','\\u01FB','\\u025B','\\u03B1','\\u040E','\\u0439','\\u0453',\n\t\t\t\t'\\u0457','\\u04F0','\\u0503','\\u0561','\\u05EA','\\u0627','\\u0649','\\u06C6','\\u06C7','\\u06CC','\\u06FD','\\u0934','\\u0935',\n\t\t\t\t'\\u09A4','\\u09CE','\\u0A85','\\u0B05','\\u0B85','\\u0C05','\\u0C85','\\u0D85','\\u0E24','\\u0E9A','\\u10350','\\u10D0','\\u1208',\n\t\t\t\t'\\u1780','\\u1820','\\u1D95','\\u1DD9','\\u1ED9','\\u1EE3','\\u311A','\\u3147','\\u4E2D','\\uA647','\\uFB4A'\n\t\t\t]\n\t\t}\n\n\t\t// DONE: matches collator PoC\n\t\taChars.sort() // always resort: default is already sorted, but in TZP we'll reuse the oIntl.collator.sort array\n\t\t//oData['chars'] = {'hash': mini(aChars.join(' , ').trim()), 'data': aChars.join(' , ').trim()}\n\n\t\t// build xslt\n\t\taChars.forEach(function(item){aData.push('<a>'+ item +'</a>')})\n\t\tconst xData = '<'+'?xml version=\"1.0\" encoding=\"UTF-8\"?><doc>'+ aData.join('') +'</doc>'\n\t\t//console.log(mini(xData)) // 8f0f6080\n\n\t\t// DONE: matches collator PoC\n\t\tlet code = isLocale\n\t\tlet control = aChars.sort(Intl.Collator(code, {usage: 'sort'}).compare).join(' , ').trim() // spaces before and to help LTR/RTL\n\t\toData['collator'] = {'hash': mini(control), 'data': control}\n\n\n\t\t//console.log(xData)\n\t\tconst xslText = '<'+'?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n\t\t\t+ '<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">'\n\t\t\t+ '<xsl:template match=\"/\"><xsl:for-each select=\"doc/a\"><xsl:sort select=\"text()\"/>'\n\t\t\t+ '<xsl:value-of select=\"text()\"/>,</xsl:for-each></xsl:template></xsl:stylesheet>'\n\t\t//console.log(mini(xslText)) // 97940905\n\n\t\tconst parser = new DOMParser()\n\t\tconst xsltProcessor = new XSLTProcessor()\n\t\tconst xslStylesheet = parser.parseFromString(xslText, \"application/xml\")\n\t\txsltProcessor.importStylesheet(xslStylesheet)\n\t\tconst xmlDoc = parser.parseFromString(xData, \"application/xml\");\n\t\tconst styledDoc = xsltProcessor.transformToDocument(xmlDoc);\n\t\t//console.log(styledDoc)\n\t\tlet aTest = styledDoc.firstChild.textContent.split(/[\\s,\\n]+/);\n\t\tdata = aTest.slice(0, -1)\n\t\toData['xslt'] = {'hash': mini(data.join(' , ').trim()), 'data': data.join(' , ').trim()}\n\n\t\tlet isMatch = oData.collator.hash == oData.xslt.hash\n\t\tif (isFF) {\n\t\t\tnotation = isMatch ? collatormatch_green : collatormatch_red\n\t\t\tnotation = ''\n\t\t}\n\t\tdom.xslt_sort.innerHTML = oData.xslt.hash +' '+ notation +\"<br>\"+ json_highlight(oData)\n\n\t} catch(e) {\n\t\tdom.xslt_sort = e+\"\"\n\t}\n}\n\nfunction run() {\n\t// clear\n\tlet items = document.getElementsByClassName('c')\n\tfor(let i=0; i < items.length; i++) {\n\t\titems[i].innerHTML = ' &nbsp; '\n\t}\n\t// pause so users see change\n\tsetTimeout(function() {\n\t\tget_locale()\n\t\tget_xml_messages()\n\t\tget_xslt_sort()\n\t\tget_numeric_inputs()\n\t\tget_validation_messages()\n\t\tget_media_messages()\n\t\tget_reporting_messages()\n\t\tget_direction()\n\t\tget_css()\n\t\tget_xslt_messages()\n\t}, 170)\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_locale()\n\tif (isFF) {\n\t\tset_reporting_items()\n\t} else {\n\t\tlet aList = ['reporting_messages','xml_messages','xslt_messages','xslt_sort']\n\t\taList.forEach(function(n) {\n\t\t\tlet el = dom[n]\n\t\t\tel.innerHTML = 'gecko only'\n\t\t\tel.classList.remove('c')\n\t\t})\n\t}\n})\n\n</script>\n</body>\n</html>\n\n\n"
  },
  {
    "path": "tests/applang.xml",
    "content": "<!DOCTYPE html SYSTEM \"chrome://global/locale/netError.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head><meta charset=\"utf-8\"/></head>\n<body><span id=\"DTD1\">&loadError.label;</span>\n<script>\nwindow.addEventListener('message', (e) => {\n\te.source.postMessage(document.getElementById('DTD1').innerText, '*');\n});\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/bridgemoji.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>bridge-moji</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\t.darkmoji {\r\n\t\t\tcolor: white;\r\n\t\t\tbackground-color: #1c1b22\r\n\t\t}\r\n\t\t.lightmoji {\r\n\t\t\tcolor: black;\r\n\t\t\tbackground-color: #ffffff\r\n\t\t}\r\n\t\t.largemoji {\r\n\t\t\tfont-size: 16px;\r\n\t\t}\r\n\t\t.padmoji {line-height: 2}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"20%\"><col width=\"80%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">bridge-moji</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn12 btnfirst\" onClick=\"run()\">[ run ]</span>\r\n\t\t\t<span class=\"btn12 btn\" onClick=\"reset()\">[ clear ]</span>\r\n\t\t\t<span class=\"btn12 btn\" onClick=\"preset()\">[ load preset ]</span>\r\n\t\t\t<span class=\"btn12 btn\">\r\n\t\t\t\tlight <input id=\"rlight\" type=\"radio\" name=\"radio\" value=\"lightmoji\" onchange=\"handleTheme(this)\">\r\n\t\t\t\tdark <input id=\"rdark\" type=\"radio\" name=\"radio\" value=\"darkmoji\" onchange=\"handleTheme(this)\" checked>\r\n\t\t\t</span><p>\r\n      <span><span class=\"no_color\">Preferred system font: </span>\r\n\t\t\t\tdefault <input id=\"default\" value='default' type=\"radio\" name=\"font\" onchange=\"handleFont(this)\">\r\n\t\t\t\tNoto Color Emoji <input id=\"noto\" value='noto' type=\"radio\" name=\"font\" onchange=\"handleFont(this)\">\r\n\t\t\t\tTwemoji <input id=\"twemoji\" value='twemoji' type=\"radio\" name=\"font\" onchange=\"handleFont(this)\">\r\n\t\t\t</span>\r\n\t\t\t<br><br>\r\n\t\t\t<textarea rows=\"4\" class=\"darkmoji\"\r\n\t\t\t\tplaceholder=\"comma delimited values: e.g: 🔥, ☎️, 🌽\"\r\n\t\t\t\tstyle=\"width: 98%; resize: vertical;\" id=\"valueE\"></textarea>\r\n\t\t\t<br><br>\r\n\t\t\t<div class=\"spaces largemoji padmoji\" id=\"results\"></div>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet emojiPreset = [\r\n\t\"👽️\", \"🤖\", \"🧠\", \"👁️\", \"🧙\", \"🧚\", \"🧜\", \"🐵\", \"🦧\", \"🐶\", \"🐺\", \"🦊\", \"🦝\", \"🐱\", \"🦁\", \"🐯\", \r\n\t\"🐴\", \"🦄\", \"🦓\", \"🦌\", \"🐮\", \"🐷\", \"🐗\", \"🐪\", \"🦙\", \"🦒\", \"🐘\", \"🦣\", \"🦏\", \"🐭\", \"🐰\", \"🐿️\", \r\n\t\"🦔\", \"🦇\", \"🐻\", \"🐨\", \"🦥\", \"🦦\", \"🦘\", \"🐥\", \"🐦️\", \"🕊️\", \"🦆\", \"🦉\", \"🦤\", \"🪶\", \"🦩\", \"🦚\", \r\n\t\"🦜\", \"🐊\", \"🐢\", \"🦎\", \"🐍\", \"🐲\", \"🦕\", \"🐳\", \"🐬\", \"🦭\", \"🐟️\", \"🐠\", \"🦈\", \"🐙\", \"🐚\", \"🐌\", \r\n\t\"🦋\", \"🐛\", \"🐝\", \"🐞\", \"💐\", \"🌹\", \"🌺\", \"🌻\", \"🌷\", \"🌲\", \"🌳\", \"🌴\", \"🌵\", \"🌿\", \"🍁\", \"🍇\", \r\n\t\"🍈\", \"🍉\", \"🍊\", \"🍋\", \"🍌\", \"🍍\", \"🥭\", \"🍏\", \"🍐\", \"🍑\", \"🍒\", \"🍓\", \"🫐\", \"🥝\", \"🍅\", \"🫒\", \r\n\t\"🥥\", \"🥑\", \"🍆\", \"🥕\", \"🌽\", \"🌶️\", \"🥬\", \"🥦\", \"🧅\", \"🍄\", \"🥜\", \"🥐\", \"🥖\", \"🥨\", \"🥯\", \"🥞\", \r\n\t\"🧇\", \"🍔\", \"🍕\", \"🌭\", \"🌮\", \"🍿\", \"🦀\", \"🦞\", \"🍨\", \"🍩\", \"🍪\", \"🎂\", \"🧁\", \"🍫\", \"🍬\", \"🍭\", \r\n\t\"🫖\", \"🧃\", \"🧉\", \"🧭\", \"🏔️\", \"🌋\", \"🏕️\", \"🏝️\", \"🏡\", \"⛲️\", \"🎠\", \"🎡\", \"🎢\", \"💈\", \"🚆\", \"🚋\", \r\n\t\"🚍️\", \"🚕\", \"🚗\", \"🚚\", \"🚜\", \"🛵\", \"🛺\", \"🛴\", \"🛹\", \"🛼\", \"⚓️\", \"⛵️\", \"🛶\", \"🚤\", \"🚢\", \"✈️\", \r\n\t\"🚁\", \"🚠\", \"🛰️\", \"🚀\", \"🛸\", \"⏰\", \"🌙\", \"🌡️\", \"☀️\", \"🪐\", \"🌟\", \"🌀\", \"🌈\", \"☂️\", \"❄️\", \"☄️\", \r\n\t\"🔥\", \"💧\", \"🌊\", \"🎃\", \"✨\", \"🎈\", \"🎉\", \"🎏\", \"🎀\", \"🎁\", \"🎟️\", \"🏆️\", \"⚽️\", \"🏀\", \"🏈\", \"🎾\", \r\n\t\"🥏\", \"🏓\", \"🏸\", \"🤿\", \"🥌\", \"🎯\", \"🪀\", \"🪁\", \"🔮\", \"🎲\", \"🧩\", \"🎨\", \"🧵\", \"👕\", \"🧦\", \"👗\", \r\n\t\"🩳\", \"🎒\", \"👟\", \"👑\", \"🧢\", \"💄\", \"💍\", \"💎\", \"📢\", \"🎶\", \"🎙️\", \"📻️\", \"🎷\", \"🪗\", \"🎸\", \"🎺\", \r\n\t\"🎻\", \"🪕\", \"🥁\", \"☎️\", \"🔋\", \"💿️\", \"🧮\", \"🎬️\", \"💡\", \"🔦\", \"🏮\", \"📕\", \"🏷️\", \"💳️\", \"✏️\", \"🖌️\", \r\n\t\"🖍️\", \"📌\", \"📎\", \"🔑\", \"🪃\", \"🏹\", \"⚖️\", \"🧲\", \"🧪\", \"🧬\", \"🔬\", \"🔭\", \"📡\", \"🪑\", \"🧹\", \"🗿\", \r\n]\r\nlet oFonts = {'default': '', noto: 'Noto Color Emoji', twemoji: 'Twemoji Mozilla'}\r\n\r\nfunction reset() {\r\n\tdom.valueE.value = \"\"\r\n}\r\nfunction preset() {\r\n\tdom.valueE.value = emojiPreset.join(\", \")\r\n}\r\npreset()\r\n\r\nfunction handleTheme(src) {\r\n\tlet remove = (src.value == \"darkmoji\" ? \"lightmoji\" : \"darkmoji\")\r\n\tdom.valueE.classList.add(src.value)\r\n\tdom.valueE.classList.remove(remove)\r\n\r\n\tdom.results.classList.add(src.value)\r\n\tdom.results.classList.remove(remove)\r\n}\r\nfunction handleFont(src) {\r\n\tlet fnt = oFonts[src.value]\r\n\tdom.valueE.style.fontFamily = fnt\r\n\tdom.results.style.fontFamily = fnt\r\n}\r\ndom.rdark.checked = true\r\ndom.default.checked = true\r\n\r\nfunction run_random(array) {\r\n\t//\r\n\tlet data = []\r\n\tfor (let i=0; i < 500; i++) {\r\n\t\tlet arr = [], line = []\r\n\t\twhile(arr.length < 4){\r\n\t\t\tlet r = Math.floor(Math.random() * array.length)\r\n\t\t\tif(arr.indexOf(r) === -1) {arr.push(r); line.push(array[r]) }\r\n\t\t}\r\n\t\tdata.push(line.join(\"\"))\r\n\t}\r\n\t// group into lines\r\n\tfunction groupLines([a,b,c,d,...rest]){\r\n\t\tif (rest.length === 0) return [[a,b,c,d].filter(x => x!==undefined)]\r\n\t\treturn [[a,b,c,d]].concat(groupLines(rest))\r\n\t}\r\n\tlet newData = groupLines(data)\r\n\t// display\r\n\tlet display = []\r\n\tnewData.forEach(function(array){\r\n\t\tdisplay.push(array.join(\"   \"))\r\n\t})\r\n\tdom.results.innerHTML = display.join(\"<br>\")\r\n}\r\n\r\nfunction run() {\r\n\t// reset\r\n\tdom.results = \"\"\r\n\tlet emojiList = []\r\n\r\n\t// clean up the list\r\n\tlet valueE = (dom.valueE.value).trim()\r\n\tvalueE = valueE.replace(/['\"]+/g, \"\")\r\n\tvalueE = valueE.trim()\r\n\tif (valueE.length) {\r\n\t\tlet tmpArr = valueE.split(\",\")\r\n\t\tfor (let i = 0 ; i < tmpArr.length; i++) {\r\n\t\t\tlet trimmed = tmpArr[i].trim()\r\n\t\t\tif (trimmed.length) {\r\n\t\t\t\temojiList.push(trimmed)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// make sure we have at least four unique items\r\n\t\temojiList = emojiList.filter(function (item, position) {\r\n\t\t\treturn emojiList.indexOf(item) === position\r\n\t\t})\r\n\t\tif (emojiList.length < 4) {\r\n\t\t\tdom.results = \"aww, snap! try adding some \"+ (emojiList.length > 0 ? \"more \" : \"\") +\"emojis\"\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\tdom.results = \"aww, snap! try adding some emojis\"\r\n\t\treturn\r\n\t}\r\n\t//console.debug(emojiList)\r\n\trun_random(emojiList)\r\n}\r\n\r\n</script>\r\n</body>\r\n</html>\r\n\r\n"
  },
  {
    "path": "tests/canvasnoise.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n  <title>canvas spoof fingerprinting</title>\r\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n  <script src=\"testglobals.js\"></script>\r\n  <script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#canvas\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb9\">\r\n\t\t<col width=\"18%\"><col width=\"82%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">canvas spoof fingerprinting\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Creates a random canvas with known results, reads it back, and analyzes\r\n\t\t\t\tthe\tdifferences. <code>getImageData</code> is being tested, but <code>toDataURL</code> and\r\n\t\t\t\t<code>toBlob</code> can also be \"decoded\" (albeit a little differently)</span>\r\n\t\t</td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\">\r\n\t\t\t<div class=\"btn-left\">\r\n\t\t\t\t<span class=\"btn9 btn\" onClick=\"run()\">[ re-run ]</span>\r\n\t\t\t\t<span><input id=\"optSolid\" type=\"checkbox\"> use a solid color</span>\r\n\t\t\t\t<span><input id=\"optStroke\" type=\"checkbox\"> use strokeText</span><br><br>\r\n\t\t\t</div>VISUALS</td></tr>\r\n\t\t<tr><td class=\"bottom padr\">control</td>\r\n\t\t\t<td>\r\n\t\t\t\t<canvas id=\"cnvBig\" width=\"180\" height=\"180\" style=\"border:5px solid white;\"></canvas> &nbsp;\r\n\t\t\t\t<canvas id=\"cnvCtrl\" width=\"20\" height=\"20\" style=\"border:1px solid white;\"></canvas>\r\n\t\t\t\t\t&nbsp; <span class=\"s9\"> &#9664; what we set</span>\r\n\t\t\t</td></tr>\r\n\t\t<tr><td></td><td><span class=\"intro\">The control that we read values from, pixel by pixel</span></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer -->\r\n\t\t<tr><td class=\"bottom padr\">1st read<br>getImageData</td>\r\n\t\t\t<td>\r\n\t\t\t\t<canvas id=\"cnvBig2\" width=\"180\" height=\"180\" style=\"border:5px solid white;\"></canvas> &nbsp;\r\n\t\t\t\t<canvas id=\"cnvGet\" width=\"20\" height=\"20\" style=\"border:1px solid white;\"></canvas>\r\n\t\t\t\t\t&nbsp; <span class=\"s9\"> &#9664; what we got back</span></td></tr>\r\n\t\t<tr><td></td><td><span class=\"intro\">What we have read back from the control, pixel by pixel</span></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer -->\r\n\t\t<tr>\r\n\t\t\t<td class=\"bottom padr\">results</td>\r\n\t\t\t<td>\r\n\t\t\t\t<span class=\"s9\">\r\n\t\t\t\t\t<canvas id=\"cnvGet2\" width=\"20\" height=\"20\" style=\"border:1px solid white;\" src=\"\"></canvas>\r\n\t\t\t\t\t\t&nbsp; &#9664; getImageData &nbsp;\r\n\t\t\t\t\t<img id=\"cnvURL\" width=\"20\" height=\"20\" style=\"border:1px solid white;\" src=\"\">\r\n\t\t\t\t\t\t&nbsp; &#9664; toDataURL &nbsp;\r\n\t\t\t\t\t<img id=\"cnvBlob\" width=\"20\" height=\"20\" style=\"border:1px solid white;\" src=\"\">\r\n\t\t\t\t\t\t&nbsp; &#9664; toBlob\r\n\t\t\t\t</span>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer -->\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer -->\r\n\r\n\t\t<!-- DATA -->\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\">DATA ANALYSIS</td></tr>\r\n\t\t<tr><td class=\"padr\">control</td><td class=\"c mono\" id=\"sethash\"></td></tr>\r\n\t\t<tr><td class=\"padr\">1st read</td><td class=\"c mono\" id=\"readhash\"></td></tr>\r\n\t\t<tr><td class=\"padr\">2nd read</td><td class=\"c mono\" id=\"readhash2\"></td></tr>\r\n\t\t<tr><td class=\"padr\">stats</td><td class=\"c mono\" id=\"readstats\"></td></tr>\r\n\t\t<tr><td class=\"padr\">diffs</td><td class=\"c mono\" id=\"readdiff\"></td></tr>\r\n\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nfunction run() {\r\n\t// vars\r\n\tlet clrSet = [], cellSet = [],\r\n\t\tclrRead = [], clrRead2 = [],\r\n\t\tcellRead = [], cellRead2 = [],\r\n\t\thashSet = \"\",\r\n\t\thashGetImage = \"\",\r\n\t\thashGetImage2 = \"\",\r\n\t\tcWidth = 20,\r\n\t\tcHeight = 20,\r\n\t\tm = 9 // multplier for large visuals\r\n\r\n\t// clear\r\n\tlet items = document.getElementsByClassName(\"c\")\r\n\tfor(let i=0; i < items.length; i++) {\r\n\t\titems[i].innerHTML = \"&nbsp\"\r\n\t}\r\n\r\n\tlet optSolid = dom.optSolid.checked\r\n\tlet optStroke = dom.optStroke.checked\r\n\r\n\t// pause so users see change\r\n\tsetTimeout(function(){\r\n\t\ttry {\r\n\t\t\tlet t0 = performance.now()\r\n\t\t\tlet cnvBig = dom.cnvBig\r\n\t\t\tlet cnvCtrl = dom.cnvCtrl\r\n\t\t\tif (cnvBig.getContext) {\r\n\t\t\t\tlet ctxBig = cnvBig.getContext('2d'),\r\n\t\t\t\t\tctxCtrl = cnvCtrl.getContext('2d'),\r\n\t\t\t\t\tctxGet = dom.cnvGet.getContext('2d'),\r\n\t\t\t\t\tctxGet2 = dom.cnvGet2.getContext('2d'),\r\n\t\t\t\t\tctxBig2 = dom.cnvBig2.getContext('2d')\r\n\r\n\t\t\t\t// note: all random values we don't use 255 so RFP-white shows up in all it's glory\r\n\t\t\t\tlet solidR = Math.floor(Math.random()*255),\r\n\t\t\t\t\tsolidG = Math.floor(Math.random()*255),\r\n\t\t\t\t\tsolidB = Math.floor(Math.random()*255)\r\n\t\t\t\tlet solidClrs = solidR +\", \"+ solidG +\", \"+ solidB +\", 255\"\r\n\r\n\t\t\t\t// fill big visual with our random color\r\n\t\t\t\tctxBig.fillStyle = \"rgba(\"+ solidClrs +\")\"\r\n\t\t\t\tctxBig.fillRect(0, 0, cnvBig.width, cnvBig.height);\r\n\t\t\t\t// ensure background is correct color in ctrl\r\n\t\t\t\tctxCtrl.fillStyle = \"rgba(\"+ solidClrs +\")\"\r\n\t\t\t\tctxCtrl.fillRect(0, 0, cnvCtrl.width, cnvCtrl.height);\r\n\r\n\t\t\t\tif (optSolid) {\r\n\t\t\t\t\tlet total = cWidth * cHeight\r\n\t\t\t\t\tfor (let i=0; i < total; i++) {\r\n\t\t\t\t\t\tclrSet.push(solidR, solidG, solidB, 255)\r\n\t\t\t\t\t\tcellSet.push(solidClrs)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet indexFont = []\r\n\t\t\t\tif (!optSolid) {\r\n\t\t\t\t\tlet clrR = Math.floor(Math.random()*255),\r\n\t\t\t\t\t\tclrG = Math.floor(Math.random()*255),\r\n\t\t\t\t\t\tclrB = Math.floor(Math.random()*255)\r\n\t\t\t\t\tlet fpText = \"\\u2588\\u2588\\u2588\\u2588\" // full block\r\n\t\t\t\t\t// order matters\r\n\t\t\t\t\t// trigger fillText stealth\r\n\t\t\t\t\t\t// only the text area is altered, so try and make it cover all of it\r\n\t\t\t\t\tctxCtrl.font = \"512px sans-serif\" // large\r\n\t\t\t\t\tctxCtrl.textBaseline = \"top\"\r\n\t\t\t\t\tctxCtrl.textBaseline = \"alphabetic\"\r\n\t\t\t\t\tctxCtrl.fillText(fpText,0,0)\r\n\t\t\t\t\t// trigger strokeText stealth: don't overwrite half the fillText\r\n\t\t\t\t\tif (optStroke) {\r\n\t\t\t\t\t\tfpText = \"-\" // straight, less curves\r\n\t\t\t\t\t\tctxCtrl.font = \"16px monospace\" // even straighter\r\n\t\t\t\t\t\tctxCtrl.strokeStyle =\"rgba(\"+ solidClrs +\")\"\r\n\t\t\t\t\t\tfor (let x=0; x < cWidth/2; x++) {\r\n\t\t\t\t\t\t\tfor (let y=0; y < cHeight/4; y++) { // divide by 4 to match TZP size\r\n\t\t\t\t\t\t\t\tctxCtrl.strokeText(fpText,x,y)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t/* results\r\n\t\t\t\t\t\tfillText only\r\n\t\t\t\t\t\t\t 49 : CB = has at least fillText\r\n\t\t\t\t\t\tfillText + strokeText\r\n\t\t\t\t\t\t\t 49 : CB = only fillText\r\n\t\t\t\t\t\t\t 49 : CB = has fillText + strokeText\r\n\t\t\t\t\t\t\t~14 : CB = only strokeText\r\n\t\t\t\t\t\t\t  0 : false positive: can't get any on my machine but see below\r\n\r\n\t\t\t\t\t\tstrokeText can create false positives (aliasing?, bezier curves etc)\r\n\t\t\t\t\t\ton my machine: windows, dpi=1, en* language default fonts, default text size etc\r\n\t\t\t\t\t\t - 10k tests on TZP main: `-` char and monospace = zero false positives\r\n\t\t\t\t\t\t\t\tzoom doesn't seem to affect it\r\n\r\n\t\t\t\t\t\ton my machine running 100 tests: false positives\r\n\t\t\t\t\t\t'a' monospace  = 98% (9 affected cells)\r\n\t\t\t\t\t\t'x' monospace  = 84% (9)\r\n\t\t\t\t\t\t'|' monospace  = 75% (5)\r\n\t\t\t\t\t\t'-' monospace  =  0% = also 0 with 10k tests\r\n\r\n\t\t\t\t\t\t'a' sans-serif = 99% (7 affected cells overall)\r\n\t\t\t\t\t\t'x' sans-serif = 94% (7)\r\n\t\t\t\t\t\t'|' sans-serif = 51% (5)\r\n\t\t\t\t\t\t'-' sans-serif = 16% (1)\r\n\r\n\t\t\t\t\t\tToo risky to use in production given all the variables across platforms/users\r\n\t\t\t\t\t\te.g. even if we decided that if only 1 (or 2) indexFonts changed and if only\r\n\t\t\t\t\t\tindexFonts changed, i.e it is in stealth mode, means a false positive: at the\r\n\t\t\t\t\t\tend of the day we really can't be sure. We need to deal in absolutes\r\n\t\t\t\t\t*/\r\n\t\t\t\t\t// then overwrite a % of the pixels with random values\r\n\t\t\t\t\tlet counter = -1\r\n\t\t\t\t\tfor (let x=0; x < cWidth; x++) {\r\n\t\t\t\t\t\tlet xEven = (x % 2 == 0)\r\n\t\t\t\t\t\tfor (let y=0; y < cHeight; y++) {\r\n\t\t\t\t\t\t\tcounter ++\r\n\t\t\t\t\t\t\tlet k = counter * 4\r\n\t\t\t\t\t\t\tlet yEven = (y % 2 == 0)\r\n\t\t\t\t\t\t\t// xEven + yEven == 1 = checkerboard = 1/2\r\n\t\t\t\t\t\t\t// xEven + yEven == 2 = another 1/4\r\n\t\t\t\t\t\t\t// xEven + yEven == 0 = the remainder: of which we can further reduce e.g. multples of 3\r\n\t\t\t\t\t\t\tlet go = (xEven + yEven == 1 || xEven + yEven == 2) // 3/4ths\r\n\t\t\t\t\t\t\tif (!go) {\r\n\t\t\t\t\t\t\t\tif ((x * y) % 3 == 0 ) {go = true} // brings us up to 351/400\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tif (go) {\r\n\t\t\t\t\t\t\t\t// get random color\r\n\t\t\t\t\t\t\t\tclrR = Math.floor(Math.random()*255)\r\n\t\t\t\t\t\t\t\tclrG = Math.floor(Math.random()*255)\r\n\t\t\t\t\t\t\t\tclrB = Math.floor(Math.random()*255)\r\n\t\t\t\t\t\t\t\tlet clrs = clrR +\", \"+ clrG +\", \"+ clrB +\", 255\"\r\n\t\t\t\t\t\t\t\tclrSet.push(clrR)\r\n\t\t\t\t\t\t\t\tclrSet.push(clrG)\r\n\t\t\t\t\t\t\t\tclrSet.push(clrB)\r\n\t\t\t\t\t\t\t\tclrSet.push(255)\r\n\t\t\t\t\t\t\t\tcellSet.push(clrs)\r\n\t\t\t\t\t\t\t\tctxBig.fillStyle = \"rgba(\"+ clrs +\")\"\r\n\t\t\t\t\t\t\t\tctxBig.fillRect(x*m, y*m, m, m)\r\n\t\t\t\t\t\t\t\tctxCtrl.fillStyle = \"rgba(\"+ clrs +\")\"\r\n\t\t\t\t\t\t\t\tctxCtrl.fillRect(x, y, 1, 1)\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tindexFont.push(k)\r\n\t\t\t\t\t\t\t\t//don't touch the canvas but record solid colors\r\n\t\t\t\t\t\t\t\tclrSet.push(solidR)\r\n\t\t\t\t\t\t\t\tclrSet.push(solidG)\r\n\t\t\t\t\t\t\t\tclrSet.push(solidB)\r\n\t\t\t\t\t\t\t\tclrSet.push(255)\r\n\t\t\t\t\t\t\t\tcellSet.push(solidClrs)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\thashSet = mini(clrSet) +\" | \"+ mini(cellSet)\r\n\t\t\t\tdom.sethash = hashSet\r\n\r\n\t\t\t\t// getImageData\r\n\t\t\t\tlet imageData = ctxCtrl.getImageData(0,0, cnvCtrl.width, cnvCtrl.height)\r\n\t\t\t\tctxGet2.putImageData(imageData, 0, 0)\r\n\r\n\t\t\t\t// toDataURL\r\n\t\t\t\tlet dataURL = cnvCtrl.toDataURL(\"image/png\")\r\n\t\t\t\tcnvURL.src = dataURL\r\n\r\n\t\t\t\t// toBlob\r\n\t\t\t\tcnvCtrl.toBlob(function(blob) {\r\n\t\t\t\t\tlet url = URL.createObjectURL(blob)\r\n\t\t\t\t\tcnvBlob.src = url\r\n\t\t\t\t})\r\n\r\n\t\t\t\t// 1st read\r\n\t\t\t\tlet testRead = ctxCtrl.getImageData(0,0, cWidth, cHeight).data\r\n\t\t\t\tlet aRead = []\r\n\t\t\t\tfor (let x=0; x < cWidth; x++) {\r\n\t\t\t\t\tfor (let y=0; y < cHeight; y++) {\r\n\t\t\t\t\t\t// we need to read x/y as the opposite\r\n\t\t\t\t\t\t// as getImageData reads down then across\r\n\t\t\t\t\t\t// so we want the 0th quartet, then 20th, then 40th\r\n\t\t\t\t\t\tlet k = x + (y * cHeight)\r\n\t\t\t\t\t\taRead = testRead.slice(k*4, (k*4) + 4)\r\n\t\t\t\t\t\tclrRead.push(aRead[0])\r\n\t\t\t\t\t\tclrRead.push(aRead[1])\r\n\t\t\t\t\t\tclrRead.push(aRead[2])\r\n\t\t\t\t\t\tclrRead.push(aRead[3])\r\n\t\t\t\t\t\tlet pixel = aRead.join(\", \")\r\n\t\t\t\t\t\tcellRead.push(pixel)\r\n\t\t\t\t\t\t// output READ visuals: just on the first read\r\n\t\t\t\t\t\tctxBig2.fillStyle = \"rgba(\"+ pixel +\")\"\r\n\t\t\t\t\t\tctxBig2.fillRect(x*m, y*m, m, m)\r\n\t\t\t\t\t\tctxGet.fillStyle = \"rgba(\"+ pixel +\")\"\r\n\t\t\t\t\t\tctxGet.fillRect(x, y, 1, 1)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\thashGetImage = mini(clrRead) +\" | \" + mini(cellRead)\r\n\r\n\t\t\t\tlet indexChanged = []\r\n\t\t\t\tfor (let i=0; i < cellSet.length; i++) {\r\n\t\t\t\t\tif (cellSet[i] !== cellRead[i]) {\r\n\t\t\t\t\t\tindexChanged.push(i * 4)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tlet aNotInFonts = indexChanged.filter(x => !indexFont.includes(x))\r\n\t\t\t\tlet isInputOnly = aNotInFonts == 0\r\n\r\n\t\t\t\t// 2nd read\r\n\t\t\t\tlet testRead2 = ctxCtrl.getImageData(0,0, cWidth, cHeight).data\r\n\t\t\t\tlet aRead2 = []\r\n\t\t\t\tfor (let x=0; x < cWidth; x++) {\r\n\t\t\t\t\tfor (let y=0; y < cHeight; y++) {\r\n\t\t\t\t\t\t// we need to read x/y as the opposite\r\n\t\t\t\t\t\t// as getImageData reads down then across\r\n\t\t\t\t\t\t// so we want the 0th quartet, then 20th, then 40th\r\n\t\t\t\t\t\tlet k = x + (y * cHeight)\r\n\t\t\t\t\t\taRead2 = testRead2.slice(k*4, (k*4) + 4)\r\n\t\t\t\t\t\tclrRead2.push(aRead2[0])\r\n\t\t\t\t\t\tclrRead2.push(aRead2[1])\r\n\t\t\t\t\t\tclrRead2.push(aRead2[2])\r\n\t\t\t\t\t\tclrRead2.push(aRead2[3])\r\n\t\t\t\t\t\tlet pixel = aRead2.join(\", \")\r\n\t\t\t\t\t\tcellRead2.push(pixel)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\thashGetImage2 = mini(clrRead2) +\" | \" + mini(cellRead2)\r\n\r\n\t\t\t\t// output\r\n\t\t\t\tif (hashGetImage2 == hashSet) {\r\n\t\t\t\t\tdom.readhash2.innerHTML = hashGetImage2 + s9 +\" [matches]\"+ sc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdom.readhash2.innerHTML = sb + hashGetImage2 + sc\r\n\t\t\t\t\t\t+ (hashGetImage2 == hashGetImage ? \" [cached]\" : \" [per-execution]\")\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (hashGetImage == hashSet) {\r\n\t\t\t\t\tdom.readhash.innerHTML = hashGetImage + s9 +\" [matches]\"+ sc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdom.readhash.innerHTML = sb + hashGetImage + sc\r\n\r\n\t\t\t\t\t// analyze\r\n\t\t\t\t\tlet changeR = [], changeG = [], changeB = [], changeA = [], changeC = [], changeC2 = [], channels = []\r\n\t\t\t\t\tlet absR = [], absG = [], absB = []\r\n\t\t\t\t\tlet negR = 0, negG = 0, negB = 0, z = 0\r\n\t\t\t\t\tlet chan = \"\"\r\n\r\n\t\t\t\t\tfor (let i=0; i < clrSet.length; i++) {\r\n\t\t\t\t\t\tlet diff = clrRead[i] - clrSet[i]\r\n\t\t\t\t\t\tif (z==0) {\r\n\t\t\t\t\t\t\tif (diff !== 0) {chan += \"r\"; changeR.push(diff); absR.push(Math.abs(diff)); if(diff < 0) {negR++}}\r\n\t\t\t\t\t\t\tz = 1\r\n\t\t\t\t\t\t} else if (z==1) {\r\n\t\t\t\t\t\t\tif (diff !== 0) {chan += \"g\"; changeG.push(diff); absG.push(Math.abs(diff)); if(diff < 0) {negG++}}\r\n\t\t\t\t\t\t\tz = 2\r\n\t\t\t\t\t\t} else if (z==2) {\r\n\t\t\t\t\t\t\tif (diff !== 0) {chan += \"b\"; changeB.push(diff); absB.push(Math.abs(diff)); if(diff < 0) {negB++}}\r\n\t\t\t\t\t\t\tz = 3\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tif (diff !== 0) {chan += \"a\"; changeA.push(diff)}\r\n\t\t\t\t\t\t\tif (chan !== \"\") {channels.push(chan)}\r\n\t\t\t\t\t\t\tz = 0\r\n\t\t\t\t\t\t\tchan = \"\"\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// what co-ordinates changed\r\n\t\t\t\t\tfor (let i=0; i < cellSet.length; i++) {\r\n\t\t\t\t\t\tif (cellSet[i] !== cellRead[i]) {changeC.push(i)}\r\n\t\t\t\t\t\tif (cellSet[i] !== cellRead2[i]) {changeC2.push(i)}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tif (changeC.length > 1) {\r\n\t\t\t\t\t\tconsole.debug(\"read 1: pixels changed: \", mini(changeC) +\"\\n\", changeC)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (changeC2.length > 1) {\r\n\t\t\t\t\t\tconsole.debug(\"read 2: pixels changed: \", mini(changeC2) +\"\\n\", changeC2)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t*/\r\n\r\n\t\t\t\t\t// channels\r\n\t\t\t\t\tlet counts = {}\r\n\t\t\t\t\tchannels.forEach(function(c) {\r\n\t\t\t\t\t\tcounts[c] = (counts[c] || 0) + 1\r\n\t\t\t\t\t})\r\n\t\t\t\t\tlet tmpchan = channels.filter(function(item, position) {return channels.indexOf(item) === position})\r\n\t\t\t\t\ttmpchan.sort()\r\n\r\n\t\t\t\t\tlet chancount = 0, chanstring = \"\"\r\n\t\t\t\t\tif (changeR.length) {chancount++; chanstring = \"r\"}\r\n\t\t\t\t\tif (changeG.length) {chancount++; chanstring += \"g\"}\r\n\t\t\t\t\tif (changeB.length) {chancount++; chanstring += \"b\"}\r\n\t\t\t\t\tif (changeA.length) {chancount++; chanstring += \"a\"}\r\n\r\n\t\t\t\t\t// stats\r\n\t\t\t\t\tdom.readstats.innerHTML = \"    cells changed: \"+ changeC.length\r\n\t\t\t\t\t\t+\"<br> channels changed: \"+ chancount +\" [\" + chanstring +\"]\"\r\n\t\t\t\t\t\t+\"<br>   channel counts: r: \"+ changeR.length\r\n\t\t\t\t\t\t+\", g: \"+ changeG.length\r\n\t\t\t\t\t\t+\", b: \"+ changeB.length\r\n\t\t\t\t\t\t+\", a: \"+ changeA.length\r\n\t\t\t\t\t\t+\"<br>   combos altered: \"+ tmpchan.length +\" [\"+ tmpchan.join(\", \") +\"]\"\r\n\t\t\t\t\t\t+\"<br>     combo counts: \"+ JSON.stringify(counts)\r\n\t\t\t\t\t\t+\"<br>     stealth mode: \"+ (isInputOnly ? sb +\"yes\"+ sc +\" [input only changed]\" : \"no\")\r\n\r\n\t\t\t\t\t// diffs\r\n\t\t\t\t\tlet tmpR = changeR.filter(function(item, position) {return changeR.indexOf(item) === position})\r\n\t\t\t\t\tlet tmpG = changeG.filter(function(item, position) {return changeG.indexOf(item) === position})\r\n\t\t\t\t\tlet tmpB = changeB.filter(function(item, position) {return changeB.indexOf(item) === position})\r\n\t\t\t\t\tlet tmpA = changeA.filter(function(item, position) {return changeA.indexOf(item) === position})\r\n\t\t\t\t\t// sort diffs numerically\r\n\t\t\t\t\ttmpR.sort(function(a, b){return a-b})\r\n\t\t\t\t\ttmpG.sort(function(a, b){return a-b})\r\n\t\t\t\t\ttmpB.sort(function(a, b){return a-b})\r\n\t\t\t\t\ttmpA.sort(function(a, b){return a-b})\r\n\t\t\t\t\t// for each we want a count and a spread (max/min)\r\n\t\t\t\t\t// +/- split (ignore a)\r\n\t\t\t\t\tlet strR = \"n/a\", strG = \"n/a\", strB = \"n/a\", strA = \"n/a\"\r\n\t\t\t\t\tif (tmpR.length) {\r\n\t\t\t\t\t\tstrR = tmpR.length +\" [\"+ tmpR[0] +\" to \"+ tmpR[tmpR.length-1] +\", \"+ (tmpR[tmpR.length-1]-tmpR[0]) +\"]\"\r\n\t\t\t\t\t\tstrR += \" \"+ negR +\"/\"+ (changeR.length-negR)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (tmpG.length) {\r\n\t\t\t\t\t\tstrG = tmpG.length +\" [\"+ tmpG[0] +\" to \"+ tmpG[tmpG.length-1] +\", \"+ (tmpG[tmpG.length-1]-tmpG[0]) +\"]\"\r\n\t\t\t\t\t\tstrG += \" \"+ negG +\"/\" + (changeG.length-negG)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (tmpB.length) {\r\n\t\t\t\t\t\tstrB = tmpB.length +\" [\"+ tmpB[0] +\" to \"+ tmpB[tmpB.length-1] +\", \"+ (tmpB[tmpB.length-1]-tmpB[0]) +\"]\"\r\n\t\t\t\t\t\tstrB += \" \"+ negB +\"/\"+ (changeB.length-negB)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (tmpA.length) {\r\n\t\t\t\t\t\tstrA = tmpA.length +\" [\"+ tmpA[0] +\" to \"+ tmpA[tmpA.length-1] +\", \"+ (tmpA[tmpA.length-1]-tmpA[0]) +\"]\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t\t// absolute stats (ignore a)\r\n\t\t\t\t\ttmpR = absR.filter(function(item, position) {return absR.indexOf(item) === position})\r\n\t\t\t\t\ttmpG = absG.filter(function(item, position) {return absG.indexOf(item) === position})\r\n\t\t\t\t\ttmpB = absB.filter(function(item, position) {return absB.indexOf(item) === position})\r\n\t\t\t\t\ttmpR.sort(function(a, b){return a-b})\r\n\t\t\t\t\ttmpG.sort(function(a, b){return a-b})\r\n\t\t\t\t\ttmpB.sort(function(a, b){return a-b})\r\n\t\t\t\t\tif (tmpR.length) {\r\n\t\t\t\t\t\tstrR += \" ... \" + tmpR.length +\" [\"+ tmpR[0] +\" to \"+ tmpR[tmpR.length-1] +\", \"+ (tmpR[tmpR.length-1]-tmpR[0]) +\"]\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (tmpG.length) {\r\n\t\t\t\t\t\tstrG += \" ... \" + tmpG.length +\" [\"+ tmpG[0] +\" to \"+ tmpG[tmpG.length-1] +\", \"+ (tmpG[tmpG.length-1]-tmpG[0]) +\"]\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (tmpB.length) {\r\n\t\t\t\t\t\tstrB += \" ... \" + tmpB.length +\" [\"+ tmpB[0] +\" to \"+ tmpB[tmpB.length-1] +\", \"+ (tmpB[tmpB.length-1]-tmpB[0]) +\"]\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdom.readdiff.innerHTML = \"  r: \"+ strR +\"<br>  g: \"+ strG +\"<br>  b: \"+ strB +\"<br>  a: \"+ strA \r\n\t\t\t\t}\r\n\t\t\t\t// perf\r\n\t\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t\t}\r\n\t\t} catch(e) {\r\n\t\t\tdom.readhash.innerHTML = sb + e.name +\": \"+ sc + e.message\r\n\t\t}\r\n\t}, 170)\r\n}\r\n\r\nrun()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/canvasrfp.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=620\">\r\n\t<title>canvas rfp</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 600px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#canvas\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb9\">\r\n\t\t<col width=\"1%\"><col width=\"99%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">canvas RFP\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"s9\">FF96+ </span>\r\n\t\t\t<span class=\"no_color\">: testing RFP characteristics in toDataURL [and by extension toBlob]</span>\r\n\t\t\t&nbsp; &#x25BA; <canvas id=\"canvas\" width=\"16\" height=\"16\"></canvas> &nbsp; &#x25C4; <br><br>\r\n\t\t\t<details><summary><span class=\"no_color\">click me for more info</span></summary>\r\n\t\t\t\t<span class=\"no_color\">\r\n\t\t\t\t\t<ul>\r\n\t\t\t\t\t<li>toDataURLs: <code>aRaw</code></li>\r\n\t\t\t\t\t<li>change canvas dimensons: max 16, min 2: <code>set_size(width, height)</code></li>\r\n\t\t\t\t\t<li>all coded patterns: <code>oControl</code></li>\r\n\t\t\t\t\t<li>running a size with no patterns will still summarize possible RFP rules</li>\r\n\t\t\t\t\t</ul>\r\n\t\t\t\t</span>\r\n\t\t</details>\r\n\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn9 btnfirst\" onClick=\"run_checks(100)\">[ run 100 ]</span>\r\n\t\t\t<span class=\"btn9 btn\" onClick=\"run_checks(500)\">[ run 500 ]</span>\r\n\t\t\t<span class=\"btn9 btn\" onClick=\"run_checks(1000)\">[ run 1000 ]</span>\r\n\t\t\t<span class=\"btn9 btn\" onClick=\"run_checks(5000)\">[ run 5000 ]</span>\r\n\t\t\t<span class=\"btn9 btn\" onClick=\"run_checks(20000)\">[ run 20000 ]</span>\r\n\t\t\t<br><br><hr>\r\n\t\t\t<br><span class=\"spaces\" style=\"color: #b3b3b3;\" id=\"totals\"></span>\r\n\t\t\t<br><span class=\"spaces\" style=\"color: #b3b3b3;\" id=\"results\"></span>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer -->\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\n// data\r\nlet aBypass = [], aRaw = [], oFail = {}, oPass = {}, oRules = {}\r\n//counts\r\nlet countTest = 0, runningTotal = 0, runningPass = 0, runningMatch = 0, maxLines = 20, oRunningTotal = {}\r\n// control\r\nlet controlHash = \"\", controlLengths = [], testA = \"\", testB = \"\", hashA = \"\", hashB = \"\", hasRules = false\r\n\r\n// FF96+ expected values: see 1724331 + 1737038\r\nlet oControl = {\r\n\t\"16x16\": {\r\n\t\t\"hash\": \"bdcce913\",\r\n\t\t\"lengths\": [174,178,182,186,190],\r\n\t\t\"ruleNos\": {},\r\n\t\t\"rules\": {},\r\n\t\t\"slice1\": \"lEQVQ4T2\",\r\n\t\t\"slice2\": [\"5ErkJggg==\",\"VORK5CYII=\",\"lFTkSuQmCC\"],\r\n\t},\r\n\t\"16x8\": {\r\n\t\t\"hash\": \"a8d0bd06\",\r\n\t\t\"lengths\": [166,170,174,178],\r\n\t\t\"ruleNos\": {},\r\n\t\t\"rules\": {},\r\n\t\t\"slice1\": \"lEQVQoU2\",\r\n\t\t\"slice2\": [\"5ErkJggg==\",\"VORK5CYII=\",\"lFTkSuQmCC\"],\r\n\t},\r\n}\r\n\r\nfunction populate_rules() {\r\n\tfor (const k of Object.keys(oControl)) {\r\n\t\tlet ruleNo = 1\r\n\t\tlet lengths = oControl[k][\"lengths\"],\r\n\t\t\tslice1 = oControl[k][\"slice1\"],\r\n\t\t\tslice2 = oControl[k][\"slice2\"]\r\n\t\tlengths.forEach(function(len) {\r\n\t\t\tslice2.forEach(function(slice) {\r\n\t\t\t\tlet key = len +\"...\"+ slice1 +\"...\"+ slice\r\n\t\t\t\toControl[k][\"rules\"][key] = ruleNo\r\n\t\t\t\toControl[k][\"ruleNos\"][ruleNo] = key\r\n\t\t\t\truleNo++\r\n\t\t\t})\r\n\t\t})\r\n\t}\r\n}\r\n\r\nlet is96 = (Intl.PluralRules.supportedLocalesOf(\"sc\").join() == \"sc\")\r\nlet sizeW = 16, sizeH = 8 // smaller faster test\r\n\r\nfunction run_checks(max) {\r\n\t/* STEP ONE\trun some checks\t*/\r\n\r\n\tif (\"undefined\" === typeof Object.toSource && \"sc\" === Intl.PluralRules.supportedLocalesOf(\"sc\").join()) {\r\n\t} else {\r\n\t\t// 95 or lower\r\n\t\tdom.results.innerHTML = \"requires Firefix 96+\"\r\n\t\treturn\r\n\t}\r\n\r\n\r\n\t// reset\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\tcountTest = max\r\n\taBypass = []\r\n\tif (runningTotal == 0) {\r\n\t\t// reset\r\n\t\tdom.totals = \"\"\r\n\t\toRunningTotal = {}\r\n\t\toRules = {}\r\n\t\thasRules = false\r\n\t\ttry {\r\n\t\t\t// pre-generate arrays\r\n\t\t\tif (oControl[sizeW +\"x\"+ sizeH] !== undefined) {\r\n\t\t\t\tfor (const k of Object.keys(oControl[sizeW +\"x\"+ sizeH][\"rules\"])) {\r\n\t\t\t\t\tlet ruleNo = oControl[sizeW +\"x\"+ sizeH][\"rules\"][k]\r\n\t\t\t\t\truleNo = \"rule\" + (ruleNo +\"\").padStart(2, \"0\")\r\n\t\t\t\t\toRunningTotal[ruleNo] = [0]\r\n\t\t\t\t\toRules[k] = oControl[sizeW +\"x\"+ sizeH][\"rules\"][k]\r\n\t\t\t\t}\r\n\t\t\t\thasRules = true\r\n\t\t\t}\r\n\t\t\t//console.log(oRunningTotal)\r\n\t\t\t//console.log(oRules)\r\n\t\t} catch(e) {}\r\n\t}\r\n\r\n\t// isFF\r\n\tif (!isFF) {aBypass.push(\"this is not gecko\")}\r\n\t// FF78+\r\n\tif (isFF && !window.Document.prototype.hasOwnProperty(\"replaceChildren\")) {\r\n\t\taBypass.push(\"this is not Firefox 78+, when RFP canvas was added\")\r\n\t}\r\n\tif (isFF && !is96) {\r\n\t\taBypass.push(\"this is not Firefox 96+, as per the test description\")\r\n\t}\r\n\t// isFile\r\n\t//if (isFF && isFile) {aBypass.push(\"RFP doesn't work on file scheme\")}\r\n\t// heh\r\n\tif (runningTotal >= 500000) {dom.results = \"that's enough tests for now... go take a break\"; return}\r\n\t// create canvas once per run\r\n\ttry {\r\n\t\tlet canvas = document.getElementById(\"canvas\")\r\n\t\tcanvas.width = sizeW\r\n\t\tcanvas.height = sizeH // always reset canvas size since width+height are variable via console\r\n\t\tlet ctx = canvas.getContext('2d')\r\n\t\tfor (let x=0; x < sizeW; x++) {\r\n\t\t\tfor (let y=0; y < sizeH; y++) {\r\n\t\t\t\tctx.fillStyle = \"rgba(\" + (x*y) +\",\"+ (x*16) +\",\"+ (y*16) +\",255)\"\r\n\t\t\t\tctx.fillRect(x, y, 1, 1)\r\n\t\t\t}\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = e.name === undefined ? \"error\" : sb+ e.name  + sc+\": \" + e.message\r\n\t\treturn\r\n\t}\r\n\t// looks good\r\n\tget_rawdata()\r\n}\r\n\r\nfunction get_rawdata() {\r\n\t/* STEP TWO get raw data */\r\n\r\n\t// info\r\n\tdom.results = \"calculating...\"\r\n\t// reset\r\n\toFail = {},\r\n\toPass = {}\r\n\taRaw = []\r\n\tcontrolHash = \"\"\r\n\tcontrolLengths = []\r\n\ttestA = \"\"\r\n\ttestB = \"\"\r\n\thashA = \"\"\r\n\thashB = \"\"\r\n\r\n\t// test away\r\n\tlet delay = 250\r\n\tif (countTest > 1000) {delay = 45}\r\n\r\n\tsetTimeout(function(){\r\n\t\ttry {\r\n\t\t\tlet t0 = performance.now()\r\n\t\t\tlet canvas = document.getElementById(\"canvas\")\r\n\r\n\t\t\t// first two tests\r\n\t\t\ttestA = canvas.toDataURL()\r\n\t\t\thashA = mini(testA)\r\n\t\t\ttestB = canvas.toDataURL()\r\n\t\t\thashB = mini(testA)\r\n\t\t\tif (testA === testB) {aBypass.push(\"canvas is not random per execution\")}\r\n\t\t\t// FF\r\n\t\t\tif (isFF) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tcontrolHash = oControl[sizeW+\"x\"+sizeH][\"hash\"]\r\n\t\t\t\t\tcontrolLengths = oControl[sizeW+\"x\"+sizeH][\"lengths\"]\r\n\t\t\t\t\tif (hashA === controlHash || hashB === controlHash) {aBypass.push(\"canvas is not being spoofed\")}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\t//console.error(e.name, e.message)\r\n\t\t\t\t\t// don't return if missing control data, we want to allow different sizes in aRaw\r\n\t\t\t\t}\r\n\t\t\t\t/*\r\n\t\t\t\tconsole.log(sizeW +\" x \"+ sizeH)\r\n\t\t\t\tconsole.log(testA.length, testA)\r\n\t\t\t\tconsole.log(testB.length, testB)\r\n\t\t\t\tconsole.log(aBypass)\r\n\t\t\t\tconsole.log(controlHash)\r\n\t\t\t\tconsole.log(controlLengths)\r\n\t\t\t\t//*/\r\n\t\t\t}\r\n\t\t\t// add existing two tests\r\n\t\t\taRaw.push(testA, testB)\r\n\t\t\t// get the rest\r\n\t\t\tfor (let i=2; i < countTest; i++) {\r\n\t\t\t\taRaw.push(canvas.toDataURL())\r\n\t\t\t}\r\n\t\t\t// raw perf\r\n\t\t\tlet t1 = performance.now()\r\n\t\t\tlet perItem = (t1-t0)/countTest\r\n\t\t\tif (!Number.isInteger(perItem)) {perItem = perItem.toFixed(2)}\r\n\t\t\tdom.perf.innerHTML = Math.round(t1-t0) +\" ms | \"+ perItem +\" each\"\r\n\r\n\t\t\t// next step\r\n\t\t\tanalyse_rawdata()\r\n\r\n\t\t} catch(e) {\r\n\t\t\tdom.results.innerHTML = (e.name === undefined ? \"error\" : sb + e.name + sc +\": \"+ e.message)\r\n\t\t}\r\n\t}, delay)\r\n}\r\n\r\nfunction analyse_rawdata() {\r\n\t/* STEP THREE analyze raw data */\r\n\r\n\t//console.log(\"'\" + aRaw.join(\"',\\n'\") + \"'\")\r\n\ttry {\r\n\t\t// get occurences of each\r\n\t\tlet oOccurrences = aRaw.reduce(function(occ, item) {\r\n\t\t\tocc[item] = (occ[item] || 0) + 1\r\n\t\t\treturn occ\r\n\t\t}, {})\r\n\r\n\t\t// keep counts by slice data + length\r\n\t\t// keep counts by slice data + length\r\n\t\tfor (const k of Object.keys(oOccurrences)) {\r\n\t\t\tlet len = k.length\r\n\t\t\tlet count = oOccurrences[k]\r\n\t\t\tlet str\r\n\t\t\t// default slices\r\n\t\t\tlet slice1 = k.slice(72,80),\r\n\t\t\t\tslice2 = k.slice(len - 10, len)\r\n\t\t\tif (hasRules) {\r\n\t\t\t\tlet key = len +\"...\"+ slice1 +\"...\"+ slice2\r\n\t\t\t\tif (Object.keys(oRules).includes(key)) {\r\n\t\t\t\t\tstr = oRules[key]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (str !== undefined) {\r\n\t\t\t\tif (oPass[str] === undefined) {\r\n\t\t\t\t\toPass[str] = count\r\n\t\t\t\t} else {\r\n\t\t\t\t\toPass[str] = oPass[str] + count\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tstr = len +\"...\"+ slice1 +\"...\"+ slice2\r\n\t\t\t\tif (oFail[str] === undefined) {\r\n\t\t\t\t\toFail[str] = count\r\n\t\t\t\t} else {\r\n\t\t\t\t\toFail[str] = oFail[str] + count\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t// next\r\n\t\toutput()\r\n\r\n\t} catch(e) {\r\n\t\tconsole.error(e.name, e.message)\r\n\t}\r\n}\r\n\r\nfunction output() {\r\n\tlet display = []\r\n\tdisplay.push(s9 +\"TESTS RUN: \" + countTest + sc\r\n\t\t+\" [\"+ sizeW +\"w x \"+ sizeH +\"h]<br>\"\r\n\t)\r\n\r\n\t// tests\r\n\t/* simulate RFP\r\n\toPass = {\"7\": countTest - 33, \"11\": 33}\r\n\toFail = {}\r\n\taBypass = []\r\n\t//*/\r\n\r\n\t/* simulate 100% matches but one basic check fails\r\n\toPass = {\"7\": countTest - 33, \"11\": 33}\r\n\toFail = {}\r\n\taBypass = [\"canvas is not random per execution\"]\r\n\t//*/\r\n\r\n\t/* simulate 100% matches but multiple basic check fails\r\n\toPass = {\"7\": countTest - 33, \"11\": 33}\r\n\toFail = {}\r\n\taBypass = [\"RFP is not enabled\",\"canvas is not random per execution\"]\r\n\t//*/\r\n\r\n\t/* simulate mixed: 1 nonmatch\r\n\toPass = {\"7\": countTest - 33, \"11\": 32}\r\n\toFail = { \"274...lEQVQ4T5...VORK5CYII=\": 1 }\r\n\taBypass = []\r\n\t//*/\r\n\r\n\t/* simulate mixed: 1 nonmatch + bypasses\r\n\toPass = {\"7\": countTest - 33, \"11\": 32}\r\n\toFail = { \"274...lEQVQ4T5...VORK5CYII=\": 1 }\r\n\taBypass = [\"RFP is not enabled\",\"canvas is not random per execution\"]\r\n\t//*/\r\n\r\n\t/* simulate mixed: multi nonmatch\r\n\toPass = {\"7\": countTest - 33, \"11\": 25}\r\n\toFail = {\r\n\t\t\"274...lEQVQ4T5...VORK5CYII=\": 3,\r\n\t\t\"280...BANANANA...WOOOOWEEE=\": 5,\r\n\t}\r\n\taBypass = []\r\n\t//*/\r\n\r\n\t/* simulate mixed: multi exceed maxLines\r\n\toPass = {\"7\": countTest - 33, \"11\": 23} // 10 fails\r\n\toFail = {\r\n\t\t\"274...lEQVQ4T5...VORK5CYII=\": 1,\r\n\t\t\"280...BANANANA...WOOOOWEEE=\": 2,\r\n\t\t\"260...==TOR===...==BROWSER=\": 1, // maxLines\r\n\t\t\"226...TZPTZPTZ...TZPTZPTZP=\": 1, // sum the rest\r\n\t\t\"274...aEQVQ4T5...aORK5CYIa=\": 1,\r\n\t\t\"274...bEQVQ4T5...bORK5CYIb=\": 1,\r\n\t\t\"274...cEQVQ4T5...cORK5CYIc=\": 2,\r\n\t\t\"274...dEQVQ4T5...dORK5CYId=\": 1,\r\n\t}\r\n\tmaxLines = 3\r\n\t//*/\r\n\r\n\tlet countMatch = 0, // matches pattern\r\n\t\tcountPass = 0 // legit passes\r\n\tlet strBypass = \"\"\r\n\taBypass.forEach(function(item) {strBypass += \"<li>\"+ item +\"</li>\"})\r\n\tif (strBypass !== \"\") {strBypass = \"<ul>\"+ strBypass + \"</ul>\"}\r\n\r\n\tfor (const k of Object.keys(oPass)) {\r\n\t\tlet count = oPass[k]\r\n\t\tcountMatch += count\r\n\t\tlet rule = oControl[sizeW +\"x\" + sizeH][\"ruleNos\"][k]\r\n\t\tdisplay.push((\"RULE\"+ k).padEnd(6) + \" : \" + rule +\" : \"+ s9 + (oPass[k] +\"\").padStart(5) + sc)\r\n\t\t// update running match totals\r\n\t\tlet curTotal = oRunningTotal[\"rule\"+ (k +\"\").padStart(2,\"0\")][0]\r\n\t\tlet newTotal = curTotal + oPass[k]\r\n\t\toRunningTotal[\"rule\"+ (k +\"\").padStart(2,\"0\")] = [newTotal]\r\n\t\t// track LEGIT pass\r\n\t\tif (aBypass.length == 0) {countPass += oPass[k]}\r\n\t}\r\n\tlet percentMatch = ((countMatch/countTest) * 100).toFixed(2)\r\n\r\n\t// MATCHES\r\n\tif (countMatch > 0) {\r\n\t\tdisplay.push(s9 +\"\".padStart(39) + \"-----\" + sc)\r\n\t\tdisplay.push(s9 +\"TOTAL PATTERN MATCHES : \".padStart(39) + (countMatch +\"\").padStart(5) + sc)\r\n\t}\r\n\t// NON-MATCHES\r\n\tlet countFail = countTest - countMatch\r\n\tif (countFail > 0) {\r\n\t\tif (countMatch > 0) {display.push(\"<br>\")}\r\n\t\tif (percentMatch == 100) {percentMatch = ((countMatch/countTest) * 100).toFixed(3)} // ensure under 100.00\r\n\t\t// only display the first x lines, sum the rest\r\n\t\tlet countLine = 0, countRemainder = 0\r\n\t\tfor (const k of Object.keys(oFail)) {\r\n\t\t\tlet countItem = oFail[k]\r\n\t\t\tif (countLine < maxLines) {\r\n\t\t\t\tcountItem = countItem.toString()\r\n\t\t\t\tdisplay.push(k + sb + countItem.padStart(17) + sc)\r\n\t\t\t} else {\r\n\t\t\t\tcountRemainder += countItem\r\n\t\t\t}\r\n\t\t\tcountLine ++\r\n\t\t}\r\n\t\tif (countRemainder > 0) {\r\n\t\t\tlet keysRemaining = Object.keys(oFail).length - maxLines\r\n\t\t\tlet summary = keysRemaining +\" other\" + (keysRemaining > 1 ? \"s\" : \"\")\r\n\t\t\tdisplay.push(summary.padStart(39) + sb + (countRemainder +\"\").padStart(5) + sc)\r\n\t\t}\r\n\t\tdisplay.push(sb +\"\".padStart(39) + \"-----\" + sc)\r\n\t\tdisplay.push(sb +\"TOTAL NON-MATCHES : \".padStart(39) + (countFail +\"\").padStart(5) + sc)\r\n\r\n\t\t// START ANALYSIS\r\n\t\tdisplay.push(\"<br><hr><br>\"+ s9 +\"ANALYSIS: \"+ sc + sb +\"\\u2715 FAILED\"+ sc\r\n\t\t\t+\" | PATTERN MATCHES \"+ sb + percentMatch +\"%\"+ sc +\" | PASSES \"\r\n\t\t\t+ (countPass === 0 ? sb +\"ZERO\" : s9 + countPass) + sc\r\n\t\t)\r\n\t}\r\n\r\n\t// ANALYSIS\r\n\tif (countMatch == countTest) {\r\n\t\tif (aBypass.length) {\r\n\t\t\t// e.g. RFP is on, but has canvas site exception and an\r\n\t\t\t// extension matches RFP canvas but does not randomize per execution\r\n\t\t\tdisplay.push(\"<br><hr><br>\"+ s9 +\"ANALYSIS: \"+ sc + sb +\"\\u2715 FAILED\"+ sc\r\n\t\t\t\t+\" | PATTERN MATCHES \"+ s9 + percentMatch +\"%\"+ sc +\" | PASSES \"+ sb +\"ZERO\"+ sc)\r\n\t\t} else {\r\n\t\t\t// we missed some control rules?\r\n\t\t\tdisplay.push(\"<br><hr><br>\"+ s9 +\"ANALYSIS: \\u2713 NAILED\"+ sc\r\n\t\t\t\t+\" | PATTERN MATCHES \"+ s9 + percentMatch +\"%\"+ sc +\" | PASSES \"+ s9 + countTest + sc)\r\n\t\t}\r\n\t}\r\n\t// BYPASS INFO\r\n\tif (aBypass.length) {\r\n\t\tdisplay.push(\"<br>You \"+ sb + (countMatch == countTest ? \"\": \"also \") +\"failed\"+ sc +\" to pass \"\r\n\t\t\t+ (aBypass.length == 1 ? \"this basic check\" : \"these basic checks\") +\", so \"\r\n\t\t\t+ sb +\"no soup for you!\"+ sc +\"<br><br>\" + strBypass\r\n\t\t)\r\n\t}\r\n\t// OUTPUT\r\n\tdom.results.innerHTML = display.join(\"<br>\")\r\n\r\n\t// RUNNING TOTALS\r\n\trunningTotal += countTest\r\n\trunningPass += countPass\r\n\trunningMatch += countMatch\r\n\r\n\tlet percentTotalMatch = ((runningMatch/runningTotal) * 100).toFixed(2)\r\n\tlet percentTotalPass = ((runningPass/runningTotal) * 100).toFixed(2)\r\n\tlet strTotalMatch = (percentTotalMatch < 100 ? sb : s9 ) + runningMatch +\" [\"+ percentTotalMatch +\"%]\" + sc\r\n\tlet strTotalPass = (percentTotalPass < 100 ? sb : s9 ) + runningPass + \" [\"+ percentTotalPass +\"%]\"+ sc\r\n\tlet strTotalTests = s9 +\"RUNNING TOTALS: \"+ sc +\"TESTS \"+ s9 + runningTotal + sc\r\n\tlet strPatternInfo = \"<br><br>\\u25BC PATTERN MATCHES [\"+ sizeW +\"w x \"+ sizeH +\"h]<br>\"\r\n\tlet str = strTotalTests +\" | MATCHES \" + strTotalMatch +\" | PASSES \" + strTotalPass + strPatternInfo\r\n\tdisplay = [str]\r\n\r\n\tif (!hasRules) {display.push(\"note: there are no pattern rules for this size\")}\r\n\tlet displayTotal = []\r\n\r\n\tfor (let i=1; i < Object.keys(oRules).length +1; i++) {\r\n\t\tlet curTotal = oRunningTotal[\"rule\"+ (i +\"\").padStart(2,\"0\")][0]\r\n\t\tlet strTotal = curTotal +\"\"\r\n\t\tif (strTotal.length > 5) {strTotal = \"> 99999\"}\r\n\t\tstrTotal = strTotal.padStart(7)\r\n\t\tlet ruleNo = \"RULE\"+ (i +\"\").padEnd(2)\r\n\t\tstrTotal = curTotal == 0 ? ruleNo +\": \"+ strTotal : s14 + ruleNo + sc +\": \"+ strTotal\r\n\t\tdisplayTotal.push(strTotal)\r\n\t\tif (((i-1) % 4) == 3 || i == Object.keys(oRules).length) { // 4 per line plus final line\r\n\t\t\tdisplay.push(displayTotal.join(\" | \"))\r\n\t\t\tdisplayTotal = []\r\n\t\t}\r\n\t}\r\n\tdisplay.push(\"<br><hr>\")\r\n\tdom.totals.innerHTML = display.join(\"<br>\")\r\n}\r\n\r\nfunction set_size(width = 16, height = 16) {\r\n\t// call from console\r\n\t// even when we don't have rules, the non-match summaries give us the rule info\r\n\t// e.g. run_check(500000) (or a million)\r\n\tif (height > 16 || height < 2 || width > 16 || width < 2 ) {\r\n\t\tconsole.log(\"try again: width and height: max value 16, min value 2\")\r\n\t\treturn\r\n\t} else {\r\n\t\tconsole.log(\"setting sizes to \"+ width +\"w x \"+ height +\"h\")\r\n\t}\r\n\tsizeW = width\r\n\tsizeH = height\r\n\toRunningTotal = {}\r\n\trunningTotal = 0\r\n\trunningPass = 0\r\n\trunningMatch = 0\r\n}\r\n\r\npopulate_rules()\r\nsetTimeout(function() {\r\n\tPromise.all([\r\n\t\tget_globals()\r\n\t]).then(function(){\r\n\t\trun_checks(100)\r\n\t})\r\n}, 50)\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/canvasspoof.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>canvas spoof detection</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\t.togC {display: none;}\r\n\t\t.wordwrap {word-break: break-all; padding-right: 30px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#canvas\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb9\">\r\n\t\t<col width=\"20%\"><col width=\"20%\"><col width=\"60%\">\r\n\t\t<thead><tr><th colspan=\"3\">\r\n\t\t\t<div class=\"nav-title\">canvas spoof detection\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\r\n\t\t<tr><td colspan=\"3\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Sets canvas with known results and reads them back.<p>FF102+ only: \r\n\t\t\t<span class=\"s9\"><b> &#x2713; </b></span> means a match and a <span class=\"bad\"><b> &#x2715; </b></span> means you lied\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td colspan=\"2\" class=\"padr\"><div class=\"btn-left\"><span class=\"btn9 btn\" onClick=\"run()\">[ re-run ]</span></div>control</td>\r\n\t\t\t<td><canvas id=\"control1\" width=\"16\" height=\"16\" style=\"border:1px solid white;\"></canvas></td>\r\n\t\t</tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!-- spacer -->\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">getImageData</td><td class=\"c mono\" id=\"getImageData\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">toDataURL</td><td class=\"c mono\" id=\"toDataURL\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">toBlob</td><td class=\"c mono\" id=\"toBlob\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!-- spacer -->\r\n\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"s9\">------</td></tr> <!-- spacer -->\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!-- spacer -->\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">control</td><td>\r\n\t\t\t\t<canvas id=\"control2\" width=\"16\" height=\"16\" style=\"border:1px solid white;\"></canvas>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">isPointInPath</td><td class=\"c mono\" id=\"isPointInPath\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">isPointInStroke</td><td class=\"c mono\" id=\"isPointInStroke\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!-- spacer -->\r\n\t\t<tr><td colspan=\"3\" class=\"showhide\">\r\n\t\t\t<span id=\"labelC\" class=\"btnb\" onClick=\"togglerows('C','base64 details')\">&#9660; show base64 details</span></td></tr>\r\n\t\t<tr class=\"togC\"><td id=\"labelDataURL\" class=\"c padr\">toDataURL</td><td colspan=\"2\" class=\"c mono wordwrap\" id=\"rawDataURL\"></td></tr>\r\n\t\t<tr class=\"togC\"><td id=\"labelBlob\" class=\"c padr\">toBlob</td><td colspan=\"2\" class=\"c mono wordwrap\" id=\"rawBlob\"></td></tr>\r\n\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet canvasTDU = dom.control1, base64DTU\r\n// //https://stackoverflow.com/questions/37992117/how-to-get-image-color-mode-cmyk-rgb-in-javascript\r\n\t// ^ can I use this to rea the chunks and remove comment fields\r\n\r\nfunction test(b64 = '', sliceSize = 512) {\r\n\t// https://stackoverflow.com/questions/16968945/convert-base64-png-data-to-javascript-file-objects\r\n\t// https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript\r\n\t/*\r\n\t\tthe 'file' variable gives me insight into hexidecimal? e.g. PNG ... IEND\r\n\t\thttps://en.wikipedia.org/wiki/PNG#File_format\r\n\t*/\r\n\tif ('' == b64) {\r\n\t\tb64 = base64DTU\r\n\t\tif (undefined == base64DTU) {return}\r\n\t} \r\n\r\n\ttry {\r\n\t\t//foo++\r\n\t\tlet png = b64.split(',')[1]\r\n\t\tconsole.log('input\\n-----\\n', png)\r\n\t\tconst byteCharacters = window.atob(png)\r\n\t\tconst byteArrays = []\r\n\t\tfor (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {\r\n\t\t\tconst slice = byteCharacters.slice(offset, offset + sliceSize)\r\n\t\t\tconst byteNumbers = new Array(slice.length)\r\n\t\t\tfor (let i = 0; i < slice.length; i++) {\r\n\t\t\t\tbyteNumbers[i] = slice.charCodeAt(i)\r\n\t\t\t}\r\n\t\t\tconst byteArray = new Uint8Array(byteNumbers)\r\n\t\t\tbyteArrays.push(byteArray)\r\n\t\t}\r\n\t\tconsole.log('byteArrays\\n-----\\n', byteArrays)\r\n\r\n\t\t// this doesn't tell me anything as they always match\r\n\t\tlet file = new Blob([window.atob(png)],  {type: 'image/png', encoding: 'utf-8'})\r\n\t\tconsole.log('file\\n-----\\n',file)\r\n\t\tlet fr = new FileReader()\r\n\t\tfr.onload = function (oFREvent) {\r\n\t\t\ttry {\r\n\t\t\t//foo++\r\n\t\t\tvar v = oFREvent.target.result.split(',')[1]; // encoding is messed up here, so we fix it\r\n\t\t\tv = atob(v)\r\n\t\t\tconsole.log('atob\\n-----\\n',v)\r\n\t\t\tlet good_b64 = btoa(decodeURIComponent(escape(v)))\r\n\t\t\tconsole.log('match', good_b64 == png)\r\n\t\t\t//'data:image/png;base64,'+ good_b64\r\n\t\t\t} catch(e) {\r\n\t\t\t\tconsole.log(e)\r\n\t\t\t}\r\n\t\t}\r\n\t\tfr.readAsDataURL(file)\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t} \r\n}\r\n\r\nfunction analyse(data, type) {\r\n\tif ('DataURL' == type) {base64DTU = data}\r\n\r\n\tlet len = data.length,\r\n\t\tslice1 = data.slice(69,70),\r\n\t\tslice2 = data.slice(70,72),\r\n\t\tslice3 = data.slice(72,80),\r\n\t\tslice4 = data.slice(data.length - 10, data.length)\r\n\tlet element = document.getElementById(\"raw\"+ type)\r\n\tdocument.getElementById(\"label\"+ type).innerHTML = \"to\"+ type\r\n\r\n\tif (slice1 == \"A\" && len > 161 && len < 191) {\r\n\t\tif (slice3 == \"lEQVQ4jW\" || slice3 == \"lEQVQ4T2\") {\r\n\t\t\tif (slice4 == \"5ErkJggg==\" || slice4 == \"VORK5CYII=\" || slice4 == \"lFTkSuQmCC\") {\r\n\t\t\t\telement.innerHTML = data.slice(0,69) + s14 + slice1 + sc + slice2 + s14 + slice3 + sc\r\n\t\t\t\t\t+ data.slice(80, len - 10) + s14 + slice4 + sc + s9 +\" [\"+ len +\"]\"+ sc\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telement.innerHTML = data + s9 +\" [\"+ len +\"]\"+ sc\r\n}\r\n\r\nfunction run() {\r\n\t// clear\r\n\tlet items = document.getElementsByClassName(\"c\")\r\n\tfor(let i=0; i < items.length; i++) {\r\n\t\titems[i].innerHTML = \"&nbsp\"\r\n\t}\r\n\t// vars\r\n\tlet oKnown = {\r\n\t\t// also FF137nightly: 1910796: Enable libz-rs on nightly: this changes our known hashes\r\n\t\tgetImageData: [\r\n\t\t\t'c7f2099a',\r\n\t\t],\r\n\t\ttoDataURL: [\r\n\t\t\t'a8d0bd06',\r\n\t\t\t'ef03b7d0', // 137+ libz-rs\r\n\t\t],\r\n\t\ttoBlob: [\r\n\t\t\t'a8d0bd06',\r\n\t\t\t'ef03b7d0', // 137+ libz-rs\r\n\t\t],\r\n\t\tisPointInPath: [\r\n\t\t\t'2f4eafe2',\r\n\t\t],\r\n\t\tisPointInStroke: [\r\n\t\t\t'8722c710',\r\n\t\t],\r\n\t}\r\n\tlet oSuccess = {\r\n\t\tgetImageData: false,\r\n\t\ttoDataURL: false,\r\n\t\ttoBlob: false,\r\n\t\tisPointInPath: false,\r\n\t\tisPointInStroke: false,\r\n\t}\r\n\r\n\tlet sizeW = 16, sizeH = 8\r\n\tdom.control1.width = sizeW\r\n\tdom.control1.height = sizeH\r\n\tdom.control2.width = sizeW\r\n\tdom.control2.height = sizeH\r\n\r\n\tvar known = {\r\n\t\tcreateHashes: function(window){\r\n\t\t\tlet outputs = [\r\n\t\t\t\t{\r\n\t\t\t\t\tname: \"toDataURL\",\r\n\t\t\t\t\tvalue: function(){\r\n\t\t\t\t\t\tlet data = getKnown().canvas.toDataURL()\r\n\t\t\t\t\t\tlet hash = mini(data)\r\n\t\t\t\t\t\tanalyse(data, \"DataURL\")\r\n\t\t\t\t\t\toSuccess[\"toDataURL\"] = true\r\n\t\t\t\t\t\treturn hash\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tname: \"toBlob\",\r\n\t\t\t\t\tvalue: function(){\r\n\t\t\t\t\t\treturn new Promise(function(resolve, reject){\r\n\t\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\t\tvar timeout = window.setTimeout(function(){\r\n\t\t\t\t\t\t\t\t\treject(\"timeout\")\r\n\t\t\t\t\t\t\t\t}, 750)\r\n\t\t\t\t\t\t\t\tgetKnown().canvas.toBlob(function(blob){\r\n\t\t\t\t\t\t\t\t\twindow.clearTimeout(timeout)\r\n\t\t\t\t\t\t\t\t\tvar reader = new FileReader()\r\n\t\t\t\t\t\t\t\t\treader.onload = function(){\r\n\t\t\t\t\t\t\t\t\t\tlet data = reader.result\r\n\t\t\t\t\t\t\t\t\t\tlet hash = mini(data)\r\n\t\t\t\t\t\t\t\t\t\tanalyse(data, \"Blob\")\r\n\t\t\t\t\t\t\t\t\t\toSuccess[\"toBlob\"] = true\r\n\t\t\t\t\t\t\t\t\t\tresolve(hash)\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\treader.onerror = function(){\r\n\t\t\t\t\t\t\t\t\t\treject(\"error\")\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\treader.readAsDataURL(blob)\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t} catch (e){\r\n\t\t\t\t\t\t\t\tresolve(e.name)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\r\n\t\t\t\t\tname: \"getImageData\",\r\n\t\t\t\t\tvalue: function(){\r\n\t\t\t\t\t\tvar context = getKnown()\r\n\t\t\t\t\t\tlet imageData = context.getImageData(0,0,sizeW, sizeH)\r\n\t\t\t\t\t\tlet data = mini(imageData.data)\r\n\t\t\t\t\t\toSuccess[\"getImageData\"] = true\r\n\t\t\t\t\t\treturn data\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\r\n\t\t\t\t\tname: \"isPointInPath\",\r\n\t\t\t\t\tvalue: function(){\r\n\t\t\t\t\t\tlet context2 = getKnownPath()\r\n\t\t\t\t\t\tlet pathData = []\r\n\t\t\t\t\t\tfor (let x = 0; x < sizeW; x++){\r\n\t\t\t\t\t\t\tfor (let y = 0; y < sizeH; y++){\r\n\t\t\t\t\t\t\t\tpathData.push(context2.isPointInPath(x, y))\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\toSuccess[\"isPointInPath\"] = true\r\n\t\t\t\t\t\treturn mini(pathData.join())\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\tclass: window.CanvasRenderingContext2D,\r\n\t\t\t\t\tname: \"isPointInStroke\",\r\n\t\t\t\t\tvalue: function(){\r\n\t\t\t\t\t\tlet context2 = getKnownPath()\r\n\t\t\t\t\t\tlet pathStroke = []\r\n\t\t\t\t\t\tfor (let x = 0; x < sizeW; x++){\r\n\t\t\t\t\t\t\tfor (let y = 0; y < sizeH; y++){\r\n\t\t\t\t\t\t\t\tpathStroke.push(context2.isPointInStroke(x, y))\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\toSuccess[\"isPointInStroke\"] = true\r\n\t\t\t\t\t\treturn mini(pathStroke.join())\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t];\r\n\t\t\tfunction isSupported(output){\r\n\t\t\t\treturn !!(output.class? output.class: window.HTMLCanvasElement).prototype[output.name]\r\n\t\t\t}\r\n\t\t\tfunction getKnown(){\r\n\t\t\t\tlet canvas = document.getElementById(\"control1\")\r\n\t\t\t\tlet ctx = canvas.getContext('2d')\r\n\t\t\t\t//let aPixels = []\r\n\t\t\t\tfor (let x=0; x < sizeW; x++) {\r\n\t\t\t\t\tfor (let y=0; y < sizeH; y++) {\r\n\t\t\t\t\t\t//aPixels.push(\"rgba(\" + (x*y) +\",\"+ (x*16) +\",\"+ (y*16) +\",255)\")\r\n\t\t\t\t\t\tctx.fillStyle = \"rgba(\" + (x*y) +\",\"+ (x*16) +\",\"+ (y*16) +\",255)\"\r\n\t\t\t\t\t\tctx.fillRect(x, y, 1, 1)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t//console.log(aPixels)\r\n\t\t\t\treturn ctx\r\n\t\t\t}\r\n\t\t\tfunction getKnownPath(){\r\n\t\t\t\tlet canvas2 = document.getElementById(\"control2\")\r\n\t\t\t\tlet ctx2 = canvas2.getContext('2d')\r\n\t\t\t\tctx2.fillStyle = \"rgba(255,255,255,255)\"\r\n\t\t\t\tctx2.beginPath()\r\n\t\t\t\tctx2.rect(2,5,8,7)\r\n\t\t\t\tctx2.closePath()\r\n\t\t\t\tctx2.fill()\r\n\t\t\t\treturn ctx2\r\n\t\t\t}\r\n\t\t\tvar finished = Promise.all(outputs.map(function(output){\r\n\t\t\t\treturn new Promise(function(resolve, reject){\r\n\t\t\t\t\tvar displayValue\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tvar supported = output.supported? output.supported(): isSupported(output);\r\n\t\t\t\t\t\tif (supported){\r\n\t\t\t\t\t\t\tdisplayValue = output.value()\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tdisplayValue = \"error\"\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} catch (e){\r\n\t\t\t\t\t\tdisplayValue = \"error\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\tPromise.resolve(displayValue).then(function(displayValue){\r\n\t\t\t\t\t\toutput.displayValue = displayValue\r\n\t\t\t\t\t\tresolve(output)\r\n\t\t\t\t\t}, function(e){\r\n\t\t\t\t\t\toutput.displayValue = e.name\r\n\t\t\t\t\t\tresolve(output)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t}))\r\n\t\t\treturn finished\r\n\t\t}\r\n\t}\r\n\r\n\t// pause so users see change\r\n\tsetTimeout(function(){\r\n\t\t// vars\r\n\t\tlet t0 = performance.now(),\r\n\t\t\tresults = []\r\n\t\t// get results\r\n\t\tPromise.all([\r\n\t\t\tknown.createHashes(window),\r\n\t\t]).then(function(item){\r\n\t\t\titem[0].forEach(function(data){\r\n\t\t\t\tlet name = data.name\r\n\t\t\t\tlet value = data.displayValue\r\n\t\t\t\tif (oSuccess[name] == true) {\r\n\t\t\t\t\tif (isVer > 101) {\r\n\t\t\t\t\t\tvalue += (oKnown[name].includes(value) ? green_tick : red_cross)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tdom[name].innerHTML = value\r\n\t\t\t})\r\n\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t})\r\n\t}, 250)\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tPromise.all([\r\n\t\tget_isVer()\r\n\t]).then(function(){\r\n\t\trun()\r\n\t})\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/chrome.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=800\">\n\t<title>chrome://</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 780px;}\n\t\t.togA, .togC {display: none;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#feature\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<!-- Tor Browser -->\n\t<table id=\"tb3\">\n\t\t<col width=\"20%\"><col width=\"80%\">\n\t\t<thead><tr><th colspan=\"2\">chrome:// & resource://</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">Enumeration of web accessible resources. \n\t\t\t\t<span class=\"s3\">Firefox</span> list compiled from 128ESR + FF132.\n\t\t\t\t<span class=\"s3\">Tor Browser</span> list compiled from 14.0 (128ESR).\n\t\t\t\tClick counts to display details.\n\t\t\t</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"no_color\">code based on work by\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://privacycheck.sec.lrz.de/active/fp_cd/fp_chrome_detect.html\">privacycheck.sec.lrz.de</a>,\n\t\t\tupdated lists thanks to <a target=\"_blank\" class=\"blue\" href=\"https://github.com/cypherpunks11\">cypherpunks11</a></span>\n\t\t</td></tr>\n\n\t\t<tr><td colspan=\"2\"><hr><br>TOR BROWSER</td></tr>\n\t\t<tr><td class=\"padr\"><div>\n\t\t\t\t<div class=\"btn-left\"><span class=\"btn3 btn\" id=\"labelTor\" onClick=\"run(`Tor`)\">[ run ]</span></div>\n\t\t\t\t<div>images</div></div></td>\n\t\t\t<td class=\"c mono\" id=\"imgHashTor\"></td></tr>\n\t\t<tr><td class=\"padr\">js</td><td class=\"c mono\" id=\"jsHashTor\"></td></tr>\n\t\t<tr><td class=\"padr\">css</td><td class=\"c mono\" id=\"cssHashTor\"></td></tr>\n\t\t<tr><td class=\"padr\">other</td><td class=\"c mono\" id=\"otherHashTor\"></td></tr>\n\t\t<tr><td class=\"padr\">combined</td></td><td class=\"c mono\" id=\"allHashTor\"></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"showhide\"><span id=\"labelA\" class=\"btnb\" onClick=\"togglerows('A')\">&#9660; show details</span></td></tr>\n\t\t<tr class=\"togA\">\n\t\t <td class=\"padr\"><span class=\"btn0 btn\" onClick=\"copyclip(`detailTor`)\">[ copy ]</span> results</td>\n\t\t <td class=\"c\" id=\"detailTor\"></td>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"><hr><br>FIREFOX</td></tr>\n\t\t<tr><td class=\"padr\"><div>\n\t\t\t\t<div class=\"btn-left\"><span class=\"btn3 btn\" id=\"labelFF\" onClick=\"run(`FF`)\">[ run ]</span></div>\n\t\t\t\t<div>images</div></div></td>\n\t\t\t<td class=\"c mono\" id=\"imgHashFF\"></td></tr>\n\t\t<tr><td class=\"padr\">js</td><td class=\"c mono\" id=\"jsHashFF\"></td></tr>\n\t\t<tr><td class=\"padr\">css</td><td class=\"c mono\" id=\"cssHashFF\"></td></tr>\n\t\t<tr><td class=\"padr\">other</td><td class=\"c mono\" id=\"otherHashFF\"></td></tr>\n\t\t<tr><td class=\"padr\">combined</td></td><td class=\"c mono\" id=\"allHashFF\"></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"showhide\"><span id=\"labelC\" class=\"btnb\" onClick=\"togglerows('C')\">&#9660; show details</span></td></tr>\n\t\t<tr class=\"togC\">\n\t\t <td class=\"padr\"><span class=\"btn0 btn\" onClick=\"copyclip(`detailFF`)\">[ copy ]</span> results</td>\n\t\t <td class=\"c\" id=\"detailFF\"></td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nfunction display(type, target) {\n\tlet element = document.getElementById(\"detail\"+ target)\n\tlet array = sDetail[type + target]\n\telement.innerHTML = array.join(\"<br>\")\n}\n\nfunction run(runtype) {\n\t// FF only\n\tif (!isFF) {\n\t\treturn\n\t}\n\n\t// cosmetics\n\tdocument.getElementById(\"imgHash\"+ runtype).innerHTML = \"&nbsp\"\n\tdocument.getElementById(\"allHash\"+ runtype).innerHTML = \"&nbsp\"\n\tdocument.getElementById(\"otherHash\"+ runtype).innerHTML = \"&nbsp\"\n\tdocument.getElementById(\"detail\"+ runtype).innerHTML = \"&nbsp\"\n\tdocument.getElementById(\"jsHash\"+ runtype).innerHTML = \"tests are running\"\n\tdocument.getElementById(\"cssHash\"+ runtype).innerHTML = \"please wait\"\n\n\t// set list to use\n\tlet list = runtype == \"FF\" ? listFF : listTor\n\n\t// build Uris\n\tlet jsUris = list.match(/(chrome|resource):.+\\.(js|jsm|mjs)$/gm).sort()\n\tlet imgUris = list.match(/(chrome|resource):.+\\.(svg|png|jpg|gif|ico|webp|avif)$/gm).sort()\n\tlet cssUris = list.match(/(chrome|resource):.+\\.css$/gm).sort()\n\tlet otherUris = list.match(/(chrome|resource):.+\\.(dtd|map|scss|eot|otf|ttf|woff|woff2|template|handlebars)$/gm)?.sort() ?? []\n\n\t// VARS\n\tlet allHash = [],\tjsHash = [], imgHash = [], cssHash = [], otherHash = [] \n\n\t// JS\n\tconst get_js = () => new Promise(resolve => {\n\t\tlet expected = jsUris.length, count = 0\n\t\tjsUris.forEach(function(src) {\n\t\t\tlet script = document.createElement('script')\n\t\t\tscript.src = src\n\t\t\tdocument.head.appendChild(script)\n\t\t\tscript.onload = function() {\n\t\t\t\tjsHash.push(src)\n\t\t\t\tallHash.push(src)\n\t\t\t\tcount++\n\t\t\t\tif (count == expected) {return resolve()}\n\t\t\t};\n\t\t\tscript.onerror = function() {\n\t\t\t\tcount++\n\t\t\t\tif (count == expected) {return resolve()}\n\t\t\t};\n\t\t\tdocument.head.removeChild(script)\n\t\t})\n\t})\n\t// IMAGES\n\tconst get_img = () => new Promise(resolve => {\n\t\tlet expected = imgUris.length, count = 0\n\t\timgUris.forEach(function(imgUri) {\n\t\t\tlet img = document.createElement(\"img\");\n\t\t\timg.src = imgUri\n\t\t\timg.style.height = \"20px\"\n\t\t\timg.style.width = \"20px\"\n\t\t\timg.onload = function() {\n\t\t\t\timgHash.push(imgUri)\n\t\t\t\tallHash.push(imgUri)\n\t\t\t\tcount++\n\t\t\t\tif (count == expected) {return resolve()}\n\t\t\t}\n\t\t\timg.onerror = function() {\n\t\t\t\tcount++\n\t\t\t\tif (count == expected) {return resolve()}\n\t\t\t};\n\t\t})\n\t})\n\t// CSS\n\t\t// FF121+: 1855861: we need to promise events\n\tlet countCSS = 0, expectedCSS = 0\n\tconst get_eventCSS = (css, cssUri) => new Promise(resolve => {\n\t\tcss.onload = function() {\n\t\t\tcountCSS++\n\t\t\tcssHash.push(cssUri)\n\t\t\tallHash.push(cssUri)\n\t\t\tdocument.head.removeChild(css)\n\t\t\treturn resolve(countCSS)\n\t\t}\n\t\tcss.onerror = function() {\n\t\t\tcountCSS++\n\t\t\tdocument.head.removeChild(css)\n\t\t\treturn resolve(countCSS)\n\t\t}\n\t})\n\tconst get_css = () => new Promise(resolve => {\n\t\texpectedCSS = cssUris.length\n\t\tcountCSS = 0\n\t\tlet count = 0\n\t\tcssUris.forEach(function(cssUri) {\n\t\t\tlet css = document.createElement(\"link\")\n\t\t\tcss.href = cssUri\n\t\t\tcss.type = \"text/css\"\n\t\t\tcss.rel = \"stylesheet\"\n\t\t\tdocument.head.appendChild(css, cssUri)\n\t\t\t// FF120+ use promises\n\t\t\tif (window.hasOwnProperty(\"UserActivation\")) {\n\t\t\t\tPromise.all([\n\t\t\t\t\tget_eventCSS(css, cssUri)\n\t\t\t\t]).then(function(){\n\t\t\t\t\tif (countCSS == expectedCSS) {return resolve()}\n\t\t\t\t})\n\t\t\t} else {\ndom.cssHashTor.innerHTML = \"i am groot\"\n\t\t\t\tcss.onload = function() {\n\t\t\t\t\tcssHash.push(cssUri)\n\t\t\t\t\tallHash.push(cssUri)\n\t\t\t\t\tcount++\n\t\t\t\t\tif (count == expectedCSS) {return resolve()}\n\t\t\t\t};\n\t\t\t\tcss.onerror = function() {\n\t\t\t\t\tcount++\n\t\t\t\t\tif (count == expectedCSS) {return resolve()}\n\t\t\t\t};\n\t\t\t\tdocument.head.removeChild(css)\n\t\t\t}\n\t\t})\n\t})\n\t// OTHER\n\tlet countOther = 0, expectedOther = 0\n\tconst get_eventOther = (css, otherUri) => new Promise(resolve => {\n\t\tcss.onload = function() {\n\t\t\tcountOther++\n\t\t\totherHash.push(otherUri)\n\t\t\tallHash.push(otherUri)\n\t\t\tdocument.head.removeChild(css)\n\t\t\treturn resolve()\n\t\t}\n\t\tcss.onerror = function() {\n\t\t\tcountOther++\n\t\t\tdocument.head.removeChild(css)\n\t\t\treturn resolve()\n\t\t}\n\t})\n\n\tconst get_other = () => new Promise(resolve => {\n\t\texpectedOther = otherUris.length\n\t\tif (expectedOther == 0) {\n\t\t\treturn resolve()\n\t\t}\n\t\tcountOther = 0\n\t\tlet count = 0\n\t\totherUris.forEach(function(otherUri) {\n\t\t\tlet other = document.createElement(\"link\")\n\t\t\tother.href = otherUri\n\t\t\tother.rel = \"stylesheet\"\n\t\t\tdocument.head.appendChild(other)\n\t\t\t// FF120+ use promises\n\t\t\tif (window.hasOwnProperty(\"UserActivation\")) {\n\t\t\t\tPromise.all([\n\t\t\t\t\tget_eventOther(other, otherUri)\n\t\t\t\t]).then(function(){\n\t\t\t\t\tif (countOther == expectedOther) {return resolve()}\n\t\t\t\t})\n\t\t\t} else {\ndom.otherHashTor.innerHTML = \"i am groot\"\n\t\t\t\tother.onload = function() {\n\t\t\t\t\totherHash.push(otherUri)\n\t\t\t\t\tallHash.push(otherUri)\n\t\t\t\t\tcount++\n\t\t\t\t\tif (count == expectedOther) {return resolve()}\n\t\t\t\t};\n\t\t\t\tother.onerror = function() {\n\t\t\t\t\tcount++\n\t\t\t\t\tif (count == expectedOther) {return resolve()}\n\t\t\t\t};\n\t\t\t\tdocument.head.removeChild(other)\n\t\t\t}\n\t\t})\n\t})\n\n\tfunction output() {\n\t\t// CONSOLE\n\t\tlet note = (runtype == \"FF\" ? \"Firefox\" : \"Tor Browser\")\n\n\t\tconsole.debug(note + \" items\",\n\t\t\t\"\\nIMG\", imgUris,\n\t\t\t\"\\nJS\", jsUris,\n\t\t\t\"\\nCSS\", cssUris,\n\t\t\t\"\\nOTHER\", otherUris,\n\t\t)\n\t\t// sort\n\t\timgHash.sort()\n\t\tjsHash.sort()\n\t\tcssHash.sort()\n\t\totherHash.sort()\n\t\tallHash.sort()\n\t\t// counts\n\t\tlet foundI = imgHash.length,\n\t\t\tfoundJ = jsHash.length,\n\t\t\tfoundC = cssHash.length,\n\t\t\tfoundO = otherHash.length,\n\t\t\tfoundA = allHash.length\n\t\t// hashes\n\t\tlet hashI = mini(imgHash),\n\t\t\thashJ = mini(jsHash),\n\t\t\thashC = mini(cssHash),\n\t\t\thashO = mini(otherHash),\n\t\t\thashA = mini(allHash)\n\t\t// remember data\n\t\tsDetail[\"img\"+ runtype] = imgHash\n\t\tsDetail[\"js\"+ runtype] = jsHash\n\t\tsDetail[\"css\"+ runtype] = cssHash\n\t\tsDetail[\"other\"+ runtype] = otherHash\n\t\t// remember ordered all data\n\t\tlet tmpArray = []\n\t\tif (foundI) {tmpArray.push(s3 +\"--- img ---\"+ sc); tmpArray = tmpArray.concat(imgHash)}\n\t\tif (foundJ) {tmpArray.push(s3 +\"--- js ---\"+ sc); tmpArray = tmpArray.concat(jsHash)}\n\t\tif (foundC) {tmpArray.push(s3 +\"--- css ---\"+ sc); tmpArray = tmpArray.concat(cssHash)}\n\t\tif (foundO) {tmpArray.push(s3 +\"--- other ---\"+ sc); tmpArray = tmpArray.concat(otherHash)}\n\t\tsDetail[\"all\"+ runtype] = tmpArray\n\n\t\t// item output\n\t\tlet stats = \" [\"+ foundI +\"/\"+ imgUris.length +\"]\"\n\t\tif (foundI) {stats = \" <span class='btn3 btnc' onclick='display(`img`,`\"+ runtype +\"`)'>\" + stats +\"</span>\"}\n\t\tdocument.getElementById(\"imgHash\"+ runtype).innerHTML = hashI + stats\n\t\t\tstats = \" [\"+ foundJ +\"/\"+ jsUris.length +\"]\"\n\t\tif (foundJ) {stats = \" <span class='btn3 btnc' onclick='display(`js`,`\"+ runtype +\"`)'>\" + stats +\"</span>\"}\n\t\tdocument.getElementById(\"jsHash\"+ runtype).innerHTML = hashJ + stats\n\t\t\tstats = \" [\"+ foundC +\"/\"+ cssUris.length +\"]\"\n\t\tif (foundC) {stats = \" <span class='btn3 btnc' onclick='display(`css`,`\"+ runtype +\"`)'>\" + stats +\"</span>\"}\n\t\tdocument.getElementById(\"cssHash\"+ runtype).innerHTML = hashC + stats\n\t\t\tstats = \" [\"+ foundO +\"/\"+ otherUris.length +\"]\"\n\t\tif (foundO) {stats = \" <span class='btn3 btnc' onclick='display(`other`,`\"+ runtype +\"`)'>\" + stats +\"</span>\"}\n\t\tdocument.getElementById(\"otherHash\"+ runtype).innerHTML = hashO + stats\n\t\t\tlet countTested = imgUris.length + jsUris.length + cssUris.length + otherUris.length\n\t\tstats = \" [\"+ foundA +\"/\"+ countTested +\"]\"\n\t\tif (foundA) {stats = \" <span class='btn3 btnc' onclick='display(`all`,`\"+ runtype +\"`)'>\" + stats +\"</span>\"}\n\t\tdocument.getElementById(\"allHash\"+ runtype).innerHTML = mini(hashA) + stats\n\n\t\t// label\n\t\tdocument.getElementById(\"label\" + runtype).innerHTML = \"[ re-run ]\"\n\t}\n\n\tPromise.all([\n\t\tget_js(),\n\t\tget_img(),\n\t\tget_css(),\n\t\tget_other(),\n\t]).then(function(results){\n\t\toutput()\n\t})\n}\n\nget_globals()\n\n\t// listFF: removed: chrome://global/content/aboutServiceWorkers.js\n\t\t// this causes errors that prevents onClick function\n\n\tlet listFF = `\n\n\t\tchrome://activity-stream/content/css/activity-stream.css\n\t\tchrome://activity-stream/content/data/content/assets/confetti.svg\n\t\tchrome://activity-stream/content/data/content/assets/device-migration.svg\n\t\tchrome://activity-stream/content/data/content/assets/euo-chatbot.svg\n\t\tchrome://activity-stream/content/data/content/assets/euo-tab-orientation.svg\n\t\tchrome://activity-stream/content/data/content/assets/firefox.svg\n\t\tchrome://activity-stream/content/data/content/assets/fox-doodle-tail.png\n\t\tchrome://activity-stream/content/data/content/assets/fox-doodle-waving-static.png\n\t\tchrome://activity-stream/content/data/content/assets/fox-doodle-waving.gif\n\t\tchrome://activity-stream/content/data/content/assets/glyph-cfr-feature-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-info-critical-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-mail-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-maximize-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-minimize-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-modal-delete-20.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-newWindow-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-open-file-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-pin-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-pocket-archive-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-pocket-delete-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-unpin-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/glyph-webextension-16.svg\n\t\tchrome://activity-stream/content/data/content/assets/heart.webp\n\t\tchrome://activity-stream/content/data/content/assets/icon-removed-bookmark.svg\n\t\tchrome://activity-stream/content/data/content/assets/long-zap.svg\n\t\tchrome://activity-stream/content/data/content/assets/mobile-download-qr-existing-user-cn.svg\n\t\tchrome://activity-stream/content/data/content/assets/mobile-download-qr-existing-user.svg\n\t\tchrome://activity-stream/content/data/content/assets/mobile-download-qr-new-user-cn.svg\n\t\tchrome://activity-stream/content/data/content/assets/mobile-download-qr-new-user.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-amo-collection.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-gratitude.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-import.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-mobilecrosspromo.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-pinprivate.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-pintaskbar.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-privacysegmentation.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-rtamo-background-image.svg\n\t\tchrome://activity-stream/content/data/content/assets/mr-settodefault.svg\n\t\tchrome://activity-stream/content/data/content/assets/noodle-C.svg\n\t\tchrome://activity-stream/content/data/content/assets/noodle-outline-L.svg\n\t\tchrome://activity-stream/content/data/content/assets/noodle-solid-L.svg\n\t\tchrome://activity-stream/content/data/content/assets/nuo-taborientation.svg\n\t\tchrome://activity-stream/content/data/content/assets/person-typing.svg\n\t\tchrome://activity-stream/content/data/content/assets/pocket-onboarding.avif\n\t\tchrome://activity-stream/content/data/content/assets/pocket-onboarding@2x.avif\n\t\tchrome://activity-stream/content/data/content/assets/pocket-swoosh.svg\n\t\tchrome://activity-stream/content/data/content/assets/remote/mountain.svg\n\t\tchrome://activity-stream/content/data/content/assets/remote/umbrella.png\n\t\tchrome://activity-stream/content/data/content/assets/short-zap.svg\n\t\tchrome://activity-stream/content/data/content/assets/spinner.svg\n\t\tchrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg\n\t\tchrome://activity-stream/content/data/content/assets/tabs-side-zap-transparent.svg\n\t\tchrome://activity-stream/content/data/content/assets/tabs-top-zap-transparent.svg\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-beach.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-landscape.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-panda.avif\n\t\tchrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/adidas.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/aliexpress-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/allegro-pl.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/amazon.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/avito-ru.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/baidu-com.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/bbc-uk.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/bing-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/ctrip-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/duckduckgo-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/ebay.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/etsy.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/facebook-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/geico.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/google-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/hrblock.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/ifeng-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/iqiyi-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/leboncoin-fr.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/nike.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/ok-ru.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/olx-pl.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/reddit-com.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/samsung.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/turbotax.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/twitter-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/vk-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/vodafone.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/weibo-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/wikipedia-org.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/wix.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/wykop-pl.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/yandex-com.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/yandex-ru.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/youtube-com.png\n\t\tchrome://activity-stream/content/data/content/tippytop/favicons/zhihu-com.ico\n\t\tchrome://activity-stream/content/data/content/tippytop/images/adidas@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/aliexpress-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/allegro-pl@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/amazon@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/avito-ru@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/baidu-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/bbc-uk@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/bing-com@2x.svg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/ctrip-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/duckduckgo-com@2x.svg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/ebay@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/etsy@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/facebook-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/geico@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/google-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/hrblock@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/ifeng-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/iqiyi-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/leboncoin-fr@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/nike@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/ok-ru@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/olx-pl@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/reddit-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/samsung@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/turbotax@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/twitter-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/vk-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/vodafone@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/weibo-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/wikipedia-org@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/wix@2x.jpg\n\t\tchrome://activity-stream/content/data/content/tippytop/images/wykop-pl@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/yandex-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/yandex-ru@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/youtube-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/images/zhihu-com@2x.png\n\t\tchrome://activity-stream/content/data/content/tippytop/top_sites.json\n\t\tchrome://branding/content/about-logo-private.png\n\t\tchrome://branding/content/about-logo-private@2x.png\n\t\tchrome://branding/content/about-logo.png\n\t\tchrome://branding/content/about-logo.svg\n\t\tchrome://branding/content/about-logo@2x.png\n\t\tchrome://branding/content/about-wordmark.svg\n\t\tchrome://branding/content/about.png\n\t\tchrome://branding/content/aboutDialog.css\n\t\tchrome://branding/content/document.ico\n\t\tchrome://branding/content/document_pdf.svg\n\t\tchrome://branding/content/favicon32.png\n\t\tchrome://branding/content/favicon64.png\n\t\tchrome://branding/content/firefox-wordmark.svg\n\t\tchrome://branding/content/icon128.png\n\t\tchrome://branding/content/icon16.png\n\t\tchrome://branding/content/icon32.png\n\t\tchrome://branding/content/icon48.png\n\t\tchrome://branding/content/icon64.png\n\t\tchrome://branding/locale/brand.properties\n\t\tchrome://browser/content/aboutDialog-appUpdater.js\n\t\tchrome://browser/content/aboutDialog.css\n\t\tchrome://browser/content/aboutDialog.js\n\t\tchrome://browser/content/aboutDialog.xhtml\n\t\tchrome://browser/content/aboutFrameCrashed.html\n\t\tchrome://browser/content/aboutPrivateBrowsing.css\n\t\tchrome://browser/content/aboutPrivateBrowsing.html\n\t\tchrome://browser/content/aboutPrivateBrowsing.js\n\t\tchrome://browser/content/aboutRestartRequired.js\n\t\tchrome://browser/content/aboutRestartRequired.xhtml\n\t\tchrome://browser/content/aboutRobots-icon.png\n\t\tchrome://browser/content/aboutRobots.css\n\t\tchrome://browser/content/aboutRobots.js\n\t\tchrome://browser/content/aboutRobots.xhtml\n\t\tchrome://browser/content/aboutSessionRestore.js\n\t\tchrome://browser/content/aboutSessionRestore.xhtml\n\t\tchrome://browser/content/aboutTabCrashed.css\n\t\tchrome://browser/content/aboutTabCrashed.js\n\t\tchrome://browser/content/aboutTabCrashed.xhtml\n\t\tchrome://browser/content/aboutWelcomeBack.xhtml\n\t\tchrome://browser/content/aboutlogins/aboutLogins.css\n\t\tchrome://browser/content/aboutlogins/aboutLogins.html\n\t\tchrome://browser/content/aboutlogins/aboutLogins.mjs\n\t\tchrome://browser/content/aboutlogins/aboutLoginsImportReport.css\n\t\tchrome://browser/content/aboutlogins/aboutLoginsImportReport.html\n\t\tchrome://browser/content/aboutlogins/aboutLoginsImportReport.mjs\n\t\tchrome://browser/content/aboutlogins/aboutLoginsUtils.mjs\n\t\tchrome://browser/content/aboutlogins/common.css\n\t\tchrome://browser/content/aboutlogins/components/confirmation-dialog.css\n\t\tchrome://browser/content/aboutlogins/components/confirmation-dialog.mjs\n\t\tchrome://browser/content/aboutlogins/components/fxaccounts-button.css\n\t\tchrome://browser/content/aboutlogins/components/fxaccounts-button.mjs\n\t\tchrome://browser/content/aboutlogins/components/generic-dialog.css\n\t\tchrome://browser/content/aboutlogins/components/generic-dialog.mjs\n\t\tchrome://browser/content/aboutlogins/components/import-details-row.mjs\n\t\tchrome://browser/content/aboutlogins/components/import-error-dialog.css\n\t\tchrome://browser/content/aboutlogins/components/import-error-dialog.mjs\n\t\tchrome://browser/content/aboutlogins/components/import-summary-dialog.css\n\t\tchrome://browser/content/aboutlogins/components/import-summary-dialog.mjs\n\t\tchrome://browser/content/aboutlogins/components/input-field/input-field.css\n\t\tchrome://browser/content/aboutlogins/components/input-field/input-field.mjs\n\t\tchrome://browser/content/aboutlogins/components/input-field/login-origin-field.mjs\n\t\tchrome://browser/content/aboutlogins/components/input-field/login-password-field.mjs\n\t\tchrome://browser/content/aboutlogins/components/input-field/login-username-field.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-alert.css\n\t\tchrome://browser/content/aboutlogins/components/login-alert.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-command-button.css\n\t\tchrome://browser/content/aboutlogins/components/login-command-button.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-filter.css\n\t\tchrome://browser/content/aboutlogins/components/login-filter.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-intro.css\n\t\tchrome://browser/content/aboutlogins/components/login-intro.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-item.css\n\t\tchrome://browser/content/aboutlogins/components/login-item.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-list-item.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-list-lit-item.css\n\t\tchrome://browser/content/aboutlogins/components/login-list-lit-item.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-list-section.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-list.css\n\t\tchrome://browser/content/aboutlogins/components/login-list.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-message-popup.css\n\t\tchrome://browser/content/aboutlogins/components/login-message-popup.mjs\n\t\tchrome://browser/content/aboutlogins/components/login-timeline.css\n\t\tchrome://browser/content/aboutlogins/components/login-timeline.mjs\n\t\tchrome://browser/content/aboutlogins/components/menu-button.css\n\t\tchrome://browser/content/aboutlogins/components/menu-button.mjs\n\t\tchrome://browser/content/aboutlogins/components/remove-logins-dialog.css\n\t\tchrome://browser/content/aboutlogins/components/remove-logins-dialog.mjs\n\t\tchrome://browser/content/aboutlogins/icons/breached-website.svg\n\t\tchrome://browser/content/aboutlogins/icons/intro-illustration.svg\n\t\tchrome://browser/content/aboutlogins/icons/password-hide.svg\n\t\tchrome://browser/content/aboutlogins/icons/password.svg\n\t\tchrome://browser/content/aboutlogins/icons/vulnerable-password.svg\n\t\tchrome://browser/content/aboutwelcome/aboutwelcome.bundle.js\n\t\tchrome://browser/content/aboutwelcome/aboutwelcome.css\n\t\tchrome://browser/content/aboutwelcome/aboutwelcome.html\n\t\tchrome://browser/content/asrouter/asrouter-admin.bundle.js\n\t\tchrome://browser/content/asrouter/asrouter-admin.html\n\t\tchrome://browser/content/asrouter/components/ASRouterAdmin/ASRouterAdmin.css\n\t\tchrome://browser/content/asrouter/components/remote-text.js\n\t\tchrome://browser/content/asrouter/render.js\n\t\tchrome://browser/content/asrouter/schemas/BackgroundTaskMessagingExperiment.schema.json\n\t\tchrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json\n\t\tchrome://browser/content/assets/cookie-banners-begone.svg\n\t\tchrome://browser/content/assets/focus-logo.svg\n\t\tchrome://browser/content/assets/focus-promo.png\n\t\tchrome://browser/content/assets/focus-qr-code.svg\n\t\tchrome://browser/content/assets/klar-qr-code.svg\n\t\tchrome://browser/content/assets/moz-vpn.svg\n\t\tchrome://browser/content/assets/private-promo-asset.svg\n\t\tchrome://browser/content/assets/vpn-logo.svg\n\t\tchrome://browser/content/backup/ArchiveJSONBlock.1.schema.json\n\t\tchrome://browser/content/backup/BackupManifest.1.schema.json\n\t\tchrome://browser/content/backup/archive.css\n\t\tchrome://browser/content/backup/archive.js\n\t\tchrome://browser/content/backup/archive.template.html\n\t\tchrome://browser/content/backup/backup-common.css\n\t\tchrome://browser/content/backup/backup-constants.mjs\n\t\tchrome://browser/content/backup/backup-settings.css\n\t\tchrome://browser/content/backup/backup-settings.mjs\n\t\tchrome://browser/content/backup/disable-backup-encryption.css\n\t\tchrome://browser/content/backup/disable-backup-encryption.mjs\n\t\tchrome://browser/content/backup/enable-backup-encryption.css\n\t\tchrome://browser/content/backup/enable-backup-encryption.mjs\n\t\tchrome://browser/content/backup/password-rules-tooltip.css\n\t\tchrome://browser/content/backup/password-rules-tooltip.mjs\n\t\tchrome://browser/content/backup/password-validation-inputs.css\n\t\tchrome://browser/content/backup/password-validation-inputs.mjs\n\t\tchrome://browser/content/backup/restore-from-backup.css\n\t\tchrome://browser/content/backup/restore-from-backup.mjs\n\t\tchrome://browser/content/backup/turn-off-scheduled-backups.css\n\t\tchrome://browser/content/backup/turn-off-scheduled-backups.mjs\n\t\tchrome://browser/content/backup/turn-on-scheduled-backups.css\n\t\tchrome://browser/content/backup/turn-on-scheduled-backups.mjs\n\t\tchrome://browser/content/blanktab.html\n\t\tchrome://browser/content/blockedSite.js\n\t\tchrome://browser/content/blockedSite.xhtml\n\t\tchrome://browser/content/browser-a11yUtils.js\n\t\tchrome://browser/content/browser-addons.js\n\t\tchrome://browser/content/browser-captivePortal.js\n\t\tchrome://browser/content/browser-command-listeners.js\n\t\tchrome://browser/content/browser-commands.js\n\t\tchrome://browser/content/browser-context.js\n\t\tchrome://browser/content/browser-customization.js\n\t\tchrome://browser/content/browser-data-submission-info-bar.js\n\t\tchrome://browser/content/browser-fullScreenAndPointerLock.js\n\t\tchrome://browser/content/browser-gestureSupport.js\n\t\tchrome://browser/content/browser-graphics-utils.js\n\t\tchrome://browser/content/browser-init.js\n\t\tchrome://browser/content/browser-menubar.js\n\t\tchrome://browser/content/browser-pageActions.js\n\t\tchrome://browser/content/browser-pagestyle.js\n\t\tchrome://browser/content/browser-places.js\n\t\tchrome://browser/content/browser-profiles.js\n\t\tchrome://browser/content/browser-safebrowsing.js\n\t\tchrome://browser/content/browser-sets.js\n\t\tchrome://browser/content/browser-siteIdentity.js\n\t\tchrome://browser/content/browser-sitePermissionPanel.js\n\t\tchrome://browser/content/browser-siteProtections.js\n\t\tchrome://browser/content/browser-sync.js\n\t\tchrome://browser/content/browser-tabsintitlebar.js\n\t\tchrome://browser/content/browser-thumbnails.js\n\t\tchrome://browser/content/browser-toolbarKeyNav.js\n\t\tchrome://browser/content/browser-unified-extensions.js\n\t\tchrome://browser/content/browser-webrtc.js\n\t\tchrome://browser/content/browser.css\n\t\tchrome://browser/content/browser.js\n\t\tchrome://browser/content/browser.xhtml\n\t\tchrome://browser/content/built_in_addons.json\n\t\tchrome://browser/content/callout-tab-pickup-dark.svg\n\t\tchrome://browser/content/callout-tab-pickup.svg\n\t\tchrome://browser/content/child/ext-browser-content-only.js\n\t\tchrome://browser/content/child/ext-browser.js\n\t\tchrome://browser/content/child/ext-devtools-inspectedWindow.js\n\t\tchrome://browser/content/child/ext-devtools-network.js\n\t\tchrome://browser/content/child/ext-devtools-panels.js\n\t\tchrome://browser/content/child/ext-devtools.js\n\t\tchrome://browser/content/child/ext-menus-child.js\n\t\tchrome://browser/content/child/ext-menus.js\n\t\tchrome://browser/content/child/ext-omnibox.js\n\t\tchrome://browser/content/child/ext-tabs.js\n\t\tchrome://browser/content/contentSearchHandoffUI.js\n\t\tchrome://browser/content/contentSearchUI.css\n\t\tchrome://browser/content/contentSearchUI.js\n\t\tchrome://browser/content/contentTheme.js\n\t\tchrome://browser/content/customizableui/panelUI.js\n\t\tchrome://browser/content/default-bookmarks.html\n\t\tchrome://browser/content/downloads/allDownloadsView.js\n\t\tchrome://browser/content/downloads/contentAreaDownloadsView.css\n\t\tchrome://browser/content/downloads/contentAreaDownloadsView.js\n\t\tchrome://browser/content/downloads/contentAreaDownloadsView.xhtml\n\t\tchrome://browser/content/downloads/downloads.css\n\t\tchrome://browser/content/downloads/downloads.js\n\t\tchrome://browser/content/downloads/downloadsCommands.js\n\t\tchrome://browser/content/downloads/indicator.js\n\t\tchrome://browser/content/ext-browser.json\n\t\tchrome://browser/content/extension-popup-panel.css\n\t\tchrome://browser/content/extension.css\n\t\tchrome://browser/content/firefoxview/card-container.css\n\t\tchrome://browser/content/firefoxview/card-container.mjs\n\t\tchrome://browser/content/firefoxview/firefoxview.css\n\t\tchrome://browser/content/firefoxview/firefoxview.html\n\t\tchrome://browser/content/firefoxview/firefoxview.mjs\n\t\tchrome://browser/content/firefoxview/fxview-empty-state.css\n\t\tchrome://browser/content/firefoxview/fxview-empty-state.mjs\n\t\tchrome://browser/content/firefoxview/fxview-search-textbox.css\n\t\tchrome://browser/content/firefoxview/fxview-search-textbox.mjs\n\t\tchrome://browser/content/firefoxview/fxview-tab-list.css\n\t\tchrome://browser/content/firefoxview/fxview-tab-list.mjs\n\t\tchrome://browser/content/firefoxview/fxview-tab-row.css\n\t\tchrome://browser/content/firefoxview/helpers.mjs\n\t\tchrome://browser/content/firefoxview/history-empty.svg\n\t\tchrome://browser/content/firefoxview/history.css\n\t\tchrome://browser/content/firefoxview/history.mjs\n\t\tchrome://browser/content/firefoxview/opentabs-tab-list.css\n\t\tchrome://browser/content/firefoxview/opentabs-tab-list.mjs\n\t\tchrome://browser/content/firefoxview/opentabs-tab-row.css\n\t\tchrome://browser/content/firefoxview/opentabs.mjs\n\t\tchrome://browser/content/firefoxview/recentbrowsing.mjs\n\t\tchrome://browser/content/firefoxview/recentlyclosed-empty.svg\n\t\tchrome://browser/content/firefoxview/recentlyclosed.mjs\n\t\tchrome://browser/content/firefoxview/search-helpers.mjs\n\t\tchrome://browser/content/firefoxview/synced-tabs-error.svg\n\t\tchrome://browser/content/firefoxview/syncedtabs.mjs\n\t\tchrome://browser/content/firefoxview/view-history.svg\n\t\tchrome://browser/content/firefoxview/view-opentabs.css\n\t\tchrome://browser/content/firefoxview/view-opentabs.svg\n\t\tchrome://browser/content/firefoxview/view-recentbrowsing.svg\n\t\tchrome://browser/content/firefoxview/view-recentlyclosed.svg\n\t\tchrome://browser/content/firefoxview/view-syncedtabs.css\n\t\tchrome://browser/content/firefoxview/view-syncedtabs.svg\n\t\tchrome://browser/content/firefoxview/viewpage.mjs\n\t\tchrome://browser/content/genai/assets/brands/chatgpt.svg\n\t\tchrome://browser/content/genai/assets/brands/claude.svg\n\t\tchrome://browser/content/genai/assets/brands/gemini.svg\n\t\tchrome://browser/content/genai/assets/brands/huggingchat.svg\n\t\tchrome://browser/content/genai/assets/brands/lechat.svg\n\t\tchrome://browser/content/genai/assets/shortcuts-animated-dark.svg\n\t\tchrome://browser/content/genai/assets/shortcuts-animated.svg\n\t\tchrome://browser/content/genai/assets/shortcuts-static-dark.svg\n\t\tchrome://browser/content/genai/assets/shortcuts-static.svg\n\t\tchrome://browser/content/genai/chat.css\n\t\tchrome://browser/content/genai/chat.html\n\t\tchrome://browser/content/genai/chat.js\n\t\tchrome://browser/content/hiddenWindowMac.xhtml\n\t\tchrome://browser/content/ion.css\n\t\tchrome://browser/content/ion.html\n\t\tchrome://browser/content/ion.js\n\t\tchrome://browser/content/license.html\n\t\tchrome://browser/content/lockwise-card.mjs\n\t\tchrome://browser/content/logos/etp-mobile.svg\n\t\tchrome://browser/content/logos/fxa-logo.svg\n\t\tchrome://browser/content/logos/lockwise.svg\n\t\tchrome://browser/content/logos/monitor.svg\n\t\tchrome://browser/content/logos/passkey.svg\n\t\tchrome://browser/content/logos/proxy-dark.svg\n\t\tchrome://browser/content/logos/proxy-light.svg\n\t\tchrome://browser/content/logos/relay.svg\n\t\tchrome://browser/content/logos/send.svg\n\t\tchrome://browser/content/logos/tracking-protection-dark-theme.svg\n\t\tchrome://browser/content/logos/tracking-protection.svg\n\t\tchrome://browser/content/logos/vpn-dark.svg\n\t\tchrome://browser/content/logos/vpn-light.svg\n\t\tchrome://browser/content/logos/vpn-promo-logo.svg\n\t\tchrome://browser/content/main-popupset.js\n\t\tchrome://browser/content/messagepreview/limelight.svg\n\t\tchrome://browser/content/messagepreview/messagepreview.css\n\t\tchrome://browser/content/messagepreview/messagepreview.html\n\t\tchrome://browser/content/messagepreview/messagepreview.js\n\t\tchrome://browser/content/messagepreview/switch.svg\n\t\tchrome://browser/content/migration/brands/360.png\n\t\tchrome://browser/content/migration/brands/brave.png\n\t\tchrome://browser/content/migration/brands/canary.png\n\t\tchrome://browser/content/migration/brands/chrome.png\n\t\tchrome://browser/content/migration/brands/chromium.png\n\t\tchrome://browser/content/migration/brands/edge.png\n\t\tchrome://browser/content/migration/brands/edgebeta.png\n\t\tchrome://browser/content/migration/brands/ie.png\n\t\tchrome://browser/content/migration/brands/opera.png\n\t\tchrome://browser/content/migration/brands/operagx.png\n\t\tchrome://browser/content/migration/brands/safari.png\n\t\tchrome://browser/content/migration/brands/vivaldi.png\n\t\tchrome://browser/content/migration/migration-dialog-window.html\n\t\tchrome://browser/content/migration/migration-dialog-window.js\n\t\tchrome://browser/content/migration/migration-wizard-constants.mjs\n\t\tchrome://browser/content/migration/migration-wizard.mjs\n\t\tchrome://browser/content/monitor-card.mjs\n\t\tchrome://browser/content/nonbrowser-mac.js\n\t\tchrome://browser/content/nsContextMenu.js\n\t\tchrome://browser/content/nsContextMenu.sys.mjs\n\t\tchrome://browser/content/pagedata/schemas/article.schema.json\n\t\tchrome://browser/content/pagedata/schemas/audio.schema.json\n\t\tchrome://browser/content/pagedata/schemas/document.schema.json\n\t\tchrome://browser/content/pagedata/schemas/general.schema.json\n\t\tchrome://browser/content/pagedata/schemas/product.schema.json\n\t\tchrome://browser/content/pagedata/schemas/video.schema.json\n\t\tchrome://browser/content/pageinfo/pageInfo.css\n\t\tchrome://browser/content/pageinfo/pageInfo.js\n\t\tchrome://browser/content/pageinfo/pageInfo.xhtml\n\t\tchrome://browser/content/pageinfo/permissions.js\n\t\tchrome://browser/content/pageinfo/security.js\n\t\tchrome://browser/content/parent/ext-bookmarks.js\n\t\tchrome://browser/content/parent/ext-browser.js\n\t\tchrome://browser/content/parent/ext-browserAction.js\n\t\tchrome://browser/content/parent/ext-chrome-settings-overrides.js\n\t\tchrome://browser/content/parent/ext-commands.js\n\t\tchrome://browser/content/parent/ext-devtools-inspectedWindow.js\n\t\tchrome://browser/content/parent/ext-devtools-network.js\n\t\tchrome://browser/content/parent/ext-devtools-panels.js\n\t\tchrome://browser/content/parent/ext-devtools.js\n\t\tchrome://browser/content/parent/ext-find.js\n\t\tchrome://browser/content/parent/ext-history.js\n\t\tchrome://browser/content/parent/ext-menus.js\n\t\tchrome://browser/content/parent/ext-normandyAddonStudy.js\n\t\tchrome://browser/content/parent/ext-omnibox.js\n\t\tchrome://browser/content/parent/ext-pageAction.js\n\t\tchrome://browser/content/parent/ext-pkcs11.js\n\t\tchrome://browser/content/parent/ext-search.js\n\t\tchrome://browser/content/parent/ext-sessions.js\n\t\tchrome://browser/content/parent/ext-sidebarAction.js\n\t\tchrome://browser/content/parent/ext-tabs.js\n\t\tchrome://browser/content/parent/ext-topSites.js\n\t\tchrome://browser/content/parent/ext-url-overrides.js\n\t\tchrome://browser/content/parent/ext-windows.js\n\t\tchrome://browser/content/places/bookmarkProperties.js\n\t\tchrome://browser/content/places/bookmarkProperties.xhtml\n\t\tchrome://browser/content/places/bookmarksSidebar.js\n\t\tchrome://browser/content/places/bookmarksSidebar.xhtml\n\t\tchrome://browser/content/places/browserPlacesViews.js\n\t\tchrome://browser/content/places/controller.js\n\t\tchrome://browser/content/places/editBookmark.js\n\t\tchrome://browser/content/places/historySidebar.js\n\t\tchrome://browser/content/places/historySidebar.xhtml\n\t\tchrome://browser/content/places/places-commands.js\n\t\tchrome://browser/content/places/places-menupopup.js\n\t\tchrome://browser/content/places/places-tree.js\n\t\tchrome://browser/content/places/places.css\n\t\tchrome://browser/content/places/places.js\n\t\tchrome://browser/content/places/places.xhtml\n\t\tchrome://browser/content/places/treeView.js\n\t\tchrome://browser/content/policies/aboutPolicies.css\n\t\tchrome://browser/content/policies/aboutPolicies.html\n\t\tchrome://browser/content/policies/aboutPolicies.js\n\t\tchrome://browser/content/policies/policies-active.svg\n\t\tchrome://browser/content/policies/policies-documentation.svg\n\t\tchrome://browser/content/policies/policies-error.svg\n\t\tchrome://browser/content/preferences/containers.js\n\t\tchrome://browser/content/preferences/dialogs/addEngine.css\n\t\tchrome://browser/content/preferences/dialogs/addEngine.js\n\t\tchrome://browser/content/preferences/dialogs/addEngine.xhtml\n\t\tchrome://browser/content/preferences/dialogs/applicationManager.js\n\t\tchrome://browser/content/preferences/dialogs/applicationManager.xhtml\n\t\tchrome://browser/content/preferences/dialogs/blocklists.js\n\t\tchrome://browser/content/preferences/dialogs/blocklists.xhtml\n\t\tchrome://browser/content/preferences/dialogs/browserLanguages.js\n\t\tchrome://browser/content/preferences/dialogs/browserLanguages.xhtml\n\t\tchrome://browser/content/preferences/dialogs/clearSiteData.css\n\t\tchrome://browser/content/preferences/dialogs/clearSiteData.js\n\t\tchrome://browser/content/preferences/dialogs/clearSiteData.xhtml\n\t\tchrome://browser/content/preferences/dialogs/colors.js\n\t\tchrome://browser/content/preferences/dialogs/colors.xhtml\n\t\tchrome://browser/content/preferences/dialogs/connection.js\n\t\tchrome://browser/content/preferences/dialogs/connection.xhtml\n\t\tchrome://browser/content/preferences/dialogs/containers.js\n\t\tchrome://browser/content/preferences/dialogs/containers.xhtml\n\t\tchrome://browser/content/preferences/dialogs/dohExceptions.js\n\t\tchrome://browser/content/preferences/dialogs/dohExceptions.xhtml\n\t\tchrome://browser/content/preferences/dialogs/fonts.js\n\t\tchrome://browser/content/preferences/dialogs/fonts.xhtml\n\t\tchrome://browser/content/preferences/dialogs/handlers.css\n\t\tchrome://browser/content/preferences/dialogs/languages.js\n\t\tchrome://browser/content/preferences/dialogs/languages.xhtml\n\t\tchrome://browser/content/preferences/dialogs/permissions.js\n\t\tchrome://browser/content/preferences/dialogs/permissions.xhtml\n\t\tchrome://browser/content/preferences/dialogs/sanitize.js\n\t\tchrome://browser/content/preferences/dialogs/sanitize.xhtml\n\t\tchrome://browser/content/preferences/dialogs/selectBookmark.js\n\t\tchrome://browser/content/preferences/dialogs/selectBookmark.xhtml\n\t\tchrome://browser/content/preferences/dialogs/siteDataRemoveSelected.js\n\t\tchrome://browser/content/preferences/dialogs/siteDataRemoveSelected.xhtml\n\t\tchrome://browser/content/preferences/dialogs/siteDataSettings.js\n\t\tchrome://browser/content/preferences/dialogs/siteDataSettings.xhtml\n\t\tchrome://browser/content/preferences/dialogs/sitePermissions.css\n\t\tchrome://browser/content/preferences/dialogs/sitePermissions.js\n\t\tchrome://browser/content/preferences/dialogs/sitePermissions.xhtml\n\t\tchrome://browser/content/preferences/dialogs/syncChooseWhatToSync.js\n\t\tchrome://browser/content/preferences/dialogs/syncChooseWhatToSync.xhtml\n\t\tchrome://browser/content/preferences/dialogs/translationExceptions.js\n\t\tchrome://browser/content/preferences/dialogs/translationExceptions.xhtml\n\t\tchrome://browser/content/preferences/dialogs/translations.js\n\t\tchrome://browser/content/preferences/dialogs/translations.xhtml\n\t\tchrome://browser/content/preferences/experimental.js\n\t\tchrome://browser/content/preferences/extensionControlled.js\n\t\tchrome://browser/content/preferences/findInPage.js\n\t\tchrome://browser/content/preferences/fxaPairDevice.js\n\t\tchrome://browser/content/preferences/fxaPairDevice.xhtml\n\t\tchrome://browser/content/preferences/home.js\n\t\tchrome://browser/content/preferences/main.js\n\t\tchrome://browser/content/preferences/more-from-mozilla-qr-code-simple-cn.svg\n\t\tchrome://browser/content/preferences/more-from-mozilla-qr-code-simple.svg\n\t\tchrome://browser/content/preferences/moreFromMozilla.js\n\t\tchrome://browser/content/preferences/preferences.js\n\t\tchrome://browser/content/preferences/preferences.xhtml\n\t\tchrome://browser/content/preferences/privacy.js\n\t\tchrome://browser/content/preferences/search.js\n\t\tchrome://browser/content/preferences/sync.js\n\t\tchrome://browser/content/preferences/translations.js\n\t\tchrome://browser/content/preferences/web-appearance-dark.svg\n\t\tchrome://browser/content/preferences/web-appearance-light.svg\n\t\tchrome://browser/content/protections.css\n\t\tchrome://browser/content/protections.html\n\t\tchrome://browser/content/protections.mjs\n\t\tchrome://browser/content/proxy-card.mjs\n\t\tchrome://browser/content/robot.ico\n\t\tchrome://browser/content/safeMode.css\n\t\tchrome://browser/content/safeMode.js\n\t\tchrome://browser/content/safeMode.xhtml\n\t\tchrome://browser/content/sanitize.xhtml\n\t\tchrome://browser/content/sanitizeDialog.css\n\t\tchrome://browser/content/sanitizeDialog.js\n\t\tchrome://browser/content/sanitize_v2.xhtml\n\t\tchrome://browser/content/schemas/bookmarks.json\n\t\tchrome://browser/content/schemas/chrome_settings_overrides.json\n\t\tchrome://browser/content/schemas/commands.json\n\t\tchrome://browser/content/schemas/devtools.json\n\t\tchrome://browser/content/schemas/devtools_inspected_window.json\n\t\tchrome://browser/content/schemas/devtools_network.json\n\t\tchrome://browser/content/schemas/devtools_panels.json\n\t\tchrome://browser/content/schemas/find.json\n\t\tchrome://browser/content/schemas/history.json\n\t\tchrome://browser/content/schemas/menus.json\n\t\tchrome://browser/content/schemas/menus_child.json\n\t\tchrome://browser/content/schemas/normandyAddonStudy.json\n\t\tchrome://browser/content/schemas/omnibox.json\n\t\tchrome://browser/content/schemas/pkcs11.json\n\t\tchrome://browser/content/schemas/search.json\n\t\tchrome://browser/content/schemas/sessions.json\n\t\tchrome://browser/content/schemas/sidebar_action.json\n\t\tchrome://browser/content/schemas/tabs.json\n\t\tchrome://browser/content/schemas/top_sites.json\n\t\tchrome://browser/content/schemas/url_overrides.json\n\t\tchrome://browser/content/schemas/windows.json\n\t\tchrome://browser/content/screenshots/cancel.svg\n\t\tchrome://browser/content/screenshots/copied-notification.svg\n\t\tchrome://browser/content/screenshots/copy.svg\n\t\tchrome://browser/content/screenshots/download-white.svg\n\t\tchrome://browser/content/screenshots/download.svg\n\t\tchrome://browser/content/screenshots/fileHelpers.mjs\n\t\tchrome://browser/content/screenshots/icon-welcome-face-without-eyes.svg\n\t\tchrome://browser/content/screenshots/menu-fullpage.svg\n\t\tchrome://browser/content/screenshots/menu-visible.svg\n\t\tchrome://browser/content/screenshots/overlay/overlay.css\n\t\tchrome://browser/content/screenshots/overlayHelpers.mjs\n\t\tchrome://browser/content/screenshots/screenshots-buttons.css\n\t\tchrome://browser/content/screenshots/screenshots-buttons.js\n\t\tchrome://browser/content/screenshots/screenshots-preview.css\n\t\tchrome://browser/content/screenshots/screenshots-preview.html\n\t\tchrome://browser/content/screenshots/screenshots-preview.mjs\n\t\tchrome://browser/content/search/autocomplete-popup.js\n\t\tchrome://browser/content/search/searchbar.js\n\t\tchrome://browser/content/setDesktopBackground.js\n\t\tchrome://browser/content/setDesktopBackground.xhtml\n\t\tchrome://browser/content/shopping/adjusted-rating.mjs\n\t\tchrome://browser/content/shopping/analysis-explainer.css\n\t\tchrome://browser/content/shopping/analysis-explainer.mjs\n\t\tchrome://browser/content/shopping/assets/competitiveness.svg\n\t\tchrome://browser/content/shopping/assets/optInDark.avif\n\t\tchrome://browser/content/shopping/assets/optInLight.avif\n\t\tchrome://browser/content/shopping/assets/packaging.svg\n\t\tchrome://browser/content/shopping/assets/price.svg\n\t\tchrome://browser/content/shopping/assets/priceTagButtonCallout.svg\n\t\tchrome://browser/content/shopping/assets/quality.svg\n\t\tchrome://browser/content/shopping/assets/ratingDark.avif\n\t\tchrome://browser/content/shopping/assets/ratingLight.avif\n\t\tchrome://browser/content/shopping/assets/reviewsVisualCallout.svg\n\t\tchrome://browser/content/shopping/assets/shipping.svg\n\t\tchrome://browser/content/shopping/assets/shopping.svg\n\t\tchrome://browser/content/shopping/assets/unanalyzedDark.avif\n\t\tchrome://browser/content/shopping/assets/unanalyzedLight.avif\n\t\tchrome://browser/content/shopping/highlight-item.css\n\t\tchrome://browser/content/shopping/highlight-item.mjs\n\t\tchrome://browser/content/shopping/highlights.mjs\n\t\tchrome://browser/content/shopping/letter-grade.css\n\t\tchrome://browser/content/shopping/letter-grade.mjs\n\t\tchrome://browser/content/shopping/onboarding.mjs\n\t\tchrome://browser/content/shopping/recommended-ad.css\n\t\tchrome://browser/content/shopping/recommended-ad.mjs\n\t\tchrome://browser/content/shopping/reliability.mjs\n\t\tchrome://browser/content/shopping/settings.css\n\t\tchrome://browser/content/shopping/settings.mjs\n\t\tchrome://browser/content/shopping/shopping-card.css\n\t\tchrome://browser/content/shopping/shopping-card.mjs\n\t\tchrome://browser/content/shopping/shopping-container.css\n\t\tchrome://browser/content/shopping/shopping-container.mjs\n\t\tchrome://browser/content/shopping/shopping-message-bar.css\n\t\tchrome://browser/content/shopping/shopping-message-bar.mjs\n\t\tchrome://browser/content/shopping/shopping-page.css\n\t\tchrome://browser/content/shopping/shopping-sidebar.js\n\t\tchrome://browser/content/shopping/shopping.html\n\t\tchrome://browser/content/shopping/unanalyzed.css\n\t\tchrome://browser/content/shopping/unanalyzed.mjs\n\t\tchrome://browser/content/sidebar/browser-sidebar.js\n\t\tchrome://browser/content/sidebar/sidebar-customize.css\n\t\tchrome://browser/content/sidebar/sidebar-customize.html\n\t\tchrome://browser/content/sidebar/sidebar-customize.mjs\n\t\tchrome://browser/content/sidebar/sidebar-history.css\n\t\tchrome://browser/content/sidebar/sidebar-history.html\n\t\tchrome://browser/content/sidebar/sidebar-history.mjs\n\t\tchrome://browser/content/sidebar/sidebar-main.css\n\t\tchrome://browser/content/sidebar/sidebar-main.mjs\n\t\tchrome://browser/content/sidebar/sidebar-page.mjs\n\t\tchrome://browser/content/sidebar/sidebar-panel-header.css\n\t\tchrome://browser/content/sidebar/sidebar-panel-header.mjs\n\t\tchrome://browser/content/sidebar/sidebar-syncedtabs.html\n\t\tchrome://browser/content/sidebar/sidebar-syncedtabs.mjs\n\t\tchrome://browser/content/sidebar/sidebar-tab-list.css\n\t\tchrome://browser/content/sidebar/sidebar-tab-list.mjs\n\t\tchrome://browser/content/sidebar/sidebar-tab-row.css\n\t\tchrome://browser/content/sidebar/sidebar.css\n\t\tchrome://browser/content/spotlight.html\n\t\tchrome://browser/content/spotlight.js\n\t\tchrome://browser/content/static-robot.png\n\t\tchrome://browser/content/syncedtabs/sidebar.js\n\t\tchrome://browser/content/syncedtabs/sidebar.xhtml\n\t\tchrome://browser/content/tabbrowser/browser-allTabsMenu.js\n\t\tchrome://browser/content/tabbrowser/browser-ctrlTab.js\n\t\tchrome://browser/content/tabbrowser/browser-fullZoom.js\n\t\tchrome://browser/content/tabbrowser/tab-hover-preview.mjs\n\t\tchrome://browser/content/tabbrowser/tab.js\n\t\tchrome://browser/content/tabbrowser/tabbrowser.js\n\t\tchrome://browser/content/tabbrowser/tabgroup.js\n\t\tchrome://browser/content/tabbrowser/tabs.js\n\t\tchrome://browser/content/tabunloader/aboutUnloads.css\n\t\tchrome://browser/content/tabunloader/aboutUnloads.html\n\t\tchrome://browser/content/tabunloader/aboutUnloads.js\n\t\tchrome://browser/content/textrecognition/textrecognition.css\n\t\tchrome://browser/content/textrecognition/textrecognition.html\n\t\tchrome://browser/content/textrecognition/textrecognition.mjs\n\t\tchrome://browser/content/translations/TranslationsPanelShared.sys.mjs\n\t\tchrome://browser/content/translations/fullPageTranslationsPanel.js\n\t\tchrome://browser/content/translations/selectTranslationsPanel.js\n\t\tchrome://browser/content/urlbar/quicksuggestOnboarding.css\n\t\tchrome://browser/content/urlbar/quicksuggestOnboarding.html\n\t\tchrome://browser/content/urlbar/quicksuggestOnboarding.js\n\t\tchrome://browser/content/urlbar/quicksuggestOnboarding_magglass.svg\n\t\tchrome://browser/content/urlbar/quicksuggestOnboarding_magglass_animation.svg\n\t\tchrome://browser/content/urlbar/suggest-example.svg\n\t\tchrome://browser/content/usercontext/usercontext.css\n\t\tchrome://browser/content/utilityOverlay.js\n\t\tchrome://browser/content/vpn-card.mjs\n\t\tchrome://browser/content/webext-panels.js\n\t\tchrome://browser/content/webext-panels.xhtml\n\t\tchrome://browser/content/webrtcIndicator.js\n\t\tchrome://browser/content/webrtcIndicator.xhtml\n\t\tchrome://browser/locale/appstrings.properties\n\t\tchrome://browser/locale/browser.properties\n\t\tchrome://browser/locale/customizableui/customizableWidgets.properties\n\t\tchrome://browser/locale/downloads/downloads.properties\n\t\tchrome://browser/locale/feeds/subscribe.properties\n\t\tchrome://browser/locale/places/bookmarkProperties.properties\n\t\tchrome://browser/locale/safebrowsing/safebrowsing.properties\n\t\tchrome://browser/locale/search.properties\n\t\tchrome://browser/locale/shellservice.properties\n\t\tchrome://browser/locale/siteData.properties\n\t\tchrome://browser/locale/sitePermissions.properties\n\t\tchrome://browser/locale/syncSetup.properties\n\t\tchrome://browser/locale/taskbar.properties\n\t\tchrome://browser/locale/uiDensity.properties\n\t\tchrome://browser/skin/UITour.css\n\t\tchrome://browser/skin/aboutFrameCrashed.css\n\t\tchrome://browser/skin/aboutRestartRequired.css\n\t\tchrome://browser/skin/aboutSessionRestore.css\n\t\tchrome://browser/skin/aboutTabCrashed.css\n\t\tchrome://browser/skin/aboutWelcomeBack.css\n\t\tchrome://browser/skin/add-circle-fill.svg\n\t\tchrome://browser/skin/addon-notification.css\n\t\tchrome://browser/skin/addons/addon-install-blocked.svg\n\t\tchrome://browser/skin/addons/addon-install-downloading.svg\n\t\tchrome://browser/skin/addons/addon-install-installed.svg\n\t\tchrome://browser/skin/addons/extension-controlled.css\n\t\tchrome://browser/skin/addons/unified-extensions.css\n\t\tchrome://browser/skin/autocomplete.css\n\t\tchrome://browser/skin/back.svg\n\t\tchrome://browser/skin/blockedSite.css\n\t\tchrome://browser/skin/bookmark-12.svg\n\t\tchrome://browser/skin/bookmark-hollow.svg\n\t\tchrome://browser/skin/bookmark-star-on-tray.svg\n\t\tchrome://browser/skin/bookmark.svg\n\t\tchrome://browser/skin/bookmarks-toolbar.svg\n\t\tchrome://browser/skin/browser-colors.css\n\t\tchrome://browser/skin/browser-custom-colors.css\n\t\tchrome://browser/skin/browser-shared.css\n\t\tchrome://browser/skin/browser.css\n\t\tchrome://browser/skin/canvas-blocked.svg\n\t\tchrome://browser/skin/canvas.svg\n\t\tchrome://browser/skin/characterEncoding.svg\n\t\tchrome://browser/skin/chevron-animation.svg\n\t\tchrome://browser/skin/circle-check-dotted.svg\n\t\tchrome://browser/skin/contextmenu.css\n\t\tchrome://browser/skin/controlcenter/3rdpartycookies-blocked.svg\n\t\tchrome://browser/skin/controlcenter/3rdpartycookies.svg\n\t\tchrome://browser/skin/controlcenter/cryptominers.svg\n\t\tchrome://browser/skin/controlcenter/dashboard.svg\n\t\tchrome://browser/skin/controlcenter/etp-milestone.svg\n\t\tchrome://browser/skin/controlcenter/hero-message-background.svg\n\t\tchrome://browser/skin/controlcenter/mcb-disabled.svg\n\t\tchrome://browser/skin/controlcenter/panel.css\n\t\tchrome://browser/skin/controlcenter/tracking-protection.svg\n\t\tchrome://browser/skin/customizableui/customizeMode.css\n\t\tchrome://browser/skin/customizableui/density-compact.svg\n\t\tchrome://browser/skin/customizableui/density-normal.svg\n\t\tchrome://browser/skin/customizableui/density-touch.svg\n\t\tchrome://browser/skin/customizableui/empty-overflow-panel.png\n\t\tchrome://browser/skin/customizableui/empty-overflow-panel@2x.png\n\t\tchrome://browser/skin/customizableui/panelUI-shared.css\n\t\tchrome://browser/skin/customizableui/panelUI.css\n\t\tchrome://browser/skin/customizableui/whimsy.png\n\t\tchrome://browser/skin/customize.svg\n\t\tchrome://browser/skin/device-desktop.svg\n\t\tchrome://browser/skin/device-phone.svg\n\t\tchrome://browser/skin/device-tablet.svg\n\t\tchrome://browser/skin/device-tv.svg\n\t\tchrome://browser/skin/device-vr.svg\n\t\tchrome://browser/skin/downloads/allDownloadsView.css\n\t\tchrome://browser/skin/downloads/allDownloadsView.inc.css\n\t\tchrome://browser/skin/downloads/contentAreaDownloadsView.css\n\t\tchrome://browser/skin/downloads/download-blockedStates.css\n\t\tchrome://browser/skin/downloads/download-summary.svg\n\t\tchrome://browser/skin/downloads/downloads.css\n\t\tchrome://browser/skin/downloads/downloads.inc.css\n\t\tchrome://browser/skin/downloads/downloads.svg\n\t\tchrome://browser/skin/downloads/indicator.css\n\t\tchrome://browser/skin/downloads/notification-finish-animation.svg\n\t\tchrome://browser/skin/downloads/notification-start-animation.svg\n\t\tchrome://browser/skin/downloads/progressmeter.css\n\t\tchrome://browser/skin/drm-icon.svg\n\t\tchrome://browser/skin/edit-cut.svg\n\t\tchrome://browser/skin/edit-paste.svg\n\t\tchrome://browser/skin/fingerprint.svg\n\t\tchrome://browser/skin/firefox-view.svg\n\t\tchrome://browser/skin/flame.svg\n\t\tchrome://browser/skin/forget.svg\n\t\tchrome://browser/skin/formautofill-notification.css\n\t\tchrome://browser/skin/formautofill/icon-capture-address-fields.svg\n\t\tchrome://browser/skin/formautofill/icon-capture-email-fields.svg\n\t\tchrome://browser/skin/formautofill/icon-doorhanger-menu.svg\n\t\tchrome://browser/skin/forward.svg\n\t\tchrome://browser/skin/fullscreen-exit.svg\n\t\tchrome://browser/skin/fullscreen.svg\n\t\tchrome://browser/skin/fxa/avatar-color.svg\n\t\tchrome://browser/skin/fxa/avatar-empty.svg\n\t\tchrome://browser/skin/fxa/avatar.svg\n\t\tchrome://browser/skin/fxa/fxa-spinner.svg\n\t\tchrome://browser/skin/fxa/monitor.svg\n\t\tchrome://browser/skin/fxa/send-to-device.svg\n\t\tchrome://browser/skin/fxa/send.svg\n\t\tchrome://browser/skin/fxa/sync-devices.svg\n\t\tchrome://browser/skin/fxa/sync-illustration-issue.svg\n\t\tchrome://browser/skin/fxa/sync-illustration.svg\n\t\tchrome://browser/skin/history.svg\n\t\tchrome://browser/skin/home.svg\n\t\tchrome://browser/skin/identity-block/identity-block.css\n\t\tchrome://browser/skin/identity-credential-notification.css\n\t\tchrome://browser/skin/import-export.svg\n\t\tchrome://browser/skin/import.svg\n\t\tchrome://browser/skin/ion.svg\n\t\tchrome://browser/skin/library.svg\n\t\tchrome://browser/skin/light-dark-overrides.css\n\t\tchrome://browser/skin/login.svg\n\t\tchrome://browser/skin/logo-android.svg\n\t\tchrome://browser/skin/logo-ios.svg\n\t\tchrome://browser/skin/mail.svg\n\t\tchrome://browser/skin/menu-badged.svg\n\t\tchrome://browser/skin/menu.svg\n\t\tchrome://browser/skin/menupanel.css\n\t\tchrome://browser/skin/migration/migration-dialog-window.css\n\t\tchrome://browser/skin/migration/migration-wizard.css\n\t\tchrome://browser/skin/migration/progress-mask.svg\n\t\tchrome://browser/skin/migration/safari-icon-3dots.svg\n\t\tchrome://browser/skin/migration/success.svg\n\t\tchrome://browser/skin/monitor-base.png\n\t\tchrome://browser/skin/monitor-border.png\n\t\tchrome://browser/skin/new-tab.svg\n\t\tchrome://browser/skin/notification-fill-12.svg\n\t\tchrome://browser/skin/notification-icons.css\n\t\tchrome://browser/skin/notification-icons/autoplay-media-blocked.svg\n\t\tchrome://browser/skin/notification-icons/autoplay-media.svg\n\t\tchrome://browser/skin/notification-icons/camera-blocked.svg\n\t\tchrome://browser/skin/notification-icons/camera.png\n\t\tchrome://browser/skin/notification-icons/camera.svg\n\t\tchrome://browser/skin/notification-icons/desktop-notification-blocked.svg\n\t\tchrome://browser/skin/notification-icons/desktop-notification.svg\n\t\tchrome://browser/skin/notification-icons/drag-indicator.svg\n\t\tchrome://browser/skin/notification-icons/geo-blocked.svg\n\t\tchrome://browser/skin/notification-icons/geo.svg\n\t\tchrome://browser/skin/notification-icons/microphone-blocked.svg\n\t\tchrome://browser/skin/notification-icons/microphone.png\n\t\tchrome://browser/skin/notification-icons/microphone.svg\n\t\tchrome://browser/skin/notification-icons/midi.svg\n\t\tchrome://browser/skin/notification-icons/minimize.svg\n\t\tchrome://browser/skin/notification-icons/persistent-storage-blocked.svg\n\t\tchrome://browser/skin/notification-icons/persistent-storage.svg\n\t\tchrome://browser/skin/notification-icons/popup.svg\n\t\tchrome://browser/skin/notification-icons/screen-blocked.svg\n\t\tchrome://browser/skin/notification-icons/screen.png\n\t\tchrome://browser/skin/notification-icons/screen.svg\n\t\tchrome://browser/skin/notification-icons/speaker.svg\n\t\tchrome://browser/skin/notification-icons/xr-blocked.svg\n\t\tchrome://browser/skin/notification-icons/xr.svg\n\t\tchrome://browser/skin/open.svg\n\t\tchrome://browser/skin/pageInfo.css\n\t\tchrome://browser/skin/pageInfo.png\n\t\tchrome://browser/skin/panic-panel/header.png\n\t\tchrome://browser/skin/panic-panel/header@2x.png\n\t\tchrome://browser/skin/panic-panel/icons.png\n\t\tchrome://browser/skin/panic-panel/icons@2x.png\n\t\tchrome://browser/skin/permissions.svg\n\t\tchrome://browser/skin/pin-12.svg\n\t\tchrome://browser/skin/places/bookmarksMenu.svg\n\t\tchrome://browser/skin/places/bookmarksToolbar.svg\n\t\tchrome://browser/skin/places/editBookmark.css\n\t\tchrome://browser/skin/places/editBookmarkPanel.css\n\t\tchrome://browser/skin/places/folder-smart.svg\n\t\tchrome://browser/skin/places/organizer-shared.css\n\t\tchrome://browser/skin/places/organizer.css\n\t\tchrome://browser/skin/places/sidebar.css\n\t\tchrome://browser/skin/places/tag.svg\n\t\tchrome://browser/skin/places/tree-icons.css\n\t\tchrome://browser/skin/preferences/alwaysAsk.png\n\t\tchrome://browser/skin/preferences/android-menu.svg\n\t\tchrome://browser/skin/preferences/application.png\n\t\tchrome://browser/skin/preferences/applications.css\n\t\tchrome://browser/skin/preferences/category-general.svg\n\t\tchrome://browser/skin/preferences/category-privacy-security.svg\n\t\tchrome://browser/skin/preferences/category-search.svg\n\t\tchrome://browser/skin/preferences/category-sync.svg\n\t\tchrome://browser/skin/preferences/containers-dialog.css\n\t\tchrome://browser/skin/preferences/containers.css\n\t\tchrome://browser/skin/preferences/dialog.css\n\t\tchrome://browser/skin/preferences/fxaPairDevice.css\n\t\tchrome://browser/skin/preferences/ios-menu.svg\n\t\tchrome://browser/skin/preferences/monitor-logo.svg\n\t\tchrome://browser/skin/preferences/mozilla-logo.svg\n\t\tchrome://browser/skin/preferences/preferences.css\n\t\tchrome://browser/skin/preferences/privacy.css\n\t\tchrome://browser/skin/preferences/relay-logo.svg\n\t\tchrome://browser/skin/preferences/saveFile.png\n\t\tchrome://browser/skin/preferences/search-arrow-indicator.svg\n\t\tchrome://browser/skin/preferences/search.css\n\t\tchrome://browser/skin/preferences/siteDataSettings.css\n\t\tchrome://browser/skin/preferences/translations.css\n\t\tchrome://browser/skin/preferences/vpn-logo.svg\n\t\tchrome://browser/skin/privateBrowsing.svg\n\t\tchrome://browser/skin/privatebrowsing/aboutPrivateBrowsing.css\n\t\tchrome://browser/skin/privatebrowsing/favicon.svg\n\t\tchrome://browser/skin/profiler-popup-backdrop.png\n\t\tchrome://browser/skin/protections/breached-password.svg\n\t\tchrome://browser/skin/protections/new-feature.svg\n\t\tchrome://browser/skin/protections/resolved-breach-gray.svg\n\t\tchrome://browser/skin/protections/resolved-breach.svg\n\t\tchrome://browser/skin/quickactions.svg\n\t\tchrome://browser/skin/reader-mode.svg\n\t\tchrome://browser/skin/reload-to-stop.svg\n\t\tchrome://browser/skin/sanitizeDialog.css\n\t\tchrome://browser/skin/sanitizeDialog_v2.css\n\t\tchrome://browser/skin/save.svg\n\t\tchrome://browser/skin/screenshot.svg\n\t\tchrome://browser/skin/search-engine-placeholder.png\n\t\tchrome://browser/skin/search-engine-placeholder@2x.png\n\t\tchrome://browser/skin/search-indicator-badge-add.svg\n\t\tchrome://browser/skin/searchbar.css\n\t\tchrome://browser/skin/setDesktopBackground.css\n\t\tchrome://browser/skin/share.svg\n\t\tchrome://browser/skin/sidebar-collapsed.svg\n\t\tchrome://browser/skin/sidebar-expanded.svg\n\t\tchrome://browser/skin/sidebar-hidden.svg\n\t\tchrome://browser/skin/sidebar-horizontal-tabs.svg\n\t\tchrome://browser/skin/sidebar-right.svg\n\t\tchrome://browser/skin/sidebar.css\n\t\tchrome://browser/skin/sidebars-right.svg\n\t\tchrome://browser/skin/sidebars.svg\n\t\tchrome://browser/skin/sort.svg\n\t\tchrome://browser/skin/stop-to-reload.svg\n\t\tchrome://browser/skin/subtract-circle-fill.svg\n\t\tchrome://browser/skin/success-animation.svg\n\t\tchrome://browser/skin/sync.svg\n\t\tchrome://browser/skin/synced-tabs.svg\n\t\tchrome://browser/skin/syncedtabs/sidebar.css\n\t\tchrome://browser/skin/tab-crashed.svg\n\t\tchrome://browser/skin/tab-list-tree.css\n\t\tchrome://browser/skin/tab.svg\n\t\tchrome://browser/skin/tabbrowser/content-area.css\n\t\tchrome://browser/skin/tabbrowser/crashed.svg\n\t\tchrome://browser/skin/tabbrowser/ctrlTab.css\n\t\tchrome://browser/skin/tabbrowser/fullscreen-and-pointerlock.css\n\t\tchrome://browser/skin/tabbrowser/loading-burst.svg\n\t\tchrome://browser/skin/tabbrowser/loading.svg\n\t\tchrome://browser/skin/tabbrowser/pendingpaint.png\n\t\tchrome://browser/skin/tabbrowser/tab-audio-blocked-small.svg\n\t\tchrome://browser/skin/tabbrowser/tab-audio-muted-small.svg\n\t\tchrome://browser/skin/tabbrowser/tab-audio-playing-small.svg\n\t\tchrome://browser/skin/tabbrowser/tab-connecting.png\n\t\tchrome://browser/skin/tabbrowser/tab-connecting@2x.png\n\t\tchrome://browser/skin/tabbrowser/tab-drag-indicator.svg\n\t\tchrome://browser/skin/tabbrowser/tab-hover-preview.css\n\t\tchrome://browser/skin/tabbrowser/tab-loading-inverted.png\n\t\tchrome://browser/skin/tabbrowser/tab-loading-inverted@2x.png\n\t\tchrome://browser/skin/tabbrowser/tab-loading.png\n\t\tchrome://browser/skin/tabbrowser/tab-loading@2x.png\n\t\tchrome://browser/skin/tabbrowser/tabs.css\n\t\tchrome://browser/skin/thumb-down.svg\n\t\tchrome://browser/skin/toolbar-drag-indicator.svg\n\t\tchrome://browser/skin/toolbarbutton-icons.css\n\t\tchrome://browser/skin/toolbarbuttons.css\n\t\tchrome://browser/skin/topsites.svg\n\t\tchrome://browser/skin/tracking-protection-active.svg\n\t\tchrome://browser/skin/tracking-protection-disabled.svg\n\t\tchrome://browser/skin/tracking-protection.svg\n\t\tchrome://browser/skin/translations.svg\n\t\tchrome://browser/skin/translations/beta.svg\n\t\tchrome://browser/skin/translations/panel.css\n\t\tchrome://browser/skin/trending.svg\n\t\tchrome://browser/skin/update-badge.svg\n\t\tchrome://browser/skin/urlbar-dynamic-results.css\n\t\tchrome://browser/skin/urlbar-searchbar.css\n\t\tchrome://browser/skin/urlbarView.css\n\t\tchrome://browser/skin/weather/cloudy.svg\n\t\tchrome://browser/skin/weather/flurries.svg\n\t\tchrome://browser/skin/weather/fog.svg\n\t\tchrome://browser/skin/weather/freezing-rain.svg\n\t\tchrome://browser/skin/weather/hazy-sunshine.svg\n\t\tchrome://browser/skin/weather/hot.svg\n\t\tchrome://browser/skin/weather/ice.svg\n\t\tchrome://browser/skin/weather/mostly-cloudy-with-showers.svg\n\t\tchrome://browser/skin/weather/mostly-cloudy-with-thunderstorms.svg\n\t\tchrome://browser/skin/weather/mostly-sunny.svg\n\t\tchrome://browser/skin/weather/night-clear.svg\n\t\tchrome://browser/skin/weather/night-hazy-moonlight.svg\n\t\tchrome://browser/skin/weather/night-mostly-clear.svg\n\t\tchrome://browser/skin/weather/night-mostly-cloudy-with-flurries.svg\n\t\tchrome://browser/skin/weather/night-partly-cloudy-with-showers.svg\n\t\tchrome://browser/skin/weather/night-partly-cloudy-with-thunderstorms.svg\n\t\tchrome://browser/skin/weather/partly-sunny-with-flurries.svg\n\t\tchrome://browser/skin/weather/partly-sunny.svg\n\t\tchrome://browser/skin/weather/rain.svg\n\t\tchrome://browser/skin/weather/showers.svg\n\t\tchrome://browser/skin/weather/snow.svg\n\t\tchrome://browser/skin/weather/sunny.svg\n\t\tchrome://browser/skin/weather/thunderstorms.svg\n\t\tchrome://browser/skin/weather/windy.svg\n\t\tchrome://browser/skin/webRTC-indicator.css\n\t\tchrome://browser/skin/webRTC-menubar-indicator.css\n\t\tchrome://browser/skin/window-controls/close-highcontrast.svg\n\t\tchrome://browser/skin/window-controls/close-themes.svg\n\t\tchrome://browser/skin/window-controls/close.svg\n\t\tchrome://browser/skin/window-controls/maximize-highcontrast.svg\n\t\tchrome://browser/skin/window-controls/maximize-themes.svg\n\t\tchrome://browser/skin/window-controls/maximize.svg\n\t\tchrome://browser/skin/window-controls/minimize-highcontrast.svg\n\t\tchrome://browser/skin/window-controls/minimize-themes.svg\n\t\tchrome://browser/skin/window-controls/minimize.svg\n\t\tchrome://browser/skin/window-controls/restore-highcontrast.svg\n\t\tchrome://browser/skin/window-controls/restore-themes.svg\n\t\tchrome://browser/skin/window-controls/restore.svg\n\t\tchrome://browser/skin/window.svg\n\t\tchrome://devtools-jsonview-styles/content/general.css\n\t\tchrome://devtools-jsonview-styles/content/headers-panel.css\n\t\tchrome://devtools-jsonview-styles/content/json-panel.css\n\t\tchrome://devtools-jsonview-styles/content/main.css\n\t\tchrome://devtools-jsonview-styles/content/search-box.css\n\t\tchrome://devtools-jsonview-styles/content/text-panel.css\n\t\tchrome://devtools-jsonview-styles/content/toolbar.css\n\t\tchrome://global/content/TopLevelVideoDocument.js\n\t\tchrome://global/content/aboutAbout.html\n\t\tchrome://global/content/aboutAbout.js\n\t\tchrome://global/content/aboutCheckerboard.css\n\t\tchrome://global/content/aboutCheckerboard.html\n\t\tchrome://global/content/aboutCheckerboard.js\n\t\tchrome://global/content/aboutGlean.css\n\t\tchrome://global/content/aboutGlean.html\n\t\tchrome://global/content/aboutGlean.js\n\t\tchrome://global/content/aboutLogging.html\n\t\tchrome://global/content/aboutLogging.js\n\t\tchrome://global/content/aboutMemory.css\n\t\tchrome://global/content/aboutMemory.js\n\t\tchrome://global/content/aboutMemory.xhtml\n\t\tchrome://global/content/aboutMozilla.css\n\t\tchrome://global/content/aboutNetError.html\n\t\tchrome://global/content/aboutNetError.mjs\n\t\tchrome://global/content/aboutNetworking.html\n\t\tchrome://global/content/aboutNetworking.js\n\t\tchrome://global/content/aboutProcesses.css\n\t\tchrome://global/content/aboutProcesses.html\n\t\tchrome://global/content/aboutProcesses.js\n\t\tchrome://global/content/aboutProfiles.js\n\t\tchrome://global/content/aboutProfiles.xhtml\n\t\tchrome://global/content/aboutRights.js\n\t\tchrome://global/content/aboutRights.xhtml\n\t\tchrome://global/content/aboutServiceWorkers.xhtml\n\t\tchrome://global/content/aboutSupport.js\n\t\tchrome://global/content/aboutSupport.xhtml\n\t\tchrome://global/content/aboutTelemetry.css\n\t\tchrome://global/content/aboutTelemetry.js\n\t\tchrome://global/content/aboutTelemetry.xhtml\n\t\tchrome://global/content/aboutThirdParty.css\n\t\tchrome://global/content/aboutThirdParty.html\n\t\tchrome://global/content/aboutThirdParty.js\n\t\tchrome://global/content/aboutUrlClassifier.css\n\t\tchrome://global/content/aboutUrlClassifier.js\n\t\tchrome://global/content/aboutUrlClassifier.xhtml\n\t\tchrome://global/content/aboutWebauthn.css\n\t\tchrome://global/content/aboutWebauthn.html\n\t\tchrome://global/content/aboutWebauthn.js\n\t\tchrome://global/content/aboutWindowsMessages.css\n\t\tchrome://global/content/aboutWindowsMessages.html\n\t\tchrome://global/content/aboutWindowsMessages.js\n\t\tchrome://global/content/aboutconfig/aboutconfig.css\n\t\tchrome://global/content/aboutconfig/aboutconfig.html\n\t\tchrome://global/content/aboutconfig/aboutconfig.js\n\t\tchrome://global/content/aboutconfig/background.svg\n\t\tchrome://global/content/aboutconfig/toggle.svg\n\t\tchrome://global/content/aboutwebrtc/aboutWebrtc.css\n\t\tchrome://global/content/aboutwebrtc/aboutWebrtc.html\n\t\tchrome://global/content/aboutwebrtc/aboutWebrtc.mjs\n\t\tchrome://global/content/aboutwebrtc/configurationList.mjs\n\t\tchrome://global/content/aboutwebrtc/copyButton.mjs\n\t\tchrome://global/content/aboutwebrtc/disclosure.mjs\n\t\tchrome://global/content/aboutwebrtc/graph.mjs\n\t\tchrome://global/content/aboutwebrtc/graphdb.mjs\n\t\tchrome://global/content/adjustableTitle.js\n\t\tchrome://global/content/alerts/alert.css\n\t\tchrome://global/content/alerts/alert.js\n\t\tchrome://global/content/alerts/alert.xhtml\n\t\tchrome://global/content/antitracking/StripOnShare.json\n\t\tchrome://global/content/antitracking/StripOnShareLGPL.json\n\t\tchrome://global/content/appPicker.js\n\t\tchrome://global/content/appPicker.xhtml\n\t\tchrome://global/content/autocomplete.css\n\t\tchrome://global/content/backgroundPageThumbs.xhtml\n\t\tchrome://global/content/bindings/calendar.js\n\t\tchrome://global/content/bindings/datekeeper.js\n\t\tchrome://global/content/bindings/datepicker.js\n\t\tchrome://global/content/bindings/datetimebox.css\n\t\tchrome://global/content/bindings/spinner.js\n\t\tchrome://global/content/bindings/timekeeper.js\n\t\tchrome://global/content/bindings/timepicker.js\n\t\tchrome://global/content/buildconfig.css\n\t\tchrome://global/content/buildconfig.html\n\t\tchrome://global/content/certviewer/certDecoder.mjs\n\t\tchrome://global/content/certviewer/certviewer.css\n\t\tchrome://global/content/certviewer/certviewer.html\n\t\tchrome://global/content/certviewer/certviewer.mjs\n\t\tchrome://global/content/certviewer/components/about-certificate-items.mjs\n\t\tchrome://global/content/certviewer/components/about-certificate-section.css\n\t\tchrome://global/content/certviewer/components/about-certificate-section.mjs\n\t\tchrome://global/content/certviewer/components/certificate-section.css\n\t\tchrome://global/content/certviewer/components/certificate-section.mjs\n\t\tchrome://global/content/certviewer/components/certificate-tabs-section.mjs\n\t\tchrome://global/content/certviewer/components/error-section.css\n\t\tchrome://global/content/certviewer/components/error-section.mjs\n\t\tchrome://global/content/certviewer/components/info-group-container.mjs\n\t\tchrome://global/content/certviewer/components/info-group.css\n\t\tchrome://global/content/certviewer/components/info-group.mjs\n\t\tchrome://global/content/certviewer/components/info-item.css\n\t\tchrome://global/content/certviewer/components/info-item.mjs\n\t\tchrome://global/content/certviewer/components/list-item.css\n\t\tchrome://global/content/certviewer/components/list-item.mjs\n\t\tchrome://global/content/certviewer/components/utils.mjs\n\t\tchrome://global/content/certviewer/vendor/pkijs.js\n\t\tchrome://global/content/commonDialog.css\n\t\tchrome://global/content/commonDialog.js\n\t\tchrome://global/content/commonDialog.xhtml\n\t\tchrome://global/content/contentAreaUtils.js\n\t\tchrome://global/content/cookiebanners/CookieBannerRule.schema.json\n\t\tchrome://global/content/crashes.css\n\t\tchrome://global/content/crashes.html\n\t\tchrome://global/content/crashes.js\n\t\tchrome://global/content/customElements.js\n\t\tchrome://global/content/datepicker.xhtml\n\t\tchrome://global/content/defaultagent/fox-doodle-peek.png\n\t\tchrome://global/content/editMenuOverlay.js\n\t\tchrome://global/content/elements/arrowscrollbox.js\n\t\tchrome://global/content/elements/autocomplete-input.js\n\t\tchrome://global/content/elements/autocomplete-popup.js\n\t\tchrome://global/content/elements/autocomplete-richlistitem.js\n\t\tchrome://global/content/elements/browser-custom-element.js\n\t\tchrome://global/content/elements/button.js\n\t\tchrome://global/content/elements/checkbox.js\n\t\tchrome://global/content/elements/datetimebox.js\n\t\tchrome://global/content/elements/dialog.js\n\t\tchrome://global/content/elements/editor.js\n\t\tchrome://global/content/elements/findbar.js\n\t\tchrome://global/content/elements/general.js\n\t\tchrome://global/content/elements/infobar.css\n\t\tchrome://global/content/elements/marquee.css\n\t\tchrome://global/content/elements/marquee.js\n\t\tchrome://global/content/elements/menu.js\n\t\tchrome://global/content/elements/menulist.js\n\t\tchrome://global/content/elements/menupopup.js\n\t\tchrome://global/content/elements/moz-button-group.css\n\t\tchrome://global/content/elements/moz-button-group.mjs\n\t\tchrome://global/content/elements/moz-button.css\n\t\tchrome://global/content/elements/moz-button.mjs\n\t\tchrome://global/content/elements/moz-card.css\n\t\tchrome://global/content/elements/moz-card.mjs\n\t\tchrome://global/content/elements/moz-checkbox.css\n\t\tchrome://global/content/elements/moz-checkbox.mjs\n\t\tchrome://global/content/elements/moz-fieldset.css\n\t\tchrome://global/content/elements/moz-fieldset.mjs\n\t\tchrome://global/content/elements/moz-five-star.css\n\t\tchrome://global/content/elements/moz-five-star.mjs\n\t\tchrome://global/content/elements/moz-input-box.js\n\t\tchrome://global/content/elements/moz-label.css\n\t\tchrome://global/content/elements/moz-label.mjs\n\t\tchrome://global/content/elements/moz-message-bar.css\n\t\tchrome://global/content/elements/moz-message-bar.mjs\n\t\tchrome://global/content/elements/moz-page-nav-button.css\n\t\tchrome://global/content/elements/moz-page-nav.css\n\t\tchrome://global/content/elements/moz-page-nav.mjs\n\t\tchrome://global/content/elements/moz-radio-group.css\n\t\tchrome://global/content/elements/moz-radio-group.mjs\n\t\tchrome://global/content/elements/moz-radio.css\n\t\tchrome://global/content/elements/moz-support-link.mjs\n\t\tchrome://global/content/elements/moz-toggle.css\n\t\tchrome://global/content/elements/moz-toggle.mjs\n\t\tchrome://global/content/elements/named-deck.js\n\t\tchrome://global/content/elements/notificationbox.js\n\t\tchrome://global/content/elements/panel-item.css\n\t\tchrome://global/content/elements/panel-list.css\n\t\tchrome://global/content/elements/panel-list.js\n\t\tchrome://global/content/elements/panel.js\n\t\tchrome://global/content/elements/popupnotification.js\n\t\tchrome://global/content/elements/radio.js\n\t\tchrome://global/content/elements/richlistbox.js\n\t\tchrome://global/content/elements/search-textbox.js\n\t\tchrome://global/content/elements/stringbundle.js\n\t\tchrome://global/content/elements/tabbox.js\n\t\tchrome://global/content/elements/text.js\n\t\tchrome://global/content/elements/textrecognition.js\n\t\tchrome://global/content/elements/toolbarbutton.js\n\t\tchrome://global/content/elements/tree.js\n\t\tchrome://global/content/elements/videocontrols.js\n\t\tchrome://global/content/elements/wizard.js\n\t\tchrome://global/content/filepicker.properties\n\t\tchrome://global/content/globalOverlay.js\n\t\tchrome://global/content/gmp-sources/openh264.json\n\t\tchrome://global/content/gmp-sources/widevinecdm.json\n\t\tchrome://global/content/gmp-sources/widevinecdm_l1.json\n\t\tchrome://global/content/httpsonlyerror/errorpage.html\n\t\tchrome://global/content/httpsonlyerror/errorpage.js\n\t\tchrome://global/content/httpsonlyerror/secure-broken.svg\n\t\tchrome://global/content/license.html\n\t\tchrome://global/content/lit-utils.mjs\n\t\tchrome://global/content/macWindowMenu.js\n\t\tchrome://global/content/megalist/Dialog.mjs\n\t\tchrome://global/content/megalist/LoginFormComponent/login-form.css\n\t\tchrome://global/content/megalist/LoginLine.css\n\t\tchrome://global/content/megalist/LoginLine.mjs\n\t\tchrome://global/content/megalist/MegalistAlpha.mjs\n\t\tchrome://global/content/megalist/MegalistView.mjs\n\t\tchrome://global/content/megalist/NotificationMessageBar.mjs\n\t\tchrome://global/content/megalist/PasswordCard.css\n\t\tchrome://global/content/megalist/PasswordCard.mjs\n\t\tchrome://global/content/megalist/VirtualizedList.mjs\n\t\tchrome://global/content/megalist/megalist.css\n\t\tchrome://global/content/megalist/megalist.html\n\t\tchrome://global/content/ml/EngineProcess.sys.mjs\n\t\tchrome://global/content/ml/MLEngine.html\n\t\tchrome://global/content/ml/MLEngine.worker.mjs\n\t\tchrome://global/content/ml/ModelHub.sys.mjs\n\t\tchrome://global/content/ml/ONNXPipeline.mjs\n\t\tchrome://global/content/ml/Utils.sys.mjs\n\t\tchrome://global/content/ml/ort-wasm-simd-threaded.jsep.mjs\n\t\tchrome://global/content/ml/ort-wasm-simd-threaded.mjs\n\t\tchrome://global/content/ml/ort.mjs\n\t\tchrome://global/content/ml/ort.webgpu.mjs\n\t\tchrome://global/content/ml/transformers.js\n\t\tchrome://global/content/mozilla.html\n\t\tchrome://global/content/neterror/aboutNetErrorCodes.js\n\t\tchrome://global/content/neterror/supportpages/connection-not-secure.html\n\t\tchrome://global/content/neterror/supportpages/time-errors.html\n\t\tchrome://global/content/notfound.wav\n\t\tchrome://global/content/pictureinpicture/player.js\n\t\tchrome://global/content/pictureinpicture/player.xhtml\n\t\tchrome://global/content/preferencesBindings.js\n\t\tchrome://global/content/print.css\n\t\tchrome://global/content/print.html\n\t\tchrome://global/content/print.js\n\t\tchrome://global/content/printPageSetup.js\n\t\tchrome://global/content/printPageSetup.xhtml\n\t\tchrome://global/content/printPagination.css\n\t\tchrome://global/content/printPreview.css\n\t\tchrome://global/content/printPreviewPagination.js\n\t\tchrome://global/content/printUtils.js\n\t\tchrome://global/content/process-content.js\n\t\tchrome://global/content/reader/aboutReader.html\n\t\tchrome://global/content/reader/color-input.css\n\t\tchrome://global/content/reader/color-input.mjs\n\t\tchrome://global/content/reader/moz-slider.css\n\t\tchrome://global/content/reader/moz-slider.mjs\n\t\tchrome://global/content/resetProfile.css\n\t\tchrome://global/content/resetProfile.js\n\t\tchrome://global/content/resetProfile.xhtml\n\t\tchrome://global/content/resetProfileProgress.xhtml\n\t\tchrome://global/content/selectDialog.css\n\t\tchrome://global/content/selectDialog.js\n\t\tchrome://global/content/selectDialog.xhtml\n\t\tchrome://global/content/shopping/ProductConfig.mjs\n\t\tchrome://global/content/shopping/ProductValidator.sys.mjs\n\t\tchrome://global/content/shopping/ShoppingProduct.mjs\n\t\tchrome://global/content/shopping/analysis_request.schema.json\n\t\tchrome://global/content/shopping/analysis_response.schema.json\n\t\tchrome://global/content/shopping/analysis_status_request.schema.json\n\t\tchrome://global/content/shopping/analysis_status_response.schema.json\n\t\tchrome://global/content/shopping/analyze_request.schema.json\n\t\tchrome://global/content/shopping/analyze_response.schema.json\n\t\tchrome://global/content/shopping/attribution_request.schema.json\n\t\tchrome://global/content/shopping/attribution_response.schema.json\n\t\tchrome://global/content/shopping/recommendations_request.schema.json\n\t\tchrome://global/content/shopping/recommendations_response.schema.json\n\t\tchrome://global/content/shopping/reporting_request.schema.json\n\t\tchrome://global/content/shopping/reporting_response.schema.json\n\t\tchrome://global/content/simplifyMode.css\n\t\tchrome://global/content/third_party/cfworker/json-schema.js\n\t\tchrome://global/content/third_party/d3/d3.js\n\t\tchrome://global/content/timepicker.xhtml\n\t\tchrome://global/content/toggle-group.css\n\t\tchrome://global/content/translations/TranslationsTelemetry.sys.mjs\n\t\tchrome://global/content/translations/Translator.mjs\n\t\tchrome://global/content/translations/bergamot-translator.js\n\t\tchrome://global/content/translations/icons/swap-languages.svg\n\t\tchrome://global/content/translations/translations-document.sys.mjs\n\t\tchrome://global/content/translations/translations-engine.html\n\t\tchrome://global/content/translations/translations-engine.sys.mjs\n\t\tchrome://global/content/translations/translations-engine.worker.js\n\t\tchrome://global/content/translations/translations.css\n\t\tchrome://global/content/translations/translations.html\n\t\tchrome://global/content/translations/translations.mjs\n\t\tchrome://global/content/treeUtils.js\n\t\tchrome://global/content/usercharacteristics/gl-matrix.js\n\t\tchrome://global/content/usercharacteristics/ssdeep.js\n\t\tchrome://global/content/usercharacteristics/usercharacteristics.css\n\t\tchrome://global/content/usercharacteristics/usercharacteristics.html\n\t\tchrome://global/content/usercharacteristics/usercharacteristics.js\n\t\tchrome://global/content/vendor/lit.all.mjs\n\t\tchrome://global/content/viewSourceUtils.js\n\t\tchrome://global/content/viewZoomOverlay.js\n\t\tchrome://global/content/widgets.css\n\t\tchrome://global/content/win.xhtml\n\t\tchrome://global/content/xml/XMLPrettyPrint.css\n\t\tchrome://global/content/xml/XMLPrettyPrint.xsl\n\t\tchrome://global/content/xul.css\n\t\tchrome://global/locale/AccessFu.properties\n\t\tchrome://global/locale/aboutStudies.properties\n\t\tchrome://global/locale/appstrings.properties\n\t\tchrome://global/locale/autocomplete.properties\n\t\tchrome://global/locale/browser.properties\n\t\tchrome://global/locale/commonDialogs.properties\n\t\tchrome://global/locale/contentAreaCommands.properties\n\t\tchrome://global/locale/css.properties\n\t\tchrome://global/locale/dialog.properties\n\t\tchrome://global/locale/dom/dom.properties\n\t\tchrome://global/locale/extensions.properties\n\t\tchrome://global/locale/fallbackMenubar.properties\n\t\tchrome://global/locale/filepicker.properties\n\t\tchrome://global/locale/global-strres.properties\n\t\tchrome://global/locale/intl.css\n\t\tchrome://global/locale/intl.properties\n\t\tchrome://global/locale/keys.properties\n\t\tchrome://global/locale/layout/HtmlForm.properties\n\t\tchrome://global/locale/layout/MediaDocument.properties\n\t\tchrome://global/locale/layout/htmlparser.properties\n\t\tchrome://global/locale/layout/xmlparser.properties\n\t\tchrome://global/locale/layout_errors.properties\n\t\tchrome://global/locale/mathml/mathml.properties\n\t\tchrome://global/locale/narrate.properties\n\t\tchrome://global/locale/nsWebBrowserPersist.properties\n\t\tchrome://global/locale/printdialog.properties\n\t\tchrome://global/locale/printing.properties\n\t\tchrome://global/locale/resetProfile.properties\n\t\tchrome://global/locale/security/caps.properties\n\t\tchrome://global/locale/security/csp.properties\n\t\tchrome://global/locale/security/security.properties\n\t\tchrome://global/locale/svg/svg.properties\n\t\tchrome://global/locale/viewSource.properties\n\t\tchrome://global/locale/wizard.properties\n\t\tchrome://global/locale/xslt/xslt.properties\n\t\tchrome://global/locale/xul.properties\n\t\tchrome://global/skin/aboutCache.css\n\t\tchrome://global/skin/aboutCacheEntry.css\n\t\tchrome://global/skin/aboutHttpsOnlyError.css\n\t\tchrome://global/skin/aboutLicense.css\n\t\tchrome://global/skin/aboutLogging.css\n\t\tchrome://global/skin/aboutMemory.css\n\t\tchrome://global/skin/aboutNetError.css\n\t\tchrome://global/skin/aboutNetworking.css\n\t\tchrome://global/skin/aboutReader.css\n\t\tchrome://global/skin/aboutRights.css\n\t\tchrome://global/skin/aboutSupport.css\n\t\tchrome://global/skin/alert.css\n\t\tchrome://global/skin/appPicker.css\n\t\tchrome://global/skin/arrow/panelarrow-vertical.svg\n\t\tchrome://global/skin/arrowscrollbox.css\n\t\tchrome://global/skin/autocomplete.css\n\t\tchrome://global/skin/button.css\n\t\tchrome://global/skin/checkbox.css\n\t\tchrome://global/skin/close-icon.css\n\t\tchrome://global/skin/commonDialog.css\n\t\tchrome://global/skin/datetimeinputpickers.css\n\t\tchrome://global/skin/design-system/text-and-typography.css\n\t\tchrome://global/skin/design-system/tokens-brand.css\n\t\tchrome://global/skin/design-system/tokens-platform.css\n\t\tchrome://global/skin/design-system/tokens-shared.css\n\t\tchrome://global/skin/dialog.css\n\t\tchrome://global/skin/dirListing/dirListing.css\n\t\tchrome://global/skin/dirListing/folder.png\n\t\tchrome://global/skin/dirListing/up.png\n\t\tchrome://global/skin/error-pages.css\n\t\tchrome://global/skin/findbar.css\n\t\tchrome://global/skin/global-shared.css\n\t\tchrome://global/skin/global.css\n\t\tchrome://global/skin/icons/Authentication.png\n\t\tchrome://global/skin/icons/Landscape.png\n\t\tchrome://global/skin/icons/Portrait.png\n\t\tchrome://global/skin/icons/arrow-down-12.svg\n\t\tchrome://global/skin/icons/arrow-down.svg\n\t\tchrome://global/skin/icons/arrow-left-12.svg\n\t\tchrome://global/skin/icons/arrow-left.svg\n\t\tchrome://global/skin/icons/arrow-right-12.svg\n\t\tchrome://global/skin/icons/arrow-right.svg\n\t\tchrome://global/skin/icons/arrow-up-12.svg\n\t\tchrome://global/skin/icons/arrow-up.svg\n\t\tchrome://global/skin/icons/autoscroll-horizontal.svg\n\t\tchrome://global/skin/icons/autoscroll-vertical.svg\n\t\tchrome://global/skin/icons/autoscroll.svg\n\t\tchrome://global/skin/icons/badge-blue.svg\n\t\tchrome://global/skin/icons/blocked.svg\n\t\tchrome://global/skin/icons/check-filled.svg\n\t\tchrome://global/skin/icons/check-partial.svg\n\t\tchrome://global/skin/icons/check.svg\n\t\tchrome://global/skin/icons/chevron.svg\n\t\tchrome://global/skin/icons/clipboard.svg\n\t\tchrome://global/skin/icons/close-12.svg\n\t\tchrome://global/skin/icons/close-fill.svg\n\t\tchrome://global/skin/icons/close.svg\n\t\tchrome://global/skin/icons/columnpicker.svg\n\t\tchrome://global/skin/icons/content-analysis.svg\n\t\tchrome://global/skin/icons/defaultFavicon.svg\n\t\tchrome://global/skin/icons/delete.svg\n\t\tchrome://global/skin/icons/developer.svg\n\t\tchrome://global/skin/icons/edit-copy.svg\n\t\tchrome://global/skin/icons/edit-outline.svg\n\t\tchrome://global/skin/icons/edit.svg\n\t\tchrome://global/skin/icons/error-64.png\n\t\tchrome://global/skin/icons/error.svg\n\t\tchrome://global/skin/icons/experiments.svg\n\t\tchrome://global/skin/icons/folder.svg\n\t\tchrome://global/skin/icons/heart.svg\n\t\tchrome://global/skin/icons/help.svg\n\t\tchrome://global/skin/icons/highlights.svg\n\t\tchrome://global/skin/icons/indicator-private-browsing.svg\n\t\tchrome://global/skin/icons/info-filled.svg\n\t\tchrome://global/skin/icons/info.svg\n\t\tchrome://global/skin/icons/lightbulb.svg\n\t\tchrome://global/skin/icons/link.svg\n\t\tchrome://global/skin/icons/loading.png\n\t\tchrome://global/skin/icons/loading.svg\n\t\tchrome://global/skin/icons/loading@2x.png\n\t\tchrome://global/skin/icons/mdn.svg\n\t\tchrome://global/skin/icons/menu-check.svg\n\t\tchrome://global/skin/icons/minus.svg\n\t\tchrome://global/skin/icons/more.svg\n\t\tchrome://global/skin/icons/newsfeed.svg\n\t\tchrome://global/skin/icons/open-in-new.svg\n\t\tchrome://global/skin/icons/page-landscape.svg\n\t\tchrome://global/skin/icons/page-portrait.svg\n\t\tchrome://global/skin/icons/pendingpaint.png\n\t\tchrome://global/skin/icons/performance.svg\n\t\tchrome://global/skin/icons/plugin.svg\n\t\tchrome://global/skin/icons/plus-20.svg\n\t\tchrome://global/skin/icons/plus.svg\n\t\tchrome://global/skin/icons/pocket-favicon.ico\n\t\tchrome://global/skin/icons/pocket-outline.svg\n\t\tchrome://global/skin/icons/pocket.svg\n\t\tchrome://global/skin/icons/print.svg\n\t\tchrome://global/skin/icons/question-64.png\n\t\tchrome://global/skin/icons/rating-star.svg\n\t\tchrome://global/skin/icons/reload.svg\n\t\tchrome://global/skin/icons/resizer.svg\n\t\tchrome://global/skin/icons/search-glass.svg\n\t\tchrome://global/skin/icons/search-textbox.svg\n\t\tchrome://global/skin/icons/security-broken.svg\n\t\tchrome://global/skin/icons/security-warning.svg\n\t\tchrome://global/skin/icons/security.svg\n\t\tchrome://global/skin/icons/settings.svg\n\t\tchrome://global/skin/icons/sort-arrow.svg\n\t\tchrome://global/skin/icons/thumbs-down-20.svg\n\t\tchrome://global/skin/icons/thumbs-up-20.svg\n\t\tchrome://global/skin/icons/trending.svg\n\t\tchrome://global/skin/icons/undo.svg\n\t\tchrome://global/skin/icons/update-icon.svg\n\t\tchrome://global/skin/icons/warning-64.png\n\t\tchrome://global/skin/icons/warning-fill-12.svg\n\t\tchrome://global/skin/icons/warning-large.png\n\t\tchrome://global/skin/icons/warning.svg\n\t\tchrome://global/skin/illustrations/about-license.svg\n\t\tchrome://global/skin/illustrations/about-rights.svg\n\t\tchrome://global/skin/illustrations/error-malformed-url.svg\n\t\tchrome://global/skin/in-content/common-shared.css\n\t\tchrome://global/skin/in-content/common.css\n\t\tchrome://global/skin/in-content/info-pages.css\n\t\tchrome://global/skin/in-content/wifi.svg\n\t\tchrome://global/skin/media/audio-muted.svg\n\t\tchrome://global/skin/media/audio.svg\n\t\tchrome://global/skin/media/audioNoAudioButton.svg\n\t\tchrome://global/skin/media/castingButton-active.svg\n\t\tchrome://global/skin/media/castingButton-ready.svg\n\t\tchrome://global/skin/media/closed-caption-settings-button.svg\n\t\tchrome://global/skin/media/closedCaptionButton-cc-off.svg\n\t\tchrome://global/skin/media/closedCaptionButton-cc-on.svg\n\t\tchrome://global/skin/media/error.png\n\t\tchrome://global/skin/media/fullscreenEnterButton.svg\n\t\tchrome://global/skin/media/fullscreenExitButton.svg\n\t\tchrome://global/skin/media/imagedoc-darknoise.png\n\t\tchrome://global/skin/media/imagedoc-lightnoise.png\n\t\tchrome://global/skin/media/pause-fill.svg\n\t\tchrome://global/skin/media/picture-in-picture-closed.svg\n\t\tchrome://global/skin/media/picture-in-picture-enter-fullscreen-button.svg\n\t\tchrome://global/skin/media/picture-in-picture-exit-fullscreen-button.svg\n\t\tchrome://global/skin/media/picture-in-picture-open.svg\n\t\tchrome://global/skin/media/picture-in-picture-seekBackward-button.svg\n\t\tchrome://global/skin/media/picture-in-picture-seekForward-button.svg\n\t\tchrome://global/skin/media/pipToggle.css\n\t\tchrome://global/skin/media/play-fill.svg\n\t\tchrome://global/skin/media/stalled.png\n\t\tchrome://global/skin/media/textrecognition.css\n\t\tchrome://global/skin/media/throbber.png\n\t\tchrome://global/skin/media/videocontrols.css\n\t\tchrome://global/skin/menu-shared.css\n\t\tchrome://global/skin/menu.css\n\t\tchrome://global/skin/menulist-shared.css\n\t\tchrome://global/skin/menulist.css\n\t\tchrome://global/skin/narrate-improved.css\n\t\tchrome://global/skin/narrate.css\n\t\tchrome://global/skin/narrate/arrow.svg\n\t\tchrome://global/skin/narrate/back.svg\n\t\tchrome://global/skin/narrate/fast.svg\n\t\tchrome://global/skin/narrate/forward.svg\n\t\tchrome://global/skin/narrate/headphone-active.svg\n\t\tchrome://global/skin/narrate/headphone.svg\n\t\tchrome://global/skin/narrate/skip-backward-20.svg\n\t\tchrome://global/skin/narrate/skip-forward-20.svg\n\t\tchrome://global/skin/narrate/slow.svg\n\t\tchrome://global/skin/narrate/start.svg\n\t\tchrome://global/skin/narrate/stop.svg\n\t\tchrome://global/skin/notification.css\n\t\tchrome://global/skin/numberinput.css\n\t\tchrome://global/skin/offlineSupportPages.css\n\t\tchrome://global/skin/pictureinpicture/player.css\n\t\tchrome://global/skin/pictureinpicture/texttracks.css\n\t\tchrome://global/skin/popup.css\n\t\tchrome://global/skin/popupnotification.css\n\t\tchrome://global/skin/printPageSetup.css\n\t\tchrome://global/skin/radio.css\n\t\tchrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg\n\t\tchrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg\n\t\tchrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg\n\t\tchrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg\n\t\tchrome://global/skin/reader/RM-Minus-24x24.svg\n\t\tchrome://global/skin/reader/RM-Plus-24x24.svg\n\t\tchrome://global/skin/reader/RM-Sans-Serif.svg\n\t\tchrome://global/skin/reader/RM-Serif.svg\n\t\tchrome://global/skin/reader/RM-Type-Controls-24x24.svg\n\t\tchrome://global/skin/reader/align-center-20.svg\n\t\tchrome://global/skin/reader/align-left-20.svg\n\t\tchrome://global/skin/reader/align-right-20.svg\n\t\tchrome://global/skin/reader/character-spacing-20.svg\n\t\tchrome://global/skin/reader/content-width-20.svg\n\t\tchrome://global/skin/reader/line-spacing-20.svg\n\t\tchrome://global/skin/reader/word-spacing-20.svg\n\t\tchrome://global/skin/richlistbox.css\n\t\tchrome://global/skin/search-textbox.css\n\t\tchrome://global/skin/splitter.css\n\t\tchrome://global/skin/tabbox.css\n\t\tchrome://global/skin/toolbar.css\n\t\tchrome://global/skin/toolbarbutton.css\n\t\tchrome://global/skin/tree/sort-asc.svg\n\t\tchrome://global/skin/tree/sort-dsc.svg\n\t\tchrome://global/skin/tree/tree.css\n\t\tchrome://global/skin/wizard.css\n\t\tchrome://pocket/content/Pocket.sys.mjs\n\t\tchrome://pocket/content/SaveToPocket.sys.mjs\n\t\tchrome://pocket/content/panels/css/global.scss\n\t\tchrome://pocket/content/panels/css/home.scss\n\t\tchrome://pocket/content/panels/css/main.compiled.css\n\t\tchrome://pocket/content/panels/css/main.scss\n\t\tchrome://pocket/content/panels/css/normalize.scss\n\t\tchrome://pocket/content/panels/css/panel.scss\n\t\tchrome://pocket/content/panels/css/saved.scss\n\t\tchrome://pocket/content/panels/css/signup.scss\n\t\tchrome://pocket/content/panels/css/styleguide.scss\n\t\tchrome://pocket/content/panels/fonts/FiraSans-Regular.woff\n\t\tchrome://pocket/content/panels/home.html\n\t\tchrome://pocket/content/panels/img/chevron-right.svg\n\t\tchrome://pocket/content/panels/img/list-view.svg\n\t\tchrome://pocket/content/panels/img/open.svg\n\t\tchrome://pocket/content/panels/img/pocketerror@1x.png\n\t\tchrome://pocket/content/panels/img/pocketerror@2x.png\n\t\tchrome://pocket/content/panels/img/pocketlogo-dark.svg\n\t\tchrome://pocket/content/panels/img/pocketlogo.svg\n\t\tchrome://pocket/content/panels/img/pocketlogo@1x.png\n\t\tchrome://pocket/content/panels/img/pocketlogo@2x.png\n\t\tchrome://pocket/content/panels/img/pocketlogosolo@1x.png\n\t\tchrome://pocket/content/panels/img/pocketlogosolo@2x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_button@1x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_button@2x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_devices@1x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_devices@2x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_hero@1x.png\n\t\tchrome://pocket/content/panels/img/pocketsignup_hero@2x.png\n\t\tchrome://pocket/content/panels/img/rainbow-reader.svg\n\t\tchrome://pocket/content/panels/img/signup_firefoxlogo@1x.png\n\t\tchrome://pocket/content/panels/img/signup_firefoxlogo@2x.png\n\t\tchrome://pocket/content/panels/img/signup_help@1x.png\n\t\tchrome://pocket/content/panels/img/signup_help@2x.png\n\t\tchrome://pocket/content/panels/img/tag_close@1x.png\n\t\tchrome://pocket/content/panels/img/tag_close@2x.png\n\t\tchrome://pocket/content/panels/img/tag_closeactive@1x.png\n\t\tchrome://pocket/content/panels/img/tag_closeactive@2x.png\n\t\tchrome://pocket/content/panels/js/home/entry.js\n\t\tchrome://pocket/content/panels/js/main.bundle.js\n\t\tchrome://pocket/content/panels/js/saved/entry.js\n\t\tchrome://pocket/content/panels/js/signup/entry.js\n\t\tchrome://pocket/content/panels/js/style-guide/entry.js\n\t\tchrome://pocket/content/panels/js/vendor.bundle.js\n\t\tchrome://pocket/content/panels/saved.html\n\t\tchrome://pocket/content/panels/signup.html\n\t\tchrome://pocket/content/panels/style-guide.html\n\t\tchrome://pocket/content/pktApi.sys.mjs\n\t\tchrome://pocket/content/pktTelemetry.sys.mjs\n\t\tchrome://pocket/content/pktUI.js\n\t\tresource://activity-stream/common/Actions.mjs\n\t\tresource://activity-stream/common/Dedupe.sys.mjs\n\t\tresource://activity-stream/common/Reducers.sys.mjs\n\t\tresource://activity-stream/data/content/abouthomecache/page.html.template\n\t\tresource://activity-stream/data/content/abouthomecache/script.js.template\n\t\tresource://activity-stream/data/content/activity-stream.bundle.js\n\t\tresource://activity-stream/data/content/newtab-render.js\n\t\tresource://activity-stream/data/custom-elements/paragraph.js\n\t\tresource://activity-stream/lib/AboutPreferences.sys.mjs\n\t\tresource://activity-stream/lib/ActivityStream.sys.mjs\n\t\tresource://activity-stream/lib/ActivityStreamMessageChannel.sys.mjs\n\t\tresource://activity-stream/lib/ActivityStreamPrefs.sys.mjs\n\t\tresource://activity-stream/lib/ActivityStreamStorage.sys.mjs\n\t\tresource://activity-stream/lib/DefaultSites.sys.mjs\n\t\tresource://activity-stream/lib/DiscoveryStreamFeed.sys.mjs\n\t\tresource://activity-stream/lib/DownloadsManager.sys.mjs\n\t\tresource://activity-stream/lib/FaviconFeed.sys.mjs\n\t\tresource://activity-stream/lib/FilterAdult.sys.mjs\n\t\tresource://activity-stream/lib/HighlightsFeed.sys.mjs\n\t\tresource://activity-stream/lib/LinksCache.sys.mjs\n\t\tresource://activity-stream/lib/NewTabInit.sys.mjs\n\t\tresource://activity-stream/lib/PersistentCache.sys.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/NaiveBayesTextTagger.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/NmfTextTagger.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/PersonalityProvider.sys.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/PersonalityProvider.worker.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/PersonalityProviderWorkerClass.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/RecipeExecutor.mjs\n\t\tresource://activity-stream/lib/PersonalityProvider/Tokenize.mjs\n\t\tresource://activity-stream/lib/PlacesFeed.sys.mjs\n\t\tresource://activity-stream/lib/PrefsFeed.sys.mjs\n\t\tresource://activity-stream/lib/RecommendationProvider.sys.mjs\n\t\tresource://activity-stream/lib/Screenshots.sys.mjs\n\t\tresource://activity-stream/lib/SearchShortcuts.sys.mjs\n\t\tresource://activity-stream/lib/SectionsManager.sys.mjs\n\t\tresource://activity-stream/lib/ShortURL.sys.mjs\n\t\tresource://activity-stream/lib/SiteClassifier.sys.mjs\n\t\tresource://activity-stream/lib/Store.sys.mjs\n\t\tresource://activity-stream/lib/SystemTickFeed.sys.mjs\n\t\tresource://activity-stream/lib/TelemetryFeed.sys.mjs\n\t\tresource://activity-stream/lib/TippyTopProvider.sys.mjs\n\t\tresource://activity-stream/lib/TopSitesFeed.sys.mjs\n\t\tresource://activity-stream/lib/TopStoriesFeed.sys.mjs\n\t\tresource://activity-stream/lib/UTEventReporting.sys.mjs\n\t\tresource://activity-stream/lib/WallpaperFeed.sys.mjs\n\t\tresource://activity-stream/lib/WeatherFeed.sys.mjs\n\t\tresource://activity-stream/lib/cache.worker.js\n\t\tresource://activity-stream/prerendered/activity-stream-noscripts.html\n\t\tresource://activity-stream/prerendered/activity-stream.html\n\t\tresource://activity-stream/vendor/Redux.sys.mjs\n\t\tresource://activity-stream/vendor/prop-types.js\n\t\tresource://activity-stream/vendor/react-dom-server.js\n\t\tresource://activity-stream/vendor/react-dom.js\n\t\tresource://activity-stream/vendor/react-redux.js\n\t\tresource://activity-stream/vendor/react-transition-group.js\n\t\tresource://activity-stream/vendor/react.js\n\t\tresource://activity-stream/vendor/redux.js\n\t\tresource://builtin-addons/search-detection/api.js\n\t\tresource://builtin-addons/search-detection/background.js\n\t\tresource://builtin-addons/search-detection/manifest.json\n\t\tresource://builtin-addons/search-detection/schema.json\n\t\tresource://content-accessible/ImageDocument.css\n\t\tresource://content-accessible/TopLevelImageDocument.css\n\t\tresource://content-accessible/TopLevelVideoDocument.css\n\t\tresource://content-accessible/accessiblecaret.css\n\t\tresource://content-accessible/details.css\n\t\tresource://content-accessible/html/folder.png\n\t\tresource://content-accessible/plaintext.css\n\t\tresource://content-accessible/searchfield-cancel.svg\n\t\tresource://content-accessible/viewsource.css\n\t\tresource://devtools-highlighter-styles/highlighters.css\n\t\tresource://devtools-shared-images/alert-small.svg\n\t\tresource://devtools-shared-images/command-pick-remote-touch.svg\n\t\tresource://devtools-shared-images/command-pick.svg\n\t\tresource://devtools-shared-images/error-small.svg\n\t\tresource://devtools-shared-images/info-small.svg\n\t\tresource://devtools-shared-images/resume.svg\n\t\tresource://devtools-shared-images/stepOver.svg\n\t\tresource://normandy-content/AboutPages.sys.mjs\n\t\tresource://normandy-content/ShieldFrameChild.sys.mjs\n\t\tresource://normandy-content/ShieldFrameParent.sys.mjs\n\t\tresource://normandy-content/about-studies/about-studies.css\n\t\tresource://normandy-content/about-studies/about-studies.html\n\t\tresource://normandy-content/about-studies/about-studies.js\n\t\tresource://normandy-vendor/LICENSE_THIRDPARTY\n\t\tresource://normandy-vendor/PropTypes.js\n\t\tresource://normandy-vendor/React.js\n\t\tresource://normandy-vendor/ReactDOM.js\n\t\tresource://normandy-vendor/classnames.js\n\t\tresource://search-extensions/1und1/favicon.ico\n\t\tresource://search-extensions/1und1/manifest.json\n\t\tresource://search-extensions/allegro-pl/favicon.ico\n\t\tresource://search-extensions/allegro-pl/manifest.json\n\t\tresource://search-extensions/amazon/_locales/au/messages.json\n\t\tresource://search-extensions/amazon/_locales/ca/messages.json\n\t\tresource://search-extensions/amazon/_locales/de/messages.json\n\t\tresource://search-extensions/amazon/_locales/en-GB/messages.json\n\t\tresource://search-extensions/amazon/_locales/france/messages.json\n\t\tresource://search-extensions/amazon/_locales/in/messages.json\n\t\tresource://search-extensions/amazon/_locales/it/messages.json\n\t\tresource://search-extensions/amazon/_locales/jp/messages.json\n\t\tresource://search-extensions/amazon/_locales/nl/messages.json\n\t\tresource://search-extensions/amazon/_locales/spain/messages.json\n\t\tresource://search-extensions/amazon/_locales/sweden/messages.json\n\t\tresource://search-extensions/amazon/favicon.ico\n\t\tresource://search-extensions/amazon/manifest.json\n\t\tresource://search-extensions/amazondotcn/_locales/default/messages.json\n\t\tresource://search-extensions/amazondotcn/_locales/mozillaonline/messages.json\n\t\tresource://search-extensions/amazondotcn/favicon.ico\n\t\tresource://search-extensions/amazondotcn/manifest.json\n\t\tresource://search-extensions/amazondotcom/_locales/en/messages.json\n\t\tresource://search-extensions/amazondotcom/_locales/us/messages.json\n\t\tresource://search-extensions/amazondotcom/favicon.ico\n\t\tresource://search-extensions/amazondotcom/manifest.json\n\t\tresource://search-extensions/azerdict/favicon.ico\n\t\tresource://search-extensions/azerdict/manifest.json\n\t\tresource://search-extensions/baidu/favicon.ico\n\t\tresource://search-extensions/baidu/manifest.json\n\t\tresource://search-extensions/bing/favicon.ico\n\t\tresource://search-extensions/bing/manifest.json\n\t\tresource://search-extensions/bok-NO/favicon.png\n\t\tresource://search-extensions/bok-NO/manifest.json\n\t\tresource://search-extensions/ceneji/favicon.png\n\t\tresource://search-extensions/ceneji/manifest.json\n\t\tresource://search-extensions/coccoc/favicon.ico\n\t\tresource://search-extensions/coccoc/manifest.json\n\t\tresource://search-extensions/daum-kr/favicon.ico\n\t\tresource://search-extensions/daum-kr/manifest.json\n\t\tresource://search-extensions/ddg/favicon.ico\n\t\tresource://search-extensions/ddg/manifest.json\n\t\tresource://search-extensions/ebay/_locales/at/messages.json\n\t\tresource://search-extensions/ebay/_locales/au/messages.json\n\t\tresource://search-extensions/ebay/_locales/be/messages.json\n\t\tresource://search-extensions/ebay/_locales/ca/messages.json\n\t\tresource://search-extensions/ebay/_locales/ch/messages.json\n\t\tresource://search-extensions/ebay/_locales/de/messages.json\n\t\tresource://search-extensions/ebay/_locales/en/messages.json\n\t\tresource://search-extensions/ebay/_locales/es/messages.json\n\t\tresource://search-extensions/ebay/_locales/fr/messages.json\n\t\tresource://search-extensions/ebay/_locales/ie/messages.json\n\t\tresource://search-extensions/ebay/_locales/it/messages.json\n\t\tresource://search-extensions/ebay/_locales/nl/messages.json\n\t\tresource://search-extensions/ebay/_locales/uk/messages.json\n\t\tresource://search-extensions/ebay/favicon.ico\n\t\tresource://search-extensions/ebay/manifest.json\n\t\tresource://search-extensions/ecosia/favicon.ico\n\t\tresource://search-extensions/ecosia/manifest.json\n\t\tresource://search-extensions/eudict/favicon.ico\n\t\tresource://search-extensions/eudict/manifest.json\n\t\tresource://search-extensions/faclair-beag/favicon.ico\n\t\tresource://search-extensions/faclair-beag/manifest.json\n\t\tresource://search-extensions/gmx/_locales/de/messages.json\n\t\tresource://search-extensions/gmx/_locales/en-GB/messages.json\n\t\tresource://search-extensions/gmx/_locales/es/messages.json\n\t\tresource://search-extensions/gmx/_locales/fr/messages.json\n\t\tresource://search-extensions/gmx/_locales/shopping/messages.json\n\t\tresource://search-extensions/gmx/favicon.png\n\t\tresource://search-extensions/gmx/manifest.json\n\t\tresource://search-extensions/google/_locales/en/messages.json\n\t\tresource://search-extensions/google/_locales/region-by/messages.json\n\t\tresource://search-extensions/google/_locales/region-kz/messages.json\n\t\tresource://search-extensions/google/_locales/region-ru/messages.json\n\t\tresource://search-extensions/google/_locales/region-tr/messages.json\n\t\tresource://search-extensions/google/favicon.ico\n\t\tresource://search-extensions/google/manifest.json\n\t\tresource://search-extensions/gulesider-NO/favicon.ico\n\t\tresource://search-extensions/gulesider-NO/manifest.json\n\t\tresource://search-extensions/leo_ende_de/favicon.png\n\t\tresource://search-extensions/leo_ende_de/manifest.json\n\t\tresource://search-extensions/longdo/favicon.ico\n\t\tresource://search-extensions/longdo/manifest.json\n\t\tresource://search-extensions/mailcom/favicon.ico\n\t\tresource://search-extensions/mailcom/manifest.json\n\t\tresource://search-extensions/mailru/_locales/default/messages.json\n\t\tresource://search-extensions/mailru/_locales/mailru001/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-az/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-en-US/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-hy-AM/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-kk/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-ro/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-ru/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-tr/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-uk/messages.json\n\t\tresource://search-extensions/mailru/_locales/okru-uz/messages.json\n\t\tresource://search-extensions/mailru/favicon.ico\n\t\tresource://search-extensions/mailru/manifest.json\n\t\tresource://search-extensions/mapy-cz/favicon.ico\n\t\tresource://search-extensions/mapy-cz/manifest.json\n\t\tresource://search-extensions/mercadolibre/_locales/ar/messages.json\n\t\tresource://search-extensions/mercadolibre/_locales/cl/messages.json\n\t\tresource://search-extensions/mercadolibre/_locales/mx/messages.json\n\t\tresource://search-extensions/mercadolibre/favicon.ico\n\t\tresource://search-extensions/mercadolibre/manifest.json\n\t\tresource://search-extensions/mercadolivre/favicon.ico\n\t\tresource://search-extensions/mercadolivre/manifest.json\n\t\tresource://search-extensions/naver-kr/favicon.ico\n\t\tresource://search-extensions/naver-kr/manifest.json\n\t\tresource://search-extensions/odpiralni/favicon.png\n\t\tresource://search-extensions/odpiralni/manifest.json\n\t\tresource://search-extensions/pazaruvaj/favicon.ico\n\t\tresource://search-extensions/pazaruvaj/manifest.json\n\t\tresource://search-extensions/priberam/favicon.png\n\t\tresource://search-extensions/priberam/manifest.json\n\t\tresource://search-extensions/prisjakt-sv-SE/favicon.ico\n\t\tresource://search-extensions/prisjakt-sv-SE/manifest.json\n\t\tresource://search-extensions/qwant/favicon.ico\n\t\tresource://search-extensions/qwant/manifest.json\n\t\tresource://search-extensions/qwantjr/favicon.ico\n\t\tresource://search-extensions/qwantjr/manifest.json\n\t\tresource://search-extensions/rakuten/favicon.ico\n\t\tresource://search-extensions/rakuten/manifest.json\n\t\tresource://search-extensions/readmoo/favicon.ico\n\t\tresource://search-extensions/readmoo/manifest.json\n\t\tresource://search-extensions/salidzinilv/favicon.ico\n\t\tresource://search-extensions/salidzinilv/manifest.json\n\t\tresource://search-extensions/seznam-cz/favicon.ico\n\t\tresource://search-extensions/seznam-cz/manifest.json\n\t\tresource://search-extensions/tyda-sv-SE/favicon.ico\n\t\tresource://search-extensions/tyda-sv-SE/manifest.json\n\t\tresource://search-extensions/vatera/favicon.ico\n\t\tresource://search-extensions/vatera/manifest.json\n\t\tresource://search-extensions/webde/favicon.ico\n\t\tresource://search-extensions/webde/manifest.json\n\t\tresource://search-extensions/wikipedia/_locales/NN/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/NO/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/af/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/an/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ar/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ast/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/az/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/be-tarask/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/be/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/bg/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/bn/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/br/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/bs/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ca/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/cy/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/cz/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/da/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/de/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/dsb/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/el/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/en/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/eo/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/es/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/et/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/eu/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/fa/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/fi/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/fr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/fy-NL/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ga-IE/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/gd/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/gl/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/gn/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/gu/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/he/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/hi/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/hr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/hsb/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/hu/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/hy/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ia/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/id/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/is/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/it/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ja/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ka/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/kab/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/kk/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/km/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/kn/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/kr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/lij/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/lo/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/lt/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ltg/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/lv/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/mk/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/mr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ms/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/my/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ne/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/nl/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/oc/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/pa/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/pl/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/pt/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/rm/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ro/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ru/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/si/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/sk/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/sl/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/sq/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/sr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/sv-SE/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ta/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/te/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/th/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/tl/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/tr/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/uk/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/ur/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/uz/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/vi/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/wo/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/zh-CN/messages.json\n\t\tresource://search-extensions/wikipedia/_locales/zh-TW/messages.json\n\t\tresource://search-extensions/wikipedia/favicon.ico\n\t\tresource://search-extensions/wikipedia/manifest.json\n\t\tresource://search-extensions/wiktionary/_locales/oc/messages.json\n\t\tresource://search-extensions/wiktionary/_locales/te/messages.json\n\t\tresource://search-extensions/wiktionary/favicon.ico\n\t\tresource://search-extensions/wiktionary/manifest.json\n\t\tresource://search-extensions/wolnelektury-pl/favicon.png\n\t\tresource://search-extensions/wolnelektury-pl/manifest.json\n\t\tresource://search-extensions/yahoo-jp-auctions/favicon.ico\n\t\tresource://search-extensions/yahoo-jp-auctions/manifest.json\n\t\tresource://search-extensions/yahoo-jp/favicon.ico\n\t\tresource://search-extensions/yahoo-jp/manifest.json\n\t\tresource://search-extensions/yandex/_locales/az/messages.json\n\t\tresource://search-extensions/yandex/_locales/by/messages.json\n\t\tresource://search-extensions/yandex/_locales/en/messages.json\n\t\tresource://search-extensions/yandex/_locales/kk/messages.json\n\t\tresource://search-extensions/yandex/_locales/ru/messages.json\n\t\tresource://search-extensions/yandex/_locales/tr/messages.json\n\t\tresource://search-extensions/yandex/_locales/ua/messages.json\n\t\tresource://search-extensions/yandex/manifest.json\n\t\tresource://search-extensions/yandex/yandex-en.ico\n\t\tresource://search-extensions/yandex/yandex-ru.ico\n\t\tresource://usercontext-content/briefcase.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-gradient-dark.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-gradient.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-noodles-left-dark.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-noodles-left.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-noodles-right-dark.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/background-noodles-right.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/icon.svg\n\t\tresource://usercontext-content/builtin-themes/alpenglow/manifest.json\n\t\tresource://usercontext-content/builtin-themes/alpenglow/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021abstract/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021cheers/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021elemental/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021foto/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021graffiti/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2021lush/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022activist/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022blue/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022blue/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022blue/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022dreamer/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022expressionist/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022green/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022green/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022green/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022innovator/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022orange/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022orange/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022orange/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022playmaker/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022purple/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022purple/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022purple/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022red/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022red/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022red/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/balanced/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/balanced/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/balanced/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/bold/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/bold/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/bold/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/soft/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/soft/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022visionary/soft/preview.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022yellow/icon.svg\n\t\tresource://usercontext-content/builtin-themes/colorways/2022yellow/manifest.json\n\t\tresource://usercontext-content/builtin-themes/colorways/2022yellow/preview.svg\n\t\tresource://usercontext-content/builtin-themes/dark/experiment.css\n\t\tresource://usercontext-content/builtin-themes/dark/icon.svg\n\t\tresource://usercontext-content/builtin-themes/dark/manifest.json\n\t\tresource://usercontext-content/builtin-themes/dark/preview.svg\n\t\tresource://usercontext-content/builtin-themes/light/experiment.css\n\t\tresource://usercontext-content/builtin-themes/light/icon.svg\n\t\tresource://usercontext-content/builtin-themes/light/manifest.json\n\t\tresource://usercontext-content/builtin-themes/light/preview.svg\n\t\tresource://usercontext-content/cart.svg\n\t\tresource://usercontext-content/chill.svg\n\t\tresource://usercontext-content/circle.svg\n\t\tresource://usercontext-content/dollar.svg\n\t\tresource://usercontext-content/fence.svg\n\t\tresource://usercontext-content/fingerprint.svg\n\t\tresource://usercontext-content/food.svg\n\t\tresource://usercontext-content/fruit.svg\n\t\tresource://usercontext-content/gift.svg\n\t\tresource://usercontext-content/pet.svg\n\t\tresource://usercontext-content/tree.svg\n\t\tresource://usercontext-content/vacation.svg\n\n\t`\n\n\tlet listTor = `\n\n\t\tchrome://branding/content/icon256.png\n\t\tchrome://browser/content/aboutDialogTor.css\n\t\tchrome://browser/content/abouttbupdate/aboutTBUpdate.css\n\t\tchrome://browser/content/abouttbupdate/aboutTBUpdate.js\n\t\tchrome://browser/content/abouttbupdate/aboutTBUpdate.xhtml\n\t\tchrome://browser/content/abouttor/1f4e3-megaphone.svg\n\t\tchrome://browser/content/abouttor/26a1-high-voltage.svg\n\t\tchrome://browser/content/abouttor/2728-sparkles.svg\n\t\tchrome://browser/content/abouttor/2764-red-heart.svg\n\t\tchrome://browser/content/abouttor/aboutTor.css\n\t\tchrome://browser/content/abouttor/aboutTor.html\n\t\tchrome://browser/content/abouttor/aboutTor.js\n\t\tchrome://browser/content/abouttor/dax-logo.svg\n\t\tchrome://browser/content/abouttor/yec-2024-browse.svg\n\t\tchrome://browser/content/abouttor/yec-2024-fonts.css\n\t\tchrome://browser/content/abouttor/yec-2024-heart.svg\n\t\tchrome://browser/content/abouttor/yec-2024-search.svg\n\t\tchrome://browser/content/abouttor/yec-2024-speak.svg\n\t\tchrome://browser/content/languageNotification.js\n\t\tchrome://browser/content/manual/ar.html\n\t\tchrome://browser/content/manual/be.html\n\t\tchrome://browser/content/manual/bg.html\n\t\tchrome://browser/content/manual/bn.html\n\t\tchrome://browser/content/manual/ca.html\n\t\tchrome://browser/content/manual/de.html\n\t\tchrome://browser/content/manual/el.html\n\t\tchrome://browser/content/manual/en.html\n\t\tchrome://browser/content/manual/es.html\n\t\tchrome://browser/content/manual/fa.html\n\t\tchrome://browser/content/manual/fi.html\n\t\tchrome://browser/content/manual/fr.html\n\t\tchrome://browser/content/manual/ga.html\n\t\tchrome://browser/content/manual/he.html\n\t\tchrome://browser/content/manual/hu.html\n\t\tchrome://browser/content/manual/id.html\n\t\tchrome://browser/content/manual/is.html\n\t\tchrome://browser/content/manual/it.html\n\t\tchrome://browser/content/manual/ja.html\n\t\tchrome://browser/content/manual/ka.html\n\t\tchrome://browser/content/manual/km.html\n\t\tchrome://browser/content/manual/ko.html\n\t\tchrome://browser/content/manual/lt.html\n\t\tchrome://browser/content/manual/mk.html\n\t\tchrome://browser/content/manual/my.html\n\t\tchrome://browser/content/manual/pl.html\n\t\tchrome://browser/content/manual/ps.html\n\t\tchrome://browser/content/manual/pt-BR.html\n\t\tchrome://browser/content/manual/pt-PT.html\n\t\tchrome://browser/content/manual/ro.html\n\t\tchrome://browser/content/manual/ru.html\n\t\tchrome://browser/content/manual/sq.html\n\t\tchrome://browser/content/manual/static/collapse.min.js\n\t\tchrome://browser/content/manual/static/css/bootstrap-grid.css\n\t\tchrome://browser/content/manual/static/css/bootstrap-reboot.css\n\t\tchrome://browser/content/manual/static/css/bootstrap.css\n\t\tchrome://browser/content/manual/static/images/android-configure.png\n\t\tchrome://browser/content/manual/static/images/android-connect.png\n\t\tchrome://browser/content/manual/static/images/android-new-circuit.png\n\t\tchrome://browser/content/manual/static/images/android-provide-a-bridge.png\n\t\tchrome://browser/content/manual/static/images/android-provided-a-bridge.png\n\t\tchrome://browser/content/manual/static/images/android-security-settings.gif\n\t\tchrome://browser/content/manual/static/images/android-select-a-bridge.png\n\t\tchrome://browser/content/manual/static/images/android-selected-a-bridge.png\n\t\tchrome://browser/content/manual/static/images/android-uninstall-device-settings.png\n\t\tchrome://browser/content/manual/static/images/android-uninstall-f-droid.png\n\t\tchrome://browser/content/manual/static/images/android-uninstall-google-play.png\n\t\tchrome://browser/content/manual/static/images/android-update-f-droid.png\n\t\tchrome://browser/content/manual/static/images/android-update-google-play.png\n\t\tchrome://browser/content/manual/static/images/android-view-logs.png\n\t\tchrome://browser/content/manual/static/images/bridge-qr.png\n\t\tchrome://browser/content/manual/static/images/bridgemoji.png\n\t\tchrome://browser/content/manual/static/images/built-in-bridge.png\n\t\tchrome://browser/content/manual/static/images/circuit_full.png\n\t\tchrome://browser/content/manual/static/images/client-auth.png\n\t\tchrome://browser/content/manual/static/images/configure.png\n\t\tchrome://browser/content/manual/static/images/connect.png\n\t\tchrome://browser/content/manual/static/images/connection-assist-auto.png\n\t\tchrome://browser/content/manual/static/images/connection-assist-offline.png\n\t\tchrome://browser/content/manual/static/images/connection-assist-select.png\n\t\tchrome://browser/content/manual/static/images/connection-assist-test.png\n\t\tchrome://browser/content/manual/static/images/connection-test-failure.png\n\t\tchrome://browser/content/manual/static/images/connection-test-success.png\n\t\tchrome://browser/content/manual/static/images/cryptocurrency-safety.png\n\t\tchrome://browser/content/manual/static/images/gettor-bot-telegram.png\n\t\tchrome://browser/content/manual/static/images/how-tor-works.png\n\t\tchrome://browser/content/manual/static/images/http-website-error.png\n\t\tchrome://browser/content/manual/static/images/https-only-mode.png\n\t\tchrome://browser/content/manual/static/images/letterboxing.png\n\t\tchrome://browser/content/manual/static/images/macos-go-to-folder-menu.png\n\t\tchrome://browser/content/manual/static/images/macos-go-to-folder-window.png\n\t\tchrome://browser/content/manual/static/images/new_identity.png\n\t\tchrome://browser/content/manual/static/images/onion-location.png\n\t\tchrome://browser/content/manual/static/images/pluggable-transport.png\n\t\tchrome://browser/content/manual/static/images/provide-bridge.png\n\t\tchrome://browser/content/manual/static/images/proxy.png\n\t\tchrome://browser/content/manual/static/images/quickstart.png\n\t\tchrome://browser/content/manual/static/images/request-a-bridge.png\n\t\tchrome://browser/content/manual/static/images/security-settings-anim.gif\n\t\tchrome://browser/content/manual/static/images/security-settings-safest.png\n\t\tchrome://browser/content/manual/static/images/tor-https-0.png\n\t\tchrome://browser/content/manual/static/images/tor-https-1.png\n\t\tchrome://browser/content/manual/static/images/tor-https-2.png\n\t\tchrome://browser/content/manual/static/images/tor-https-3.png\n\t\tchrome://browser/content/manual/static/images/update1.png\n\t\tchrome://browser/content/manual/static/images/update4.png\n\t\tchrome://browser/content/manual/static/js/anchor.min.js\n\t\tchrome://browser/content/manual/static/js/bootstrap.bundle.js\n\t\tchrome://browser/content/manual/static/js/bootstrap.bundle.min.js\n\t\tchrome://browser/content/manual/static/js/bootstrap.js\n\t\tchrome://browser/content/manual/static/js/bootstrap.min.js\n\t\tchrome://browser/content/manual/static/js/clipboard.min.js\n\t\tchrome://browser/content/manual/static/js/collapse.min.js\n\t\tchrome://browser/content/manual/static/js/download.js\n\t\tchrome://browser/content/manual/static/js/errors.js\n\t\tchrome://browser/content/manual/static/js/fallback.js\n\t\tchrome://browser/content/manual/static/js/holder.min.js\n\t\tchrome://browser/content/manual/static/js/jquery-3.2.1.min.js\n\t\tchrome://browser/content/manual/static/js/jquery-slim.min.js\n\t\tchrome://browser/content/manual/static/js/modernizr.js\n\t\tchrome://browser/content/manual/static/js/popper.min.js\n\t\tchrome://browser/content/manual/static/js/scrollspy.min.js\n\t\tchrome://browser/content/manual/static/js/util.min.js\n\t\tchrome://browser/content/manual/sw.html\n\t\tchrome://browser/content/manual/th.html\n\t\tchrome://browser/content/manual/tk.html\n\t\tchrome://browser/content/manual/tr.html\n\t\tchrome://browser/content/manual/uk.html\n\t\tchrome://browser/content/manual/vi.html\n\t\tchrome://browser/content/manual/wo.html\n\t\tchrome://browser/content/manual/zh-CN.html\n\t\tchrome://browser/content/manual/zh-TW.html\n\t\tchrome://browser/content/newIdentityDialog.css\n\t\tchrome://browser/content/newIdentityDialog.js\n\t\tchrome://browser/content/newIdentityDialog.xhtml\n\t\tchrome://browser/content/newidentity.js\n\t\tchrome://browser/content/onionservices/authPreferences.css\n\t\tchrome://browser/content/onionservices/authPreferences.js\n\t\tchrome://browser/content/onionservices/authPrompt.js\n\t\tchrome://browser/content/onionservices/onionservices.css\n\t\tchrome://browser/content/onionservices/savedKeysDialog.js\n\t\tchrome://browser/content/onionservices/savedKeysDialog.xhtml\n\t\tchrome://browser/content/preferences/letterboxing-middle-dark.svg\n\t\tchrome://browser/content/preferences/letterboxing-middle-light.svg\n\t\tchrome://browser/content/preferences/letterboxing-top-dark.svg\n\t\tchrome://browser/content/preferences/letterboxing-top-light.svg\n\t\tchrome://browser/content/preferences/letterboxing.css\n\t\tchrome://browser/content/preferences/letterboxing.js\n\t\tchrome://browser/content/rulesets/aboutRulesets.css\n\t\tchrome://browser/content/rulesets/aboutRulesets.html\n\t\tchrome://browser/content/rulesets/aboutRulesets.js\n\t\tchrome://browser/content/rulesets/securedrop.svg\n\t\tchrome://browser/content/securitylevel/securityLevel.js\n\t\tchrome://browser/content/securitylevel/securityLevelButton.css\n\t\tchrome://browser/content/securitylevel/securityLevelIcon.svg\n\t\tchrome://browser/content/securitylevel/securityLevelPanel.css\n\t\tchrome://browser/content/securitylevel/securityLevelPreferences.css\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1fd.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e6-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1e7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ef.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e7-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1fd.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e8-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1ef.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1e9-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ea-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1ef.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1eb-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1e7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ec-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ed-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ee-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ef-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ef-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ef-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ef-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f0-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1e7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f1-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1fd.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f2-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1f5.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f3-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f4-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f5-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f6-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f7-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f7-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f7-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f7-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f7-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1e7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ef.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1fd.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f8-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1e9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1ed.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1ef.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f1.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f4.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f7.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1fb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1f9-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1fe.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fa-1f1ff.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1e8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1ec.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1ee.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1f3.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fb-1f1fa.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fc-1f1eb.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fc-1f1f8.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fd-1f1f0.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fe-1f1ea.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1fe-1f1f9.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ff-1f1e6.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ff-1f1f2.svg\n\t\tchrome://browser/content/tor-circuit-flags/1f1ff-1f1fc.svg\n\t\tchrome://browser/content/tor-circuit-flags/README.txt\n\t\tchrome://browser/content/tor-circuit-icon-mask.svg\n\t\tchrome://browser/content/tor-circuit-node-end.svg\n\t\tchrome://browser/content/tor-circuit-node-middle.svg\n\t\tchrome://browser/content/tor-circuit-node-relays.svg\n\t\tchrome://browser/content/tor-circuit-node-start.svg\n\t\tchrome://browser/content/tor-circuit-redirect.svg\n\t\tchrome://browser/content/torCircuitPanel.css\n\t\tchrome://browser/content/torCircuitPanel.js\n\t\tchrome://browser/content/torpreferences/bridge-bot.svg\n\t\tchrome://browser/content/torpreferences/bridge-qr.svg\n\t\tchrome://browser/content/torpreferences/bridge.svg\n\t\tchrome://browser/content/torpreferences/bridgeQrDialog.js\n\t\tchrome://browser/content/torpreferences/bridgeQrDialog.xhtml\n\t\tchrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js\n\t\tchrome://browser/content/torpreferences/bridgemoji/annotations.json\n\t\tchrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f300.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f308.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f30a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f30b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f319.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f31f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f321.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f32d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f32e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f332.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f333.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f334.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f335.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f336.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f337.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f339.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f33a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f33b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f33d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f33f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f341.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f344.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f345.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f346.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f347.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f348.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f349.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f34a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f34b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f34c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f34d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f34f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f350.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f351.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f352.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f353.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f354.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f355.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f368.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f369.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f36a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f36b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f36c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f36d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f37f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f380.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f381.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f382.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f383.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f388.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f389.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f38f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f392.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f399.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f39f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3a0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3a1.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3a2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3a8.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3ac.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3af.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3b2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3b6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3b7.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3b8.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3ba.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3bb.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3be.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3c0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3c6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3c8.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3d3.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3d4.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3d5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3dd.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3e1.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3ee.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3f7.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3f8.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f3f9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f40a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f40c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f40d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f417.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f418.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f419.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f41a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f41b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f41d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f41e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f41f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f420.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f422.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f425.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f426.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f428.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f42a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f42c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f42d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f42e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f42f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f430.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f431.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f432.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f433.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f434.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f435.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f436.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f437.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f43a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f43b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f43f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f441.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f451.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f455.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f457.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f45f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f47d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f484.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f488.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f48d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f48e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f490.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4a1.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4a7.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4b3.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4bf.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4cc.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4ce.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4d5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4e1.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4e2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f4fb.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f50b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f511.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f525.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f526.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f52c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f52d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f52e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f54a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f58c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f58d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f5ff.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f680.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f681.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f686.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f68b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f68d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f695.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f697.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f69a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f69c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6a0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6a2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6a4.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f4.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f8.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6f9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6fa.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f6fc.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f916.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f93f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f941.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f94c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f94f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f950.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f951.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f955.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f956.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f95c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f95d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f95e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f965.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f966.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f968.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f96c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f96d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f96f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f980.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f981.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f984.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f986.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f987.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f988.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f989.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f98a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f98b.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f98c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f98e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f98f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f992.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f993.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f994.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f995.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f998.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f999.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f99a.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f99c.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f99d.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f99e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a3.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a4.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a7.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9a9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9ad.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9c1.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9c3.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9c5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9c7.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9c9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9d9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9da.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9dc.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9e0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9e2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9e6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9e9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9ea.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9ec.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9ed.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9ee.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9f2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9f5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1f9f9.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa73.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa80.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa81.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa83.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa90.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa91.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa95.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fa97.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fab6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fad0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fad2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/1fad6.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/23f0.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2600.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2602.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2604.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/260e.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2693.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2696.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/26bd.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/26f2.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/26f5.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2708.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/270f.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2728.svg\n\t\tchrome://browser/content/torpreferences/bridgemoji/svgs/2744.svg\n\t\tchrome://browser/content/torpreferences/builtinBridgeDialog.js\n\t\tchrome://browser/content/torpreferences/builtinBridgeDialog.xhtml\n\t\tchrome://browser/content/torpreferences/connectionCategory.inc.xhtml\n\t\tchrome://browser/content/torpreferences/connectionPane.js\n\t\tchrome://browser/content/torpreferences/connectionPane.xhtml\n\t\tchrome://browser/content/torpreferences/connectionSettingsDialog.js\n\t\tchrome://browser/content/torpreferences/connectionSettingsDialog.xhtml\n\t\tchrome://browser/content/torpreferences/lox-bridge-icon.svg\n\t\tchrome://browser/content/torpreferences/lox-bridge-pass.svg\n\t\tchrome://browser/content/torpreferences/lox-complete-ring.svg\n\t\tchrome://browser/content/torpreferences/lox-invite-icon.svg\n\t\tchrome://browser/content/torpreferences/lox-progress-ring.svg\n\t\tchrome://browser/content/torpreferences/lox-success.svg\n\t\tchrome://browser/content/torpreferences/loxInviteDialog.js\n\t\tchrome://browser/content/torpreferences/loxInviteDialog.xhtml\n\t\tchrome://browser/content/torpreferences/mail.svg\n\t\tchrome://browser/content/torpreferences/network-broken.svg\n\t\tchrome://browser/content/torpreferences/network.svg\n\t\tchrome://browser/content/torpreferences/provideBridgeDialog.js\n\t\tchrome://browser/content/torpreferences/provideBridgeDialog.xhtml\n\t\tchrome://browser/content/torpreferences/requestBridgeDialog.js\n\t\tchrome://browser/content/torpreferences/requestBridgeDialog.xhtml\n\t\tchrome://browser/content/torpreferences/telegram-logo.svg\n\t\tchrome://browser/content/torpreferences/torLogDialog.js\n\t\tchrome://browser/content/torpreferences/torLogDialog.xhtml\n\t\tchrome://browser/content/torpreferences/torPreferences.css\n\t\tchrome://browser/locale/aboutTBUpdate.dtd\n\t\tchrome://browser/skin/new_circuit.svg\n\t\tchrome://browser/skin/new_identity.svg\n\t\tchrome://browser/skin/onionlocation.css\n\t\tchrome://browser/skin/tor-branding.css\n\t\tchrome://browser/skin/tor-urlbar-button.css\n\t\tchrome://global/content/lox/lox_wasm_bg.wasm\n\t\tchrome://global/content/pt_config.json\n\t\tchrome://global/content/search/duckduckgo.ico\n\t\tchrome://global/content/search/startpage.png\n\t\tchrome://global/content/search/torBrowserSearchEngineIcons.json\n\t\tchrome://global/content/search/torBrowserSearchEngines.json\n\t\tchrome://global/content/search/wikipedia.ico\n\t\tchrome://global/content/torconnect/aboutTorConnect.css\n\t\tchrome://global/content/torconnect/aboutTorConnect.html\n\t\tchrome://global/content/torconnect/aboutTorConnect.js\n\t\tchrome://global/content/torconnect/arrow-right.svg\n\t\tchrome://global/content/torconnect/bridge.svg\n\t\tchrome://global/content/torconnect/connection-failure.svg\n\t\tchrome://global/content/torconnect/connection-location.svg\n\t\tchrome://global/content/torconnect/tor-connect-broken.svg\n\t\tchrome://global/content/torconnect/tor-connect.svg\n\t\tchrome://global/content/torconnect/tor-not-connected-to-connected-animated.svg\n\t\tchrome://global/content/torconnect/torConnectTitlebarStatus.css\n\t\tchrome://global/content/torconnect/torConnectTitlebarStatus.js\n\t\tchrome://global/content/torconnect/torConnectUrlbarButton.js\n\t\tchrome://global/skin/icons/onion-site.svg\n\t\tchrome://global/skin/icons/onion-slash.svg\n\t\tchrome://global/skin/icons/onion-warning.svg\n\t\tchrome://global/skin/icons/tor-dark-loading.png\n\t\tchrome://global/skin/icons/tor-dark-loading@2x.png\n\t\tchrome://global/skin/icons/tor-light-loading.png\n\t\tchrome://global/skin/icons/tor-light-loading@2x.png\n\t\tchrome://global/skin/onion-pattern.css\n\t\tchrome://global/skin/onion-pattern.svg\n\t\tchrome://global/skin/tor-colors.css\n\t\tresource://search-extensions/ddg-onion/favicon.ico\n\t\tresource://search-extensions/ddg-onion/manifest.json\n\t\tresource://search-extensions/startpage-onion/favicon.png\n\t\tresource://search-extensions/startpage-onion/manifest.json\n\t\tresource://search-extensions/startpage/favicon.png\n\t\tresource://search-extensions/startpage/manifest.json\n\n\t\t// Mullvad Browser\n\t\tchrome://browser/content/mullvad-browser/2728-sparkles.svg\n\t\tchrome://browser/content/mullvad-browser/aboutMullvadBrowser.css\n\t\tchrome://browser/content/mullvad-browser/aboutMullvadBrowser.js\n\t\tchrome://browser/content/mullvad-browser/aboutMullvadBrowser.xhtml\n\t\tchrome://browser/content/mullvad-browser/mullvadBrowserFont.css\n\t\tchrome://global/content/search/brave.svg\n\t\tchrome://global/content/search/metager.ico\n\t\tchrome://global/content/search/mojeek.ico\n\t\tchrome://global/content/search/mullvad-leta.svg\n\t\tchrome://global/content/search/mullvadBrowserSearchEngineIcons.json\n\t\tchrome://global/content/search/mullvadBrowserSearchEngines.json\n\t\tresource://search-extensions/brave/favicon.svg\n\t\tresource://search-extensions/brave/manifest.json\n\t\tresource://search-extensions/ddg-html/favicon.ico\n\t\tresource://search-extensions/ddg-html/manifest.json\n\t\tresource://search-extensions/metager/favicon.ico\n\t\tresource://search-extensions/metager/manifest.json\n\t\tresource://search-extensions/mojeek/favicon.ico\n\t\tresource://search-extensions/mojeek/manifest.json\n\t\tresource://search-extensions/mullvad-leta/favicon.svg\n\t\tresource://search-extensions/mullvad-leta/manifest.json\n\n\t`\n\n</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "tests/codecs_can_is.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>codecs: canPlay & isType</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#codecs\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb13\">\r\n\t\t<col width=\"1%\"><col width=\"24%\"><col width=\"75%\">\r\n\t\t<thead><tr><th colspan=\"3\">\r\n\t\t\t<div class=\"nav-title\">codecs: canPlay & isType\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"3\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Testing <code>canPlayType</code> + <code>isTypeSupported</code> with large\r\n\t\t\t\t<span class='btn13 btnc mono' onClick='display(`audiolist`)'>audio</span> +\r\n\t\t\t\t<span class='btn13 btnc mono' onClick='display(`videolist`)'>video</span> lists\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"3\"><hr><br></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">canPlayType</td><td class=\"mono\">\r\n\t\t\t<span class=\"c\" id=\"audiocan\"></span> | <span class=\"c\" id=\"videocan\"></span></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">isTypeSupported</td><td class=\"mono\">\r\n\t\t\t<span class=\"c\" id=\"audiotype\"></span> | <span class=\"c\" id=\"videotype\"></span></td></tr>\r\n\t\t<tr><td colspan=\"3\"><br><hr><br></td></tr>\r\n\t\t<tr><td></td>\r\n\t\t\t<td colspan=\"2\" class=\"mono spaces\" id=\"details\"></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet v = \"video/\", a = \"audio/\"\r\nlet audiolist = [\r\n\t'application/ogg',\r\n\ta+'3gpp', \r\n\ta+'3gpp2', \r\n\ta+'aac',\r\n\ta+'flac',\r\n\ta+'matroska',\r\n\ta+'mp3',\r\n\ta+'mp4',\r\n\ta+'mp4; codecs=',\r\n\ta+'mp4; codecs=\"\"',\r\n\ta+'mp4; codecs=\"flac\"',\r\n\ta+'mp4; codecs=\"mp3\"',\r\n\ta+'mp4; codecs=\"opus\"',\r\n\ta+'mp4; codecs=\\'\\'',\r\n\ta+'mpeg',\r\n\ta+'mpeg; codecs=\"mp3\"',\r\n\ta+'ogg; codecs=\"flac\"',\r\n\ta+'ogg; codecs=\"opus\"',\r\n\ta+'ogg; codecs=\"vorbis\"',\r\n\ta+'wav',\r\n\ta+'wav; codecs=\"1\"',\r\n\ta+'wave',\r\n\ta+'wave; codecs=\"1\"',\r\n\ta+'webm',\r\n\ta+'webm; codecs=\"opus\"',\r\n\ta+'webm; codecs=\"vorbis\"',\r\n\ta+'x-aac',\r\n\ta+'x-flac',\r\n\ta+'x-m4a',\r\n\ta+'x-matroska',\r\n\ta+'x-pn-wav',\r\n\ta+'x-pn-wav; codecs=\"1\"',\r\n\ta+'x-wav',\r\n\ta+'x-wav; codecs=\"1\"',\r\n\ta+'mp4; codecs=\"mp4a.40\"',\r\n\ta+'mp4; codecs=\"mp4a.40.1\"',\r\n\ta+'mp4; codecs=\"mp4a.40.2\"',\r\n\ta+'mp4; codecs=\"mp4a.40.3\"',\r\n\ta+'mp4; codecs=\"mp4a.40.4\"',\r\n\ta+'mp4; codecs=\"mp4a.40.5\"',\r\n\ta+'mp4; codecs=\"mp4a.40.6\"',\r\n\ta+'mp4; codecs=\"mp4a.40.7\"',\r\n\ta+'mp4; codecs=\"mp4a.40.8\"',\r\n\ta+'mp4; codecs=\"mp4a.40.9\"',\r\n\ta+'mp4; codecs=\"mp4a.40.12\"',\r\n\ta+'mp4; codecs=\"mp4a.40.13\"',\r\n\ta+'mp4; codecs=\"mp4a.40.14\"',\r\n\ta+'mp4; codecs=\"mp4a.40.15\"',\r\n\ta+'mp4; codecs=\"mp4a.40.16\"',\r\n\ta+'mp4; codecs=\"mp4a.40.17\"',\r\n\ta+'mp4; codecs=\"mp4a.40.19\"',\r\n\ta+'mp4; codecs=\"mp4a.40.20\"',\r\n\ta+'mp4; codecs=\"mp4a.40.21\"',\r\n\ta+'mp4; codecs=\"mp4a.40.22\"',\r\n\ta+'mp4; codecs=\"mp4a.40.23\"',\r\n\ta+'mp4; codecs=\"mp4a.40.24\"',\r\n\ta+'mp4; codecs=\"mp4a.40.25\"',\r\n\ta+'mp4; codecs=\"mp4a.40.26\"',\r\n\ta+'mp4; codecs=\"mp4a.40.27\"',\r\n\ta+'mp4; codecs=\"mp4a.40.28\"',\r\n\ta+'mp4; codecs=\"mp4a.40.29\"',\r\n\ta+'mp4; codecs=\"mp4a.40.32\"',\r\n\ta+'mp4; codecs=\"mp4a.40.33\"',\r\n\ta+'mp4; codecs=\"mp4a.40.34\"',\r\n\ta+'mp4; codecs=\"mp4a.40.35\"',\r\n\ta+'mp4; codecs=\"mp4a.40.36\"',\r\n\ta+'mp4; codecs=\"mp4a.40.42\"',\r\n\ta+'mp4; codecs=\"mp4a.66\"',\r\n\ta+'mp4; codecs=\"mp4a.67\"',\r\n\ta+'mp4; codecs=\"mp4a.68\"',\r\n\ta+'mp4; codecs=\"mp4a.69\"',\r\n\ta+'mp4; codecs=\"mp4a.6B\"',\r\n\ta+'mp4; codecs=\".mp3\"',\r\n]\r\nlet videolist = [\r\n\t'application/ogg',\r\n\tv+'3gpp',\r\n\tv+'3gpp2',\r\n\tv+'matroska',\r\n\tv+'mp4',\r\n\tv+'mp4; codecs=',\r\n\tv+'mp4; codecs=\"\"',\r\n\tv+'mp4; codecs=\"avc1\"',\r\n\tv+'mp4; codecs=\"avc3\"',\r\n\tv+'mp4; codecs=\"flac\"',\r\n\tv+'mp4; codecs=\"hev1.1.6.L93.B0\"', // 1853448\r\n\tv+'mp4; codecs=\"hev1.2.4.L120.B0\"',\r\n\tv+'mp4; codecs=\"hvc1.1.6.L93.B0\"',\r\n\tv+'mp4; codecs=\"hvc1.2.4.L120.B0\"',\r\n\tv+'mp4; codecs=\"opus\"',\r\n\tv+'mp4; codecs=\"vp09.00.10.08\"',\r\n\tv+'mp4; codecs=\"vp09.00.50.08\"',\r\n\tv+'mp4; codecs=\"vp09.00.51.08.01.01.01.01.00\"',\r\n\tv+'mp4; codecs=\\'\\'',\r\n\tv+'ogg',\r\n\tv+'ogg; codecs=\"flac\"',\r\n\tv+'ogg; codecs=\"opus\"',\r\n\tv+'ogg; codecs=\"theora\"',\r\n\tv+'ogg; codecs=\"theora, flac\"',\r\n\tv+'ogg; codecs=\"theora, speex\"',\r\n\tv+'ogg; codecs=\"theora, vorbis\"',\r\n\tv+'quicktime',\r\n\tv+'webm',\r\n\tv+'webm; codecs=\"av1\"',\r\n\tv+'webm; codecs=\"vorbis\"',\r\n\tv+'webm; codecs=\"vp8\"',\r\n\tv+'webm; codecs=\"vp8, opus\"',\r\n\tv+'webm; codecs=\"vp8, vorbis\"',\r\n\tv+'webm; codecs=\"vp9\"',\r\n\tv+'webm; codecs=\"vp9, opus\"',\r\n\tv+'webm; codecs=\"vp9, vorbis\"',\r\n\tv+'x-m4v',\r\n\tv+'x-matroska',\r\n]\r\nlet videomp4 = [\r\n\t\"3gvo\",\"a3d1\",\"a3d2\",\"a3d3\",\"a3d4\",\"a3ds\",\"ac-3\",\"ac-4\",\"alac\",\"alaw\",\"av01\",\"avc2\",\"avc4\",\"avcp\",\"dra1\",\r\n\t\"drac\",\"dts+\",\"dts-\",\"dtsc\",\"dtse\",\"dtsh\",\"dtsl\",\"dtsx\",\"dvav\",\"dvhe\",\"ec-3\",\"enca\",\"encf\",\"encm\",\"encs\",\r\n\t\"enct\",\"encv\",\"fdp\",\"g719\",\"g726\",\"hev1\",\"hvc1\",\"hvt1\",\"ixse\",\"lhv1\",\"lhe1\",\"lht1\",\"m2ts\",\"m4ae\",\"mett\",\r\n\t\"metx\",\"mha1\",\"mha2\",\"mhm1\",\"mhm2\",\"mjp2\",\"mlix\",\"mlpa\",\"mp4a\",\"mp4s\",\"mp4v\",\"mvc1\",\"mvc2\",\"mvc3\",\"mvc4\",\r\n\t\"mvd1\",\"mvd2\",\"mvd3\",\"mvd4\",\"oksd\",\"pm2t\",\"prtp\",\"raw\",\"resv\",\"rm2t\",\"rrtp\",\"rsrp\",\"rtmd\",\"rtp\",\"s263\",\r\n\t\"samr\",\"sawb\",\"sawp\",\"sevc\",\"sm2t\",\"sqcp\",\"srtp\",\"ssmv\",\"stpp\",\"stgs\",\"svc1\",\"svc2\",\"svcm\",\"tc64\",\"tmcd\",\r\n\t\"twos\",\"tx3g\",\"ulaw\",\"unid\",\"urim\",\"vc-1\",\"vp08\",\"vp09\",\"wvtt\",\r\n]\r\n\r\nlet videoavc1 = [\r\n\t\"42000a\",\"42000b\",\"42000c\",\"42000d\",\"420014\",\"420015\",\"420016\",\"42001e\",\"42001f\",\"420020\",\r\n\t\"420028\",\"420029\",\"42002a\",\"420032\",\"420033\",\"420034\",\"42400a\",\"42400b\",\"42400c\",\"42400d\",\r\n\t\"424014\",\"424015\",\"424016\",\"42401e\",\"42401f\",\"424020\",\"424028\",\"424029\",\"42402a\",\"424032\",\r\n\t\"424033\",\"424034\",\"4d000a\",\"4d000b\",\"4d000c\",\"4d000d\",\"4d0014\",\"4d0015\",\"4d0016\",\"4d001e\",\r\n\t\"4d001f\",\"4d0020\",\"4d0028\",\"4d0029\",\"4d002a\",\"4d0032\",\"4d0033\",\"4d0034\",\"4d400a\",\"4d400b\",\r\n\t\"4d400c\",\"4d400d\",\"4d4014\",\"4d4015\",\"4d4016\",\"4d401e\",\"4d401f\",\"4d4020\",\"4d4028\",\"4d4029\",\r\n\t\"4d402a\",\"4d4032\",\"4d4033\",\"4d4034\",\"58000a\",\"58000b\",\"58000c\",\"58000d\",\"580014\",\"580015\",\r\n\t\"580016\",\"58001e\",\"58001f\",\"580020\",\"580028\",\"580029\",\"58002a\",\"580032\",\"580033\",\"580034\",\r\n\t\"64000a\",\"64000b\",\"64000c\",\"64000d\",\"640014\",\"640015\",\"640016\",\"64001e\",\"64001f\",\"640020\",\r\n\t\"640028\",\"640029\",\"64002a\",\"640032\",\"640033\",\"640034\",\"64080a\",\"64080b\",\"64080c\",\"64080d\",\r\n\t\"640814\",\"640815\",\"640816\",\"64081e\",\"64081f\",\"640820\",\"640828\",\"640829\",\"64082a\",\"640832\",\r\n\t\"640833\",\"640834\",\"6e000a\",\"6e000b\",\"6e000c\",\"6e000d\",\"6e0014\",\"6e0015\",\"6e0016\",\"6e001e\",\r\n\t\"6e001f\",\"6e0020\",\"6e0028\",\"6e0029\",\"6e002a\",\"6e0032\",\"6e0033\",\"6e0034\",\"6e100a\",\"6e100b\",\r\n\t\"6e100c\",\"6e100d\",\"6e1014\",\"6e1015\",\"6e1016\",\"6e101e\",\"6e101f\",\"6e1020\",\"6e1028\",\"6e1029\",\r\n\t\"6e102a\",\"6e1032\",\"6e1033\",\"6e1034\",\"7a000a\",\"7a000b\",\"7a000c\",\"7a000d\",\"7a0014\",\"7a0015\",\r\n\t\"7a0016\",\"7a001e\",\"7a001f\",\"7a0020\",\"7a0028\",\"7a0029\",\"7a002a\",\"7a0032\",\"7a0033\",\"7a0034\",\r\n\t\"7a100a\",\"7a100b\",\"7a100c\",\"7a100d\",\"7a1014\",\"7a1015\",\"7a1016\",\"7a101e\",\"7a101f\",\"7a1020\",\r\n\t\"7a1028\",\"7a1029\",\"7a102a\",\"7a1032\",\"7a1033\",\"7a1034\",\"f4000a\",\"f4000b\",\"f4000c\",\"f4000d\",\r\n\t\"f40014\",\"f40015\",\"f40016\",\"f4001e\",\"f4001f\",\"f40020\",\"f40028\",\"f40029\",\"f4002a\",\"f40032\",\r\n\t\"f40033\",\"f40034\",\"f4100a\",\"f4100b\",\"f4100c\",\"f4100d\",\"f41014\",\"f41015\",\"f41016\",\"f4101e\",\r\n\t\"f4101f\",\"f41020\",\"f41028\",\"f41029\",\"f4102a\",\"f41032\",\"f41033\",\"f41034\",\"2c000a\",\"2c000b\",\r\n\t\"2c000c\",\"2c000d\",\"2c0014\",\"2c0015\",\"2c0016\",\"2c001e\",\"2c001f\",\"2c0020\",\"2c0028\",\"2c0029\",\r\n\t\"2c002a\",\"2c0032\",\"2c0033\",\"2c0034\",\r\n]\r\nlet a1 = [0, 1, 2],\r\n\ta2 = [\"00\",\"01\",\"02\",\"03\",\"04\",\"05\",\"06\",\"07\",\"08\",\"09\",10,11,12,13,14,15,16,17,18,19,20,21,22,23,31],\r\n\ta3 = [\"H\",\"M\"],\r\n\ta4 = [\"08\",\"10\",\"12\"]\r\nlet videoav01 = []\r\na1.forEach(function(part1) {\r\n\ta2.forEach(function(part2) {\r\n\t\ta3.forEach(function(part3) {\r\n\t\t\ta4.forEach(function(part4) {\r\n\t\t\t\tlet value = part1 +\".\"+ part2 +\"\"+ part3 +\".\"+ part4\r\n\t\t\t\tvideoav01.push(value)\r\n\t\t\t})\r\n\t\t})\r\n\t})\r\n})\r\nvideomp4.forEach(function(codec) {videolist.push('video/mp4; codecs=\"'+ codec +'\"')})\r\nvideoavc1.forEach(function(codec) {videolist.push('video/mp4; codecs=\"avc1.'+ codec +'\"')})\r\nvideoav01.forEach(function(codec) {videolist.push('video/mp4; codecs=\"av01.'+ codec +'\"')})\r\n\r\nlet oData = {}\r\n\r\nfunction display(item) {\r\n\tlet data = oData[item]\r\n\tlet hash = mini(data)\r\n\tlet count = \"\"\r\n\tif (item == \"audiolist\" || item == \"videolist\") {count = s13 +\" [\"+ data.length +\"]\"+ sc}\r\n\tdom.details.innerHTML = s13 + item + \": \" +sc + hash + count +\"<br>\"+ json_highlight(data) //JSON.stringify(data, null, 2)\r\n}\r\n\r\nfunction getButton(name, text = \"details\") {\r\n\treturn \" <span class='btn13 btnc' onClick='display(`\"+ name +\"`)'>[\"+ text +\"]</span>\"\r\n}\r\n\r\nfunction get_media(type) {\r\n\t// https://privacycheck.sec.lrz.de/active/fp_cpt/fp_can_play_type.html\r\n\t// https://cconcolato.github.io/media-mime-support/\r\n\tconst METRICcan = \"canPlayType_\"+ type,\r\n\t\tMETRICtype = \"isTypeSupported_\"+ type\r\n\r\n\tlet oMedia = {\r\n\t\t\"canPlay\": {\"maybe\": [],\"probably\": []},\r\n\t\t\"isType\": {\"recorder\": [],\"source\": []}\r\n\t}\r\n\r\n\ttry {\r\n\t\tvar obj = document.createElement(type)\r\n\t\t// collect\r\n\t\tlet go1 = true, go2 = true, go3 = true, err1, err2, err3\r\n\t\tlet list = oData[type +\"list\"]\r\n\t\tlist.sort()\r\n\r\n\t\tlist.forEach(function(item) {\r\n\t\t\tlet tmp = item.replace(type +\"\\/\",\"\") // strip \"video/\",\"audio/\"\r\n\t\t\tif (go1) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tlet str = obj.canPlayType(item)\r\n\t\t\t\t\tif (str == \"maybe\" || str == \"probably\") {oMedia[\"canPlay\"][str].push(tmp)}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tgo1 = false; err1 = zErr; console.log(type, \"canPlayType\", e)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (go2) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tif (MediaRecorder.isTypeSupported(item)) {oMedia[\"isType\"][\"recorder\"].push(tmp)}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tgo2 = false; err2 = zErr; console.log(type, \"MediaRecorder\", e)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (go3) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tif (MediaSource.isTypeSupported(item)) {oMedia[\"isType\"][\"source\"].push(tmp)}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tgo3 = false; err3 = zErr; console.log(type, \"MediaSource\", e)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t// canplay\r\n\t\tlet canDisplay\r\n\t\tif (go1) {\r\n\t\t\tlet aMaybe = oMedia[\"canPlay\"][\"maybe\"]\r\n\t\t\tlet aProbably = oMedia[\"canPlay\"][\"probably\"]\r\n\t\t\tif (aMaybe.length == 0 && aProbably.length == 0) {\r\n\t\t\t\tcanDisplay = \"none\"\r\n\t\t\t} else {\r\n\t\t\t\tlet canobj = {}\r\n\t\t\t\tif (aMaybe.length) {canobj[\"maybe\"] = aMaybe.sort()}\r\n\t\t\t\tif (aProbably.length) {canobj[\"probably\"] = aProbably.sort()}\r\n\t\t\t\tlet canHash = mini(canobj)\r\n\t\t\t\tcanDisplay = canHash + getButton(METRICcan, aMaybe.length +\"/\" + aProbably.length)\r\n\t\t\t\toData[METRICcan] = canobj\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tcanDisplay = err1\r\n\t\t}\r\n\t\tdom[type +\"can\"].innerHTML = canDisplay\r\n\r\n\t\t// isType\r\n\t\tlet typeDisplay\r\n\t\tif (!go2 && !go3) {\r\n\t\t\ttypeDisplay = err2 // just display first error\r\n\t\t} else {\r\n\t\t\tlet aRecorder = oMedia[\"isType\"][\"recorder\"]\r\n\t\t\tlet aSource = oMedia[\"isType\"][\"source\"]\r\n\t\t\tif (aRecorder.length == 0 && aSource.length == 0) {\r\n\t\t\t\ttypeDisplay = \"none\"\r\n\t\t\t} else {\r\n\t\t\t\tlet typeobj = {}\r\n\t\t\t\tif (go2 && aRecorder.length) {typeobj[\"MediaRecorder\"] = aRecorder.sort()}\r\n\t\t\t\tif (go3 && aSource.length) {typeobj[\"MediaSource\"] = aSource.sort()}\r\n\t\t\t\tlet typeHash = mini(typeobj)\r\n\t\t\t\tlet notation = (go2 ? aRecorder.length : zErr) +\"/\"+ (go3 ? aSource.length : zErr)\r\n\t\t\t\ttypeDisplay = typeHash + getButton(METRICtype, notation)\r\n\t\t\t\toData[METRICtype] = typeobj\r\n\t\t\t}\r\n\t\t}\r\n\t\tdom[type +\"type\"].innerHTML = typeDisplay\r\n\t\t// ToDo: media: remove audio/video element?\r\n\t\treturn\r\n\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t\tdom[type +\"can\"].innerHTML = zErr\r\n\t\tdom[type +\"type\"].innerHTML = zErr\r\n\t\treturn\r\n\t}\r\n}\r\n\r\nfunction run() {\r\n\t// reset\r\n\tdom.details = \"click counts to list their results here\"\r\n\toData = {}\r\n\toData[\"audiolist\"] = audiolist\r\n\toData[\"videolist\"] = videolist\r\n\r\n\tlet t0 = performance.now()\r\n\tPromise.all([\r\n\t\tget_media(\"audio\"),\r\n\t\tget_media(\"video\"),\r\n\t]).then(function(){\r\n\t\tdom.perf = Math.round(performance.now() - t0) + \" ms\"\r\n\t})\t\r\n}\r\n\r\n// do once\r\naudiolist.sort()\r\nlet audiocount = audiolist.length\r\naudiolist = audiolist.filter(function(item, position) {return audiolist.indexOf(item) === position})\r\nif (audiolist.length !== audiocount) {console.log(\"audiolist has dupes\")}\r\n\r\nvideolist.sort()\r\nlet videocount = videolist.length\r\n//console.log(videolist.join(\"\\n\"))\r\nvideolist = videolist.filter(function(item, position) {return videolist.indexOf(item) === position})\r\nif (videolist.length !== videocount) {console.log(\"videolist has dupes\", videocount, videolist.length)}\r\n//console.log(videolist.join(\"\\n\"))\r\n\r\nrun()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/collation.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>collation</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">collation</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Output is limited to results that do not match English.\r\n\t\t\tClicking run with no inputs will instead run examples</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<!-- localeCompare -->\r\n\t\t\t\t<hr><br>\r\n\t\t\t\t\t<span class=\"s4\">LOCALECOMPARE</span>\r\n\t\t\t\t\t<span class=\"btn4 btn\" onClick=\"compare()\">[ run ]</span>\r\n\t\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset(`compare`)\">[ clear input ]</span>\r\n\t\t\t\t<br><br>\r\n\t\t\t\t<input type=\"text\" placeholder=\"a\" id=\"valueA\"> &nbsp; <input type=\"text\" placeholder=\"A\" id=\"valueB\">\r\n\t\t\t\t<br>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"output_compare\"></span>\r\n\t\t\t\t<!-- Intl.Collator -->\r\n\t\t\t\t<br><br><hr><br>\r\n\t\t\t\t\t<span class=\"s4\">INTL.COLLATOR</span>\r\n\t\t\t\t\t<span class=\"btn4 btn\" onClick=\"collator()\">[ run ]</span>\r\n\t\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset(`collator`)\">[ clear input ]</span>\r\n\t\t\t\t<br><br>\r\n\t\t\t\t<textarea rows=\"3\" placeholder=\"sort: comma delimited values: e.g: a,A,th,tw\"\r\n\t\t\t\t\t\tstyle=\"width: 98%; resize: vertical\" id=\"valueC\"></textarea>\r\n\t\t\t\t<textarea rows=\"1\" placeholder=\"search: comma delimited values: e.g: a,A,th,tw\"\r\n\t\t\t\t\t\tstyle=\"width: 98%; resize: vertical\" id=\"valueD\"></textarea>\r\n\t\t\t\t<br><br>\r\n\t\t\t\t\t<span class=\"spaces\" id=\"output_collator\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet isReverse = false\r\n\r\nvar list = []\r\ngLocales.forEach(function(str) {\r\n\tlet code = str.split(\",\")[0]\r\n\tif (Intl.Collator.supportedLocalesOf([code]).length) {list.push(str)}\r\n})\r\n\r\nvar collatorSort = [\r\n\t'a',\r\n\t'A',\r\n\t'aa',\r\n\t'ch', // cs,sk,sq,uz\r\n\t'ez',\r\n\t'kz',\r\n\t'ng',\r\n\t'ph',\r\n\t'ts','tt', // ew, ha\r\n\t'y',\r\n\t// latin small\r\n\t'\\u00E2',   // a + CIRCUMFLEX\r\n\t'\\u00E4',   // a + DIAERESIS\r\n\t'\\u01FB',   // a + RING ABOVE + ACUTE\r\n\t'\\u0107',   // c + ACUTE\r\n\t'\\u0109',   // c + CIRCUMFLEX\r\n\t'\\u00E7\\a', // c + CEDILLA\r\n\t'\\u00EB',   // e + DIAERESIS\r\n\t'\\u00ED',   // i + ACUTE\r\n\t'\\u00EE',   // i + CIRCUMFLEX\r\n\t'\\u0137\\a', // k + CEDILLA\r\n\t'\\u0144',   // n + ACUTE\r\n\t'\\u00F1',   // n + TILDE\r\n\t'\\u1ED9',   // o + CIRCUMFLEX + DOT BELOW\r\n\t'\\u00F6',   // o + DIAERESIS\r\n\t'\\u1EE3',   // o + HORN + DOT BELOW\r\n\t// other\r\n\t'\\u0627',  //     ARABIC ALEF\r\n\t'\\u0649',  //     ARABIC ALEF MAKSURA\r\n\t'\\u06CC',  //     ARABIC FARSI YEH\r\n\t'\\u06C6',  //     ARABIC OE\r\n\t'\\u06C7',  //     ARABIC U\r\n\t'\\u06FD',  //     ARABIC SINDHI AMPERSAND\r\n\t'\\u0561',  //   ARMENIAN AYB small\r\n\t'\\u09A4',  //    BENGALI TA\r\n\t'\\u09CE',  //    BENGALI KHANDA TA\r\n\t'\\u311A',  //   BOPOMOFO A\r\n\t'\\u0453',  //   CYRILLIC GJE small\r\n\t'\\uA647',  //   CYRILLIC IOTA small\r\n\t'\\u0503',  //   CYRILLIC KOMI DJE small\r\n\t'\\u0439',  //   CYRILLIC SHORT I small\r\n\t'\\u0457',  //   CYRILLIC YI small\r\n\t'\\u040E',  //   CYRILLIC SHORT U capital\r\n\t'\\u04F0',  //   CYRILLIC U + DIAERESIS capital\r\n\t'\\u4E2D',  //        CJK Ideograph\r\n\t'\\u0934',  // DEVANAGARI LLLA\r\n\t'\\u0935',  // DEVANAGARI VA\r\n\t'\\u1208',  //   ETHIOPIC SYLLABLE LA (amharic)\r\n\t'\\u10D0',  // \tGEORGIAN AN\r\n\t'\\u03B1',  //      GREEK ALPHA small\r\n\t'\\u0A85',  //   GUJARATI A\r\n\t'\\u3147',  //     HANGUL IEUNG\r\n\t'\\u05EA',  //     HEBREW TAV\r\n\t'\\uFB4A',  //     HEBREW TAV + DAGESH\r\n\t'\\u0C85',  //    KANNADA A\r\n\t'\\u1780',  //      KHMER KA\r\n\t'\\u0E9A',  //        LAO BO\r\n\t'\\u1D95',  //      LATIN SCHWA + RETROFLEX HOOK\r\n\t'\\u025B',  //      LATIN SMALL OPEN E\r\n\t'\\u0149',  //      LATIN SMALL N PRECEDED BY APOSTROPHE\r\n\t'\\u00F0',  //      LATIN SMALL ETH small\r\n\t'\\u1DD9',  // COMBINING LATIN SMALL LETTER ETH\r\n\t'\\u1820',  //  MONGOLIAN A\r\n\t'\\u10350', // OLD PERMIC AN\r\n\t'\\u0B05',  //      ORIYA A\r\n\t'\\u0D85',  //    SINHALA AYANNA\r\n\t'\\u0B85',  //      TAMIL A\r\n\t'\\u0C05',  //     TELUGU A\r\n\t'\\u0E24',  //       THAI RU\r\n]\r\n\r\nvar collatorSearch = [\r\n\t// latin small\r\n\t'\\u0107',   // c + ACUTE\r\n\t'\\u0109',   // c + CIRCUMFLEX\r\n\t'\\u1ED9',   // o + CIRCUMFLEX + DOT BELOW\r\n\t'\\u00F6',   // o + DIAERESIS\r\n]\r\n\r\nvar compareExamples = [\r\n\t'\\u0109,\\u00E7\\a',\r\n\t'a,\\u03B1',\r\n\t'\\u0107,\\u0109',\r\n\t'c,ch',\r\n\t'\\u00EB,ez',\r\n\t'\\u0137\\a,kz',\r\n\t'\\u1ED9,\\u1EE3',\r\n\t'\\u0109,ch',\r\n\t'\\u0627,\\u06FD',\r\n\t'a,A',\r\n\t'\\u00E7\\a,ch',\r\n\t'\\u0144,\\u00F1',\r\n\t'n,ng',\r\n\t'\\u00ED,\\u00EE',\r\n\t'r,\\u0453',\r\n\t'\\u00F6,\\u1EE3',\r\n\t'\\u00F1,ng',\r\n\t'\\u0107,ch',\r\n\t'ts,tt',\r\n\t'\\u1D95,\\u025B',\r\n\t'\\u040E,\\u04F0',\r\n\t'u,\\u04F0',\r\n\t'x,y',\r\n\t'\\u05EA,\\uFB4A',\r\n]\r\n\r\nvar aLegend = [],\r\n\taLocales = [],\r\n\taLocaleGroups,\r\n\tcounter = 0,\r\n\toSortvsSearch = {},\r\n\toRaw = {}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tlet aCanonical = []\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet test = Intl.Collator.supportedLocalesOf([code])\r\n\t\t\tif (test.length == 1) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet\theader = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction reset(type) {\r\n\tif (type == \"compare\") {\r\n\t\tdom.valueA.value = \"\"\r\n\t\tdom.valueB.value = \"\"\r\n\t} else {\r\n\t\tdom.valueC.value = \"\"\r\n\t\tdom.valueD.value = \"\"\r\n\t}\r\n}\r\n\r\nfunction check_example() {\r\n\tlet control = collatorSort.length\r\n\t// make sure example is dupe free\r\n\tcollatorSort = collatorSort.filter(function (item, position) {\r\n\t\treturn collatorSort.indexOf(item) === position\r\n\t})\r\n\tif (collatorSort.length !== control) {\r\n\t\tconsole.debug(\"attention thorin: collation example contains dupes.. get your shit together\")\r\n\t}\r\n}\r\n\r\nfunction compare() {\r\n\tfunction example() {\r\n\t\tvalueA = compareExamples[counter].split(\",\")[0]\r\n\t\tvalueB = compareExamples[counter].split(\",\")[1]\r\n\t\tcounter++\r\n\t\tif (counter == compareExamples.length) {counter = 0}\r\n\t}\r\n\t// clear\r\n\tlet el = dom.output_compare\r\n\t// vars\r\n\tlet valueA = dom.valueA.value,\r\n\t\tvalueB = dom.valueB.value,\r\n\t\tisExample = false,\r\n\t\tgo = false\r\n\t// make sure we have two valid values\r\n\tvalueA = valueA.trim()\r\n\tvalueB = valueB.trim()\r\n\tif (valueA.length && valueB.length) {\r\n\t\tif (valueA !== valueB) {\r\n\t\t\tdom.valueA.value = valueA\r\n\t\t\tdom.valueB.value = valueB\r\n\t\t\tgo = true\r\n\t\t}\r\n\t}\r\n\t// run random example\r\n\tif (valueA.length == 0 && valueB.length == 0) {\r\n\t\tisExample = true\r\n\t\texample()\r\n\t\tgo = true\r\n\t}\r\n\tif (!isExample) {\r\n\t\tel.innerHTML = \"&nbsp\"\r\n\t}\r\n\t// good to go\r\n\tif (go) {\r\n\t\t// delay so user can see changes\r\n\t\tsetTimeout(function() {\r\n\t\t\tlet output = [], str = \"\"\r\n\t\t\tif (isExample) {\r\n\t\t\t\tstr = \"example \" + s12 + (counter == 0 ? compareExamples.length : counter)\r\n\t\t\t\t\t+ sc +\" of \"+ s12 + compareExamples.length + sc +\": comparing \"\r\n\t\t\t\t\t+ s12 + valueA + sc +\" and \"+ s12 + valueB + sc +\"<br>\"\r\n\t\t\t\toutput.push(str)\r\n\t\t\t} else {\r\n\t\t\t\tstr = \"comparing \" + s12 + valueA + sc +\" and \"+ s12 + valueB + sc +\"<br>\"\r\n\t\t\t\toutput.push(str)\r\n\t\t\t}\r\n\t\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\t\tlet control = valueA.localeCompare(valueB, \"en-US\")\r\n\t\t\t\tlet code = list[i].split(\",\")[0]\r\n\t\t\t\tlet name = list[i].split(\",\")[1].trim()\r\n\t\t\t\tlet test = valueA.localeCompare(valueB, code)\r\n\t\t\t\tif (control !== test) {\r\n\t\t\t\t\toutput.push(s12 + code.padStart(12) + sc +\": \"+ name)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (output.length == 1) {\r\n\t\t\t\toutput.push(\"    nothing to report\")\r\n\t\t\t}\r\n\t\t\tel.innerHTML = output.join(\"<br>\")\r\n\t\t}, 170)\r\n\t} else {\r\n\t\t// crash and burn\r\n\t\tel.innerHTML = \"please provide two different values\"\r\n\t}\r\n}\r\n\r\nfunction collator() {\r\n\t// clear\r\n\tlet el = dom.output_collator\r\n\tel.innerHTML = \"&nbsp\"\r\n\tlegend()\r\n\t// vars\r\n\tlet valueC = dom.valueC.value,\r\n\t\tvalueD = dom.valueD.value,\r\n\t\tcharsSort = [],\r\n\t\tcharsSearch = [],\r\n\t\tgoSort = false,\r\n\t\tgoSearch = false\r\n\r\n\t// make sure we have at least two valid values\r\n\tvalueC = valueC.trim()\r\n\tif (valueC.length) {\r\n\t\tlet tmpArr = valueC.split(\",\")\r\n\t\tfor (let i = 0 ; i < tmpArr.length; i++) {\r\n\t\t\tlet trimmed = tmpArr[i].trim()\r\n\t\t\tif (trimmed.length) {\r\n\t\t\t\tcharsSort.push(trimmed)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// make sure we have more than one item\r\n\t\tcharsSort = charsSort.filter(function (item, position) {\r\n\t\t\treturn charsSort.indexOf(item) === position\r\n\t\t})\r\n\t\tif (charsSort.length > 1) {\r\n\t\t\tgoSort = true\r\n\t\t\tdom.valueC.value = charsSort.join(\" , \")\r\n\t\t}\r\n\t} else {\r\n\t\t// use example\r\n\t\tcharsSort = collatorSort\r\n\t\tgoSort = true\r\n\t}\r\n\r\n\tvalueD = valueD.trim()\r\n\tif (valueD.length) {\r\n\t\tlet tmpArr = valueD.split(\",\")\r\n\t\tfor (let i = 0 ; i < tmpArr.length; i++) {\r\n\t\t\tlet trimmed = tmpArr[i].trim()\r\n\t\t\tif (trimmed.length) {\r\n\t\t\t\tcharsSearch.push(trimmed)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// make sure we have more than one item\r\n\t\tcharsSearch = charsSearch.filter(function (item, position) {\r\n\t\t\treturn charsSearch.indexOf(item) === position\r\n\t\t})\r\n\t\tif (charsSearch.length > 1) {\r\n\t\t\tgoSearch = true\r\n\t\t\tdom.valueD.value = charsSearch.join(\" , \")\r\n\t\t}\r\n\t} else {\r\n\t\t// use example\r\n\t\tcharsSearch = collatorSearch\r\n\t\tgoSearch = true\r\n\t}\r\n\r\n\t// good to go\r\n\tif (goSort == true && goSearch == true) {\r\n\t\t// reset\r\n\t\toSortvsSearch = {\"search_is_nonenglish\": [],} \r\n\t\toRaw = {}\r\n\r\n\t\t// delay so user can see changes\r\n\t\tsetTimeout(function() {\r\n\t\t\tlet output = []\r\n\t\t\tlet isDetails = charsSort.join(', ').length > 50\r\n\t\t\t// reset\r\n\t\t\tcharsSort.sort()\r\n\t\t\tcharsSearch.sort()\r\n\t\t\t// what if we reverse sorted, does that change anything\r\n\t\t\tif (isReverse) {\r\n\t\t\t\t//console.log('reversed chars arrays')\r\n\t\t\t\tcharsSort.reverse()\r\n\t\t\t\tcharsSearch.reverse()\r\n\t\t\t}\r\n\t\t\t//console.log(mini(charsSort), mini(charsSearch))\r\n\t\t\tcollatorSort.sort()\r\n\t\t\tcollatorSearch.sort()\r\n\t\t\tlet exampleHash = mini(collatorSort) +\" | \"+ mini(collatorSearch)\r\n\t\t\tlet thisHash = mini(charsSort) +\" | \"+ mini(charsSearch)\r\n\r\n\t\t\tif (isReverse) {\r\n\t\t\t\t//console.log('reversed collator arrays')\r\n\t\t\t\tcollatorSort.reverse()\r\n\t\t\t\tcollatorSearch.reverse()\r\n\t\t\t\texampleHash = mini(collatorSort) +\" | \"+ mini(collatorSearch)\r\n\t\t\t\tthisHash = mini(charsSort) +\" | \"+ mini(charsSearch)\r\n\t\t\t}\r\n\r\n\t\t\t// display chars used\r\n\t\t\tlet strItem = s4.trim() +\"ITEMS: \"+ sc + charsSort.length\r\n\t\t\t\t+ (thisHash == exampleHash ? s12 +\" [example]\"+ sc : \"\") +\"<br><br>\"\r\n\t\t\t\t+ s12 +\"sort: \"+ mini(charsSort.join(\" , \").trim()) +': '+ sc + charsSort.join(\" , \").trim() +\"<br><br>\"\r\n\t\t\t\t+ s12 +\"search: \"+ sc + charsSearch.join(\" , \").trim() +\"<br>\"\r\n\t\t\toutput.push(strItem)\r\n\r\n\t\t\t// set control\r\n\t\t\tcharsSort = charsSort.sort(Intl.Collator(\"en\").compare)\r\n\t\t\tlet ensortdata = charsSort.join(\" , \").trim()\r\n\t\t\tlet ensorthash = mini(ensortdata)\r\n\t\t\tcharsSearch.sort(Intl.Collator(\"en\", {usage: \"search\"}).compare)\r\n\t\t\tlet ensearchdata = charsSearch.join(\" , \").trim()\r\n\t\t\tlet ensearchhash = mini(ensearchdata)\r\n\t\t\tlet entmpobj = {\r\n\t\t\t\t\"search\": ensearchdata,\r\n\t\t\t\t\"sort\": ensortdata,\r\n\t\t\t}\r\n\r\n\t\t\tlet control = mini(entmpobj)\r\n\t\t\tif (undefined == oRaw[control]) {oRaw[control] = entmpobj}\r\n\r\n\t\t\tlet control_display = s12 +'search: '+ sc + ensearchhash + s12 + \" | sort: \"+ sc + ensorthash + s12 + \" | both: \"+ sc + control\r\n\t\t\tstrItem = s4.trim() +\"CONTROL: \"+ sc + sg + \"en\"+ sc +\"<br><br>\"\r\n\t\t\t\t+ s12 +\"   hash: \"+ sc + control_display + \"<br>\"\r\n\t\t\tlet ctlDetails = \"<li>\"+ s14 +\"search: \"+ sc + oRaw[control]['search'] +\"</li>\"\r\n\t\t\t\t\t\t\t+ \"<li>\"+ s14 +\"  sort: \"+ sc + oRaw[control]['sort'] +\"</li>\"\r\n\t\t\tif (isDetails) {ctlDetails = \"<li><details><summary>\"+ s14 +\"data\"+ sc +\"</summary><ul>\"+ ctlDetails +\"</ul></details></li>\"}\r\n\r\n\t\t\toutput.push(strItem +\"<ul>\"+ ctlDetails +\"</ul>\")\r\n\t\t\t// results header\r\n\t\t\tstrItem = s4.trim() +\"RESULTS\"+ sc +\"<br>\"\r\n\t\t\toutput.push(strItem)\r\n\r\n\t\t\t// loop\r\n\t\t\tlet diffs = [],\r\n\t\t\t\tlegendnew = [],\r\n\t\t\t\tcodes = []\r\n\t\t\tlet oTempData = {}\r\n\t\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\t\t// important: reset to original order\r\n\t\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\t\t// always reset order\r\n\t\t\t\tcharsSearch.sort()\r\n\t\t\t\tcharsSort.sort()\r\n\t\t\t\tif (isReverse) {\r\n\t\t\t\t\t//console.log('reversed test data', str)\r\n\t\t\t\t\tcharsSearch.reverse()\r\n\t\t\t\t\tcharsSort.reverse()\r\n\t\t\t\t}\r\n\r\n\t\t\t\tcharsSearch.sort(Intl.Collator(code, {usage: \"search\"}).compare)\r\n\t\t\t\tlet searchdata = charsSearch.join(\" , \").trim()\r\n\t\t\t\tlet searchhash = mini(searchdata)\r\n\r\n\t\t\t\tcharsSort.sort(Intl.Collator(code, {usage: \"sort\"}).compare)\r\n\t\t\t\tlet sortdata = charsSort.join(\" , \").trim()\r\n\t\t\t\tlet sorthash = mini(sortdata)\r\n\r\n\t\t\t\tlet tmpobj = {\r\n\t\t\t\t\t\"search\": searchdata,\r\n\t\t\t\t\t\"sort\": sortdata,\r\n\t\t\t\t}\r\n\t\t\t\tlet combinedhash = mini(tmpobj)\r\n\t\t\t\tif (undefined == oRaw[combinedhash]) {oRaw[combinedhash] = tmpobj}\r\n\r\n\t\t\t\tif (searchhash !== ensearchhash) {\r\n\t\t\t\t\toSortvsSearch[\"search_is_nonenglish\"].push(code + \": \" + searchhash +\", \" + sorthash)\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet hash = searchhash +\" | \"+ sorthash +\" | \"+ combinedhash // search then sort then combined\r\n\t\t\t\tif (combinedhash !== control) {\r\n\t\t\t\t\tcodes.push(code)\r\n\t\t\t\t\tdiffs.push(hash +\":\"+ code +\":\"+ name)\r\n\t\t\t\t\tif (oTempData[hash] == undefined) {oTempData[hash] = []}\r\n\t\t\t\t\toTempData[hash].push(code +\" \"+ name)\r\n\t\t\t\t}\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (combinedhash == control) {\r\n\t\t\t\t\tlegendnew.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlegendnew.push(sg + code.padStart(7) + sc +\": \"+ name)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// we have diffs\r\n\t\t\t// sort by hash+locale so we can group hashes\r\n\t\t\tdiffs.sort()\r\n\t\t\tif (diffs.length) {\r\n\t\t\t\t// output colored legend\r\n\t\t\t\tlet\theader = s4 +\"LEGEND [\"+ list.length +\"]\"+ sc +\"<br><br>\"\r\n\t\t\t\tdom.legend.innerHTML = header + legendnew.join(\"<br>\")\r\n\t\t\t\tlet tmpH = \"\", tmpC = \"\", tmpL = \"\", tmpStr = \"\", nxtH = \"\", locales = \"\"\r\n\t\t\t\tlet newArr = []\r\n\t\t\t\tfor (let i = 0 ; i < diffs.length; i++) {\r\n\t\t\t\t\t// get current item\r\n\t\t\t\t\ttmpStr = diffs[i]\r\n\t\t\t\t\ttmpH = tmpStr.split(\":\")[0] // hash\r\n\t\t\t\t\ttmpC = tmpStr.split(\":\")[1] // code\r\n\t\t\t\t\ttmpL = tmpStr.split(\":\")[2] // locale name (assuming no more colons)\r\n\t\t\t\t\tlocales += sg + tmpC + sc +\" \"+ tmpL +\" \" // color up code\r\n\t\t\t\t\t// grab next item\r\n\t\t\t\t\tif (i < diffs.length - 1) {\r\n\t\t\t\t\t\tnxtH = diffs[(i+1)].split(\":\")[0]\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tnxtH = \"end\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// build new item\r\n\t\t\t\t\tif (nxtH !== tmpH) {\r\n\t\t\t\t\t\tnewArr.push(locales.trim() +\":\"+ tmpH)\r\n\t\t\t\t\t\tlocales = \"\" // reset\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// sort newArr (i.e order by first locale)\r\n\t\t\t\tnewArr.sort()\r\n\t\t\t\tlet lineItems = []\r\n\t\t\t\ttmpH = \"\", tmpL = \"\", tmpStr = \"\"\r\n\t\t\t\tlet len = 0, tmpCH = \"\"\r\n\t\t\t\t// newArr\r\n\t\t\t\tfor (let i = 0 ; i < newArr.length; i++) {\r\n\t\t\t\t\ttmpStr = newArr[i]\r\n\t\t\t\t\tlen = tmpStr.length\r\n\t\t\t\t\ttmpL = tmpStr.split(\":\")[0]\r\n\t\t\t\t\ttmpH = tmpStr.split(\":\")[1] // hashes\r\n\t\t\t\t\ttmpCH = tmpH.split(\" | \")[2] // combined hash for data lookup\r\n\t\t\t\t\ttmpStr = s12 + (i + 1).toString().padStart(3) +\": \"+ sc\r\n\t\t\t\t\t// counter then hash then details then locales\r\n\t\t\t\t\tlet strDetails = \"<li>\"+ s14 +\"search: \"+ sc + oRaw[tmpCH]['search'] +\"</li>\"\r\n\t\t\t\t\t\t\t+ \"<li>\"+ s14 +\"  sort: \"+ sc + oRaw[tmpCH]['sort'] +\"</li>\"\r\n\t\t\t\t\tif (isDetails) {strDetails = \"<li><details><summary>\"+ s14 +\"data\"+ sc +\"</summary><ul>\"+ strDetails +\"</ul></details></li>\"}\r\n\t\t\t\t\tlineItems.push (tmpStr + tmpH +\"<br><ul>\"+ strDetails +\"<li>\"+ tmpL +\"</li></ul>\")\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// results output\r\n\t\t\t\tstrItem = s12 +\"  stats: \"+ sc\r\n\t\t\t\t\t+ sg + newArr.length +\" unique\"\r\n\t\t\t\t\t+ sc +\" from \"+ sg + codes.length + sc + \" of \"+ sg + aLocales.length + sc +\" locales supported\"\r\n\t\t\t\toutput.push(strItem +\"<br>\")\r\n\r\n\t\t\t\t// locales hash\r\n\t\t\t\taLocaleGroups = []\r\n\t\t\t\tfor (const k of Object.keys(oTempData)) {\r\n\t\t\t\t\taLocaleGroups.push(oTempData[k])\r\n\t\t\t\t}\r\n\t\t\t\taLocaleGroups.sort()\r\n\t\t\t\tlet localesHash = mini(aLocaleGroups)\r\n\r\n\t\t\t\tlet ff = \"\"\r\n\t\t\t\t// don't notate unless it's our own preset\r\n\t\t\t\tif (isFF && thisHash == exampleHash) {\r\n\t\t\t\t\t// notate new if 140+\r\n\t\t\t\t\tif (isVer > 139) {\r\n\t\t\t\t\t\tif (localesHash == \"874da3f0\") { // 1937541: FF150+ 83\r\n\t\t\t\t\t\t} else if (localesHash == \"bbe811d0\") { // FF140+ 84\r\n\t\t\t\t\t\t} else {localesHash += ' '+ zNEW}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tlet localesBtn = \" <span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\t\t\tstrItem = s12 +\"locales: \"+ sc + localesHash + localesBtn +\"<br>\"\r\n\t\t\t\toutput.push(strItem)\r\n\r\n\t\t\t\t// details\r\n\t\t\t\tstrItem = s4.trim() +\"DETAILS\"+ sc + s99 +' [search, sort, combined]' + sc +'<br>'\r\n\t\t\t\toutput.push(strItem)\r\n\t\t\t\toutput.push(lineItems.join(\"<br>\"))\r\n\t\t\t} else {\r\n\t\t\t\toutput.push(\"nothing to report\")\r\n\t\t\t}\r\n\t\t\tel.innerHTML = output.join(\"<br>\")\r\n\t\t}, 170)\r\n\t} else {\r\n\t\t// crash and burn\r\n\t\tel.innerHTML = \"please provide two different values\"\r\n\t}\r\n}\r\n\r\nfunction log_console(name) {\r\n\tif (name == 'locales') {\r\n\t\tlet hash = mini(aLocaleGroups)\r\n\t\tconsole.log(name +': ' + hash +'\\n'+ aLocaleGroups.join('\\n'))\r\n\t}\r\n}\r\n\r\ncheck_example()\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t// blink\r\n\t\t'fa-af,persian (afghanistan)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\tcollator()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/csscolors.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>css colors</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 480px; max-width: 580px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"hidden\"><span id=\"sColorElement\"></span></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#css\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb14\">\r\n\t\t<col width=\"15%\"><col width=\"85%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">css colors\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"no_color\">\r\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://www.w3.org/TR/css-color-4/\">https://www.w3.org/TR/css-color-4/</a>\r\n\t\t</span></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td colspan=\"2\" style=\"text-align: left;\">\r\n\t\t\t<hr><br>\r\n\t\t\t<span class=\"no_color c mono spaces\" id=\"summary\"></span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t<span class=\"no_color c mono spaces\" id=\"results\"></span></td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n<script>\r\n'use strict';\r\n\r\nlet oColors = {\r\n\t\"all\": {\r\n\t\t\"countcolor\": 0,\r\n\t\t\"countsupported\": 0,\r\n\t\t\"data\": {},\r\n\t\t\"databyname\": [],\r\n\t\t\"hash\": \"\",\r\n\t}\r\n}\r\n\r\nfunction get_colors() {\r\n\r\n\t/* https://www.w3.org/TR/css-color-4/ */\r\n\t/* 95+: test_bug232227.html */\r\n\t// sorted lists\r\n\tlet oList = {\r\n\t\t\"css4\": [\r\n\t\t\t'-moz-activehyperlinktext','-moz-default-color','-moz-default-background-color',\r\n\t\t\t'-moz-hyperlinktext','-moz-visitedhyperlinktext',\r\n\t\t\t'AccentColor','AccentColorText','ActiveText','ButtonBorder','ButtonFace','ButtonText',\r\n\t\t\t'Canvas','CanvasText','Field','FieldText','GrayText','Highlight','HighlightText','LinkText',\r\n\t\t\t'Mark','MarkText','SelectedItem','SelectedItemText','VisitedText',\r\n\t\t],\r\n\t\t\"deprecated\": [ // 23 \"deprecated\"\r\n\t\t\t'ActiveBorder','ActiveCaption','AppWorkspace','Background','ButtonHighlight','ButtonShadow',\r\n\t\t\t'CaptionText','InactiveBorder','InactiveCaption','InactiveCaptionText','InfoBackground',\r\n\t\t\t'InfoText','Menu','MenuText','Scrollbar','ThreeDDarkShadow','ThreeDFace','ThreeDHighlight',\r\n\t\t\t'ThreeDLightShadow','ThreeDShadow','Window','WindowFrame','WindowText',\r\n\t\t],\r\n\t\t\"moz\": [\r\n\t\t\t'-moz-cellhighlight','-moz-cellhighlighttext','-moz-combobox','-moz-comboboxtext','-moz-dialog',\r\n\t\t\t'-moz-dialogtext','-moz-field','-moz-fieldtext','-moz-html-cellhighlight','-moz-html-cellhighlighttext',\r\n\t\t\t'-moz-menubarhovertext','-moz-menuhover','-moz-menuhovertext','-moz-oddtreerow',\r\n\t\t\t// removed FF141: 1968925\r\n\t\t\t'-moz-buttonhoverface','-moz-buttonhovertext',\r\n\t\t\t// removed FF140: can't find bugzilla\r\n\t\t\t'-moz-eventreerow',\r\n\t\t\t// removed FF122: 1867854\r\n\t\t\t'-moz-mac-defaultbuttontext','-moz-mac-disabledtoolbartext', '-moz-mac-focusring','-moz-nativehyperlinktext', \r\n\t\t\t// removed FF121: 1863691\r\n\t\t\t'-moz-mac-active-menuitem','-moz-mac-active-source-list-selection','-moz-mac-menuitem',\r\n\t\t\t'-moz-mac-menupopup','-moz-mac-source-list','-moz-mac-source-list-selection','-moz-mac-tooltip',\r\n\t\t\t// removed FF119: 1857695\r\n\t\t\t'-moz-mac-menutextdisable','-moz-mac-menutextselect',\r\n\t\t\t// removed FF117\r\n\t\t\t\"-moz-buttondefault\",\"-moz-dragtargetzone\",\"-moz-mac-chrome-active\",\"-moz-mac-chrome-inactive\",\r\n\t\t\t\"-moz-mac-menuselect\",\"-moz-mac-menushadow\",\"-moz-mac-secondaryhighlight\",\"-moz-menubartext\",\r\n\t\t\t\"-moz-win-communicationstext\",\"-moz-win-mediatext\",\r\n\t\t\t// removed FF103\r\n\t\t\t'-moz-mac-vibrant-titlebar-dark','-moz-mac-vibrant-titlebar-light',\r\n\t\t\t// removed FF94\r\n\t\t\t'-moz-mac-buttonactivetext',\r\n\t\t\t// removed FF90\r\n\t\t\t'-moz-gtk-info-bar-text',\r\n\t\t\t// removed FF88\r\n\t\t\t'-moz-mac-vibrancy-dark','-moz-mac-vibrancy-light','-moz-win-accentcolor','-moz-win-accentcolortext',\r\n\t\t\t// removed FF78 or lower\r\n\t\t\t'-moz-accent-color','-moz-accent-color-foreground','-moz-appearance','-moz-colheaderhovertext',\r\n\t\t\t'-moz-colheadertext','-moz-gtk-buttonactivetext','-moz-mac-accentdarkestshadow',\r\n\t\t\t'-moz-mac-accentdarkshadow','-moz-mac-accentface','-moz-mac-accentlightesthighlight',\r\n\t\t\t'-moz-mac-accentlightshadow','-moz-mac-accentregularhighlight','-moz-mac-accentregularshadow',\r\n\t\t\t'-moz-win-communications-toolbox','-moz-win-media-toolbox',\r\n\t\t\t// test\r\n\t\t\t//'-moz-made-up'\r\n\t\t],\r\n\t}\r\n\t// note: when contrast control (forced colors) is used, removed -moz named colors will be false positives\r\n\r\n\tfunction rgba2hex(orig) {\r\n\t\tvar a, isPercent,\r\n\t\t\trgb = orig.replace(/\\s/g, '').match(/^rgba?\\((\\d+),(\\d+),(\\d+),?([^,\\s)]+)?/i),\r\n\t\t\talpha = (rgb && rgb[4] || \"\").trim(),\r\n\t\t\thex = rgb ?\r\n\t\t\t(rgb[1] | 1 << 8).toString(16).slice(1) +\r\n\t\t\t(rgb[2] | 1 << 8).toString(16).slice(1) +\r\n\t\t\t(rgb[3] | 1 << 8).toString(16).slice(1) : orig;\r\n\t\tif (alpha !== '') {a = alpha\r\n\t\t} else {\r\n\t\t\ta = 0o1\r\n\t\t\trgb = rgb.slice(0, rgb.length - 1)\r\n\t\t}\r\n\t\t// multiply before convert to HEX\r\n\t\ta = ((a * 255) | 1 << 8).toString(16).slice(1)\r\n\t\thex = hex + a\r\n\t\trgb = rgb.slice(1, rgb.length)\r\n\t\treturn hex +' '+ rgb.join('-')\r\n\t}\r\n\r\n\ttry {\r\n\t\tconst element = dom.sColorElement\r\n\t\tconst strColor = \"rgba(1, 2, 3, 0.5)\"\r\n\t\tlet tmpAll = {}, aTempAll = [], count = 0\r\n\t\tfor (const type of Object.keys(oList)) {\r\n\t\t\tlet oTemp = {}\r\n\t\t\tlet aList = oList[type]\r\n\t\t\taList.sort()\r\n\t\t\taList.forEach(function(style) {\r\n\t\t\t\telement.style.backgroundColor = strColor // reset color\r\n\t\t\t\telement.style.backgroundColor = style\r\n\t\t\t\tlet rgb = window.getComputedStyle(element, null).getPropertyValue(\"background-color\")\r\n\t\t\t\tif (rgb !== strColor) { // drop obsolete\r\n\t\t\t\t\tif (oTemp[rgb] == undefined) {oTemp[rgb] = [style]} else {oTemp[rgb].push(style)}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tlet tmpobj = {}\r\n\t\t\tcount = 0\r\n\t\t\tfor (const k of Object.keys(oTemp)) {tmpobj[rgba2hex(k)] = oTemp[k]} // rgba2hex\r\n\t\t\toColors[type] = {\r\n\t\t\t\t\"countcolor\": 0,\r\n\t\t\t\t\"countsupported\": 0,\r\n\t\t\t\t\"data\": {},\r\n\t\t\t\t\"databyname\": [],\r\n\t\t\t\t\"hash\": \"\",\r\n\t\t\t}\r\n\t\t\tlet aTemp = []\r\n\t\t\tfor (const k of Object.keys(tmpobj).sort()) {\r\n\t\t\t\toColors[type][\"data\"][k] = tmpobj[k]; count += tmpobj[k].length // sort/count\r\n\t\t\t\tif (tmpAll[k] == undefined) {tmpAll[k] = []}\r\n\t\t\t\ttmpAll[k][type] = []\r\n\t\t\t\ttmpAll[k][type] = tmpAll[k].concat(tmpobj[k])\r\n\t\t\t\ttmpobj[k].forEach(function(item) {\r\n\t\t\t\t\taTemp.push(item +\": \"+ k)\r\n\t\t\t\t}) // array by name\r\n\t\t\t}\r\n\t\t\toColors[type][\"countcolor\"] = Object.keys(tmpobj).length\r\n\t\t\toColors[type][\"countsupported\"] = count\r\n\t\t\toColors[type][\"hash\"] = mini(oColors[type][\"data\"])\r\n\t\t\taTempAll = aTempAll.concat(aTemp)\r\n\t\t\taTemp.sort()\r\n\t\t\toColors[type][\"databyname\"] = aTemp\r\n\r\n\t\t}\r\n\t\t// all\r\n\t\tcount = 0\r\n\t\taTempAll.sort()\r\n\t\toColors[\"all\"][\"databyname\"] = aTempAll\r\n\t\tfor (const k of Object.keys(tmpAll).sort()) {\r\n\t\t\toColors[\"all\"][\"data\"][k] = {}\r\n\t\t\tfor (const name of Object.keys(tmpAll[k]).sort()) {\r\n\t\t\t\tlet data = tmpAll[k][name] // don't sort: leave in original groups\r\n\t\t\t\toColors[\"all\"][\"data\"][k][name] = data\r\n\t\t\t\tcount += data.length\r\n\t\t\t}\r\n\t\t}\r\n\t\toColors.all.countcolor += Object.keys(tmpAll).length\r\n\t\toColors.all.countsupported += count\r\n\t\toColors.all.hash = mini(oColors[\"all\"][\"data\"])\r\n\t\toutput()\r\n\t} catch(e) {\r\n\t\tconsole.error(e.name, e.message)\r\n\t\tdom.summary = e.type +\": \"+ e.message\r\n\t}\r\n}\r\n\r\nfunction log_console(type, list) {\r\n\tlet data = oColors[type][list],\r\n\t\thash = mini(data),\r\n\t\tcounts = \"\"\r\n\tif (list == \"data\") {\r\n\t\tcounts = \" [\"+ oColors[type][\"countcolor\"] + \"/\" + oColors[type][\"countsupported\"] +\"]\"\r\n\t}\r\n\tconsole.log(type +\": \"+ hash + counts +\"\\n\" + JSON.stringify(data, null, 2))\r\n}\r\n\r\nfunction output() {\r\n\tlet summary1 = [], summary2 = [], summary3 = []\r\n\tfor (const k of Object.keys(oColors).sort()) {\r\n\t\tlet data = oColors[k]\r\n\t\tlet str = k\r\n\t\tlet counts = data.countcolor + \"/\" + data.countsupported\r\n\t\tcounts = counts.padStart(6)\r\n\t\tstr = \"<span class='btn14 btnc' onClick='display(`\" + k +\"`)'>\"+  str +\"</span>: \"+ counts\r\n\t\tsummary1.push(str)\r\n\t\tlet len = k.length + 8\r\n\t\tsummary2.push(s16 + (data.hash).padEnd(len) +sc)\r\n\t}\r\n\tdom.summary.innerHTML = summary1.join(\" | \") +\"<br>\"+ summary2.join(\" | \")\r\n\tdisplay(\"all\")\r\n}\r\n\r\nfunction display(type) {\r\n\tlet results = []\r\n\tlet data = oColors[type][\"data\"],\r\n\t\thash = oColors[type][\"hash\"],\r\n\t\tcounts = \" [\"+ oColors[type][\"countcolor\"] + \"/\" + oColors[type][\"countsupported\"] +\"]\"\r\n\tlet consoleBtns = \" | CONSOLE: \"\r\n\t\t+ \"<span class='btn14 btnc' onClick='log_console(`\" + type +\"`,`databyname`)'>[by name]</span> \"\r\n\t\t+ \"<span class='btn14 btnc' onClick='log_console(`\" + type +\"`,`data`)'>[by color]</span> \"\r\n\r\n\tresults.push(s14 + type + sc + \": \" + oColors[type][\"hash\"] + counts + consoleBtns +\"<br><br>{\")\r\n\tfor (const k of Object.keys(data)) {\r\n\t\tlet k1 = k.split(\" \")[0]\r\n\t\tlet k2 = k.split(\" \")[1]\r\n\r\n\t\tresults.push(\"  \\\"\"+ k1 +\"\\\": <span style='background-color:#\"+ k1 +\"; border: 1px solid white'> &nbsp </span> \"+ k2)\r\n\t\tlet resultsStr = \"\"\r\n\t\tif (type == \"all\") {\r\n\t\t\tfor (const name of Object.keys(data[k])) {\r\n\t\t\t\tresultsStr += \"<ul>\"+ s14 + name + sc +\": \" + data[k][name].join(\", \") +\"</ul>\"\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tresultsStr = \"<ul>\"+ data[k].join(\", \") +\"</ul>\"\r\n\t\t}\r\n\t\tresults.push(resultsStr)\r\n\t}\r\n\tdom.results.innerHTML = results.join(\"<br>\") +\"}\"\r\n}\r\n\r\nget_colors()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dncalendar.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=800\">\n\t<title>dn: calendar</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 680px;}\n\t\tul.main {margin-left: -20px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\n\t\t<tr>\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\n\t\t</tr>\n\t</table>\n\n\t<table id=\"tb4\">\n\t\t<col width=\"32%\"><col width=\"68%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">displaynames: calendar\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\n\t\t\t<br><br><hr><br>\n\t\t\t\t<span id =\"results\"></span>\n\t\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames#script_code_display_names\n\nlet aItems = [\n\t//https://tc39.es/ecma402/#sec-calendar-types\n\t//https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml\n\t// Ff140 counts\n\t'gregory', \t\t\t\t\t// 150\n\t'islamic-umalqura', // 111 | + 7 -> 162\n\t'ethiopic', \t\t\t\t// 125 | + 3 -> 165\n\t'chinese', \t\t\t\t\t// 124 | + 1 -> 166\n\t'islamic-rgsa', \t\t//  39 | + 1 -> 167 - F147+ 1955545 Use \"islamic-tbla\" when either \"islamic\" or \"islamic-rgsa\" was requested\n\t'roc', \t\t\t\t\t\t\t// 127 | + 1 -> 168\n\t'buddhist', //125\n\t'coptic', // 117\n\t'dangi', // 118\n\t'ethioaa', // 120\n\t'hebrew', // 127\n\t'indian', // 104\n\t'islamic', // 110 - F147+ 1955545 Use \"islamic-tbla\" when either \"islamic\" or \"islamic-rgsa\" was requested\n\t'islamic-civil', // 113\n\t'islamic-tbla', // 55\n\t'iso8601', // 122\n\t'japanese', // 125\n\t'persian', // 120\n\t// other: is an alias\n\t//'ethiopic-amete-alem', // 120 135b2a92-max = same as ethioaa's hash\n]\n// remove unsupported calendars\nlet aGood = Intl.supportedValuesOf('calendar')\naItems = aItems.filter(x => aGood.includes(x))\naItems.sort()\n\nvar list = gLocales,\n\taLegend = [],\n\taLocales = [],\n\tisSupported = false,\n\tlocalesHashAll = \"\" // to compare min to\n\nfunction log_console(name) {\n\tlet hash = mini(sDetail[name])\n\tif (name == \"locales\") {\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\n\t} else {\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\n\t}\n}\n\nfunction legend() {\n\t// build once\n\tif (aLegend.length == 0) {\n\t\tlist.sort()\n\t\tfor (let i = 0 ; i < list.length; i++) {\n\t\t\tlet str = list[i].toLowerCase()\n\t\t\tlet code = str.split(\",\")[0].trim()\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\n\t\t\tlet go = true\n\t\t\tif (isSupported) {\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\n\t\t\t}\n\t\t\tif (go) {\n\t\t\t\taLocales.push(code)\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\n\t\t\t\tif (name.includes(\"(\")) {\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\n\t\t\t\t\tlet name1 = name.substring(\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \n\t\t\t\t\t\tname.lastIndexOf(\")\")\n\t\t\t\t\t)\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\n\t\t\t\t\tif (isSplit) {\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name0 +\" \"+ name1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\n\t\t\t}\n\t\t}\n\t}\n\t// output\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\n}\n\nfunction run_main(method) {\n\tlet t0 = performance.now()\n\tlet oData = {}, oTempData = {}\n\tlet spacer = '<br><br>'\n\tlet tests = {}\n\tdom.perf = \"\"\n\tdom.results = \"\"\n\n\tif (method == \"all\") {\n\t\ttests = {\n\t\t\t'calendar': {'long': aItems, 'narrow': aItems, 'short': aItems}\n\t\t}\n\t} else {\n\t\t// just one of the styles covers this, let's go with short\n\t\ttests = {\n\t\t\t'calendar': {\n\t\t\t\t'short': [\n\t\t\t\t\t// FF147+\n\t\t\t\t\t'gregory', // 153\n\t\t\t\t\t'islamic-umalqura', // 117 | + 12 -> 165\n\t\t\t\t\t'ethiopic', // 129 | + 3 -> 168\n\t\t\t\t\t'chinese', // 129 | + 1 -> 169\n\t\t\t\t\t'roc', // 131 | + 1 -> 170\n\t\t\t\t\t'islamic-tbla', // 59 | +1 -> 171\n\t\t\t\t\t// these next two required FF147+\n\t\t\t\t\t'japanese', // 131 | +1 = 172\n\t\t\t\t\t'dangi', // 123 | + 1 = 173\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\taLocales.forEach(function(code) { // for each locale\n\t\t\tlet oType = {}\n\t\t\tObject.keys(tests).sort().forEach(function(t){ // for each type\n\t\t\t\toType[t] = {}\n\t\t\t\tfor (const s of Object.keys(tests[t])) { // for each style\n\t\t\t\t\toType[t][s] = ('all' == method) ? [] : {}\n\t\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: t, style: s})\n\t\t\t\t\ttests[t][s].sort().forEach(function(item){\n\t\t\t\t\t\titem = item.toLowerCase()\n\t\t\t\t\t\tlet value = dn.of(item)\n\t\t\t\t\t\tif ('all' == method) {\n\t\t\t\t\t\t\toType[t][s].push(item +': '+ value)\t\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toType[t][s][item] = value\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\n\t\t\tif (oTempData[hash] == undefined) {\n\t\t\t\toTempData[hash] = {}\n\t\t\t\toTempData[hash][\"locales\"] = [code]\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\toTempData[hash][\"locales\"].push(code)\n\t\t\t}\n\t\t})\n\t\t// handle empty\n\t\tif (Object.keys(oTempData).length == 0) {\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\n\t\t\treturn\n\t\t}\n\t\t// order in new object\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\n\t\t\toData[h] = {}\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\n\t\t\t\tif (key == \"locales\") {\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\n\t\t\t\t} else {\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//console.log(oTempData)\n\n\t\tlet localeGroups = [], displaylist = []\n\t\tfor (const k of Object.keys(oData)) { // for each hash\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\n\t\t\tlet str = \"\"\n\t\t\tfor (const type of Object.keys(oData[k])) { // for each type\n\t\t\t\tif (type !== \"locales\") {\n\t\t\t\t\tstr += \"<li>\"+ s14 + type + sc +\"</li>\"\n\t\t\t\t\tObject.keys(oData[k][type]).forEach(function(s){ // for each style\n\t\t\t\t\t\tif ('all' == method) {\n\t\t\t\t\t\t\t// color up the parts\n\t\t\t\t\t\t\tlet aParts = [], array = oData[k][type][s]\n\t\t\t\t\t\t\tarray.forEach(function(item){ // for each item\n\t\t\t\t\t\t\t\tlet parts = item.split(\":\")\n\t\t\t\t\t\t\t\titem = item.replace(parts[0] +':', s12 + parts[0]+ ':' + sc)\n\t\t\t\t\t\t\t\taParts.push(item)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tstr += s16 + s.slice(0,1).toUpperCase() +\": \"+ sc + aParts.join(', ') +\"</br>\"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstr += s16 + s + sc + '<ul class=\"main\">'\n\t\t\t\t\t\t\tObject.keys(oData[k][type][s]).forEach(function(item){ // for each item\n\t\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + item +': '+ sc + oData[k][type][s][item] +\"</li>\"\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tstr += '</ul>'\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ('all' == method) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\n\t\t\tdisplaylist.push(\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\n\t\t\t\t+ \"<ul class='main'>\"+ str\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\n\t\t\t)\n\t\t}\n\n\t\t// hashes + btns\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\n\t\tlet resultsHash = mini(oData)\n\t\tsDetail[\"results\"] = oData\n\n\t\tlocaleGroups.sort()\n\t\tsDetail[\"locales\"] = localeGroups\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\n\t\tlet localesHash = mini(localeGroups)\n\n\t\t/*\n\t\tconsole.log(localesHash)\n\t\tconsole.log(localeGroups)\n\t\tconsole.log(resultsHash)\n\t\tconsole.log(oData)\n\t\tconsole.log(oTempData)\n\t\t//*/\n\n\t\t// notations\n\t\tlet localesMatch = \"\"\n\t\tif (method == \"all\") {\n\t\t\tlocalesHashAll = localesHash\n\t\t\t// notate new if 140+\n\t\t\tif (isVer > 139) {\n\t\t\t\t// results\n\t\t\t\tif (resultsHash == \"f9edfcf5\") { // FF147+\n\t\t\t\t} else if (resultsHash == \"8010db57\") { // FF140-146\n\t\t\t\t} else {resultsHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t\t// locales\n\t\t\t\tif (localesHash == \"c4ca9d63\") { // FF147+: 173\n\t\t\t\t} else if (localesHash == \"8aee271e\") { // FF140-146: 168\n\t\t\t\t} else {localesHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (method == \"min\") {\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\n\t\t}\n\t\t// display\n\t\tlet display = s4 + method.toUpperCase() +\": \"\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\n\t\t// perf\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\n\t}\n}\n\nfunction run(method) {\n\tif (isSupported) {\n\t\t//reset\n\t\tsetBtn(method)\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t\t// delay so users see change and allow paint\n\t\tsetTimeout(function() {\n\t\t\trun_main(method)\n\t\t}, 1)\n\t}\n}\n\nfunction setBtn(method) {\n\t// reset btns\n\tlet items = document.getElementsByClassName(\"btn8\")\n\tfor (let i=0; i < items.length; i++) {\n\t\titems[i].classList.add(\"btn4\")\n\t\titems[i].classList.remove(\"btn8\")\n\t}\n\t// set btn\n\tlet el = document.getElementById(\"b\"+ method)\n\tel.classList.add(\"btn8\")\n\tel.classList.remove(\"btn4\")\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_isVer()\n\tbuildnav()\n\n\ttry {\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\n\t\tisSupported = true\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\n\t}\n\t// add additional locales to core locales for this test\n\tlet aListExtra = [\n'bn-in,bengali (india)',\n'bs-cyrl,bosnian (cyrillic)',\n'en-au,english (australia)',\n'en-ca,english (canada)',\n'es-us,spanish (united states)',\n'es-ar,spanish (argentina)',\n'es-mx,spanish (mexico)',\n'ff-adlm,fulah (adlam)',\n'kk-cn,kazakh (china)',\n'ks-deva,kashmiri (devanagari)',\n'kxv-telu,kuvi (telugu)',\n'pa-arab,punjabi (arabic)',\n'pt-ao,portuguese (angola)',\n'sd-deva,sindhi (devanagari)',\n'se-fi,northern sami (finland)',\n'sr-latn,serbian (latin)',\n'sw-ke,swahili (kenya)',\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\n'yo-bj,yoruba (benin)',\n'yue-cn,cantonese (china)',\n\t]\n\tlist = list.concat(aListExtra)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\t//list = ['en','de','fr','pl','pt','sv']\n\n\tlegend()\n\tif (isSupported) {\n\t\tsetBtn(\"all\")\n\t\tsetTimeout(function() {\n\t\t\trun_main(\"all\")\n\t\t}, 100)\n\t}\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/dncurrency.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=800\">\n\t<title>dn: currency</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 680px;}\n\t\tul.main {margin-left: -20px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\n\t\t<tr>\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\n\t\t</tr>\n\t</table>\n\n\t<table id=\"tb4\">\n\t\t<col width=\"32%\"><col width=\"68%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">displaynames: currency\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy.\n\t\t\tThe <code>TINY</code> test is a minimalist hardcoded test built for speed.</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\n\t\t\t\t<span id=\"btiny\" class=\"btn4 btn\" onClick=\"run('tiny')\">[ TINY ]</span>\n\t\t\t<br><br><hr><br>\n\t\t\t\t<span id =\"results\"></span>\n\t\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames#currency_code_display_names\n\nlet aAll = [ // for reference\n'ADP', 'AED', 'AFA', 'AFN', 'ALK', 'ALL', 'AMD', 'ANG', 'AOA', 'AOK',\n'AON', 'AOR', 'ARA', 'ARL', 'ARM', 'ARP', 'ARS', 'ATS', 'AUD', 'AWG',\n'AZM', 'AZN', 'BAD', 'BAM', 'BAN', 'BBD', 'BDT', 'BEC', 'BEF', 'BEL',\n'BGL', 'BGM', 'BGN', 'BGO', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BOL',\n'BOP', 'BOV', 'BRB', 'BRC', 'BRE', 'BRL', 'BRN', 'BRR', 'BRZ', 'BSD',\n'BTN', 'BUK', 'BWP', 'BYB', 'BYN', 'BYR', 'BZD', 'CAD', 'CDF', 'CHE',\n'CHF', 'CHW', 'CLE', 'CLF', 'CLP', 'CNH', 'CNX', 'CNY', 'COP', 'COU',\n'CRC', 'CSD', 'CSK', 'CUC', 'CUP', 'CVE', 'CYP', 'CZK', 'DDM', 'DEM',\n'DJF', 'DKK', 'DOP', 'DZD', 'ECS', 'ECV', 'EEK', 'EGP', 'ERN', 'ESA',\n'ESB', 'ESP', 'ETB', 'EUR', 'FIM', 'FJD', 'FKP', 'FRF', 'GBP', 'GEK',\n'GEL', 'GHC', 'GHS', 'GIP', 'GMD', 'GNF', 'GNS', 'GQE', 'GRD', 'GTQ',\n'GWE', 'GWP', 'GYD', 'HKD', 'HNL', 'HRD', 'HRK', 'HTG', 'HUF', 'IDR',\n'IEP', 'ILP', 'ILR', 'ILS', 'INR', 'IQD', 'IRR', 'ISJ', 'ISK', 'ITL',\n'JMD', 'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRH', 'KRO',\n'KRW', 'KWD', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL',\n'LTT', 'LUC', 'LUF', 'LUL', 'LVL', 'LVR', 'LYD', 'MAD', 'MAF', 'MCF',\n'MDC', 'MDL', 'MGA', 'MGF', 'MKD', 'MKN', 'MLF', 'MMK', 'MNT', 'MOP',\n'MRO', 'MRU', 'MTL', 'MTP', 'MUR', 'MVP', 'MVR', 'MWK', 'MXN', 'MXP',\n'MXV', 'MYR', 'MZE', 'MZM', 'MZN', 'NAD', 'NGN', 'NIC', 'NIO', 'NLG',\n'NOK', 'NPR', 'NZD', 'OMR', 'PAB', 'PEI', 'PEN', 'PES', 'PGK', 'PHP',\n'PKR', 'PLN', 'PLZ', 'PTE', 'PYG', 'QAR', 'RHD', 'ROL', 'RON', 'RSD',\n'RUB', 'RUR', 'RWF', 'SAR', 'SBD', 'SCR', 'SDD', 'SDG', 'SDP', 'SEK',\n'SGD', 'SHP', 'SIT', 'SKK', 'SLE', 'SLL', 'SOS', 'SRD', 'SRG', 'SSP',\n'STD', 'STN', 'SUR', 'SVC', 'SYP', 'SZL', 'THB', 'TJR', 'TJS', 'TMM',\n'TMT', 'TND', 'TOP', 'TPE', 'TRL', 'TRY', 'TTD', 'TWD', 'TZS', 'UAH',\n'UAK', 'UGS', 'UGX', 'USD', 'USN', 'USS', 'UYI', 'UYP', 'UYU', 'UYW',\n'UZS', 'VEB', 'VED', 'VEF', 'VES', 'VND', 'VNN', 'VUV', 'WST', 'XAF',\n'XAG', 'XAU', 'XBA', 'XBB', 'XBC', 'XBD', 'XCD', 'XCG', 'XDR', 'XEU',\n'XFO', 'XFU', 'XOF', 'XPD', 'XPF', 'XPT', 'XRE', 'XSU', 'XTS', 'XUA',\n'XXX', 'YDD', 'YER', 'YUD', 'YUM', 'YUN', 'YUR', 'ZAL', 'ZAR', 'ZMK',\n'ZMW', 'ZRN', 'ZRZ', 'ZWD', 'ZWG', 'ZWL', 'ZWR',\n]\n\n// currencies: 307 supported in gecko | 159 in chrome\n\t// so we should filter these to those supported per engine\nlet aItems = []\ntry {aAll = Intl.supportedValuesOf('currency')} catch(e) {}\naAll.sort()\naItems = aAll\n\nlet aTestItems = [\n\t// these add something in gecko but we can reduce the styles they test\n\t'AFN', 'ANG', 'AOA', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BIF', 'BMD', \n\t'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CDF', 'CLP', 'CNY', \n\t'COP', 'CRC', 'CUP', 'DJF', 'DKK', 'DOP', 'DZD', 'ERN', 'ETB', 'EUR', \n\t'FJD', 'FKP', 'FRF', 'GBP', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', \n\t'HNL', 'HTG', 'IDR', 'IQD', 'JMD', 'JPY', 'KES', 'KGS', 'KMF', 'KYD', \n\t'KZT', 'LKR', 'LRD', 'LSL', 'LUF', 'MDL', 'MGA', 'MKD', 'MOP', 'MRU', \n\t'MUR', 'MWK', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NZD', 'PAB', 'PEN', \n\t'PGK', 'PHP', 'PKR', 'PLN', 'PTE', 'PYG', 'RUB', 'RUR', 'RWF', 'SBD', \n\t'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLE', 'SOS', 'SRD', 'SSP', 'STN', \n\t'SYP', 'SZL', 'TND', 'TOP', 'TTD', 'TZS', 'UGX', 'USD', 'UYW', 'VES', \n\t'VUV', 'WST', 'XAF', 'XCD', 'ZAR', 'ZMW', \n\n/* these add nothing in gecko or blink\n\t'ADP', 'AED', 'AFA', 'ALK', 'ALL', 'AMD', 'AOK', 'AON', 'AOR', 'ARA', \n\t'ARL', 'ARM', 'ARP', 'ARS', 'ATS', 'AZM', 'BAD', 'BAN', 'BDT', 'BEC', \n\t'BEF', 'BEL', 'BGL', 'BGM', 'BGN', 'BGO', 'BHD', 'BOL', 'BOP', 'BOV', \n\t'BRB', 'BRC', 'BRE', 'BRN', 'BRR', 'BRZ', 'BTN', 'BUK', 'BYB', 'BYN', \n\t'BYR', 'CHE', 'CHF', 'CHW', 'CLE', 'CLF', 'CNH', 'CNX', 'COU', 'CSD', \n\t'CSK', 'CUC', 'CVE', 'CYP', 'CZK', 'DDM', 'DEM', 'ECS', 'ECV', 'EEK', \n\t'EGP', 'ESA', 'ESB', 'ESP', 'FIM', 'GEK', 'GEL', 'GHC', 'GNS', 'GQE', \n\t'GRD', 'GWE', 'GWP', 'HKD', 'HRD', 'HRK', 'HUF', 'IEP', 'ILP', 'ILR', \n\t'ILS', 'INR', 'IRR', 'ISJ', 'ISK', 'ITL', 'JOD', 'KHR', 'KPW', 'KRH', \n\t'KRO', 'KRW', 'KWD', 'LAK', 'LBP', 'LTL', 'LTT', 'LUC', 'LUL', 'LVL', \n\t'LVR', 'LYD', 'MAD', 'MAF', 'MCF', 'MDC', 'MGF', 'MKN', 'MLF', 'MMK', \n\t'MNT', 'MRO', 'MTL', 'MTP', 'MVP', 'MVR', 'MXN', 'MXP', 'MXV', 'MZE', \n\t'MZM', 'NIC', 'NLG', 'NOK', 'NPR', 'OMR', 'PEI', 'PES', 'PLZ', 'QAR', \n\t'RHD', 'ROL', 'RON', 'RSD', 'SAR', 'SDD', 'SDP', 'SIT', 'SKK', 'SLL', \n\t'SRG', 'STD', 'SUR', 'SVC', 'THB', 'TJR', 'TJS', 'TMM', 'TMT', 'TPE', \n\t'TRL', 'TRY', 'TWD', 'UAH', 'UAK', 'UGS', 'USN', 'USS', 'UYI', 'UYP', \n\t'UYU', 'UZS', 'VEB', 'VED', 'VEF', 'VND', 'VNN', 'XAG', 'XAU', 'XBA', \n\t'XBB', 'XBC', 'XBD', 'XCG', 'XDR', 'XEU', 'XFO', 'XFU', 'XOF', 'XPD', \n\t'XPF', 'XPT', 'XRE', 'XSU', 'XTS', 'XUA', 'XXX', 'YDD', 'YER', 'YUD', \n\t'YUM', 'YUN', 'YUR', 'ZAL', 'ZMK', 'ZRN', 'ZRZ', 'ZWD', 'ZWG', 'ZWL', \n\t'ZWR',\n//*/\n]\n//aItems = aTestItems\n// filter aItems\naItems = aItems.filter(x => aAll.includes(x))\n\nvar list = gLocales,\n\taLegend = [],\n\taLocales = [],\n\toTestData = {},\n\tisSupported = false,\n\tlocalesHashAll = \"\" // to compare min to\n\nfunction compute_data() {\n\taRes = []\n\tfor (const key of Object.keys(oTestData)) {\n\t\tif (key !== '1') {\n\t\t\taRes.push([\"'\" + oTestData[key].join(\"','\") +\"', // \"+ key])\n\t\t}\n\t}\n\tconsole.log(aRes.join('\\n'))\n}\n\nfunction testitems() {\n\t//loop each script on it's own\n\tdom.perf = \"\"\n\tdom.results = \"\"\n\toTestData = {}\n\ttry {\n\t\taItems.forEach(function(item){\n\t\t\t/* take one of the highest oTestData results and keep adding until you reach max\n\t\t\titem = [\n\t\t\t\t'VI','US','ZZ','CM','VC','TL','FR','QO','GB','ZA','KP','PL','KH','ZM',\n\t\t\t\titem\n\t\t\t]\n\t\t\t//*/\n\t\t\t// allow testing arrays of scripts\n\t\t\tlet testarray = 'object' == typeof item ? item : [item]\n\t\t\trun_main('all', testarray, true)\n\t\t})\n\t} catch(e) {\n\t\tconsole.log(e)\n\t}\n}\n\nfunction log_console(name) {\n\tlet hash = mini(sDetail[name])\n\tif (name == \"currencies\") {\n\t\tconsole.log(name +\": \" + sDetail[name].length +\"\\n\"+ sDetail[name].join(\", \"))\n\t} else if (name == \"allcurrencies\") {\n\t\tconsole.log(\"all supported currencies\" +\": \" + aAll.length +\"\\n\"+ aAll.join(\", \"))\n\t} else if (name == \"locales\") {\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\n\t} else {\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\n\t}\n}\n\nfunction legend() {\n\t// build once\n\tif (aLegend.length == 0) {\n\t\tlist.sort()\n\t\tfor (let i = 0 ; i < list.length; i++) {\n\t\t\tlet str = list[i].toLowerCase()\n\t\t\tlet code = str.split(\",\")[0].trim()\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\n\t\t\tlet go = true\n\t\t\tif (isSupported) {\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\n\t\t\t}\n\t\t\tif (go) {\n\t\t\t\taLocales.push(code)\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\n\t\t\t\tif (name.includes(\"(\")) {\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\n\t\t\t\t\tlet name1 = name.substring(\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \n\t\t\t\t\t\tname.lastIndexOf(\")\")\n\t\t\t\t\t)\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\n\t\t\t\t\tif (isSplit) {\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name0 +\" \"+ name1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\n\t\t\t}\n\t\t}\n\t}\n\t// output\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\n}\n\nlet oData = {}\nfunction run_main(method, aTest, isLoop = false) {\n\tlet t0 = performance.now()\n\toData = {}\n\tsDetail['currencies'] = []\n\n\tlet oTempData = {} \n\tlet spacer = '<br><br>'\n\tlet tests = {}\n\tlet intTests = 0\n\tlet aUsed = undefined == aTest ? aItems: aTest\n\tif (!isLoop) {\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t}\n\t//aUsed = ['NZD','USD']\n\n\tif (method == \"all\") {\n\t\taUsed.sort()\n\t\tsDetail['currencies'] = aUsed\n\t\t// it is a lot faster to loop by style then currency\n\t\ttests = {'long': aUsed, 'narrow': aUsed, 'short': aUsed}\n\t\tintTests = aUsed.length * 3\n\t\t// long     244\n\t\t// narrow   168\n\t\t// short    296\n\t\t// combined 398\n\n\t} else if (method == \"min\") {\n\t\tlet minBase = [\n\t\t'AFN', 'ANG', 'AOA', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BIF', 'BMD', \n\t\t'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CDF', 'CLP', 'CNY', \n\t\t'COP', 'CRC', 'CUP', 'DJF', 'DKK', 'DOP', 'DZD', 'ERN', 'ETB', 'EUR', \n\t\t'FJD', 'FKP', 'FRF', 'GBP', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', \n\t\t'HNL', 'HTG', 'IDR', 'IQD', 'JMD', 'JPY', 'KES', 'KGS', 'KMF', 'KYD', \n\t\t'KZT', 'LKR', 'LRD', 'LSL', 'LUF', 'MDL', 'MGA', 'MKD', 'MOP', 'MRU', \n\t\t'MUR', 'MWK', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NZD', 'PAB', 'PEN', \n\t\t'PGK', 'PHP', 'PKR', 'PLN', 'PTE', 'PYG', 'RUB', 'RUR', 'RWF', 'SBD', \n\t\t'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLE', 'SOS', 'SRD', 'SSP', 'STN', \n\t\t'SYP', 'SZL', 'TND', 'TOP', 'TTD', 'TZS', 'UGX', 'USD', 'UYW', 'VES', \n\t\t'VUV', 'WST', 'XAF', 'XCD', 'ZAR', 'ZMW']\n\n\t\ttests = {\n\t\t\t'long': ['AUD','CAD','GBP','JPY','MZN','NIO','SEK','SZL','TZS','USD','XAF'],\n\t\t\t'narrow': ['BND','EUR'],\n\t\t\t'short': [\n\t\t\t\t// long/narrow items: tested by removing them: all needed\n\t\t\t\t'AUD', 'BND', 'CAD', 'EUR', 'GBP', 'JPY', 'MZN', 'NIO', 'SEK', 'SZL', 'TZS', 'USD', 'XAF',\n\t\t\t\t// the rest: required from earlier testing\n\t\t\t\t'AFN', 'ANG', 'AOA', 'AWG', 'AZN', 'BAM', 'BBD', 'BIF', 'BMD', \n\t\t\t\t'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CDF', 'CLP', 'CNY', \n\t\t\t\t'COP', 'CRC', 'CUP', 'DJF', 'DKK', 'DOP', 'DZD', 'ERN', 'ETB',  \n\t\t\t\t'FJD', 'FKP', 'FRF', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', \n\t\t\t\t'HNL', 'HTG', 'IDR', 'IQD', 'JMD', 'KES', 'KGS', 'KMF', 'KYD', \n\t\t\t\t'KZT', 'LKR', 'LRD', 'LSL', 'LUF', 'MDL', 'MGA', 'MKD', 'MOP', 'MRU', \n\t\t\t\t'MUR', 'MWK', 'MYR', 'NAD', 'NGN', 'NZD', 'PAB', 'PEN', \n\t\t\t\t'PGK', 'PHP', 'PKR', 'PLN', 'PTE', 'PYG', 'RUB', 'RUR', 'RWF', 'SBD', \n\t\t\t\t'SCR', 'SDG', 'SGD', 'SHP', 'SLE', 'SOS', 'SRD', 'SSP', 'STN', \n\t\t\t\t'SYP', 'TND', 'TOP', 'TTD', 'UGX', 'UYW', 'VES', \n\t\t\t\t'VUV', 'WST', 'XCD', 'ZAR', 'ZMW',\n\t\t\t]\n\t\t}\n\t\ttests.short = tests.short.sort()\n\t} else {\n\t\t// TINY\n\t\t\t// we already use USD, GBP, ETB and KES in numberformat: currencydisplay\n\t\t\t// so ignore those\n\n\t\ttests = {\n\t\t\t// anything outside of the core longs is miniscule +1s or +2s\n\t\t\t'long': [\n'JPY', // 177\n'XAF', // 197 +20\n'NIO', // 204 +7\n'SZL', // 207 +3\n'TZS', // 210 +3\n'SEK', // 212 +2\n//'AUD','CAD','MZN','GBP','USD',\n\t\t\t],\n\t\t}\n\t}\n\tif ('all' !== method) {\n\t\tlet tmpArray = []\n\t\tfor (const s of Object.keys(tests)) {\n\t\t\ttmpArray = tmpArray.concat(tests[s])\n\t\t\t// remove unsupported\n\t\t\ttmpArray = tmpArray.filter(x => aAll.includes(x))\n\t\t\ttests[s] = tmpArray\n\t\t\tintTests += tests[s].length\n\t\t}\n\t\ttmpArray = tmpArray.filter(function(item, position) {return tmpArray.indexOf(item) === position})\n\t\tsDetail['currencies'] = tmpArray.sort()\n\t}\n\t//console.log(tests)\n\n\ttry {\n\t\taLocales.forEach(function(code) { // for each locale\n\t\t\tlet oType = {}\n\t\t\tfor (const s of Object.keys(tests).sort()) { // for each style\n\t\t\t\toType[s] = {}\n\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: 'currency', style: s})\n\t\t\t\ttests[s].sort().forEach(function(item){\n\t\t\t\t\tlet value = dn.of(item)\n\t\t\t\t\toType[s][item] = value\n\t\t\t\t})\n\t\t\t}\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\n\t\t\tif (oTempData[hash] == undefined) {\n\t\t\t\toTempData[hash] = {}\n\t\t\t\toTempData[hash][\"locales\"] = [code]\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\toTempData[hash][\"locales\"].push(code)\n\t\t\t}\n\t\t})\n\t\t// handle empty\n\t\tif (Object.keys(oTempData).length == 0) {\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\n\t\t\treturn\n\t\t}\n\t\t// order in new object\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\n\t\t\toData[h] = {}\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\n\t\t\t\tif (key == \"locales\") {\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\n\t\t\t\t} else {\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//console.log(oTempData)\n\n\t\tlet localeGroups = [], displaylist = []\n\t\tfor (const k of Object.keys(oData)) { // for each hash\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\n\t\t\tif (!isLoop) {\n\t\t\t\tlet str = \"\"\n\t\t\t\tfor (const c of Object.keys(oData[k])) { // for each currency or style\n\t\t\t\t\tif (c !== \"locales\") {\n\t\t\t\t\t\tstr += '<li>'+ s16 + c.slice(0,1).toUpperCase() +': '+ sc\n\t\t\t\t\t\tObject.keys(oData[k][c]).forEach(function(s){ // for each style or currency\n\t\t\t\t\t\t\tstr += ' '+ s12 + s +\": \"+ sc + oData[k][c][s]\n\t\t\t\t\t\t})\n\t\t\t\t\t\tstr += \"</li>\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ('tiny' !== method) {str = \"<details><summary>data</summary>\"+ str +\"</details>\"}\n\t\t\t\tdisplaylist.push(\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif (isLoop) {\n\t\t\tlet testCount = localeGroups.length\n\t\t\t// record it\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\n\t\t\toTestData[testCount].push(aUsed)\n\t\t\t//console.log(testCount, aUsed)\n\t\t\t/*\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\n\t\t\t\t+ s16 + testCount + sc +': '+ '[\\''+ aUsed.join('\\', \\'') +'\\']'\n\t\t\t//*/\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\n\t\t\t\t+ '\\''+ aUsed.join('\\', \\'') +'\\', // ' + testCount\n\t\t\treturn\n\t\t}\n\n\t\t// hashes + btns\n\t\tlet curBtn = \"<span class='btn4 btnc' onClick='log_console(`allcurrencies`)'>[\" + aAll.length + \"]</span>\" // all supported\n\t\tif (sDetail['currencies'].length !== aAll.length) {\n\t\t\tcurBtn = \"<span class='btn4 btnc' onClick='log_console(`currencies`)'>[\" + sDetail.currencies.length + \"]</span> from \"+ curBtn\n\t\t}\n\t\tcurBtn += ' ['+ intTests +' tests]'\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\n\t\tlet resultsHash = mini(oData)\n\t\tsDetail[\"results\"] = oData\n\n\t\tlocaleGroups.sort()\n\t\tsDetail[\"locales\"] = localeGroups\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\n\t\tlet localesHash = mini(localeGroups)\n\n\t\t/*\n\t\tconsole.log(localesHash)\n\t\tconsole.log(localeGroups)\n\t\tconsole.log(resultsHash)\n\t\tconsole.log(oData)\n\t\tconsole.log(oTempData)\n\t\t//*/\n\n\t\t// notations\n\t\tlet localesMatch = \"\"\n\t\tif (method == \"all\") {\n\t\t\tlocalesHashAll = localesHash\n\t\t\t// notate new if 140+\n\t\t\tif (isVer > 139) {\n\t\t\t\t// results\n\t\t\t\tif (resultsHash == \"c2325de7\") { // FF147+\n\t\t\t\t} else if (resultsHash == \"3ada2b0a\") { // FF140-146\n\t\t\t\t} else {resultsHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t\t// locales\n\t\t\t\tif (localesHash == \"295f8248\") { // FF147+ 402\n\t\t\t\t} else if (localesHash == \"e5f55367\") { // FF140-146 398\n\t\t\t\t} else {localesHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (method == \"min\") {\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\n\t\t}\n\t\t// display\n\t\tlet display = s4 + method.toUpperCase() +\": \"\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc + spacer\n\t\t\t+ s12 +\"currencies: \"+ sc + curBtn + spacer\n\t\t\t+ s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\n\t\t// perf\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\n\t}\n}\n\nfunction run(method) {\n\tif (isSupported) {\n\t\t//reset\n\t\tsetBtn(method)\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t\t// delay so users see change and allow paint\n\t\tsetTimeout(function() {\n\t\t\trun_main(method)\n\t\t}, 1)\n\t}\n}\n\nfunction setBtn(method) {\n\t// reset btns\n\tlet items = document.getElementsByClassName(\"btn8\")\n\tfor (let i=0; i < items.length; i++) {\n\t\titems[i].classList.add(\"btn4\")\n\t\titems[i].classList.remove(\"btn8\")\n\t}\n\t// set btn\n\tlet el = document.getElementById(\"b\"+ method)\n\tel.classList.add(\"btn8\")\n\tel.classList.remove(\"btn4\")\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_isVer()\n\tbuildnav()\n\n\ttry {\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\n\t\tisSupported = true\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\n\t}\n\t// add additional locales to core locales for this test\n\tlet aListExtra = [\n\"af-na,afrikaans (namibia)\",\n\"ar-ae,arabic (united arabic emirates)\",\n\"ar-dj,arabic (djibouti)\",\n\"ar-er,arabic (eritrea)\",\n\"ar-km,arabic (cosmoros)\",\n\"ar-lb,arabic (lebanon)\",\n\"ar-so,arabic (somalia)\",\n\"ar-ss,arabic (south sudan)\",\n\"az-cyrl,azerbaijani (cyrillic)\",\n\"bn-in,bengali (india)\",\n\"bo-in,tibetan (india)\",\n\"bs-cyrl,bosnian (cyrillic)\",\n\"ca-fr,catalan (france)\",\n\"de-ch,german (switzerland)\",\n\"de-li,german (liechtenstein)\",\n\"de-lu,german (luxembourg)\",\n\"en-ag,english (antigua & barbuda)\",\n\"en-au,english (australia)\",\n\"en-bb,english (barbados)\",\n\"en-bi,english (burundi)\",\n\"en-bm,english (bermuda)\",\n\"en-bs,english (bahamas)\",\n\"en-bw,english (botswana)\",\n\"en-bz,english (belize)\",\n\"en-ca,english (canada)\",\n\"en-cc,english (cocos islands)\",\n\"en-dk,english (denmark)\",\n\"en-er,english (eritrea)\",\n\"en-fj,english (fiji)\",\n\"en-fk,english (falkland islands)\",\n\"en-gb,english (united kingdom)\",\n\"en-gg,english (guernsey)\",\n\"en-gh,english (ghana)\",\n\"en-gi,english (gibraltar)\",\n\"en-gm,english (gambia)\",\n\"en-gy,english (guyana)\",\n\"en-in,english (india)\",\n\"en-jm,english (jamaica)\",\n\"en-ke,english (kenya)\",\n\"en-ky,english (cayman islands)\",\n\"en-lr,english (liberia)\",\n\"en-ls,english (lesotho)\",\n\"en-mg,english (madagascar)\",\n\"en-mo,english (macau)\",\n\"en-mt,english (malta)\",\n\"en-mu,english (mauritius)\",\n\"en-mw,english (malawai)\",\n\"en-my,english (malaysia)\",\n\"en-na,english (namibia)\",\n\"en-ng,english (nigeria)\",\n\"en-nz,english (new zealand)\",\n\"en-pg,english (papua new guinea)\",\n\"en-pk,english (pakistan)\",\n\"en-rw,english (rwanda)\",\n\"en-sb,english (solomon islands)\",\n\"en-sc,english (seychelles)\",\n\"en-se,english (sweden)\",\n\"en-sg,english (singapore)\",\n\"en-sh,english (saint helena)\",\n\"en-sl,english (sierra leone)\",\n\"en-ss,english (south sudan)\",\n\"en-sx,english (sint maarten)\",\n\"en-sz,english (swaziland)\",\n\"en-to,english (tonga)\",\n\"en-tt,english (trinidad & tobago)\",\n\"en-tz,english (tanzania)\",\n\"en-ug,english (uganda)\",\n\"en-vu,english (vanuatu)\",\n\"en-ws,english (samoa)\",\n\"en-zm,english (zambia)\",\n\"es-419,spanish (latin america and the caribbean)\",\n\"es-ar,spanish (argentina)\",\n\"es-bo,spanish (bolivia)\",\n\"es-br,spanish (brazil)\",\n\"es-bz,spanish (belize)\",\n\"es-cl,spanish (chile)\",\n\"es-co,spanish (colombia)\",\n\"es-cr,spanish (costa rica)\",\n\"es-cu,spanish (cuba)\",\n\"es-do,spanish (dominican republic)\",\n\"es-ec,spanish (ecuador)\",\n\"es-gq,spanish (equatorial guinea)\",\n\"es-gt,spanish (guatemala)\",\n\"es-hn,spanish (honduras)\",\n\"es-mx,spanish (mexico)\",\n\"es-ni,spanish (nicaragua)\",\n\"es-pa,spanish (panama)\",\n\"es-pe,spanish (peru)\",\n\"es-ph,spanish (philippines)\",\n\"es-py,spanish (paraguay)\",\n\"es-us,spanish (united states)\",\n\"es-uy,spanish (uruguay)\",\n\"es-ve,spanish (venezuela)\",\n\"fa-af,persian (afghanistan)\",\n\"ff-adlm,fulah (adlam)\",\n\"ff-adlm-bf,fulah (adlam burkina faso)\",\n\"ff-adlm-gh,fulah (adlam ghana)\",\n\"ff-adlm-gm,fulah (adlam gambia)\",\n\"ff-adlm-lr,fulah (adlamd liberia)\",\n\"ff-adlm-mr,fulah (adlam mauritania)\",\n\"ff-adlm-ng,fulah (adlam nigeria)\",\n\"ff-adlm-sl,fulah (adlam sierra leone)\",\n\"ff-gn,fulah (guinea)\",\n\"ff-mr,fulah (mauritania)\",\n\"fo-dk,faroese (denmark)\",\n\"fr-bi,french (burundi)\",\n\"fr-ca,french (canada)\",\n\"fr-cd,french (congo kinshasa)\",\n\"fr-dj,french (djibouti)\",\n\"fr-dz,french (algeria)\",\n\"fr-gn,french (guinea)\",\n\"fr-ht,french (haiti)\",\n\"fr-km,french (comoros)\",\n\"fr-lu,french (luxembourg)\",\n\"fr-mg,french (madagascar)\",\n\"fr-mr,french (mauritania)\",\n\"fr-mu,french (mauritius)\",\n\"fr-rw,french (rwanda)\",\n\"fr-sc,french (seychelles)\",\n\"fr-sy,french (syria)\",\n\"fr-tn,french (tunisia)\",\n\"fr-vu,french (vanuatu)\",\n\"ha-gh,hausa (ghana)\",\n\"hi-latn,hindi (latin)\",\n\"hr-ba,croatian (bosnia & herzegovina)\",\n'kk-cn,kazakh (china)',\n\"ks-deva,kashmiri (devanagari)\",\n\"kxv-telu,kuvi (telugu)\",\n\"ln-ao,lingala (angola)\",\n\"mas-tz,masia (tanzania)\",\n\"ms-bn,malay (brunei)\",\n\"ms-id,malay (indonesia)\",\n\"ms-sg,malay (singapore)\",\n\"nl-aw,dutch (aruba)\",\n\"nl-bq,dutch (caribbean netherlands)\",\n\"nl-cw,dutch (curaçao)\",\n\"nl-sr,dutch (suriname)\",\n\"om-ke,oromo (kenya)\",\n\"os-ru,ossetian (russia)\",\n\"pa-pk,punjabi (pakistan)\",\n\"ps-pk,pashto (pakistan)\",\n\"pt-ao,portuguese (angola)\",\n\"pt-cv,portuguese (cape verde)\",\n\"pt-lu,portuguese (luxembourg)\",\n\"pt-mo,portuguese (macau)\",\n\"pt-mz,portuguese (mazambique)\",\n\"pt-pt,portuguese (portugal)\",\n\"pt-st,portuguese (são tomé & príncipe)\",\n\"qu-bo,quechua (bolivia)\",\n\"qu-ec,quechua (ecuador)\",\n\"ro-md,romanian (moldova)\",\n\"ru-by,russian (belarus)\",\n\"ru-kg,russian (kyrgyzstan)\",\n\"ru-kz,russian (kazakhstan)\",\n\"ru-md,russian (moldova)\",\n\"sd-deva,sindhi (devanagari)\",\n\"se-se,northern sami\",\n\"shi-latn,tachelhit (latin)\",\n\"so-dj,somali (djibouti)\",\n\"so-et,somali (ethiopia)\",\n\"so-ke,somali (kenya)\",\n\"sq-mk,albanian (macedonia)\",\n\"sr-cyrl-ba,serbian (cyrillic bosnia & herzegovina)\",\n\"sr-latn,serbian (latin)\",\n\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\n'st-ls,southern sotho',\n\"sw-cd,swahili (congo kinshasa)\",\n\"sw-ke,swahili (kenya)\",\n\"sw-ug,swahili (uganda)\",\n\"ta-lk,tamil (sri lanka)\",\n\"ta-my,tamil (malaysia)\",\n\"ta-sg,tamil (singapore)\",\n\"teo-ke,teso (kenya)\",\n\"ti-er,tigrinya (eritrea)\",\n'tn-bw,tswana (botswana)',\n\"ur-in,urdu (india)\",\n\"uz-af,uzbek (afghanistan)\",\n\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\n\"vai-latn,vai (latin)\",\n\"yo-bj,yoruba (benin)\",\n\"yue-hans,cantonese (simplified)\",\n\"zh-hans-hk,chinese (simplified hong kong)\",\n\"zh-hans-mo,chinese (simplified macau)\",\n\"zh-hant-mo,chinese (traditional macau)\",\n//*/\n\t]\n\tlist = list.concat(aListExtra)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\t//list = ['en','de','sv']\n\n\tlegend()\n\tif (isSupported) {\n\t\tsetBtn(\"all\")\n\t\tsetTimeout(function() {\n\t\t\trun_main(\"all\")\n\t\t}, 100)\n\t}\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/dndatetime.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=800\">\n\t<title>dn: datetimefield</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 680px;}\n\t\tul.main {margin-left: -20px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\n\t\t<tr>\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\n\t\t</tr>\n\t</table>\n\n\t<table id=\"tb4\">\n\t\t<col width=\"32%\"><col width=\"68%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">displaynames: datetimefield\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\n\t\t\t<br><br><hr><br>\n\t\t\t\t<span id =\"results\"></span>\n\t\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nvar list = gLocales,\n\taLegend = [],\n\taLocales = [],\n\tisSupported = false,\n\tlocalesHashAll = \"\" // to compare min to\n\nfunction log_console(name) {\n\tlet hash = mini(sDetail[name])\n\tif (name == \"locales\") {\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\n\t} else {\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\n\t}\n}\n\nfunction legend() {\n\t// build once\n\tif (aLegend.length == 0) {\n\t\tlist.sort()\n\t\tfor (let i = 0 ; i < list.length; i++) {\n\t\t\tlet str = list[i].toLowerCase()\n\t\t\tlet code = str.split(\",\")[0].trim()\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\n\t\t\tlet go = true\n\t\t\tif (isSupported) {\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\n\t\t\t}\n\t\t\tif (go) {\n\t\t\t\taLocales.push(code)\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\n\t\t\t\tif (name.includes(\"(\")) {\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\n\t\t\t\t\tlet name1 = name.substring(\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \n\t\t\t\t\t\tname.lastIndexOf(\")\")\n\t\t\t\t\t)\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\n\t\t\t\t\tif (isSplit) {\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name0 +\" \"+ name1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\n\t\t\t}\n\t\t}\n\t}\n\t// output\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\n}\n\nfunction run_main(method) {\n\tlet t0 = performance.now()\n\tlet oData = {}, oTempData = {}\n\tlet spacer = \"<br><br>\"\n\n\tlet tests = {}\n\t// https://tc39.es/ecma402/#table-validcodefordatetimefield\n\tlet aItems = ['day','dayPeriod','era','hour','minute','month','quarter','second','timeZoneName','weekOfYear','weekday','year']\n\tif (method == \"all\") {\n\t\ttests = {\n\t\t\t'dateTimeField': {\n\t\t\t\t'long': aItems,\n\t\t\t\t'narrow': aItems, // 240\n\t\t\t\t'short': aItems,\n\t\t\t},\n\t\t}\n\t} else {\n\t\t// long doesn't add anything | hour, minute, month, quarter, and year also don't seem to add anything\n\t\t// narrow on it's own is only 1 unique item off\n\t\ttests = {\n\t\t\t'dateTimeField': {\n\t\t\t\t'narrow': ['day','dayPeriod','weekOfYear','weekday'],\n\t\t\t\t'short': ['era','month','second','timeZoneName'],\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\taLocales.forEach(function(code) { // for each locale\n\t\t\tlet oType = {}\n\t\t\tObject.keys(tests).sort().forEach(function(t){ // for each type\n\t\t\t\toType[t] = {}\n\t\t\t\tfor (const s of Object.keys(tests[t])) { // for each style\n\t\t\t\t\toType[t][s] = ('all' == method) ? [] : {}\n\t\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: t, style: s})\n\t\t\t\t\ttests[t][s].forEach(function(item){\n\t\t\t\t\t\tlet value = dn.of(item)\n\t\t\t\t\t\tif ('all' == method) {\n\t\t\t\t\t\t\toType[t][s].push(item +': '+ value)\t\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toType[t][s][item] = value\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\n\t\t\tif (oTempData[hash] == undefined) {\n\t\t\t\toTempData[hash] = {}\n\t\t\t\toTempData[hash][\"locales\"] = [code]\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\toTempData[hash][\"locales\"].push(code)\n\t\t\t}\n\t\t})\n\t\t// handle empty\n\t\tif (Object.keys(oTempData).length == 0) {\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\n\t\t\treturn\n\t\t}\n\t\t// order in new object\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\n\t\t\toData[h] = {}\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\n\t\t\t\tif (key == \"locales\") {\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\n\t\t\t\t} else {\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//console.log(oTempData)\n\n\t\tlet localeGroups = [], displaylist = []\n\t\tfor (const k of Object.keys(oData)) { // for each hash\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\n\n\t\t\tlet str = \"\"\n\t\t\tfor (const type of Object.keys(oData[k])) { // for each type\n\t\t\t\tif (type !== \"locales\") {\n\t\t\t\t\tstr += \"<li>\"+ s14 + type + sc +\"</li>\"\n\t\t\t\t\tObject.keys(oData[k][type]).forEach(function(s){ // for each style\n\t\t\t\t\t\tif ('all' == method) {\n\t\t\t\t\t\t\t// color up the parts\n\t\t\t\t\t\t\tlet aParts = [], array = oData[k][type][s]\n\t\t\t\t\t\t\tarray.forEach(function(item){ // for each item\n\t\t\t\t\t\t\t\tlet parts = item.split(\":\")\n\t\t\t\t\t\t\t\titem = item.replace(parts[0] +':', s12 + parts[0]+ ':' + sc)\n\t\t\t\t\t\t\t\taParts.push(item)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tstr += s16 + s.slice(0,1).toUpperCase() +\": \"+ sc + aParts.join(', ') +\"</br>\"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstr += s16 + s + sc + '<ul class=\"main\">'\n\t\t\t\t\t\t\tObject.keys(oData[k][type][s]).forEach(function(item){ // for each item\n\t\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + item +': '+ sc + oData[k][type][s][item] +\"</li>\"\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tstr += '</ul>'\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tdisplaylist.push(\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\n\t\t\t\t+ \"<ul class='main'>\"+ str\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\n\t\t\t)\n\t\t}\n\n\t\t// hashes + btns\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\n\t\tlet resultsHash = mini(oData)\n\t\tsDetail[\"results\"] = oData\n\n\t\tlocaleGroups.sort()\n\t\tsDetail[\"locales\"] = localeGroups\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\n\t\tlet localesHash = mini(localeGroups)\n\n\t\t/*\n\t\tconsole.log(localesHash)\n\t\tconsole.log(localeGroups)\n\t\tconsole.log(resultsHash)\n\t\tconsole.log(oData)\n\t\tconsole.log(oTempData)\n\t\t//*/\n\n\t\t// notations\n\t\tlet localesMatch = \"\"\n\t\tif (method == \"all\") {\n\t\t\tlocalesHashAll = localesHash\n\t\t\t// notate new if 140+\n\t\t\tif (isVer > 139) {\n\t\t\t\t// results\n\t\t\t\tif (resultsHash == \"c45bf84d\") { // FF147+\n\t\t\t\t} else if (resultsHash == \"fc64a9af\") { // FF140-146\n\t\t\t\t} else {resultsHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t\t// locales\n\t\t\t\tif (localesHash == \"f8cf35ab\") { // FF147+ 245\n\t\t\t\t} else if (localesHash == \"4cd7e49a\") { // FF140-146 241\n\t\t\t\t} else {localesHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (method == \"min\") {\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\n\t\t}\n\t\t// display\n\t\tlet display = s4 + method.toUpperCase() +\": \"\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\n\t\t// perf\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\n\t}\n}\n\nfunction run(method) {\n\tif (isSupported) {\n\t\t//reset\n\t\tsetBtn(method)\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t\t// delay so users see change and allow paint\n\t\tsetTimeout(function() {\n\t\t\trun_main(method)\n\t\t}, 1)\n\t}\n}\n\nfunction setBtn(method) {\n\t// reset btns\n\tlet items = document.getElementsByClassName(\"btn8\")\n\tfor (let i=0; i < items.length; i++) {\n\t\titems[i].classList.add(\"btn4\")\n\t\titems[i].classList.remove(\"btn8\")\n\t}\n\t// set btn\n\tlet el = document.getElementById(\"b\"+ method)\n\tel.classList.add(\"btn8\")\n\tel.classList.remove(\"btn4\")\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_isVer()\n\tbuildnav()\n\n\ttry {\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\n\t\tisSupported = true\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\n\t}\n\t// add additional locales to core locales for this test\n\tlet aListExtra = [\n'bs-cyrl,bosnian (cyrillic)',\n'de-ch,german (switzerland)',\n'en-001,english',\n'en-ca,english (canada)',\n'en-sg,english (singapore)',\n'es-ar,spanish (argentina)',\n'es-br,spanish (brazil)',\n'es-do,spanish (dominican republic)',\n'es-py,spanish (paraguay)',\n'ff-adlm,fulah (adlam)',\n'fr-ca,french (canada)',\n'fr-ht,french (haiti)',\n'hi-latn,hindi (latin)',\n'kk-cn,kazakh (china)',\n'kok-latn,konkani (latin)',\n'ks-deva,kashmiri (devanagari)',\n'kxv-telu,kuvi (telugu)',\n'pa-arab,punjabi (arabic)',\n'pt-ao,portuguese (angola)',\n'sd-deva,sindhi (devanagari)',\n'se-fi,northern sami (finland)',\n'shi-latn,tachelhit (latin)',\n'sr-ba,serbian (bosnia & herzegovina)',\n'sr-latn,serbian (latin)',\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\n'sw-cd,swahili (congo kinshasa)',\n'ur-in,urdu (india)',\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\n'vai-latn,vai (latin)',\n'yo-bj,yoruba (benin)',\n'yue-cn,cantonese (china)',\n\t]\n\tlist = list.concat(aListExtra)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\t//list = ['en','de','fr','nl']\n\n\tlegend()\n\tif (isSupported) {\n\t\tsetBtn(\"all\")\n\t\tsetTimeout(function() {\n\t\t\trun_main(\"all\")\n\t\t}, 100)\n\t}\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/dnlanguage.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>dn: language</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"32%\"><col width=\"68%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">displaynames: language\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy.\r\n\t\t\tThe <code>TINY</code> test is a minimalist hardcoded test built for speed.</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<span id=\"btiny\" class=\"btn4 btn\" onClick=\"run('tiny')\">[ TINY ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames#language_display_names\r\n\r\n/*\r\n   item levels atm: default 298  likely 531  expanded 1090\r\n locale levels atm: default 245  likely 495  expanded 1088\r\nwhat is max unique?\r\n\r\n       |  likely  |  expand  | <-- locales\r\nlikely |   293    |   293    |\r\nexpand |   293    |   293    |\r\n  |\r\nitems\r\n\r\nso that means we can safely use likely for aItems\r\nand then use default for locales and add extras (from usual suspects i.e likely)\r\n*/\r\n\r\n\r\nlet aItemsIgnore = []\r\nlet aItems = [\r\n\t// we rebuild this\r\n\t// these entries are for mmy notes on gecko entropy effects\r\n\t// and minimum test builds\r\n\t'en',    // 208 (199 core)\r\n\t'fr-ch', // 236 (221 core)\r\n\t'kl',    // 242 (224 core)\r\n\t'zh-hk', // 247\r\n\t'gu',    // 251\r\n\t'bn-in', // 255\r\n\t'sr-ba', // 258\r\n\t// going up in twos\r\n\t'bho','en-sb','gv','ko-kp','sw-cd', // 268\r\n\t// going up in ones\r\n\t'ar-eh','da','en-ae','en-ke','en-sh','ff', 'guz','ksh','kw','mgo',\r\n\t'nb','nyn','pa-arab','raj','ro-md','rwk','saq','szl','te','twq',\r\n\t'uz-arab','vmw','vun','zgh','zu',\r\n\t// end of gecko\r\n\r\n]\r\n\r\nfunction setup_items(level = 'default') {\r\n\t// get data\r\n\tlet aTmp = gLocalesOriginal\r\n\tif ('default' !== level) {\r\n\t\taTmp = aTmp.concat(gLocalesLikely)\r\n\t}\r\n\tif ('expand' == level) {\r\n\t\taTmp = aTmp.concat(gLocalesExpand)\r\n\t}\r\n\taTmp = aTmp.filter(function(item, position) {return aTmp.indexOf(item) === position})\r\n\taTmp.sort()\r\n\t// clean up date\r\n\taTmp.forEach(function(item){\r\n\t\titem = item.split(',')[0].trim()\r\n\t\titem = item.toLowerCase()\r\n\t\t// only allow language and subtag: not region or variant\r\n\t\tif (item.split('-').length < 3) {\r\n\t\t\taItems.push(item)\r\n\t\t} else {\r\n\t\t\t//console.log('rejected', item)\r\n\t\t}\r\n\t})\r\n\taItems.sort()\r\n}\r\nsetup_items('likely')\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toTestData = {},\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction testitems() {\r\n\t//loop each script on it's own\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\toTestData = {}\r\n\ttry {\r\n\t\taItems.forEach(function(item){\r\n\t\t\t/* take one of the highest oTestData results and keep adding until you reach max\r\n\t\t\titem = [\r\n\t\t\t\t'en','fr-ch','kl','zh-hk','gu','bn-in','sr-ba', //258\r\n\t\t\t\t// down to +2's\r\n\t\t\t\t'bho','en-sb','gv','ko-kp','sw-cd', // 268\r\n\t\t\t\t// down to +1s\r\n\t\t\t\t'ar-eh','da','en-ae','en-ke','en-sh','ff', 'guz','ksh','kw','mgo',\r\n\t\t\t\t'nb','nyn','pa-arab','raj','ro-md','rwk','saq','szl','te','twq',\r\n\t\t\t\t'uz-arab','vmw','vun','zgh','zu',\r\n\t\t\t\titem\r\n\t\t\t]\r\n\t\t\t//*/\r\n\t\t\t// allow testing arrays of scripts\r\n\t\t\tlet testarray = 'object' == typeof item ? item : [item]\r\n\t\t\trun_main('all', testarray, true)\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t}\r\n}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method, aTest, isLoop = false) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {} \r\n\tlet spacer = '<br><br>'\r\n\tlet tests = {}\r\n\tlet aUsed = undefined == aTest ? aItems: aTest\r\n\tif (!isLoop) {\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t}\r\n\r\n\tif (method == \"all\") {\r\n\t\taUsed.sort()\r\n\t\t//tests = {'language': {'dialect': aUsed, 'standard': aUsed}}\r\n\t\t// temp just use dialect as we work out min items\r\n\t\ttests = {'language': {'dialect': aUsed}}\r\n\r\n\t} else if (method == \"min\") {\r\n\t\ttests = {\r\n\t\t\t'language': {\r\n\t\t\t\t'dialect': [\r\n\t\t\t\t\t'en','fr-ch','kl','zh-hk','gu','bn-in','sr-ba', //258\r\n\t\t\t\t\t// down to +2's\r\n\t\t\t\t\t'bho','en-sb','gv','ko-kp','sw-cd', // 268\r\n\t\t\t\t\t// down to +1s\r\n\t\t\t\t\t'ar-eh','da','en-ae','en-ke','en-sh','ff', 'guz','ksh','kw','mgo',\r\n\t\t\t\t\t'nb','nyn','pa-arab','raj','ro-md','rwk','saq','szl','te','twq',\r\n\t\t\t\t\t'uz-arab','vmw','vun','zgh','zu',\r\n\t\t\t\t],\r\n\t\t\t\t// and blink needs help\r\n\t\t\t\t'standard': [\r\n\t\t\t\t\t'ak','az','be','bs','haw',\r\n\t\t\t\t],\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t} else {\r\n\t\t// TINY\r\n\t\ttests = {\r\n\t\t\t'language': {\r\n\t\t\t\t'dialect': [\r\n\t\t\t\t'en','fr-ch','kl','zh-hk',\r\n\t\t\t\t'gu', // splits include en-us, various spanish/french\r\n\t\t\t\t'bn-in', // splits include en-au, en-in\r\n\t\t\t\t'sr-ba', // splits include en-ca, es-br\r\n\t\t\t\t]\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\tlet oType = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(t){ // for each type\r\n\t\t\t\toType[t] = {}\r\n\t\t\t\tfor (const s of Object.keys(tests[t])) { // for each style\r\n\t\t\t\t\toType[t][s] = ('all' == method) ? [] : {}\r\n\t\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: t, languageDisplay: s})\r\n\t\t\t\t\ttests[t][s].sort().forEach(function(item){\r\n\t\t\t\t\t\tlet value = dn.of(item)\r\n\t\t\t\t\t\tif ('all' == method) {\r\n\t\t\t\t\t\t\toType[t][s].push(item +': '+ value)\t\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\toType[t][s][item] = value\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\r\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\r\n\t\t\t\tif (key == \"locales\") {\r\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\r\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oTempData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tif (!isLoop) {\r\n\t\t\t\tlet str = \"\"\r\n\t\t\t\tfor (const type of Object.keys(oData[k])) { // for each type\r\n\t\t\t\t\tif (type !== \"locales\") {\r\n\t\t\t\t\t\tstr += \"<li>\"+ s14 + type + sc +\"</li>\"\r\n\t\t\t\t\t\tObject.keys(oData[k][type]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\t\tif ('all' == method) {\r\n\t\t\t\t\t\t\t\t// color up the parts\r\n\t\t\t\t\t\t\t\tlet aParts = [], array = oData[k][type][s]\r\n\t\t\t\t\t\t\t\tarray.forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tlet parts = item.split(\":\")\r\n\t\t\t\t\t\t\t\t\titem = item.replace(parts[0] +':', s12 + parts[0]+ ':' + sc)\r\n\t\t\t\t\t\t\t\t\taParts.push(item)\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += s16 + s +\": \"+ sc + aParts.join(', ') +\"</br>\"\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tstr += s16 + s + sc + '<ul class=\"main\">'\r\n\t\t\t\t\t\t\t\tObject.keys(oData[k][type][s]).forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + item +': '+ sc + oData[k][type][s][item] +\"</li>\"\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += '</ul>'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif ('tiny' !== method) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (isLoop) {\r\n\t\t\tlet testCount = localeGroups.length\r\n\t\t\t// record it\r\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\r\n\t\t\toTestData[testCount].push(aUsed)\r\n\t\t\t//console.log(testCount, aUsed)\r\n\t\t\t/*\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ s16 + testCount + sc +': '+ '[\\''+ aUsed.join('\\', \\'') +'\\']'\r\n\t\t\t//*/\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ '\\''+ aUsed.join('\\', \\'') +'\\', // ' + testCount\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"5dc44060\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"6da69df6\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"86d81065\") { // FF147+ 293\r\n\t\t\t\t} else if (localesHash == \"95e2270c\") { // FF140-146 293\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n//*\r\n'ar-eg,arabic (egypt)',\r\n'ar-ly,arabic (libya)',\r\n'ar-sa,arabic (saudi arabia)',\r\n'az-cyrl,azerbaijani (cyrillic)',\r\n'bn-in,bengali (india)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'de-at,german (austria)',\r\n'de-ch,german (switzerland)',\r\n'en-ag,english (antigua & barbuda)',\r\n'en-au,english (australia)',\r\n'en-ca,english (canada)',\r\n'en-gb,english (united kingdom)',\r\n'en-in,english (india)',\r\n'es-ar,spanish (argentina)',\r\n'es-br,spanish (brazil)',\r\n'es-cl,spanish (chile)',\r\n'es-mx,spanish (mexico)',\r\n'es-pr,spanish (puerto rico)',\r\n'es-us,spanish (united states)',\r\n'fa-af,persian (afghanistan)',\r\n'ff-adlm,fulah (adlam)',\r\n'fr-be,french (belgium)',\r\n'fr-ca,french (canada)',\r\n'fr-ch,french (switzerland)',\r\n'hi-latn,hindi (latin)',\r\n'kk-cn,kazakh (china)',\r\n'ko-kp,korean (north korea)',\r\n'kok-latn,konkani (latin)',\r\n'ks-deva,kashmiri (devanagari)',\r\n'kxv-telu,kuvi (telugu)',\r\n'pa-arab,punjabi (arabic)',\r\n'ps-pk,pashto (pakistan)',\r\n'pt-ao,portuguese (angola)',\r\n'ro-md,romanian (moldova)',\r\n'ru-ua,russian (ukraine)',\r\n'sd-deva,sindhi (devanagari)',\r\n'se-fi,northern sami (finland)',\r\n'shi-latn,tachelhit (latin)',\r\n'sr-ba,serbian (bosnia & herzegovina)',\r\n'sr-cyrl-me,serbian (cyrillic montenegro)',\r\n'sr-cyrl-xk,serbian (cyrillic kosovo)',\r\n'sr-latn,serbian (latin)',\r\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\r\n'sr-latn-me,serbian (latin montenegro)',\r\n'sr-latn-xk,serbian (latin kosovo)',\r\n'sw-cd,swahili (congo kinshasa)',\r\n'sw-ke,swahili (kenya)',\r\n'ti-er,tigrinya (eritrea)',\r\n'ur-in,urdu (india)',\r\n'uz-af,uzbek (afghanistan)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'vai-latn,vai (latin)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-cn,cantonese (china)',\r\n//*/\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en','de','fr','pl','pt','sv']\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dnregion.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>dn: region</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"32%\"><col width=\"68%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">displaynames: region\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy.\r\n\t\t\tThe <code>TINY</code> test is a minimalist hardcoded test built for speed.</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<span id=\"btiny\" class=\"btn4 btn\" onClick=\"run('tiny')\">[ TINY ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames#region_code_display_names\r\n// ISO-3166 2-letter country codes\r\n// https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2\r\n// https://www.iso.org/obp/ui/#iso:pub:PUB500001:en\r\n\r\n/**\r\n\t0. ensure a good range of locales in list\r\n\t1. generate_codes() -> all possible 676 two-letter codes into aItems\r\n\t2. testregions()\r\n\t\ttest each code individually and populate into oTestData\r\n\t\twhere codes are bucketized by their unique hash count\r\n\t3. oTestData\r\n\t\te.g. oTestData[1] shows 385 codes, such as 'AA'\r\n\t\te.g. console.log(\"'\" + oTestData[1].join(\"','\") +\"'\")\r\n\t\tthese are the useless codes\r\n\t\trun_main('all',aItemsIgnore) and see for yourself\r\n\t4. compute_data()\r\n\t\tignore oTestData[1]\r\n\t\tlogs to console an output for pasting into aRegions\r\n\t\tas a starting point\r\n\r\n\tgecko 140+\r\n\tcode space: 676 | not supported 385 | left for max tests 291\r\n\r\n\r\n**/\r\n\r\nlet aItemsIgnore = [\r\n\t// 385 confirmed all locales return the same hash\r\n\t// run_main('all', aItemsIgnore)\r\n\t'AA','AB','AH','AJ','AK','AP','AV','AY','BC','BK','BP','BX','CB','CE','CJ','DA','DB','DC','DF','DH',\r\n\t'DI','DL','DN','DP','DQ','DR','DS','DT','DU','DV','DW','DX','EB','ED','EF','EI','EJ','EK','EL','EM',\r\n\t'EN','EO','EP','EQ','EV','EW','EX','EY','FA','FB','FC','FD','FE','FF','FG','FH','FL','FN','FP','FS',\r\n\t'FT','FU','FV','FW','FY','FZ','GC','GJ','GK','GO','GV','GX','GZ','HA','HB','HC','HD','HE','HF','HG',\r\n\t'HH','HI','HJ','HL','HO','HP','HQ','HS','HW','HX','HY','HZ','IA','IB','IF','IG','IH','II','IJ','IK',\r\n\t'IP','IU','IV','IW','IX','IY','IZ','JA','JB','JC','JD','JF','JG','JH','JI','JJ','JK','JL','JN','JQ',\r\n\t'JR','JS','JU','JV','JW','JX','JY','JZ','KA','KB','KC','KD','KF','KJ','KK','KL','KO','KQ','KS','KT',\r\n\t'KU','KV','KX','LD','LE','LF','LG','LH','LJ','LL','LM','LN','LO','LP','LQ','LW','LX','LZ','MB','MJ',\r\n\t'NB','ND','NJ','NK','NM','NN','NS','NV','NW','NX','NY','OA','OB','OC','OD','OE','OF','OG','OH','OI',\r\n\t'OJ','OK','OL','ON','OO','OP','OQ','OR','OS','OT','OU','OV','OW','OX','OY','OZ','PB','PD','PI','PJ',\r\n\t'PO','PP','PQ','PV','PX','QB','QC','QD','QE','QF','QG','QH','QI','QJ','QK','QL','QM','QN','QP','QQ',\r\n\t'QR','QS','QT','QV','QW','QX','QY','QZ','RA','RB','RC','RD','RF','RG','RI','RJ','RK','RL','RM','RN',\r\n\t'RP','RQ','RR','RT','RV','RX','RY','RZ','SF','SP','SQ','SW','TB','TE','TI','TQ','TS','TU','TX','TY',\r\n\t'UB','UC','UD','UE','UF','UH','UI','UJ','UL','UO','UP','UQ','UR','UT','UU','UV','UW','UX','VB','VF',\r\n\t'VH','VJ','VK','VL','VM','VO','VP','VQ','VR','VS','VT','VV','VW','VX','VY','VZ','WA','WB','WC','WD',\r\n\t'WE','WG','WH','WI','WJ','WL','WM','WN','WO','WP','WQ','WR','WT','WU','WV','WW','WX','WY','WZ','XC',\r\n\t'XD','XE','XF','XG','XH','XI','XJ','XL','XM','XN','XO','XP','XQ','XR','XS','XT','XU','XV','XW','XX',\r\n\t'XY','XZ','YA','YB','YC','YF','YG','YH','YI','YJ','YK','YL','YM','YN','YO','YP','YQ','YR','YS','YV',\r\n\t'YW','YX','YY','YZ','ZB','ZC','ZD','ZE','ZF','ZG','ZH','ZI','ZJ','ZK','ZL','ZN','ZO','ZP','ZQ','ZS',\r\n\t'ZT','ZU','ZV','ZX','ZY',\r\n]\r\n\r\n// 291 confirmed supported in FF140+\r\nlet aItems = [\r\n// first cover gLocales: alpha-2 and alpha-3 codes (aiming for 233/245)\r\n'VI', // 185\r\n'US', // 184\r\n'ZZ', // 150\r\n'CM', // 106 and after that we're growing in 2's\r\n// switch to extended locale list\r\n\t// starting at 229 for the above: aiming for 281\r\n'VC', // 179 | 235\r\n'TL', // 139 | 239\r\n'FR', // 149 | 242\r\n'QO', // 144 | 245\r\n'GB', // 183 | 247 now growing in 2's\r\n'ZA', // 169 | 249\r\n'KP', // 167 | 251\r\n'PL', // 129 | 253\r\n'KH', // 124 | 255\r\n'ZM', // 101 | 257\r\n// now growing in 1's\r\n\r\n\r\n'UK','VG', // 182\r\n'PS', // 181\r\n'TC', // 181\r\n'MP', // 179\r\n'KY', // 178\r\n'CK','MH', // 176\r\n'BA', // 175\r\n'NF', // 175\r\n'SB', // 174\r\n'CF','FK', // 173\r\n'PM', // 171\r\n'GF','PF', // 170\r\n'AE', // 169\r\n'KR', // 167\r\n'DO','ST', // 166\r\n'GQ','KN', // 165\r\n'AS','PN', // 161\r\n'NZ', // 160\r\n'CH','PG', // 158\r\n'CZ','DD','DE','NC', // 157\r\n'GS', // 155\r\n'AG', // 153\r\n'NT','SA','TT', // 152\r\n'JT', // 151\r\n'MI','PU','UM','WK', // 151\r\n'GR','HM','TF', // 150\r\n'FX', // 149\r\n'AZ','CC','NL', // 148\r\n'CD','MO','ZR', // 147\r\n'CI','WF', // 146\r\n'IC', // 145\r\n'CX','HK','RU','SC','SU','TR', // 144\r\n'HR', // 143\r\n'ES','FO', // 142\r\n'AT','KM','VA', // 141\r\n'AX','SH', // 140\r\n'MV','NO', // 139\r\n'TP', // 139\r\n'BV','CV','HU', // 138\r\n'GE','SE', // 137\r\n'EG', // 136\r\n'BE','DZ','EU','QU', // 135\r\n'CY','KG','PH', // 134\r\n'CN','CP','GW', // 133\r\n'FM','PC', // 132\r\n'EH','LC', // 131\r\n'AC','AU','BG','BQ','GL','UN', // 130\r\n'RO','SJ','SK', // 129\r\n'CG','IE', // 128\r\n'IS', // 128\r\n'IM','JO','SI', // 128\r\n'BR','BU','MM','UZ', // 127\r\n'DK','ET','MR','MU','SS', // 126\r\n'AM','FI','JP','TH','TM', // 125\r\n'BY', // 124\r\n'KZ','LU','MK', // 123\r\n'IN','TJ', // 122\r\n'NG','SL','UA', // 120\r\n'AR','ID','IL','PT','XA', // 119\r\n'SZ', // 118\r\n'BD','EE','LT','RE', // 117\r\n'EA','ER','GP','IT','LI','MY','VD','VN', // 116\r\n'AL','DJ','TN', // 115\r\n'MD', // 114\r\n'BH','BO','CL','IO','MX', // 113\r\n'BM', // 112\r\n'AF','CO','LV','PR','SV','VE', // 111\r\n'MN','SY', // 110\r\n'BN','BS','GI','TD','XB', // 109\r\n'EZ','UY', // 108\r\n'BF','HV','MS','NE', // 107\r\n'EC','MZ', // 106\r\n'AI','MA','PY', // 105\r\n'BL','CR','LR','SO','TZ', // 104\r\n'GD','JM', // 103\r\n'HN','KW','RH','SM','ZW', // 102\r\n'BW','LB','SD','YT', // 101\r\n\r\n// under 100\r\n'AD','AN','AO','AQ','AW','BB','BI','BJ',\r\n'BT','BZ','CA','CQ','CS','CT','CU','CW',\r\n'DG','DM','DY','FJ','FQ','GA','GG','GH',\r\n'GM','GN','GT','GU','GY','HT','IQ','IR',\r\n'JE','KE','KI','LA','LK','LS','LY','MC',\r\n'ME','MF','MG','ML','MQ','MT','MW','NA',\r\n'NH','NI','NP','NQ','NR','NU','OM','PA',\r\n'PE','PK','PW','PZ','QA','RS','RW','SG',\r\n'SN','SR','SX','TA','TG','TK','TO','TV',\r\n'TW','UG','VU','WS','XK','YD','YE','YU',\r\n]\r\naItems.sort()\r\n\r\n\r\nfunction generate_codes() {\r\n\tlet aAll = []\r\n\tfor (let i =65; i < 91; i++) {\r\n\t\tlet one = String.fromCharCode(i)\r\n\t\tfor (let j =65; j < 91; j++) {\r\n\t\t\tlet two = String.fromCharCode(j)\r\n\t\t\taAll.push(one + two)\r\n\t\t}\r\n\t}\r\n\tconsole.log(aAll)\r\n\taRegions = aAll\r\n}\r\n\r\n\r\nfunction compute_data() {\r\n\taRes = []\r\n\tfor (const key of Object.keys(oTestData)) {\r\n\t\tif (key !== '1') {\r\n\t\t\taRes.push([\"'\" + oTestData[key].join(\"','\") +\"', // \"+ key])\r\n\t\t}\r\n\t}\r\n\tconsole.log(aRes.join('\\n'))\r\n}\r\n\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toTestData = {},\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction testitems() {\r\n\t//loop each script on it's own\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\toTestData = {}\r\n\ttry {\r\n\t\taItems.forEach(function(item){\r\n\t\t\t/* take one of the highest oTestData results and keep adding until you reach max\r\n\t\t\titem = [\r\n\t\t\t\t'VI','US','ZZ','CM','VC','TL','FR','QO','GB','ZA','KP','PL','KH','ZM',\r\n\t\t\t\titem\r\n\t\t\t]\r\n\t\t\t//*/\r\n\t\t\t// allow testing arrays of scripts\r\n\t\t\tlet testarray = 'object' == typeof item ? item : [item]\r\n\t\t\trun_main('all', testarray, true)\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t}\r\n}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method, aTest, isLoop = false) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {} \r\n\tlet spacer = '<br><br>'\r\n\tlet tests = {}\r\n\tlet aUsed = undefined == aTest ? aItems: aTest\r\n\tif (!isLoop) {\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t}\r\n\r\n\tif (method == \"all\") {\r\n\t\taUsed.sort()\r\n\t\ttests = {'region': {'long': aUsed, 'narrow': aUsed, 'short': aUsed}}\r\n\r\n\t} else if (method == \"min\") {\r\n\t\ttests = {\r\n\t\t\t'region': {\r\n\t\t\t\t'narrow': [\r\n\t\t\t\t\t'VI','US','ZZ','CM','VC','TL','FR','QO','GB','ZA','KP','PL','KH','ZM',\r\n\t\t\t\t\t// in ones\r\n\t\t\t\t\t'AS','CN','EH','GH','GL','GS','IM','IT','JT','KE','KY','LS',\r\n\t\t\t\t\t'MM','MO','MS','MZ','NL','PK','PS','RW','SJ','SS','SZ','TF',\r\n\t\t\t\t\t'UM', // and UM for blink\r\n\t\t\t\t],\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\t// TINY\r\n\t\ttests = {\r\n\t\t\t'region': {'narrow': ['VI','US','ZZ','CM','VC','TL','FR'],}\r\n\t\t}\r\n\t}\r\n\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\tlet oType = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(t){ // for each type\r\n\t\t\t\toType[t] = {}\r\n\t\t\t\tfor (const s of Object.keys(tests[t])) { // for each style\r\n\t\t\t\t\toType[t][s] = ('tiny' !== method) ? [] : {}\r\n\t\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: t, style: s})\r\n\t\t\t\t\ttests[t][s].sort().forEach(function(item){\r\n\t\t\t\t\t\tlet value = dn.of(item)\r\n\t\t\t\t\t\tif ('tiny' !== method) {\r\n\t\t\t\t\t\t\toType[t][s].push(item +': '+ value)\t\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\toType[t][s][item] = value\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\r\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\r\n\t\t\t\tif (key == \"locales\") {\r\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\r\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oTempData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tif (!isLoop) {\r\n\t\t\t\tlet str = \"\"\r\n\t\t\t\tfor (const type of Object.keys(oData[k])) { // for each type\r\n\t\t\t\t\tif (type !== \"locales\") {\r\n\t\t\t\t\t\tstr += \"<li>\"+ s14 + type + sc +\"</li>\"\r\n\t\t\t\t\t\tObject.keys(oData[k][type]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\t\tif ('tiny' !== method) {\r\n\t\t\t\t\t\t\t\t// color up the parts\r\n\t\t\t\t\t\t\t\tlet aParts = [], array = oData[k][type][s]\r\n\t\t\t\t\t\t\t\tarray.forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tlet parts = item.split(\":\")\r\n\t\t\t\t\t\t\t\t\titem = item.replace(parts[0] +':', s12 + parts[0]+ ':' + sc)\r\n\t\t\t\t\t\t\t\t\taParts.push(item)\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += s16 + s.slice(0,1).toUpperCase() +\": \"+ sc + aParts.join(', ') +\"</br>\"\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tstr += s16 + s + sc + '<ul class=\"main\">'\r\n\t\t\t\t\t\t\t\tObject.keys(oData[k][type][s]).forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + item +': '+ sc + oData[k][type][s][item] +\"</li>\"\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += '</ul>'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif ('tiny' !== method) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (isLoop) {\r\n\t\t\tlet testCount = localeGroups.length\r\n\t\t\t// record it\r\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\r\n\t\t\toTestData[testCount].push(aUsed)\r\n\t\t\t//console.log(testCount, aUsed)\r\n\t\t\t/*\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ s16 + testCount + sc +': '+ '[\\''+ aUsed.join('\\', \\'') +'\\']'\r\n\t\t\t//*/\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ '\\''+ aUsed.join('\\', \\'') +'\\', // ' + testCount\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"7b25db13\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"a0c62bf7\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"57c71705\") { // FF147+ 283\r\n\t\t\t\t} else if (localesHash == \"3207fd4a\") { // FF140-146 281\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n'ar-ly,arabic (libya)',\r\n'ar-sa,arabic (saudi arabia)',\r\n'az-cyrl,azerbaijani (cyrillic)',\r\n'bn-in,bengali (india)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'de-at,german (austria)',\r\n'de-ch,german (switzerland)',\r\n'en-au,english (australia)',\r\n'en-ca,english (canada)',\r\n'en-in,english (india)',\r\n'es-ar,spanish (argentina)',\r\n'es-br,spanish (brazil)',\r\n'es-cl,spanish (chile)',\r\n'es-mx,spanish (mexico)',\r\n'es-pr,spanish (puerto rico)',\r\n'es-us,spanish (united states)',\r\n'fa-af,persian (afghanistan)',\r\n'ff-adlm,fulah (adlam)',\r\n'fr-be,french (belgium)',\r\n'fr-ca,french (canada)',\r\n'hi-latn,hindi (latin)',\r\n'kk-cn,kazakh (china)',\r\n'ko-kp,korean (north korea)',\r\n'kok-latn,konkani (latin)',\r\n'ks-deva,kashmiri (devanagari)',\r\n'kxv-telu,kuvi (telugu)',\r\n'pa-arab,punjabi (arabic)',\r\n'ps-pk,pashto (pakistan)',\r\n'pt-ao,portuguese (angola)',\r\n'ro-md,romanian (moldova)',\r\n'ru-ua,russian (ukraine)',\r\n'sd-deva,sindhi (devanagari)',\r\n'se-fi,northern sami (finland)',\r\n'shi-latn,tachelhit (latin)',\r\n'sr-ba,serbian (bosnia & herzegovina)',\r\n'sr-cyrl-me,serbian (cyrillic montenegro)',\r\n'sr-cyrl-xk,serbian (cyrillic kosovo)',\r\n'sr-latn,serbian (latin)',\r\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\r\n'sr-latn-me,serbian (latin montenegro)',\r\n'sr-latn-xk,serbian (latin kosovo)',\r\n'sw-cd,swahili (congo kinshasa)',\r\n'sw-ke,swahili (kenya)',\r\n'ur-in,urdu (india)',\r\n'uz-af,uzbek (afghanistan)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'vai-latn,vai (latin)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-cn,cantonese (china)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en','de','fr','pl','pt','sv']\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dnscript.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>dn: script</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"32%\"><col width=\"68%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">displaynames: script\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames#script_code_display_names\r\n\r\n\r\n// https://unicode.org/iso15924/iso15924-codes.html\r\n// nonsense 4 letter codes, or ones not supported simply\r\n\t// return themselves in all styles: we can determine these\r\n\t// one by one if we get a single unique hash across all locales\r\n\r\nlet aItemsIgnore = [\r\n\t// confirmed all locales return the same hash\r\n\t'Berf','Chis','Hntl','Kitl','Leke','Nkdb','Pcun','Pelm','Piqd',\r\n\t'Psin','Qaaa','Qabx','Ranj','Seal','Shui','Sidt','Tayo','Tols',\r\n]\r\n\r\n// confirmed supported in FF140+\r\nlet aItems = [\r\n'Zxxx', // 155\r\n'Latn', // 127 -> 174\r\n'Hans', // 150 -> 181\r\n'Arab', // 124 -> 183\r\n'Deva', // 89 -> 185\r\n'Zzzz', // 152 -> 187\r\n'Cyrl', // 128 -> 188\r\n'Mymr', // 109 -> 189\r\n'Mong', // 108 -> 190\r\n'Beng', // 106 -> 191\r\n'Orya', // 92 -> 192\r\n'Guru', // 90 -> 193\r\n\r\n'Hant', // 145\r\n'Hrkt', // 125\r\n'Ethi', // 124\r\n'Jpan', // 122\r\n'Armn', // 119\r\n'Cans', // 119\r\n'Geor', // 117\r\n'Hebr', // 117\r\n'Kore', // 116\r\n'Zyyy', // 114\r\n'Grek', // 113\r\n'Zmth', // 112\r\n'Zsym', // 111\r\n'Sinh', // 110\r\n'Syrc', // 109\r\n'Tibt', // 106\r\n'Hanb', // 103\r\n'Sund', // 102\r\n'Laoo', // 100\r\n'Mlym', // 99\r\n'Taml', // 98\r\n'Thai', // 97\r\n'Gujr', // 94\r\n'Khmr', // 94\r\n'Brai', // 92\r\n'Deva', // 89\r\n'Cher', // 86\r\n'Knda', // 86\r\n'Mtei', // 86\r\n'Telu', // 85\r\n'Egyp', // 84\r\n'Hani', // 84\r\n'Ital', // 83\r\n'Zinh', // 83\r\n'Xpeo', // 82\r\n'Xsux', // 82\r\n'Hung', // 81\r\n'Olck', // 81\r\n\r\n// 61 to 80\r\n'Adlm','Aran','Armi','Avst','Bali','Bopo','Bugi','Cakm',\r\n'Cari','Copt','Cprt','Cyrs','Egyd','Egyh','Glag','Goth',\r\n'Hang','Hira','Jamo','Java','Kana','Latf','Latg','Lina',\r\n'Linb','Lyci','Lydi','Mand','Mani','Maya','Mero','Nkoo',\r\n'Osma','Perm','Phli','Phlp','Phnx','Plrd','Prti','Rohg',\r\n'Runr','Samr','Sgnw','Shaw','Syre','Syrj','Syrn','Talu',\r\n'Tfng','Thaa','Ugar','Vaii','Visp','Yiii','Zsye',\r\n\r\n// under 60\r\n'Afak','Aghb','Ahom','Bamu','Bass','Batk','Bhks','Blis',\r\n'Brah','Buhd','Cham','Chrs','Cirt','Cpmn','Diak','Dogr',\r\n'Dsrt','Dupl','Elba','Elym','Geok','Gong','Gonm','Gran',\r\n'Hano','Hatr','Hluw','Hmng','Hmnp','Inds','Jurc','Kali',\r\n'Kawi','Khar','Khoj','Kits','Kpel','Kthi','Lana','Lepc',\r\n'Limb','Lisu','Loma','Mahj','Maka','Marc','Medf','Mend',\r\n'Merc','Modi','Moon','Mroo','Mult','Nagm','Nand','Narb',\r\n'Nbat','Newa','Nkgb','Nshu','Ogam','Orkh','Osge','Ougr',\r\n'Palm','Pauc','Phag','Phlv','Rjng','Roro','Sara','Sarb',\r\n'Saur','Shrd','Sidd','Sind','Sogd','Sogo','Sora','Soyo',\r\n'Sylo','Tagb','Takr','Tale','Tang','Tavt','Teng','Tglg',\r\n'Tirh','Tnsa','Toto','Vith','Wara','Wcho','Wole','Yezi',\r\n'Zanb',\r\n// FYI: these seven are the same for all locales in blink\r\n'Gara','Sunu','Tutg', // 4\r\n'Gukh','Krai','Onao','Todr', // 3\r\n//*/\r\n\t]\r\naItems.sort()\r\n\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toTestData = {},\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction testitems() {\r\n\t//loop each script on it's own\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\toTestData = {}\r\n\ttry {\r\n\t\taItems.forEach(function(item){\r\n\t\t\t/* take one of the highest oTestData results and keep adding until you reach max\r\n\t\t\titem = [\r\n\t\t\t\t'Zxxx','Latn','Hans','Arab','Deva','Zzzz','Cyrl','Mymr','Mong','Beng','Orya','Guru',\r\n\t\t\t\titem\r\n\t\t\t]\r\n\t\t\t//*/\r\n\t\t\t// allow testing arrays of scripts\r\n\t\t\tlet testarray = 'object' == typeof item ? item : [item]\r\n\t\t\trun_main('all', testarray, true)\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t}\r\n}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DisplayNames.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method, aTest, isLoop = false) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {} \r\n\tlet spacer = '<br><br>'\r\n\tlet tests = {}\r\n\tlet aUsed = undefined == aTest ? aItems: aTest\r\n\tif (!isLoop) {\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t}\r\n\r\n\tif (method == \"all\") {\r\n\t\taUsed.sort()\r\n\t\ttests = {'script': {'long': aUsed, 'narrow': aUsed, 'short': aUsed}}\r\n\r\n\t} else {\r\n\t\t// long + narrow add nothing\r\n\t\ttests = {'script': {'short': ['Zxxx','Latn','Hans','Arab','Deva','Zzzz','Cyrl','Mymr','Mong','Beng','Orya','Guru','Hrkt']}}\r\n\t}\r\n\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\tlet oType = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(t){ // for each type\r\n\t\t\t\toType[t] = {}\r\n\t\t\t\tfor (const s of Object.keys(tests[t])) { // for each style\r\n\t\t\t\t\toType[t][s] = ('all' == method) ? [] : {}\r\n\t\t\t\t\tlet dn = new Intl.DisplayNames([code], {type: t, style: s})\r\n\t\t\t\t\ttests[t][s].sort().forEach(function(item){\r\n\t\t\t\t\t\t//item = item.toLowerCase() // blink is case sensitive\r\n\t\t\t\t\t\tlet value = dn.of(item)\r\n\t\t\t\t\t\tif ('all' == method) {\r\n\t\t\t\t\t\t\toType[t][s].push(item.toLowerCase() +': '+ value)\t\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\toType[t][s][item.toLowerCase()] = value\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oType) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oType).forEach(function(typekey){\r\n\t\t\t\t\toTempData[hash][typekey] = oType[typekey]\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const key of Object.keys(oTempData[h]).sort()) { // for each granularity\r\n\t\t\t\tif (key == \"locales\") {\r\n\t\t\t\t\toData[h][key] = oTempData[h][key].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][key]).length) {\r\n\t\t\t\t\t\toData[h][key] = oTempData[h][key]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oTempData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tif (!isLoop) {\r\n\t\t\t\tlet str = \"\"\r\n\t\t\t\tfor (const type of Object.keys(oData[k])) { // for each type\r\n\t\t\t\t\tif (type !== \"locales\") {\r\n\t\t\t\t\t\tstr += \"<li>\"+ s14 + type + sc +\"</li>\"\r\n\t\t\t\t\t\tObject.keys(oData[k][type]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\t\tif ('all' == method) {\r\n\t\t\t\t\t\t\t\t// color up the parts\r\n\t\t\t\t\t\t\t\tlet aParts = [], array = oData[k][type][s]\r\n\t\t\t\t\t\t\t\tarray.forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tlet parts = item.split(\":\")\r\n\t\t\t\t\t\t\t\t\titem = item.replace(parts[0] +':', s12 + parts[0]+ ':' + sc)\r\n\t\t\t\t\t\t\t\t\taParts.push(item)\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += s16 + s.slice(0,1).toUpperCase() +\": \"+ sc + aParts.join(', ') +\"</br>\"\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tstr += s16 + s + sc + '<ul class=\"main\">'\r\n\t\t\t\t\t\t\t\tObject.keys(oData[k][type][s]).forEach(function(item){ // for each item\r\n\t\t\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + item +': '+ sc + oData[k][type][s][item] +\"</li>\"\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\tstr += '</ul>'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif ('all' == method) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oData)\r\n\r\n\t\tif (isLoop) {\r\n\t\t\tlet testCount = localeGroups.length\r\n\t\t\t// record it\r\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\r\n\t\t\toTestData[testCount].push(aUsed)\r\n\t\t\t//console.log(testCount, aUsed)\r\n\t\t\t/*\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ s16 + testCount + sc +': '+ '[\\''+ aUsed.join('\\', \\'') +'\\']'\r\n\t\t\t//*/\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ '\\''+ aUsed.join('\\', \\'') +'\\', // ' + testCount\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"08964df0\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"4d2a0147\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"4f0204b0\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"eeac2f94\") { // FF147+ 195\r\n\t\t\t\t} else if (localesHash == \"552c1baf\") { // FF140-146 193\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\tlet test = new Intl.DisplayNames(undefined, {type: 'region'}).resolvedOptions().locale\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message +'banana'\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n'az-cyrl,azerbaijani (cyrillic)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'en-au,english (australia)',\r\n'en-gb,english (united kingdom)',\r\n'en-in,english (india)',\r\n'es-us,spanish (united states)',\r\n'es-ar,spanish (argentina)',\r\n'fa-af,persian (afghanistan)',\r\n'ff-adlm,fulah (adlam)',\r\n'fr-ca,french (canada)',\r\n'hi-latn,hindi (latin)',\r\n'kk-cn,kazakh (china)',\r\n'kok-latn,konkani (latin)',\r\n'ks-deva,kashmiri (devanagari)',\r\n'kxv-telu,kuvi (telugu)',\r\n'pa-arab,punjabi (arabic)',\r\n'pt-ao,portuguese (angola)',\r\n'sd-deva,sindhi (devanagari)',\r\n'se-fi,northern sami (finland)',\r\n'sr-latn,serbian (latin)',\r\n'sw-ke,swahili (kenya)',\r\n'uz-af,uzbek (afghanistan)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-cn,cantonese (china)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en','de','fr','pl','pt','sv']\r\n\t//list = ['en']\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/domrectspoof.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>domrect spoof detection</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\t#tzpRect {\r\n\t\t\tbackground-color: #545aa7;\r\n\t\t\ttop: 0;\r\n\t\t\tleft: 0;\r\n\t\t\twidth:100px;\r\n\t\t\theight:100px;\r\n\t\t\ttransform: rotate(45deg);\r\n\t\t\tpadding: 0px;\r\n\t\t\topacity: 0.5;\r\n\t\t\tz-index: -20;\r\n\t\t\tposition: fixed; /* must be fixed */\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div id=\"tzpRect\"></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb15\">\r\n\t\t<col width=\"13%\"><col width=\"12%\"><col width=\"25%\"><col width=\"50%\">\r\n\t\t<thead><tr><th colspan=\"4\">\r\n\t\t\t<div class=\"nav-title\">domrect spoof detection\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"4\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Measures a fixed element, rotated to forced decimal precision. On gecko\r\n\t\t\tthis is an expected known result at any zoom level (try it: it auto-runs on resize) and on any platform.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"4\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">expected gecko</td><td colspan=\"2\" class=\"mono spaces\" id=\"expected\"></td></tr>\r\n\t\t<tr><td colspan=\"4\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"4\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\" class=\"padr\">Element.getBoundingClientRect</td><td class=\"mono\" id=\"res0\"></td></tr>\r\n\t\t<tr><td colspan=\"3\" class=\"padr\">Element.getClientRects</td><td class=\"mono\" id=\"res1\"></td></tr>\r\n\t\t<tr><td colspan=\"3\" class=\"padr\">range.getBoundingClientRect</td><td class=\"mono\" id=\"res2\"></td></tr>\r\n\t\t<tr><td colspan=\"3\" class=\"padr\">range.getClientRects</td><td class=\"mono\" id=\"res3\"></td></tr>\r\n\t\t<tr><td colspan=\"4\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"4\"><hr></td></tr>\r\n\t\t<tr><td>history</td><td colspan=\"3\" class=\"mono spaces\" id=\"history\"></td></tr>\r\n\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet aHistory = []\r\nlet target = dom.tzpRect\r\nlet valid = \"4447a487\",\r\n\tvalid1 = \"f39a5bc7\",\r\n\tvalid2 = \"dd03a1b3\"\r\nlet expected = [\r\n\ts12 +\" one: \"+ sc + valid1,\r\n\t\"  x, left, y, top: -20.716659545898438\",\r\n\ts12 +\" two: \"+ sc + valid2,\r\n\t\"    width, height: 141.41665649414062\",\r\n\t\"    right, bottom: 120.69999694824219\",\r\n\ts12 +\"full: \"+ sc + valid,\r\n]\r\nlet count = 0\r\ndom.expected.innerHTML = expected.join(\"<br>\")\r\n\r\nlet sNames = [\"Element.getBoundingClientRect\", \"Element.getClientRects\",\r\n\t\"Range.getBoundingClientRect\", \"Range.getClientRects\"]\r\n\r\nlet oBlinkNew = {}\r\n\r\nfunction get_rect() {\r\n\tif (aHistory.length > 19) {aHistory = aHistory.slice(-19)}\r\n\t// clear details\r\n\tsNames.forEach(function(name) {sDetail[name] = []})\r\n\r\n\tlet el = dom.tzpRect\r\n\tlet res = []\r\n\r\n\t/* boxquads: rules\r\n\t\t\t\ttarget.getBoxQuads() +'' == \"[object DOMQuad]\"\r\n\t\t target.getBoxQuads()[0] +'' == \"[object DOMQuad]\"\r\n\ttarget.getBoxQuads()[0].p1 +'' == \"[object DOMPoint]\"\r\n\r\n\t\tit's a fixed square\r\n\t-------------------\r\n\tstable\r\n\t\tall w = 1, all z = 0\r\n\t\tone of x or y == 50\r\n\t\t\t^ can get decimals e.g. at 133% zoom 50.000003814697266\r\n\t\t\t^ p1, p3 it is x\r\n\t\t\t^ p2, p4 it is y\r\n\t\tthe other of x or y\r\n\t\t\t^ p1, p4 = same\r\n\t\t\t^ p2, p3 = same\r\n\t\tstable hash = 6ec100c2 (with [object DOM*] checks) \r\n\r\n\tmath\r\n\tthe other we do math with:\r\n\t\t^ p1, p3 it is y\r\n\t\t^ p2, p4 it is x\r\n\t*/\r\n\t/*\r\n\ttry {\r\n\t\t// with DPR of 1 floaties happen at 67% and 133% zoom only\r\n\t\tlet box = el.getBoxQuads()[0]\r\n\t\tlet p1 = box.p1, p2 = box.p2, p3 = box.p3, p4 = box.p4\r\n\t\tlet oStable = {\r\n\t\t\t'a': el.getBoxQuads() +'',\r\n\t\t\t'b': el.getBoxQuads()[0] +'',\r\n\t\t\t'c': el.getBoxQuads()[0].p1 +'',\r\n\t\t\t'match1y+4x': [p1.y == p4.x],\r\n\t\t\t'match2x+3y': [p2.x == p3.y],\r\n\t\t\t'p1': [p1.w, (Math.round((p1.x)*10000))/10000, p1.z],\r\n\t\t\t'p2': [p2.w, (Math.round((p2.y)*10000))/10000, p2.z],\r\n\t\t\t'p3': [p3.w, (Math.round((p3.x)*10000))/10000, p3.z],\r\n\t\t\t'p4': [p4.w, (Math.round((p4.y)*10000))/10000, p4.z],\r\n\t\t}\r\n\t\tlet stablehash = mini(oStable) // f73069e0 \r\n\t\tif ('6ec100c2' !== stablehash) {\r\n\t\t\tconsole.log(stablehash, oStable)\r\n\t\t}\r\n\t\tlet aMath = [p2.x - p1.y, p3.y - p4.x] // one is width the other height\r\n\t\t//console.log(aMath)\r\n\t} catch(e) {\r\n\t\t//console.log(e+'')\r\n\t}\r\n\t//*/\r\n\r\n\tfor (let i=0; i < 4; i++) {\r\n\t\tlet output = document.getElementById(\"res\"+i)\r\n\t\ttry {\r\n\t\t\tlet obj = \"\"\r\n\r\n\t\t\tif (i == 0) {\r\n\t\t\t\tobj = el.getBoundingClientRect()\r\n\t\t\t} else if (i == 1) {\r\n\t\t\t\tobj = el.getClientRects()[0]\r\n\t\t\t} else {\r\n\t\t\t\tlet range = document.createRange()\r\n\t\t\t\trange.selectNode(el)\r\n\t\t\t\tif (i == 2) {\r\n\t\t\t\t\tobj = range.getBoundingClientRect()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tobj = range.getClientRects()[0]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// 3 unique values, but cover all names\r\n\t\t\tlet array1 = [obj.x, obj.left, obj.y, obj.top]\r\n\t\t\tlet array2 = [obj.width, obj.height, obj.right, obj.bottom]\r\n\t\t\tlet array = array1.concat(array2)\r\n\r\n\t\t\tlet hash = mini(array.join())\r\n\t\t\tlet hash1 = mini(array1.join())\r\n\t\t\tlet hash2 = mini(array2.join())\r\n\r\n\t\t\tif (isEngine == \"blink\") {\r\n\t\t\t\tif (!knownBlink.includes(hash)) {\r\n\t\t\t\t\tif (oBlinkNew[hash] == undefined) {oBlinkNew[hash] = array}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// part 1\r\n\t\t\tlet display = hash1\r\n\t\t\tif (isFF) {display += (hash1 == valid1 ? green_tick : red_cross)}\r\n\t\t\t// part 2\r\n\t\t\tdisplay += \" | \" + hash2\r\n\t\t\tif (isFF) {display += (hash2 == valid2 ? green_tick : red_cross)}\r\n\r\n\t\t\t// always display details clickable unless known full gecko\r\n\t\t\tif (hash !== valid) {\r\n\t\t\t\tsDetail[sNames[i]] = array\r\n\t\t\t\tdisplay += buildButton(\"15\", sNames[i] +\", true\", \"details\")\r\n\t\t\t}\r\n\t\t\toutput.innerHTML = display\r\n\t\t\tif (isFF) {\r\n\t\t\t\tres.push(hash + (mini(array.join()) == valid ? green_tick : red_cross))\r\n\t\t\t} else {\r\n\t\t\t\tres.push(hash)\r\n\t\t\t}\r\n\t\t} catch(e) {\r\n\t\t\toutput.innerHTML = e.name\r\n\t\t\tres.push(zErr)\r\n\t\t}\r\n\t}\r\n\r\n\t\r\n\r\n\r\n\t// add to history\r\n\tcount++\r\n\tif (res.length) {\r\n\t\taHistory.push((count.toString()).padStart(3)+ \": \" + res.join(\" | \"))\r\n\t}\r\n\tdom.history.innerHTML = aHistory.join(\"<br>\")\r\n}\r\n\r\nfunction outputBlink() {\r\n\tif (isEngine !== \"blink\") { return }\r\n\tif (Object.keys(oBlinkNew).length) {\r\n\t\tfor (const hash of Object.keys(oBlinkNew)) {\r\n\t\t\tconsole.log(hash, oBlinkNew[hash])\r\n\t\t}\r\n\t}\r\n}\r\n\r\nlet knownBlink = []\r\nlet oBlinkSamples = {\r\n\t// windows dpr=1, dpi=1\r\n\t// all zoom levels, default positions\r\n\t\"2cd8d217\": [-20.71068000793457, -20.71068000793457, -20.71068000793457, -20.71068000793457, 141.42135620117188, 141.42135620117188, 120.7106761932373, 120.7106761932373],\r\n\t\"3fcff5df\": [-20.710678100585938, -20.710678100585938, -20.710678100585938, -20.710678100585938, 141.42137145996094, 141.42137145996094, 120.710693359375, 120.710693359375],\r\n\t\"5f81a4df\": [-20.710678100585938, -20.710678100585938, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42135620117188, 120.71067810058594, 120.71067810058594],\r\n\t\"29e0447b\": [-20.707441329956055, -20.707441329956055, -20.707441329956055, -20.707441329956055, 141.39926147460938, 141.39926147460938, 120.69182014465332, 120.69182014465332],\r\n\t\"820458b3\": [-20.71068000793457, -20.71068000793457, -20.71068000793457, -20.71068000793457, 141.42137145996094, 141.42137145996094, 120.71069145202637, 120.71069145202637],\r\n\t// zoom 500\r\n\t\"10d1949f\": [-20.710681915283203, -20.710681915283203, -20.710681915283203, -20.710681915283203, 141.42137145996094, 141.42137145996094, 120.71068954467773, 120.71068954467773],\r\n\t\"c3ec468d\": [-20.710668563842773, -20.710668563842773, -20.710668563842773, -20.710668563842773, 141.42135620117188, 141.42135620117188, 120.7106876373291, 120.7106876373291],\r\n\t\"2d2f0e42\": [-20.710668563842773, -20.710668563842773, -20.710668563842773, -20.710668563842773, 141.42137145996094, 141.42135620117188, 120.71068954467773, 120.7106876373291],\r\n\t// 400 + 200\r\n\t\"1de05d90\": [-20.710693359375, -20.710693359375, -20.710678100585938, -20.710678100585938, 141.42137145996094, 141.42135620117188, 120.71067810058594, 120.71067810058594],\r\n\t// 300\r\n\t\"db71bd4a\": [-20.710674285888672, -20.710674285888672, -20.710683822631836, -20.710683822631836, 141.42135620117188, 141.42135620117188, 120.7106819152832, 120.71067237854004],\r\n\t\"baf82439\": [-20.710674285888672, -20.710674285888672, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42137145996094, 120.7106819152832, 120.710693359375],\r\n\t\"efce3af9\": [-20.710674285888672, -20.710674285888672, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42135620117188, 120.7106819152832, 120.71067810058594],\r\n\t\"6a972f1e\": [-20.710678100585938, -20.710678100585938, -20.710683822631836, -20.710683822631836, 141.42137145996094, 141.42135620117188, 120.710693359375, 120.71067237854004],\r\n\t\"243cc010\": [-20.710678100585938, -20.710678100585938, -20.710683822631836, -20.710683822631836, 141.42135620117188, 141.42135620117188, 120.71067810058594, 120.71067237854004],\r\n\t// 250 + 125\r\n\t\"61b13db7\": [-20.710681915283203, -20.710681915283203, -20.710678100585938, -20.710678100585938, 141.42137145996094, 141.42135620117188, 120.71068954467773, 120.71067810058594],\r\n\t\"a699afe9\": [-20.710681915283203, -20.710681915283203, -20.710678100585938, -20.710678100585938, 141.42137145996094, 141.42137145996094, 120.71068954467773, 120.710693359375],\r\n\t// 175\r\n\t\"83d6e13b\": [-20.710676193237305, -20.710676193237305, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42135620117188, 120.71068000793457, 120.71067810058594],\r\n\t\"2ec16097\": [-20.710676193237305, -20.710676193237305, -20.710676193237305, -20.710676193237305, 141.42135620117188, 141.42135620117188, 120.71068000793457, 120.71068000793457],\r\n\t// 150\r\n\t\"3710b115\": [-20.710678100585938, -20.710678100585938, -20.710681915283203, -20.710681915283203, 141.42135620117188, 141.42135620117188, 120.71067810058594, 120.71067428588867],\r\n\t// 125\r\n\t\"9b7056e0\": [-20.710683822631836, -20.710683822631836, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42137145996094, 120.71067237854004, 120.710693359375],\r\n\t\"7b7a02ee\": [-20.710683822631836, -20.710683822631836, -20.710678100585938, -20.710678100585938, 141.42135620117188, 141.42135620117188, 120.71067237854004, 120.71067810058594],\r\n\t// 110\r\n\t\"41da0d9e\": [-20.710683822631836, -20.710683822631836, -20.710670471191406, -20.710670471191406, 141.42135620117188, 141.4213409423828, 120.71067237854004, 120.7106704711914],\r\n\t\"9395cf12\": [-20.710678100585938, -20.710678100585938, -20.710670471191406, -20.710670471191406, 141.4213409423828, 141.4213409423828, 120.71066284179688, 120.7106704711914],\r\n\t\"588e14ad\": [-20.710678100585938, -20.710678100585938, -20.710670471191406, -20.710670471191406, 141.42135620117188, 141.4213409423828, 120.71067810058594, 120.7106704711914],\r\n\t\"2033442d\": [-20.710678100585938, -20.710678100585938, -20.710670471191406, -20.710670471191406, 141.42137145996094, 141.4213409423828, 120.710693359375, 120.7106704711914],\r\n\t\"ab629b83\": [-20.710678100585938, -20.710678100585938, -20.710670471191406, -20.710670471191406, 141.42137145996094, 141.42135620117188, 120.710693359375, 120.71068572998047],\r\n\t\"bbe63d75\": [-20.710678100585938, -20.710678100585938, -20.710670471191406, -20.710670471191406, 141.42135620117188, 141.42135620117188, 120.71067810058594, 120.71068572998047],\r\n\t// 100\r\n\t\"d468b282\": [-20.710678100585938, -20.710678100585938, -20.710693359375, -20.710693359375, 141.42135620117188, 141.42137145996094, 120.71067810058594, 120.71067810058594],\r\n\t// 90\r\n\t\"1067b86e\": [-20.71068000793457, -20.71068000793457, -20.7106876373291, -20.7106876373291, 141.42135620117188, 141.42137145996094, 120.7106761932373, 120.71068382263184],\r\n\t\"af2cc7d9\": [-20.71068000793457, -20.71068000793457, -20.7106876373291, -20.7106876373291, 141.42137145996094, 141.42137145996094, 120.71069145202637, 120.71068382263184],\r\n\t// 75\r\n\t\"6403ea2f\": [-20.710678100585938, -20.710678100585938, -20.710674285888672, -20.710674285888672, 141.42137145996094, 141.42135620117188, 120.710693359375, 120.7106819152832],\r\n\t\"d9c4977d\": [-20.710678100585938, -20.710678100585938, -20.710674285888672, -20.710674285888672, 141.42135620117188, 141.42135620117188, 120.71067810058594, 120.7106819152832],\r\n\t\"0a1577ed\": [-20.710678100585938, -20.710678100585938, -20.710678100585938, -20.710678100585938, 141.42137145996094, 141.42135620117188, 120.710693359375, 120.71067810058594],\r\n\t// 67\r\n\t\"518c2bea\": [-20.707441329956055, -20.707441329956055, -20.707443237304688, -20.707443237304688, 141.39926147460938, 141.39926147460938, 120.69182014465332, 120.69181823730469],\r\n\t// other?\r\n\t\"11882a7d\": [-20.710678100585938, -20.710678100585938, -20.710693359375, -20.710693359375, 141.42135620117188, 141.42138671875, 120.71067810058594, 120.710693359375],\r\n\r\n}\r\nfor (const hash of Object.keys(oBlinkSamples)) {\r\n\tknownBlink.push(hash)\r\n}\r\n\r\nfunction testBlink() {\r\n\tfor (const hash of Object.keys(oBlinkSamples)) {\r\n\t\tlet array = oBlinkSamples[hash]\r\n\t\tif (mini(array.join()) !== hash) {console.log(hash, \"hash mismatch\")}\r\n\r\n\t\tlet x = array[0], left = array[1]\r\n\t\tlet y = array[2], top = array[3]\r\n\t\tlet width = array[4], height = array[5]\r\n\t\tlet right = array[6], bottom = array[7]\r\n\t\tlet aLies = []\r\n\t\tif (x !== left) {\r\n\t\t\taLies.push([\"x !== left\", x, left].join(\", \"))\r\n\t\t}\r\n\t\tif (y !== top) {\r\n\t\t\taLies.push([\"y !== top\", y, top].join(\", \"))\r\n\t\t}\r\n\t\tif (right - x !== width) {\r\n\t\t\taLies.push([\"right - x !== width\", right, x, \"expected \"+ width, \"got \"+ (right - x), \"diff \"+ ((right - x) - width)].join(\", \"))\r\n\t\t}\r\n\t\tif (bottom - y !== height) {\r\n\t\t\taLies.push([\"bottom - y !== height\", bottom, y, \"expected \"+ height, \"got \"+ (bottom - y), \"diff \"+ ((bottom - y) - height)].join(\", \"))\r\n\t\t}\r\n\t\tif (aLies.length) {\r\n\t\t\tconsole.log(hash +\"\\n - \"+ aLies.join(\"\\n - \"))\r\n\t\t}\t\t\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\twindow.addEventListener(\"resize\", get_rect)\r\n\tget_rect()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/domrectspoofratio.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>domrect spoof aspect ratio</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 780px;}\r\n\t\t#tzpRect {\r\n\t\t\tbackground-color: #545aa7;\r\n\t\t\ttop: 0;\r\n\t\t\tleft: 0;\r\n\t\t\twidth:100px;\r\n\t\t\theight:100px;\r\n\t\t\t/* scaleX, scaleY, skewX, skewY, translateX, translateY */\r\n\t\t\ttransform: matrix(.3333, 1.6666, 1, 1, 100, 100);\r\n\t\t\tpadding: 0px;\r\n\t\t\topacity: 0.5;\r\n\t\t\tz-index: -20;\r\n\t\t\tposition: fixed;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div id=\"tzpRect\"></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb15\">\r\n\t\t<col width=\"1%\"><col width=\"29%\"><col width=\"70%\">\r\n\t\t<thead><tr><th colspan=\"3\">\r\n\t\t\t<div class=\"nav-title\">domrect spoof detection: aspect ratio\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"3\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Measures a fixed element, transformed to forced decimal precision with\r\n\t\t\ta known aspect ratio to check if width or height have been tampered with.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">Element.getBoundingClientRect</td><td class=\"mono\" id=\"res0\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">Element.getClientRects</td><td class=\"mono\" id=\"res1\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">range.getBoundingClientRect</td><td class=\"mono\" id=\"res2\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">range.getClientRects</td><td class=\"mono\" id=\"res3\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td>history</td><td colspan=\"2\" class=\"mono spaces\" id=\"history\"></td></tr>\r\n\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet aHistory = []\r\nlet count = 0\r\nlet sNames = [\"Element.getBoundingClientRect\", \"Element.getClientRects\",\r\n\t\"Range.getBoundingClientRect\", \"Range.getClientRects\"]\r\nlet knownBlink = [\r\n\t// standard\r\n\t0.5, 0.4999999427781113, 0.5000000572218888, \r\n\t// when page is scrolled so no longer top left = 0\r\n\t0.4999999427781178,\r\n\t0.4999998855562356,\r\n]\r\n\r\nfunction get_rect() {\r\n\tif (aHistory.length > 19) {aHistory = aHistory.slice(-19)}\r\n\r\n\tlet el = dom.tzpRect\r\n\tlet res = []\r\n\tfor (let i=0; i < 4; i++) {\r\n\t\tlet output = document.getElementById(\"res\"+i)\r\n\t\ttry {\r\n\t\t\tlet obj = \"\"\r\n\r\n\t\t\tif (i == 0) {\r\n\t\t\t\tobj = el.getBoundingClientRect()\r\n\t\t\t} else if (i == 1) {\r\n\t\t\t\tobj = el.getClientRects()[0]\r\n\t\t\t} else {\r\n\t\t\t\tlet range = document.createRange()\r\n\t\t\t\trange.selectNode(el)\r\n\t\t\t\tif (i == 2) {\r\n\t\t\t\t\tobj = range.getBoundingClientRect()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tobj = range.getClientRects()[0]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tlet array = [obj.width, obj.height, (obj.width / obj.height)]\r\n\t\t\tlet display = array.join(\" | \")\r\n\t\t\tlet mark = \"\"\r\n\t\t\tif (isFF) {\r\n\t\t\t\t mark = array[2] == 0.5000000572204611 ? green_tick : red_cross\r\n\t\t\t} else if (isEngine == \"blink\") {\r\n\t\t\t\tmark = knownBlink.includes(array[2]) ? green_tick : red_cross\r\n\t\t\t}\r\n\t\t\toutput.innerHTML = display + mark\r\n\r\n\t\t\tres.push(array[2] + mark)\r\n\r\n\t\t} catch(e) {\r\n\t\t\toutput.innerHTML = e.name\r\n\t\t\tres.push(zErr)\r\n\t\t}\r\n\t}\r\n\t// add to history\r\n\tcount++\r\n\tif (res.length) {\r\n\t\taHistory.push((count.toString()).padStart(3)+ \": \" + res.join(\" | \"))\r\n\t}\r\n\tdom.history.innerHTML = aHistory.join(\"<br>\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\twindow.addEventListener(\"resize\", get_rect)\r\n\tget_rect()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dtfcomponents.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>dtf: components</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">datetimeformat: components\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy with DateTimeFormat date-time components</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\", // to compare min to\r\n\toData = {}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DateTimeFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method = \"all\") {\r\n\tlet t0 = performance.now()\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\t// all dates (days/months/am-pm) must be timezone resistent:  we are checking locale only\r\n\t\t// timezonename (and locale) is tested in the a different PoC - see TZP set_oIntlDateTests section\r\n\t// thus we use UTC time so everyone uses the exact same dates, and then we pass\r\n\t\t// UTC as the timezone so nothing shifts, preserving our specific datetimes\r\n\t// note: only some sub-tests actually expose the day and/or hr\r\n\r\n\tlet dates = {\r\n\t\t// fractionalSecondDigits: we only ever reveal the seconds\r\n\t\tFSD: new Date('2023-06-10T01:12:34.567Z'),\r\n\t\t// month (x4) + year (xJan): we only ever reveal the month or year\r\n\t\tJan: new Date('2023-01-15T00:00:00.000Z'),\r\n\t\tFeb: new Date('2023-02-15T00:00:00.000Z'),\r\n\t\tMar: new Date('2023-03-15T00:00:00.000Z'),\r\n\t\tApr: new Date('2023-04-15T00:00:00.000Z'),\r\n\t\tMay: new Date('2023-05-15T00:00:00.000Z'),\r\n\t\tJun: new Date('2023-06-15T00:00:00.000Z'),\r\n\t\tJul: new Date('2023-07-15T00:00:00.000Z'),\r\n\t\tAug: new Date('2023-08-15T00:00:00.000Z'),\r\n\t\tSep: new Date('2023-09-15T00:00:00.000Z'),\r\n\t\tOct: new Date('2023-10-15T00:00:00.000Z'),\r\n\t\tNov: new Date('2023-11-15T00:00:00.000Z'),\r\n\t\tDec: new Date('2023-12-15T00:00:00.000Z'),\r\n\t\t// days (x2) + hrs (xFri) expose doh! days + hrs\r\n\t\tDay: new Date(\"2003-01-01T01:00:00.000Z\"), // single digit day\r\n\t\tSat: new Date('2023-01-14T01:00:00.000Z'),\r\n\t\tSun: new Date('2023-01-15T01:00:00.000Z'),\r\n\t\tMon: new Date('2023-01-16T01:00:00.000Z'),\r\n\t\tTue: new Date('2023-01-17T01:00:00.000Z'),\r\n\t\tWed: new Date('2023-01-18T01:00:00.000Z'), // doubles as hour 1 + minute 0\r\n\t\tThu: new Date('2023-01-19T01:00:00.000Z'),\r\n\t\tFri: new Date('2023-01-20T13:00:00.000Z'), // doubles as hour 13\r\n\t\t// era: expose day\r\n\t\tEra1: new Date('-000002-01-15T01:00:00.000Z'), // BC\r\n\t\tEra2: new Date('2001-01-01T01:00:00.000Z'), // AD\r\n\t}\r\n\r\n\r\n\t// https://www.w3schools.com/jsref/jsref_tolocalestring.asp\r\n\tlet testsAll = {\r\n\t\t\"day\": {\r\n\t\t\tdates: [dates.Day],\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {day: \"2-digit\"},\r\n\t\t\t\t\"numeric\": {day: \"numeric\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"era\": {\r\n\t\t\tdates: [dates.Era1, dates.Era2],\r\n\t\t\toptions: {\r\n\t\t\t\t\"long\": {era: \"long\"},\r\n\t\t\t\t\"narrow\": {era: \"narrow\"},\r\n\t\t\t\t\"short\": {era: \"short\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t// this adds nothing on it's own: what adds entropy is the delimiters and decimal separator\r\n\t\t\"fractionalSecondDigits\": {\r\n\t\t\tdates: [dates.FSD],\r\n\t\t\toptions: {\r\n\t\t\t\t\"1\": {minute: \"numeric\", second: 'numeric', fractionalSecondDigits: 1},\r\n\t\t\t\t\"2\": {minute: \"numeric\", second: 'numeric', fractionalSecondDigits: 2},\r\n\t\t\t\t\"3\": {minute: \"numeric\", second: 'numeric', fractionalSecondDigits: 3},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"hour\": {\r\n\t\t\tdates: [dates.Wed, dates.Fri],\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {hour: \"2-digit\"},\r\n\t\t\t\t\"numeric\": {hour: \"numeric\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"hourCycle\": {\r\n\t\t\tdates: [dates.Wed],\r\n\t\t\toptions: {\r\n\t\t\t\t\"h112\": {hour: \"2-digit\", hourCycle: \"h11\"},\r\n\t\t\t\t// these add nothing except perf cost!\r\n\t\t\t\t//\"h11-n\": {hour: \"numeric\", hourCycle: \"h11\"},\r\n\t\t\t\t//\"h12-2\": {hour: \"2-digit\", hourCycle: \"h12\"},\r\n\t\t\t\t//\"h12-n\": {hour: \"numeric\", hourCycle: \"h12\"},\r\n\t\t\t\t//\"h23-2\": {hour: \"2-digit\", hourCycle: \"h23\"},\r\n\t\t\t\t//\"h23-n\": {hour: \"numeric\", hourCycle: \"h23\"},\r\n\t\t\t\t//\"h24-2\": {hour: \"2-digit\", hourCycle: \"h24\"},\r\n\t\t\t\t//\"h24-n\": {hour: \"numeric\", hourCycle: \"h24\"},\r\n\t\t\t\t},\r\n\t\t},\r\n\t\t\"minute\": {\r\n\t\t\tdates: [dates.Wed], // single digit 0 vs 00\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {minute: \"2-digit\"},\r\n\t\t\t\t\"numeric\": {minute: \"numeric\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"month\": {\r\n\t\t\tdates: [\r\n\t\t\t\tdates.Jan, dates.Feb, dates.Mar, dates.Apr, dates.May, dates.Jun,\r\n\t\t\t\tdates.Jul, dates.Aug, dates.Sep, dates.Oct, dates.Nov, dates.Dec,\r\n\t\t\t],\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {month: \"2-digit\"},\r\n\t\t\t\t\"long\": {month: \"long\"},\r\n\t\t\t\t\"narrow\": {month: \"narrow\"},\r\n\t\t\t\t\"numeric\": {month: \"numeric\"},\r\n\t\t\t\t\"short\": {month: \"short\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"second\": {\r\n\t\t\tdates: [dates.FSD],\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {second: \"2-digit\"},\r\n\t\t\t\t\"numeric\": {second: \"numeric\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"weekday\": {\r\n\t\t\tdates: [dates.Mon, dates.Tue, dates.Wed, dates.Thu, dates.Fri, dates.Sat, dates.Sun],\r\n\t\t\toptions: {\r\n\t\t\t\t\"long\": {weekday: \"long\"},\r\n\t\t\t\t\"narrow\": {weekday: \"narrow\"},\r\n\t\t\t\t\"short\": {weekday: \"short\"},\r\n\t\t\t},\r\n\t\t},\r\n\t\t\"year\": {\r\n\t\t\tdates: [dates.Jan],\r\n\t\t\toptions: {\r\n\t\t\t\t\"2-digit\": {year: \"2-digit\"},\r\n\t\t\t\t\"numeric\": {year: \"numeric\"},\r\n\t\t\t},\r\n\t\t},\r\n\t}\r\n\tlet testsMin = {\r\n\t\t\"era\": [\r\n\t\t\t// to match TZP we will add controlling the year-month-day part (in TZP this is needed to get toLocaleString to match)\r\n\t\t\t[dates.Era1, {\"long\": {era: \"long\", year: \"numeric\", month: \"numeric\", day: \"numeric\"}}],\r\n\t\t],\r\n\t\t\"fractionalSecondDigits\": [\r\n\t\t\t[dates.FSD, {\"1\": {minute: \"numeric\", second: 'numeric', fractionalSecondDigits: 1}}]\r\n\t\t],\r\n\t\t\"hour\": [\r\n\t\t\t[dates.Wed, {\"numeric\": {hour: \"numeric\"}}],\r\n\t\t],\r\n\t\t\"hourCycle\": [\r\n\t\t\t[dates.Wed, {\"h112\": {hour: \"2-digit\", hourCycle: \"h11\"}}], // adds like 2 items\r\n\t\t],\r\n\t\t\"month\": [\r\n\t\t\t[dates.Nov, {\"narrow\": {month: \"narrow\"}}],\r\n\t\t\t[dates.Jan, {\"short\": {month: \"short\"}}],\r\n\t\t\t[dates.Jun, {\"short\": {month: \"short\"}}], // needed for FF146 and lower\r\n\t\t\t[dates.Sep, {\"short\": {month: \"short\"}}],\r\n\t\t\t[dates.Nov, {\"short\": {month: \"short\"}}],\r\n\t\t],\r\n\t\t\"weekday\": [\r\n\t\t\t[dates.Wed, {\"narrow\": {weekday: \"narrow\"}, \"long\": {weekday: \"long\"}}],\r\n\t\t\t[dates.Fri, {\"short\": {weekday: \"short\"}, \"narrow\": {weekday: \"narrow\"}, \"long\": {weekday: \"long\"}}],\r\n\t\t],\r\n\t\t\"year\": [ // needed for blink\r\n\t\t\t[dates.Jan, {\"2-digit\": {year: \"2-digit\"}}],\r\n\t\t],\r\n\t}\r\n\tlet tests = method == \"all\" ? testsAll : testsMin\r\n\t//console.log(tests)\r\n\r\n\tlet oConst = {}\r\n\toData = {}\r\n\r\n\t//aLocales = ['en']\r\n\ttry {\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oTempData = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(test) {\r\n\t\t\t\toTempData[test] = {}\r\n\t\t\t\tif (method == \"all\") {\r\n\t\t\t\t\tObject.keys(tests[test].options).forEach(function(opt) {\r\n\t\t\t\t\t\toTempData[test][opt] = []\r\n\t\t\t\t\t\tlet options = tests[test].options[opt]\r\n\t\t\t\t\t\toptions['timeZone'] = \"UTC\"\r\n\t\t\t\t\t\tlet formatter = new Intl.DateTimeFormat(code, options)\r\n\t\t\t\t\t\tlet dates = tests[test].dates\r\n\t\t\t\t\t\tdates.forEach(function(date){\r\n\t\t\t\t\t\t\toTempData[test][opt].push(formatter.format(date))\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t})\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttry {oConst.WeS = new Intl.DateTimeFormat(code, {weekday: \"short\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.WeN = new Intl.DateTimeFormat(code, {weekday: \"narrow\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.WeL = new Intl.DateTimeFormat(code, {weekday: \"long\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.MoS = new Intl.DateTimeFormat(code, {month: \"short\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.MoN = new Intl.DateTimeFormat(code, {month: \"narrow\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.HoN = new Intl.DateTimeFormat(code, {hour: \"numeric\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.HoH = new Intl.DateTimeFormat(code, {hour: \"2-digit\", hourCycle: \"h11\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.ErL = new Intl.DateTimeFormat(code, {era: \"long\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.Fr1 = new Intl.DateTimeFormat(code, {minute: \"numeric\", second: 'numeric', timeZone: \"UTC\", fractionalSecondDigits: 1})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.Fr2 = new Intl.DateTimeFormat(code, {minute: \"numeric\", second: 'numeric', timeZone: \"UTC\", fractionalSecondDigits: 2})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.Fr3 = new Intl.DateTimeFormat(code, {minute: \"numeric\", second: 'numeric', timeZone: \"UTC\", fractionalSecondDigits: 3})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.Ye2 = new Intl.DateTimeFormat(code, {year: \"2-digit\", timeZone: \"UTC\"})} catch(e) {}\r\n\r\n\t\t\t\t\t/* only maybe used if trying to test other values outside of current min\r\n\t\t\t\t\ttry {oConst.ErN = new Intl.DateTimeFormat(code, {era: \"narrow\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.ErS = new Intl.DateTimeFormat(code, {era: \"short\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.MoL = new Intl.DateTimeFormat(code, {month: \"long\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.Se2 = new Intl.DateTimeFormat(code, {second: \"2-digit\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.SeN = new Intl.DateTimeFormat(code, {second: \"numeric\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\ttry {oConst.YeN = new Intl.DateTimeFormat(code, {year: \"numeric\", timeZone: \"UTC\"})} catch(e) {}\r\n\t\t\t\t\t//*/\r\n\r\n\t\t\t\t\tlet array = tests[test]\r\n\t\t\t\t\tarray.forEach(function(item) {\r\n\t\t\t\t\t\tlet date = item[0]\r\n\t\t\t\t\t\tlet styles = item[1]\r\n\t\t\t\t\t\tObject.keys(styles).forEach(function(opt) {\r\n\t\t\t\t\t\t\tif (oTempData[test][opt] == undefined) {\r\n\t\t\t\t\t\t\t\toTempData[test][opt] = []\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tlet constructor = test.slice(0,1).toUpperCase() + test.slice(1,2) + opt.slice(0,1).toUpperCase()\r\n\t\t\t\t\t\t\t//console.log(test.slice(0,1).toUpperCase() + test.slice(1,2) + opt.slice(0,1).toUpperCase())\r\n\t\t\t\t\t\t\tlet formatter = oConst[constructor]\r\n\t\t\t\t\t\t\toTempData[test][opt].push(formatter.format(date))\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oTempData)\r\n\t\t\t//console.log(hash, oTempData)\r\n\r\n\t\t\tif (oData[hash] == undefined) {\r\n\t\t\t\toData[hash] = {}\r\n\t\t\t\toData[hash][\"locales\"] = [code]\r\n\t\t\t\toData[hash][\"data\"] = {}\r\n\t\t\t\tfor (const k of Object.keys(oTempData).sort()) {\r\n\t\t\t\t\toData[hash][\"data\"][k] = oTempData[k]\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\toData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t//console.log(oData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].length\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const p of Object.keys(oData[k][\"data\"])) {\r\n\t\t\t\tstr += s14 + p +\": \"+ sc\r\n\t\t\t\tfor (const i of Object.keys(oData[k][\"data\"][p]).sort()) {\r\n\t\t\t\t\tlet option\r\n\t\t\t\t\tlet slicelen = (p == \"hourCycle\" ? 5 : 1)\r\n\t\t\t\t\tif (i == \"numeric\") {\r\n\t\t\t\t\t\toption = \"Num\"\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\toption = i.slice(0,slicelen).toUpperCase()\r\n\t\t\t\t\t}\r\n\t\t\t\t\tstr += \"<li>\"+ s16 + option +\": \"+ sc + oData[k][\"data\"][p][i].join(\", \") +\"</li>\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"].join(\", \") +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// ignore if non-supported used, which return same as undefined = user's resolved options\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"d3d21dbf\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"6e33774c\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"e0558272\") { // FF140-146+\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"bfb86c10\") { // FF151+: 326\r\n\t\t\t\t} else if (localesHash == \"b4751570\") { // FF147+: 325\r\n\t\t\t\t} else if (localesHash == \"32f59b41\") { // FF140-146: 326\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\r\n\t\t// display\r\n\t\tlet display = s4 + localeGroups.length + sc +\" from \"+ s4 + aLocales.length + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF58+\r\n\t\tlet test = new Intl.DateTimeFormat(\"en\").formatToParts(new Date)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"af-na,afrikaans (namibia)\",\r\n\t\t\"ar-ae,arabic (united arabic emirates)\",\r\n\t\t\"ar-dz,arabic (algeria)\",\r\n\t\t\"ar-il,arabic (israel)\",\r\n\t\t\"ar-iq,arabic (iraq)\",\r\n\t\t\"ar-ly,arabic (libya)\",\r\n\t\t\"ar-ma,arabic (morocco)\",\r\n\t\t\"ar-mr,arabic (mauritania)\",\r\n\t\t\"ar-sa,arabic (saudi arabia)\",\r\n\t\t\"az-cyrl,azerbaijani (cyrillic)\",\r\n\t\t\"bn-in,bengali (india)\",\r\n\t\t\"bo-in,tibetan (india)\",\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"ckb-ir,central kurdish (iran)\",\r\n\t\t\"de-at,german (austria)\",\r\n\t\t\"de-ch,german (switzerland)\",\r\n\t\t\"de-lu,german (luxembourg)\",\r\n\t\t\"ee-tg,éwé (togo)\",\r\n\t\t\"en-ag,english (antigua & barbuda)\",\r\n\t\t\"en-ai,english (anguilla)\",\r\n\t\t\"en-at,english (austria)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-bi,english (burundi)\",\r\n\t\t\"en-ca,english (canada)\",\r\n\t\t\"en-ch,english (switzerland)\",\r\n\t\t\"en-dk,english (denmark)\",\r\n\t\t\"en-gb,english (united kingdom)\",\r\n\t\t\"en-ie,english (ireland)\",\r\n\t\t\"en-il,english (israel)\",\r\n\t\t\"es-419,spanish (latin america and the caribbean)\",\r\n\t\t\"es-ar,spanish (argentina)\",\r\n\t\t\"es-bz,spanish (belize)\",\r\n\t\t\"es-cl,spanish (chile)\",\r\n\t\t\"es-co,spanish (colombia)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-mx,spanish (mexico)\",\r\n\t\t\"es-pe,spanish (peru)\",\r\n\t\t\"es-ph,spanish (philippines)\",\r\n\t\t\"es-uy,spanish (uruguay)\",\r\n\t\t\"es-ve,spanish (venezuela)\",\r\n\t\t\"fa-af,persian (afghanistan)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"ff-adlm-gh,fulah (adlam ghana)\",\r\n\t\t\"ff-gh,fulah (ghana)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t\"fr-cm,french (cameroon)\",\r\n\t\t\"fr-dj,french (djibouti)\",\r\n\t\t\"fr-ma,french (morocco)\",\r\n\t\t\"ha-gh,hausa (ghana)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t\"hr-ba,croatian (bosnia & herzegovina)\",\r\n\t\t\"it-ch,italian (switzerland)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"kok-latn,konkani (latin)\",\r\n\t\t\"ks-deva,kashmiri (devanagari)\",\r\n\t\t\"kxv-telu,kuvi (telugu)\",\r\n\t\t\"lrc-iq,northern luri (iraq)\",\r\n\t\t\"ms-bn,malay (brunei)\",\r\n\t\t\"ms-id,malay (indonesia)\",\r\n\t\t\"ne-in,nepali (india)\",\r\n\t\t\"om-ke,oromo (kenya)\",\r\n\t\t\"pa-arab,punjabi (arabic)\",\r\n\t\t\"ps-pk,pashto (pakistan)\",\r\n\t\t\"pt-ao,portuguese (angola)\",\r\n\t\t\"pt-mo,portuguese (macau)\",\r\n\t\t\"qu-bo,quechua (bolivia)\",\r\n\t\t\"ro-md,romanian (moldova)\",\r\n\t\t\"sd-deva,sindhi (devanagari)\",\r\n\t\t\"se-fi,northern sami (finland)\",\r\n\t\t\"shi-latn,tachelhit (latin)\",\r\n\t\t\"so-ke,somali (kenya)\",\r\n\t\t\"sq-mk,albanian (macedonia)\",\r\n\t\t\"sr-ba,serbian (bosnia & herzegovina)\",\r\n\t\t\"sr-cyrl-me,serbian (cyrillic montenegro)\",\r\n\t\t\"sr-cyrl-xk,serbian (cyrillic kosovo)\",\r\n\t\t\"sr-latn,serbian (latin)\",\r\n\t\t\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\r\n\t\t\"sr-latn-me,serbian (latin montenegro)\",\r\n\t\t\"sr-latn-xk,serbian (latin kosovo)\",\r\n\t\t'st-ls,southern sotho',\r\n\t\t'sv-ax,swedish (åland islands)',\r\n\t\t\"sv-fi,swedish (finland)\",\r\n\t\t\"sw-cd,swahili (congo kinshasa)\",\r\n\t\t\"ta-lk,tamil (sri lanka)\",\r\n\t\t\"ti-er,tigrinya (eritrea)\",\r\n\t\t\"tr-cy,turkish (cyprus)\",\r\n\t\t\"ur-in,urdu (india)\",\r\n\t\t\"uz-af,uzbek (afghanistan)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"vai-latn,vai (latin)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t\"zh-hans-hk,chinese (simplified hong kong)\",\r\n\t\t// blink\r\n\t\t'ar-bh,arabic (bahrain)',\r\n\t\t'uz-arab,uzbek (arabic)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en,english']\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dtfdatetimestyle.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>dtf: date-&-timestyle</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 680px; max-width: 780px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t\tli.dates {margin-left: 10px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">datetimeformat: date-&-timestyle\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy with DateTimeFormat date-&-timeStyles.\r\n\t\t\tNumbers will vary depending on the timezone. To check all timezones, type <code>run_all()</code> in the console and\r\n\t\t\tgo make a cup of tea.</span>\r\n\t\t\t<span id=\"alert\"></span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<select name=\"timezones\" id=\"timezones\" onChange=\"run(`all`)\"><option></option></select>\r\n\t\t\t\t<span class=\"btn4 btnfirst\" onClick=\"run('next')\"> &nbsp; [ &#9654; ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tlocalesHashAll = '', // to compare min to\r\n\toData = {},\r\n\toDates = {},\r\n\ttzData = {},\r\n\ttzDataAll = {},\r\n\tisMax,\r\n\tisMin,\r\n\taTZ = [],\r\n\toCheck = {},\r\n\tisAuto = false,\r\n\tspacer = '<br><br>'\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == 'locales') {\r\n\t\tconsole.log(name +': ' + hash +'\\n'+ sDetail['locales'].join('\\n'))\r\n\t} else {\r\n\t\tconsole.log(name +': ' + hash +'\\n', sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\t// populate tz options once as well\r\n\t\ttry {\r\n\t\t\ttry {\r\n\t\t\t\taTZ = Intl.supportedValuesOf('timeZone')\r\n\t\t\t} catch(e) {} // supportedValuesOf not supported\r\n\t\t\t// add anything else that will work\r\n\t\t\tlet testDate = new Date()\r\n\t\t\tlet testTZ = []\r\n\t\t\tgTimezones.forEach(function(n) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tlet test = testDate.toLocaleString('en', {timeZone: n})\r\n\t\t\t\t\ttestTZ.push(n)\r\n\t\t\t\t} catch(e) {}\r\n\t\t\t})\r\n\t\t\t// is there anything in supported not in gTimezones\r\n\t\t\tlet aMissing = aTZ.filter(x => !gTimezones.includes(x))\r\n\t\t\tif (aMissing.length) {\r\n\t\t\t\tconsole.log('suported timezones missing in gTimezones\\n', aMissing)\r\n\t\t\t\tdom.alert.innerHTML = '<br>'+ s1 +'[alert] supported timezones missing in gTimezones'+ sc\r\n\t\t\t}\r\n\t\t\taTZ = aTZ.concat(testTZ)\r\n\t\t\taTZ = aTZ.filter(function(item, position) {return aTZ.indexOf(item) === position})\r\n\t\t\taTZ.sort()\r\n//aTZ =['America/Bogota','America/Detroit','America/Havana']\r\n\t\t\t// chrome keeps crashing the tab with run_all : break into bits and test manually\r\n\t\t\t//aTZ = aTZ.slice(400, 600) // 146 - 150\r\n\r\n\t\t\t// arrange into an object\r\n\t\t\tlet optTZ = {}\r\n\t\t\taTZ.forEach(function(n) {\r\n\t\t\t\tlet group, name\r\n\t\t\t\tif (n.includes('/')) {\r\n\t\t\t\t\tgroup = n.split('/')[0]\r\n\t\t\t\t\tname = n.slice(group.length +1) //+1 remove the '/'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tgroup = 'AAA' // tmp group name, we want it first alphabetically\r\n\t\t\t\t\tname = n\r\n\t\t\t\t}\r\n\t\t\t\tif (optTZ[group] == undefined) {optTZ[group] = []}\r\n\t\t\t\toptTZ[group].push(name)\r\n\t\t\t})\r\n\t\t\t//console.log(optTZ)\r\n\t\t\tlet aOptions = []\r\n\t\t\t// always add undefined as our first\r\n\t\t\tif (isFF) {aOptions.push(\"<optgroup label = '\"+ \"misc\" +\"'>\")}\r\n\t\t\taOptions.push(\"<option value = '\"+ \"undefined\" +\"'> \"+ \"undefined\" +\"</option>\")\r\n\t\t\tfor (const k of Object.keys(optTZ).sort()) {\r\n\t\t\t\tlet prefix = k+'/'\r\n\t\t\t\tif (k == 'AAA') {\r\n\t\t\t\t\tprefix = ''\r\n\t\t\t\t} else {\r\n\t\t\t\t\taOptions.push('<hr>')\r\n\t\t\t\t\tif (isFF) { // blink doesn't fully populate with optgroup\r\n\t\t\t\t\t\taOptions.push(\"<optgroup label = '\"+ k.toUpperCase() +\"'>\")\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\toptTZ[k].forEach(function(tz) {\r\n\t\t\t\t\taOptions.push(\"<option value = '\"+ prefix + tz +\"'> \"+ prefix + tz +\"</option>\")\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\tdom.timezones.innerHTML = aOptions.join('')\r\n\t\t} catch(e) {\r\n\t\t\tconsole.log(e)\r\n\t\t}\r\n\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(',')[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(',')[1]) ? str.split(',')[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DateTimeFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes('(') && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes('(')) {\r\n\t\t\t\t\tlet name0 = name.split('(')[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf('(') + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(')')\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +'('+ name1 + ')'+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +'<br>'+ ' '.repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +' '+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +': '+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +'LEGEND ['+ aLegend.length +']'+ sc +'<br><br>'\r\n\tdom.legend.innerHTML = header + aLegend.join('<br>')\r\n}\r\n\r\nfunction set_dates() {\r\n\t// if we use UTC then we can check the original date hasn't been altered\r\n\t\t// but we will have to end up testing more dates to cover specific days\r\n\r\n\t// timezones can be 14 hrs less or 12 hrs more but (IIUIC) our selected dates aren't hiting those instances\r\n\t\t// where it exceeds ±12 (or I lucked out) and we end up only needing two identical times on subsequent days\r\n\t// lets do 7 days per month and two times per day\r\n\t// this gives us a maximum result to start from\r\n\r\n\toDates = {\r\n\t\tJanA: new Date('2024-01-02T04:12:34.000Z'),\r\n\t\tJanB: new Date('2024-01-02T14:12:34.000Z'),\r\n\t\tJanC: new Date('2024-01-03T04:12:34.000Z'),\r\n\t\tJanD: new Date('2024-01-03T14:12:34.000Z'),\r\n\t\tJanE: new Date('2024-01-04T04:12:34.000Z'),\r\n\t\tJanF: new Date('2024-01-04T14:12:34.000Z'),\r\n\t\tJanG: new Date('2024-01-05T04:12:34.000Z'),\r\n\t\tJanH: new Date('2024-01-05T14:12:34.000Z'),\r\n\t\tJanI: new Date('2024-01-06T04:12:34.000Z'),\r\n\t\tJanJ: new Date('2024-01-06T14:12:34.000Z'),\r\n\t\tJanK: new Date('2024-01-07T04:12:34.000Z'),\r\n\t\tJanL: new Date('2024-01-07T14:12:34.000Z'),\r\n\t\tJanM: new Date('2024-01-08T04:12:34.000Z'),\r\n\t\tJanN: new Date('2024-01-08T14:12:34.000Z'),\r\n\r\n\t\tMayA: new Date('2024-05-02T04:12:34.000Z'),\r\n\t\tMayB: new Date('2024-05-02T14:12:34.000Z'),\r\n\t\tMayC: new Date('2024-05-03T04:12:34.000Z'),\r\n\t\tMayD: new Date('2024-05-03T14:12:34.000Z'),\r\n\t\tMayE: new Date('2024-05-04T04:12:34.000Z'),\r\n\t\tMayF: new Date('2024-05-04T14:12:34.000Z'),\r\n\t\tMayG: new Date('2024-05-05T04:12:34.000Z'),\r\n\t\tMayH: new Date('2024-05-05T14:12:34.000Z'),\r\n\t\tMayI: new Date('2024-05-06T04:12:34.000Z'),\r\n\t\tMayJ: new Date('2024-05-06T14:12:34.000Z'),\r\n\t\tMayK: new Date('2024-05-07T04:12:34.000Z'),\r\n\t\tMayL: new Date('2024-05-07T14:12:34.000Z'),\r\n\t\tMayM: new Date('2024-05-08T04:12:34.000Z'),\r\n\t\tMayN: new Date('2024-05-08T14:12:34.000Z'),\r\n\r\n\t\tJulA: new Date('2024-07-02T04:12:34.000Z'),\r\n\t\tJulB: new Date('2024-07-02T14:12:34.000Z'),\r\n\t\tJulC: new Date('2024-07-03T04:12:34.000Z'),\r\n\t\tJulD: new Date('2024-07-03T14:12:34.000Z'),\r\n\t\tJulE: new Date('2024-07-04T04:12:34.000Z'),\r\n\t\tJulF: new Date('2024-07-04T14:12:34.000Z'),\r\n\t\tJulG: new Date('2024-07-05T04:12:34.000Z'),\r\n\t\tJulH: new Date('2024-07-05T14:12:34.000Z'),\r\n\t\tJulI: new Date('2024-07-06T04:12:34.000Z'),\r\n\t\tJulJ: new Date('2024-07-06T14:12:34.000Z'),\r\n\t\tJulK: new Date('2024-07-07T04:12:34.000Z'),\r\n\t\tJulL: new Date('2024-07-07T14:12:34.000Z'),\r\n\t\tJulM: new Date('2024-07-08T04:12:34.000Z'),\r\n\t\tJulN: new Date('2024-07-08T14:12:34.000Z'),\r\n\r\n\t\tSepA: new Date('2024-09-02T04:12:34.000Z'),\r\n\t\tSepB: new Date('2024-09-02T14:12:34.000Z'),\r\n\t\tSepC: new Date('2024-09-03T04:12:34.000Z'),\r\n\t\tSepD: new Date('2024-09-03T14:12:34.000Z'),\r\n\t\tSepE: new Date('2024-09-04T04:12:34.000Z'),\r\n\t\tSepF: new Date('2024-09-04T14:12:34.000Z'),\r\n\t\tSepG: new Date('2024-09-05T04:12:34.000Z'),\r\n\t\tSepH: new Date('2024-09-05T14:12:34.000Z'),\r\n\t\tSepI: new Date('2024-09-06T04:12:34.000Z'),\r\n\t\tSepJ: new Date('2024-09-06T14:12:34.000Z'),\r\n\t\tSepK: new Date('2024-09-07T04:12:34.000Z'),\r\n\t\tSepL: new Date('2024-09-07T14:12:34.000Z'),\r\n\t\tSepM: new Date('2024-09-08T04:12:34.000Z'),\r\n\t\tSepN: new Date('2024-09-08T14:12:34.000Z'),\r\n\r\n\t\tNovA: new Date('2024-11-02T04:12:34.000Z'),\r\n\t\tNovB: new Date('2024-11-02T14:12:34.000Z'),\r\n\t\tNovC: new Date('2024-11-03T04:12:34.000Z'),\r\n\t\tNovD: new Date('2024-11-03T14:12:34.000Z'),\r\n\t\tNovE: new Date('2024-11-04T04:12:34.000Z'),\r\n\t\tNovF: new Date('2024-11-04T14:12:34.000Z'),\r\n\t\tNovG: new Date('2024-11-05T04:12:34.000Z'),\r\n\t\tNovH: new Date('2024-11-05T14:12:34.000Z'),\r\n\t\tNovI: new Date('2024-11-06T04:12:34.000Z'),\r\n\t\tNovJ: new Date('2024-11-06T14:12:34.000Z'),\r\n\t\tNovK: new Date('2024-11-07T04:12:34.000Z'),\r\n\t\tNovL: new Date('2024-11-07T14:12:34.000Z'),\r\n\t\tNovM: new Date('2024-11-08T04:12:34.000Z'),\r\n\t\tNovN: new Date('2024-11-08T14:12:34.000Z'),\r\n\r\n\t\tFebA: new Date('2024-02-02T04:12:34.000Z'),\r\n\t\tFebB: new Date('2024-02-02T14:12:34.000Z'),\r\n\t\tFebC: new Date('2024-02-03T04:12:34.000Z'),\r\n\t\tFebD: new Date('2024-02-03T14:12:34.000Z'),\r\n\t\tFebE: new Date('2024-02-04T04:12:34.000Z'),\r\n\t\tFebF: new Date('2024-02-04T14:12:34.000Z'),\r\n\t\tFebG: new Date('2024-02-05T04:12:34.000Z'),\r\n\t\tFebH: new Date('2024-02-05T14:12:34.000Z'),\r\n\t\tFebI: new Date('2024-02-06T04:12:34.000Z'),\r\n\t\tFebJ: new Date('2024-02-06T14:12:34.000Z'),\r\n\t\tFebK: new Date('2024-02-07T04:12:34.000Z'),\r\n\t\tFebL: new Date('2024-02-07T14:12:34.000Z'),\r\n\t\tFebM: new Date('2024-02-08T04:12:34.000Z'),\r\n\t\tFebN: new Date('2024-02-08T14:12:34.000Z'),\r\n\r\n\t\tMarA: new Date('2024-03-02T04:12:34.000Z'),\r\n\t\tMarB: new Date('2024-03-02T14:12:34.000Z'),\r\n\t\tMarC: new Date('2024-03-03T04:12:34.000Z'),\r\n\t\tMarD: new Date('2024-03-03T14:12:34.000Z'),\r\n\t\tMarE: new Date('2024-03-04T04:12:34.000Z'),\r\n\t\tMarF: new Date('2024-03-04T14:12:34.000Z'),\r\n\t\tMarG: new Date('2024-03-05T04:12:34.000Z'),\r\n\t\tMarH: new Date('2024-03-05T14:12:34.000Z'),\r\n\t\tMarI: new Date('2024-03-06T04:12:34.000Z'),\r\n\t\tMarJ: new Date('2024-03-06T14:12:34.000Z'),\r\n\t\tMarK: new Date('2024-03-07T04:12:34.000Z'),\r\n\t\tMarL: new Date('2024-03-07T14:12:34.000Z'),\r\n\t\tMarM: new Date('2024-03-08T04:12:34.000Z'),\r\n\t\tMarN: new Date('2024-03-08T14:12:34.000Z'),\r\n\r\n\t\tAprA: new Date('2024-04-02T04:12:34.000Z'),\r\n\t\tAprB: new Date('2024-04-02T14:12:34.000Z'),\r\n\t\tAprC: new Date('2024-04-03T04:12:34.000Z'),\r\n\t\tAprD: new Date('2024-04-03T14:12:34.000Z'),\r\n\t\tAprE: new Date('2024-04-04T04:12:34.000Z'),\r\n\t\tAprF: new Date('2024-04-04T14:12:34.000Z'),\r\n\t\tAprG: new Date('2024-04-05T04:12:34.000Z'),\r\n\t\tAprH: new Date('2024-04-05T14:12:34.000Z'),\r\n\t\tAprI: new Date('2024-04-06T04:12:34.000Z'),\r\n\t\tAprJ: new Date('2024-04-06T14:12:34.000Z'),\r\n\t\tAprK: new Date('2024-04-07T04:12:34.000Z'),\r\n\t\tAprL: new Date('2024-04-07T14:12:34.000Z'),\r\n\t\tAprM: new Date('2024-04-08T04:12:34.000Z'),\r\n\t\tAprN: new Date('2024-04-08T14:12:34.000Z'),\r\n\r\n\t\tJunA: new Date('2024-06-02T04:12:34.000Z'),\r\n\t\tJunB: new Date('2024-06-02T14:12:34.000Z'),\r\n\t\tJunC: new Date('2024-06-03T04:12:34.000Z'),\r\n\t\tJunD: new Date('2024-06-03T14:12:34.000Z'),\r\n\t\tJunE: new Date('2024-06-04T04:12:34.000Z'),\r\n\t\tJunF: new Date('2024-06-04T14:12:34.000Z'),\r\n\t\tJunG: new Date('2024-06-05T04:12:34.000Z'),\r\n\t\tJunH: new Date('2024-06-05T14:12:34.000Z'),\r\n\t\tJunI: new Date('2024-06-06T04:12:34.000Z'),\r\n\t\tJunJ: new Date('2024-06-06T14:12:34.000Z'),\r\n\t\tJunK: new Date('2024-06-07T04:12:34.000Z'),\r\n\t\tJunL: new Date('2024-06-07T14:12:34.000Z'),\r\n\t\tJunM: new Date('2024-06-08T04:12:34.000Z'),\r\n\t\tJunN: new Date('2024-06-08T14:12:34.000Z'),\r\n\r\n\t\tAugA: new Date('2024-08-02T04:12:34.000Z'),\r\n\t\tAugB: new Date('2024-08-02T14:12:34.000Z'),\r\n\t\tAugC: new Date('2024-08-03T04:12:34.000Z'),\r\n\t\tAugD: new Date('2024-08-03T14:12:34.000Z'),\r\n\t\tAugE: new Date('2024-08-04T04:12:34.000Z'),\r\n\t\tAugF: new Date('2024-08-04T14:12:34.000Z'),\r\n\t\tAugG: new Date('2024-08-05T04:12:34.000Z'),\r\n\t\tAugH: new Date('2024-08-05T14:12:34.000Z'),\r\n\t\tAugI: new Date('2024-08-06T04:12:34.000Z'),\r\n\t\tAugJ: new Date('2024-08-06T14:12:34.000Z'),\r\n\t\tAugK: new Date('2024-08-07T04:12:34.000Z'),\r\n\t\tAugL: new Date('2024-08-07T14:12:34.000Z'),\r\n\t\tAugM: new Date('2024-08-08T04:12:34.000Z'),\r\n\t\tAugN: new Date('2024-08-08T14:12:34.000Z'),\r\n\r\n\t\tOctA: new Date('2024-10-02T04:12:34.000Z'),\r\n\t\tOctB: new Date('2024-10-02T14:12:34.000Z'),\r\n\t\tOctC: new Date('2024-10-03T04:12:34.000Z'),\r\n\t\tOctD: new Date('2024-10-03T14:12:34.000Z'),\r\n\t\tOctE: new Date('2024-10-04T04:12:34.000Z'),\r\n\t\tOctF: new Date('2024-10-04T14:12:34.000Z'),\r\n\t\tOctG: new Date('2024-10-05T04:12:34.000Z'),\r\n\t\tOctH: new Date('2024-10-05T14:12:34.000Z'),\r\n\t\tOctI: new Date('2024-10-06T04:12:34.000Z'),\r\n\t\tOctJ: new Date('2024-10-06T14:12:34.000Z'),\r\n\t\tOctK: new Date('2024-10-07T04:12:34.000Z'),\r\n\t\tOctL: new Date('2024-10-07T14:12:34.000Z'),\r\n\t\tOctM: new Date('2024-10-08T04:12:34.000Z'),\r\n\t\tOctN: new Date('2024-10-08T14:12:34.000Z'),\r\n\r\n\t\tDecA: new Date('2024-12-02T04:12:34.000Z'),\r\n\t\tDecB: new Date('2024-12-02T14:12:34.000Z'),\r\n\t\tDecC: new Date('2024-12-03T04:12:34.000Z'),\r\n\t\tDecD: new Date('2024-12-03T14:12:34.000Z'),\r\n\t\tDecE: new Date('2024-12-04T04:12:34.000Z'),\r\n\t\tDecF: new Date('2024-12-04T14:12:34.000Z'),\r\n\t\tDecG: new Date('2024-12-05T04:12:34.000Z'),\r\n\t\tDecH: new Date('2024-12-05T14:12:34.000Z'),\r\n\t\tDecI: new Date('2024-12-06T04:12:34.000Z'),\r\n\t\tDecJ: new Date('2024-12-06T14:12:34.000Z'),\r\n\t\tDecK: new Date('2024-12-07T04:12:34.000Z'),\r\n\t\tDecL: new Date('2024-12-07T14:12:34.000Z'),\r\n\t\tDecM: new Date('2024-12-08T04:12:34.000Z'),\r\n\t\tDecN: new Date('2024-12-08T14:12:34.000Z'),\r\n\t}\r\n}\r\n\r\nconst run_main = (method = 'all', tz) => new Promise(resolve => {\r\n\tlet t0 = performance.now()\r\n\r\n\t// skip the auto all tests if I hardcoded them to speed up tests\r\n\tif (isAuto && method == 'all') {\r\n\t\tif (Object.keys(tzDataAll).length) {return resolve()}\r\n\t}\r\n\r\n\tif (isAuto) {\r\n\t\tlet step = method == 'all' ? 1 : 2\r\n\t\tlet tzstep = ((aTZ.indexOf(tz) + 1)+'').padStart(3) +'/' + aTZ.length\r\n\t\tdom.results.innerHTML = 'step '+ step +' of 2 ['+ method +'] '+ spacer + s16 + tzstep + sc + ' '+ tz \r\n\t}\r\n\r\n\t//console.log(isAuto, 'running', tz)\r\n\tif (!isAuto) {\r\n\t\tif (tz == undefined) {\r\n\t\t\ttz = dom.timezones.value\r\n\t\t} else {\r\n\t\t\ttry {\r\n\t\t\t\tdom.timezones.value = tz\r\n\t\t\t} catch(e) {\r\n\t\t\t\tconsole.log(e)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif (tz == 'undefined') {tz = undefined}\r\n\t//tz = 'America/Paramaribo' // temp\r\n\r\n\tlet dates = oDates\r\n\tlet testsAll = {\r\n\t\t'default': [\r\n\t\t\t[dates.JanE, ['F','L','M','S']],\r\n\t\t\t[dates.JanG, ['F','L','M','S']],\r\n\t\t\t[dates.MayA, ['F','L','M','S']],\r\n\t\t\t[dates.MayM, ['F','L','M','S']],\r\n\t\t\t[dates.JulF, ['F','L','M','S']],\r\n\t\t\t[dates.JulH, ['F','L','M','S']],\r\n\t\t\t[dates.SepC, ['F','L','M','S']],\r\n\t\t\t[dates.SepE, ['F','L','M','S']],\r\n\t\t\t[dates.NovD, ['F','L','M','S']],\r\n\t\t\t[dates.NovF, ['F','L','M','S']],\r\n\t\t\t// MAX: these add nothing\r\n\t\t\t/*\r\n\t\t\t// weekdays/times per month\r\n\t\t\t//[dates.JanA, ['F','L','M','S']],\r\n\t\t\t//[dates.JanB, ['F','L','M','S']],\r\n\t\t\t//[dates.JanC, ['F','L','M','S']],\r\n\t\t\t//[dates.JanD, ['F','L','M','S']],\r\n\t\t\t//[dates.JanF, ['F','L','M','S']],\r\n\t\t\t//[dates.JanH, ['F','L','M','S']],\r\n\t\t\t//[dates.JanI, ['F','L','M','S']],\r\n\t\t\t//[dates.JanJ, ['F','L','M','S']],\r\n\t\t\t//[dates.JanK, ['F','L','M','S']],\r\n\t\t\t//[dates.JanL, ['F','L','M','S']],\r\n\t\t\t//[dates.JanM, ['F','L','M','S']],\r\n\t\t\t//[dates.JanN, ['F','L','M','S']],\r\n\r\n\t\t\t//[dates.MayB, ['F','L','M','S']],\r\n\t\t\t//[dates.MayC, ['F','L','M','S']],\r\n\t\t\t//[dates.MayD, ['F','L','M','S']],\r\n\t\t\t//[dates.MayE, ['F','L','M','S']],\r\n\t\t\t//[dates.MayF, ['F','L','M','S']],\r\n\t\t\t//[dates.MayG, ['F','L','M','S']],\r\n\t\t\t//[dates.MayH, ['F','L','M','S']],\r\n\t\t\t//[dates.MayI, ['F','L','M','S']],\r\n\t\t\t//[dates.MayJ, ['F','L','M','S']],\r\n\t\t\t//[dates.MayK, ['F','L','M','S']],\r\n\t\t\t//[dates.MayL, ['F','L','M','S']],\r\n\t\t\t//[dates.MayN, ['F','L','M','S']],\r\n\r\n\t\t\t//[dates.JulA, ['F','L','M','S']],\r\n\t\t\t//[dates.JulB, ['F','L','M','S']],\r\n\t\t\t//[dates.JulC, ['F','L','M','S']],\r\n\t\t\t//[dates.JulD, ['F','L','M','S']],\r\n\t\t\t//[dates.JulE, ['F','L','M','S']],\r\n\t\t\t//[dates.JulG, ['F','L','M','S']],\r\n\t\t\t//[dates.JulI, ['F','L','M','S']],\r\n\t\t\t//[dates.JulJ, ['F','L','M','S']],\r\n\t\t\t//[dates.JulK, ['F','L','M','S']],\r\n\t\t\t//[dates.JulL, ['F','L','M','S']],\r\n\t\t\t//[dates.JulM, ['F','L','M','S']],\r\n\t\t\t//[dates.JulN, ['F','L','M','S']],\r\n\r\n\t\t\t//[dates.SepA, ['F','L','M','S']],\r\n\t\t\t//[dates.SepB, ['F','L','M','S']],\r\n\t\t\t//[dates.SepD, ['F','L','M','S']],\r\n\t\t\t//[dates.SepF, ['F','L','M','S']],\r\n\t\t\t//[dates.SepG, ['F','L','M','S']],\r\n\t\t\t//[dates.SepH, ['F','L','M','S']],\r\n\t\t\t//[dates.SepI, ['F','L','M','S']],\r\n\t\t\t//[dates.SepJ, ['F','L','M','S']],\r\n\t\t\t//[dates.SepK, ['F','L','M','S']],\r\n\t\t\t//[dates.SepL, ['F','L','M','S']],\r\n\t\t\t//[dates.SepM, ['F','L','M','S']],\r\n\t\t\t//[dates.SepN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.NovA, ['F','L','M','S']],\r\n\t\t\t[dates.NovB, ['F','L','M','S']],\r\n\t\t\t[dates.NovC, ['F','L','M','S']],\r\n\t\t\t[dates.NovE, ['F','L','M','S']],\r\n\t\t\t[dates.NovG, ['F','L','M','S']],\r\n\t\t\t[dates.NovH, ['F','L','M','S']],\r\n\t\t\t[dates.NovI, ['F','L','M','S']],\r\n\t\t\t[dates.NovJ, ['F','L','M','S']],\r\n\t\t\t[dates.NovK, ['F','L','M','S']],\r\n\t\t\t[dates.NovL, ['F','L','M','S']],\r\n\t\t\t[dates.NovM, ['F','L','M','S']],\r\n\t\t\t[dates.NovN, ['F','L','M','S']],\r\n\r\n\t\t\t// entire months\r\n\t\t\t[dates.FebA, ['F','L','M','S']],\r\n\t\t\t[dates.FebB, ['F','L','M','S']],\r\n\t\t\t[dates.FebC, ['F','L','M','S']],\r\n\t\t\t[dates.FebD, ['F','L','M','S']],\r\n\t\t\t[dates.FebE, ['F','L','M','S']],\r\n\t\t\t[dates.FebF, ['F','L','M','S']],\r\n\t\t\t[dates.FebG, ['F','L','M','S']],\r\n\t\t\t[dates.FebH, ['F','L','M','S']],\r\n\t\t\t[dates.FebI, ['F','L','M','S']],\r\n\t\t\t[dates.FebJ, ['F','L','M','S']],\r\n\t\t\t[dates.FebK, ['F','L','M','S']],\r\n\t\t\t[dates.FebL, ['F','L','M','S']],\r\n\t\t\t[dates.FebM, ['F','L','M','S']],\r\n\t\t\t[dates.FebN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.MarA, ['F','L','M','S']],\r\n\t\t\t[dates.MarB, ['F','L','M','S']],\r\n\t\t\t[dates.MarC, ['F','L','M','S']],\r\n\t\t\t[dates.MarD, ['F','L','M','S']],\r\n\t\t\t[dates.MarE, ['F','L','M','S']],\r\n\t\t\t[dates.MarF, ['F','L','M','S']],\r\n\t\t\t[dates.MarG, ['F','L','M','S']],\r\n\t\t\t[dates.MarH, ['F','L','M','S']],\r\n\t\t\t[dates.MarI, ['F','L','M','S']],\r\n\t\t\t[dates.MarJ, ['F','L','M','S']],\r\n\t\t\t[dates.MarK, ['F','L','M','S']],\r\n\t\t\t[dates.MarL, ['F','L','M','S']],\r\n\t\t\t[dates.MarM, ['F','L','M','S']],\r\n\t\t\t[dates.MarN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.AprA, ['F','L','M','S']],\r\n\t\t\t[dates.AprB, ['F','L','M','S']],\r\n\t\t\t[dates.AprC, ['F','L','M','S']],\r\n\t\t\t[dates.AprD, ['F','L','M','S']],\r\n\t\t\t[dates.AprE, ['F','L','M','S']],\r\n\t\t\t[dates.AprF, ['F','L','M','S']],\r\n\t\t\t[dates.AprG, ['F','L','M','S']],\r\n\t\t\t[dates.AprH, ['F','L','M','S']],\r\n\t\t\t[dates.AprI, ['F','L','M','S']],\r\n\t\t\t[dates.AprJ, ['F','L','M','S']],\r\n\t\t\t[dates.AprK, ['F','L','M','S']],\r\n\t\t\t[dates.AprL, ['F','L','M','S']],\r\n\t\t\t[dates.AprM, ['F','L','M','S']],\r\n\t\t\t[dates.AprN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.JunA, ['F','L','M','S']],\r\n\t\t\t[dates.JunB, ['F','L','M','S']],\r\n\t\t\t[dates.JunC, ['F','L','M','S']],\r\n\t\t\t[dates.JunD, ['F','L','M','S']],\r\n\t\t\t[dates.JunE, ['F','L','M','S']],\r\n\t\t\t[dates.JunF, ['F','L','M','S']],\r\n\t\t\t[dates.JunG, ['F','L','M','S']],\r\n\t\t\t[dates.JunH, ['F','L','M','S']],\r\n\t\t\t[dates.JunI, ['F','L','M','S']],\r\n\t\t\t[dates.JunJ, ['F','L','M','S']],\r\n\t\t\t[dates.JunK, ['F','L','M','S']],\r\n\t\t\t[dates.JunL, ['F','L','M','S']],\r\n\t\t\t[dates.JunM, ['F','L','M','S']],\r\n\t\t\t[dates.JunN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.AugA, ['F','L','M','S']],\r\n\t\t\t[dates.AugB, ['F','L','M','S']],\r\n\t\t\t[dates.AugC, ['F','L','M','S']],\r\n\t\t\t[dates.AugD, ['F','L','M','S']],\r\n\t\t\t[dates.AugE, ['F','L','M','S']],\r\n\t\t\t[dates.AugF, ['F','L','M','S']],\r\n\t\t\t[dates.AugG, ['F','L','M','S']],\r\n\t\t\t[dates.AugH, ['F','L','M','S']],\r\n\t\t\t[dates.AugI, ['F','L','M','S']],\r\n\t\t\t[dates.AugJ, ['F','L','M','S']],\r\n\t\t\t[dates.AugK, ['F','L','M','S']],\r\n\t\t\t[dates.AugL, ['F','L','M','S']],\r\n\t\t\t[dates.AugM, ['F','L','M','S']],\r\n\t\t\t[dates.AugN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.OctA, ['F','L','M','S']],\r\n\t\t\t[dates.OctB, ['F','L','M','S']],\r\n\t\t\t[dates.OctC, ['F','L','M','S']],\r\n\t\t\t[dates.OctD, ['F','L','M','S']],\r\n\t\t\t[dates.OctE, ['F','L','M','S']],\r\n\t\t\t[dates.OctF, ['F','L','M','S']],\r\n\t\t\t[dates.OctG, ['F','L','M','S']],\r\n\t\t\t[dates.OctH, ['F','L','M','S']],\r\n\t\t\t[dates.OctI, ['F','L','M','S']],\r\n\t\t\t[dates.OctJ, ['F','L','M','S']],\r\n\t\t\t[dates.OctK, ['F','L','M','S']],\r\n\t\t\t[dates.OctL, ['F','L','M','S']],\r\n\t\t\t[dates.OctM, ['F','L','M','S']],\r\n\t\t\t[dates.OctN, ['F','L','M','S']],\r\n\r\n\t\t\t[dates.DecA, ['F','L','M','S']],\r\n\t\t\t[dates.DecB, ['F','L','M','S']],\r\n\t\t\t[dates.DecC, ['F','L','M','S']],\r\n\t\t\t[dates.DecD, ['F','L','M','S']],\r\n\t\t\t[dates.DecE, ['F','L','M','S']],\r\n\t\t\t[dates.DecF, ['F','L','M','S']],\r\n\t\t\t[dates.DecG, ['F','L','M','S']],\r\n\t\t\t[dates.DecH, ['F','L','M','S']],\r\n\t\t\t[dates.DecI, ['F','L','M','S']],\r\n\t\t\t[dates.DecJ, ['F','L','M','S']],\r\n\t\t\t[dates.DecK, ['F','L','M','S']],\r\n\t\t\t[dates.DecL, ['F','L','M','S']],\r\n\t\t\t[dates.DecM, ['F','L','M','S']],\r\n\t\t\t[dates.DecN, ['F','L','M','S']],\r\n\t\t\t//*/\r\n\t\t],\r\n\t\t'ethiopic': [ // 76bada35 (without the post minimums changes to default)\r\n\t\t\t// adding on top of default\r\n\t\t\t\t// ignore short + long: tested: adds nothing\r\n\t\t\t\t// note we only need one month: tested with january (but we'll include all for the sake of it)\r\n\t\t\t\t// note we're not testing every weekday per month; just using base gregory dates\r\n\t\t\t[dates.JanE, ['F','M']],\r\n\t\t\t[dates.JanG, ['F','M']],\r\n\t\t\t[dates.MayA, ['F','M']],\r\n\t\t\t[dates.MayM, ['F','M']],\r\n\t\t\t[dates.JulF, ['F','M']],\r\n\t\t\t[dates.JulH, ['F','M']],\r\n\t\t\t[dates.SepC, ['F','M']],\r\n\t\t\t[dates.SepE, ['F','M']],\r\n\t\t\t[dates.NovD, ['F','M']],\r\n\t\t\t[dates.NovF, ['F','M']],\r\n\t\t],\r\n\t\t'japanese': [ // 1de2b69f\r\n\t\t\t// adding on top of default\r\n\t\t\t\t// ignore short + long: tested: adds nothing\r\n\t\t\t\t// note we're not testing every weekday per month; just using base gregory dates\r\n\t\t\t[dates.JanE, ['F','M']],\r\n\t\t\t[dates.JanG, ['F','M']],\r\n\t\t\t[dates.MayA, ['F','M']],\r\n\t\t\t[dates.MayM, ['F','M']],\r\n\t\t\t[dates.JulF, ['F','M']],\r\n\t\t\t[dates.JulH, ['F','M']],\r\n\t\t\t[dates.SepC, ['F','M']],\r\n\t\t\t[dates.SepE, ['F','M']],\r\n\t\t\t[dates.NovD, ['F','M']],\r\n\t\t\t[dates.NovF, ['F','M']],\r\n\t\t],\r\n\t\t// other calendars add nothing: tested:\r\n\t\t\t// buddhist, chinese, coptic, dangi, ethioaa, hebrew, indian, \r\n\t\t\t// islamic-civil, islamic-tbla, islamic-umalqura, iso8601, persian, roc\r\n\t\t\t// note we're not testing every weekday per month; just using base gregory dates\r\n\t}\r\n\r\n\t// matches\r\n\tlet testsMin = {\r\n\t\t// get minimum as we add each calendar\r\n\t\t\t// then once done, see if we can reduce anything from previous calendars\r\n\t\t'default': [\r\n\t\t\t// this is pretty much it for gregory: can't improve and seems to always cover everything\r\n\t\t\t[dates.JanE, ['FM','ML']],\r\n\t\t\t[dates.JanG, ['FM','ML']],\r\n\t\t\t//[dates.MayA, ['FM']], // we can drop May now we have ethiopic + japanese minimums\r\n\t\t\t//[dates.MayM, ['FM']],\r\n\t\t\t[dates.JulF, ['FM','ML']],\r\n\t\t\t[dates.JulH, ['FM','ML']],\r\n\t\t\t[dates.SepC, ['FM','SF']],\r\n\t\t\t[dates.SepE, ['FM','SF']],\r\n\t\t\t//[dates.NovD, ['FM','ML']], // we can drop Nov 'FM' now we have ethiopic + japanese minimums\r\n\t\t\t//[dates.NovF, ['FM','ML']],\r\n\t\t\t[dates.NovD, ['ML']],\r\n\t\t\t[dates.NovF, ['ML']],\r\n\t\t],\r\n\t\t'ethiopic': [\r\n\t\t\t// note: FM or MF combos don't cut it\r\n\t\t\t// looks like we need 1xF and 1xM across any dates(s): let's keep it simple with a single date\r\n\t\t\t[dates.JanE, ['F','M']],\r\n\t\t],\r\n\t\t'japanese': [\r\n\t\t\t// looks like this is all we need\r\n\t\t\t[dates.SepC, ['M']],\r\n\t\t\t[dates.NovD, ['M']], // required for blink 147\r\n\t\t],\r\n\t}\r\n\t//testsMin = {}\r\n\r\n\tlet tests = 'all' == method ? testsAll : testsMin\r\n\tlet oConst = {}\r\n\toData = {}\r\n\r\n\tlet oOptions = {\r\n\t\t'F': 'full',\r\n\t\t'M': 'medium',\r\n\t\t'L': 'long',\r\n\t\t'S': 'short',\r\n\t\t'FL': 'full_long',\r\n\t\t'FM': 'full_medium',\r\n\t\t'FS': 'full_short',\r\n\t\t'LF': 'long_full',\r\n\t\t'LS': 'long_short',\r\n\t\t'LM': 'long_medium',\r\n\t\t'MF': 'medium_full',\r\n\t\t'ML': 'medium_long',\r\n\t\t'MS': 'medium_short',\r\n\t\t'SF': 'short_full',\r\n\t\t'SL': 'short_long',\r\n\t\t'SM': 'short_medium',\r\n\t}\r\n\r\n\t//aLocales = ['en','fr']\r\n\ttry {\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oTempData = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(cal) {\r\n\t\t\t\tlet src = cal\r\n\t\t\t\t// calendar in options really slows this down\r\n\t\t\t\tif ('all' == method) {\r\n\t\t\t\t\tif ('default' == cal) {\r\n\t\t\t\t\t\ttry {oConst.DaF = Intl.DateTimeFormat(code, {dateStyle: 'full', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaM = Intl.DateTimeFormat(code, {dateStyle: 'medium', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaL = Intl.DateTimeFormat(code, {dateStyle: 'long', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaS = Intl.DateTimeFormat(code, {dateStyle: 'short', timeStyle: 'short', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t// add calendar default name for visual info: not required for entropy\r\n\t\t\t\t\t\t//cal += '-'+ oConst.DaF.resolvedOptions().calendar\r\n\t\t\t\t\t\t/* \r\n\t\t\t\t\t\t\tth = buddhist\r\n\t\t\t\t\t\t\tar-sa = islamic-umalqura\r\n\t\t\t\t\t\t\tckb-ir, fa, fa-af, lrc, msn, ps, uz-arab = persian\r\n\t\t\t\t\t\t*/\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttry {oConst.DaF = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'full', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaM = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'medium', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaL = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'long', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaS = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'short', timeStyle: 'short', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif ('default' == cal) {\r\n\t\t\t\t\t\t/* debugging\r\n\t\t\t\t\t\ttry {oConst.DaF = Intl.DateTimeFormat(code, {dateStyle: 'full', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaL = Intl.DateTimeFormat(code, {dateStyle: 'long', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaM = Intl.DateTimeFormat(code, {dateStyle: 'medium', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaS = Intl.DateTimeFormat(code, {dateStyle: 'short', timeStyle: 'short', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaLS = Intl.DateTimeFormat(code, {dateStyle: 'long', timeStyle: 'short', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaMS = Intl.DateTimeFormat(code, {dateStyle: 'medium', timeStyle: 'short', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaFL = Intl.DateTimeFormat(code, {dateStyle: 'full', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaLF = Intl.DateTimeFormat(code, {dateStyle: 'long', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaSL = Intl.DateTimeFormat(code, {dateStyle: 'short', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t//*/\r\n\t\t\t\t\t\ttry {oConst.DaFM = Intl.DateTimeFormat(code, {dateStyle: 'full', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaML = Intl.DateTimeFormat(code, {dateStyle: 'medium', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaSF = Intl.DateTimeFormat(code, {dateStyle: 'short', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t// add calendar default name for visual info: not required for entropy\r\n\t\t\t\t\t\t//cal += '-'+ oConst.DaFM.resolvedOptions().calendar\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttry {oConst.DaF = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'full', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\ttry {oConst.DaM = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'medium', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\r\n\t\t\t\t\t\t//try {oConst.DaMF = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'medium', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t//try {oConst.DaFM = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'full', timeStyle: 'medium', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t//try {oConst.DaMF = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'medium', timeStyle: 'full', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t\t//try {oConst.DaML = Intl.DateTimeFormat(code, {calendar: cal, dateStyle: 'medium', timeStyle: 'long', timeZone: tz})} catch(e) {}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\toTempData[cal] = {}\r\n\r\n\t\t\t\tlet array = tests[src]\r\n\t\t\t\tarray.forEach(function(item) {\r\n\t\t\t\t\tlet date = item[0]\r\n\t\t\t\t\tlet aStyles = item[1]\r\n\t\t\t\t\taStyles.forEach(function(opt) {\r\n\t\t\t\t\t\tlet k = oOptions[opt] // match TZP with friendly names\r\n\t\t\t\t\t\tif (oTempData[cal][k] == undefined) {oTempData[cal][k] = []}\r\n\t\t\t\t\t\tlet constructor = 'Da'+ opt\r\n\t\t\t\t\t\tlet formatter = oConst[constructor]\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\toTempData[cal][k].push(formatter.format(date))\r\n\t\t\t\t\t\t} catch(e) {\r\n\t\t\t\t\t\t\toTempData[cal][k].push('error')\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\t//console.log(oTempData)\r\n\r\n\t\t\tlet hash = mini(oTempData)\r\n\t\t\tif (oData[hash] == undefined) {\r\n\t\t\t\toData[hash] = {}\r\n\t\t\t\toData[hash]['locales'] = [code]\r\n\t\t\t\toData[hash]['data'] = {}\r\n\t\t\t\tfor (const k of Object.keys(oTempData).sort()) {\r\n\t\t\t\t\toData[hash]['data'][k] = oTempData[k]\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\toData[hash]['locales'].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// perf\r\n\t\tif (!isAuto) {\r\n\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +' ms'\r\n\t\t\t//console.log(oData)\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k]['locales'])\r\n\t\t\tlet localeCount = oData[k]['locales'].length\r\n\t\t\tif (!isAuto) {\r\n\t\t\t\tlet str = ''\r\n\t\t\t\tfor (const p of Object.keys(oData[k]['data'])) {\r\n\t\t\t\t\tstr += s14 + p +': '+ sc\r\n\t\t\t\t\tfor (const style of Object.keys(oData[k]['data'][p]).sort()) {\r\n\t\t\t\t\t\tlet linedata = \"<li class='dates'>\"+ oData[k][\"data\"][p][style].join(\"</li><li class='dates'>\") + \"</li>\"\r\n\t\t\t\t\t\tstr += \"<li>\"+ s16 + style.toLowerCase() +\": \"+ sc + linedata +\"</li>\"\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"].join(\", \") +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = '', localesBtn = ''\r\n\t\tlocaleGroups.sort()\r\n\t\tif (!isAuto) {\r\n\t\t\tsDetail['results'] = oData\r\n\t\t\tresultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\t\tsDetail['locales'] = localeGroups\r\n\t\t\tlocalesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\t}\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = ''\r\n\t\tif ('all' == method) {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// don't notate anything: the numbers can change depending on your timezone\r\n\t\t} else if ('min' == method) {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\r\n\t\tif (tz !== undefined) {\r\n\t\t\tif (tzData[tz] == undefined) {\r\n\t\t\t\ttzData[tz] = {}\r\n\t\t\t}\r\n\t\t\tlet buckets = localeGroups.length\r\n\t\t\tif ('all' == method) {\r\n\t\t\t\tif (isMax == undefined) {\r\n\t\t\t\t\tisMax = buckets\r\n\t\t\t\t\tisMin = buckets\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (buckets > isMax) {isMax = buckets}\r\n\t\t\t\t\tif (buckets < isMin) {isMin = buckets}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\ttzData[tz][method] = [buckets, localesHash]\r\n\t\t}\r\n\r\n\t\tif (!isAuto) {\r\n\t\t\t// display\r\n\t\t\tlet display = s8 +'timeZone: '+ sc + tz\r\n\t\t\t\t+ spacer + s4 + localeGroups.length + sc +' from '+ s4 + aLocales.length + sc\r\n\t\t\t\t+ spacer + s16 +'results: '+ sc + resultsHash +' ' + resultsBtn +'<br>'\r\n\t\t\t\t+ s12 +'locales: '+ sc + localesHash +' '+ localesBtn + localesMatch + spacer\r\n\t\t\tdom.results.innerHTML = display + '<br>' + displaylist.join('<br>')\r\n\t\t}\r\n\t\treturn resolve()\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +': '+ sc + e.message\r\n\t\treturn resolve()\r\n\t}\r\n})\r\n\r\nfunction run_summary() {\r\n\t// clear isAuto\r\n\tisAuto = false\r\n\tlet aCheck = []\r\n\tfor (const k of Object.keys(tzData).sort()) {\r\n\t\taCheck.push(k +': '+ tzData[k].all[0])\r\n\t\tif (tzData[k].min == undefined) {\r\n\t\t\toCheck['missing'].push(k)\r\n\t\t} else {\r\n\t\t\tlet strAll = tzData[k]['all'].join(', ')\r\n\t\t\tlet strMin = tzData[k]['min'].join(', ')\r\n\t\t\tif (strAll == strMin) {\r\n\t\t\t\toCheck['match'].push(k +': ' + strAll)\r\n\t\t\t} else {\r\n\t\t\t\tstrAll = s14 + tzData[k]['all'][0] + sc +' ['+ tzData[k]['all'][1] +']'\r\n\t\t\t\tstrMin = s14 + tzData[k]['min'][0] + sc +' ['+ tzData[k]['min'][1] +']'\r\n\t\t\t\toCheck['mismatch'].push(k +': '+ strAll +' vs ' + strMin)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\toCheck['max'] = isMax\r\n\toCheck['min'] = isMin\r\n\r\n\tlet display = []\r\n\tlet count = oCheck['match'].length\r\n\tlet expected = aTZ.length\r\n\tdisplay.push(s14 +'MATCH: '+ sc + count +'/'+ expected)\r\n\tcount = oCheck['missing'].length\r\n\tif (oCheck['missing'].length) {\r\n\t\tdisplay.push('<br>' + s14 +'MISSING: '+ sc + count +'<br>')\r\n\t\tdisplay.push(oCheck['missing'].join('<br>'))\r\n\t}\r\n\tcount = oCheck['mismatch'].length\r\n\tif (oCheck['mismatch'].length) {\r\n\t\tdisplay.push('<br>' + s14 +'MISMATCH: '+ sc + count +'<br>')\r\n\t\tdisplay.push(oCheck['mismatch'].join('<br>'))\r\n\t}\r\n\tdisplay.push('<br>' + s14 +'RANGE: '+ sc + isMin +' - '+ isMax)\r\n\tdom.results.innerHTML = s16 + 'ALL vs MIN'  + sc + spacer\r\n\t\t+ display.join('<br>')\r\n\t\t+ spacer +'<hr><br>'+ s16 +'ENTROPY:'+ sc +' (number of locale results per timezone)' + sc\r\n\t\t\t+ ' '+ mini(aCheck) + spacer + aCheck.join('<br>') + spacer\r\n\tconsole.log(oCheck)\r\n\tconsole.log(mini(aCheck), '\\n', aCheck)\r\n}\r\n\r\nconst run_both = (tz) => new Promise(resolve => {\r\n\tsetTimeout(function(){\r\n\t\tPromise.all([\r\n\t\t\trun_main('all', tz)\r\n\t\t]).then(function(){\r\n\t\t\tsetTimeout(function(){\r\n\t\t\t\tPromise.all([\r\n\t\t\t\t\trun_main('min', tz)\r\n\t\t\t\t]).then(function(){\r\n\t\t\t\t\treturn resolve()\r\n\t\t\t\t})\r\n\t\t\t}, 1)\r\n\t\t})\r\n\t}, 1)\r\n})\r\n\r\nfunction run_all() {\r\n\t// prevent user runs\r\n\tisAuto = true\r\n\t// reset buttons\r\n\tsetBtn()\r\n\t// clear displays\r\n\tdom.perf = ''\r\n\tdom.results = ''\r\n\t// reset data\r\n\toCheck = {'match': [], 'max': 0, 'min': 0, 'mismatch': [], 'missing': [] }\r\n\ttzData = {}\r\n\tisMax = undefined\r\n\tisMin = undefined\r\n\r\n\t// speed up tesys by using a hardcoded 'all'\r\n\tif (Object.keys(tzDataAll).length) {\r\n\t\tfor (const k of Object.keys(tzDataAll).sort()) {\r\n\t\t\ttzData[k] = {}\r\n\t\t\ttzData[k]['all'] = tzDataAll[k]['all']\r\n\t\t}\r\n\t}\r\n\t// loop timezones\r\n\tfor (let i=0; i < aTZ.length; i++) {\r\n\t\tlet tz = aTZ[i]\r\n\t\tPromise.all([\r\n\t\t\trun_both(tz)\r\n\t\t]).then(function(){\r\n\t\t\tif (i == (aTZ.length - 1)) {\r\n\t\t\t\trun_summary()\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isAuto) {\r\n\t\t// do not allow user clicks to interfer with an auto run\r\n\t\treturn\r\n\t}\r\n\tif (isSupported) {\r\n\t\tif (method == 'next') {\r\n\t\t\tmethod = 'all'\r\n\t\t\tlet target = dom.timezones\r\n\t\t\ttarget.selectedIndex++\r\n\t\t\tif (target.value == '') {target.selectedIndex++} // end of list, return to top\r\n\t\t}\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = ''\r\n\t\tdom.results = ''\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName('btn8')\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add('btn4')\r\n\t\titems[i].classList.remove('btn8')\r\n\t}\r\n\tif (!isAuto) {\r\n\t\t// set btn\r\n\t\tlet el = document.getElementById('b'+ method)\r\n\t\tel.classList.add('btn8')\r\n\t\tel.classList.remove('btn4')\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF58+\r\n\t\tlet formatF = new Intl.DateTimeFormat('en', {dateStyle: 'full', timeStyle: 'full'})\r\n\t\tlet formatS = new Intl.DateTimeFormat('en', {dateStyle: 'short', timeStyle: 'short'})\r\n\t\tlet testDate = new Date('January 5, 2024')\r\n\t\tif (formatF.format(testDate) !== formatS.format(testDate)) {\r\n\t\t\tisSupported = true\r\n\t\t} else {\r\n\t\t\tdom.results.innerHTML = s4 + 'dateStyle | timeStyle:' + sc + ' not supported'\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +':' + sc +' '+ e.message\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n'af-na,afrikaans (namibia)',\r\n'ar-ae,arabic (united arabic emirates)',\r\n'ar-dz,arabic (algeria)',\r\n'ar-il,arabic (israel)',\r\n'ar-iq,arabic (iraq)',\r\n'ar-km,arabic (cosmoros)',\r\n'ar-ma,arabic (morocco)',\r\n'ar-mr,arabic (mauritania)',\r\n'ar-sa,arabic (saudi arabia)',\r\n'az-cyrl,azerbaijani (cyrillic)',\r\n'bn-in,bengali (india)',\r\n'bo-in,tibetan (india)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'ckb-ir,central kurdish (iran)',\r\n'de-at,german (austria)',\r\n'de-ch,german (switzerland)',\r\n'ee-tg,éwé (togo)',\r\n'en-001,english',\r\n'en-150,english (europe)',\r\n'en-ae,english (united arab emirates)',\r\n'en-au,english (australia)',\r\n'en-be,english (belgium)',\r\n'en-bi,english (burundi)',\r\n'en-bw,english (botswana)',\r\n'en-bz,english (belize)',\r\n'en-ca,english (canada)',\r\n'en-ch,english (switzerland)',\r\n'en-dk,english (denmark)',\r\n'en-er,english (eritrea)',\r\n'en-fi,english (finland)',\r\n'en-gb,english (united kingdom)',\r\n'en-gu,english (guam)',\r\n'en-gy,english (guyana)',\r\n'en-hk,english (hong kong)',\r\n'en-ie,english (ireland)',\r\n'en-il,english (israel)',\r\n'en-in,english (india)',\r\n'en-ke,english (kenya)',\r\n'en-mh,english (marshall islands)',\r\n'en-mo,english (macau)',\r\n'en-mt,english (malta)',\r\n'en-my,english (malaysia)',\r\n'en-nz,english (new zealand)',\r\n'en-pk,english (pakistan)',\r\n'en-se,english (sweden)',\r\n'en-sg,english (singapore)',\r\n'en-za,english (south africa)',\r\n'en-zw,english (zimbabwe)',\r\n'es-ar,spanish (argentina)',\r\n'es-419,spanish (latin america and the caribbean)',\r\n'es-bo,spanish (bolivia)',\r\n'es-br,spanish (brazil)',\r\n'es-cl,spanish (chile)',\r\n'es-co,spanish (colombia)',\r\n'es-do,spanish (dominican republic)',\r\n'es-ec,spanish (ecuador)',\r\n'es-gt,spanish (guatemala)',\r\n'es-hn,spanish (honduras)',\r\n'es-mx,spanish (mexico)',\r\n'es-pa,spanish (panama)',\r\n'es-pe,spanish (peru)',\r\n'es-ph,spanish (philippines)',\r\n'es-py,spanish (paraguay)',\r\n'es-us,spanish (united states)',\r\n'es-uy,spanish (uruguay)',\r\n'es-ve,spanish (venezuela)',\r\n'fa-af,persian (afghanistan)',\r\n'ff-adlm,fulah (adlam)',\r\n'ff-adlm-gh,fulah (adlam ghana)',\r\n'ff-gh,fulah (ghana)',\r\n'ff-mr,fulah (mauritania)',\r\n'fr-be,french (belgium)',\r\n'fr-ca,french (canada)',\r\n'fr-ch,french (switzerland)',\r\n'fr-dj,french (djibouti)',\r\n'fr-gf,french (french guiana)',\r\n'fr-ma,french (morocco)',\r\n'fr-ml,french (mali)',\r\n'ha-gh,hausa (ghana)',\r\n'hi-latn,hindi (latin)',\r\n'hr-ba,croatian (bosnia & herzegovina)',\r\n'it-ch,italian (switzerland)',\r\n'kk-cn,kazakh (china)',\r\n'ko-kp,korean (north korea)',\r\n'kok-in,konkani (india)',\r\n'kok-latn,konkani (latin)',\r\n'ks-deva,kashmiri (devanagari)',\r\n'kxv-telu,kuvi (telugu)',\r\n'lrc-iq,northern luri (iraq)',\r\n'ms-bn,malay (brunei)',\r\n'ms-id,malay (indonesia)',\r\n'ne-in,nepali (india)',\r\n'nl-be,dutch (belgium)',\r\n'nl-sr,dutch (suriname)',\r\n'om-ke,oromo (kenya)',\r\n'pa-arab,punjabi (arabic)',\r\n'ps-pk,pashto (pakistan)',\r\n'pt-ao,portuguese (angola)',\r\n'pt-ch,portuguese (switzerland)',\r\n'pt-mo,portuguese (macau)',\r\n'qu-bo,quechua (bolivia)',\r\n'qu-ec,quechua (ecuador)',\r\n'sd-deva,sindhi (devanagari)',\r\n'se-fi,northern sami (finland)',\r\n'shi-latn,tachelhit (latin)',\r\n'so-ke,somali (kenya)',\r\n'sq-mk,albanian (macedonia)',\r\n'sr-ba,serbian (bosnia & herzegovina)',\r\n'sr-cyrl-me,serbian (cyrillic montenegro)',\r\n'sr-cyrl-xk,serbian (cyrillic kosovo)',\r\n'sr-latn,serbian (latin)',\r\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\r\n'sr-latn-me,serbian (latin montenegro)',\r\n'sr-latn-xk,serbian (latin kosovo)',\r\n'st-ls,southern sotho',\r\n'sv-ax,swedish (åland islands)',\r\n'sv-fi,swedish (finland)',\r\n'sw-ke,swahili (kenya)',\r\n'ta-lk,tamil (sri lanka)',\r\n'ta-my,tamil (malaysia)',\r\n'ti-er,tigrinya (eritrea)',\r\n'tr-cy,turkish (cyprus)',\r\n'ur-in,urdu (india)',\r\n'uz-arab,uzbek (arabic)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'vai-latn,vai (latin)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-cn,cantonese (china)',\r\n'zh-hans-hk,chinese (simplified hong kong)',\r\n'zh-hans-mo,chinese (simplified macau)',\r\n'zh-my,chinese (malaysia)',\r\n\t\t// blink\r\n\t\t'ar-bh,arabic (bahrain)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['ar-sa,arabic (saudi arabia)','en,english','fa,persian','th,thai',]\r\n\t//list = ['en,english']\r\n\t//list = ['pt-ao','pt-ch'] // e.g. split by europe/vatican\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tset_dates()\r\n\t\tsetBtn('all')\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main('all')\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dtfdayperiod.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>dft: dayperiod</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">datetimeformat: dayperiod\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy: from three\r\n\t\t\toptions (<code>narrow</code>, <code>short</code>, <code>long</code>) and five times\r\n\t\t\t(<code>8:00</code>, <code>12:00</code>, <code>15:00</code>, <code>18:00</code>, <code>22:00</code>).\r\n\t\t\tClick custom to test any configuration.</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"bnarrow\" class=\"btn4 btnfirst\" onClick=\"run('narrow')\">[ N ]</span>\r\n\t\t\t\t<span id=\"bshort\" class=\"btn4 btn\" onClick=\"run('short')\">[ S ]</span>\r\n\t\t\t\t<span id=\"blong\" class=\"btn4 btn\" onClick=\"run('long')\">[ L ]</span>\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bcustom\" class=\"btn4 btn\" onClick=\"run('custom')\">[ CUSTOM ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span class=\"hidden\" id =\"customoptions\">\r\n\t\t\t\t\t<p class=\"mono spaces pad\">NARROW: 8 <input type=\"checkbox\" id=\"narrow08\"> 12 <input type=\"checkbox\" id=\"narrow12\"> 15 <input type=\"checkbox\" id=\"narrow15\"> 18 <input type=\"checkbox\" id=\"narrow18\"> 22 <input type=\"checkbox\" id=\"narrow22\"></p>\r\n\t\t\t\t\t<p class=\"mono spaces pad\"> SHORT: 8 <input type=\"checkbox\" id=\"short08\"> 12 <input type=\"checkbox\" id=\"short12\"> 15 <input type=\"checkbox\" id=\"short15\"> 18 <input type=\"checkbox\" id=\"short18\"> 22 <input type=\"checkbox\" id=\"short22\"></p>\r\n\t\t\t\t\t<p class=\"mono spaces pad\">  LONG: 8 <input type=\"checkbox\" id=\"long08\"> 12 <input type=\"checkbox\" id=\"long12\"> 15 <input type=\"checkbox\" id=\"long15\"> 18 <input type=\"checkbox\" id=\"long18\"> 22 <input type=\"checkbox\" id=\"long22\"></p>\r\n\t\t\t\t<span class=\"btn4 btnfirst\" onClick=\"reset_custom('clear')\">[ CLEAR ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset_custom('min')\">[ RESET MIN ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset_custom('all')\">[ ALL ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"run_custom()\">[ RUN ]</span>\r\n\t\t\t\t\t<br><br><hr><br>\r\n\t\t\t\t</span>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare custom to\r\n\r\nconst oDays = {\r\n\t\"08\": new Date(\"2019-01-30T08:00:00Z\"),\r\n\t\"12\": new Date(\"2019-01-30T12:00:00Z\"),\r\n\t\"15\": new Date(\"2019-01-30T15:00:00Z\"),\r\n\t\"18\": new Date(\"2019-01-30T18:00:00Z\"),\r\n\t\"22\": new Date(\"2019-01-30T22:00:00Z\")\r\n}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction reset_custom(type) {\r\n\t// check/uncheck everything\r\n\tlet checkState = (type == \"all\")\r\n\tlet styles = [\"narrow\",\"short\",\"long\"],\r\n\t\ttimes = [\"08\",12,15,18,22]\r\n\tstyles.forEach(function(s){\r\n\t\ttimes.forEach(function(t){\r\n\t\t\tdocument.getElementById(s +t).checked = checkState\r\n\t\t})\r\n\t})\r\n\tif (type == \"min\") {\r\n\t\t// gecko min preset\r\n\t\tdom.narrow08.checked = true\r\n\t\tdom.long08.checked = true\r\n\t\tdom.short12.checked = true\r\n\t\tdom.narrow15.checked = true\r\n\t\tdom.short18.checked = true\r\n\r\n\t\t// assuming 110 changes stick from ICU 72: 1792775\r\n\t\tif (isFF && \"object\" !== typeof ondeviceorientationabsolute) {\r\n\t\t\t// FF90-109\r\n\t\t\tdom.short22.checked = true\r\n\t\t} else {\r\n\t\t\t// FF110+, also chrome since at least 115\r\n\t\t\tdom.short15.checked = true\r\n\t\t\tdom.long22.checked = true\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet test = Intl.DateTimeFormat.supportedLocalesOf([code])\r\n\t\t\tif (test.length) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet\theader = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction get_dayperiod(date, code, option) {\r\n\t// always use h12\r\n\treturn new Intl.DateTimeFormat(code, {hourCycle: \"h12\", timeZone: 'UTC', dayPeriod: option}).format(date)\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\ttry {\r\n\t\tlet styles = [\"narrow\",\"short\",\"long\"]\r\n\t\tlet times = [\"08\",12,15,18,22]\r\n\t\tlet oOptions = {}\r\n\t\t\r\n\t\t// select what to test\r\n\t\tif (method == \"custom\") {\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttimes.forEach(function(t){\r\n\t\t\t\t\toOptions[s + t] = document.getElementById(s +t).checked\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\tif (method == \"narrow\") {styles = [\"narrow\"]}\r\n\t\t\tif (method == \"short\") {styles = [\"short\"]}\r\n\t\t\tif (method == \"long\") {styles = [\"long\"]}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttimes.forEach(function(t){\r\n\t\t\t\t\toOptions[s + t] = true\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t}\r\n\t\t// test: 3 methods x 5 dayPeriods\r\n\t\taLocales.forEach(function(code) {\r\n\r\n\t\t\t// set our three options once per code\r\n\t\t\tlet dteS = new Intl.DateTimeFormat(code, {hourCycle: \"h12\", timeZone: 'UTC', dayPeriod: \"short\"}),\r\n\t\t\t\tdteN = new Intl.DateTimeFormat(code, {hourCycle: \"h12\", timeZone: 'UTC', dayPeriod: \"narrow\"}),\r\n\t\t\t\tdteL = new Intl.DateTimeFormat(code, {hourCycle: \"h12\", timeZone: 'UTC', dayPeriod: \"long\"})\r\n\r\n\t\t\tlet oStyles = {\"narrow\": [], \"short\": [], \"long\": []}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttimes.forEach(function(t){\r\n\t\t\t\t\tif (oOptions[s + t] == true) {\r\n\t\t\t\t\t\tif (s == \"short\") {\r\n\t\t\t\t\t\t\toStyles[s].push(dteS.format(oDays[t]))\r\n\t\t\t\t\t\t} else if (s ==\"narrow\") {\r\n\t\t\t\t\t\t\toStyles[s].push(dteN.format(oDays[t]))\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\toStyles[s].push(dteL.format(oDays[t]))\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\toTempData[hash][\"long\"] = oStyles[\"long\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"short\"] = oStyles[\"short\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"narrow\"] = oStyles[\"narrow\"].join(\" | \")\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet strN = oData[k][\"narrow\"],\r\n\t\t\t\tstrS = oData[k][\"short\"],\r\n\t\t\t\tstrL = oData[k][\"long\"],\r\n\t\t\t\tlocaleCount = oData[k][\"locales\"].split(\",\").length\r\n\t\t\tif (strN.length) {strN = \"<li>\"+ s16 +\"N: \"+ sc + strN +\"</li>\"}\r\n\t\t\tif (strS.length) {strS = \"<li>\"+ s16 +\"S: \"+ sc + strS +\"</li>\"}\r\n\t\t\tif (strL.length) {strL = \"<li>\"+ s16 +\"L: \"+ sc + strL +\"</li>\"}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\" + strN + strS + strL\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"2fb81fcd\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"5acc9006\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"aaff42db\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"8d237b4d\") { // FF147+: 227\r\n\t\t\t\t} else if (localesHash == \"0d1d591e\") { // FF140+: 225\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"custom\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \"+ resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\tlet element = dom.customoptions\r\n\r\n\t\tif (method == \"custom\") {\r\n\t\t\t// unhide custom section\r\n\t\t\telement.classList.remove(\"hidden\")\r\n\r\n\t\t} else {\r\n\t\t\t// hide custom\r\n\t\t\telement.classList.add(\"hidden\")\r\n\r\n\t\t\tdom.results = \"running test...\"\r\n\t\t\t// delay so users see change and allow paint\r\n\t\t\tsetTimeout(function() {\r\n\t\t\t\trun_main(method)\r\n\t\t\t}, 1)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction run_custom() {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"running test...\"\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"custom\")\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction run_test() {\r\n\tlet A = get_dayperiod(new Date(\"2019-01-30T08:00:00Z\"), \"en\", \"long\")\r\n\tlet B = get_dayperiod(new Date(\"2019-01-30T12:00:00Z\"), \"en\", \"long\")\r\n\tif (A == B) {\r\n\t\tisSupported = false\r\n\t\tdom.results.innerHTML = s4 + \"dayPeriod:\" + sc + \" not supported\"\r\n\t} else {\r\n\t\tisSupported = true\r\n\t\tsetBtn(\"all\")\r\n\t\tdom.results = \"running test...\"\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\treset_custom(\"min\")\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"az-cyrl,azerbaijani (cyrillic)\",\r\n\t\t\"bn-in,bengali (india)\",\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-ca,english (canada)\",\r\n\t\t\"es-ar,spanish (argentina)\",\r\n\t\t\"es-co,spanish (colombia)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-mx,spanish (mexico)\",\r\n\t\t\"es-pa,spanish (panama)\",\r\n\t\t\"fa-af,persian (afghanistan)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t\"fr-ch,french (switzerland)\",\r\n\t\t\"fr-sn,french (senegal)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"kok-latn,konkani (latin)\",\r\n\t\t\"ks-deva,kashmiri (devanagari)\",\r\n\t\t\"kxv-telu,kuvi (telugu)\",\r\n\t\t\"pt-pt,portuguese (portugal)\",\r\n\t\t\"ro-md,romanian (moldova)\",\r\n\t\t\"sd-deva,sindhi (devanagari)\",\r\n\t\t\"se-fi,northern sami (finland)\",\r\n\t\t\"shi-latn,tachelhit (latin)\",\r\n\t\t\"sr-cyrl-ba,serbian (cyrillic bosnia & herzegovina)\",\r\n\t\t\"sr-cyrl-me,serbian (cyrillic montenegro)\",\r\n\t\t\"sr-cyrl-xk,serbian (cyrillic kosovo)\",\r\n\t\t\"sr-latn,serbian (latin)\",\r\n\t\t\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\r\n\t\t\"sr-latn-me,serbian (latin montenegro)\",\r\n\t\t\"sr-latn-xk,serbian (latin kosovo)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\trun_test()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dtflistformat.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>dtf: listformat</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">datetimeformat: listformat\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy: from three\r\n\t\t\tstyles (<code>narrow</code>, <code>short</code>, <code>long</code>) and three types\r\n\t\t\t(<code>conjunction</code>, <code>disjunction</code>, <code>unit</code>).\r\n\t\t\tClick custom to test any configuration.</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"bnarrow\" class=\"btn4 btnfirst\" onClick=\"run('narrow')\">[ N ]</span>\r\n\t\t\t\t<span id=\"bshort\" class=\"btn4 btn\" onClick=\"run('short')\">[ S ]</span>\r\n\t\t\t\t<span id=\"blong\" class=\"btn4 btn\" onClick=\"run('long')\">[ L ]</span>\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bcustom\" class=\"btn4 btn\" onClick=\"run('custom')\">[ CUSTOM ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span class=\"hidden\" id =\"customoptions\">\r\n\t\t\t\t\t<p class=\"mono spaces\">NARROW: conjunction <input type=\"checkbox\" id=\"narrowconjunction\"> disjunction <input type=\"checkbox\" id=\"narrowdisjunction\"> unit <input type=\"checkbox\" id=\"narrowunit\"></p>\r\n\t\t\t\t\t<p class=\"mono spaces\"> SHORT: conjunction <input type=\"checkbox\" id=\"shortconjunction\"> disjunction <input type=\"checkbox\" id=\"shortdisjunction\"> unit <input type=\"checkbox\" id=\"shortunit\"></p>\r\n\t\t\t\t\t<p class=\"mono spaces\">  LONG: conjunction <input type=\"checkbox\" id=\"longconjunction\"> disjunction <input type=\"checkbox\" id=\"longdisjunction\"> unit <input type=\"checkbox\" id=\"longunit\"></p>\r\n\t\t\t\t<span class=\"btn4 btnfirst\" onClick=\"reset_custom('clear')\">[ CLEAR ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset_custom('min')\">[ RESET MIN ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset_custom('all')\">[ ALL ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"run_custom()\">[ RUN ]</span>\r\n\t\t\t\t\t<br><br><hr><br>\r\n\t\t\t\t</span>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare custom to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction reset_custom(type) {\r\n\t// check/uncheck everything\r\n\tlet checkState = (type == \"all\")\r\n\tlet styles = [\"narrow\",\"short\",\"long\"],\r\n\t\ttypes = [\"conjunction\",\"disjunction\",\"unit\"]\r\n\tstyles.forEach(function(s){\r\n\t\ttypes.forEach(function(t){\r\n\t\t\tdocument.getElementById(s +t).checked = checkState\r\n\t\t})\r\n\t})\r\n\tif (type == \"min\") {\r\n\t\t// min preset\r\n\t\tdom.narrowunit.checked = true\r\n\t\tdom.narrowconjunction.checked = true\r\n\t\tdom.narrowdisjunction.checked = true\r\n\t\tdom.longconjunction.checked = true\r\n\t\tdom.shortunit.checked = true\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DateTimeFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction get_dayperiod(date, code, option) {\r\n\t// always use h12\r\n\treturn new Intl.DateTimeFormat(code, {hourCycle: \"h12\", dayPeriod: option}).format(date)\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\ttry {\r\n\t\tlet\tstyles = ['narrow','short','long']\r\n\t\tlet types = ['conjunction','disjunction','unit']\r\n\t\tlet oOptions = {}\r\n\r\n\t\t// select what to test\r\n\t\tif (method == \"custom\") {\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttypes.forEach(function(t){\r\n\t\t\t\t\toOptions[s + t] = document.getElementById(s +t).checked\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\tif (method == \"narrow\") {styles = [\"narrow\"]}\r\n\t\t\tif (method == \"short\") {styles = [\"short\"]}\r\n\t\t\tif (method == \"long\") {styles = [\"long\"]}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttypes.forEach(function(t){\r\n\t\t\t\t\toOptions[s + t] = true\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t}\r\n\t\t//test: 3 styles x 3 types\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oStyles = {\"narrow\": [], \"short\": [], \"long\": []}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\ttypes.forEach(function(t){\r\n\t\t\t\t\tif (oOptions[s + t] == true) {\r\n\t\t\t\t\t\toStyles[s].push(new Intl.ListFormat(code, {style: s, type: t}).format([\"a\",\"b\",\"c\"]))\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\toTempData[hash][\"long\"] = oStyles[\"long\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"short\"] = oStyles[\"short\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"narrow\"] = oStyles[\"narrow\"].join(\" | \")\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet strN = oData[k][\"narrow\"],\r\n\t\t\t\tstrS = oData[k][\"short\"],\r\n\t\t\t\tstrL = oData[k][\"long\"],\r\n\t\t\t\tlocaleCount = oData[k][\"locales\"].split(\",\").length\r\n\t\t\tif (strN.length) {strN = \"<li>\"+ s16 +\"N: \"+ sc + strN +\"</li>\"}\r\n\t\t\tif (strS.length) {strS = \"<li>\"+ s16 +\"S: \"+ sc + strS +\"</li>\"}\r\n\t\t\tif (strL.length) {strL = \"<li>\"+ s16 +\"L: \"+ sc + strL +\"</li>\"}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\" + strN + strS + strL\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"9c5d9625\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"324524af\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"6c202f92\") { // FF147+: 157\r\n\t\t\t\t} else if (localesHash == \"4aff24fd\") { // FF140-146: 153\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"custom\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\tlet element = dom.customoptions\r\n\r\n\t\tif (method == \"custom\") {\r\n\t\t\t// unhide custom section\r\n\t\t\telement.classList.remove(\"hidden\")\r\n\r\n\t\t} else {\r\n\t\t\t// hide custom\r\n\t\t\telement.classList.add(\"hidden\")\r\n\t\t\tdom.results = \"\"\r\n\t\t\t// delay so users see change and allow paint\r\n\t\t\tsetTimeout(function() {\r\n\t\t\t\trun_main(method)\r\n\t\t\t}, 1)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction run_custom() {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"custom\")\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\t// support\r\n\ttry {\r\n\t\tlet test = new Intl.ListFormat(undefined, {style: \"short\", type: \"conjunction\"}).format([\"a\",\"b\",\"c\"])\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\treset_custom(\"min\")\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"en-in,english (india)\",\r\n\t\t\"en-sg,english (singapore)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-us,spanish (united states)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t'kok-latn,konkani (latin)',\r\n\t\t\"ks-deva,kashmiri (devanagari)\",\r\n\t\t\"kxv-telu,kuvi (telugu)\",\r\n\t\t\"pt-pt,portuguese (portugal)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t// blink\r\n\t\t'sr-latn,serbian (latin)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/dtfrelated.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>dtf: relatedyear</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\n\t\tul.main {margin-left: -20px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\n\t\t<tr>\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\n\t\t</tr>\n\t</table>\n\n\t<table id=\"tb4\">\n\t\t<col width=\"200px\"><col>\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">datetimeformat: relatedyear\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy with DateTimeFormat relatedYear</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\n\t\t\t<br><br><hr><br>\n\t\t\t\t<span id =\"results\"></span>\n\t\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat\n\nvar list = gLocales,\n\taLegend = [],\n\taLocales = [],\n\tisSupported = false,\n\tlocalesHashAll = \"\", // to compare min to\n\toData = {},\n\toCompare = {'false': [], 'true': []}, // check to*String matches INTL\n\tcheckLocaleStrings = false\n\nfunction log_console(name) {\n\tlet hash = mini(sDetail[name])\n\tif (name == \"locales\") {\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\n\t} else {\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\n\t}\n}\n\nfunction legend() {\n\t// build once\n\tif (aLegend.length == 0) {\n\t\tlist.sort()\n\t\tfor (let i = 0 ; i < list.length; i++) {\n\t\t\tlet str = list[i].toLowerCase()\n\t\t\tlet code = str.split(\",\")[0].trim()\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\n\t\t\tlet go = true\n\t\t\tif (isSupported) {\n\t\t\t\tgo = Intl.DateTimeFormat.supportedLocalesOf([code]).length > 0\n\t\t\t}\n\t\t\tif (go) {\n\t\t\t\taLocales.push(code)\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\n\t\t\t\tif (name.includes(\"(\")) {\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\n\t\t\t\t\tlet name1 = name.substring(\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \n\t\t\t\t\t\tname.lastIndexOf(\")\")\n\t\t\t\t\t)\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\n\t\t\t\t\tif (isSplit) {\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name0 +\" \"+ name1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\n\t\t\t}\n\t\t}\n\t}\n\t// output\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\n}\n\nfunction run_main(method = \"all\") {\n\tlet t0 = performance.now()\n\tlet spacer = \"<br><br>\"\n\n\t// all dates (days/months/am-pm) must be timezone resistent:  we are checking locale only\n\t\t// timezonename (and locale) is tested in the a different PoC - see TZP set_oIntlDateTests section\n\t// thus we use UTC time so everyone uses the exact same dates, and then we pass\n\t\t// UTC as the timezone so nothing shifts, preserving our specific datetimes\n\n\tlet dateA = new Date(\"January 5, 2023 1:00:00\"),\n\t\tdateB = new Date(-1,12,5,1)\n\n\tlet RY1 = new Date('-000002-01-15T01:00:00.000Z'),\n\t\tRY2 = new Date('2023-01-15T00:00:00.000Z')\n\tlet datesAll = [RY1, RY2]\n\n\tlet testsAll = {}\n\tlet testsAllMini = {\n\t\t'2-digit': datesAll,\n\t\t'long': datesAll,\n\t\t'narrow': datesAll,\n\t\t'numeric': datesAll,\n\t\t'short': datesAll,\n\t}\n\tlet aSupportedCal = []\n\ttry {\n\t\taSupportedCal = Intl.supportedValuesOf('calendar')\n\t} catch(e) {}\n\t// which calendars don't do much\n\t// note: F147+ 1955545 Use \"islamic-tbla\" when either \"islamic\" or \"islamic-rgsa\" was requested\n\tlet aCalendars = [\n\t\t'buddhist', // yes\n\t\t'chinese', // yes\n\t\t'coptic',\n\t\t'default',\n\t\t'gregory', // yes\n\t\t'hebrew', // yes\n\t\t'indian', // yes\n\t\t'islamic-tbla',\n\t\t'japanese',\n\t\t'roc', // blink\n\t\t// these seem redundant but include them for the full test\n\t\t'dangi',\n\t\t'ethioaa',\n\t\t'ethiopic',\n\t\t'islamic',\n\t\t'islamic-civil',\n\t\t'islamic-rgsa',\n\t\t'islamic-umalqura',\n\t\t'iso8601',\n\t\t'persian',\n\t]\n\taCalendars = aCalendars.filter(x => aSupportedCal.includes(x))\n\ttestsAll = {'default': testsAllMini}\n\tif (aCalendars.length) {\n\t\taCalendars.forEach(function(cal) {testsAll[cal] = testsAllMini})\n\t}\n\tlet testsMin = {\n\t\t'buddhist': {'long': [RY1]},\n\t\t'chinese': {'long': [RY1]},\n\t\t'coptic': {'long': [RY2]},\n\t\t'default': {'long': [RY1]},\n\t\t'gregory': {'long': [RY1]},\n\t\t'hebrew': {'long': [RY1]},\n\t\t'indian': {'long': [RY1]},\n\t\t'islamic-tbla': {'long': [RY1]},\n\t\t'japanese': {'long': datesAll}, // only one to use both\n\t\t'roc': {'long': [RY1]},\n\t}\n\tlet tests = method == \"all\" ? testsAll : testsMin\n\n\toData = {}\n\toCompare = {'false': [], 'true': []}\n\t//aLocales = ['en']\n\ttry {\n\t\taLocales.forEach(function(code) {\n\t\t\tlet oTempData = {}\n\t\t\tObject.keys(tests).sort().forEach(function(cal) {\n\t\t\t\toTempData[cal] = {}\n\t\t\t\tObject.keys(tests[cal]).forEach(function(style) {\n\t\t\t\t\toTempData[cal][style] = []\n\t\t\t\t\tlet calname = 'default' == cal ? undefined : cal\n\t\t\t\t\tlet formatter = Intl.DateTimeFormat(code, {calendar: cal, relatedYear: style, timeZone: 'UTC'})\n\t\t\t\t\ttests[cal][style].forEach(function(d) {\n\t\t\t\t\t\toTempData[cal][style].push(formatter.format(d))\n\t\t\t\t\t\tif (checkLocaleStrings) {\n\t\t\t\t\t\t\t//* test toLocaleString\n\t\t\t\t\t\t\tlet intlvalue = formatter.format(d)\n\t\t\t\t\t\t\tlet strlocale = (d).toLocaleString(code, {calendar: cal, day: 'numeric', month: 'numeric', year: 'numeric', timeZone: 'UTC'})\n\t\t\t\t\t\t\tlet isMatch = intlvalue == strlocale\n\t\t\t\t\t\t\toCompare[isMatch].push([intlvalue, strlocale])\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\t//console.log(oTempData)\n\n\t\t\tlet hash = mini(oTempData)\n\t\t\tif (oData[hash] == undefined) {\n\t\t\t\toData[hash] = {}\n\t\t\t\toData[hash][\"locales\"] = [code]\n\t\t\t\toData[hash][\"data\"] = {}\n\t\t\t\tfor (const k of Object.keys(oTempData).sort()) {\n\t\t\t\t\toData[hash][\"data\"][k] = oTempData[k]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\toData[hash][\"locales\"].push(code)\n\t\t\t}\n\t\t})\n\t\tif (checkLocaleStrings) {console.log(oCompare)}\n\t\t// perf\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\n\t\t//console.log(oData)\n\n\t\tlet localeGroups = [], displaylist = []\n\t\tfor (const k of Object.keys(oData)) {\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\n\t\t\tlet localeCount = oData[k][\"locales\"].length\n\t\t\tlet str = ''\n\t\t\tfor (const cal of Object.keys(oData[k][\"data\"])) {\n\t\t\t\tif ('min' == method) {str += '<ul class=\"main\"><li>'}\n\t\t\t\tstr += s14 + cal +\": \"+ sc\n\t\t\t\tfor (const style of Object.keys(oData[k][\"data\"][cal]).sort()) {\n\n\t\t\t\t\tlet shortstyle\n\t\t\t\t\tif (style == \"numeric\") {shortstyle = \"Num\"} else {shortstyle = style.slice(0,1).toUpperCase()}\n\t\t\t\t\tlet tmpStr = s16 + shortstyle +\": \"+ sc + oData[k][\"data\"][cal][style].join(\", \")\n\t\t\t\t\tif ('all' == method) {\n\t\t\t\t\t\tstr += '<ul class=\"main\"><li>'+ tmpStr +\"</li>\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstr += ' '+ tmpStr\n\t\t\t\t\t}\n\t\t\t\t\tif ('min' == method) {str += '</li>'}\n\t\t\t\t\tstr += '</ul>'\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (method == 'all') {str = '<details>'+ str +'</details>'}\n\t\t\tdisplaylist.push(\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\n\t\t\t\t+ \"<ul class='main'>\"+ str\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"].join(\", \") +\"</li></ul>\"\n\t\t\t)\n\t\t}\n\n\t\t// hashes + btns\n\t\tsDetail[\"results\"] = oData\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\n\t\tlet resultsHash = mini(oData)\n\t\tlocaleGroups.sort()\n\t\tsDetail[\"locales\"] = localeGroups\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\n\t\tlet localesHash = mini(localeGroups)\n\n\t\t/*\n\t\tconsole.log(localesHash)\n\t\tconsole.log(localeGroups)\n\t\tconsole.log(resultsHash)\n\t\tconsole.log(oData)\n\t\t//*/\n\n\t\t// notations\n\t\tlet localesMatch = \"\"\n\t\tif (method == \"all\") {\n\t\t\tlocalesHashAll = localesHash\n\t\t\t// notate new if 140+\n\t\t\tif (isVer > 139) {\n\t\t\t\t// ignore if non-supported used, which return same as undefined = user's resolved options\n\t\t\t\t// results\n\t\t\t\tif (resultsHash == \"6f15efe5\") { // FF151+\n\t\t\t\t} else if (resultsHash == \"fddaa32a\") { // FF147-150\n\t\t\t\t} else if (resultsHash == \"4b451618\") { // FF140-146\n\t\t\t\t} else {resultsHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t\t// locales\n\t\t\t\tif (localesHash == \"e57bb0b6\") { // FF151+: 226\n\t\t\t\t} else if (localesHash == \"bd60b370\") { // FF147-150: 174\n\t\t\t\t} else if (localesHash == \"51160b4d\") { // FF140-146: 171\n\t\t\t\t} else {localesHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\n\t\t}\n\n\t\t// display\n\t\tlet display = s4 + localeGroups.length + sc +\" from \"+ s4 + aLocales.length + sc\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\n\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\n\t}\n}\n\nfunction run(method) {\n\tif (isSupported) {\n\t\t//reset\n\t\tsetBtn(method)\n\t\tdom.perf = \"\"\n\t\tlet msg = method == 'all' ? 'running... it takes a few seconds' : ''\n\t\tdom.results = msg\n\t\t// delay so users see change and allow paint\n\t\tsetTimeout(function() {\n\t\t\trun_main(method)\n\t\t}, 1)\n\t}\n}\n\nfunction setBtn(method) {\n\t// reset btns\n\tlet items = document.getElementsByClassName(\"btn8\")\n\tfor (let i=0; i < items.length; i++) {\n\t\titems[i].classList.add(\"btn4\")\n\t\titems[i].classList.remove(\"btn8\")\n\t}\n\t// set btn\n\tlet el = document.getElementById(\"b\"+ method)\n\tel.classList.add(\"btn8\")\n\tel.classList.remove(\"btn4\")\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_isVer()\n\tbuildnav()\n\n\ttry {\n\t\t// pointless if we can't use the feature being tested: FF58+\n\t\tlet test = new Intl.DateTimeFormat(\"en\").formatToParts(new Date)\n\t\tisSupported = true\n\t\tdom.results = 'running... it takes a few seconds'\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\n\t}\n\t// add additional locales to core locales for this test\n\tlet aListExtra = [\n\t\t'ar-bh,arabic (bahrain)',\n\t\t'az-cyrl,azerbaijani (cyrillic)',\n\t\t'bn-in,bengali (india)',\n\t\t'bs-cyrl,bosnian (cyrillic)',\n\t\t'ckb-ir,central kurdish (iran)',\n\t\t'en-001,english',\n\t\t'en-ae,english (united arab emirates)',\n\t\t'en-au,english (australia)',\n\t\t'en-be,english (belgium)',\n\t\t'en-bw,english (botswana)',\n\t\t'en-ca,english (canada)',\n\t\t'en-ch,english (switzerland)',\n\t\t'en-gb,english (united kingdom)',\n\t\t'en-hk,english (hong kong)',\n\t\t'en-in,english (india)',\n\t\t'en-nz,english (new zealand)',\n\t\t'en-se,english (sweden)',\n\t\t'en-za,english (south africa)',\n\t\t'es-ar,spanish (argentina)',\n\t\t'es-cl,spanish (chile)',\n\t\t'es-pa,spanish (panama)',\n\t\t'es-pr,spanish (puerto rico)',\n\t\t'fa-af,persian (afghanistan)',\n\t\t'ff-adlm,fulah (adlam)',\n\t\t'fr-ca,french (canada)',\n\t\t'fr-ch,french (switzerland)',\n\t\t'hi-latn,hindi (latin)',\n\t\t'kk-cn,kazakh (china)',\n\t\t'kok-latn,konkani (latin)',\n\t\t'ks-deva,kashmiri (devanagari)',\n\t\t'kxv-telu,kuvi (telugu)',\n\t\t'lrc-iq,northern luri (iraq)',\n\t\t'nl-be,dutch (belgium)',\n\t\t'pa-arab,punjabi (arabic)',\n\t\t'ps-pk,pashto (pakistan)',\n\t\t'pt-ao,portuguese (angola)',\n\t\t'sd-deva,sindhi (devanagari)',\n\t\t'se-fi,northern sami (finland)',\n\t\t'shi-latn,tachelhit (latin)',\n\t\t'sr-latn,serbian (latin)',\n\t\t'sv-fi,swedish (finland)',\n\t\t'ur-in,urdu (india)',\n\t\t'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\n\t\t'yue-cn,cantonese (china)',\n\t\t'zh-hans-hk,chinese (simplified hong kong)',\n\n\t\t// blink\n\t\t//*\n\t\t'ar-sa,arabic (saudi arabia)',\n\t\t'uz-af,uzbek (afghanistan)',\n\t\t'uz-arab,uzbek (arabic)',\n\t\t//*/\n\t]\n\tlist = list.concat(aListExtra)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\tlegend()\n\tif (isSupported) {\n\t\tsetBtn('all')\n\t\tsetTimeout(function() {\n\t\t\trun('all')\n\t\t}, 100)\n\t}\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/dtftimezonename.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>dtf: timezonename</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 680px; max-width: 780px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t\tdiv.nav-down {width: 450px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">datetimeformat: timezonename\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"timezone\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A firefox (128+) proof to confirm minimum tests for maximum entropy in timeZoneName.\r\n\t\t\tThe default test covers base languages, but you can expand locales to expose\r\n\t\t\t<span class=\"s4\">regional differences</span> on all three tests.\r\n\t\t\tThe <code>TINY</code> test is a minimalist hardcoded test built for speed.\r\n\t\t\tYou can also test individual timezones via the console; e.g. <code>run(\"Asia/Seoul\")</code>\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<span id=\"btiny\" class=\"btn4 btn\" onClick=\"run('tiny')\">[ TINY ]</span>\r\n\t\t\t\t<input type=\"checkbox\" id=\"optExpanded\"> expand locales\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet tzIgnore = [\r\n// MISC: acronyms and old-timey aliases: adds nothing\r\n\"CET\",\"CST6CDT\",\"Cuba\",\"EET\",\"EST\",\"EST5EDT\",\"Egypt\",\"Eire\",\"Factory\",\"GB\",\"GB-Eire\",\r\n\"GMT\",\"GMT+0\",\"GMT-0\",\"GMT0\",\"Greenwich\",\"HST\",\"Hongkong\",\"Iceland\",\"Iran\",\"Israel\",\r\n\"Jamaica\",\"Japan\",\"Kwajalein\",\"Libya\",\"MET\",\"MST\",\"MST7MDT\",\"NZ\",\"NZ-CHAT\",\"Navajo\",\r\n\"PRC\",\"PST8PDT\",\"Poland\",\"Portugal\",\"ROC\",\"ROK\",\"Singapore\",\"Turkey\",\"UCT\",\"UTC\",\r\n\"Universal\",\"W-SU\",\"WET\",\"Zulu\",\r\n\r\n\"Brazil/Acre\",\"Brazil/DeNoronha\",\"Brazil/East\",\"Brazil/West\",\"Canada/Atlantic\",\r\n\"Canada/Central\",\"Canada/Eastern\",\"Canada/Mountain\",\"Canada/Newfoundland\",\"Canada/Pacific\",\r\n\"Canada/Saskatchewan\",\"Canada/Yukon\",\"Chile/Continental\",\"Chile/EasterIsland\",\r\n\r\n\"Etc/GMT\",\"Etc/GMT+0\",\"Etc/GMT+1\",\"Etc/GMT+10\",\"Etc/GMT+11\",\"Etc/GMT+12\",\"Etc/GMT+2\",\r\n\"Etc/GMT+3\",\"Etc/GMT+4\",\"Etc/GMT+5\",\"Etc/GMT+6\",\"Etc/GMT+7\",\"Etc/GMT+8\",\"Etc/GMT+9\",\r\n\"Etc/GMT-0\",\"Etc/GMT-1\",\"Etc/GMT-10\",\"Etc/GMT-11\",\"Etc/GMT-12\",\"Etc/GMT-13\",\"Etc/GMT-14\",\r\n\"Etc/GMT-2\",\"Etc/GMT-3\",\"Etc/GMT-4\",\"Etc/GMT-5\",\"Etc/GMT-6\",\"Etc/GMT-7\",\"Etc/GMT-8\",\"Etc/GMT-9\",\r\n\"Etc/GMT0\",\"Etc/Greenwich\",\"Etc/UCT\",\"Etc/UTC\",\"Etc/Universal\",\"Etc/Zulu\",\r\n\r\n\"Mexico/BajaNorte\",\"Mexico/BajaSur\",\"Mexico/General\",\"US/Alaska\",\"US/Aleutian\",\"US/Arizona\",\r\n\"US/Central\",\"US/East-Indiana\",\"US/Eastern\",\"US/Hawaii\",\"US/Indiana-Starke\",\"US/Michigan\",\r\n\"US/Mountain\",\"US/Pacific\",\"US/Samoa\"\r\n]\r\n\r\nvar aTimezones = [],\r\n\taTimezonesLowerCase = [],\r\n\taTimezonesSupported = [],\r\n\taTimezonesSupportedLowerCase = [],\r\n\toLists = {},\r\n\taLegend = [],\r\n\taLocales = [],\r\n\taLegendExpanded = [],\r\n\taLocalesExpanded = [],\r\n\tisSupported = false,\r\n\tisSupportedValues = false,\r\n\tlocalesHashAll = \"\", // to compare min to\r\n\tlocalesHashAllExpanded = \"\", // to compare min to\r\n\tstrMinWarning = \" don't panic, it's working<br><br> ... running 'expanded' takes a few seconds\",\r\n\tstrMaxWarning = \" don't panic, it's working<br><br> ... running 'ALL' can take over a minute\"\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"timezones\") {\r\n\t\tconsole.log(name +\"\\n\", sDetail.timezones)\r\n\t} else if (name == \"timezonesall\") {\r\n\t\tconsole.log(\"supported timezones\\n\", Intl.supportedValuesOf('timeZone'))\r\n\t} else if (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[\"locales\"])\r\n\t} else if (sDetail[name] !== undefined) {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tlet optExpanded = dom.optExpanded.checked\r\n\tlet method = optExpanded ? \"expanded\" : \"list\"\r\n\tlet list = oLists[method]\r\n\r\n\tlet proceed = false\r\n\tif (method == \"list\") {\r\n\t\tif (aLegend.length == 0) {proceed = true}\r\n\t} else {\r\n\t\tif (aLegendExpanded.length == 0) {proceed = true}\r\n\t}\r\n\r\n\tif (proceed) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.DateTimeFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\tif (method == \"list\") {\r\n\t\t\t\t\taLocales.push(code)\r\n\t\t\t\t} else {\r\n\t\t\t\t\taLocalesExpanded.push(code)\r\n\t\t\t\t}\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (method == \"list\") {\r\n\t\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t\t} else {\r\n\t\t\t\t\taLegendExpanded.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet aDisplay = (method == \"list\" ? aLegend : aLegendExpanded)\r\n\tlet header = s4 +\"LEGEND [\"+ aDisplay.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aDisplay.join(\"<br>\")\r\n}\r\n\r\nfunction run_yr(startyear, addyears = 10) {\r\n\t// add a year to our test to see if increases max unique results\r\n\t\t// no change by adding all months for: 1800-2026\r\n\r\n\tdom.optExpanded.checked = true\r\n\tsetBtn('all')\r\n\tdom.results.innerHTML = ''\r\n\tdom.perf.innerHTML = ''\r\n\tlegend() // set legend to expanded\r\n\tlet aYears = []\r\n\tfor (let i = 0; i < addyears; i++) {\r\n\t\taYears.push(startyear + i)\r\n\t}\r\n\tconsole.log(aYears)\r\n\tlet isSummary = aYears.length > 1\r\n\tsetTimeout(function() {\r\n\t\taYears.forEach(function(yr){\r\n\t\t\trun_main('all_confirmed', yr, isSummary)\r\n\t\t})\r\n\t}, 50)\r\n}\r\n\r\nfunction run_main(method, yr, isSummary = false) {\r\n\tif (undefined == method) {return}\r\n\tif (undefined == yr) {isSummary = false}\r\n\r\n\tlet spacer = \"<br><br>\"\r\n\tif (method == \"all\") {\r\n\t\t// show a warning and add a button with isWarning = true\r\n\t\tlet confirmStr = \"are you sure?\"+ spacer\r\n\t\t\t+\"it can take <span class='bad'>up to a minute</span> (or more, on slower devices)\" + spacer\r\n\t\t\t+\"it might <span class='bad'>make your fans whir</span>\" + spacer +\"<br>\"\r\n\t\t\t+\"<span class='btnbad btnfirst' onClick='run(`all_confirmed`)'>\"\r\n\t\t\t+\"[ YES, i'm sure. RUN it ]</span>\"\r\n\t\tdom.results.innerHTML = confirmStr\r\n\t\treturn\r\n\t}\r\n\tif (method == \"all_confirmed\") {method = \"all\"}\r\n\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet optExpanded = dom.optExpanded.checked\r\n\r\n\t// Date.UTC(year, monthIndex, day, hour, minute, second, millisecond)\r\n\tlet tzNames = [\"short\",\"long\",\"shortOffset\",\"longOffset\",\"shortGeneric\",\"longGeneric\"] // full list\r\n\tlet tzDays = [\r\n\t\tnew Date('2019-08-15T00:00:00.000Z'), // 2019 August is a sweet spot: nightly 152 = 340 max (154a6a7c)\r\n\t]\r\n\tif ('all' == method && undefined !== yr) {\r\n\t\ttzDays.push(\r\n\t\t\tnew Date(yr +'-01-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-02-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-03-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-04-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-05-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-06-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-07-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-08-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-09-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-10-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-11-15T00:00:00.000Z'),\r\n\t\t\tnew Date(yr +'-12-15T00:00:00.000Z'),\r\n\t\t)\r\n\t}\r\n\ttzNames = [\"short\",\"shortGeneric\",\"longGeneric\"] // reduce test size\r\n\r\n\tlet tzSG = {\"shortGeneric\": tzDays},\r\n\t\ttzLG = {\"longGeneric\": tzDays},\r\n\t\ttzS = {\"short\": tzDays},\r\n\t\ttzSSG = {\"short\": tzDays, \"shortGeneric\": tzDays}\r\n\r\n\tlet both = {\"shortGeneric\": tzDays, \"longGeneric\": tzDays}\r\n\tlet everything = {\r\n\t\t\t\"short\": tzDays,\r\n\t\t\t\"shortGeneric\": tzDays,\r\n\t\t\t\"longGeneric\": tzDays,\r\n\t\t\t/* these don't add anything\r\n\t\t\t\"long\": tzDays,\r\n\t\t\t\"shortOffset\": tzDays,\r\n\t\t\t\"longOffset\": tzDays,\r\n\t\t\t//*/\r\n\t\t}\r\n\tlet tests = {}\r\n\r\n\tif (method == \"all\") {\r\n\t\t// all supported timezones minus some crap x both\r\n\t\taTimezones.forEach(function(tz) {\r\n\t\t\ttests[tz] = everything\r\n\t\t})\r\n\t} else if (method == \"min\") {\r\n\t\tif (optExpanded) {\r\n\t\t\t// expanded\r\n\t\t\ttests = {\r\n\t\t\t\t'Africa/Brazzaville': tzSG,\r\n\t\t\t\t\"Africa/Dakar\": tzLG,\r\n\t\t\t\t\"Africa/Douala\": tzLG,\r\n\t\t\t\t\"Africa/Lusaka\": tzSG,\r\n\t\t\t\t\"Africa/Nairobi\": tzSG,\r\n\t\t\t\t\"America/Argentina/Cordoba\": tzSG,\r\n\t\t\t\t\"America/Bogota\": tzSG,\r\n\t\t\t\t\"America/Caracas\": tzSG,\r\n\t\t\t\t\"America/Cayenne\": tzSG,\r\n\t\t\t\t\"America/Guayaquil\": tzSG,\r\n\t\t\t\t\"America/Guyana\": tzSG,\r\n\t\t\t\t\"America/La_Paz\": tzSG,\r\n\t\t\t\t\"America/Lima\": tzSG,\r\n\t\t\t\t'America/Mendoza': tzSG, // for blink\r\n\t\t\t\t\"America/Montevideo\": tzSG,\r\n\t\t\t\t\"America/Paramaribo\": tzSG,\r\n\t\t\t\t\"America/Toronto\": tzLG,\r\n\t\t\t\t\"America/Winnipeg\": tzLG,\r\n\t\t\t\t\"Asia/Dili\": tzSG,\r\n\t\t\t\t\"Asia/Hong_Kong\": tzSG,\r\n\t\t\t\t\"Asia/Kolkata\": tzSG,\r\n\t\t\t\t\"Asia/Kuching\": tzSG,\r\n\t\t\t\t\"Asia/Muscat\": tzSG,\r\n\t\t\t\t\"Asia/Nicosia\": tzLG,\r\n\t\t\t\t'Asia/Rangoon': tzSG, // for blink\r\n\t\t\t\t\"Asia/Seoul\": tzLG,\r\n\t\t\t\t\"Asia/Shanghai\": tzSG,\r\n\t\t\t\t\"Asia/Singapore\": tzSG,\r\n\t\t\t\t\"Asia/Yangon\": tzSG,\r\n\t\t\t\t\"Asia/Yerevan\": tzSG,\r\n\t\t\t\t\"Atlantic/Azores\": tzSG,\r\n\t\t\t\t\"Atlantic/Bermuda\": both,\r\n\t\t\t\t\"Atlantic/South_Georgia\": tzSG,\r\n\t\t\t\t\"Australia/Lord_Howe\": tzSG,\r\n\t\t\t\t\"Europe/Isle_of_Man\": tzSG,\r\n\t\t\t\t\"Europe/London\": tzSSG,\r\n\t\t\t\t\"Europe/Minsk\": tzSG, // for blink\r\n\t\t\t\t\"Europe/Sarajevo\": both,\r\n\t\t\t\t\"Indian/Cocos\": tzS,\r\n\t\t\t\t\"Pacific/Pago_Pago\": tzLG,\r\n\t\t\t\t\"Pacific/Saipan\": tzSG,\r\n\t\t\t}\r\n\t\t\t// test\r\n\t\t\tlet aMore = []\r\n\t\t\taMore.forEach(function(tz) {\r\n\t\t\t\tif (undefined == tests[tz]) {tests[tz] = both}\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\ttests = {\r\n\t\t\t\t\"Africa/Dakar\": tzLG,\r\n\t\t\t\t\"Africa/Douala\": tzLG,\r\n\t\t\t\t\"Africa/Lusaka\": tzSG,\r\n\t\t\t\t\"Africa/Nairobi\": tzSG,\r\n\t\t\t\t\"America/La_Paz\": tzSG,\r\n\t\t\t\t\"Asia/Seoul\": tzLG,\r\n\t\t\t\t\"Asia/Shanghai\": tzSG,\r\n\t\t\t\t\"Asia/Yerevan\": tzSG,\r\n\t\t\t\t\"Europe/Isle_of_Man\": tzSG,\r\n\t\t\t\t\"Europe/London\": tzSG,\r\n\t\t\t\t\"Pacific/Pago_Pago\": tzLG,\r\n\t\t\t\t\"Pacific/Saipan\": tzSG,\r\n\t\t\t}\r\n\t\t}\r\n\t} else if (method == \"tiny\") {\r\n\t\t// wee fast test with bang for buck\r\n\t\ttests = {\r\n\t\t\t// gecko non-expanded 226 | expanded 280\r\n\t\t\t\"Africa/Douala\": tzLG, // +4\r\n\t\t\t\"America/Montevideo\": tzSG, // +4\r\n\t\t\t\"America/Winnipeg\": tzLG, // +3\r\n\t\t\t'Asia/Hong_Kong': tzSG, // +4\r\n\t\t\t\"Asia/Seoul\": tzLG, // +3\r\n\t\t\t\"Europe/London\": tzSG, // +4\r\n\t\t\t\"Asia/Muscat\": tzSG, // +4\r\n\t\t}\r\n\t} else {\r\n\t\t// test valid timezone\r\n\t\ttry {\r\n\t\t\tlet testDate = new Date()\r\n\t\t\tlet test = new Intl.DateTimeFormat('en', {timeZone: method}).format(testDate)\r\n\t\t} catch(e) {\r\n\t\t\tconsole.log(e)\r\n\t\t\tdom.results.innerHTML = s16 + e.name +\": \"+ sc + e.message\r\n\t\t\treturn\r\n\t\t}\r\n\t\t/*\r\n\t\t// only allow supported\r\n\t\tlet methodtest = method.toLowerCase()\r\n\t\tif (!aTimezonesSupportedLowerCase.includes(methodtest)) {\r\n\t\t\tdom.results.innerHTML = s3 + method +\": \"+ sc +'not supported'\r\n\t\t\treturn\r\n\t\t}\r\n\t\t//*/\r\n\t\ttests = {}\r\n\t\ttests[method] = both\r\n\t}\r\n\r\n\tif ('all' == method || 'min' == method || 'tiny' == method) {\r\n\t\t// remove unsupported: e.g. from tiny/min lists\r\n\t\tfor (const k of Object.keys(tests)) {\r\n\t\t\tif (!aTimezonesLowerCase.includes(k.toLowerCase())) {delete tests[k]}\r\n\t\t}\r\n\t}\r\n\r\n\tlet tzUsed = []\r\n\tObject.keys(tests).forEach(function(tz) {\r\n\t\ttzUsed.push(tz)\r\n\t})\r\n\ttzUsed.sort()\r\n\tsDetail[\"timezones\"] = tzUsed\r\n\r\n\tlet oStyles = {}\r\n\tlet array = optExpanded ? aLocalesExpanded : aLocales\r\n\ttry {\r\n\t\tarray.forEach(function(code) { // for each locale\r\n\t\t\toStyles = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(tz){ // for each tz\r\n\t\t\t\toStyles[tz] = {}\r\n\t\t\t\tObject.keys(tests[tz]).forEach(function(tzn){ // for each tzname\r\n\t\t\t\t\toStyles[tz][tzn] = []\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\t// note: use hour12 - https://bugzilla.mozilla.org/show_bug.cgi?id=1645115#c9\r\n\t\t\t\t\t\t\t// IDK if this is really needed here but it can't hurt\r\n\t\t\t\t\t\t\t// we use y+m+d numeric so we toLocaleString will match\r\n\t\t\t\t\t\t\t// ^ does not affect entropy or hashes at all which are stable across timezones: e.g. TB at UTC vs my machine\r\n\t\t\t\t\t\tlet option = {year: \"numeric\", month: \"numeric\", day: \"numeric\", hour12: true, timeZone: tz, timeZoneName: tzn}\r\n\t\t\t\t\t\tlet formatter = Intl.DateTimeFormat(code, option)\r\n\t\t\t\t\t\ttests[tz][tzn].forEach(function(d){ // for each date\r\n\t\t\t\t\t\t\toStyles[tz][tzn].push(formatter.format(d))\r\n\t\t\t\t\t\t\t/* what do we  get if we only use the name\r\n\t\t\t\t\t\t\tlet tzDateString = formatter.formatToParts(d).map(({type, value}) => {\r\n\t\t\t\t\t\t\t\tif (type == \"timeZoneName\" || type == \"unknown\") {\r\n\t\t\t\t\t\t\t\t\toStyles[tz][tzn].push(value)\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t//*/\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\t//console.log(e.name, e.message)\r\n\t\t\t\t\t} // ignore invalid\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oStyles).forEach(function(tz){ // each tz\r\n\t\t\t\t\toTempData[hash][tz] = {}\r\n\t\t\t\t\tObject.keys(oStyles[tz]).forEach(function(tzn){ // for each tzname\r\n\t\t\t\t\t\tif (oStyles[tz][tzn].length) {\r\n\t\t\t\t\t\t\toTempData[hash][tz][tzn] = oStyles[tz][tzn].join(\" | \")\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t//console.log(oTempData)\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const tz of Object.keys(oTempData[h]).sort()) { // for each tz\r\n\t\t\t\tif (tz == \"locales\") {\r\n\t\t\t\t\toData[h][tz] = oTempData[h][tz].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][tz]).length) {\r\n\t\t\t\t\t\toData[h][tz] = oTempData[h][tz]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const h of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[h][\"locales\"])\r\n\t\t\tlet localeCount = oData[h][\"locales\"].split(\",\").length\r\n\t\t\tif (!isSummary) {\r\n\t\t\t\tlet str = \"\"\r\n\t\t\t\tfor (const not of Object.keys(oData[h])) { // for each notation\r\n\t\t\t\t\tif (not !== \"locales\") {\r\n\t\t\t\t\t\tlet linecount = Object.keys(oData[h][not]).length\r\n\t\t\t\t\t\tif (linecount == 1) {\r\n\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + not +\": \"+ sc\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tstr += \"<li>\"+ s12 + not + sc +\"</li>\"\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tObject.keys(oData[h][not]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\t\tstr += s16 + s +\": \"+ sc + oData[h][not][s] +\"</br>\"\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\tif (linecount == 1) {str += \"</li>\"}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// wrap into details for long lists\r\n\t\t\t\tif (Object.keys(tests).length > 15) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + h + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[h][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\tlet tzBtn = \"<span class='btn4 btnc' onClick='log_console(`timezones`)'>[\" + tzUsed.length + \"]</span>\"\r\n\t\t\t+\" from <span class='btn4 btnc' onClick='log_console(`timezonesall`)'>[\" + aTimezonesSupported.length + \"]</span>\"\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tif (optExpanded) {\r\n\t\t\t\tlocalesHashAllExpanded = localesHash\r\n\t\t\t} else {\r\n\t\t\t\tlocalesHashAll = localesHash\r\n\t\t\t}\r\n\t\t\t// notate new if expanded and 140+\r\n\t\t\tif (isVer > 139 && optExpanded) {\r\n\t\t\t\t// expanded results\r\n\t\t\t\tif (resultsHash == '0db200af') { // FF151+\r\n\t\t\t\t} else if (resultsHash == 'b8d2903c') { // FF149-150: 2009907\r\n\t\t\t\t} else if (resultsHash == '2ca8e181') { // FF147-148\r\n\t\t\t\t} else if (resultsHash == '31a3ce60') { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// expanded locales\r\n\t\t\t\tif (localesHash == '154a6a7c') { // FF151+: 340\r\n\t\t\t\t} else if (localesHash == 'ab48ed50') { // FF147-150: 339\r\n\t\t\t\t} else if (localesHash == 'dc340cf5') { // FF140-146: 336\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\t// don't notate if user hasn't run all\r\n\t\t\tif (optExpanded) {\r\n\t\t\t\tif (localesHashAllExpanded !== \"\") {\r\n\t\t\t\t\tlocalesMatch = localesHash == localesHashAllExpanded ? green_tick : red_cross\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif (localesHashAll !== \"\") {\r\n\t\t\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t// display\r\n\t\tif (!isSummary) {\r\n\t\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + array.length +\"]\" + sc\r\n\t\t\t\t//+\" &nbsp KEY: <span class='s16'>L</span> longGeneric <span class='s16'>S</span> shortGeneric\"\r\n\t\t\t\t+ spacer\r\n\t\t\t\t+ s12 +\"timezones: \"+ sc + tzBtn + spacer\r\n\t\t\t\t+ s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t} else {\r\n\t\t\tconsole.log(yr, localesHash, localeGroups.length, tzDays.length)\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML +'<br>' + s4 + yr + sc +': '+ localesHash +' ['+ localeGroups.length +' | '+ tzDays.length + ']'\r\n\t\t}\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported && isSupportedValues) {\r\n\t\ttry {\r\n\t\t\tdom.timezone.innerHTML = Intl.DateTimeFormat().resolvedOptions().timeZone\r\n\t\t} catch(e) {dom.timezone.innerHTML = ''}\r\n\t\tlet optExpanded = dom.optExpanded.checked\r\n\t\t// set legend\r\n\t\tlegend()\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = ''\r\n\t\tlet status = 'calculating ...'\r\n\t\tif (method == 'all_confirmed') {\r\n\t\t\tstatus += strMaxWarning\r\n\t\t} else if (method == 'min' && optExpanded) {\r\n\t\t\tstatus += strMinWarning\r\n\t\t}\r\n\t\tdom.results.innerHTML = status\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\tif (method == \"all_confirmed\") {method = \"all\"}\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\ttry {\r\n\t\tlet el = document.getElementById(\"b\"+ method)\r\n\t\tel.classList.add(\"btn8\")\r\n\t\tel.classList.remove(\"btn4\")\r\n\t} catch(e) {}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\tdom.optExpanded.checked = false\r\n\r\n\ttry {\r\n\t\t// FF91+: pointless if we can't use the feature being tested\r\n\t\tlet formatter = Intl.DateTimeFormat(\"en-US\", {hour12: true, timeZoneName: \"longGeneric\"})\r\n\t\tisSupported = true\r\n\t\t// FF93+: to simplify matters and help perf, we also require supportedValuesOf\r\n\t\tlet test = Intl.supportedValuesOf(\"timeZone\")\r\n\t\tisSupportedValues = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\r\n\t// set lists\r\n\tlet list = gLocales\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = [\"en-au,english (australia)\",\"en-bm,english (bermuda)\"]\r\n\t//list = [\"en,english\"]\r\n\t//list = ['de','en-NZ']\r\n\tlist.sort()\r\n\r\n\toLists[\"list\"] = list\r\n\t// expanded: add additional locales to core locales for this test\r\n\tlet listExtra = [\r\n\t\t\"ar-ae,arabic (united arabic emirates)\",\r\n\t\t\"ar-bh,arabic (bahrain)\",\r\n\t\t\"ar-eg,arabic (egypt)\",\r\n\t\t\"ar-ly,arabic (libya)\",\r\n\t\t\"ar-sa,arabic (saudi arabia)\",\r\n\t\t\"ar-tn,arabic (tunisia)\",\r\n\t\t\"az-cyrl,azerbaijani (cyrillic)\",\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"ckb-ir,central kurdish (iran)\",\r\n\t\t\"de-ch,german (switzerland)\",\r\n\t\t\"en-ae,english (united arab emirates)\",\r\n\t\t\"en-ag,english (antigua & barbuda)\",\r\n\t\t\"en-at,english (austria)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-be,english (belgium)\",\r\n\t\t\"en-bm,english (bermuda)\",\r\n\t\t\"en-bw,english (botswana)\",\r\n\t\t\"en-bz,english (belize)\",\r\n\t\t\"en-ca,english (canada)\",\r\n\t\t\"en-ch,english (switzerland)\",\r\n\t\t\"en-gb,english (united kingdom)\",\r\n\t\t\"en-gu,english (guam)\",\r\n\t\t\"en-gy,english (guyana)\",\r\n\t\t\"en-hk,english (hong kong)\",\r\n\t\t\"en-ie,english (ireland)\",\r\n\t\t\"en-in,english (india)\",\r\n\t\t\"en-jm,english (jamaica)\",\r\n\t\t\"en-mh,english (marshall islands)\",\r\n\t\t\"en-mo,english (macau)\",\r\n\t\t\"en-my,english (malaysia)\",\r\n\t\t\"en-nz,english (new zealand)\",\r\n\t\t\"en-pr,english (puerto rico)\",\r\n\t\t\"en-se,english (sweden)\",\r\n\t\t\"en-sg,english (singapore)\",\r\n\t\t\"en-za,english (south africa)\",\r\n\t\t\"en-zw,english (zimbabwe)\",\r\n\t\t\"es-ar,spanish (argentina)\",\r\n\t\t\"es-bo,spanish (bolivia)\",\r\n\t\t\"es-br,spanish (brazil)\",\r\n\t\t\"es-bz,spanish (belize)\",\r\n\t\t\"es-cl,spanish (chile)\",\r\n\t\t\"es-co,spanish (colombia)\",\r\n\t\t\"es-cr,spanish (costa rica)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-ec,spanish (ecuador)\",\r\n\t\t\"es-mx,spanish (mexico)\",\r\n\t\t\"es-pa,spanish (panama)\",\r\n\t\t\"es-pe,spanish (peru)\",\r\n\t\t\"es-pr,spanish (puerto rico)\",\r\n\t\t\"es-us,spanish (united states)\",\r\n\t\t\"es-uy,spanish (uruguay)\",\r\n\t\t\"es-ve,spanish (venezuela)\",\r\n\t\t\"fa-af,persian (afghanistan)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"fr-be,french (belgium)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t\"fr-ch,french (switzerland)\",\r\n\t\t\"fr-gf,french (french guiana)\",\r\n\t\t\"fr-gp,french (guadeloupe)\",\r\n\t\t\"fr-tn,french (tunisia)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"ko-kp,korean (north korea)\",\r\n\t\t\"kok-latn,konkani (latin)\",\r\n\t\t\"ks-deva,kashmiri (devanagari)\",\r\n\t\t\"kxv-telu,kuvi (telugu)\",\r\n\t\t\"lrc-iq,northern luri (iraq)\",\r\n\t\t\"ms-id,malay (indonesia)\",\r\n\t\t\"ne-in,nepali (india)\",\r\n\t\t\"nl-aw,dutch (aruba)\",\r\n\t\t\"nl-be,dutch (belgium)\",\r\n\t\t\"nl-sr,dutch (suriname)\",\r\n\t\t\"pa-pk,punjabi (pakistan)\",\r\n\t\t\"ps-pk,pashto (pakistan)\",\r\n\t\t\"pt-ao,portuguese (angola)\",\r\n\t\t\"pt-ch,portuguese (switzerland)\",\r\n\t\t\"pt-cv,portuguese (cape verde)\",\r\n\t\t\"qu-bo,quechua (bolivia)\",\r\n\t\t\"qu-ec,quechua (ecuador)\",\r\n\t\t\"ro-md,romanian (moldova)\",\r\n\t\t\"ru-ua,russian (ukraine)\",\r\n\t\t\"sd-deva,sindhi (devanagari)\",\r\n\t\t\"se-fi,northern sami (finland)\",\r\n\t\t\"shi-latn,tachelhit (latin)\",\r\n\t\t\"sr-ba,serbian (bosnia & herzegovina)\",\r\n\t\t\"sr-cyrl-me,serbian (cyrillic montenegro)\",\r\n\t\t\"sr-latn,serbian (latin)\",\r\n\t\t\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\r\n\t\t\"sr-latn-xk,serbian (latin kosovo)\",\r\n\t\t\"sr-me,serbian (montenegro)\",\r\n\t\t\"sr-xk,serbian (kosovo)\",\r\n\t\t'sv-fi,swedish (finland)',\r\n\t\t\"sw-cd,swahili (congo kinshasa)\",\r\n\t\t\"sw-ke,swahili (kenya)\",\r\n\t\t\"ta-my,tamil (malaysia)\",\r\n\t\t\"ur-in,urdu (india)\",\r\n\t\t\"uz-af,uzbek (afghanistan)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"vai-latn,vai (latin)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t\"zh-hans-hk,chinese (simplified hong kong)\",\r\n\t\t\"zh-hans-mo,chinese (simplified macau)\",\r\n\t\t// blink\r\n\t\t'ee-tg,éwé (togo)',\r\n\t\t'uz-arab,uzbek (arabic)',\r\n\t]\r\n\tlet expanded = listExtra.concat(list)\r\n\texpanded = expanded.filter(function(item, position) {return expanded.indexOf(item) === position})\r\n\t// testing\r\n\t//expanded =['nso,northern sotho','tn,tswana','lmo,lombard','kl,greenlandic',]\r\n\texpanded.sort()\r\n\toLists['expanded'] = expanded\r\n\r\n\tlegend()\r\n\tif (isSupportedValues) {\r\n\t\t// FF93+: supportedValuesOf\r\n\t\taTimezonesSupported = Intl.supportedValuesOf('timeZone')\r\n\t\taTimezonesSupported.sort()\r\n\t\taTimezonesSupported.forEach(function(tz) {\r\n\t\t\tif (!tzIgnore.includes(tz)) {\r\n\t\t\t\taTimezones.push(tz)\r\n\t\t\t\taTimezonesLowerCase.push(tz.toLowerCase())\r\n\t\t\t}\r\n\t\t\taTimezonesSupportedLowerCase.push(tz.toLowerCase())\r\n\t\t})\r\n\t}\r\n\r\n\tif (isSupported && isSupportedValues) {\r\n\t\ttry {dom.timezone.innerHTML = Intl.DateTimeFormat().resolvedOptions().timeZone} catch(e) {}\r\n\t\tsetBtn('min')\r\n\t\tdom.results = 'calculating ...'\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main('min')\r\n\t\t}, 50)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/duration.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=600\">\n\t<title>durationformat</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\n\t\tul.main {margin-left: -20px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\n\t\t<tr>\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\n\t\t</tr>\n\t</table>\n\n\t<table id=\"tb4\">\n\t\t<col width=\"200px\"><col>\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">durationformat\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy in Intl.DurationFormat</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\n\t\t\t<br><br><hr><br>\n\t\t\t\t<span id =\"results\"></span>\n\t\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nvar list = gLocales,\n\taLegend = [],\n\taLocales = [],\n\toTestData = {},\n\tisSupported = false,\n\tlocalesHashAll = \"\" // to compare min to\n\n// when numberingSystem is undefined, what does it use exactly?\n\t// undefined = 216\n\t//       all = 216\n\t// anyway, looks like we don't care about per numbering system for entropy\n\nlet aNumsys = [\n'adlm', // 210\n'ahom', // 210\n'arab', // 187\n'arabext', // 188\n'bali', // 210\n'beng', // 210\n'bhks', // 210\n'brah', // 210\n'cakm', // 210\n'cham', // 210\n'deva', // 210\n'diak', // 210\n'fullwide', // 209\n'gara', // 210\n'gong', // 210\n'gonm', // 210\n'gujr', // 210\n'gukh', // 210\n'guru', // 210\n'hanidec', // 210\n'hmng', // 210\n'hmnp', // 210\n'java', // 210\n'kali', // 210\n'kawi', // 210\n'khmr', // 210\n'knda', // 210\n'krai', // 210\n'lana', // 210\n'lanatham', // 210\n'laoo', // 210\n'latn', // 210\n'lepc', // 210\n'limb', // 210\n'mathbold', // 210\n'mathdbl', // 210\n'mathmono', // 210\n'mathsanb', // 210\n'mathsans', // 210\n'mlym', // 210\n'modi', // 210\n'mong', // 210\n'mroo', // 210\n'mtei', // 210\n'mymr', // 210\n'mymrepka', // 210\n'mymrpao', // 210\n'mymrshan', // 210\n'mymrtlng', // 210\n'nagm', // 210\n'newa', // 210\n'nkoo', // 210\n'olck', // 210\n'onao', // 210\n'orya', // 210\n'osma', // 210\n'outlined', // 210\n'rohg', // 210\n'saur', // 210\n'segment', // 210\n'shrd', // 210\n'sind', // 210\n'sinh', // 210\n'sora', // 210\n'sund', // 210\n'sunu', // 210\n'takr', // 210\n'talu', // 210\n'tamldec', // 210\n'telu', // 210\n'thai', // 210\n'tibt', // 210\n'tirh', // 210\n'tnsa', // 210\n'undefined', // 216\n'vaii', // 210\n'wara', // 210\n'wcho', // 210\n]\n\nfunction log_console(name) {\n\tlet hash = mini(sDetail[name])\n\tif (name == \"locales\") {\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\n\t} else {\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\n\t}\n}\n\nfunction legend() {\n\t// build once\n\tif (aLegend.length == 0) {\n\t\tlist.sort()\n\t\tfor (let i = 0 ; i < list.length; i++) {\n\t\t\tlet str = list[i].toLowerCase()\n\t\t\tlet code = str.split(\",\")[0].trim()\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\n\t\t\tlet go = Intl.DurationFormat.supportedLocalesOf([code]).length > 0\n\t\t\tif (go) {\n\t\t\t\taLocales.push(code)\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\n\t\t\t\tif (name.includes(\"(\")) {\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\n\t\t\t\t\tlet name1 = name.substring(\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \n\t\t\t\t\t\tname.lastIndexOf(\")\")\n\t\t\t\t\t)\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\n\t\t\t\t\tif (isSplit) {\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name0 +\" \"+ name1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\n\t\t\t}\n\t\t}\n\t}\n\t// output\n\tlet\theader = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\n}\n\nfunction testitems() {\n\t//loop each numsys on it's own\n\tdom.perf = \"\"\n\tdom.results = \"\"\n\toTestData = {}\n\tsetTimeout(function() {\n\t\ttry {\n\t\t\taNumsys.forEach(function(item){\n\t\t\t\t// allow testing arrays of scripts\n\t\t\t\tlet testarray = 'object' == typeof item ? item : [item]\n\t\t\t\trun_main('all', testarray, true)\n\t\t\t})\n\t\t} catch(e) {\n\t\t\tconsole.log(e)\n\t\t}\n\t}, 5)\n}\n\nfunction run_main(method, aTest, isLoop = false) {\n\tif (!isSupported) {return}\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/DurationFormat\n\tlet t0 = performance.now()\n\tlet oData = {}, oTempData = {}\n\tlet spacer = \"<br><br>\"\n\tif (!isLoop) {\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t}\n\n\tlet styles = ['digital','long','narrow','short']\n\tlet options = {\n\t\t'0000': {'years': 0, 'months': 0,'weeks': 0,'days': 0,'hours': 0,'minutes': 0,'seconds': 0,'milliseconds': 0,'microseconds': 0,'nanoseconds': 0},\n\t\t'0001': {'years': 1, 'months': 1,'weeks': 1,'days': 1,'hours': 1,'minutes': 1,'seconds': 1,'milliseconds': 1,'microseconds': 1,'nanoseconds': 1},\n\t\t'0002': {'years': 2, 'months': 2,'weeks': 2,'days': 2,'hours': 2,'minutes': 2,'seconds': 2,'milliseconds': 2,'microseconds': 2,'nanoseconds': 2},\n\t\t'1k': {'years': 1000, 'months': 1000,'weeks': 1000,'hours': 1000,'minutes': 1000,'seconds': 1000,'milliseconds': 1000,'microseconds': 1000,'nanoseconds': 1000},\n\t\t//'10k': {'years': 10000, 'months': 10000,'weeks': 10000,'hours': 10000,'minutes': 10000,'seconds': 10000,'milliseconds': 10000,'microseconds': 10000,'nanoseconds': 10000},\n\t\t'mixed': {'years': 0, 'months': 1,'weeks': 2,'days': 3,'hours': 4,'minutes': 5,'seconds': 6,'milliseconds': 7,'microseconds': 8,'nanoseconds': 9},\n\t\t'split': {'years': 1, 'nanoseconds': 1},\n\t\t// there's also how things are joined e.g. \"0 y e 1 ns\" (zero years and 1 nanosecond)\n\t}\n\tlet digitaloptions = {\n\t\t'0000': {'hours': 0,'minutes': 0,'seconds': 0,'milliseconds': 0,'microseconds': 0,'nanoseconds': 0},\n\t\t'0001': {'hours': 1,'minutes': 1,'seconds': 1,'milliseconds': 1,'microseconds': 1,'nanoseconds': 1},\n\t\t'0002': {'hours': 2,'minutes': 2,'seconds': 2,'milliseconds': 2,'microseconds': 2,'nanoseconds': 2},\n\t}\n\tlet tests = {}\n\n\tif ('all' == method) {\n\t\tstyles.forEach(function(s) {\n\t\t\tif ('digital' == s) {tests[s] = digitaloptions} else {tests[s] = options}\n\t\t})\n\t\t//console.log(tests)\n\t} else if (method == \"min\") {\n\t\t// all styles add entropy: digital: 13 | long: 4 | narrow: 5 | short: 2\n\t\ttests = {\n\t\t\t'digital': {'a': {'milliseconds': 1}},\n\t\t\t'long': {'a': {'years': 1, 'microseconds': 1}, 'b': {'seconds': 2}},\n\t\t\t'narrow': {'a': {'years': 1, 'months': 2, 'seconds': 1, 'microseconds': 1000}},\n\t\t\t'short': {'a': {'days': 2, 'seconds': 2, 'nanoseconds': 1}},\n\t\t}\n\t}\n\n\tlet numsys = [undefined]\n\tnumsys = undefined == aTest ? numsys : aTest\n\tnumsys.sort()\n\n\t/* // which split makes the difference\n\tif ('all' == method) {\n\t\t//delete tests.long.split\n\t\tdelete tests.narrow.split // narrow is useless\n\t\tdelete tests.short.split // short is useless\n\t\tconsole.log(tests)\n\t}\n\t//*/\n\t//console.log(tests)\n\n\ttry {\n\t\taLocales.forEach(function(code) {\n\t\t\tlet oStyles = {}\n\t\t\tObject.keys(tests).forEach(function(s){ // each style\n\t\t\t\toStyles[s] = {}\n\t\t\t\tnumsys.forEach(function(ns){ // each numberingsystem\n\t\t\t\t\toStyles[s][ns] = {}\n\t\t\t\t\t// set formatter once per code + style\n\t\t\t\t\t// always display so we catch 0's\n\t\t\t\t\tlet nsvalue = ns\n\t\t\t\t\t// make sure undefined is nsvalue and \"undefined\" is ns (our key)\n\t\t\t\t\tif ('undefined' == ns || undefined == ns) {\n\t\t\t\t\t\tnsvalue = undefined\n\t\t\t\t\t\tns = 'undefined'\n\t\t\t\t\t}\n\n\t\t\t\t\tlet formatoptions, splitoptions\n\t\t\t\t\tif ('digital' == s) {\n\t\t\t\t\t\tformatoptions = {\n\t\t\t\t\t\t\t'style': s, \n\t\t\t\t\t\t\t'numberingSystem': nsvalue,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsplitoptions = {\n\t\t\t\t\t\t\t'style': s, \n\t\t\t\t\t\t\t'numberingSystem': nsvalue,\n\t\t\t\t\t\t}\t\t\t\t\t\t\n\t\t\t\t\t\tformatoptions = {\n\t\t\t\t\t\t\t'style': s, \n\t\t\t\t\t\t\t'numberingSystem': nsvalue,\n\t\t\t\t\t\t\t'yearsDisplay': 'always',\n\t\t\t\t\t\t\t'monthsDisplay': 'always',\n\t\t\t\t\t\t\t'weeksDisplay': 'always',\n\t\t\t\t\t\t\t'daysDisplay': 'always',\n\t\t\t\t\t\t\t'hoursDisplay': 'always',\n\t\t\t\t\t\t\t'minutesDisplay': 'always',\n\t\t\t\t\t\t\t'secondsDisplay': 'always',\n\t\t\t\t\t\t\t'millisecondsDisplay': 'always',\n\t\t\t\t\t\t\t'microsecondsDisplay':'always',\n\t\t\t\t\t\t\t'nanosecondsDisplay': 'always',\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ('min' == method) {\n\t\t\t\t\t\t\t// we only seem to need one lot of zero\n\t\t\t\t\t\t\tformatoptions = {\n\t\t\t\t\t\t\t\t'style': s, \n\t\t\t\t\t\t\t\t'numberingSystem': nsvalue,\n\t\t\t\t\t\t\t\t'yearsDisplay': 'always',\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlet formatter = new Intl.DurationFormat(code, formatoptions)\n\t\t\t\t\tlet splitformatter = new Intl.DurationFormat(code, splitoptions)\n\t\t\t\t\t// for each test\n\t\t\t\t\tObject.keys(tests[s]).sort().forEach(function(t){\n\t\t\t\t\t\tif ('split' == t) {\n\t\t\t\t\t\t\toStyles[s][ns][t] = splitformatter.format(tests[s][t])\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toStyles[s][ns][t] = formatter.format(tests[s][t])\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\n\t\t\t//if (code == 'yo-bj') {console.log(code, hash, oStyles)}\n\n\t\t\tif (oTempData[hash] == undefined) {\n\t\t\t\toTempData[hash] = {}\n\t\t\t\toTempData[hash][\"locales\"] = [code]\n\t\t\t\tnumsys.forEach(function(ns){ // each numberingsystem\n\t\t\t\t\toTempData[hash][ns] = {}\n\t\t\t\t\tObject.keys(tests).sort().forEach(function(s){\n\t\t\t\t\t\toTempData[hash][ns][s] = {}\n\t\t\t\t\t\tObject.keys(tests[s]).sort().forEach(function(t){\n\t\t\t\t\t\t\toTempData[hash][ns][s][t] = oStyles[s][ns][t]\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\toTempData[hash][\"locales\"].push(code)\n\t\t\t}\n\t\t})\n\t\t// handle empty\n\t\tif (Object.keys(oTempData).length == 0) {\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\n\t\t\treturn\n\t\t}\n\t\t// order object\n\t\tfor (const n of Object.keys(oTempData).sort()) {\n\t\t\toData[n] = {}\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\n\t\t\t\tif (p == \"locales\") {\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\n\t\t\t\t} else {\n\t\t\t\t\toData[n][p] = oTempData[n][p]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet localeGroups = [], displaylist = []\n\t\tfor (const k of Object.keys(oData)) {\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\n\t\t\tif (!isLoop) {\n\t\t\t\tlet displayData = []\n\t\t\t\tfor (const ns of Object.keys(oData[k])) {\n\t\t\t\t\tif ('locales' !== ns) {\n\t\t\t\t\t\tdisplayData.push(s14 + 'numberingSystem: '+ns + sc)\n\t\t\t\t\t\tfor (const s of Object.keys(oData[k][ns])) {\n\t\t\t\t\t\t\tif ('min' == method) {\n\t\t\t\t\t\t\t\tlet sInitial = s.slice(0,1).toUpperCase()\n\t\t\t\t\t\t\t\tlet aTests = []\n\t\t\t\t\t\t\t\tfor (const t of Object.keys(oData[k][ns][s])) {\n\t\t\t\t\t\t\t\t\taTests.push(oData[k][ns][s][t])\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdisplayData.push('<li>'+ s16 + sInitial +\": \"+ sc + aTests.join(' | ') +\"</li>\")\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdisplayData.push('<li>'+ s16 + s +\": \"+ sc)\n\t\t\t\t\t\t\t\tfor (const t of Object.keys(oData[k][ns][s])) {\n\t\t\t\t\t\t\t\t\tlet x = parseInt(t)\n\t\t\t\t\t\t\t\t\tif (isNaN(x)) {x = t}\n\t\t\t\t\t\t\t\t\tif ('1k' == t) (x = t)\n\t\t\t\t\t\t\t\t\tdisplayData.push('<li>'+ s12 + x +\": \"+ sc + oData[k][ns][s][t] +\"</li>\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdisplayData.push('</li>')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet strData = displayData.join('')\n\t\t\t\tif ('all' == method) {strData = '<details>' + strData +'</details>'}\n\t\t\t\tdisplaylist.push(\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\n\t\t\t\t\t+ \"<ul class='main'>\"+ strData\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif (isLoop) {\n\t\t\tlet testCount = localeGroups.length\n\t\t\t// record it\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\n\t\t\toTestData[testCount].push(numsys)\n\t\t\t//console.log(testCount, aUsed)\n\t\t\t/*\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\n\t\t\t\t+ s16 + testCount + sc +': '+ '[\\''+ aUsed.join('\\', \\'') +'\\']'\n\t\t\t//*/\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\n\t\t\t\t+ '\\''+ numsys.join('\\', \\'') +'\\', // ' + testCount\n\t\t\treturn\n\t\t}\n\n\t\t// hashes + btns\n\t\tsDetail[\"results\"] = oData\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\n\t\tlet resultsHash = mini(oData)\n\t\tlocaleGroups.sort()\n\t\tsDetail[\"locales\"] = localeGroups\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\n\t\tlet localesHash = mini(localeGroups)\n\n\t\t/*\n\t\tconsole.log(localesHash)\n\t\tconsole.log(localeGroups)\n\t\tconsole.log(resultsHash)\n\t\tconsole.log(oData)\n\t\tconsole.log(oTempData)\n\t\t//*/\n\n\t\t// notations\n\t\tlet localesMatch = \"\"\n\t\tif (method == \"all\") {\n\t\t\tlocalesHashAll = localesHash\n\t\t\t// notate new if 140+\n\t\t\tif (isVer > 139) {\n\t\t\t\t// results\n\t\t\t\tif (resultsHash == '92b05a5c') { // FF151+\n\t\t\t\t} else if (resultsHash == '319ff3fd') { // FF147-150\n\t\t\t\t} else if (resultsHash == '1294206a') { // FF140-146\n\t\t\t\t} else {resultsHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t\t// locales\n\t\t\t\tif (localesHash == '97048ba7') { // FF151+: 219\n\t\t\t\t} else if (localesHash == '91d4e56b') { // FF147-150: 218\n\t\t\t\t} else if (localesHash == 'c5d9dc08') { // FF140-146: 216\n\t\t\t\t} else if (isFF) {localesHash += ' '+ zNEW\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (method == \"min\") {\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\n\t\t}\n\t\t// display\n\t\tlet display = s4 + method.toUpperCase() +\": \"\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\n\t\t// perf\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\n\t}\n}\n\nfunction run(method) {\n\tif (isSupported) {\n\t\t//reset\n\t\tsetBtn(method)\n\t\tdom.perf = \"\"\n\t\tdom.results = \"\"\n\t\t// delay so users see change and allow paint\n\t\tsetTimeout(function() {\n\t\t\trun_main(method)\n\t\t}, 1)\n\t}\n}\n\nfunction setBtn(method) {\n\t// reset btns\n\tlet items = document.getElementsByClassName(\"btn8\")\n\tfor (let i=0; i < items.length; i++) {\n\t\titems[i].classList.add(\"btn4\")\n\t\titems[i].classList.remove(\"btn8\")\n\t}\n\t// set btn\n\tlet el = document.getElementById(\"b\"+ method)\n\tel.classList.add(\"btn8\")\n\tel.classList.remove(\"btn4\")\n}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tget_isVer()\n\tbuildnav()\n\n\t// add additional locales to core locales for this test\n\tlet aListExtra = [\n\"ar-bh,arabic (bahrain)\",\n\"ar-dz,arabic (algeria)\",\n\"ar-sa,arabic (saudi arabia)\",\n\"bs-cyrl,bosnian (cyrillic)\",\n\"de-at,german (austria)\",\n\"de-ch,german (switzerland)\",\n\"en-at,english (austria)\",\n'en-au,english (australia)',\n\"en-ca,english (canada)\",\n\"en-ch,english (switzerland)\",\n\"en-dk,english (denmark)\",\n\"en-fi,english (finland)\",\n\"en-se,english (sweden)\",\n\"es-419,spanish (latin america and the caribbean)\",\n\"es-ar,spanish (argentina)\",\n\"es-bo,spanish (bolivia)\",\n\"es-co,spanish (colombia)\",\n\"es-cr,spanish (costa rica)\",\n\"es-do,spanish (dominican republic)\",\n\"es-mx,spanish (mexico)\",\n\"es-py,spanish (paraguay)\",\n\"es-us,spanish (united states)\",\n\"ff-adlm,fulah (adlam)\",\n\"fr-ca,french (canada)\",\n'fr-ch,french (switzerland)',\n\"fr-lu,french (luxembourg)\",\n\"hi-latn,hindi (latin)\",\n\"it-ch,italian (switzerland)\",\n'kk-cn,kazakh (china)',\n\"kok-latn,konkani (latin)\",\n\"ks-deva,kashmiri (devanagari)\",\n\"kxv-telu,kuvi (telugu)\",\n\"ms-bn,malay (brunei)\",\n\"ms-id,malay (indonesia)\",\n\"ps-pk,pashto (pakistan)\",\n\"pt-ao,portuguese (angola)\",\n\"pt-ch,portuguese (switzerland)\",\n\"pt-pt,portuguese (portugal)\",\n\"qu-bo,quechua (bolivia)\",\n\"ro-md,romanian (moldova)\",\n\"ru-ua,russian (ukraine)\",\n\"sr-ba,serbian (bosnia & herzegovina)\",\n\"sr-cyrl-me,serbian (cyrillic montenegro)\",\n\"sr-latn,serbian (latin)\",\n\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\n\"sv-fi,swedish (finland)\",\n\"sw-cd,swahili (congo kinshasa)\",\n\"sw-ke,swahili (kenya)\",\n\"ur-in,urdu (india)\",\n\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\n\"yo-bj,yoruba (benin)\",\n\"yue-cn,cantonese (china)\",\n\n//* blink\n'en-001,english',\n'az-cyrl,azerbaijani (cyrillic)',\n'pa-arab,punjabi (arabic)',\n\t]\n\tlist = list.concat(aListExtra)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\t//list = ['en,english','fr,french','de,german']\n\t//list = ['en,english']\n\t//list = ['wo,wolof','tg,tajik','ie,interlingue','ln,lingala'] // example of split durations adding 'and\"\n\t//list = ['af','en','fr','he']\n\tlist.sort()\n\n\tlegend()\n\t// check supported\n\ttry {\n\t\tlet test = new Intl.DurationFormat('en').resolvedOptions()\n\t\tisSupported = true\n\t} catch(e) {\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\n\t}\n\t// check bigint support: FF68+\n\ttry {\n\t\tlet y = BigInt(\"9999999999999999\")\n\t\tisBigIntSupported = true\n\t} catch(e) {}\n\tsetBtn(\"all\")\n\tsetTimeout(function() {\n\t\trun_main(\"all\")\n\t}, 100)\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/elementfont.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>elements: fonts</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 480px; max-width: 780px;}\n\t</style>\n</head>\n\n<body>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb15\">\n\t\t<col width=\"15%\"><col width=\"85%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">elements: fonts\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t\t<div class=\"nav-down\"><span class=\"perf\" id=\"locale\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">basic font element test, using clientrect, with different variables</span>\n\t\t\t<br><br>\n\t\t\t<span class=\"btnfirst btn15\" onClick=\"run()\">[ run ]</span>\n\t\t\t<input type=\"checkbox\" name=\"expand\" style=\"margin: 0; height: 12px\" onClick='run()'>\n\t\t\t<span class=\"no_color\">expand font-styles</span>\n\t\t\t&nbsp; <b>|</b> &nbsp; \n\t\t\t<span class=\"btn btn15\" onClick=\"run_lang()\">[ scripts ]</span>\n\t\t</td></tr>\n\t\t<tr>\n\t\t\t<td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<hr><br>\n\t\t\t<span class=\"no_color c mono spaces\" id=\"results\"></span></td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n\n<script>\n'use strict';\nlet isLanguage = '', isLocale = ''\nlet sizeA = ['3.9pt','141.7pt','266.6pt',]\nlet sizeB = ['3.9pt','xx-small','x-small','small','medium','large','x-large','xx-large','xxx-large','141.7pt','266.6pt']\n\nlet oList = {\n\t// keep in sorted order\n\t// https://developer.mozilla.org/en-US/docs/Web/CSS/generic-family\n\t'cursive': sizeA,\n\t'emoji': sizeA, // windows: emoji = serif\n\t'fangsong': sizeA,\n\t'fantasy': sizeA, // windows: fantasy = sans\n\t'math': sizeA,\n\t'monospace': sizeB,\n\t'sans-serif': sizeB,\n\t'serif': sizeB,\n\t'system-ui': sizeA,\n}\nlet aExpand = [\n\t'none',\n\t'ui-monospace',\n\t\"ui-sans-serif\",\n\t'ui-serif',\n\t\"ui-rounded\"\n]\n\nlet bulkData = {}\n\nfunction get_element_font(lang = '') {\n\tlet t0 = performance.now()\n\tlet oUsed = {}\n\tfor (const k of Object.keys(oList)) {oUsed[k] = oList[k]}\n\tif (dom.expand.checked) {aExpand.forEach(function(k){oUsed[k] = sizeB})}\n\n\tconst id = 'element-fp'\n\tlet hash, data = {}, method\n\ttry {\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\n\t\tlet oData = {}, tmpobj = {}\n\t\tfor (const k of Object.keys(oUsed).sort()) {\n\t\t\tlet sizes = oUsed[k]\n\t\t\tlet tmpsizes = []\n\t\t\tsizes.forEach(function(size) {\n\t\t\t\t// create + measure each individually as preceeding elements can affect subsequent ones\n\t\t\t\tdom[id].innerHTML = \"<div lang='\"+ lang +\"' style='font-size:\"+ size +\";' class='\"+ k +\"'>...</div>\"\n\t\t\t\tlet target = dom[id].firstChild\n\t\t\t\t// method\n\t\t\t\tmethod = target.getBoundingClientRect()\n\t\t\t\t// width+height = max entropy\n\t\t\t\ttmpsizes.push([size, method.width, method.height, method.x, method.y])\n\t\t\t})\n\t\t\tlet sizehash = mini(tmpsizes)\n\t\t\tif (oData[sizehash] == undefined) {oData[sizehash] = {data: tmpsizes, group: [k]}\n\t\t\t} else {oData[sizehash].group.push(k)}\n\t\t}\n\t\t// group by styles\n\t\tfor (const k of Object.keys(oData)){data[oData[k].group.join(' ')] = oData[k].data}\n\t\tlet count = Object.keys(data).length\n\t\thash = mini(data)\n\t\tdom.results.innerHTML = s15 + hash + sc +'<br>'+ json_highlight(data, 105)\n\t} catch(e) {\n\t\tdom.results.innerHTML = e+''\n\t}\n\t// remove element\n\tremoveElementFn(id)\n\tdom.perf.innerHTML = Math.round(performance.now() -t0) +\" ms\"\n}\n\ntry {isLocale = Intl.DateTimeFormat().resolvedOptions().locale} catch(e) {isLocale = zErr}\ntry {isLanguage = navigator.language} catch(e) {isLanguage = zErr}\ntry {dom.locale.innerHTML = isLanguage +' | '+ isLocale} catch(e) {console.log(e)}\n\nfunction run() {\n\tdom.results = \"\"\n\tdom.perf = \"\"\n\tsetTimeout(function() {\n\t\tget_element_font()\n\t}, 170)\n}\ndom.expand.checked = false\nget_element_font()\n\n//make it easy to check all zoom values\nif (isFile) {\n\t//window.addEventListener(\"resize\", get_element_font)\n}\n\nfunction run_lang() {\n\tlet t0 = performance.now()\n\tbulkData = {}\n\tlet oUsed = {}\n\t// unique measurement sets per style: this is all we need: = same on windows FF as sizeB\n\t\t// windows TB almost the same: 'x-large' = +1 sans-serif | 'xxx-large' = +1 monospace\n\t// the bigger the better it seems\n\n\t// perf is atrocious at 100ms\n\tlet sizes = ['3.6pt','947.7pt']\n\t// one small helps, and one large but not too large seems sufficient (at least on windows)\n\t\t// 947.7pt is better than 862.3 - but 10k+ is a disaster and 1137.7pt is slightly worse than 947.7\n\t\t// it's just going to vary slightly with DPR and locale/language and platform/fonts\n\t\t// the above is good: 1 small, 1 large\n\n\n\tfor (const k of Object.keys(oList)) {oUsed[k] = oList[k]}\n\n\t// one of each script (from default script sizes)\n\tlet aList = [\n\t\t'bn','bo','en','gu','he','hi','ja','ka','km','kn','ko','ml','or','pa','si','ta','te','th','x-math','zh-CN','zh-TW',\n\t\t// these make no diff on win FF, but some do on win TB, etc, so always include all 29 scripts\n\t\t'ar','cr','el','gez','hy','my','ru','zh-HK',\n\t\t// blank seems to be useless, they don't indicate anything such as locale/applang AFAICT\n\t\t\t// TB: this always matches en: is this because it's a en-US build\n\t\t'',\n\t]\n\taList.sort()\n\t/* WINDOWS FF: (sizeB)\n\t\tfa405cbc\n\t\t{\n\t\t\t\"cursive\": 6,\n\t\t\t\"emoji\": 20,\n\t\t\t\"fangsong\": 2,\n\t\t\t\"fantasy\": 2,\n\t\t\t\"monospace\": 18,\n\t\t\t\"sans-serif\": 15,\n\t\t\t\"serif\": 21,\n\t\t\t\"system-ui\": 4\n\t\t}\n\n\tWINDOWS TB:\n\t\t075fbad5\n\t\t{\n\t\t\t\"cursive\": 2,\n\t\t\t\"emoji\": 21,\n\t\t\t\"fangsong\": 21,\n\t\t\t\"fantasy\": 2,\n\t\t\t\"monospace\": 17,\n\t\t\t\"sans-serif\": 17,\n\t\t\t\"serif\": 19,\n\t\t\t\"system-ui\": 21\n\t\t}\n\n\tso: at this point I think we get enough entropy just by adding all scripts to FF serif and \n\n\t*/\n\n\n\t// ToDo: group data by sizes and by script (we already have it by style)\n\n\tconst id = 'element-fp'\n\ttry {\n\t\tconst doc = document\n\t\tconst div = doc.createElement('div')\n\t\tdiv.setAttribute('id', id)\n\t\tdoc.body.appendChild(div)\n\n\t\t// populate our object keys\n\t\tfor (const style of Object.keys(oUsed).sort()) {bulkData[style] = {}}\n\t\t//group by style then hash of the sizes\n\t\tlet method\n\t\taList.forEach(function(lang) {\n\t\t\tfor (const style of Object.keys(oUsed).sort()) {\n\t\t\t\tlet tmpsizes = []\n\t\t\t\tsizes.forEach(function(size) {\n\t\t\t\t\t// create + measure each individually as preceeding elements can affect subsequent ones\n\t\t\t\t\tif ('' == lang) {\n\t\t\t\t\t\tdom[id].innerHTML = \"<div style='font-size:\"+ size +\";' class='\"+ style +\"'>...</div>\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdom[id].innerHTML = \"<div lang='\"+ lang +\"' style='font-size:\"+ size +\";' class='\"+ style +\"'>...</div>\"\n\t\t\t\t\t}\n\t\t\t\t\tlet target = dom[id].firstChild\n\t\t\t\t\t// method\n\t\t\t\t\tmethod = target.getBoundingClientRect()\n\t\t\t\t\t// width+height = max entropy\n\t\t\t\t\ttmpsizes.push([size, method.width, method.height, method.x, method.y])\n\t\t\t\t})\n\t\t\t\tlet hash = mini(tmpsizes)\n\t\t\t\tif (undefined == bulkData[style][hash]) {\n\t\t\t\t\tbulkData[style][hash] = {'data': tmpsizes, 'group': [lang]}\n\t\t\t\t} else {\n\t\t\t\t\tbulkData[style][hash].group.push(lang)\n\t\t\t\t}\n\t\t\t\tif ('en' == lang) {\n\t\t\t\t\tbulkData[style][hash]['en'] = true\n\t\t\t\t} else if ('' == lang) {\n\t\t\t\t\tbulkData[style][hash]['blank'] = true\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t} catch(e) {\n\t\tdom.results.innerHTML = e+''\n\t}\n\tlet perf = performance.now() - t0\n\t// remove element\n\tremoveElementFn(id)\n\tconsole.log(perf, 'ms |', mini(bulkData), '|', aList.length)\n\tlet oCounts = {}\n\tfor (const k of Object.keys(bulkData)) {oCounts[k] = Object.keys(bulkData[k]).length}\n\tconsole.log(bulkData)\n\n\tdom.results.innerHTML = s15 + mini(oCounts) + sc +'<br>'+ json_highlight(oCounts, 105)\n\t\t+ '<br><br>'+ s15 + mini(bulkData) + sc +'<br>'+ json_highlight(bulkData, 105)\n\tdom.perf.innerHTML = Math.round(perf) +\" ms\"\n\n}\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/elementforms.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>elements: forms</title>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<link rel=\"stylesheet\" href=\"chrome://global/locale/intl.css\">\n  <script src=\"testglobals.js\"></script>\n  <script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 780px; max-width: 780px}\n\t\t/* tweak overlay for this PoC */\n\t\t#overlay {\n\t\t\twidth: 500px;\n\t\t\tmin-width: 300px;\n\t\t}\n\t\t#overlay div.pad {padding: 0px 25px 15px;}\n\t\t.mozInputs > input,\n\t\t.mozInputs > select,\n\t\t.mozInputs > progress {\n\t\t\t-moz-appearance: none;\n\t\t\t-webkit-appearance: none;\n\t\t\tappearance: none;\n\t\t}\n\t\t.revert {\n\t\t\tall: revert;\n\t\t}\n\t\t/** custom input styles **/\n\t\t/*details, input, option {font-size: 45px;}*/\n\n\t</style>\n</head>\n\n<body>\n\t<div id=\"element-fp\"></div>\n\t<div id=\"modaloverlay\" onClick=\"hide_overlays()\"></div>\n\t<div id=\"overlay\">\n\t\t<div class=\"mono spaces pad\"><span id=\"overlaytop\"></span>\n\t\t\t<div style=\"text-align: right;\" id=\"overlaybuttons\"><span class='btn0 btnc' onClick='hide_overlays()'>[CLOSE]</span></div>\n\t\t\t<p>These are examples only. The real test creates, measures, and then removes each element individually, as the order of and/or the presence of other elements can affect results.</p>\n\t\t\t<br><input type='radio' id=\"demoNative\" onchange=\"style_demo()\" name=\"demostyle\" checked> NATIVE STYLED <input type='radio' onchange=\"style_demo()\" name=\"demostyle\"> UNSTYLED\n\t\t\t<div class=\"normalized\">\n\t\t\t\t<p class=\"native\">\n\t\t\t\t\t<input style=\"display:inline;\" type=\"button\"> &nbsp;\n\t\t\t\t\t<input style=\"display:inline;\" type=\"checkbox\"> &nbsp;\n\t\t\t\t\t<input style=\"display:inline;\" type=\"radio\"> &nbsp;\n\t\t\t\t\t<input style=\"display:inline;\" type=\"color\"> &nbsp;\n\t\t\t\t\t<input style=\"display:inline;\" type=\"reset\"> &nbsp;\n\t\t\t\t\t<select style=\"display:inline;\"><option></option></select> &nbsp;\n\t\t\t\t</p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"date\"> &nbsp; <input style=\"display:inline;\" type=\"time\"></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"datetime-local\"></p>\n\t\t\t\t<!-- week/month for chrome-->\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"month\"> &nbsp; <input style=\"display:inline;\" type=\"week\"></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"file\" webkitdirectory directory></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"file\" ></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"file\" multiple=\"\"></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline; color: var(--test0);\" type=\"image\"></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"number\"> &nbsp; <input type=\"submit\" style=\"display:inline;\"></p>\n\t\t\t\t<p class=\"native\">\n\t\t\t\t\t<select style=\"display:inline;\" multiple=\"\"><option></option></select> &nbsp;\n\t\t\t\t\t<select style=\"display:inline;\" multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option></select> &nbsp;\n\t\t\t\t\t<select style=\"display:inline;\" multiple=\"\"><option>Mōá?-&#xffff;</option></select>\n\t\t\t\t</p>\n\t\t\t\t<p class=\"native\"><details style=\"display:inline;\"></details></p>\n\t\t\t\t<p class=\"native\"><input style=\"display:inline;\" type=\"range\" min=\"0\" max=\"2\" value=\"1\"></p>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<!-- app lang -->\n\t<table id=\"tb15\">\n\t\t<col width=\"25%\"><col width=\"75%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">elements: forms\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t\t<div class=\"nav-down\"><span class=\"perf\" id=\"locale\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"btn15 btnfirst\" onClick=\"run()\">[ run ]</span>\n\t\t\t<span class=\"btn15 btn\" onClick=\"run(true)\">[ TZP ]</span>\n\t\t\t<span class=\"no_color\">\n\t\t\t\t<input id=\"optVertical\" checked type=\"checkbox\"> &nbsp; vertical\n\t\t\t\t<input id=\"optUnstyled\" type=\"checkbox\"> &nbsp; unstyled &nbsp; |\n\t\t\t\t<input id=\"optEverything\" onChange=\"style_everything()\" type=\"checkbox\"> &nbsp; everything &nbsp; | &nbsp;\n\t\t\t\t<a class='blue' href='https://searchfox.org/mozilla-central/source/dom/locales/en-US/chrome/layout/HtmlForm.properties'\n\t\t\t\t\ttarget='blank'>searchfox HtmlForm</a>\n\t\t\t\t<span class=\"btn btn0\" onCLick=\"show_overlay()\">[ example ]</span>\n\t\t\t</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"><hr><br></td></tr>\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<div class=\"c mono spaces no_color\" id=\"elements\"></div>\n\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nlet isLocale = \"\"\nlet isTestSets = false\nlet oData = {}\ndom.optVertical.checked = true\ndom.optUnstyled.checked = false\ndom.optEverything.checked = false\n\ndom.demoNative.checked = true\nfunction style_demo() {\n\t// flip style in examples\n\tlet addstyle = dom.demoNative.checked ? \"native\" : \"mozInputs\"\n\tlet remstyle = addstyle == \"native\" ? \"mozInputs\" : \"native\"\n\ttry {\n\t\tlet items = document.querySelectorAll(\".\"+ remstyle)\n\t\tfor (let i=0; i < items.length; i++) {\n\t\t\titems[i].classList.add(addstyle)\n\t\t\titems[i].classList.remove(remstyle)\n\t\t}\n\t} catch(e) {}\n}\n\nfunction style_everything() {\n\tlet isEverything = dom.optEverything.checked\n\tdom.optVertical.disabled = isEverything\n\tdom.optUnstyled.disabled = isEverything\n}\nstyle_everything()\n\ntry {\n\t// escape to close\n\tdocument.onkeydown = function(evt) {\n\t\tevt = evt || window.event;\n\t\tvar isEscape = false;\n\t\tif (\"key\" in evt) {\n\t\t\tisEscape = (evt.key === \"Escape\" || evt.key === \"Esc\");\n\t\t} else {\n\t\t\tisEscape = (evt.keyCode === 27);\n\t\t}\n\t\tif (isEscape) {\n\t\t\thide_overlays()\n\t\t}\n\t}\n\toverlay.addEventListener(\"keydown\", (e) => {\n\t\tconsole.log(e.key)\n\t})\n} catch(e) {\n\tconsole.log(e)\n}\n\nfunction get_elements(isTZP = false) {\n\tlet t0 = performance.now()\n\t// ui.useOverlayScrollbars: 0=on, 1=off\n\t// windows 11 on -> off (en-US)\n\t\t//  empty 29 -> 12 = -17\n\t\t// spaces 49 -> 32 = -17\n\t\t// string 78 -> 61 = -17\n\t\t// matches scrollbar in document/viewport/element\n\t\t// I expect this will _have_ to leak subpixels given we are measuring with clientRect\n\n\tlet tmpData = {}, target\n\tlet isEverything = dom.optEverything.checked\n\tlet aStyles = [], aWritingStyles = []\n\n\tlet params = []\n\tif (isTZP) {\n\t\taWritingStyles = ['vertical-lr']\n\t\taStyles = ['native','unstyled']\n\t\tparams = ['TZP']\n\t} else if (isEverything) {\n\t\taWritingStyles = ['horizontal-tb','vertical-lr']\n\t\taStyles = ['native','unstyled']\n\t\tparams = ['everything']\n\t} else {\n\t\tif (dom.optVertical.checked) {aWritingStyles = ['vertical-lr']} else {aWritingStyles = ['horizontal-tb']}\n\t\tif (dom.optUnstyled.checked) {aStyles = ['unstyled']} else {aStyles = ['native']}\n\t\tparams.push(aWritingStyles[0].slice(0,-3))\n\t\tparams.push(aStyles[0])\n\t}\n\n\tlet oList = {\n\t\tbutton: '', checkbox: '', color: '', date: '', \"datetime-local\": '', file: '',\n\t\timage: '', number: '', radio: '', range: '', reset: '', submit: '', time: '', \n\t\tdetails: '<details></details>',\n\t\tdetails_open: '<details open=\"\">.</details>',\n\t\tprogress: '<progress></progress>',\n\t\tselect: '<select><option></option></select>',\n\t\tselect_empty: '<select multiple=\"\"><option></option></select>',\n\t\tselect_empty_option: '<select multiple=\"\"><option></option></select>',\n\t\tselect_spaces: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\tselect_spaces_option: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\tselect_string: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\tselect_string_option: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\ttextarea: '<textarea></textarea>',\n\t\ttextarea_3x5: '<textarea cols=\"5\" rows=\"3\"></textarea>',\n\t\t// always == file\n\t\tdirectory: '<input webkitdirectory directory type=\"file\">',\n\t\tfiles: '<input multiple=\"\" type=\"file\">',\n\t\t// gecko: should always = number\n\t\tdatetime: '', email: '', month: '', password: '',\n\t\tsearch: '', tel: '', text: '', url: '', week: '',\n\t\t// always 0\n\t\thidden: '',\n\t}\n\tlet oTZP = {\n\t\t'native': {\n\t\t\tbutton: '',\n\t\t\tcheckbox: '',\n\t\t\tcolor: '',\n\t\t\tdate: '',\n\t\t\t'datetime-local': '',\n\t\t\tdetails: '<details></details>',\n\t\t\t'details_open': '<details open=\"\">.</details>',\n\t\t\tfile: '',\n\t\t\timage: '',\n\t\t\tmonth: '',\n\t\t\tnumber: '',\n\t\t\tprogress: '<progress></progress>',\n\t\t\tradio: '',\n\t\t\trange: '',\n\t\t\treset: '',\n\t\t\tselect: '<select><option></option></select>',\n\t\t\tselect_empty: '<select multiple=\"\"><option></option></select>',\n\t\t\tselect_empty_option: '<select multiple=\"\"><option></option></select>',\n\t\t\tselect_spaces: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\t\tselect_spaces_option: '<select multiple=\"\"><option> &nbsp;  &nbsp;  &nbsp; </option>\"</select>',\n\t\t\tselect_string: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\t\tselect_string_option: '<select multiple=\"\"><option>Mōá?-&#xffff;</option></select>',\n\t\t\tsubmit: '',\n\t\t\ttextarea: '<textarea></textarea>',\n\t\t\ttextarea_3x5: '<textarea cols=\"5\" rows=\"3\"></textarea>',\n\t\t\ttime: '',\n\t\t\tweek: '',\n\t\t\t// month + week are same as number but not in chrome | gecko may follow suit\n\t\t},\n\t\t'unstyled': {\n\t\t\t// differ on windows\n\t\t\t// ToDo: check linux/mac/android\n\t\t\tcheckbox: '',\n\t\t\tprogress: '<progress></progress>',\n\t\t\tradio: '',\n\t\t\tselect: '<select><option></option></select>',\n\t\t}\n\t}\n\n\ttry {\n\t\tconst parent = dom['element-fp']\n\t\tlet count = 0\n\t\tlet oSets = {\n\t\t\tsetAll: new Set(),\n\t\t\t// 2\n\t\t\tsetWH: new Set(),\n\t\t\tsetWX: new Set(), // terrible\n\t\t\tsetWY: new Set(), // 57/59\n\t\t\tsetHX: new Set(), // 56/59\n\t\t\tsetHY: new Set(),\n\t\t\tsetXY: new Set(),\n\t\t\t// 3\n\t\t\tsetWHX: new Set(), // 59/59\n\t\t\tsetWHY: new Set(), // 57\n\t\t}\n\n\t\taStyles.forEach(function(style){\n\t\t\tif (isTZP) {\n\t\t\t\ttmpData[style] = {}\n\t\t\t} else if (isEverything) {\n\t\t\t\ttmpData[style] = {}\n\t\t\t}\n\t\t\taWritingStyles.forEach(function(writingstyle){\n\t\t\t\tif (isTZP) {\n\t\t\t\t} else if (isEverything) {\n\t\t\t\t\ttmpData[style][writingstyle] = {}\n\t\t\t\t}\n\n\t\t\t\tlet oItems = isTZP ? oTZP[style] : oList\n\n\t\t\t\tfor (const k of Object.keys(oItems).sort()) {\n\t\t\t\t\tcount++\n\t\t\t\t\t// important to clear the div so no other elements can affect measurements\n\t\t\t\t\tparent.innerHTML = \"\"\n\t\t\t\t\tlet data = []\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparent.innerHTML = ('' == oList[k] ? '<input type=\"'+ k +'\">' : oList[k])\n\t\t\t\t\t\ttarget = parent.firstChild\n\t\t\t\t\t\ttarget.setAttribute(\"style\",\"display:inline; writing-mode: \"+ writingstyle +\";\")\n\t\t\t\t\t\tif (style == \"unstyled\") {target.classList.add('unstyled')}\n\t\t\t\t\t\tif (k.includes('_option')) {target = target.lastElementChild}\n\n\t\t\t\t\t\tlet method = target.getBoundingClientRect()\n\t\t\t\t\t\tdata = [method.width, method.height, method.x, method.y]\n\t\t\t\t\t\toSets.setAll.add(data.join(' '))\n\t\t\t\t\t\tif (isTestSets) {\n\t\t\t\t\t\t\t// 2s\n\t\t\t\t\t\t\toSets.setWH.add(method.width +' '+ method.height)\n\t\t\t\t\t\t\toSets.setWX.add(method.width +' '+ method.x)\n\t\t\t\t\t\t\toSets.setWY.add(method.width +' '+ method.y)\n\t\t\t\t\t\t\toSets.setHX.add(method.height +' '+ method.x)\n\t\t\t\t\t\t\toSets.setHY.add(method.height +' '+ method.y)\n\t\t\t\t\t\t\toSets.setXY.add(method.x +' '+ method.y)\n\t\t\t\t\t\t\t// 3s\n\t\t\t\t\t\t\toSets.setWHX.add(method.width +' '+ method.height +' '+ method.x)\n\t\t\t\t\t\t\toSets.setWHY.add(method.width +' '+ method.height +' '+ method.y)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isTZP) {\n\t\t\t\t\t\t\tlet itemhash = mini(data)\n\t\t\t\t\t\t\tif (undefined == tmpData[style][itemhash]) {tmpData[style][itemhash] = {'data': data, 'group': [k]}\n\t\t\t\t\t\t\t} else {tmpData[style][itemhash]['group'].push(k)}\n\t\t\t\t\t\t} else if (isEverything) {\n\t\t\t\t\t\t\ttmpData[style][writingstyle][k] = data\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttmpData[k] = data\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tif (isTZP) {\n\t\t\t\t\t\t\ttmpData[style][k] = [k +\", \"+ e+'']\n\t\t\t\t\t\t} else if (isEverything) {\n\t\t\t\t\t\t\ttmpData[style][writingstyle][k] = [e+'']\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttmpData[k] = [e+'']\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tparent.innerHTML = \"\" // just in case\n\t\toData = {} // reset\n\t\tif (isTZP) {\n\t\t\t// group by results\n\t\t\tlet newobj = {}\n\t\t\tfor (const key of Object.keys(tmpData)) {\n\t\t\t\tnewobj[key] = {}\n\t\t\t\tfor (const k of Object.keys(tmpData[key])) {\n\t\t\t\t\tnewobj[key][tmpData[key][k].group.join(' ')] = tmpData[key][k]['data']\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const key of Object.keys(newobj)) {\n\t\t\t\toData[key] = {}\n\t\t\t\tfor (const k of Object.keys(newobj[key]).sort()) {oData[key][k] = newobj[key][k]}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const k of Object.keys(tmpData).sort()) {\n\t\t\t\tif (isEverything) {\n\t\t\t\t\toData[k] = {}\n\t\t\t\t\tfor (const j of Object.keys(tmpData[k]).sort()) {\n\t\t\t\t\t\toData[k][j] = {}\n\t\t\t\t\t\tfor (const m of Object.keys(tmpData[k][j]).sort()) {\n\t\t\t\t\t\t\toData[k][j][m] = tmpData[k][j][m]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\toData[k] = tmpData[k]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet hash = mini(oData), notation = \"\"\n\n\t\tlet t1 = performance.now()\n\t\ttry {dom.perf.innerHTML = Math.round(t1 - t0) +\" ms\"} catch(e) {}\n\n\t\tlet strParam = (params.length ? \" [\"+ params.join(\" \") +\"]\" : \"\")\n\t\tlet strCount = ' [' + count + ' items | ' + oSets.setAll.size + ' unique]'\n\t\tlet display = [], tmphash = ''\n\t\tif (!isTZP && isEverything) {\n\t\t\tfor (const k of Object.keys(oData).sort()) {\n\t\t\t\ttmphash = mini(oData[k])\n\t\t\t\tdisplay.push(s15 + k.toUpperCase() +\": \" + sc + tmphash +\"<br>\")\n\t\t\t\tfor (const j of Object.keys(oData[k]).sort()) {\n\t\t\t\t\ttmphash = mini(oData[k][j])\n\t\t\t\t\tdisplay.push(s15+ (k +\" \"+ j).toUpperCase() +\": \" + sc + tmphash +\"<br>\")\n\t\t\t\t\tdisplay.push(json_highlight(oData[k][j], 120) +\"<br>\")\n\t\t\t\t}\n\t\t\t}\n\t\t\thash = s15 +\"EVERYTHING: \" + sc + hash\n\t\t}\n\t\tdom.elements.innerHTML = hash + notation + strParam + strCount + \"<br>\"\n\t\t\t+ (!isTZP && isEverything ? \"<br>\" + display.join(\"<br>\") : json_highlight(oData, 120))\n\n\t\tif (isTestSets) {\n\t\t\tconsole.log('---\\n' + (isEverything ? 'everything' : params.join(' ')))\n\t\t\tfor (const k of Object.keys(oSets)) {\n\t\t\t\tconsole.log(k, oSets[k].size)\n\t\t\t}\n\t\t}\n\t\treturn\n\t} catch(e) {\n\t\tdom.elements = e+\"\"\n\t\treturn\n\t}\n}\n\nfunction run(isTZP = false) {\n\t// clear\n\tdom.elements.innerHTML = \" &nbsp; \"\n\t// pause so users see change\n\tsetTimeout(function() {\n\t\tget_elements(isTZP)\n\t}, 170)\n}\n\ntry {isLocale = Intl.DateTimeFormat().resolvedOptions().locale} catch(e) {isLocale = zErr}\ntry {dom.locale.innerHTML = isLocale} catch(e) {}\n\nPromise.all([\n\tget_globals()\n]).then(function(){\n\tstyle_everything()\n\trun()\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/elementkeys.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>element keys</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 90%; min-width: 480px; max-width: 780px}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb15\">\r\n\t\t<thead><tr><th>\r\n\t\t\t<div class=\"nav-title\">element keys\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"perf2\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td class=\"intro\">\r\n\t\t\t<span class=\"no_color\"></span>\r\n\t\t\t\t<span class=\"btn15 btnfirst\" onClick=\"run()\">[ run ]</span>\r\n\t\t\t\t\t<input type=\"checkbox\" id=\"optAll\"> <span class=\"no_color\">all elements\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td><hr></td></tr>\r\n\t\t<tr><td></td></tr>\r\n\t\t<tr><td style=\"text-align: left; color: var(--test0);\" class=\"mono spaces\" id=\"details\"></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n//https://w3c.github.io/elements-of-html/\r\n\r\n// ignore: checked in gecko + blink (desktop)\r\n\t// all match even unsorted\r\nlet oListIgnore = {\r\n\t\"CHECK\": [\r\n\t\t// input types = input\r\n\t\t'input|button','input|checkbox','input|color','input|date','input|datetime',\r\n\t\t'input|datetime-local','input|file','input|image','input|month','input|number',\r\n\t\t'input|password','input|radio','input|range','input|reset','input|search',\r\n\t\t'input|submit','input|tel','input|text','input|time','input|url','input|week',\r\n\t],\r\n\t\"STABLE B\": [\r\n\t\t'caption','h1','h2','h3','h4','h5','h6','p', // = div\r\n\t\t'ins', // = del\r\n\t\t'xmp', // = pre\r\n\t\t'dl','menu', // = dir\r\n\t\t'q', // = blockquote\r\n\t\t'tfoot','thead', // = tbody\r\n\t\t'th', // = td\r\n\t\t'colgroup', // = col\r\n\t\t// = span\r\n\t\t'abbr','acronym','address','applet','article','aside','b',\r\n\t\t'basefont','bdi','bdo','big','center','cite','code','dd','dfn','dt',\r\n\t\t'element-details','em','figcaption','figure','footer','head','header','hgroup','i','isindex',\r\n\t\t'kbd','keygen','main','mark','menuitem','nav','nextid','nobr','noembed',\r\n\t\t'noframes','noscript','picture','plaintext','portal','rb','rbc','rp','rt',\r\n\t\t'rtc','ruby','s','samp','search','section','small','strike','strong','sub',\r\n\t\t'summary','sup','tt','u','var','wbr',\r\n\t],\r\n}\r\n\r\nlet oList = {\r\n\t// as of 137n\r\n\t\"CHECK A\": [\r\n\t// changes since FF128\r\n\t\t// cold 3.5 | rerun 1.7\r\n\t\t'audio',\r\n\t\t\t// 116\tsetSinkId, sinkId\r\n\t\t\t// 128\t(seekToNextFrame)\r\n\t\t\t// 137n\tallowedToPlay\r\n\t\t'button',\r\n\t\t\t// 125\tpopoverTargetElement, popoverTargetAction\r\n\t\t\t// 144  commandForElement, command\r\n\t\t'details',\r\n\t\t\t// 130\tname\r\n\t\t'dialog',\r\n\t\t\t// first added in 98 (show, showModal, close, open, returnValue)\r\n\t\t\t// 139\trequestClose\r\n\t\t'template',\r\n\t\t\t// 123\tshadowRootMode, shadowRootDelegatesFocus\r\n\t\t\t// 125\tshadowRootClonable\r\n\t\t\t// 128\tshadowRootSerializable\r\n\t\t'video',\r\n\t\t\t// 116\tsetSinkId, sinkId\r\n\t\t\t// 122\tdisablePictureInPicture\r\n\t\t\t// 128\t(seekToNextFrame)\r\n\t\t\t// 132\trequestVideoFrameCallback, cancelVideoFrameCallback\r\n\t\t\t// 137n\tallowedToPlay\r\n\t\t'img',\r\n\t\t\t// 132\tfetchPriority\r\n\t\t//'link',\r\n\t\t\t// 132\tfetchPriority\r\n\t\t'script',\r\n\t\t\t// 132\tfetchPriority\r\n\t\t\t// 135\tinnerText, textContent\r\n\t\t'input',\r\n\t\t\t// 116\tdirName\r\n\t\t\t// 125\tpopoverTargetElement, popoverTargetAction\r\n\t\t\t// 142  (mozIsTextField)\r\n\t],\r\n\t\"STABLE A\": [\r\n\t// no changes since FF126\r\n\t\t// fast since the parser is already created\r\n\t\t// cold: 0.7 | rerun 0.7\r\n\t\t'iframe',\r\n\t\t\t// 121\t(loading)\r\n\t\t'marquee',\r\n\t\t\t// 126\t(onbounce, onfinish, onstart)\r\n\t\t'select',\r\n\t\t\t// 122\tshowPicker\r\n\t\t'textarea',\r\n\t\t\t// 116\tdirName\r\n\t],\r\n\t// no changes since FF111\r\n\t\"STABLE B\": [\r\n\t\t//*\r\n\t\t'source', // 108 (width, height)\r\n\t\t'meta', // 106 (media)\r\n\t\t'form', // 75, 111 (rel, relList)\r\n\t\t'canvas', // 74, 105\r\n\t\t'object', // 68\r\n\t\t'slot', // assignedNodes + name (63) assignedElements (66), assign (92)\r\n\t\t'body', // 57, 69, 89\r\n\t\t'frameset', // 57, 69, 89\r\n\t\t'meter', // 56 (labels)\r\n\t\t'progress', // 56 (labels)\r\n\t\t'style', // disabled, media, type, sheet (scoped removed in 55)\r\n\t\t//* super boring\r\n\t\t// FF52-130+\r\n\t\t'a',\r\n\t\t'area',\r\n\t\t'base', // href, target\r\n\t\t'blockquote', // cite\r\n\t\t'br', // clear\r\n\t\t'col',\r\n\t\t'data', // value\r\n\t\t'datalist', // options\r\n\t\t'del', // cite, dateTime\r\n\t\t'dir', // compact\r\n\t\t'div', // align\r\n\t\t'embed',\r\n\t\t'fieldset',\r\n\t\t'font', // color, face, size\r\n\t\t'frame',\r\n\t\t'html', // version\r\n\t\t'hr', // align, color, noShade, size, width\r\n\t\t'label', // form, htmlFor, control\r\n\t\t'legend', // form, align\r\n\t\t'li', // value, type\r\n\t\t'map', // name, areas\r\n\t\t'ol', // reversed, start, type, compact\r\n\t\t'optgroup', // disabled, label\r\n\t\t'option',\r\n\t\t'output',\r\n\t\t'param', // name, value, type, valueType\r\n\t\t'pre', // width\r\n\t\t'span', // nothing found\r\n\t\t'table',\r\n\t\t'tbody',\r\n\t\t'td',\r\n\t\t'time', // dateTime\r\n\t\t'title', // text\r\n\t\t'tr',\r\n\t\t'track',\r\n\t\t'ul', // compact, type\r\n\t\t//*/\r\n\t],\r\n\t//*/\r\n}\r\n\r\nlet oData = {}\r\nlet oCommon = {}\r\nlet aEverything = []\r\nlet oPreHash = {}\r\n\r\nlet isDOMParser = true\r\n// DOMParser can't be used for these\r\nlet aNoParser = [\r\n\t'link','template', // checkA\r\n\t'base','basefont','caption','col','colgroup','frame','frameset','noframes','noscript','tbody','td','tfoot','th','thead','tr', // stable\r\n]\r\n\r\nfunction run() {\r\n\t// set/reset\r\n\tlet t0 = performance.now()\r\n\toData = {}\r\n\toCommon = {}\r\n\taEverything = []\r\n\toPreHash = {}\r\n\tlet tmpHash = {}\r\n\tlet tmpPreHash = {}\r\n\tlet oOrder = {}\r\n\tlet perfA, perfB\r\n\r\n\tlet splitValue = isFF ? \"click\" : \"title\" // title works for chrome + safari\r\n\t/* interesting (without extension fuckery which I have not tested)\r\n\t\tFF122\r\n\t\texcept for 'select', the last 310 items are the same\r\n\r\n\t\tseems to be everything after click\r\n\t\tselect doesn't have 'remove'\r\n\r\n\t\tpostClick actually equals span\r\n\t*/\r\n\r\n\tlet tmpList = {}, totalElements = 0\r\n\tfor (const k of Object.keys(oList)) {\r\n\t\tlet aList = oList[k]\r\n\t\tif (dom.optAll.checked) {\r\n\t\t\tif (oListIgnore[k] !== undefined) {aList = aList.concat(oListIgnore[k])}\r\n\t\t}\r\n\t\ttotalElements += aList.length\r\n\t\ttmpList[k] = aList.sort()\r\n\t}\r\n\tconst parentid = \"elementsdiv\"\r\n\tfunction cleanup() {\r\n\t\ttry {document.getElementById(parentid).remove()} catch(e) {}\r\n\t}\r\n\r\n\ttry {\r\n\t\tlet htmlElement\r\n\t\tlet parser = new DOMParser\r\n\t\t/* \r\n\t\t\t   dom = 18ms cold, 10ms rerun \r\n\t\t\tparser =  8ms cold,  5ms rerun (so far: 8 default items still to correctly add/enumerate)\r\n\t\tso about twice as fast\r\n\r\n\t\twe could use parser where possible but DOM where we have to\r\n\t\t- we should reduce what we NEED to test first\r\n\r\n\t\tHTMLElementKeys was using a div\r\n\t\t- cydec was missing items: clientHeight clientWidth scrollHeight scrollWidth\r\n\t\t\t- does using the parser fix this?\r\n\t\t\t- are we still picking up those four items in prototype/proxy lies?\r\n\r\n\t\t- NoScript\r\n\t\t\t- every element had 13 items split with a space = we would still detect this\r\n\t\t*/\r\n\r\n\t\t// create one parent div to hold the others\r\n\t\tlet parent = document.createElement('div')\r\n\t\tparent.setAttribute(\"id\", parentid)\r\n\t\tdocument.body.appendChild(parent)\r\n\r\n\t\t// loop elements\r\n\t\tfor (const k of Object.keys(tmpList).sort()) {\r\n\t\t\tif (k == 'STABLE A') {\r\n\t\t\t\tperfA = performance.now() - t0\r\n\t\t\t} else if (k ==\"STABLE B\") {\r\n\t\t\t\tperfB = (performance.now() - t0) - perfA\r\n\t\t\t}\r\n\r\n\t\t\tlet aList = tmpList[k]\r\n\t\t\ttmpHash[k] = {}\r\n\t\t\ttmpPreHash[k] = {}\r\n\r\n\t\t\taList.forEach(function(el) {\r\n\t\t\t\tlet item = el.split(\"|\")[0]\r\n\t\t\t\tlet type = el.split(\"|\")[1]\r\n\t\t\t\tlet keys = []\r\n\t\t\t\ttry {\r\n\t\t\t\t\t// method\r\n\t\t\t\t\tif (isDOMParser && !aNoParser.includes(el)) {\r\n\t\t\t\t\t\t// this is faster, but some elements it seems we can't do this way\r\n\t\t\t\t\t\t/* broken in default 61* elements\r\n\t\t\t\t\t\t\tbody,\t\t// doc.body\r\n\t\t\t\t\t\t\thtml,\t\t// doc.all[0]\r\n\t\t\t\t\t\t\tmeta,\t\t// doc.all[3]\r\n\t\t\t\t\t\t\tscript,\t// doc.all[3]\r\n\t\t\t\t\t\t\tstyle,\t// doc.all[3]\r\n\t\t\t\t\t\t\ttitle,\t// doc.all[3]\r\n\t\t\t\t\t\t*/\r\n\r\n\t\t\t\t\t\tlet tmpitem = item\r\n\t\t\t\t\t\tif (item == \"title\") {\r\n\t\t\t\t\t\t\ttmpitem = \"<html><head><title></head></html>\"\r\n\t\t\t\t\t\t} else if (item == \"meta\") {\r\n\t\t\t\t\t\t\ttmpitem = \"<html><head><meta></head></html>\"\r\n\t\t\t\t\t\t} else if (item == \"script\") {\r\n\t\t\t\t\t\t\ttmpitem = \"<html></html><script>\"\r\n\t\t\t\t\t\t} else if (item == \"style\") {\r\n\t\t\t\t\t\t\ttmpitem = \"<html><style></html>\"\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tlet str = \"<\"+ tmpitem + (type == undefined ? \"\": \" type='\" + type +\"'\") + \">\"\r\n\t\t\t\t\t\tlet doc = parser.parseFromString(str, \"text/html\")\r\n\r\n\t\t\t\t\t\tif (item == \"body\") {\r\n\t\t\t\t\t\t\thtmlElement = doc.body\r\n\t\t\t\t\t\t} else if (item == \"head\") { // all elements\r\n\t\t\t\t\t\t\thtmlElement = doc.head\r\n\t\t\t\t\t\t} else if (item == \"html\") {\r\n\t\t\t\t\t\t\thtmlElement = doc.all[0]\r\n\t\t\t\t\t\t} else if (item == \"meta\" || item == \"script\" || item == \"style\" || item == \"title\") {\r\n\t\t\t\t\t\t\thtmlElement = doc.all[3]\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\thtmlElement = doc.body.firstChild // firstElementChild ?\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tconst id = \"target\"+el\r\n\t\t\t\t\t\tconst element = document.createElement(item)\r\n\t\t\t\t\t\telement.setAttribute(\"id\", id)\r\n\t\t\t\t\t\tparent.appendChild(element)\r\n\t\t\t\t\t\tif (type !== undefined) {\r\n\t\t\t\t\t\t\tdocument.getElementById(id).type = type\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\thtmlElement = document.getElementById(id)\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tfor (const key in htmlElement) {keys.push(key)}\r\n\t\t\t\t\t// get pre + post `click` (gecko)\r\n\t\t\t\t\tlet splitIndex = keys.indexOf(splitValue)\r\n\t\t\t\t\tlet aPre = [], aPost = []\r\n\t\t\t\t\t// what if `click` or `title` is missing: every post will be an empty array\r\n\t\t\t\t\t\t// ^ not worth coding to exclude empty arrays\r\n\t\t\t\t\taPre = keys.slice(0, splitIndex)\r\n\t\t\t\t\taPre.forEach(function(j) {aEverything.push(j)})\r\n\t\t\t\t\taPost = keys.slice(splitIndex, keys.length)\r\n\t\t\t\t\tlet prehash = mini(aPre)\r\n\t\t\t\t\tlet posthash = mini(aPost)\r\n\t\t\t\t\t// posthash tampering\r\n\t\t\t\t\tif (oCommon[posthash] == undefined) {\r\n\t\t\t\t\t\taPost.forEach(function(j) {aEverything.push(j)})\r\n\t\t\t\t\t\toCommon[posthash] = {}\r\n\t\t\t\t\t\toCommon[posthash][\"data\"] = aPost\r\n\t\t\t\t\t\toCommon[posthash][\"display\"] = []\r\n\t\t\t\t\t\tlet aCommonTamper = []\r\n\t\t\t\t\t\taPost.forEach(function(r) {\r\n\t\t\t\t\t\t\tif (r.includes(\" \")) {\r\n\t\t\t\t\t\t\t\toCommon[posthash][\"display\"].push(sb + r + sc)\r\n\t\t\t\t\t\t\t\taCommonTamper.push(r)\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\toCommon[posthash][\"display\"].push(r)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\tif (aCommonTamper.length) {\r\n\t\t\t\t\t\t\toCommon[posthash][\"tampered\"] = aCommonTamper\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// prehash tampering\r\n\t\t\t\t\t// ^ ToDo\r\n\r\n\t\t\t\t\tlet hash = mini(keys) // always use full keys hash so we get unique items\r\n\t\t\t\t\t// record each unique hash + keys + count\r\n\t\t\t\t\t// we need to use the full hash because two elements could have the same pre but different post\r\n\t\t\t\t\tif (tmpHash[k][hash] == undefined) {\r\n\t\t\t\t\t\ttmpHash[k][hash] = {\r\n\t\t\t\t\t\t\t\"count\": aPre.length,\r\n\t\t\t\t\t\t\t\"data\": keys,\r\n\t\t\t\t\t\t\t\"elements\": [],\r\n\t\t\t\t\t\t\t\"predata\": aPre,\r\n\t\t\t\t\t\t\t\"prehash\": prehash,\r\n\t\t\t\t\t\t\t\"posthash\": posthash\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\ttmpHash[k][hash][\"elements\"].push(el)\r\n\t\t\t\t\t// record post hashes separately\r\n\t\t\t\t\tif (tmpPreHash[k][prehash] == undefined) {tmpPreHash[k][prehash] = []}\r\n\t\t\t\t\ttmpPreHash[k][prehash].push(el)\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tconsole.log(el, e+\"\")\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t\t// remove parent div\r\n\t\tcleanup()\r\n\t\tdom.perf = Math.round(performance.now() - t0) +\" ms\"\r\n\t\tdom.perf2 = perfA.toFixed(1) +\" | \" + perfB.toFixed(1)\r\n\r\n\t\t//* everything\r\n\t\taEverything = aEverything.filter(function(item, position) {return aEverything.indexOf(item) === position}) // deduped\r\n\t\taEverything = aEverything.filter(x => ![\"constructor\"].includes(x)) // remove constructor\r\n\t\taEverything.sort() // order is artifical due to htmlList so lets remove that here\r\n\t\t//console.log(\"EVERYTHING: \" + mini(aEverything) +\" [\"+ aEverything.length +\"]\\n\", \"['\"+ aEverything.join(\"','\") +\"']\")\r\n\t\t//EVERYTHING: d6d0fc0e [630]\r\n\t\t//*/\r\n\r\n\t\t// sort into oData by hash, and determine oOrder (by first element name)\r\n\t\tfor (const k of Object.keys(tmpHash).sort()) {\r\n\t\t\toData[k] = {}\r\n\t\t\toOrder[k] = {}\r\n\t\t\tfor (const j of Object.keys(tmpHash[k]).sort()) {\r\n\t\t\t\toData[k][j] = tmpHash[k][j]\r\n\t\t\t\tlet aTmp = oData[k][j].elements.sort() // sort array in oData\r\n\t\t\t\toOrder[k][aTmp[0]] = j\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (const k of Object.keys(tmpPreHash).sort()) {\r\n\t\t\toPreHash[k] = {}\r\n\t\t\tfor (const j of Object.keys(tmpPreHash[k]).sort()) {\r\n\t\t\t\toPreHash[k][j] = tmpPreHash[k][j].sort()\r\n\t\t\t}\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = [], totalResults = 0\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\ttotalResults += Object.keys(oData[k]).length\r\n\t\t}\r\n\t\tlet strAll = s15 +\"ALL\"+ sc +\": \"+ s6 + mini(oData) + sc\r\n\t\t\t+\" <span class='btn15 btnc' onclick='console.log(oData)'>[console]</span> | unique values: \"+ s6 + mini(aEverything) + sc\r\n\t\t\t+\" <span class='btn15 btnc' onclick='console.log(aEverything)'>[\" + aEverything.length +\"]</span> | \"\r\n\t\t\t+ totalElements +\" elements | \" + totalResults +\" hashes<br><br><hr>\"\r\n\t\tdisplay.push(strAll)\r\n\r\n\t\t// display common\r\n\t\tlet countTampered = \"\"\r\n\t\tlet toggle = \"common\"\r\n\t\tdisplay.push(\r\n\t\t\t\"<span id='labelhidden\" + toggle + \"' class='btnfirst btn0' onClick=\\\"togglerows('hidden\" + toggle +\"','expand')\\\">[ expand ]</span> \"\r\n\t\t\t+ s15 + \"COMMON\"+ sc +\"<br>\")\r\n\t\tfor (const j of Object.keys(oCommon).sort()) {\r\n\t\t\tlet obj = oCommon[j]\r\n\t\t\tcountTampered = obj.tampered !== undefined ? sb +\" [\"+ obj.tampered.length +\"]\"+ sc : \"\"\r\n\t\t\tdisplay.push(\r\n\t\t\t\ts6 + j + sc + (\" [\"+ obj.data.length +\"]\").padStart(5) + countTampered\r\n\t\t\t\t\t+\t\"<span class='toghidden\" + toggle +\" hidden faint'>[<br>\" + \"<span class='indent'>\" + obj.display.join(\", \") +\"</span><br>]</span>\"\r\n\t\t\t)\r\n\t\t}\r\n\t\tfor (const m of Object.keys(oOrder).sort()) {\r\n\t\t\tdisplay.push(\"<br><hr>\")\r\n\t\t\t// display pre\r\n\t\t\ttoggle = \"pre\"+ m\r\n\t\t\tlet notation = '', hash = mini(oPreHash[m])\r\n\r\n\t\t\tif (isFF && !dom.optAll.checked) {\r\n\t\t\t\tlet is111 = HTMLElement.prototype.hasOwnProperty(\"translate\")\r\n\t\t\t\tlet is126 = false\r\n\t\t\t\ttry {is126 = \"function\" === typeof URL.parse} catch(e) {}\r\n\t\t\t\tif ('STABLE B' == m && is111) {\r\n\t\t\t\t\t/*\r\n\t\t\t\t\t\t111 'form'\t\t\t(rel, relList)\r\n\t\t\t\t\t\t108 'source'\t\t(width, height)\r\n\t\t\t\t\t\t106 'meta'\t\t\t(media)\r\n\t\t\t\t\t\t105 'canvas'\r\n\t\t\t\t\t\t92 'slot' \t\t\tassign\r\n\t\t\t\t\t\t89 'body' + frameset\r\n\t\t\t\t\t\t75 'form'\r\n\t\t\t\t\t\t74 'canvas'\r\n\t\t\t\t\t\t69 'body' + frameset\r\n\t\t\t\t\t\t68 'object',\r\n\t\t\t\t\t\t66 'slot' \t\t\tassignedElements\r\n\t\t\t\t\t\t63 'slot' \t\t\tassignedNodes + name\r\n\t\t\t\t\t\t57 'body' + frameset\r\n\t\t\t\t\t\t56 'meter'\t\t\tlabels\r\n\t\t\t\t\t\t56 'progress'\t\tlabels\r\n\t\t\t\t\t\t55 'style'\t\t\tdisabled, media, type, sheet (scoped removed)\r\n\t\t\t\t\t*/\r\n\t\t\t\t\tif (hash == \"1471d575\") {notation = sg +' ['+ green_tick +' FF111+]'+ sc\r\n\t\t\t\t\t} else {notation = ' '+ zNEW\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if ('STABLE A' == m && is126) {\r\n\t\t\t\t\t/*\r\n\t\t\t\t\t'iframe',\r\n\t\t\t\t\t\t// 121\t(loading)\r\n\t\t\t\t\t'marquee',\r\n\t\t\t\t\t\t// 126\t(onbounce, onfinish, onstart)\r\n\t\t\t\t\t'select',\r\n\t\t\t\t\t\t// 122\tshowPicker\r\n\t\t\t\t\t'textarea',\r\n\t\t\t\t\t\t// 116\tdirName\r\n\t\t\t\t\t*/\r\n\t\t\t\t\t// bba6c822 125\r\n\t\t\t\t\tif (hash == \"1ae1316a\") {notation = sg +' ['+ green_tick +' FF126+]'+ sc\r\n\t\t\t\t\t} else {notation = ' '+ zNEW\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tlet preString = \" | hash <span class='btn15 btnc' onclick='console.log(oPreHash[\\\"\"+ m +\"\\\"])'>[\"+ hash +\"]</span>\"\r\n\t\t\tdisplay.push(\r\n\t\t\t\t\"<span id='labelhidden\" + toggle + \"' class='btnfirst btn0' onClick=\\\"togglerows('hidden\" + toggle +\"','expand')\\\">[ expand ]</span> \"\r\n\t\t\t\t+ s12 + m + sc + preString + notation +\" | \"+ tmpList[m].length +\" elements | \"+ Object.keys(oData[m]).length +\" hashes<br>\"\r\n\t\t\t)\r\n\r\n\t\t\tfor (const j of Object.keys(oOrder[m]).sort()) {\r\n\t\t\t\tlet k = oOrder[m][j]\r\n\t\t\t\tlet obj = oData[m][k]\r\n\t\t\t\tlet commonhash = obj.posthash\r\n\t\t\t\tcountTampered = \"\"\r\n\t\t\t\tif (oCommon[commonhash] !== undefined) {\r\n\t\t\t\t\tif (oCommon[commonhash].tampered !== undefined) {\r\n\t\t\t\t\t\tcountTampered = sb +\" [\"+ oCommon[commonhash].tampered.length +\"]\"+ sc\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tdisplay.push(\r\n\t\t\t\t\ts6 + k + sc +\": \" + obj.prehash + (\" [\"+ obj.predata.length +\"]\").padStart(5)\r\n\t\t\t\t\t+ \" + \"+ s15 + obj.posthash + sc + countTampered +\": \"+ obj.elements.join(\", \")\r\n\t\t\t\t\t+\t\"<span class='toghidden\" + toggle +\" hidden faint'>[<br>\" + \"<span class='indent'>\" + obj.predata.join(\", \") +\"</span><br>]</span>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tdom.details.innerHTML = display.join(\"<br>\")\r\n\r\n\t} catch (e) {\r\n\t\tcleanup()\r\n\t\tdom.details.innerHTML = e+\"\"\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals(),\r\n]).then(function(){\r\n\tdom.optAll.checked = false\r\n\trun()\r\n})\r\n</script>\r\n</body>\r\n</html>\r\n\r\n"
  },
  {
    "path": "tests/elementother.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>elements: other</title>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\"> <!-- -->\n  <script src=\"testglobals.js\"></script>\n  <script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 97%; min-width: 780px; max-width: 780px}\n\n\t\t/*** custom styles so we can check they are removed ***/\n\t\t/* you can run check_revert() to test */\n\n\t\t#element-fp a {margin: 2px; padding: 2px;}\n\t\t#element-fp b {margin: 2px; padding: 2px;}\n\t\t#element-fp br {margin: 2px; padding: 2px;}\n\t\t#element-fp code {margin: 2px; padding: 2px;}\n\t\t#element-fp div {margin: 2px; padding: 2px;}\n\t\t#element-fp hr {margin: 2px; padding: 2px;}\n\t\t#element-fp i {margin: 2px; padding: 2px;}\n\t\t#element-fp span {margin: 2px; padding: 2px;}\n\t\t#element-fp div>span {font-size: 2.9em;}\n\t\t#element-fp font[size=\"1\"] {font-size: 1.1em;}\n\t\t#element-fp font[size=\"2\"] {font-size: 1.2em;}\n\t\t#element-fp font[size=\"3\"] {font-size: 1.3em;}\n\t\t#element-fp font[size=\"4\"] {font-size: 1.4em;}\n\t\t#element-fp font[size=\"5\"] {font-size: 1.5em;}\n\t\t#element-fp font[size=\"6\"] {font-size: 1.6em;}\n\t\t#element-fp font[size=\"7\"] {font-size: 1.7em;}\n\t\t#element-fp h1 {margin: 2px; padding: 2px;}\n\t\t#element-fp h2 {margin: 2px; padding: 2px;}\n\t\t#element-fp h3 {margin: 2px; padding: 2px;}\n\t\t#element-fp h4 {margin: 2px; padding: 2px;}\n\t\t#element-fp h5 {margin: 2px; padding: 2px;}\n\t\t#element-fp h6 {margin: 2px; padding: 2px;}\n\t\t#element-fp p {margin: 2px; padding: 2px;}\n\t\t#element-fp small {margin: 2px; padding: 2px;}\n\t\t#element-fp sub {margin: 2px; padding: 2px;}\n\t\t#element-fp sup {margin: 2px; padding: 2px;}\n\n\t\tabbr, acronym, address, applet, article, aside, audio,\n\t\tbase, basefont, big, blockquote, caption, center, cite,\n\t\tdata, dd, del, dfn, dialog, dir, dl, dt {\n\t\t\tmargin: 2px; padding: 2px;\n\t\t}\n\n\t\tem, figcaption, fieldset, figure, footer, form, geolocation,\n\t\theader, hgroup, ins, isindex, keygen, kbd, label, legend, li,\n\t\tlink, main, mark, marquee, menu, nav, nobr, noframes {\n\t\t\tmargin: 2px; padding: 2px;\n\t\t}\n\n\t\tol, optgroup, option, output, plaintext, portal, pre, q, rb, rbc,\n\t\trp, rt, rtc, ruby, s, samp, search, section, slot, strike, strong,\n\t\tsummary, time, title, tt, u, ul, var, xmp {\n\t\t\tmargin: 2px; padding: 2px;\n\t\t}\n\n\t\tcanvas, iframe, img, meter, object, picture, video {\n\t\t\twidth: 75px;\n\t\t}\n\n\t\t/* don't think I can affect the style of these with JS enabled\n\t\t\tnoembed, noscript = 0x0\n\t\t*/\n\t\t#element-fp noembed {border: 20px; padding: 2px;}\n\t\t#element-fp noscript {bortder: 20px; padding: 2px;}\n\n\t</style>\n</head>\n\n<body>\n\t<div id=\"element-fp\"></div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb15\">\n\t\t<col width=\"25%\"><col width=\"75%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">elements: other\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t\t<div class=\"nav-down\"><span class=\"perf\" id=\"locale\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span class=\"no_color\">Element sizes not covered in\n\t\t\t\t<a class=\"blue\" target=\"_blank\" href=\"elementforms.html\">element forms</a>. Counts for unique\n\t\t\t\telements across the two tests are not directly comparable. In everything all elements are\n\t\t\t\ttreated equally with both <code>writing-modes</code>, which is not the case in the TZPtest.\n \t\t\t</span><br><br>\n\t\t\t<span class=\"btn15 btnfirst\" onClick=\"run(`everything`)\">[ everything ]</span>\n\t\t\t<span class=\"btn15 btnfirst\" onClick=\"run(`TZP`)\">[ TZP ]</span>\n\t\t\t<span class=\"btn15 btnfirst\" onClick=\"list_elements()\">[ elements ]</span>\n\t\t\t<span class=\"btn15 btnfirst\">\n\t\t\t\t<a class=\"blue\" target=\"_blank\" href=\"elementother_nocss.html\" onClick=\"run('everything')\">[ NO CSS TEST ]</a>\n\t\t\t</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"><hr><br></td></tr>\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<div class=\"c mono spaces no_color\" id=\"elements\"></div>\n\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n/*\nchanging app language has no effect (the only char we're using is '.')\notherwise looks like everything is pretty much fixed/hardcoded/set due to widgets\nand defaults - so this is exposing subpixels (and version changes) only IIUIC\n\nedit: except maybe french and quote\n*/\n\nlet strElement = '', isRevert = true\nlet oData = {}, oList = {}, oListBase = {}, oTZP = {}, oChecks = {}, oCheckSummary = {}\nlet testTarget\n\nlet aRemainder = []\nlet aIgnored = [\n\t// ignore\n\t'area', // used in map and sets it's own size\n\t'bdi','bdo', //: bidirectional elements\n\t'col','colgroup',\n\t'datalist',\n\t'element-details', // https://mdn.github.io/web-components-examples/element-details/\n\t'form', // all forms covered in another metric\n\t'frame','frameset', // deprecated\n\t'head', // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/head\n\t'map',\n\t'menuitem', // deprecated\n\t'nextid', // died 1997 with HTML v3.2\n\t'param', // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/param\n\t'script',\n\t'source',\n\t'style',\n\t'template', // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template\n\t'track', // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/track\n\t'wbr', // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/wbr\n]\n\nlet aUseFirstChild = ['hgroup'] // we want to measure the first element, not the last\nlet oExtraStyles = {\n\t'marquee': '; width: 20px; height: 20px', // if we don't constrain it, it changes with inner window sizes\n}\n\nfunction build_lists() {\n\t// note: some elements we insert a char \".\" to a) force a height\n\t// or b) for unique measurements without a char to get more precision/decimal places\n\t// always use the same char\n\tlet oTmp = {\n\t\ta: '<a href=\"\">.</a>',\n\t\taudio: '<audio controls=\"\"></audio>',\n\t\tbase: '<base href=\"\"/>', // empty: width/x are zero but height/y are interesting\n\t\tbasefont: '<basefont color=\"#FF0000\"/>',\n\t\tbig_x2: '<big><big>.</big></big>',\n\t\tbig_x3: '<big><big><big>.</big></big></big>',\n\t\t// always revert tables because we have that in our inline style in the plain test\n\t\tcaption: '<table><caption>.</caption></table>',\n\t\tdd: '<dl><dd>.</dd></dl>',\n\t\tdialog: '<dialog open=\"\"></dialog>',\n\t\tdt: '<dl><dt>.</dt></dl>',\n\t\tfigcaption: '<figure><figcaption>.</figcaption></figure>',\n\t\thgroup: '<hgroup><h1>.</h1><p class=\"revert\">.</p></hgroup>', // revert 2nd child (p)\n\t\tisindex: '<isindex prompt=\"search\"></isindex>',\n\t\tkeygen: '<form method=\"POST\"><keygen></keygen></form>',\n\t\tlabel: '<div id=\"elementlabel\"><label for=\"elementlabel\">.</label></div<',\n\t\tlegend: '<fieldset><legend>.</legend></fieldset>',\n\t\tli: '<ul><li></li></ul>',\n\t\tlink: '<link href=\"\" rel=\"stylesheet\">',\n\t\tnoembed: '<embed><noembed>.</noembed>',\n\t\toutput: '<form><input>=<output class=\"revert\"></output></form>',\n\t\t\t// '<form><input id=\"a\" value=\".\"> = <output id=\"x\" name=\"x\" for=\"a\"></output></form>',\n\t\t\t// we don't actually need to calculate any values\n\t\trb: '<ruby><rb>.</rb></ruby>',\n\t\trbc: '<ruby><rbc>.</rbc></ruby>',\n\t\trp: '<ruby><rp>.</rp></ruby>',\n\t\trt: '<ruby><rt>.</rt></ruby>',\n\t\trtc: '<ruby><rtc>.</rtc></ruby>',\n\t\tsummary: '<details><summary>.</summary></details>',\n\t\t'q_empty': '<q></q>',\n\t\t// tables\n\t\ttbody: '<table><tbody></tbody></table>', \n\t\ttd: '<table><tr><td></td></tr></table>',\n\t\ttfoot: '<table><tfoot></tfoot></table>',\n\t\tth: '<table><thead><tr><th></th></tr></thead></table>',\n\t\tthead: '<table><thead></thead></table>',\n\t\ttr: '<table><tr></tr></table>',\n\t\t// other\n\t\t'ol_li': '<ol><li>.</li></ol>',\n\t\t'menu_li': '<menu>.<li></li></menu>',\n\t\t'ul_li': '<ul><li>.</li></ul>',\n\t\t// test error\n\t\t//'error': '<frame></frame>'\n\t}\n\t// programatically add more\n\tlet oExtra = {\n\t\t'char': [\n\t\t\t'abbr','acronym','address','applet','article','aside',\n\t\t\t'b','big','blockquote','center','cite','code',\n\t\t\t'data','del','dir','div','dfn','dl','em','footer',\n\t\t\t'h1','h2','h3','h4','h5','h6','header',\n\t\t\t'i','ins','iframe','kbd',\n\t\t\t'main','mark','marquee','menu','meter',\n\t\t\t'nobr','noframes','noscript','ol','option','p','pre','q',\n\t\t\t'ruby',\n\t\t\t's','samp','section','slot','small','span','strike','strong','sub','sup',\n\t\t\t'time','title','tt',\n\t\t\t'u','ul','var','xmp',\n\t\t],\n\t\tnochar: [\n\t\t\t'canvas','fieldset','figure','geolocation','img',\n\t\t\t'nav','optgroup','picture',\n\t\t\t'portal', // https://wicg.github.io/portals/#the-portal-element\n\t\t\t'search','table','video',\n\t\t],\n\t\tstandalone: [\n\t\t\t'br','hr','object','plaintext',\n\t\t]\n\t}\n\tfor (const type of Object.keys(oExtra)) {\n\t\tfor (const k of Object.keys(oExtra[type])) {\n\t\t\tlet array = oExtra[type]\n\t\t\tarray.forEach(function(item) {\n\t\t\t\tif ('standalone' == type) {\n\t\t\t\t\toTmp[item] = '<'+ item +'>'\n\t\t\t\t} else {\n\t\t\t\t\tlet str = 'char' == type ? '.' : ''\n\t\t\t\t\toTmp[item] = '<'+ item +'>'+ str +'</'+ item +'>'\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\t// sort into final object\n\tfor (const k of Object.keys(oTmp).sort()) {\n\t\toListBase[k] = oTmp[k]\n\t}\n\n\t// build TZP\n\t\t// we can't get more unique elements than actual elements used\n\t\t// so don't double up: e.g. in blink android we have 35 unique elements in\n\t\t// everything but only 34 elements used in tzp (at the time): so we need to\n\t\t// add a new element: the point of difference was in \"base, basefont, picture\"\n\t\t// (grouped in everything) - we already use base, so instead add basefont or picture \n\toTmp = {\n\t\t'horizontal-tb': ['base','figure'],\n\t\t'vertical-lr': [\n\t\t\t'a','audio','b','base','big','big_x2','big_x3','blockquote','br',\n\t\t\t'canvas','caption','code','dd','dialog','dl','dt','fieldset','figcaption',\n\t\t\t'geolocation','h1','h2','h3','h4','h5','h6','hgroup','hr','i','iframe','img',\n\t\t\t'legend','li','marquee','menu_li','meter','noembed','noscript',\n\t\t\t'ol_li','optgroup','option','output','plaintext','pre','q','q_empty',\n\t\t\t'rt','small','sub','sup','tfoot','td','ul',\n\t\t]\n\t}\n\t// populate\n\tfor (const t of Object.keys(oTmp).sort()) {\n\t\toTZP[t] = {}\n\t\tlet array = oTmp[t].sort()\n\t\tarray.forEach(function(item) {oTZP[t][item] = oListBase[item]})\n\t}\n}\n\nfunction create_element(str, vertical = true, isLoop = false) {\n\tlet style = vertical ? 'vertical-lr' : 'horizontal-tb'\n\toList = {}\n\toList[style] = {'test': str}\n\tlet oRes = get_elements('everything', true) // returned tmpdata, target\n\tlet itemdata = oRes[0]['test'][style.slice(0,-3)]\n\tif (!isLoop) {\n\t\ttestTarget = oRes[1]\n\t\tconsole.log('testTarget', style, '\\n', testTarget)\n\t\tconsole.log('isRevert', isRevert, mini(itemdata), itemdata)\n\t} else {\n\t\treturn [mini(itemdata), itemdata]\n\t}\n}\n\nfunction get_elements(type, isTest = false) {\n\tif (undefined == type) {type = 'TZP'}\n\tlet t0 = performance.now()\n\toData = {}\n\tlet params = [s15 + type + sc]\n\tif (!isTest) {\n\t\toList = 'TZP' == type ? oTZP : {'horizontal-tb': oListBase, 'vertical-lr': oListBase}\n\t}\n\n\tlet tmpdata = {}, newobj = {}\n\tlet target, testCount = 0, shadowData = {}\n\tconst parent = dom['element-fp']\n\ttry {\n\t\t// count unique hashes per element+writingstyle + unique element names\n\t\tlet setHash = new Set(), setElements = new Set()\n\n\t\t// create a shadow data object to track unique elements\n\t\t\t// note: it would be easy to create misleading uniqueness given\n\t\t\t// 30+ unqiue elements in everything x 2 writing-modes but\n\t\t\t// we can be diligent in spotting points of difference (e.g. see\n\t\t\t// noembed vertical for blink) and it would be nice to match up\n\t\t\t// the numbers across the two tests\n\t\t// ^ use shadowData\n\n\t\tfor (const s of Object.keys(oList).sort()) {\n\t\t\tlet style = s.slice(0,-3)\n\t\t\tif ('TZP' == type) {tmpdata[style] = {}}\n\t\t\tfor (const k of Object.keys(oList[s]).sort()) {\n\t\t\t\tlet itemdata = {}\n\t\t\t\t// count measurments taken\n\t\t\t\ttestCount++\n\t\t\t\t// unique elements tested\n\t\t\t\tsetElements.add(k)\n\t\t\t\t// set parent, determine target to measure and as we walk\n\t\t\t\t// the children, ensure no other css affects any element\n\t\t\t\t//parent.innerHTML = ''\n\t\t\t\tparent.innerHTML = oList[s][k]\n\t\t\t\ttry {\n\t\t\t\t\ttarget = parent.firstChild\n\t\t\t\t\t// revert everything\n\t\t\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\t\t\tif (isRevert) {target.classList.add('revert')}\n\t\t\t\t\t\tlet newtarget = target.children[0]\n\t\t\t\t\t\tif (undefined == newtarget) {break}\n\t\t\t\t\t\ttarget = newtarget\n\t\t\t\t\t}\n\t\t\t\t\t// choose target\n\t\t\t\t\tif (aUseFirstChild.includes(k)) {target = parent.firstChild}\n\t\t\t\t\t// set style\n\t\t\t\t\tlet extraStyle = undefined == oExtraStyles[k] ? '' : oExtraStyles[k]\n\t\t\t\t\ttarget.setAttribute(\"style\",\"display:inline; writing-mode: \"+ s + extraStyle +\";\")\n\t\t\t\t\tlet method = target.getBoundingClientRect()\n\t\t\t\t\titemdata = [method.width, method.height, method.x, method.y]\n\t\t\t\t} catch(e) {\n\t\t\t\t\titemdata = zErr\n\t\t\t\t\t//console.log(k, e+'')\n\t\t\t\t}\n\n\t\t\t\tif ('TZP' == type) {\n\t\t\t\t\tlet itemhash = mini(itemdata)\n\t\t\t\t\t// add shadowData\n\t\t\t\t\tif (undefined == shadowData[k]) {shadowData[k] = {}}\n\t\t\t\t\tshadowData[k][style] = itemdata\n\t\t\t\t\t// unique measurments\n\t\t\t\t\tsetHash.add(itemhash)\n\t\t\t\t\t// record\n\t\t\t\t\tif (undefined == tmpdata[style][itemhash]) {tmpdata[style][itemhash] = {'data': itemdata, 'group': [k]}\n\t\t\t\t\t} else {tmpdata[style][itemhash]['group'].push(k)}\n\t\t\t\t} else {\n\t\t\t\t\tif (undefined == tmpdata[k]) {tmpdata[k] = {}}\n\t\t\t\t\ttmpdata[k][style] = itemdata\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isTest) {\n\t\t\treturn [tmpdata, target]\n\t\t}\n\n\t\t// counts\n\t\tlet aElements = Array.from(setElements)\n\t\tparams.push(aElements.length +' elements')\n\t\tparams.push(testCount +' measurements')\n\n\t\t//console.log(tmpdata)\n\t\tlet aHash\n\t\tif ('TZP' == type) {\n\t\t\t// group by results\n\t\t\tfor (const s of Object.keys(tmpdata)) {\n\t\t\t\tnewobj[s] = {}\n\t\t\t\tfor (const k of Object.keys(tmpdata[s])) {\n\t\t\t\t\tlet keydata = tmpdata[s][k].group.sort()\n\t\t\t\t\tnewobj[s][keydata.join(' ')] = tmpdata[s][k]['data']\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const s of Object.keys(newobj)) {\n\t\t\t\toData[s] = {}\n\t\t\t\tfor (const k of Object.keys(newobj[s]).sort()) {oData[s][k] = newobj[s][k]}\n\t\t\t}\n\t\t\taHash = Array.from(setHash)\n\t\t\tparams.push(aHash.length +' unique measurements')\n\t\t\t// shadowData\n\t\t\t//console.log(shadowData)\n\t\t\tlet setShadow = new Set()\n\t\t\tfor (const k of Object.keys(shadowData)) {\n\t\t\t\tlet elementhash = mini(shadowData[k])\n\t\t\t\tsetShadow.add(elementhash)\n\t\t\t}\n\t\t\tlet aShadow = Array.from(setShadow)\n\t\t\tparams.push(aShadow.length +' unique elements')\n\t\t} else {\n\t\t\t// group by element hash\n\t\t\tfor (const k of Object.keys(tmpdata)) {\n\t\t\t\tlet elementhash = mini(tmpdata[k])\n\t\t\t\tsetHash.add(elementhash)\n\t\t\t\tif (undefined == newobj[elementhash]) {newobj[elementhash] = {'data': tmpdata[k], 'elements': [k]}\n\t\t\t\t} else {newobj[elementhash]['elements'].push(k)}\n\t\t\t}\n\t\t\t//\n\t\t\tfor (const k of Object.keys(newobj)) { // don't sort\n\t\t\t\toData[k] = {\n\t\t\t\t\t'elements': newobj[k]['elements'].join(', '),\n\t\t\t\t\t'data': newobj[k]['data']\n\t\t\t\t}\n\t\t\t}\n\t\t\taHash = Array.from(setHash)\n\t\t\tparams.push(aHash.length +' unique elements')\n\t\t}\n\n\n\t\t//console.log(tmpdata)\n\t\tparent.innerHTML = \"\" // clear display\n\n\t\tlet hash = mini(oData), notation = \"\"\n\t\tlet t1 = performance.now()\n\t\ttry {dom.perf.innerHTML = Math.round(t1 - t0) +\" ms\"} catch(e) {}\n\n\t\tlet strParam = (params.length ? \" [\"+ params.join(\" | \") +\"]\" : \"\")\n\t\tlet display = [], tmphash = \"\"\n\t\tdom.elements.innerHTML = hash + strParam +\"<br><br>\"+ json_highlight(oData, 120)\n\n\t\treturn\n\t} catch(e) {\n\t\tparent.innerHTML = \"\" // clear display\n\t\tdom.elements = e+\"\"\n\t\treturn\n\t}\n}\n\nfunction run(type) {\n\t// clear\n\tdom.elements.innerHTML = \" &nbsp; \"\n\t// pause so users see change\n\tsetTimeout(function() {\n\t\tget_elements(type)\n\t}, 170)\n}\n\nPromise.all([\n\tget_globals(),\n\tbuild_lists(),\n]).then(function(){\n\tisRevert = true\n\trun_once()\n\trun()\n})\n\n\n/*** NOT USED in NOCSS test ***/\nfunction run_once() {\n\t// display lang/locale\n\tlet isLanguage = '', isLocale = ''\n\ttry {isLocale = Intl.DateTimeFormat().resolvedOptions().locale} catch(e) {isLocale = zErr}\n\ttry {isLanguage = navigator.language} catch(e) {isLanguage = zErr}\n\ttry {dom.locale.innerHTML = isLanguage +' | '+ isLocale} catch(e) {}\n\n\t// enumerate elements: messy as F but who cares .. track element use and display\n\tlet sKey = '<span class=\"key\">',\n\t\tsNumber = '<span class=\"number\">',\n\t\tsString = '<span class=\"string\">'\n\n\tlet tzpSet = new Set(), aAll = [], aRedundant = []\n\tfor (const t of Object.keys(oTZP)) {for (const k of Object.keys(oTZP[t])) {tzpSet.add(k)}}\n\tlet aTZP = Array.from(tzpSet).sort()\n\tfor (const k of Object.keys(oListBase)) {if (!aTZP.includes(k)) {aRedundant.push(k)}}\n\tlet oElements = {\n\t\t'ignored': aIgnored.sort(),\n\t\t'redundant': aRedundant.sort(),\n\t\t'tzp': aTZP.sort(),\n\t\t'remainder': aRemainder.sort(),\n\t}\n\tfor (const k of Object.keys(oElements)) {aAll = aAll.concat(oElements[k])}\n\taAll.sort()\n\tlet aAllCheck = aAll.filter(function(item, position) {return aAll.indexOf(item) === position})\n\tlet sanityStr = ''\n\tif (aAll.length !== aAllCheck.length) {\n\t\tsanityStr = sb+' [duplicates]'+sc\n\t\tconsole.log('duplicates\\n', aAll)\n\t}\n\tlet countTotal = aRedundant.length + aTZP.length + aIgnored.length + aRemainder.length\n\tstrElement = '<u>ELEMENTS</u> [' + countTotal +']' + sanityStr + '<br><br>'\n\tstrElement += '{<br><div class=\"indent\">'+\n\t\tsKey +'ignored: ' + sc +'{ ' + sNumber + aIgnored.length + sc\n\t\t\t+'<br><div class=\"indent\">'+ sString + aIgnored.join(', ') + sc +'</div><br>}'\n\t\t+'</div><br>}'\n\tstrElement += '<br>{<br><div class=\"indent\">'+\n\t\tsKey +'redundant: ' + sc +'{ ' + sNumber + aRedundant.length + sc\n\t\t\t+'<br><div class=\"indent\">'+ sString + aRedundant.join(', ') + sc +'</div><br>}'\n\t\t+'</div><br>}'\n\tstrElement += '<br>{<br><div class=\"indent\">'+\n\t\tsKey +'TZP: ' + sc +'{ ' + sNumber + aTZP.length + sc\n\t\t\t+'<br><div class=\"indent\">'+ sString + aTZP.join(', ') + sc +'</div><br>}'\n\t\t+'</div><br>}'\n\tif (aRemainder.length) {\n\t\tstrElement += '<br>{<br><div class=\"indent\">'+\n\t\t\tsKey +'TBA:: work in progres: ' + sc +'{ ' + sNumber + aRemainder.length + sc\n\t\t\t\t+'<br><div class=\"indent\">'+ sString + aRemainder.join(', ') + sc +'</div><br>}'\n\t\t\t+'</div><br>}'\n\t}\n}\n\nfunction list_elements() {\n\tdom.perf.innerHTML = ''\n\tdom.elements.innerHTML = strElement\n}\n\nfunction check_revert(vertical = true) {\n\t// with our inline styles messing with things everything should be false (no matches)\n\t// if we comment out the inline styles, everything should match\n\tlet style = vertical ? 'vertical' : 'horizontal'\n\t// remember isRevert\n\tlet isRevertState = isRevert\n\t// reset\n\toChecks = {}\n\toCheckSummary = {}\n\t// cycle thru each element and measure with and\n\t// without revert to ensure we are changing it\n\tfor (const k of Object.keys(oListBase)) {\n\t\tlet str = oListBase[k]\n\t\tisRevert = true\n\t\tlet data = create_element(str, vertical, true)\n\t\tlet hashR = data[0]\n\t\toChecks[k] = {\n\t\t\t'normal': '',\n\t\t\t'revert': data,\n\t\t}\n\t\tisRevert = false\n\t\tdata = create_element(str, vertical, true)\n\t\tlet hashN = data[0]\n\t\toChecks[k].normal = data\n\t\tlet test = hashR == hashN\n\t\tif (undefined == oCheckSummary[test]) {oCheckSummary[test] = []}\n\t\toCheckSummary[test].push(k)\n\t}\n\tconsole.log(style)\n\tconsole.log('summary\\n' ,oCheckSummary)\n\tconsole.log('data\\n', oChecks)\n\t// restore isRevert\n\tisRevert = \tisRevertState\n}\n\n\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/elementother_nocss.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>elements: other [no css]</title>\n  <!--<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\"> <!-- -->\n  <script src=\"testglobals.js\"></script>\n  <script src=\"testgeneric.js\"></script>\n\t<style>\n\t\t.mono {font-family: monospace, \"Courier New\"; font-size: 12px;}\n\t\t.spaces {white-space: pre-wrap;}\n\t\t#tb15 {\n\t\t\twidth: 97%;\n\t\t\tmin-width: 780px;\n\t\t\tmax-width: 780px;\n\t\t}\n\t\t#element-fp {\n\t\t\tposition: fixed;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tfont-family: none;\n\t\t\tfont-size: initial;\n\t\t\tfont-style: normal;\n\t\t\tfont-variant: normal;\n\t\t\tfont-weight: normal;\n\t\t\tline-height: normal;\n\t\t\ttext-transform: none;\n\t\t\ttext-align: left;\n\t\t\ttext-decoration: none;\n\t\t\ttext-shadow: none;\n\t\t\twhite-space: nowrap;\n\t\t\ttransform: skew(1.787542deg,3.263901deg);\n\t\t}\n\t</style>\n</head>\n\n<body>\n\t<div id=\"element-fp\"></div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#elements\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb15\">\n\t\t<col width=\"25%\"><col width=\"75%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">elements: other [no css]</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\">\n\t\t\t<span>The <a href=\"elementother.html\">elements: other</a> hash should match the one here - to ensure\n\t\t\tthat custom styles don't affect the fingerprint.</span><br><br>\n\t\t\t\t<span class=\"btn15 btnfirst\" style=\"cursor: pointer;\" onClick=\"run(`everything`)\">[ everything ]</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"><hr><br></td></tr>\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\n\t\t\t<div class=\"c mono spaces no_color\" id=\"elements\"></div>\n\t\t</td></tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nlet isRevert = false\nlet oData = {}, oList = {}, oListBase = {}\nlet testTarget\n\nlet aUseFirstChild = ['hgroup'] // we want to measure the first element, not the last\nlet oExtraStyles = {\n\t'marquee': '; width: 20px; height: 20px', // if we don't constrain it, it changes with inner window sizes\n}\n\nfunction build_lists() {\n\t// note: some elements we insert a char \".\" to a) force a height\n\t// or b) for unique measurements without a char to get more precision/decimal places\n\t// always use the same char\n\tlet oTmp = {\n\t\ta: '<a href=\"\">.</a>',\n\t\taudio: '<audio controls=\"\"></audio>',\n\t\tbase: '<base href=\"\"/>', // empty: width/x are zero but height/y are interesting\n\t\tbasefont: '<basefont color=\"#FF0000\"/>',\n\t\tbig_x2: '<big><big>.</big></big>',\n\t\tbig_x3: '<big><big><big>.</big></big></big>',\n\t\t// always revert tables because we have that in our inline style in the plain test\n\t\tcaption: '<table><caption>.</caption></table>',\n\t\tdd: '<dl><dd>.</dd></dl>',\n\t\tdialog: '<dialog open=\"\"></dialog>',\n\t\tdt: '<dl><dt>.</dt></dl>',\n\t\tfigcaption: '<figure><figcaption>.</figcaption></figure>',\n\t\thgroup: '<hgroup><h1>.</h1><p class=\"revert\">.</p></hgroup>', // revert 2nd child (p)\n\t\tisindex: '<isindex prompt=\"search\"></isindex>',\n\t\tkeygen: '<form method=\"POST\"><keygen></keygen></form>',\n\t\tlabel: '<div id=\"elementlabel\"><label for=\"elementlabel\">.</label></div<',\n\t\tlegend: '<fieldset><legend>.</legend></fieldset>',\n\t\tli: '<ul><li></li></ul>',\n\t\tlink: '<link href=\"\" rel=\"stylesheet\">',\n\t\tnoembed: '<embed><noembed>.</noembed>',\n\t\toutput: '<form><input>=<output class=\"revert\"></output></form>',\n\t\t\t// '<form><input id=\"a\" value=\".\"> = <output id=\"x\" name=\"x\" for=\"a\"></output></form>',\n\t\t\t// we don't actually need to calculate any values\n\t\trb: '<ruby><rb>.</rb></ruby>',\n\t\trbc: '<ruby><rbc>.</rbc></ruby>',\n\t\trp: '<ruby><rp>.</rp></ruby>',\n\t\trt: '<ruby><rt>.</rt></ruby>',\n\t\trtc: '<ruby><rtc>.</rtc></ruby>',\n\t\tsummary: '<details><summary>.</summary></details>',\n\t\t'q_empty': '<q></q>',\n\t\t// tables\n\t\ttbody: '<table><tbody></tbody></table>', \n\t\ttd: '<table><tr><td></td></tr></table>',\n\t\ttfoot: '<table><tfoot></tfoot></table>',\n\t\tth: '<table><thead><tr><th></th></tr></thead></table>',\n\t\tthead: '<table><thead></thead></table>',\n\t\ttr: '<table><tr></tr></table>',\n\t\t// other\n\t\t'ol_li': '<ol><li>.</li></ol>',\n\t\t'menu_li': '<menu>.<li></li></menu>',\n\t\t'ul_li': '<ul><li>.</li></ul>',\n\t\t// test error\n\t\t//'error': '<frame></frame>'\n\t}\n\t// programatically add more\n\tlet oExtra = {\n\t\t'char': [\n\t\t\t'abbr','acronym','address','applet','article','aside',\n\t\t\t'b','big','blockquote','center','cite','code',\n\t\t\t'data','del','dir','div','dfn','dl','em','footer',\n\t\t\t'h1','h2','h3','h4','h5','h6','header',\n\t\t\t'i','ins','iframe','kbd',\n\t\t\t'main','mark','marquee','menu','meter',\n\t\t\t'nobr','noframes','noscript','ol','option','p','pre','q',\n\t\t\t'ruby',\n\t\t\t's','samp','section','slot','small','span','strike','strong','sub','sup',\n\t\t\t'time','title','tt',\n\t\t\t'u','ul','var','xmp',\n\t\t],\n\t\tnochar: [\n\t\t\t'canvas','fieldset','figure','geolocation','img',\n\t\t\t'nav','optgroup','picture',\n\t\t\t'portal', // https://wicg.github.io/portals/#the-portal-element\n\t\t\t'search','table','video',\n\t\t],\n\t\tstandalone: [\n\t\t\t'br','hr','object','plaintext',\n\t\t]\n\t}\n\tfor (const type of Object.keys(oExtra)) {\n\t\tfor (const k of Object.keys(oExtra[type])) {\n\t\t\tlet array = oExtra[type]\n\t\t\tarray.forEach(function(item) {\n\t\t\t\tif ('standalone' == type) {\n\t\t\t\t\toTmp[item] = '<'+ item +'>'\n\t\t\t\t} else {\n\t\t\t\t\tlet str = 'char' == type ? '.' : ''\n\t\t\t\t\toTmp[item] = '<'+ item +'>'+ str +'</'+ item +'>'\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\t// sort into final object\n\tfor (const k of Object.keys(oTmp).sort()) {\n\t\toListBase[k] = oTmp[k]\n\t}\n}\n\nfunction create_element(str, vertical = true) {\n\tlet style = vertical ? 'vertical-lr' : 'horizontal-tb'\n\toList = {}\n\toList[style] = {'test': str}\n\tlet oRes = get_elements('everything', true) // returned tmpdata, target\n\tlet itemdata = oRes[0]['test'][style.slice(0,-3)]\n\ttestTarget = oRes[1]\n\tconsole.log('testTarget', style, '\\n', testTarget)\n\tconsole.log('isRevert', isRevert, mini(itemdata), itemdata)\n}\n\nfunction get_elements(type, isTest = false) {\n\ttype = 'everything'\n\tlet t0 = performance.now()\n\toData = {}\n\tlet params = [s15 + type + sc]\n\tif (!isTest) {\n\t\toList = {'horizontal-tb': oListBase, 'vertical-lr': oListBase}\n\t}\n\n\tlet tmpdata = {}, newobj = {}\n\tlet target, testCount = 0, shadowData = {}\n\tconst parent = dom['element-fp']\n\ttry {\n\t\t// count unique hashes per element+writingstyle + unique element names\n\t\tlet setHash = new Set(), setElements = new Set()\n\n\t\t// create a shadow data object to track unique elements\n\t\t\t// note: it would be easy to create misleading uniqueness given\n\t\t\t// 30+ unqiue elements in everything x 2 writing-modes but\n\t\t\t// we can be diligent in spotting points of difference (e.g. see\n\t\t\t// noembed vertical for blink) and it would be nice to match up\n\t\t\t// the numbers across the two tests\n\t\t// ^ use shadowData\n\n\t\tfor (const s of Object.keys(oList).sort()) {\n\t\t\tlet style = s.slice(0,-3)\n\t\t\tfor (const k of Object.keys(oList[s]).sort()) {\n\t\t\t\tlet itemdata = {}\n\t\t\t\t// count measurments taken\n\t\t\t\ttestCount++\n\t\t\t\t// unique elements tested\n\t\t\t\tsetElements.add(k)\n\t\t\t\t// set parent, determine target to measure and as we walk\n\t\t\t\t// the children, ensure no other css affects any element\n\t\t\t\t//parent.innerHTML = ''\n\t\t\t\tparent.innerHTML = oList[s][k]\n\t\t\t\ttry {\n\t\t\t\t\ttarget = parent.firstChild\n\t\t\t\t\t// revert everything\n\t\t\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\t\t\tif (isRevert) {target.classList.add('revert')}\n\t\t\t\t\t\tlet newtarget = target.children[0]\n\t\t\t\t\t\tif (undefined == newtarget) {break}\n\t\t\t\t\t\ttarget = newtarget\n\t\t\t\t\t}\n\t\t\t\t\t// choose target\n\t\t\t\t\tif (aUseFirstChild.includes(k)) {target = parent.firstChild}\n\t\t\t\t\t// set style\n\t\t\t\t\tlet extraStyle = undefined == oExtraStyles[k] ? '' : oExtraStyles[k]\n\t\t\t\t\ttarget.setAttribute(\"style\",\"display:inline; writing-mode: \"+ s + extraStyle +\";\")\n\t\t\t\t\tlet method = target.getBoundingClientRect()\n\t\t\t\t\titemdata = [method.width, method.height, method.x, method.y]\n\t\t\t\t} catch(e) {\n\t\t\t\t\titemdata = zErr\n\t\t\t\t\t//console.log(k, e+'')\n\t\t\t\t}\n\t\t\t\tif (undefined == tmpdata[k]) {tmpdata[k] = {}}\n\t\t\t\ttmpdata[k][style] = itemdata\n\t\t\t}\n\t\t}\n\n\t\tif (isTest) {\n\t\t\treturn [tmpdata, target]\n\t\t}\n\n\t\t// counts\n\t\tlet aElements = Array.from(setElements)\n\t\tparams.push(aElements.length +' elements')\n\t\tparams.push(testCount +' measurements')\n\n\t\t//console.log(tmpdata)\n\t\tlet aHash\n\t\t// group by element hash\n\t\tfor (const k of Object.keys(tmpdata)) {\n\t\t\tlet elementhash = mini(tmpdata[k])\n\t\t\tsetHash.add(elementhash)\n\t\t\tif (undefined == newobj[elementhash]) {newobj[elementhash] = {'data': tmpdata[k], 'elements': [k]}\n\t\t\t} else {newobj[elementhash]['elements'].push(k)}\n\t\t}\n\t\t//\n\t\tfor (const k of Object.keys(newobj)) { // don't sort\n\t\t\toData[k] = {\n\t\t\t\t'elements': newobj[k]['elements'].join(', '),\n\t\t\t\t'data': newobj[k]['data']\n\t\t\t}\n\t\t}\n\t\taHash = Array.from(setHash)\n\t\tparams.push(aHash.length +' unique elements')\n\n\t\t//console.log(tmpdata)\n\t\tparent.innerHTML = \"\" // clear display\n\n\t\tlet hash = mini(oData), notation = \"\"\n\t\tlet t1 = performance.now()\n\t\ttry {dom.perf.innerHTML = Math.round(t1 - t0) +\" ms\"} catch(e) {}\n\n\t\tlet strParam = (params.length ? \" [\"+ params.join(\" | \") +\"]\" : \"\")\n\t\tlet display = [], tmphash = \"\"\n\t\tdom.elements.innerHTML = hash + strParam +\"<br><br>\"+ json_highlight(oData, 120)\n\n\t\treturn\n\t} catch(e) {\n\t\tparent.innerHTML = \"\" // clear display\n\t\tdom.elements = e+\"\"\n\t\treturn\n\t}\n}\n\nfunction run(type) {\n\t// clear\n\tdom.elements.innerHTML = \" &nbsp; \"\n\t// pause so users see change\n\tsetTimeout(function() {\n\t\tget_elements(type)\n\t}, 170)\n}\n\nPromise.all([\n\tget_globals(),\n\tbuild_lists(),\n]).then(function(){\n\tisRevert = false\n\trun()\n})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/engine.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=700\">\r\n\t<title>engine</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 680px;}\r\n\t\t#tb3 td:first-child { text-align: left; vertical-align: top;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"offscreen\">\r\n\t\t<div id=\"mozFont\"></div>\r\n\t\t<div id=\"test95a\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-1</div>\r\n\t\t<div id=\"test95b\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-12020-1</div>\r\n\t</div>\r\n\t<div class=\"hidden\">\r\n\t\t<div><input type=\"time\" min=\"14:00:00\" max=\"12:00:00\" value=\"15:00:00\" id=\"test76\"></div>\r\n\t\t<span id=\"mozColor\"></span>\r\n\t</div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#feature\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb3\">\r\n\t\t<col width=\"50%\"><col width=\"50%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">engine\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A tiny sample of the myriad ways engines differ. This test is tweaked\r\n\t\t\tfor gecko [FF52+]: you want <span class=\"s9\"><b> &#x2713; </b></span>'s. Anything different\r\n\t\t\t<span class=\"bad\"><b> &#x2716; </b></span> or blocked <span class=\"s4\"><b> &#x2715; </b></span>\r\n\t\t\tonly makes you stand out. A single unspoofable property check can take as little as <span class=\"s3\">0.01 ms</span>.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\"><span class=\"no_color c mono spaces\" id=\"results\"></span></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet results = [],\r\n\titemnumbers = [],\r\n\tdata = {},\r\n\taPerf = [],\r\n\tcount = 0,\r\n\texpected = 96,\r\n\tgt0, // global timer\r\n\tbBlock = false, // throw a test error\r\n\tbNum = false // display numbering instead of perf\r\n\r\nfunction fnTrim(str, len) {\r\n\tstr = str.replace(/(\\r\\n|\\n|\\r)/gm,\"\")\r\n\tif (str.length > len) {str = str.slice(0,len-3) +\"...\"}\r\n\treturn str\r\n}\r\n\r\nfunction fnClean(str) {\r\n\tif (str == \"\") {str = \"empty string\"\r\n\t} else if (str === undefined) {str = zU\r\n\t} else if (str == zU) {str = zUQ}\r\n\treturn str\r\n}\r\n\r\nfunction logPerf() {\r\n\tif (aPerf.length) {\r\n\t\tconsole.log(\"PERF LOG\\n\", aPerf)\r\n\t}\r\n}\r\n\r\nfunction fnDisplay() {\r\n\t// perf\r\n\tlet perfValue = Math.round(performance.now() - gt0) +\" ms\"\t\t\r\n\tlet perfTop = perfValue\r\n\t// remove the first 5 items: section headers + hr\r\n\tif (aPerf.length) {\r\n\t\tperfTop = \"<span style='cursor: pointer;' onClick='logPerf()'>\" + perfTop +\"</span>\"\r\n\t\taPerf.splice(0, 5)\r\n\t}\r\n\tdom.perf.innerHTML = perfTop\r\n\r\n\t// data -> ordered results\r\n\tconst names = Object.keys(data).sort((a,b) => a-b)\r\n\tfor (const k of names) {results.push(k +\"~~~\"+ data[k])}\r\n\tlet display = [],\r\n\t\terrcount = 0\r\n\tlet countTrue = 0, countFalse = 0, countBlock = 0, countNA = 0\r\n\t// check no numbers duplicated\r\n\tif (expected !== results.length) {\r\n\t\tdisplay.push(sb+ \"ERROR:\"+ sc + \" duplicate numbers detected<br>\")\r\n\t}\r\n\t// parse for pretty output\r\n\tfor (let i=0; i < results.length; i++) {\r\n\t\tlet pad = 35\r\n\t\tlet order = results[i].split(\"~~~\")[0],\r\n\t\t\tdesc = results[i].split(\"~~~\")[1].trim(),\r\n\t\t\tresult = results[i].split(\"~~~\")[2],\r\n\t\t\tmatch = results[i].split(\"~~~\")[3],\r\n\t\t\tperf = results[i].split(\"~~~\")[4]\r\n\t\tif (perf == undefined || perf == \"\" || perf == \"undefined\") {perf = \" - \"\r\n\t\t} else if (perf !== \"NaN\") {\r\n\t\t\tperf = Math.round(perf)\r\n\t\t\tperf = perf.toString()\r\n\t\t\tperf = perf.padStart(2) +\" \"\r\n\t\t}\r\n\r\n\t\tif (desc == \"header\") {\r\n\t\t\tlet note = \"\"\r\n\t\t\tif (result == \"errors\") {\r\n\t\t\t\tnote = \" [there are thousands of these]\"\r\n\t\t\t}\r\n\t\t\tresult = s14 + result.toUpperCase() + sc + note\r\n\t\t\tdisplay.push(\"<span>\"+ result + \"</span><br>\")\r\n\t\t} else if (desc == \"hr\") {\r\n\t\t\tdisplay.push(\"<br><hr>\")\r\n\t\t} else {\r\n\t\t\tif (match == zErr) {match = yellow_block; countBlock++\r\n\t\t\t} else if (match == \"true\") {match = green_tick; countTrue++\r\n\t\t\t} else if (match == \"false\") {match = red_cross; countFalse++\r\n\t\t\t} else {match = white_na; countNA++}\r\n\t\t\tmatch += \" \"\r\n\t\t\tif (desc == \"error\") {\r\n\t\t\t\tpad = 3; errcount++; desc = errcount.toString()\r\n\t\t\t}\r\n\t\t\tdesc = s3 + desc.padStart(pad) + sc\r\n\t\t\tlet sectionspace = \"\"\r\n\t\t\tif (i+1 < results.length) {\r\n\t\t\t\tlet nextitem = results[i+1].split(\"~~~\")[1].trim()\r\n\t\t\t\tif (nextitem == \"header\") {sectionspace = \"<br>\"}\r\n\t\t\t}\r\n\t\t\tdesc += s99 +\" \"+ (bNum ? order : perf) + sc\r\n\t\t\tdisplay.push(desc + match +\" \"+ result + sectionspace)\r\n\t\t}\r\n\t}\r\n\t// summary\r\n\tlet countValid = countTrue + countFalse + countBlock // na tests ignored\r\n\tlet countFail = countFalse + countBlock\r\n\tlet percentPassed = Math.floor((countTrue/countValid)*100)\r\n\tlet isPass = percentPassed == 100 ? true : false\r\n\tlet percentFailed = Math.ceil((countFail/countValid)*100)\r\n\tlet summary = \"\"\r\n\tif (isFFvalid) {\r\n\t\tlet needStr = \"... who needs \"+ s3+ (expected - 5) + \" tests\" + sc\r\n\t\t\t+ \" and \"+ s3 + \"several hundred metrics\" + sc + \" in \"+ s3 + perfValue + sc + \"?\"\r\n\t\tsummary += s14 +\"MINI TESTS \"+ sc + needStr +\"<br><br>\"\r\n\t\t\t+ \"engine: \"+ isEnginePretty.trim() +\"<br><br>\"\r\n\t\t\t+ \" gecko: \"+ isFFpretty.trim() +\"<br><br><hr><br>\"\r\n\t}\r\n\tsummary += s14 +\"SUMMARY \"+ sc\r\n\t\t+\"PASS: \".padStart(7) + countTrue + (isPass? sg : sb) +\" [\"+ percentPassed +\"%]\"+ sc\r\n\t\t+\"FAIL: \".padStart(7) + countFail + (isPass? sg : sb) +\" [\"+ percentFailed +\"%]\"+ sc\r\n\tif (countNA > 0) {summary += \"N/A: \".padStart(6) + countNA}\r\n\tif (isFF) {\r\n\t\tsummary += s14+ \"DEBUG\".padStart(7) + sc\r\n\t\tlet strVer = isVer\r\n\t\tstrVer += (isVer == isVerMax ? \"+\" : \"\")\r\n\t\tstrVer += (isVer == 52 ? \" or lower\" : \"\")\r\n\t\tsummary += \"VER: \".padStart(6) + strVer\r\n\t}\r\n\tsummary += \" \" + buildButton(3, \"all\", \"all details\")\r\n\t// output\r\n\tdom.results.innerHTML = summary + \"<br><br>\" + display.join(\"<br>\")\r\n}\r\n\r\nfunction fnRecord(order, description, result, match, error, perf) {\r\n\tif (\"number\" === typeof perf) {\r\n\t\tperf = performance.now() - perf\r\n\t}\r\n\r\n\tif (error !== undefined && error !== \"skip\") {\r\n\t\tlet hash = mini(error)\r\n\t\tif (hash !== \"d8281b3c\") { // assignment to undeclared variable bB\r\n\t\t\t//console.error(order, result, error)\r\n\t\t\thash = s3+ \" [\"+ hash +\"]\"+ sc\r\n\t\t} else {\r\n\t\t\tresult = \"test error thrown\"; hash = \"\"\r\n\t\t}\r\n\t\tresult += hash\r\n\t}\r\n\taPerf.push([perf, order, description])\r\n\torder = (order+\"\").padStart(3,\"0\")\r\n\tdata[order] = description +\"~~~\"+ result +\"~~~\"+ match + \"~~~\" + perf\r\n\tcount ++\r\n\titemnumbers.push(order +\" \"+ description)\r\n\tif (count == expected) {\r\n\t\tfnDisplay()\r\n\t}\r\n\tif (count > expected) {console.error(count, \"expected count too low\")}\r\n}\r\n\r\nfunction get_colorgamut(num, title) {\r\n\t// added in FF110+ 1422237\r\n\tlet q = \"(color-gamut: \", res = \"undefined\", bolC = false\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tif (window.matchMedia(q +\"srgb)\").matches) {res = \"srgb\"}\r\n\t\tif (window.matchMedia(q +\"p3)\").matches) {res = \"p3\"}\r\n\t\tif (window.matchMedia(q +\"rec2020)\").matches) {res = \"rec2020\"}\r\n\t\tlet bool = res == \"undefined\"\r\n\t\tif (isVer > 109) {\r\n\t\t\tbool = zNA\r\n\t\t\tres = zNA +\": FF109 or lower \"+ s3 +\"[\"+ res +\"]\"+ sc\r\n\t\t} else if (!isFF) {\r\n\t\t\tbool = zNA\r\n\t\t\tres += \" [FF109 or lower]\"\r\n\t\t}\r\n\t\tfnRecord(num, title, res, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_dates(num) {\r\n\t// 1274354: meta\r\n\t// 1557650\r\n\tlet strA = \"[5 digit year] new Date\"\r\n\ttry {\r\n\t\tlet t0A = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resA = new Date(\"19999-11-11\")\r\n\t\tlet bolA = resA == \"Invalid Date\" ? true : false\r\n\t\tif (!bolA) {resA = fnTrim(resA.toString(), 40)}\r\n\t\tif (isVer > 119) {\r\n\t\t\tbolA = zNA\r\n\t\t\tresA = zNA +\": FF119 or lower \" + s3 +\"[\"+ fnTrim(resA.toString(), 23) +\"]\" + sc\r\n\t\t} else if (!isFF) {\r\n\t\t\tbolA = zNA\r\n\t\t\tresA += \" [FF119 or lower]\"\r\n\t\t}\r\n\t\tfnRecord(num, strA, resA, bolA, \"skip\", t0A)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, strA, e.name, zErr, e.message)\r\n\t}\r\n\t// 1515318\r\n\tlet strB = \"[hyphen] new Date\"\r\n\ttry {\r\n\t\tlet t0B = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resB = new Date(\"31-Mar-2011\").getFullYear()\r\n\t\t// FF: 63 and lower = NaN, 64+ = -2011\r\n\t\tlet bolB = false\r\n\t\tif (resB == -2011) {bolB = true\r\n\t\t} else if (isVer < 64 && isNaN(resB)) {bolB = true\r\n\t\t} else if (isVer > 112 && resB == 2011) {\r\n\t\t\tbolB = zNA\r\n\t\t\tresB = zNA +\": FF112 or lower \" + s3 +\"[\"+ resB+\"]\" + sc\r\n\t\t} else if (!isFF) {\r\n\t\t\tbolB = zNA\r\n\t\t\tresB += \" [FF112 or lower]\"\r\n\t\t}\r\n\t\tfnRecord(num+1, strB, resB, bolB, \"skip\", t0B)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+1, strB, e.name, zErr, e.message)\r\n\t}\r\n\t// 1439800\r\n\tlet strC = \"[string] new Data\"\r\n\ttry {\r\n\t\tlet t0C = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resC = new Date(\"11-Nov-11\")\r\n\t\tlet bolC = resC.toString() == \"Invalid Date\" ? true : false\r\n\t\tif (!bolC) {\r\n\t\t\tif (isFF) {\r\n\t\t\t\tif (isVer > 112) {\r\n\t\t\t\t\tbolC = zNA\r\n\t\t\t\t\tresC = zNA +\": FF112 or lower \" + s3 +\"[\"+ fnTrim(resC.toString(), 23) +\"]\"+sc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tresC = fnTrim(resC.toString(), 40)\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tbolC = zNA\r\n\t\t\t\tresC = fnTrim(resC.toString(), 22) +\" [FF112 or lower]\"\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num+2, strC, resC, bolC, \"skip\", t0C)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+2, strC, e.name, zErr, e.message)\r\n\t}\r\n\t// 1599375\r\n\tlet strD = \"[+0000 UTC] Date.parse\"\r\n\ttry {\r\n\t\tlet t0D = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resD = Date.parse(\"2019-11-26 07:39:58.286157072 +0000 UTC\")\r\n\t\tlet bolD = \"\"\r\n\t\tif (isVer > 120) {\r\n\t\t\tbolD = zNA\r\n\t\t\tresD = zNA +\": FF120 or lower \" + s3 +\"[\"+ fnTrim(resD.toString(), 23) +\"]\"+sc\r\n\t\t} else {\r\n\t\t\tbolD = isNaN(resD) ? true : false\r\n\t\t}\r\n\t\tfnRecord(num+3, strD, resD, bolD, \"skip\", t0D)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+3, strD, e.name, zErr, e.message)\r\n\t}\r\n\t// 1730155\r\n\tlet strE = \"[localized] Date.parse\"\r\n\ttry {\r\n\t\tlet t0E = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resE = Date.parse(\"Mercredi 8 Septembre 2021\")\r\n\t\tlet bolE = \"\"\r\n\t\tif (isVer > 121) { // awaiting v122 test\r\n\t\t\tbolE = zNA\r\n\t\t\tresE = zNA +\": FF121 or lower \" + s3 +\"[\"+ fnTrim(resE.toString(), 23) +\"]\"+sc\r\n\t\t} else {\r\n\t\t\tbolE = isNaN(resE) ? true : false\r\n\t\t}\r\n\t\tfnRecord(num+4, strE, resE, bolE, \"skip\", t0E)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+4, strE, e.name, zErr, e.message)\r\n\t}\r\n\t// 1783731\r\n\tlet strF = \"[subtraction] Date.parse\"\r\n\ttry {\r\n\t\tlet t0F = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resF = Date.parse('2022-08-08') - Date.parse('2022-08-8')\r\n\t\tlet bolF = \"\"\r\n\t\tif (isVer > 120) {\r\n\t\t\tbolF = zNA\r\n\t\t\tresF = zNA +\": FF120 or lower \" + s3 +\"[\"+ fnTrim(resF.toString(), 23) +\"]\"+sc\r\n\t\t} else {\r\n\t\t\tbolF = resF == 0\r\n\t\t}\r\n\t\tfnRecord(num+6, strF, resF, bolF, \"skip\", t0F)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+6, strF, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_errors(num) {\r\n\tlet tests = [\r\n\t\t[\"var a = {}; a.b = a; JSON.stringify(a)\", \"TypeError: cyclic object value\"],\r\n\t\t[\"alert('A)\",\"SyntaxError: '' string literal contains an unescaped line break\"],\r\n\t\t[\"let a = 1_00_;\",\"SyntaxError: underscore can appear only between digits, not after t...\", // FF72+\r\n\t\t\t\"SyntaxError: identifier starts immediately after numeric literal\", // FF60-69\r\n\t\t\t\"SyntaxError: missing digit after '_' numeric separator\"], // FF70-71\r\n\t\t[\"const foo;foo.bar\",\"SyntaxError: missing = in const declaration\"],\r\n\t\t[\"null.bar\",\"TypeError: can't access property \\\"bar\\\" of null\",\"TypeError: null has no properties\"],\r\n\t\t[\"(1).toString(1000)\",\"RangeError: radix must be an integer at least 2 and no greater than 36\"],\r\n\t\t[\"var x = new Array(-1)\",\"RangeError: invalid array length\"],\r\n\t\t[\"[...undefined].length\",\"TypeError: can't access property Symbol.iterator of undefined\",\"TypeError: undefined has no properties\"],\r\n\t\t[\"const tzp=1; const tzp=2;\",\"SyntaxError: redeclaration of const tzp\"],\r\n\t\t[\"const foo;foo.bar\",\"SyntaxError: missing = in const declaration\"],\r\n\t\t[\"var x = @\",\"SyntaxError: illegal character U+0040\",\"SyntaxError: illegal character\"],\r\n\t]\r\n\ttests.forEach(function(array) {\r\n\t\tlet t0 = performance.now()\r\n\t\ttry {\r\n\t\t\tnewFn(array[0])\r\n\t\t} catch(e) {\r\n\t\t\tlet bool = false\r\n\t\t\tlet str = fnTrim(e.name +\": \"+ e.message, 70)\r\n\t\t\tif (isVer < 60 && array[0] == \"alert('A)\") {\r\n\t\t\t\tif (str == \"SyntaxError: unterminated string literal\") {bool = true}\r\n\t\t\t} else {\r\n\t\t\t\tif (str == array[1] || str == array[2] || str == array[3]) {bool = true}\r\n\t\t\t}\r\n\t\t\tfnRecord(num, \"error\", str, bool, \"skip\", t0)\r\n\t\t\tnum++\r\n\t\t}\r\n\t})\r\n}\r\n\r\nfunction get_eval_length(num, title) {\r\n\tlet bool = false\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet test = eval.toString().length\r\n\t\tif (test == 37) {bool = true}\r\n\t\tfnRecord(num, title, test, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_iframe_props(num1, num2, title) {\r\n\tlet t0 = performance.now()\r\n\t// just a small sample\r\n\tlet listE = ['dump','fullScreen','getDefaultComputedStyle','mozInnerScreenX',\r\n\t\t'mozInnerScreenY','netscape','onmozfullscreenchange','onmozfullscreenerror']\r\n\tlet listU = ['BatteryManager','HID','Keyboard','Lock','Serial',\r\n\t\t'USB','WakeLock','WebKitMutationObserver','XRAnchor','chrome','webkitCancelAnimationFrame',\r\n\t\t'webkitMediaStream','webkitRTCPeerConnection','webkitRequestAnimationFrame',\r\n\t\t'webkitRequestFileSystem','webkitResolveLocalFileSystemURL','webkitStorageInfo']\r\n\t// FYI: don't use MIDIAccess: FF60+ behind dom.webmidi.enabled... 1752906: FF99+ default enabled\r\n\r\n\tif (isVer > 121) { listU = listU.filter(x => ![\"WakeLock\"].includes(x)) } // FF122+ dom.screenwakelock.enabled: 1589554\r\n\tif (isVer > 92) { listU = listU.filter(x => ![\"Lock\"].includes(x)) } // FF93+ \"dom.weblocks.enabled\": 1739233: FF96+ Lock\r\n\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\t// create & append iframe\r\n\t\tlet id = \"iframe-window-version\"\r\n\t\tlet el = document.createElement(\"iframe\")\r\n\t\tel.setAttribute(\"id\", id)\r\n\t\tel.setAttribute('style', 'display: none')\r\n\t\tdocument.body.appendChild(el)\r\n\t\t// get props\r\n\t\tlet iframe = document.getElementById(id)\r\n\t\tlet contentWindow = iframe.contentWindow\r\n\t\tlet props = Object.getOwnPropertyNames(contentWindow)\r\n\t\t// remove iframe\r\n\t\tiframe.parentNode.removeChild(iframe)\r\n\t\tprops.sort()\r\n\t\t// expected\r\n\t\tlet arrayE = props.filter(x => listE.includes(x))\r\n\t\tlet resE = \"\", boolE = false\r\n\t\tif (arrayE.length) {\r\n\t\t\tsDetail[\"expected_\"+ title] = arrayE\r\n\t\t\tresE = mini(arrayE) + buildButton(3, \"expected_\"+ title, arrayE.length)\r\n\t\t\tif (arrayE.length > 4) {boolE = true}\r\n\t\t} else {\r\n\t\t\tresE = \"none\"\r\n\t\t}\r\n\t\t// unexpected\r\n\t\tlet arrayU = props.filter(x => listU.includes(x))\r\n\t\t// ignore BatteryManager FF72+\r\n\t\tif (isFF && isVer < 72) {arrayU = arrayU.filter(x => ![\"BatteryManager\"].includes(x))}\r\n\t\tlet resU = \"\", boolU = false\r\n\t\tif (arrayU.length) {\r\n\t\t\tsDetail[\"unexpected_\"+ title] = arrayU\r\n\t\t\tresU = mini(arrayU) + buildButton(3, \"unexpected_\"+ title, arrayU.length)\r\n\t\t} else {\r\n\t\t\tresU = \"none\"\r\n\t\t\tboolU = true\r\n\t\t}\r\n\t\t// record\r\n\t\tfnRecord(num1, title, resE, boolE, \"skip\", t0)\r\n\t\tfnRecord(num2, title, resU, boolU, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num1, title, e.name, zErr, e.message)\r\n\t\tfnRecord(num2, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_installtrigger(num) {\r\n\t// FF100+: 1754441 behind prefs slated for deprecation\r\n\tlet strA = \"[typeof] InstallTrigger\", boolA = false\r\n\ttry {\r\n\t\tlet t0A = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resA = typeof InstallTrigger\r\n\t\tif (resA == \"object\") {boolA = true}\r\n\t\tif (isVer > 99 && !boolA) {\r\n\t\t\tfnRecord(num, strA, zNA +\": FF99 or lower\" + s3 +\" [\"+ resA +\"]\"+ sc, zNA, \"skip\", t0A)\r\n\t\t} else {\r\n\t\t\tif (!isFF) {resA += \" [FF99 or lower]\"; boolA = zNA}\r\n\t\t\tfnRecord(num, strA, resA, boolA, \"skip\", t0A)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(num, strA, e.name, zErr, e.message)\r\n\t}\r\n\r\n\tlet strB = \"[window] InstallTrigger\", boolB = false\r\n\ttry {\r\n\t\tlet t0B = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resB = \"InstallTrigger\" in window\r\n\t\tif (isVer > 99 && !resB) {\r\n\t\t\tfnRecord(num+1, strB, zNA +\": FF99 or lower\" + s3 +\" [\"+ resB +\"]\"+ sc, zNA, \"skip\", t0B)\r\n\t\t} else {\r\n\t\t\tif (!isFF) {resB += \" [FF99 or lower]\" }\r\n\t\t\tfnRecord(num+1, strB, resB, resB, \"skip\", t0B)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(num+1, strB, e.name, zErr, e.message)\r\n\t}\r\n\r\n\tlet strC = \"[typeof] InstallTriggerImpl\", boolC = false\r\n\t// FF60 or lower\r\n\tif (isFF && isVer < 61) {\r\n\t\tfnRecord(num+2, strC, zNA +\": FF61+ required\", zNA)\r\n\t} else {\r\n\t\ttry {\r\n\t\t\tlet t0C = performance.now()\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tlet resC = typeof InstallTriggerImpl\r\n\t\t\tif (resC == \"function\") {boolC = true}\r\n\t\t\tif (isVer > 99 && !boolC) {\r\n\t\t\t\tfnRecord(num+2, strC, zNA +\": FF99 or lower\" + s3 +\" [\"+ resC +\"]\"+ sc, zNA, \"skip\", t0C)\r\n\t\t\t} else {\r\n\t\t\t\tif (!isFF) {resC += \" [FF99 or lower]\"; boolC = zNA }\r\n\t\t\t\tfnRecord(num+2, strC, resC, boolC, \"skip\", t0C)\r\n\t\t\t}\r\n\t\t} catch(e) {\r\n\t\t\tfnRecord(num+2, strC, e.name, zErr, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_intl_canonical_locale(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet list = ['bh','hye','no','tl','tw'], bool = false\r\n\t\tlet res = []\r\n\t\tlist.forEach(function(i) {\r\n\t\t\tres.push(Intl.getCanonicalLocales(i))\r\n\t\t})\r\n\t\tlet hash = res.length > 0 ? mini(res) : \"none\"\r\n\t\tif (hash == \"df6e312d\") {bool = true // FF91+\r\n\t\t} else if (hash == \"6dc73a60\") {bool = true // FF70-90\r\n\t\t} else if (hash == \"0e0d831d\") {bool = true // FF52-69\r\n\t\t}\r\n\t\t// FF60-69 : bh=bh,  hye=hye, no=no, tl=tl,  tw=tw\r\n\t\t// FF70-90 : bh=bho, hye=hy,  no=nb, tl=fil, tw=ak\r\n\t\t// FF91+   : bh=bho, hye=hy,  no=no, tl=fil, tw=ak // webkit now matches\r\n\t\t// blink   : bh=bh,  hye=hy,  no=no, tl=fil, tw=tw\r\n\t\t// webkit  : bh=bh,  hye=hy,  no=no, tl=tl,  tw=tw\r\n\t\tif (res.length > 0) {\r\n\t\t\tif (res.join(\", \").length < 48) {\r\n\t\t\t\thash = res.join(\", \")\r\n\t\t\t} else {\r\n\t\t\t\tsDetail[\"specific_\"+ title] = res\r\n\t\t\t\thash += buildButton(3, \"specific_\"+ title, \"details\")\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_intl_supportedlocales(num) {\r\n\t// DTF, NF, RTF, LF, DN\r\n\t/* NOTES\r\n\t// FF52+ common items (130) not in blink103 common (59) = 71 items\r\n\t\t// FF52: DTF + NF / FF65: RTF / FF78: LF / FF86: DN\r\n\t\t// remove if present: bh bho, hye hy, no nb, tl fil, tw ak [aliases/maps]\r\n\tgeckoNotBlink = [\r\n\t\t// mix it up per test\r\n\t\t'eo','gl','ka','mt','om', // used in C\r\n\t\t\t// from collator list below that is in common with this list\r\n\t\t\t// ^ note mt only added if .compare works\r\n\r\n\t\t'ki','lu','qu','rn','rw', // used in PR (limited choices)\r\n\t\t'as','fo','ln','ne','to', // used in DTF\r\n\t\t'br','eu','ii','ks','ps', // used in DN\r\n\t\t'ee','gd','kk','lo','yi', // used in LF\r\n\t\t'bo','ha','lb','my','sq', // used in NF\r\n\t\t'cy','km','mg','os','sn', // used in RTF\r\n\t\t// 35 spare\r\n\t\t'ast','be','bm','ce','ckb','dsb','dz','ff','fur','fy',\r\n\t\t'ga','gv','haw','hsb','ig','is','kab','kl','kok','kw',\r\n\t\t'ky','lg','mk','mn','nd','nn','or','rm','se','sg','si',\r\n\t\t'so','ti','ug','yo','zu'\r\n\t]\r\n\t// webkit items not in FF105\r\n\t\t// note: older webkit only: make sure one in each: 'co','cv','oc','nso','tig'\r\n\tWKnotGecko = [\r\n\t\t'ba','co','dv','gn','io','iu','nr','nso','nv','ny','oc','ss','st','tig','tn','ts','ve','wa'\r\n\t\t// 'cv' // cv is supported in FF109+\r\n\t]\r\n\t*/\r\n\t// COLLATOR\r\n\t/*\r\n\tgeckoNotBlink = [\r\n\t\t'as','az','be','bo','bs','cy','dsb','dz','ee','eo','fo','ga','gl',\r\n\t\t'ha','haw','hsb','ig','is','ka','kk','kl','km','kok','ky','lb',\r\n\t\t'ln','lo','mk','mn','mt','my','ne','nn','om','or','pa','ps','se',\r\n\t\t'si','sq','to','ug','uz','yi','yo','zu'\r\n\t]\r\n\twebkit = same as gecko 91+\r\n\told_list = [\r\n\t\t'az','cy','dsb','ee','eo','gl','ha','haw','hsb','ig',\r\n\t\t'ka','kk','ln','lo','mt','om','pa','se','sq','to',\r\n\t]\r\n\t*/\r\n\t// PR\r\n\t/*\r\n\t\t FF52+  geckoNotBlink = ['ki','kok','lu','qu','rn','rw']\r\n\t\tFF105-  blinkNotGecko = ['an','dv','io','iu','lij','nr','nso','ny','ss','st','tig','tn','ts','ve','vo','wa']\r\n\t\tFF105- webkitNotGecko = ['ba','co','cv','dv','gn','io','iu','nr','nso','nv','ny','oc','ss','st','tig','tn','ts','ve','wa']\r\n\t\ttherefore\r\n\t\tblinkOnly = ['an','lij','vo']\r\n\t\twebkitOnly = ['ba',co','cv',''gn','ny','oc']\r\n\t\tlet list = [\r\n\t\t\t'ki','lu','qu','rn','rw', // gecko: pick 5\r\n\t\t\t'ba','co','cv','gn','oc', // webkit only: pick 4\r\n\t\t\t'an','lij','vo', // chrome only: not needed\r\n\t\t]\r\n\t*/\r\n\tlet oList = {\r\n\t\t'Collator': {errVer: 50, expected: 'f7e01839', list: ['eo','gl','ka','om']},\r\n\t\t'DateTimeFormat': {errVer: 50, expected: '2d45bfd6', list: ['as','fo','ln','ne','to', 'co','nr','st','tig']},\r\n\t\t'DisplayNames': {errVer: 86, expected: '502f87c2', list: ['br','eu','ii','ks','ps','gn','iu','nv','tn']},\r\n\t\t'ListFormat': {errVer: 78, expected: 'a2d97917', list: ['ee','gd','kk','lo','yi','ba','io','oc','ve']},\r\n\t\t'NumberFormat': {errVer: 50, expected: '3709a703', list: ['bo','ha','lb','my','sq','dv','ny','oc','ts']},\r\n\t\t'PluralRules': {errVer: 58, expected: '73b49209', list: ['ki','lu','qu','rn','rw','ba','co','gn','oc']},\r\n\t\t'RelativeTimeFormat': {errVer: 65, expected: 'fc7fa0cc', list: ['cy','km','mg','os','sn','gn','nso','ss','wa']},\r\n\t}\r\n\tlet control = (['c','C']).sort(Intl.Collator('mt').compare)\r\n\tif (control.join('') == 'Cc') {oList['Collator']['list'].push('mt')}\r\n\tif (isVer > 133) {\r\n\t\toList.DateTimeFormat.expected = 'd240302d'\r\n\t\toList.DisplayNames.expected = 'ccf106a6'\r\n\t\toList.ListFormat.expected = '52f07243'\r\n\t\toList.NumberFormat.expected = '741aaeb3'\r\n\t\toList.PluralRules.expected = '2d5fb02b'\r\n\t\toList.RelativeTimeFormat.expected = '98ac597e'\r\n\t}\r\n\tif (isVer > 146) {\r\n\t\t// 2000225 (i think) - 'ba' (bashkir) now supported\r\n\t\toList.ListFormat.expected = '130cf142'\r\n\t\toList.PluralRules.expected = 'ed7c2f2a'\r\n\t}\r\n\r\n\tnum = (num - 5)\r\n\tfor (const type of Object.keys(oList)) {\r\n\t\tnum += 5\r\n\t\tlet t0 = performance.now(), res = [], title = \"Intl.\"+ type\r\n\t\tlet errVer = oList[type].errVer,\r\n\t\t\texpected = oList[type].expected,\r\n\t\t\tlist = oList[type].list.sort()\r\n\r\n\t\ttry {\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tif (type == \"Collator\") { res = Intl.Collator.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"DateTimeFormat\") { res = Intl.DateTimeFormat.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"DisplayNames\") { res = Intl.DisplayNames.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"ListFormat\") { res = Intl.ListFormat.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"NumberFormat\") { res = Intl.NumberFormat.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"PluralRules\") { res = Intl.PluralRules.supportedLocalesOf(list)\r\n\t\t\t} else if (type == \"RelativeTimeFormat\") { res = Intl.RelativeTimeFormat.supportedLocalesOf(list)\r\n\t\t\t}\r\n\t\t\tlet hash = res.length > 0 ? mini(res) : \"none\"\r\n\t\t\t//console.log(title, hash)\r\n\t\t\tlet bool = hash == expected\r\n\t\t\tif (res.length > 0) {\r\n\t\t\t\tif (res.join(\", \").length < 48) {\r\n\t\t\t\t\thash = res.join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tsDetail[\"specific_\"+ title] = res\r\n\t\t\t\t\thash += buildButton(3, \"specific_\"+ title, res.length)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t\t} catch(e) {\r\n\t\t\tif (isFF) {\r\n\t\t\t\tif (isVer >= errVer) {\r\n\t\t\t\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlet msg = e.message\r\n\t\t\t\t\tmsg = msg.replace(\"can't access property \\\"supportedLocalesOf\\\", \", \"\") // trim *error_fix\r\n\t\t\t\t\tif (e.name == \"TypeError\" && msg == \"Intl.\" + type +\" is undefined\") {\r\n\t\t\t\t\t\tfnRecord(num, title, zNA +\": FF\" + errVer +\"+ required\", zNA)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfnRecord(num, title, zNA +\": not supported\", zNA)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_intl_supportedvaluesof(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet aSupported = Intl.supportedValuesOf(\"timeZone\")\r\n\t\tlet aExpected = ['CET','EET','EST','HST','MET','MST','WET',] // b7a58624\r\n\t\tlet res = aSupported.filter(x => aExpected.includes(x))\r\n\r\n\t\tlet hash = res.length > 0 ? mini(res) : \"none\"\r\n\t\tlet bool = hash == \"b7a58624\"\r\n\r\n\t\tif (res.length > 0) {\r\n\t\t\tif (res.join(\", \").length < 48) {\r\n\t\t\t\thash = res.join(\", \")\r\n\t\t\t} else {\r\n\t\t\t\tsDetail[\"specific_\"+ title] = res\r\n\t\t\t\thash += buildButton(3, \"specific_\"+ title, res.length)\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (isVer > 134) {\r\n\t\t\tbool = zNA\r\n\t\t\thash = zNA +\": FF109 or lower \"+ s3 +\"[\"+ hash +\"]\"+ sc\r\n\t\t} else if (!isFF) {\r\n\t\t\tbool = zNA\r\n\t\t\thash += \" [FF109 or lower]\"\r\n\t\t}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t\t//fnRecord(num, title, hash, bool, \"skip\", t0)\r\n\r\n\t} catch(e) {\r\n\t\t// FF92- not supported\r\n\t\tif (isVer < 93) {\r\n\t\t\tfnRecord(num, title, zNA +\": not supported\", zNA)\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\nfunction get_js_client_hints(num, title) {\r\n\t// testing: https://web.dev/user-agent-client-hints/\r\n\t// https://user-agent-client-hints.glitch.me/javascript.html\r\n\t// TypeError can't access property \"X\", navigator.X is undefined\r\n\tfunction output() {\r\n\t\tif (res.length > 0) {\r\n\t\t\tres.sort()\r\n\t\t\tsDetail[\"unexpected_\"+ title] = res\r\n\t\t\tlet hash = mini(res) + buildButton(3, \"unexpected_\"+ title , res.length)\r\n\t\t\tfnRecord(num, title, hash, false, \"skip\", t0)\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, \"none\", true, \"skip\", t0)\r\n\t\t}\r\n\t}\r\n\tlet res = []\r\n\tlet t0 = performance.now()\r\n\r\n\ttry {\r\n\t\tlet resB = navigator.userAgentData.brands\r\n\t\tresB.forEach(function(object) {\r\n\t\t\tlet valueB = fnClean(object.version)\r\n\t\t\tres.push(\"brands: \"+ object.brand +\": \"+ valueB)\r\n\t\t})\r\n\t} catch(e) {}\r\n\ttry {\r\n\t\tres.push(\"mobile: \" + navigator.userAgentData.mobile)\r\n\t} catch(e) {}\r\n\ttry {\r\n\t\tres.push(\"platform: \" + navigator.userAgentData.platform)\r\n\t} catch(e) {}\r\n\ttry {\r\n\t\tnavigator\r\n\t\t\t.userAgentData.getHighEntropyValues(\r\n\t\t\t\t[\"architecture\",\"bitness\",\"brands\",\"mobile\",\"model\",\"platform\",\"platformVersion\",\"uaFullVersion\"]\r\n\t\t\t).then(ua => {\r\n\t\t\t\tconst names = Object.keys(ua)\r\n\t\t\t\tfor (const k of names) {\r\n\t\t\t\t\tlet valueU = fnClean(ua[k])\r\n\t\t\t\t\tres.push(\"high entropy: \"+ k +\": \"+ valueU)\r\n\t\t\t\t}\r\n\t\t\t\toutput()\r\n\t\t\t})\r\n\t} catch(e) {\r\n\t\toutput()\r\n\t}\r\n}\r\n\r\nfunction get_last_prototype_keys(num1, title) {\r\n\t// HTMLAnchorElement\r\n\tlet title1 = title +\" HTMLAnchorElement\"\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tObject.keys(HTMLAnchorElement.prototype)\r\n\t\tlet props1 = Object.getOwnPropertyNames(HTMLAnchorElement.prototype) // includes the constructor\r\n\t\tlet res1 = props1.slice(-3).join(\", \")\r\n\t\t// search, hash, constructor\r\n\t\tfnRecord(num1, title1, res1, res1 == \"search, hash, constructor\", \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num1, title1, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// HTMLLinkElement\r\n\tlet title2 = title +\" HTMLLinkElement\"\r\n\tlet num2 = num1 + 1\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tObject.keys(HTMLLinkElement.prototype)\r\n\t\tlet props2 = Object.getOwnPropertyNames(HTMLLinkElement.prototype) // includes the constructor\r\n\t\tlet res2 = props2.slice(-3).join(\", \")\r\n\t\tlet bool2 = res2 == \"as, sheet, constructor\"\r\n\t\t// 52-55: integrity, sheet, constructor\r\n\t\tif (isVer <= 56 && res2 == \"integrity, sheet, constructor\") {bool2 = true}\r\n\t\tfnRecord(num2, title2, res2, bool2, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num2, title2, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_locale_compare(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet list = [\r\n\t\t\t[\"cy\",\"n\",\"ng\"],\r\n\t\t\t[\"gl\",\"\\u00F1\",\"ng\"],\r\n\t\t\t[\"ha\",\"ts\",\"tt\"],\r\n\t\t\t[\"hsb\",\"\\u0107\",\"\\u0109\"],\r\n\t\t\t[\"ig\",\"c\",\"ch\"],\r\n\t\t\t[\"ka\",\"\\u0107\",\"\\u10D0\"],\r\n\t\t\t[\"mt\",\"c\",\"C\"], // MAC: Intl.Collator supportedlocale but does not collate it\r\n\t\t\t[\"om\",\"ch\",\"\\u00ED\"],\r\n\t\t\t[\"sq\",\"\\u00EB\",\"ez\"],\r\n\t\t\t[\"to\",\"\\u00ED\",\"\\u00EE\"],\r\n\t\t]\r\n\t\tlet res = []\r\n\t\tlist.forEach(function(item) {\r\n\t\t\tres.push(item[1].localeCompare(item[2], item[0]))\r\n\t\t})\r\n\t\tlet hash = res.length > 0 ? mini(res) : \"none\"\r\n\t\tlet bool = hash == \"2e086db5\"\r\n\t\tif (res.length > 0) {\r\n\t\t\thash = res.join(\", \")\r\n\t\t}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_math(num, title) {\r\n\t// polyfill\r\n\tfunction cbrt(x) {\r\n\t\ttry {\r\n\t\t\tlet y = Math.pow(Math.abs(x), 1 / 3)\r\n\t\t\treturn x < 0 ? -y : y\r\n\t\t} catch(e) {\r\n\t\t\treturn \"error\"\r\n\t\t}\r\n\t}\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet res = []\r\n\t\tfor(let i=0; i < 6; i++) {\r\n\t\t\ttry {\r\n\t\t\t\tlet fnResult = \"unknown\"\r\n\t\t\t\tif (i == 0) {fnResult = cbrt(Math.PI) // polyfill\r\n\t\t\t\t} else if (i == 1) {fnResult = Math.log10(7*Math.LOG10E)\r\n\t\t\t\t} else if (i == 2) {fnResult = Math.log10(2*Math.SQRT1_2)\r\n\t\t\t\t} else if (i == 3) {fnResult = Math.acos(0.123)\r\n\t\t\t\t} else if (i == 4) {fnResult = Math.acosh(Math.SQRT2)\r\n\t\t\t\t} else if (i == 5) {fnResult = Math.atan(2)\r\n\t\t\t\t}\r\n\t\t\t\tres.push(fnResult)\r\n\t\t\t} catch(e) {\r\n\t\t\t\tres.push(\"error\")\r\n\t\t\t}\r\n\t\t}\r\n\t\t// I have to use sha1 due to legacy data\r\n\t\tlet hash = sha1(res.join()).substring(0,20)\r\n\t\tlet engine = \"unkown\"\r\n\t\tif (hash == \"ede9ca53efbb1902cc21\") {engine = \"blink\"\r\n\t\t} else if (hash == \"05513f36d87dd78af60a\") {engine = \"webkit\"\r\n\t\t} else if (hash == \"38172d9426d77af71baa\") {engine = \"edgeHTML\"\r\n\t\t} else if (hash == \"36f067c652c8cfd90725\") {engine = \"trident\"\r\n\t\t} else if (hash == \"225f4a612fdca4065043\") {engine = \"gecko\"\r\n\t\t} else if (hash == \"cb89002a8d6fabf859f6\") {engine = \"gecko\"\r\n\t\t}\r\n\t\tlet bool = engine == \"gecko\" ? true : false\r\n\t\t// now redo as mini\r\n\t\tsDetail[\"expected_\"+ title] = res\r\n\t\thash = mini(res) + buildButton(3, \"expected_\"+ title, engine)\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_media_constraints(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet noiseFF = [\r\n\t\t\t'groupId', // FF70+\r\n\t\t\t'channelCount', // FF56+\r\n\t\t\t'autoGainControl','noiseSuppression', // FF55+\r\n\t\t\t'mozAutoGainControl','mozNoiseSuppression', // FF54-\r\n\t\t\t'resizeMode', // FF144+\r\n\t\t]\r\n\t\tlet data = navigator.mediaDevices.getSupportedConstraints()\r\n\t\tlet res = Object.keys(data)\r\n\t\tres = res.filter(x => !noiseFF.includes(x))\r\n\t\tres.sort()\r\n\t\tlet hash = res.length > 0 ? mini(res) : \"none\"\r\n\t\tlet bool = hash == \"27fb15ca\" // FF52+\r\n\t\tif (res.length > 0) {\r\n\t\t\tsDetail[\"expected_\"+ title] = res\r\n\t\t\thash += buildButton(3, \"expected_\"+ title, res.length)\r\n\t\t}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\t// e.g. TB disables mediaDevices\r\n\t\tlet error = mini(e.name +\": \"+ e.message), isNA = false\r\n\t\tif (error == \"f2244861\") {\r\n\t\t\t// TypeError: can't access property \"getSupportedConstraints\", navigator.mediaDevices is undefined\r\n\t\t\tisNA = true\r\n\t\t} else if (error == \"9fc627cf\") {\r\n\t\t\t// TypeError: navigator.mediaDevices is undefined\r\n\t\t\tisNA = true\r\n\t\t}\r\n\t\tif (isNA) {\r\n\t\t\tfnRecord(num, title, zNA +\": disabled\", zNA)\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_mimetypes(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tif (\"mimeTypes\" in navigator) {\r\n\t\t\tlet res = [], bool = false\r\n\t\t\tlet m = navigator.mimeTypes\r\n\t\t\tif (m.length == 0) {\r\n\t\t\t\tfnRecord(num, title, \"none\", true, \"skip\", t0)\r\n\t\t\t} else {\r\n\t\t\t\tfor (let i=0; i < m.length; i++) {\r\n\t\t\t\t\tres.push( m[i].type + (m[i].description == \"\" ? \": * \" : \": \"+ m[i].type)\r\n\t\t\t\t\t\t+ (m[i].suffixes == \"\" ? \": *\" : \": \"+ m[i].suffixes) )\r\n\t\t\t\t}\r\n\t\t\t\tres.sort()\r\n\t\t\t\t// FF84 or lower allow just Flash\r\n\t\t\t\tif (isFF && isVer < 85 && res.length == 2) {\r\n\t\t\t\t\tlet mime1 = res[0].split(\":\")[0]\r\n\t\t\t\t\tlet mime2 = res[1].split(\":\")[0]\r\n\t\t\t\t\tif (mime1 == \"application/x-futuresplash\" && mime2 == \"application/x-shockwave-flash\") {\r\n\t\t\t\t\t\tbool = true\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tlet hash = mini(res)\r\n\t\t\t\tif (isVer > 98 || !isFF) { // allow non-FF to have the same\r\n\t\t\t\t\t// FF99+: controlled by pdfjs.disabled\r\n\t\t\t\t\t// 1720353: hardcoded mimeTypes\r\n\t\t\t\t\tif (hash == \"d63af80f\") {bool = true}\r\n\t\t\t\t}\r\n\t\t\t\tsDetail[\"expected_\"+ title] = res\r\n\t\t\t\thash += buildButton(3, \"expected_\"+ title, res.length)\r\n\t\t\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, e.name, zErr)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_moz_colors(num, title) {\r\n\tlet aList = [\r\n\t\t// css4\r\n\t\t'-moz-activehyperlinktext','-moz-default-color','-moz-default-background-color','-moz-hyperlinktext','-moz-visitedhyperlinktext',\r\n\t\t// stand-ins\r\n\t\t'-moz-buttondefault','-moz-buttonhoverface','-moz-buttonhovertext','-moz-cellhighlight','-moz-cellhighlighttext','-moz-combobox','-moz-comboboxtext','-moz-dialog','-moz-dialogtext','-moz-dragtargetzone','-moz-eventreerow','-moz-field','-moz-fieldtext','-moz-html-cellhighlight','-moz-html-cellhighlighttext','-moz-mac-chrome-active','-moz-mac-chrome-inactive','-moz-mac-disabledtoolbartext','-moz-mac-focusring','-moz-mac-menuselect','-moz-mac-menushadow','-moz-mac-menutextdisable','-moz-mac-menutextselect','-moz-mac-secondaryhighlight','-moz-menubarhovertext','-moz-menubartext','-moz-menuhover','-moz-menuhovertext','-moz-nativehyperlinktext','-moz-oddtreerow','-moz-win-communicationstext','-moz-win-mediatext',\r\n\t\t// moz\r\n\t\t'-moz-accent-color','-moz-accent-color-foreground','-moz-appearance','-moz-colheaderhovertext','-moz-colheadertext','-moz-gtk-buttonactivetext','-moz-gtk-info-bar-text','-moz-mac-accentdarkestshadow','-moz-mac-accentdarkshadow','-moz-mac-accentface','-moz-mac-accentlightesthighlight','-moz-mac-accentlightshadow','-moz-mac-accentregularhighlight','-moz-mac-accentregularshadow','-moz-mac-active-menuitem','-moz-mac-active-source-list-selection','-moz-mac-buttonactivetext','-moz-mac-defaultbuttontext','-moz-mac-menuitem','-moz-mac-menupopup','-moz-mac-source-list','-moz-mac-source-list-selection','-moz-mac-tooltip','-moz-mac-vibrancy-dark','-moz-mac-vibrancy-light','-moz-mac-vibrant-titlebar-dark','-moz-mac-vibrant-titlebar-light','-moz-win-accentcolor','-moz-win-accentcolortext','-moz-win-communications-toolbox','-moz-win-media-toolbox',\r\n\t]\r\n\taList.sort()\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet aRes = []\r\n\t\tlet element = dom.mozColor\r\n\t\tlet strColor = \"rgba(1, 2, 3, 0.5)\"\r\n\t\taList.forEach(function(style) {\r\n\t\t\telement.style.backgroundColor = strColor // always reset\r\n\t\t\telement.style.backgroundColor = style\r\n\t\t\tlet rgb = window.getComputedStyle(element, null).getPropertyValue(\"background-color\")\r\n\t\t\tif (rgb !== strColor) {\r\n\t\t\t\taRes.push(style +\":\"+ rgb) // only record those affected\r\n\t\t\t}\r\n\t\t})\r\n\t\tlet bool = aRes.length > 0\r\n\t\tif (bool) {\r\n\t\t\tsDetail[\"expected_\"+ title] = aRes\r\n\t\t}\r\n\t\tlet hash = bool ? mini(aRes) + buildButton(3, \"expected_\"+ title, aRes.length) : \"none\"\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_moz_fonts(num, title) {\r\n\tlet aResults = [],\r\n\t\tm = \"-moz-\",\r\n\t\taFonts = [m+\"window\",m+\"desktop\",m+\"document\",m+\"workspace\",m+\"info\",m+\"pull-down-menu\",m+\"dialog\",m+\"button\",m+\"list\",m+\"field\"]\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tlet el = dom.mozFont\r\n\t\tif (bBlock) {bB = 1}\r\n\t\taFonts.forEach(function(font){\r\n\t\t\t// catch blocked\r\n\t\t\tlet test = getComputedStyle(el).getPropertyValue(\"font-family\")\r\n\t\t\tel.style.font = \"99px sans-serif\"\r\n\t\t\ttry {el.style.font = font} catch(err) {}\r\n\t\t\tlet s = \"\"\r\n\t\t\tif (window.getComputedStyle) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\ts = getComputedStyle(el, null)\r\n\t\t\t\t} catch(e) {}\r\n\t\t\t}\r\n\t\t\tif (s !== \"\") {\r\n\t\t\t\tlet f = s.fontSize != \"99px\" ? s.fontFamily : undefined\r\n\t\t\t\tif (f !== undefined) {aResults.push(f)}\r\n\t\t\t}\r\n\t\t})\r\n\t\tlet bool = aResults.length > 0\r\n\t\tif (bool) {\r\n\t\t\tsDetail[\"expected_\"+ title] = aResults\r\n\t\t}\r\n\t\tlet hash = bool ? mini(aResults) + buildButton(3, \"expected_\"+ title, aResults.length) : \"none\"\r\n\t\tif (isVer > 108) {\r\n\t\t\tbool = zNA; hash = zNA +\": FF108 or lower\" // 1802957\r\n\t\t}\r\n\t\tif (!isFF) {hash += \" [FF108 or lower]\"; bool = zNA}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_moz_objects(num, name) {\r\n\tlet title = \"[\"+ name +\"] moz\"\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet res = []\r\n\t\tlet oList = {}\r\n\t\tlet isObj = false\r\n\t\tlet useHas = false // use hasOwnProperty or default getOwnPropertyDescriptor\r\n\r\n\t\tif (name === \"Document\" && \"function\" === typeof Document) {\r\n\t\t\toList[name] = [Document.prototype,\r\n\t\t\t\t[\"mozSetImageElement\",\"mozCancelFullScreen\",\"mozFullScreen\",\"mozFullScreenEnabled\",\r\n\t\t\t\t\t\"mozFullScreenElement\",\"onmozfullscreenchange\",\"onmozfullscreenerror\"]\r\n\t\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"HTMLElement\" && \"function\" === typeof HTMLElement) {\r\n\t\t\toList[name] = [HTMLElement.prototype, [\"onmozfullscreenchange\",\"onmozfullscreenerror\"]]\r\n\t\t}\r\n\t\tif (name === \"HTMLMediaElement\" && \"function\" === typeof HTMLMediaElement) {\r\n\t\t\toList[name] = [HTMLMediaElement.prototype, [\"mozCaptureStream\",\"mozCaptureStreamUntilEnded\",\r\n\t\t\t\t\"mozGetMetadata\",\"mozPreservesPitch\",\"mozAudioCaptured\",\"mozFragmentEnd\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"HTMLVideoElement\" && \"function\" === typeof HTMLVideoElement) {\r\n\t\t\toList[name] = [HTMLVideoElement.prototype, [\"mozParsedFrames\",\"mozDecodedFrames\",\r\n\t\t\t\t\"mozPresentedFrames\",\"mozPaintedFrames\",\"mozFrameDelay\",\"mozHasAudio\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"MouseEvent\" && \"function\" === typeof MouseEvent) {\r\n\t\t\toList[name] = [MouseEvent.prototype,\r\n\t\t\t\t[\"MOZ_SOURCE_UNKNOWN\",\"MOZ_SOURCE_MOUSE\",\"MOZ_SOURCE_PEN\",\"MOZ_SOURCE_ERASER\",\r\n\t\t\t\t\"MOZ_SOURCE_CURSOR\",\"MOZ_SOURCE_TOUCH\",\"MOZ_SOURCE_KEYBOARD\",\"mozPressure\",\r\n\t\t\t\t\"mozInputSource\",\"MOZ_SOURCE_UNKNOWN\",\"MOZ_SOURCE_MOUSE\",\"MOZ_SOURCE_PEN\",\r\n\t\t\t\t\"MOZ_SOURCE_ERASER\",\"MOZ_SOURCE_CURSOR\",\"MOZ_SOURCE_TOUCH\",\"MOZ_SOURCE_KEYBOARD\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"Screen\" && \"function\" === typeof Screen) {\r\n\t\t\toList[name] = [Screen.prototype,\r\n\t\t\t\t[\"mozLockOrientation\",\"mozUnlockOrientation\",\"mozOrientation\",\"onmozorientationchange\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"SVGElement\" && \"function\" === typeof SVGElement) {\r\n\t\t\toList[name] = [SVGElement.prototype,[\"onmozfullscreenchange\",\"onmozfullscreenerror\"]]\r\n\t\t}\r\n\t\tif (name === \"HTMLInputElement\" && \"function\" === typeof HTMLInputElement) {\r\n\t\t\toList[name] = [HTMLInputElement.prototype,[\"mozIsTextField\"]]\r\n\t\t}\r\n\t\tif (name === \"Navigator\" && \"function\" === typeof Navigator) {\r\n\t\t\toList[name] = [Navigator.prototype,[\"mozGetUserMedia\"]]\r\n\t\t}\r\n\t\tif (name === \"HTMLCanvasElement\" && \"function\" === typeof HTMLCanvasElement) {\r\n\t\t\toList[name] = [HTMLCanvasElement.prototype,[\"mozOpaque\",\"mozPrintCallback\"]]\r\n\t\t}\r\n\t\tif (name === \"CanvasRenderingContext2D\" && \"function\" === typeof CanvasRenderingContext2D) {\r\n\t\t\toList[name] = [CanvasRenderingContext2D.prototype,\r\n\t\t\t\t[\"mozCurrentTransform\",\"mozCurrentTransformInverse\",\"mozTextStyle\",\"mozImageSmoothingEnabled\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"MathMLElement\" && \"function\" === typeof MathMLElement) {\r\n\t\t\toList[name] = [MathMLElement.prototype,[\"onmozfullscreenchange\",\"onmozfullscreenerror\"]]\r\n\t\t}\r\n\t\tif (name === \"DataTransfer\" && \"function\" === typeof DataTransfer) {\r\n\t\t\toList[name] = [DataTransfer.prototype,[\"mozCursor\",\"mozUserCancelled\",\"mozSourceNode\"]]\r\n\t\t}\r\n\t\tif (name === \"ShadowRoot\" && \"function\" === typeof ShadowRoot) {\r\n\t\t\toList[name] = [ShadowRoot.prototype,[\"mozFullScreenElement\"]]\r\n\t\t}\r\n\t\tif (name === \"XMLHttpRequest\" && \"function\" === typeof XMLHttpRequest) {\r\n\t\t\toList[name] = [XMLHttpRequest.prototype,[\"mozAnon\",\"mozSystem\"]]\r\n\t\t}\r\n\t\tif (name === \"IDBObjectStore\" && \"function\" === typeof IDBObjectStore) {\r\n\t\t\toList[name] = [IDBObjectStore.prototype,[\"mozGetAll\"]]\r\n\t\t}\r\n\t\tif (name === \"IDBIndex\" && \"function\" === typeof IDBIndex) {\r\n\t\t\toList[name] = [IDBIndex.prototype,[\"mozGetAll\",\"mozGetAllKeys\"]]\r\n\t\t}\r\n\t\tif (name === \"OfflineResourceList\" && \"function\" === typeof OfflineResourceList) {\r\n\t\t\toList[name] = [OfflineResourceList.prototype,\r\n\t\t\t\t[\"mozHasItem\",\"mozItem\",\"mozAdd\",\"mozRemove\",\"mozItems\",\"mozLength\"]\r\n\t\t\t]\r\n\t\t}\r\n\t\tif (name === \"Element\" && \"function\" === typeof Element) {\r\n\t\t\tuseHas = true\r\n\t\t\toList[name] = [Element.prototype,[\"mozMatchesSelector\",\"​mozRequestFullScreen\"]]\r\n\t\t}\r\n\t\tif (name === \"CSS2|CSSStyle\") {\r\n\t\t\tlet source\r\n\t\t\tif (\"function\" === typeof CSS2Properties) {\r\n\t\t\t\tsource = CSS2Properties\r\n\t\t\t\ttitle = \"[CSS2Properties] moz\"\r\n\t\t\t} else if (\"function\" === typeof CSSStyleProperties) {\r\n\t\t\t\tsource = CSSStyleProperties\r\n\t\t\t\ttitle = \"[CSSStyleProperties] moz\"\r\n\t\t\t}\r\n\t\t\tif (undefined !== source) {\r\n\t\t\t\toList[name] = [source.prototype,\r\n\t\t\t\t\t['MozAnimation','MozAnimationDelay','MozAnimationDirection','MozAnimationDuration','MozAnimationFillMode',\r\n\t\t\t\t\t'MozAnimationIterationCount','MozAnimationName','MozAnimationPlayState','MozAnimationTimingFunction',\r\n\t\t\t\t\t'MozAppearance','MozBackfaceVisibility','MozBorderEnd','MozBorderEndColor','MozBorderEndStyle',\r\n\t\t\t\t\t'MozBorderEndWidth','MozBorderImage','MozBorderStart','MozBorderStartColor','MozBorderStartStyle',\r\n\t\t\t\t\t'MozBorderStartWidth','MozBoxAlign','MozBoxDirection','MozBoxFlex','MozBoxOrdinalGroup','MozBoxOrient',\r\n\t\t\t\t\t'MozBoxPack','MozBoxSizing','MozFloatEdge','MozFontFeatureSettings','MozFontLanguageOverride',\r\n\t\t\t\t\t'MozForceBrokenImageIcon','MozHyphens','MozImageRegion','MozMarginEnd','MozMarginStart','MozOrient',\r\n\t\t\t\t\t'MozPaddingEnd','MozPaddingStart','MozPerspective','MozPerspectiveOrigin','MozTabSize','MozTextSizeAdjust',\r\n\t\t\t\t\t'MozTransform','MozTransformOrigin','MozTransformStyle','MozTransition','MozTransitionDelay',\r\n\t\t\t\t\t'MozTransitionDuration','MozTransitionProperty','MozTransitionTimingFunction','MozUserFocus','MozUserInput',\r\n\t\t\t\t\t'MozUserModify','MozUserSelect','MozWindowDragging',\r\n\t\t\t\t\t// aliases\r\n\t\t\t\t\t'-moz-animation','-moz-animation-delay','-moz-animation-direction','-moz-animation-duration',\r\n\t\t\t\t\t'-moz-animation-fill-mode','-moz-animation-iteration-count','-moz-animation-name','-moz-animation-play-state',\r\n\t\t\t\t\t'-moz-animation-timing-function','-moz-appearance','-moz-backface-visibility','-moz-border-end',\r\n\t\t\t\t\t'-moz-border-end-color','-moz-border-end-style','-moz-border-end-width','-moz-border-image','-moz-border-start',\r\n\t\t\t\t\t'-moz-border-start-color','-moz-border-start-style','-moz-border-start-width','-moz-box-align',\r\n\t\t\t\t\t'-moz-box-direction','-moz-box-flex','-moz-box-ordinal-group','-moz-box-orient','-moz-box-pack',\r\n\t\t\t\t\t'-moz-box-sizing','-moz-float-edge','-moz-font-feature-settings','-moz-font-language-override',\r\n\t\t\t\t\t'-moz-force-broken-image-icon','-moz-hyphens','-moz-image-region','-moz-margin-end','-moz-margin-start',\r\n\t\t\t\t\t'-moz-orient','-moz-padding-end','-moz-padding-start','-moz-perspective','-moz-perspective-origin',\r\n\t\t\t\t\t'-moz-tab-size','-moz-text-size-adjust','-moz-transform','-moz-transform-origin','-moz-transform-style',\r\n\t\t\t\t\t'-moz-transition','-moz-transition-delay','-moz-transition-duration','-moz-transition-property',\r\n\t\t\t\t\t'-moz-transition-timing-function','-moz-user-focus','-moz-user-input','-moz-user-modify','-moz-user-select',\r\n\t\t\t\t\t'-moz-window-dragging',\r\n\t\t\t\t\t]\r\n\t\t\t\t]\r\n\t\t\t}\r\n\t\t}\r\n\t\t// object: don't use, we can do this elsewhere\r\n\t\tif (name === \"window\" && \"object\" == typeof window) {\r\n\t\t\tisObj = true\r\n\t\t\toList[name] = [window,[\"mozRTCPeerConnection\",\"CSSMozDocumentRule\"]]\r\n\t\t}\r\n\r\n\t\t// do it!\r\n\t\tif (oList[name] !== undefined) {\r\n\t\t\tlet obj = oList[name][0]\r\n\t\t\tlet props = oList[name][1]\r\n\t\t\tif (isObj) {\r\n\t\t\t\tprops.forEach(function(element) {\r\n\t\t\t\t\tif (element in obj) {res.push(element)}\r\n\t\t\t\t})\r\n\t\t\t} else if (useHas) {\r\n\t\t\t\tprops.forEach(function(element) {\r\n\t\t\t\t\tif (obj.hasOwnProperty(element)) {res.push(element)}\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\tprops.forEach(function(element) {\r\n\t\t\t\t\tif (\"object\" === typeof Object.getOwnPropertyDescriptor(obj, element)) {\r\n\t\t\t\t\t\tres.push(element)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\tres.sort()\r\n\t\t}\r\n\t\tlet hash = \"none\", bool = false\r\n\t\tif (res.length > 0) {\r\n\t\t\tif (res.join(\", \").length < 48) {\r\n\t\t\t\thash = res.join(\", \")\r\n\t\t\t} else {\r\n\t\t\t\tsDetail[\"expected_\"+ title] = res\r\n\t\t\t\thash = mini(res) + buildButton(3, \"expected_\"+ title, res.length)\r\n\t\t\t}\r\n\t\t\tbool = true\r\n\t\t} else if (isFF) {\r\n\t\t\t// FF none exceptions\r\n\t\t\tif (isVer < 63 && name === \"ShadowRoot\") {bool = zNA, hash = zNA +\": FF63+ required\"}\r\n\t\t\tif (isVer < 71 && name === \"MathMLElement\") {bool = zNA, hash = zNA +\": FF71+ required\"}\r\n\t\t\tif (isVer > 141 && name === 'HTMLInputElement') {bool = zNA, hash = zNA +\": FF141 or lower\"}\r\n\t\t\tif (name === \"OfflineResourceList\") {bool = zNA, hash = zNA +\": assuming Beta/Dev/Nightly pref\"}\r\n\t\t\tif (name === \"Navigator\") {\r\n\t\t\t\t// TB/PM build without WebRTC: mozGetUserMedia\r\n\t\t\t\t\t// and because of that also disable gUM (or code on sites suffer)\r\n\t\t\t\tif (undefined == window.RTCPeerConnection) {\r\n\t\t\t\t\tbool = zNA\r\n\t\t\t\t\thash = zNA +\": --disable-webrtc\"+ s3 +\" [\"+ hash +\"]\"+ sc\r\n\t\t\t\t} else if (isFF) {\r\n\t\t\t\t\t// behind prefs: both media.peerconnection.enabled + media.navigator.enabled\r\n\t\t\t\t\tbool = zNA\r\n\t\t\t\t\thash = zNA + s3 +\" [\"+ hash +\"]\"+ sc + sb +\" [not firefox default]\"+ sc\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (isVer > 112 && name === \"CanvasRenderingContext2D\") { // 1822955\r\n\t\t\t\tbool = zNA\r\n\t\t\t\thash = zNA +\": FF112 or lower\"\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// non-FF\r\n\t\t\tif (name === \"CanvasRenderingContext2D\") {\r\n\t\t\t\tbool = zNA\r\n\t\t\t\thash += \" [FF112 or lower]\"\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_nav_keys(num1, num2, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet aNavKeys = Object.keys(Object.getOwnPropertyDescriptors(Navigator.prototype))\r\n\t\t// expected FF only\r\n\t\tlet aExpected = [\"buildID\",\"oscpu\",\"taintEnabled\"]\r\n\t\tlet resE = aNavKeys.filter(x => aExpected.includes(x))\r\n\t\tlet boolE = resE.length == 3 ? true : false\r\n\t\tresE = resE.length > 0 ? resE.join(\", \") : \"none\"\r\n\t\tfnRecord(num1, title, resE, boolE, \"skip\", t0)\r\n\r\n\t\t// not expected: blink items\r\n\t\tlet aNot = [\"canShare\",\"clearAppBadge\",\"deviceMemory\",\"getBattery\",\"getInstalledRelatedApps\",\r\n\t\t\t\"getUserMedia\",\"globalPrivacyControl\",\"hid\",\"keyboard\",\"locks\",\"managed\",\"presentation\",\"scheduling\",\r\n\t\t\t\"serial\",\"setAppBadge\",\"unregisterProtocolHandler\",\"usb\",\"userActivation\",\"userAgentData\",\"wakeLock\",\r\n\t\t\t\"webkitGetUserMedia\",\"webkitPersistentStorage\",\"webkitTemporaryStorage\",\"xr\",\"SharedWorker\",\"Worker\"]\r\n\t\t// FYI: don't use requestMIDIAccess: FF60+ behind dom.webmidi.enabled... 1752906: FF99+ default enabled\r\n\r\n\t\tif (isVer > 121) { aNot = aNot.filter(x => ![\"wakeLock\"].includes(x)) } // FF122+ dom.screenwakelock.enabled 1589554\r\n\t\tif (isVer > 119) { aNot = aNot.filter(x => ![\"userActivation\"].includes(x)) } // 1791079: FF120+\r\n\t\tif (isVer > 95) {aNot = aNot.filter(x => ![\"canShare\"].includes(x)) } // 1666203: FF96+ canShare\r\n\t\tif (isVer > 94 || isEngine == \"goanna\") { aNot = aNot.filter(x => ![\"globalPrivacyControl\"].includes(x))} // 1670058: FF95+ globalPrivacyControl | PM 30\r\n\t\tif (isVer > 92) { aNot = aNot.filter(x => ![\"locks\"].includes(x)) } // FF93+ dom.weblocks.enabled: 1739233: FF96+ locks\r\n\r\n\t\tlet resN = aNavKeys.filter(x => aNot.includes(x))\r\n\t\tlet boolN = resN.length == 0\r\n\t\tif (resN.length > 0) {\r\n\t\t\tsDetail[\"unexpected_\"+ title] = resN\r\n\t\t\tresN = mini(resN) + buildButton(3, \"unexpected_\"+ title, resN.length)\r\n\t\t} else {\r\n\t\t\tresN = \"none\"\r\n\t\t}\r\n\t\tfnRecord(num2, title, resN, boolN, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num1, title, e.name, zErr, e.message)\r\n\t\tfnRecord(num2, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_nav_screen(num) {\r\n\tlet strA = \"[left] screen\", boolA = false\r\n\ttry {\r\n\t\tlet t0A = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resA = screen.left\r\n\t\tif (!isNaN(resA)) {boolA = true} else {resA = fnClean(resA)}\r\n\t\tfnRecord(num, strA, resA, boolA, \"skip\", t0A)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, strA, e.name, zErr, e.message)\r\n\t}\r\n\tlet strB = \"[top] screen\", boolB = false\r\n\ttry {\r\n\t\tlet t0B = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resB = screen.top\r\n\t\tif (!isNaN(resB)) {boolB = true} else {resB = fnClean(resB)}\r\n\t\tfnRecord(num+1, strB, resB, boolB, \"skip\", t0B)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+1, strB, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_nav_values(num1, num2, num3) {\r\n\t// expected\r\n\tlet strA = \"[oscpu] navigator\", boolA = false\r\n\ttry {\r\n\t\tlet t0A = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resA = navigator.oscpu\r\n\t\tif (typeof resA !== \"string\") {\r\n\t\t\t//console.debug(typeof resA)\r\n\t\t} else {\r\n\t\t\tif (resA == \"\") {resA = \"empty string\"\r\n\t\t\t} else if (resA == undefined) {resA = zU\r\n\t\t\t} else if (resA == zU) {resA = zUQ\r\n\t\t\t} else {boolA = true}\r\n\t\t}\r\n\t\tfnRecord(num1, strA, resA, boolA, \"skip\", t0A)\r\n\t} catch(e) {\r\n\t\tfnRecord(num1, strA, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// not expected\r\n\t// bluetooth\r\n\tlet strBT = \"[bluetooth] navigator\", boolBT = false, numBT = num2\r\n\ttry {\r\n\t\tlet t0BT = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resBT = navigator.bluetooth\r\n\t\tif (resBT === undefined) {\r\n\t\t\tfnRecord(numBT, strBT, zU, true, \"skip\", t0BT)\r\n\t\t} else {\r\n\t\t\ttry {\r\n\t\t\t\tnavigator.bluetooth.getAvailability().then(available => {\r\n\t\t\t\t\tif (available) {\r\n\t\t\t\t\t\tfnRecord(numBT, strBT, \"supported\", boolBT)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tfnRecord(numBT, strBT, \"not supported\", boolBT)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t} catch(e) {\r\n\t\t\t\tfnRecord(numBT, strBT, e.name, zErr)\r\n\t\t\t}\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(numBT, strBT, e.name, zErr, e.message)\r\n\t}\r\n\t// deviceMemory\r\n\tlet strDM = \"[deviceMemory] navigator\", boolDM = false, numDM = num2+2\r\n\ttry {\r\n\t\tlet t0DM = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resDM = navigator.deviceMemory\r\n\t\tif (resDM == undefined) {boolDM = true}\r\n\t\tresDM = fnClean(resDM)\r\n\t\tfnRecord(numDM, strDM, resDM, boolDM, \"skip\", t0DM)\r\n\t} catch(e) {\r\n\t\tfnRecord(numDM, strDM, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// globalprivacycontrol\r\n\tlet strG = \"[gpc] navigator\", boolG = false, numG = num2+6\r\n\ttry {\r\n\t\tlet t0G = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resG = navigator.globalPrivacyControl\r\n\t\tresG = fnClean(resG)\r\n\t\tif (resG == \"undefined\") {boolG = true\r\n\t\t} else if (isEngine == \"goanna\" && resG == \"empty string\") {boolG = true; resG += s3 + \" [goanna]\"+ sc}\r\n\r\n\t\t// 1670058: FF95+ globalPrivacyControl\r\n\t\tif (isVer > 94) {\r\n\t\t\tresG = zNA +\": FF94 or lower\" + s3 +\" [\"+ resG +\"]\" + sc\r\n\t\t\tboolG = zNA\r\n\t\t} else if (!isFF) {\r\n\t\t\tresG += \" [FF94 or lower]\"\r\n\t\t\tboolG = zNA\r\n\t\t}\r\n\t\tfnRecord(numG, strG, resG, boolG, \"skip\", t0G)\r\n\t} catch(e) {\r\n\t\tfnRecord(numG, strG, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// keyboard\r\n\tlet strK = \"[keyboard] navigator\", numK = num2+8\r\n\tlet t0K = performance.now()\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resK = navigator.keyboard\r\n\t\tif (resK == undefined) {\r\n\t\t\tfnRecord(numK, strK, \"undefined\", true, \"skip\", t0K)\r\n\t\t} else {\r\n\t\t\tlet keys = []\r\n\t\t\t// https://wicg.github.io/keyboard-map/\r\n\t\t\t// https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system\r\n\t\t\tlet listK = ['Backquote','Backslash','Backspace','BracketLeft','BracketRight','Comma',\r\n\t\t\t\t'Digit0','Digit1','Digit2','Digit3','Digit4','Digit5','Digit6','Digit7','Digit8','Digit9',\r\n\t\t\t\t'Equal','IntlBackslash','IntlRo','IntlYen','KeyA','KeyB','KeyC','KeyD','KeyE','KeyF','KeyG',\r\n\t\t\t\t'KeyH','KeyI','KeyJ','KeyK','KeyL','KeyM','KeyN','KeyO','KeyP','KeyQ','KeyR','KeyS','KeyT',\r\n\t\t\t\t'KeyU','KeyV','KeyW','KeyX','KeyY','KeyZ','Minus','Period','Quote','Semicolon','Slash']\r\n\t\t\tresK.getLayoutMap().then(keyboardLayoutMap => {\r\n\t\t\t\tlistK.forEach(function(key) {\r\n\t\t\t\t\ttry {keys.push(key +\": \"+ keyboardLayoutMap.get(key))} catch(e) {keys.push(key +\": e.name\")}\r\n\t\t\t\t})\r\n\t\t\t\tsDetail[\"unexpected_\"+ strK] = keys\r\n\t\t\t\tlet hash = mini(keys) + buildButton(3, \"unexpected_\"+ strK , keys.length)\r\n\t\t\t\tfnRecord(numK, strK, hash, false, \"skip\", t0K)\r\n\t\t\t})\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(numK, strK, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// vendor\r\n\tlet strV = \"[vendor] navigator\", boolV = false, numV = num2+10\r\n\ttry {\r\n\t\tlet t0V = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resV = navigator.vendor\r\n\t\tif (resV == \"\") {boolV = true}\r\n\t\tresV = fnClean(resV)\r\n\t\tfnRecord(numV, strV, resV, boolV, \"skip\", t0V)\r\n\t} catch(e) {\r\n\t\tfnRecord(numV, strV, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// specific\r\n\tlet strX = \"[buildID] navigator\", boolX = false\r\n\ttry {\r\n\t\tlet t0X = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resX = navigator.buildID\r\n\t\tif (isVer > 63) {\r\n\t\t\t// all FF64+\r\n\t\t\tif (resX == \"20181001000000\") {boolX = true}\r\n\t\t} else {\r\n\t\t\tresX = fnClean(resX)\r\n\t\t\t// FF52-63: 14 digit number starting with 2017\r\n\t\t\tlet year = resX.slice(0,4) *1\r\n\t\t\tif (resX.length == 14) {\r\n\t\t\t\tlet isCheck = false\r\n\t\t\t\tif (year > 2016 && year < 2020) {isCheck = true}\r\n\t\t\t\tif (isVer == 52) {if (year > 2016) {isCheck = true}} // FF52 waterfox basilisk palemoon\r\n\t\t\t\tif (isCheck) {\r\n\t\t\t\t\tif (!isNaN(resX * 1)) {boolX = true}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// RFP FF56-63 = 20100101\r\n\t\t\t\t\t// note: not worth checking if RFP is enabled based on old-timey settings\r\n\t\t\t\tif (resX == \"20100101\") {boolX = true}\t\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num3, strX, resX, boolX, \"skip\", t0X)\r\n\t} catch(e) {\r\n\t\tfnRecord(num3, strX, e.name, zErr, e.message)\r\n\t}\r\n\t// DNT: FF: 1, unspecified\r\n\tlet strDNT = \"[doNotTrack] navigator\", boolDNT = false, numDNT = num3+2\r\n\tlet t0DNT = performance.now()\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet testDNT = navigator.doNotTrack\r\n\t\tif (testDNT == 1 || testDNT == \"unspecified\") {\r\n\t\t\tboolDNT = true\r\n\t\t} else {\r\n\t\t\ttestDNT = fnClean(testDNT)\r\n\t\t}\r\n\t\tif (isFF && !boolDNT) {\r\n\t\t\t// PM not supported: returns undefined\r\n\t\t\tif (isEngine == \"goanna\" && testDNT == \"undefined\") {\r\n\t\t\t\tfnRecord(numDNT, strDNT, testDNT, true, \"skip\", t0DNT)\r\n\t\t\t} else {\r\n\t\t\t\tfnRecord(numDNT, strDNT, testDNT, zErr, \"skip\", t0DNT)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tfnRecord(numDNT, strDNT, testDNT, boolDNT, \"skip\", t0DNT)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(numDNT, strDNT, e.name, zErr, e.message)\r\n\t}\r\n\r\n\tlet strY = \"[productSub] navigator\", boolY = false, numY = num3+4\r\n\ttry {\r\n\t\tlet t0Y = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resY = navigator.productSub\r\n\t\tif (resY == \"20100101\") {boolY = true} else {resY = fnClean(resY)}\r\n\t\tfnRecord(numY, strY, resY, boolY, \"skip\", t0Y)\r\n\t} catch(e) {\r\n\t\tfnRecord(numY, strY, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// not expected\r\n\t// getbattery\r\n\tlet strB = \"[getBattery] navigator\", boolB = false, numB = num2+4\r\n\tlet resB = []\r\n\tlet t0B = performance.now()\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\t// not going to add eventlisteners\r\n\t\tnavigator.getBattery().then(function(battery) {\r\n\t\t\ttry {resB.push(battery.level * 100 + \"%\")} catch(e) {resB.push(\"error\")}\r\n\t\t\ttry {resB.push((battery.charging ? \"\": \"not \") +\"charging\")} catch(e) {resB.push(\"error\")}\r\n\t\t\ttry {resB.push(battery.chargingTime)} catch(e) {resB.push(\"error\")}\r\n\t\t\ttry {resB.push(battery.dischargingTime)} catch(e) {resB.push(\"error\")}\r\n\t\t\tfnRecord(numB, strB, resB.join(\", \"), false, \"skip\", t0B)\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tif (e.name == \"TypeError\" && e.message.substring(0,38) == \"navigator.getBattery is not a function\") {\r\n\t\t\tif (e.message == \"navigator.getBattery is not a function\") {boolB = true} // webkit wil be false\r\n\t\t\tfnRecord(numB, strB, e.name, boolB, e.message, t0B)\r\n\t\t} else {\r\n\t\t\tfnRecord(numB, strB, e.name, zErr, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_obj_enumeration(num, title) {\r\n\t// 1762188\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet t = ['a','b'], result = []\r\n\t\tfor (let i in t) {\r\n\t\t\tfor (let j in t) {\r\n\t\t\t\tresult.push([i, j, t[i], t[j]])\r\n\t\t\t\tlet v = t[i]\r\n\t\t\t\tdelete t[i]\r\n\t\t\t\tt[i] = v\r\n\t\t\t}\r\n\t\t}\r\n\t\tlet res = result.join(\",\") // nonFF = 0,0,a,a,0,1,a,b,1,0,b,a,1,1,b,b\r\n\t\tlet bool = res == \"0,0,a,a,0,1,a,b,1,0,b,a\" ? true : false\r\n\t\tfnRecord(num, title, res, bool, \"skip\", t0)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_permission(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet userVis = \"userVisibleOnly\" // non-FF\r\n\t\tnavigator.permissions.query({name:\"push\"}).then(function(result) {\r\n\t\t\tlet res = result.state\r\n\t\t\tlet bool = (res == \"prompt\" || res == \"denied\" || res == \"granted\") ? true : false\r\n\t\t\tfnRecord(num, title, res, bool, \"skip\", t0)\r\n\t\t}).catch(error => {\r\n\t\t\tif ((error.message).includes(userVis)) {\r\n\t\t\t\tfnRecord(num, title, userVis, false, \"skip\", t0)\r\n\t\t\t} else {\r\n\t\t\t\tfnRecord(num, title, error.name, zErr, error.message)\r\n\t\t\t}\r\n\t\t})\r\n\t} catch(e) {\r\n\t\t// not supported in webkit: https://caniuse.com/?search=push%20permission\r\n\t\tif (isEngine == \"webkit\") {\r\n\t\t\tfnRecord(num, title, \"not supported\", false)\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_plugins(num, title) {\r\n\ttry {\r\n\t\tlet t0 = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tif (\"plugins\" in navigator) {\r\n\t\t\tlet res = [], bool = false\r\n\t\t\tlet p = navigator.plugins\r\n\t\t\tif (p.length == 0) {\r\n\t\t\t\tfnRecord(num, title, \"none\", true, \"skip\", t0)\r\n\t\t\t} else {\r\n\t\t\t\tfor (let i=0; i < p.length; i++) {\r\n\t\t\t\t\tres.push(p[i].name + (p[i].filename == \"\" ? \": * \" : \": \"+ p[i].filename)\r\n\t\t\t\t\t\t+ (p[i].description == \"\" ? \": *\" : \": \"+ p[i].description))\r\n\t\t\t\t}\r\n\t\t\t\tres.sort()\r\n\t\t\t\t// FF84 or lower allow just Flash\r\n\t\t\t\tif (isFF && isVer < 85 && res.length == 1) {\r\n\t\t\t\t\tif (res[0].split(\":\")[0] == \"Shockwave Flash\") {bool = true}\r\n\t\t\t\t}\r\n\t\t\t\tlet hash = mini(res)\r\n\t\t\t\tif (isVer > 98 || !isFF) { // allow non-FF to have the same\r\n\t\t\t\t\t// FF99+: controlled by pdfjs.disabled\r\n\t\t\t\t\t// 1720353: hardcoded mimeTypes\r\n\t\t\t\t\tif (hash == \"b46e6634\") {\r\n\t\t\t\t\t\tbool = true\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tsDetail[\"expected_\"+ title] = res\r\n\t\t\t\thash += buildButton(3, \"expected_\"+ title, res.length)\r\n\t\t\t\tfnRecord(num, title, hash, bool, \"skip\", t0)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tfnRecord(num, title, e.name, zErr)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tfnRecord(num, title, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_stacklength(num1, num2, num3) {\r\n\tlet t0 = performance.now()\r\n\tlet level = 0, test1 = 0\r\n\tfunction recurse() {\r\n\t\tlevel++\r\n\t\trecurse()\r\n\t}\r\n\ttry {\r\n\t\trecurse()\r\n\t} catch(e) {\r\n\t\ttest1 = level\r\n\t}\r\n\tlevel = 0\r\n\ttry {\r\n\t\trecurse()\r\n\t} catch(e) {\r\n\t\t// timing an error property lookup is not the same as generating a alow recurse one\r\n\t\t// columnNumber\r\n\t\tlet strCN = \"[columnNumber] error\", errCN = false\r\n\t\ttry {\r\n\t\t\tlet t0E = performance.now()\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tlet resCN = e.columnNumber\r\n\t\t\tlet testCN = resCN == undefined? false : true\r\n\t\t\tfnRecord(num1, strCN, resCN, testCN, \"skip\", t0E)\r\n\t\t} catch(n) {\r\n\t\t\tfnRecord(num1, strCN, n.name, zErr, n.message)\r\n\t\t}\r\n\t\t// fileName\r\n\t\tlet strFN = \"[fileName] error\", errFN = false\r\n\t\ttry {\r\n\t\t\tlet t0F = performance.now()\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tlet resFN = e.fileName\r\n\t\t\tif (resFN !== undefined) {resFN = resFN.slice(0,8) + \"...\"}\r\n\t\t\tlet testFN = resFN == undefined? false : true\r\n\t\t\tfnRecord(num1+1, strFN, resFN, testFN, \"skip\", t0F)\r\n\t\t} catch(n) {\r\n\t\t\tfnRecord(num1+1, strFN, n.name, zErr, n.message)\r\n\t\t}\r\n\t\t// lineNumber\r\n\t\tlet strLN = \"[lineNumber] error\", errLN = false\r\n\t\ttry {\r\n\t\t\tlet t0LN = performance.now()\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tlet resLN = e.lineNumber\r\n\t\t\tlet testLN = resLN == undefined? false : true\r\n\t\t\tfnRecord(num1+2, strLN, resLN, testLN, \"skip\", t0LN)\r\n\t\t} catch(n) {\r\n\t\t\tfnRecord(num1+2, strLN, n.name, zErr, n.message)\r\n\t\t}\r\n\r\n\t\t// first error\r\n\t\tlet perf = (t0- performance.now()) + performance.now()\r\n\t\tlet strRE = \"error\", resRE = e.name +\": \"+ e.message\r\n\t\tif (resRE == \"InternalError: too much recursion\") {\r\n\t\t\tfnRecord(num3, strRE, resRE, true, \"skip\", perf)\r\n\t\t} else {\r\n\t\t\tfnRecord(num3, strRE, resRE, false, \"skip\", )\r\n\t\t}\r\n\t\t// stack length: only for FYI purposes\r\n\t\tlet strSL = \"stack length\"\r\n\t\ttry {\r\n\t\t\tif (bBlock) {bB = 1}\r\n\t\t\tlet resSL = e.stack.toString().length\r\n\t\t\tresSL = zNA +\": FYI: \"+ s3 +\"[\"+ resSL +\"]\"+sc\r\n\t\t\tfnRecord(num2, strSL, resSL, zNA, \"skip\", perf)\r\n\t\t} catch(n) {\r\n\t\t\tfnRecord(num2, strSL, n.name, zErr, n.message, perf)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_storage_estimate(num, title) {\r\n\r\n\t// Notes: Win10 VM is  52gb / 33gb spare | FF is always the same in PB mode\r\n\t\t//   2147483648 : FF57-96 Windows / Android / Win 10 VM FF60-96\r\n\t/*\r\n\tFF97+ is not stable enough: see https://bugzilla.mozilla.org/show_bug.cgi?id=1735713\r\n   10737418240 : Windows, vannTenn's + bashonly's Linux, Android Fabrizio 100gb spare from 128gb\r\n    5641604300 : Android Fabrizio 49gb spare from 64gb\r\n    5512729395 : Android Thorin 44gb spare from 64gb\r\n    5301081292 : Android bashonly 40gb spare from 64gb\r\n    5256596684 : Win 10 VM 33gb spare from 52gb\r\n    2934867968 : Debian XCFE 2glops 650gb spare from 1TB\r\n    1521166745 : Ubuntu VM Fabrizio with 15GB of storage\r\n    1177328025 : Android aleyvo 1.5gb spare from 16gb\r\n\t*/\r\n\r\n\t// other: who cares if they match\r\n\t\t// brave:     2147483648 (same in incognito and Tor window)\r\n\t\t// opera:      310418104 normal\r\n\t\t// opera:      521917312 private\r\n\t\t// chrome: 1200238045593 normal\r\n\t\t// chrome:   33076376370 normal android\r\n\t\t// chrome:     485041940 incognito\r\n\t\t// chrome:     204974075 incognito android\r\n\r\n\t// ToDo: check if FF97+ new storage quota is based on disk or free disk space size\r\n\tlet t0 = performance.now()\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tnavigator.storage.estimate().then(estimate => {\r\n\t\t\tlet quota = estimate.quota,\r\n\t\t\t\tbool = false\r\n\t\t\tif (isFF) {\r\n\t\t\t\tif (isVer > 96) {\r\n\t\t\t\t\tbool = zNA\r\n\t\t\t\t\tquota = zNA +\": variable in FF97+ \"+ s3 +\"[\"+ quota +\"]\"+ sc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (quota > 2147000000 && quota < 2148000000) {bool = true}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tbool = zNA\r\n\t\t\t\tquota += \" [FF96 or lower]\"\r\n\t\t\t}\r\n\t\t\tfnRecord(num, title, quota, bool, \"skip\", t0)\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tif (isEngine == \"webkit\") {\r\n\t\t\tfnRecord(num, title, e.name, false, e.message) // not supported in webkit\r\n\t\t} else {\r\n\t\t\t// FF56 or lower\r\n\t\t\tlet result = e.name\r\n\t\t\tlet type = zErr\r\n\t\t\tlet error = e.message\r\n\t\t\tif (isFF && isVer < 57 && e.message == \"navigator.storage is undefined\") {\r\n\t\t\t\tresult = zNA +\": FF57+ required\"\r\n\t\t\t\ttype = zNA\r\n\t\t\t\terror = undefined\r\n\t\t\t}\r\n\t\t\tfnRecord(num, title, result, type, error)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_window_moz(num) {\r\n\r\n\tlet strC = \"[CSSMozDocumentRule] window\"\r\n\ttry {\r\n\t\tlet t0C = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resC = typeof CSSMozDocumentRule\r\n\t\tlet boolC = \"function\" === typeof CSSMozDocumentRule // FF53+\r\n\t\tif (!boolC && isVer == 52) {\r\n\t\t\tboolC = \"object\" === resC\r\n\t\t}\r\n\t\tfnRecord(num, strC, resC, boolC, \"skip\", t0C)\r\n\t} catch(e) {\r\n\t\tfnRecord(num, strC, e.name, zErr, e.message)\r\n\t}\r\n\r\n\tlet strM = \"[mozRTCPeerConnection] window\"\r\n\ttry {\r\n\t\tlet t0M = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resM = typeof mozRTCPeerConnection\r\n\t\tlet boolM = \"function\" === typeof mozRTCPeerConnection\r\n\t\tif (!boolM && isFF && \"undefined\" === resM) {\r\n\t\t\t// TB/PM build without WebRTC\r\n\t\t\tif (undefined == window.RTCPeerConnection) {\r\n\t\t\t\tboolM = zNA\r\n\t\t\t\tresM = zNA +\": --disable-webrtc\"+ s3 +\" [\"+ resM +\"]\"+ sc\r\n\t\t\t} else if (isVer > 112) { // 1531812\r\n\t\t\t\tboolM = zNA\r\n\t\t\t\tresM = zNA +\": FF112 or lower\"+ s3 +\" [\"+ resM +\"]\"+ sc\r\n\t\t\t}\r\n\t\t} else if (!isFF) {\r\n\t\t\tboolM = zNA\r\n\t\t\tresM += \" [FF112 or lower]\"\r\n\t\t}\r\n\t\tfnRecord(num+1, strM, resM, boolM, \"skip\", t0M)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+1, strM, e.name, zErr, e.message)\r\n\t}\r\n\r\n\tlet strA = \"[mozInnerScreenX] window\", boolA = false\r\n\ttry {\r\n\t\tlet t0A = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resA = window.mozInnerScreenX\r\n\t\tif (!isNaN(resA)) {boolA = true} else {resA = fnClean(resA)}\r\n\t\tfnRecord(num+2, strA, resA, boolA, \"skip\", t0A)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+2, strA, e.name, zErr, e.message)\r\n\t}\r\n\tlet strB = \"[mozInnerScreenY] window\", boolB = false\r\n\ttry {\r\n\t\tlet t0B = performance.now()\r\n\t\tif (bBlock) {bB = 1}\r\n\t\tlet resB = window.mozInnerScreenY\r\n\t\tif (!isNaN(resB)) {boolB = true} else {resB = fnClean(resB)}\r\n\t\tfnRecord(num+3, strB, resB, boolB, \"skip\", t0B)\r\n\t} catch(e) {\r\n\t\tfnRecord(num+3, strB, e.name, zErr, e.message)\r\n\t}\r\n}\r\n\r\nfunction get_window_props(num1, num2, title) {\r\n\t// conservative small subset of items from engineprops PoC\r\n\tfunction rec(type, value, name) {\r\n\t\tif (value !== \"undefined\") {\r\n\t\t\tif (type == num1) {resE.push(name)} else {resU.push(name)}\r\n\t\t}\r\n\t}\r\n\r\n\t// expected\r\n\tlet t0E = performance.now()\r\n\tlet resE = []\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\t// enumerate\r\n\t\trec(num1, typeof KeyEvent, \"KeyEvent\")\r\n\t\trec(num1, typeof dump, \"dump\")\r\n\t\trec(num1, typeof fullScreen, \"fullScreen\")\r\n\t\trec(num1, typeof getDefaultComputedStyle, \"getDefaultComputedStyle\")\r\n\t\trec(num1, typeof mozInnerScreenX, \"mozInnerScreenX\")\r\n\t\trec(num1, typeof mozInnerScreenY, \"mozInnerScreenY\")\r\n\t\trec(num1, typeof onabsolutedeviceorientation, \"onabsolutedeviceorientation\") // removed FF110 1689631\r\n\t\t//rec(num1, typeof onbeforeinput, \"onbeforeinput\")\r\n\t\trec(num1, typeof onmozfullscreenchange, \"onmozfullscreenchange\")\r\n\t\trec(num1, typeof onmozfullscreenerror, \"onmozfullscreenerror\")\r\n\t\trec(num1, typeof scrollByLines, \"scrollByLines\")\r\n\t\trec(num1, typeof scrollByPages, \"scrollByPages\")\r\n\t\trec(num1, typeof scrollMaxX, \"scrollMaxX\")\r\n\t\trec(num1, typeof scrollMaxY, \"scrollMaxY\")\r\n\t\trec(num1, typeof setResizable, \"setResizable\")\r\n\t\trec(num1, typeof sizeToContent, \"sizeToContent\") // removed nightly FF117+ 1832733 / 1600400\r\n\t\trec(num1, typeof u2f, \"u2f\")\r\n\t\trec(num1, typeof updateCommands, \"updateCommands\")\r\n\t\t// finish\r\n\t\tlet boolE = resE.length > 0\r\n\t\tlet hashE = boolE ? mini(resE) : \"none\"\r\n\t\tif (boolE) {\r\n\t\t\tif (resE.join(\", \").length < 48) {\r\n\t\t\t\thashE = resE.join(\", \")\r\n\t\t\t} else {\r\n\t\t\t\tsDetail[\"expected_\"+ title] = resE\r\n\t\t\t\thashE += buildButton(3, \"expected_\"+ title, resE.length)\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num1, title, hashE, boolE, \"skip\", t0E)\r\n\t} catch(e) {\r\n\t\tfnRecord(num1, title, e.name, zErr, e.message)\r\n\t}\r\n\r\n\t// unexpected\r\n\tlet t0U = performance.now()\r\n\tlet resU = []\r\n\ttry {\r\n\t\tif (bBlock) {bB = 1}\r\n\t\t// enumerate\r\n\t\t\t// webkit only\r\n\t\trec(num2, typeof browser, \"browser\")\r\n\t\trec(num2, typeof getMatchedCSSRules, \"getMatchedCSSRules\")\r\n\t\trec(num2, typeof safari, \"safari\")\r\n\t\trec(num2, typeof showModalDialog, \"showModalDialog\")\r\n\t\trec(num2, typeof webkitCancelRequestAnimationFrame, \"webkitCancelRequestAnimationFrame\")\r\n\t\trec(num2, typeof webkitConvertPointFromNodeToPage, \"webkitConvertPointFromNodeToPage\")\r\n\t\trec(num2, typeof webkitConvertPointFromPageToNode, \"webkitConvertPointFromPageToNode\")\r\n\t\trec(num2, typeof webkitIndexedDB, \"webkitIndexedDB\")\r\n\t\t\t// blink only\r\n\t\trec(num2, typeof Keyboard, \"Keyboard\")\r\n\t\trec(num2, typeof KeyboardLayoutMap, \"KeyboardLayoutMap\")\r\n\t\trec(num2, typeof PERSISTENT, \"PERSISTENT\")\r\n\t\trec(num2, typeof TEMPORARY, \"TEMPORARY\")\r\n\t\trec(num2, typeof chrome, \"chrome\")\r\n\t\trec(num2, typeof cookieStore, \"cookieStore\") // FF132 nightly 1918643\r\n\t\trec(num2, typeof getScreenDetails, \"getScreenDetails\")\r\n\t\trec(num2, typeof launchQueue, \"launchQueue\")\r\n\t\trec(num2, typeof navigation, \"navigation\") // FF146 nightly 1979288\r\n\t\trec(num2, typeof offscreenBuffering, \"offscreenBuffering\")\r\n\t\trec(num2, typeof onappinstalled, \"onappinstalled\")\r\n\t\trec(num2, typeof onbeforeinstallprompt, \"onbeforeinstallprompt\")\r\n\t\trec(num2, typeof onbeforematch, \"onbeforematch\") // FF139 1761043 dom.hidden_until_found.enabled\r\n\t\trec(num2, typeof onbeforexrselect, \"onbeforexrselect\")\r\n\t\trec(num2, typeof oncontextlost, \"oncontextlost\")\r\n\t\trec(num2, typeof oncontextrestored, \"oncontextrestored\")\r\n\t\trec(num2, typeof ondeviceorientationabsolute, \"ondeviceorientationabsolute\") // FF110+ 1689631\r\n\t\trec(num2, typeof onsearch, \"onsearch\")\r\n\t\trec(num2, typeof openDatabase, \"openDatabase\")\r\n\t\trec(num2, typeof opr, \"opr\") // OPERA desktop at least\r\n\t\trec(num2, typeof originAgentCluster, \"originAgentCluster\") // FF138+ 1665474\r\n\t\trec(num2, typeof queryLocalFonts, \"queryLocalFonts\")\r\n\t\trec(num2, typeof showDirectoryPicker, \"showDirectoryPicker\")\r\n\t\trec(num2, typeof showOpenFilePicker, \"showOpenFilePicker\")\r\n\t\trec(num2, typeof showSaveFilePicker, \"showSaveFilePicker\")\r\n\t\trec(num2, typeof trustedTypes, \"trustedTypes\") // FF145 nightly 1955251\r\n\t\trec(num2, typeof webkitRequestFileSystem, \"webkitRequestFileSystem\")\r\n\t\trec(num2, typeof webkitResolveLocalFileSystemURL, \"webkitResolveLocalFileSystemURL\")\r\n\t\trec(num2, typeof webkitStorageInfo, \"webkitStorageInfo\")\r\n\r\n\t\t// finish\r\n\t\tif (isVer > 109) {\r\n\t\t\t// FF110+ 1689631\r\n\t\t\tresU = resU.filter(x => ![\"ondeviceorientationabsolute\"].includes(x))\r\n\t\t\tif (isVer > 125) {\r\n\t\t\t\t// FF126: 1887729\r\n\t\t\t\tresU = resU.filter(x => ![\"oncontextlost\"].includes(x))\r\n\t\t\t\tresU = resU.filter(x => ![\"oncontextrestored\"].includes(x))\r\n\t\t\t}\r\n\t\t\tif (isVer > 131) {resU = resU.filter(x => ![\"cookieStore\"].includes(x))} // FF132 nightly 1918643\r\n\t\t\tif (isVer > 137) {resU = resU.filter(x => ![\"originAgentCluster\"].includes(x))}\r\n\t\t\tif (isVer > 138) {resU = resU.filter(x => ![\"onbeforematch\"].includes(x))}\r\n\t\t\tif (isVer > 144) {resU = resU.filter(x => ![\"trustedTypes\"].includes(x))} // FF145 nightly 1955251\r\n\t\t\tif (isVer > 145) {resU = resU.filter(x => ![\"navigation\"].includes(x))} // FF146 nightly 1979288 dom.navigation.webidl.enabled\r\n\t\t}\r\n\r\n\t\tlet boolU = resU.length == 0\r\n\t\tlet hashU = !boolU ? mini(resU) : \"none\"\r\n\t\tif (!boolU) {\r\n\t\t\tif (resU.join(\", \").length < 48) {\r\n\t\t\t\thashU = resU.join(\", \")\r\n\t\t\t} else {\r\n\t\t\t\tsDetail[\"unexpected_\"+ title] = resU\r\n\t\t\t\thashU += buildButton(3, \"unexpected_\"+ title, resU.length)\r\n\t\t\t}\r\n\t\t}\r\n\t\tfnRecord(num2, title, hashU, boolU, \"skip\", t0U)\r\n\t} catch(e) {\r\n\t\tfnRecord(num2, title, e.name, zErr, e.message)\r\n\t}\r\n\r\n}\r\n\r\nfunction rerun() {\r\n\t// reset\r\n\tresults = []\r\n\titemnumbers = [],\r\n\tdata = {},\r\n\taPerf = [],\r\n\tcount = 0,\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\t// do it: delay for user to see visual change\r\n\tsetTimeout(function() {\r\n\t\trun()\r\n\t}, 120)\r\n}\r\n\r\nfunction run() {\r\n\t// now get on with it\r\n\tfnRecord(0, \"header\", \"expected\")\r\n\tfnRecord(300, \"header\", \"not expected\")\r\n\tfnRecord(600, \"header\", \"specific values\")\r\n\tfnRecord(899, \"hr\")\r\n\tfnRecord(900, \"header\", \"errors\")\r\n\t// OK, let's kick some ass\r\n\t// stagger to get more accurate perf\r\n\r\n\tsetTimeout(function() {\r\n\t\tgt0 = performance.now()\r\n\t\taPerf.push([\"START 1\", gt0])\r\n\t\tget_installtrigger(50) // 50-52\r\n\t\tget_eval_length(630, \"eval.toString().length\")\r\n\t\tget_colorgamut(310, \"[css] color-gamut\")\r\n\t\tget_nav_screen(170)\r\n\t\tget_window_props(260, 450, \"[properties] window\")\r\n\t\tget_obj_enumeration(750, \"object enumeration\")\r\n\t\tget_last_prototype_keys(640, \"[last x keys]\") // 640+641\r\n\t\tget_math(720, \"math\")\r\n\t\tget_nav_values(150, 400, 740) // 100 expected, 400-410 not expected, 740.. specific\r\n\t\tget_dates(620)\r\n\t\tget_js_client_hints(320, \"[js] client hints\")\r\n\t\tget_mimetypes(730, \"mimetypes\")\r\n\t\tget_plugins(770, \"plugins\")\r\n\t\tget_media_constraints(725, \"[constraints] mediaDevices\")\r\n\t}, 1)\r\n\r\n\tsetTimeout(function() {\r\n\t\taPerf.push([\"START 2\", performance.now(), performance.now() - gt0])\r\n\t\tget_moz_objects(88, \"CanvasRenderingContext2D\")\r\n\t\tget_moz_objects(94, \"CSS2|CSSStyle\")\r\n\t\tget_moz_objects(96, \"DataTransfer\")\r\n\t\tget_moz_objects(98, \"Document\")\r\n\t\tget_moz_objects(100, \"Element\")\r\n\t\tget_moz_objects(106, \"HTMLElement\")\r\n\t\tget_moz_objects(108, \"HTMLCanvasElement\")\r\n\t\tget_moz_objects(110, \"HTMLInputElement\")\r\n\t\tget_moz_objects(112, \"HTMLMediaElement\")\r\n\t\tget_moz_objects(114, \"HTMLVideoElement\")\r\n\t\tget_moz_objects(116, \"IDBIndex\")\r\n\t\tget_moz_objects(118, \"IDBObjectStore\")\r\n\t\tget_moz_objects(120, \"MathMLElement\") // added in FF71\r\n\t\tget_moz_objects(122, \"MouseEvent\")\r\n\t\tget_moz_objects(126, \"Navigator\")\r\n\t\t\t// ^media.peerconnection.enabled + media.navigator.enabled = false negative\r\n\t\tget_moz_objects(128, \"OfflineResourceList\")\r\n\t\t\t// ^pref: browser.cache.offline.enable [pref exists since Jesus]\r\n\t\t\t// nightly/beta/dev default off - since when? FF71+ 1237782 ?\r\n\t\tget_moz_objects(130, \"Screen\")\r\n\t\tget_moz_objects(132, \"ShadowRoot\") // added in FF63\r\n\t\tget_moz_objects(134, \"SVGElement\")\r\n\t\tget_moz_objects(136, \"XMLHttpRequest\")\r\n\t}, 1)\r\n\r\n\tsetTimeout(function() {\r\n\t\taPerf.push([\"START 3\", performance.now(), performance.now() - gt0])\r\n\t\tget_window_moz(250) // 250-253\r\n\t\tget_nav_keys(85, 350, \"[navigator] keys\") // 85 expected, 350 not expected\r\n\t\tget_intl_supportedlocales(655) // +5's: C, DTF, DN, LF, NF, PR, RTF\r\n\t\tget_intl_canonical_locale(667, \"Intl.getCanonicalLocales\")\r\n\t\tget_locale_compare(710, \"locale.compare\")\r\n\t\tget_intl_supportedvaluesof(700, \"Intl.supportedValuesOf\") // timeZone\r\n\t}, 1)\r\n\r\n\tsetTimeout(function() {\r\n\t\taPerf.push([\"START 4\", performance.now(), performance.now() - gt0])\r\n\t\tget_moz_colors(90, \"[colors] moz\")\r\n\t\tget_moz_fonts(102, \"[fonts] moz\")\r\n\t\tget_errors(902)\r\n\t\tget_stacklength(30,850,901) // 30-32 error properties, 850 = stack length, 901 = first error message\r\n\t}, 1)\r\n\r\n\tsetTimeout(function() {\r\n\t\taPerf.push([\"START THE END\", performance.now(), performance.now() - gt0])\r\n\t\tget_iframe_props(40, 330, \"[properties] iframe\") // 40 expected, 330 not expected,\r\n\t}, 1)\r\n\tsetTimeout(function() {\r\n\t\tget_permission(800, \"[permissions] push\")\r\n\t\tget_storage_estimate(870, \"storage estimate\")\r\n\t}, 1)\r\n\r\n}\r\n\r\nsetTimeout(function() {\r\n\tPromise.all([\r\n\t\tget_globals()\r\n\t]).then(function(){\r\n\t\tPromise.all([\r\n\t\t\tget_is95(),\r\n\t\t\tget_isVer(),\r\n\t\t]).then(function(){\r\n\t\t\trun()\r\n\t\t})\r\n\t})\r\n}, 50)\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/engineprop.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=900\">\r\n\t<title>engine properties</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<style>\r\n\t\ttable {width: 90%; min-width: 600px; max-width: 880px}\r\n\t\t#tb3 td:first-child { text-align: left; vertical-align: top;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#feature\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb3\">\r\n\t\t<col width=\"50%\"><col width=\"50%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">engine properties\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn3 btnfirst\" onClick=\"rerun()\">[ run test ]</span>\r\n\t\t\t<span class=\"btn3 btn\" onClick=\"logConsole(`list`)\">[ log list ]</span> | \r\n\t\t\t<span class=\"btn3 btn\" onClick=\"scrape()\">[ scrape ]</span> | \r\n\t\t\t<span class=\"btn3 btn\">\r\n\t\t\t\t<select name=\"items\" id=\"items\">\r\n<option value=\"w\">caches</option>\r\n<option value=\"w\">console</option>\r\n<option value=\"w\">crypto</option>\r\n<option value=\"w\">CSS</option>\r\n<option value=\"p\">CSS2Properties</option>\r\n<option value=\"p\">CSSStyleProperties</option>\r\n<option value=\"p\">CanvasRenderingContext2D</option>\r\n<option value=\"w\">clientInformation</option>\r\n<option value=\"p\">Element</option>\r\n<option value=\"p\">Headers</option>\r\n<option value=\"w\">navigator</option>\r\n<option value=\"w\">performance</option>\r\n<option value=\"w\">screen</option>\r\n\t\t\t\t</select> \r\n\t\t\t<span onClick=\"get_engineItem()\">[ run item ]</span>\r\n\t\t\t</span> | \r\n\t\t\t<span class=\"btn3 btn\" onClick=\"copyclip(`results`)\">[ copy ]</span>\r\n\t\t\t<br><br>\r\n\t\t\t<hr>\r\n\t\t\t<br>\r\n\t\t\t<span class=\"spaces\" style=\"color: #b3b3b3;\" id=\"results\"></span>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet oTypes = {},\r\n\taList = [],\r\n\taNew = [], // level 1 items found in scrape not in the test\r\n\taErr = [], // invalid group codes\r\n\taType = [], // missing a typeof check\r\n\ttstart,\r\n\tpadNum = 14,\r\n\tdomresult = document.getElementById(\"results\"),\r\n\tdomperf = document.getElementById(\"perf\"),\r\n\ts3 = \"<span class='s3'>\",\r\n\ts9 = \"<span class='s9'>\",\r\n\ts12 = \"<span class='s12'>\",\r\n\ts14 = \"<span class='s14'>\",\r\n\ts17 = \"<span class='s17'>\",\r\n\tsb = \"<span class='bad'>\",\r\n\tsg = \"<span class='good'>\",\r\n\tsc = \"</span>\",\r\n\tgreen_tick = \"<span style='font-size: 10px;'><b>\" + s9 +\" \\u2713\"+ sc + \"</b></span>\",\r\n\tred_cross = \"<span style='font-size: 10px;'><b>\" + sb +\" \\u2715\"+ sc + \"</b></span>\"\r\n\r\n/** functions from generic **/\r\n// we don't want to include globals or generic which will pollute scraping\r\n\r\nfunction json_highlight(json) {\r\n\tif (typeof json != 'string') {\r\n\t\tjson = json_stringify(json);\r\n\t}\r\n\tjson = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\r\n\treturn json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {\r\n\t\tvar cls = 'number';\r\n\t\tif (/^\"/.test(match)) {\r\n\t\t\tif (/:$/.test(match)) {\r\n\t\t\t\tcls = 'key';\r\n\t\t\t} else {\r\n\t\t\t\tcls = 'string';\r\n\t\t\t\t// color undefined (aka \"typeof undefined\")\r\n\t\t\t\t//if (match == \"\\\"typeof undefined\\\"\") {cls = 'null';}\r\n\t\t\t}\r\n\t\t} else if (/true|false/.test(match)) {\r\n\t\t\tcls = 'boolean';\r\n\t\t} else if (/null/.test(match)) {\r\n\t\t\tcls = 'null';\r\n\t\t}\r\n\t\treturn '<span class=\"'+ cls +'\">'+ match +'</span>';\r\n\t})\r\n}\r\n\r\nfunction json_stringify(passedObj, options = {}) {\r\n\t/* https://github.com/lydell/json-stringify-pretty-compact */\r\n\tconst stringOrChar = /(\"(?:[^\\\\\"]|\\\\.)*\")|[:,]/g;\r\n\tconst indent = JSON.stringify(\r\n\t\t[1],\r\n\t\tundefined,\r\n\t\toptions.indent === undefined ? 2 : options.indent\r\n\t).slice(2, -3);\r\n\tconst maxLength =\r\n\t\tindent === \"\"\r\n\t\t\t? Infinity\r\n\t\t\t: options.maxLength === undefined\r\n\t\t\t? 65 // was 80\r\n\t\t\t: options.maxLength;\r\n\tlet { replacer } = options;\r\n\r\n\treturn (function _stringify(obj, currentIndent, reserved) {\r\n\t\tif (obj && typeof obj.toJSON === \"function\") {\r\n\t\t\tobj = obj.toJSON();\r\n\t\t}\r\n\r\n\t\t// display undefined under an alias so we always have the right number of values\r\n\t\t// this is just a display, it does not alter the fingerprint data\r\n\t\t//if (obj === undefined) {obj = \"typeof undefined\"}\r\n\r\n\t\tconst string = JSON.stringify(obj, replacer);\r\n\t\tif (string === undefined) {\r\n\t\t\treturn string;\r\n\t\t}\r\n\t\tconst length = maxLength - currentIndent.length - reserved;\r\n\t\tif (string.length <= length) {\r\n\t\t\tconst prettified = string.replace(\r\n\t\t\t\tstringOrChar,\r\n\t\t\t\t(match, stringLiteral) => {\r\n\t\t\t\t\treturn stringLiteral || `${match} `;\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t\tif (prettified.length <= length) {\r\n\t\t\t\treturn prettified;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (replacer != null) {\r\n\t\t\tobj = JSON.parse(string);\r\n\t\t\treplacer = undefined;\r\n\t\t}\r\n\t\tif (typeof obj === \"object\" && obj !== null) {\r\n\t\t\tconst nextIndent = currentIndent + indent;\r\n\t\t\tconst items = [];\r\n\t\t\tlet index = 0;\r\n\t\t\tlet start;\r\n\t\t\tlet end;\r\n\t\t\tif (Array.isArray(obj)) {\r\n\t\t\t\tstart = \"[\";\r\n\t\t\t\tend = \"]\";\r\n\t\t\t\tconst { length } = obj;\r\n\t\t\t\tfor (; index < length; index++) {\r\n\t\t\t\t\titems.push(\r\n\t\t\t\t\t\t_stringify(obj[index], nextIndent, index === length - 1 ? 0 : 1) ||\r\n\t\t\t\t\t\t\"null\"\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tstart = \"{\";\r\n\t\t\t\tend = \"}\";\r\n\t\t\t\tconst keys = Object.keys(obj);\r\n\t\t\t\tconst { length } = keys;\r\n\t\t\t\tfor (; index < length; index++) {\r\n\t\t\t\t\tconst key = keys[index];\r\n\t\t\t\t\tconst keyPart = `${JSON.stringify(key)}: `;\r\n\t\t\t\t\tconst value = _stringify(\r\n\t\t\t\t\t\tobj[key],\r\n\t\t\t\t\t\tnextIndent,\r\n\t\t\t\t\t\tkeyPart.length + (index === length - 1 ? 0 : 1)\r\n\t\t\t\t\t);\r\n\t\t\t\t\tif (value !== undefined) {\r\n\t\t\t\t\t\titems.push(keyPart + value);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (items.length > 0) {\r\n\t\t\t\treturn [start, indent + items.join(`,\\n${nextIndent}`), end].join(\r\n\t\t\t\t`\\n${currentIndent}`\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\treturn string;\r\n\t})(passedObj, \"\", 0);\r\n}\r\n\r\nfunction mini(str) {\r\n\t// https://stackoverflow.com/a/22429679\r\n\tconst json = `${JSON.stringify(str)}`\r\n\tlet i, len, hash = 0x811c9dc5\r\n\tfor (i = 0, len = json.length; i < len; i++) {\r\n\t\thash = Math.imul(31, hash) + json.charCodeAt(i) | 0\r\n\t}\r\n\treturn ('0000000' + (hash >>> 0).toString(16)).slice(-8)\r\n}\r\n\r\nfunction copyclip(element) {\r\n\t// fallback: e.g FF62-\r\n\tfunction copyExec() {\r\n\t\tif (document.selection) {\r\n\t\t\tlet range = document.body.createTextRange()\r\n\t\t\trange.moveToElementText(document.getElementById(element))\r\n\t\t\trange.select().createTextRange()\r\n\t\t\tdocument.execCommand(\"copy\")\r\n\t\t} else if (window.getSelection) {\r\n\t\t\tlet range = document.createRange()\r\n\t\t\trange.selectNode(document.getElementById(element))\r\n\t\t\twindow.getSelection().addRange(range)\r\n\t\t\tdocument.execCommand(\"copy\")\r\n\t\t}\r\n\t}\r\n\t// clipboard API\r\n\tif (\"clipboard\" in navigator) {\r\n\t\ttry {\r\n\t\t\tlet content = document.getElementById(element).innerHTML\r\n\t\t\t// remove spans, change linebreaks\r\n\t\t\tlet regex = /<br\\s*[\\/]?>/gi\r\n\t\t\tcontent = content.replace(regex, \"\\r\\n\")\r\n\t\t\tcontent = content.replace(/<\\/?span[^>]*>/g,\"\")\r\n\t\t\tcontent = content.replace(/<\\/?hr[^>]*>/g,\"\")\r\n\t\t\t// get it\r\n\t\t\tnavigator.clipboard.writeText(content).then(function() {\r\n\t\t\t\t// clipboard successfully set\r\n\t\t\t}, function() {\r\n\t\t\t\t// clipboard write failed\r\n\t\t\t\tcopyExec()\r\n\t\t\t})\r\n\t\t} catch(e) {\r\n\t\t\tcopyExec()\r\n\t\t}\r\n\t} else {\r\n\t\tcopyExec()\r\n\t}\r\n}\r\n\r\n/** function for this test **/\r\n\r\nfunction collectProps () {\r\n\tconst objCache = new Set();\r\n\t// skip these\r\n\tobjCache.add(window.self);\r\n\t// prevent us decending into the DOM\r\n\tobjCache.add(document);\r\n\tobjCache.add(document.activeElement);\r\n\t//objCache.add(event?.currentTarget); // FF73 or lower this causes a syntax error\r\n\tif (self.event && event.currentTarget) objCache.add(event.currentTarget)\r\n\r\n\tconst recursionAllowed = new Set();\r\n\trecursionAllowed.add('navigator');\r\n\tfunction extractObjectProps (obj, breadcrumbs = [], depth = 0) {\r\n\t\tif (depth >= 5) {\r\n\t\t\treturn {};\r\n\t\t}\r\n\t\tconst props = {};\r\n\t\tconst proplist = [];\r\n\t\tfor (const prop in obj) {\r\n\t\t\tproplist.push(prop);\r\n\t\t}\r\n\t\tproplist.sort().forEach((prop) => {\r\n\t\t\ttry {\r\n\t\t\t\tconst desc = Object.getOwnPropertyDescriptor(obj, prop) || {};\r\n\t\t\t\tdesc.type = typeof obj[prop];\r\n\t\t\t\tif (desc.type === 'function') {\r\n\t\t\t\t\tdesc.value = obj[prop].toString();\r\n\t\t\t\t} else if (desc.type === 'object' && prop !== \"opener\") {\r\n\t\t\t\t\t// don't enuemrate opener object\r\n\t\t\t\t\tif (!objCache.has(obj[prop]) || recursionAllowed.has(prop)) {\r\n\t\t\t\t\t\tobjCache.add(obj[prop]);\r\n\t\t\t\t\t\tdesc.properties = extractObjectProps(obj[prop], [...breadcrumbs, prop], depth + 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdelete desc.value;\r\n\t\t\t\t}\r\n\t\t\t\tprops[prop] = desc;\r\n\t\t\t} catch (e) {\r\n\t\t\t\tconsole.warn(`Error for ${breadcrumbs.join('.')}.${prop}`, e);\r\n\t\t\t}\r\n\t\t});\r\n\t\treturn props;\r\n\t}\r\n\treturn {\r\n\t\twindow: extractObjectProps(window)\r\n\t}\r\n}\r\n\r\nfunction get_engineProp(item) {\r\n\ttry {\r\n\t\tlet objCache = new Set()\r\n\t\tlet order = {}\r\n\t\tobjCache.add(window.self)\r\n\t\t// prevent us decending into the DOM\r\n\t\tobjCache.add(document);\r\n\t\tobjCache.add(document.activeElement);\r\n\t\t//objCache.add(event?.currentTarget); // FF73 or lower this causes a syntax error\r\n\t\tif (self.event && event.currentTarget) objCache.add(event.currentTarget)\r\n\t\tfunction get_perf_props(item) {\r\n\t\t\tconst recursionAllowed = new Set();\r\n\t\t\trecursionAllowed.add('navigator');\r\n\t\t\tfunction extractObjectProps (obj, breadcrumbs = [], depth = 0) {\r\n\t\t\t\tif (depth >= 5) {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t\tconst props = {}\r\n\t\t\t\tconst proplist = []\r\n\t\t\t\tfor (const prop in obj) {\r\n\t\t\t\t\tproplist.push(prop)\r\n\t\t\t\t}\r\n\t\t\t\tlet proptemp = []\r\n\t\t\t\tproplist.forEach((prop) => {\r\n\t\t\t\t\tproptemp.push(prop)\r\n\t\t\t\t})\r\n\t\t\t\tif (proptemp.length) {\r\n\t\t\t\t\tif (\"object\" === typeof obj && obj !== null) {\r\n\t\t\t\t\t\tlet name = (obj+\"\").slice(8, -1)\r\n\t\t\t\t\t\torder[name] = {}\r\n\t\t\t\t\t\torder[name][\"count\"] = proptemp.length\r\n\t\t\t\t\t\torder[name][\"hash\"] = mini(proptemp)\r\n\t\t\t\t\t\torder[name][\"data\"] = proptemp\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tproplist.sort().forEach((prop) => {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tconst desc = Object.getOwnPropertyDescriptor(obj, prop) || {}\r\n\t\t\t\t\t\tif (typeof obj[prop] === 'function') {\r\n\t\t\t\t\t\t\tdesc.length = obj[prop].toString().length\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tdesc.type = typeof obj[prop]\r\n\t\t\t\t\t\tif (desc.type === 'function') {\r\n\t\t\t\t\t\t\tdesc.value = obj[prop].toString()\r\n\t\t\t\t\t\t} else if (desc.type === 'object' && prop !== \"opener\") {\r\n\t\t\t\t\t\t\t// don't enuemrate opener object\r\n\t\t\t\t\t\t\t//if (!objCache.has(obj[prop])) { // || recursionAllowed.has(prop)) {\r\n\t\t\t\t\t\t\tif (!objCache.has(obj[prop]) || recursionAllowed.has(prop)) {\r\n\t\t\t\t\t\t\t\tobjCache.add(obj[prop])\r\n\t\t\t\t\t\t\t\tdesc.properties = extractObjectProps(obj[prop], [...breadcrumbs, prop], depth + 1)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tdelete desc.value\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tprops[prop] = desc\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tconsole.warn(`Error for ${breadcrumbs.join('.')}.${prop}`, e)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\treturn props\r\n\t\t\t}\r\n\t\t\treturn extractObjectProps(item)\r\n\t\t}\r\n\t\tlet data = get_perf_props(item)\r\n\t\tlet datahash = mini(data)\r\n\t\tlet oData = {\r\n\t\t\t\"data\": {\r\n\t\t\t\thash: datahash,\r\n\t\t\t\tmetrics: data,\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (Object.keys(order).length) {\r\n\t\t\t// reorder order\r\n\t\t\tlet neworder = {}\r\n\t\t\tfor (const k of Object.keys(order).sort()) {neworder[k] = order[k]}\r\n\t\t\tlet orderhash = mini(neworder)\r\n\t\t\toData[\"order\"] = {\"hash\": orderhash, \"metrics\": neworder}\r\n\t\t}\r\n\t\treturn oData\r\n\t} catch(e) {\r\n\t\treturn e+\"\"\r\n\t}\r\n}\r\n\r\nfunction get_engineItem() {\r\n\tdomresult.innerHTML = \"\"\r\n\tdomperf.innerHTML = \"\"\r\n\r\n\tsetTimeout(function() {\r\n\t\ttstart = performance.now()\r\n\t\tlet selection = document.getElementById(\"items\")\r\n\t\tlet type = selection.value\r\n\t\tlet name = selection.options[selection.selectedIndex].text\r\n\t\tlet displayname = s3 + name + sc + \": \"\r\n\t\tlet item\r\n\r\n\t\ttry {\r\n\t\t\tif (\"w\" === type) {\r\n\t\t\t\titem = window[name]\r\n\t\t\t} else if (\"p\" === type) {\r\n\t\t\t\titem = window[name].prototype\r\n\t\t\t}\r\n\t\t\tif (item !== undefined) {\r\n\t\t\t\tlet oData = get_engineProp(item)\r\n\t\t\t\tif (\"string\" == typeof oData) {\r\n\t\t\t\t\tdomresult.innerHTML = displayname + oData\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlet hash = mini(oData)\r\n\t\t\t\t\tdomperf.innerHTML = Math.round((performance.now() - tstart)) +\" ms\"\r\n\t\t\t\t\tdomresult.innerHTML = displayname + hash + \"<br><br>\" + json_highlight(oData)\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tdomresult.innerHTML = displayname + \"oophs... not coded yet\"\r\n\t\t\t}\r\n\t\t} catch(e) {\r\n\t\t\tdomresult.innerHTML = displayname + e+\"\"\r\n\t\t}\r\n\t}, 170)\r\n}\r\n\r\nfunction logConsole(name) {\r\n\tlet array = [], str = \"\"\r\n\tif (name == \"list\") { array = aList; str = \"TEST LIST\"\r\n\t}\r\n\tconsole.log(str +\" [\" + array.length +\"]\\n\" + array.join(\"\\n\"))\r\n}\r\n\r\nfunction output() {\r\n\tdomperf.innerHTML = Math.round((performance.now() - tstart)) +\" ms | \"+ aList.length +\" items\"\r\n\taList.sort()\r\n\tlet display = []\r\n\tfor (const k of Object.keys(oTypes).sort()) {\r\n\t\tlet group = oTypes[k]\r\n\t\tlet gHash = []\r\n\t\tlet gDisplay = []\r\n\t\tlet gCount = 0\r\n\t\tlet gCountUndefined = 0\r\n\t\t// get group details\r\n\t\tfor (const n of Object.keys(group).sort()) {\r\n\t\t\tlet data = group[n]\r\n\t\t\tdata.sort()\r\n\t\t\tgCount += data.length\r\n\t\t\tif (data.length) {\r\n\t\t\t\tlet tHash = mini(data)\r\n\t\t\t\tgHash.push(n +\": \"+ tHash)\r\n\t\t\t\tgDisplay.push(s3 + n.padStart(padNum) + sc +\": \"+ tHash + s3 +\" [\"+ data.length +\"]\"+ sc +\"<br>\")\r\n\t\t\t\tgDisplay.push(data.join(\", \") + \"<br>\")\r\n\t\t\t\tif (n == \"undefined\") {\r\n\t\t\t\t\tgCountUndefined += data.length\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (gDisplay.length) {\r\n\t\t\t// get group hash\r\n\t\t\tlet hashG = gHash.length ? mini(gHash) +\" [\"+ gCount +\"]\" : \"\"\r\n\t\t\t// add group header\r\n\t\t\tlet strG = \"\", strExtra = \"\", strColor = s12\r\n\t\t\t\t//only\r\n\t\t\tif (k == 10) {strG = \"BLINK ONLY\"\r\n\t\t\t} else if (k == 11) { strG = \"EDGEHTML ONLY\"\r\n\t\t\t} else if (k == 12) { strG = \"GECKO ONLY\"\r\n\t\t\t} else if (k == 14) { strG = \"WEBKIT ONLY\"\r\n\t\t\t\t// in two but not the other\r\n\t\t\t} else if (k == 50) { strG = \"BLINK + GECKO\"; strColor = s14; strExtra = \" [not webkit... yet]\"\r\n\t\t\t} else if (k == 52) { strG = \"BLINK + WEBKIT\"; strColor = s14; strExtra = \" [not gecko... yet]\"\r\n\t\t\t} else if (k == 54) { strG = \"GECKO + WEBKIT\"; strColor = s14; strExtra = \" [not blink... yet]\"\r\n\t\t\t\t// the rest\r\n\t\t\t} else if (k == 98) { strG = \"UNDETERMINED\"; strColor = s17; strExtra = \" to be assessed\"\r\n\t\t\t} else if (k == 99) { strG = \"IGNORE\"; strColor = s17; strExtra = \" confirmed in all three engines\"\r\n\t\t\t}\r\n\t\t\tstrG = strG.padStart(padNum)\r\n\t\t\tlet header = strG + (gHash.length ? \": \"+ hashG : \"\")\r\n\t\t\t// add a green \"all undefined\" - we should get at least three per engine\r\n\t\t\tlet strU = \"\"\r\n\t\t\tif (gHash.length && k < 90) {\r\n\t\t\t\tif (gCount == gCountUndefined) {\r\n\t\t\t\t\tstrU = \" \"+ green_tick + sg +\" all \"+ gCountUndefined +\" undefined\"+ sc\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tdisplay.push(strColor + header + strExtra + sc + strU +\"<br>\")\r\n\t\t\t// add group detail\r\n\t\t\tif (gHash.length) {\r\n\t\t\t\tdisplay.push(gDisplay.join(\"<br>\"))\r\n\t\t\t}\r\n\t\t\t// hr\r\n\t\t\tif (k < 99) {\r\n\t\t\t\tdisplay.push(\"<br><hr>\")\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// DEV ALERTS\r\n\tlet strAlert = \"\"\r\n\t// invalid group codes\r\n\tif (aErr.length) {\r\n\t\tstrAlert += sb + \"INVALID GROUP CODES: \" + sc + aErr.join(\", \") +\"<br><br>\"\r\n\t}\r\n\t// missing typeof parameter\r\n\tif (aType.length) {\r\n\t\tstrAlert += sb + \"MSSING TYPEOF CHECK: \" + sc + aType.join(\", \") +\"<br><br>\"\r\n\t}\r\n\t// dupes\r\n\tlet aDupe = aList\r\n\taList = aList.filter(function(item, position) {return aList.indexOf(item) === position})\r\n\tlet hDupe = mini(aDupe.join())\r\n\tlet hList = mini(aList.join())\r\n\tlet strDupe = \"\"\r\n\tif (hList !== hDupe) {\r\n\t\taDupe = aDupe.filter((item, index) => aDupe.indexOf(item) !== index)\r\n\t\tstrAlert += sb + \"DUPLICATES: \" + sc + aDupe.join(\", \") + \"<br><br>\"\r\n\t}\r\n\t// alerts\r\n\tif (strAlert !== \"\") {strAlert += \"<hr><br>\"}\r\n\r\n\t// function hash\r\n\t\t// gecko: 39: 334c4512 : function atob() {\\n    [native code]\\n}\r\n\t\t// blink: 33: 1dd1c7f6 : function atob() { [native code] }\r\n\t\t// edgehtml: 33: 1dd1c7f6 : function atob() { [native code] }\r\n\tlet strF = \"\"\r\n\ttry {\r\n\t\tlet testF = atob.toString()\r\n\t\ttestF = testF.replace(/\\n/g,'\\\\n');\r\n\t\tlet hashF = \"length \"+ testF.length + \": \" + mini(testF)\r\n\t\tstrF = s14 + (\"FUNCTION\").padStart(padNum) + sc +\": \"+ hashF + \" : \" + testF + \"<br><br><hr><br>\"\r\n\t} catch(e) {\r\n\t\tconsole.error(e.name, e.message)\r\n\t}\r\n\t// output\r\n\tlet strM = s14 + (\"MINI TEST\").padStart(padNum) + sc + \": \"+ isEnginePretty + \"<br><br>\"\r\n\tdomresult.innerHTML = strAlert + strM + strF + display.join(\"<br>\")\r\n}\r\n\r\nfunction rec(type, name, group = 98, expected) {\r\n\taList.push(name)\r\n\ttry {\r\n\t\tif (expected !== undefined) {\r\n\t\t\tif (type !== \"undefined\" && type !== expected) {\r\n\t\t\t\tname = sb + name +\" [\"+ expected +\"]\"+ sc\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// missing typeof parameter\r\n\t\t\taType.push(name)\r\n\t\t}\r\n\t\toTypes[group][type].push(name)\r\n\t} catch(e) {\r\n\t\taErr.push(name)\r\n\t\toTypes[98][type].push(name)\r\n\t}\r\n}\r\n\r\nfunction reset() {\r\n\taList = []\r\n\taErr = []\r\n\taType = []\r\n\tdomresult.innerHTML = \"\"\r\n\toTypes = {}\r\n\tlet groups = [10,11,12,14,50,52,54,98,99]\r\n\tlet types = [\"bigint\",\"boolean\",\"function\",\"number\",\"object\",\"string\",\"symbol\",\"undefined\",]\r\n\tgroups.forEach(function(group) {\r\n\t\toTypes[group] = {}\r\n\t\ttypes.forEach(function(type) {\r\n\t\t\toTypes[group][type] = []\r\n\t\t})\r\n\t})\r\n}\r\n\r\nfunction rerun() {\r\n\treset()\r\n\t// delay so user sees changes\r\n\tsetTimeout(function() {\r\n\t\trun()\r\n\t}, 170)\r\n}\r\n\r\nfunction run() {\r\n\ttry {\r\n\t\ttstart = performance.now()\r\n\t\tlet code\r\n\r\n\t\t/* LEVEL ONE */\r\n\t\t// undetermined: status confirmed in EOL comments\r\n\t\tcode = 98\r\n\t\t//rec(typeof keys, \"keys\", code) // NFI where I got this from\r\n\r\n\t\t// blink only\r\n\t\tcode = 10\r\n\t\trec(typeof Keyboard, \"Keyboard\", code, \"function\")\r\n\t\trec(typeof KeyboardLayoutMap, \"KeyboardLayoutMap\", code, \"function\")\r\n\t\trec(typeof PERSISTENT, \"PERSISTENT\", code, \"number\")\r\n\t\trec(typeof TEMPORARY, \"TEMPORARY\", code, \"number\")\r\n\t\trec(typeof chrome, \"chrome\", code, \"object\") // edgeHTML\r\n\t\trec(typeof onappinstalled, \"onappinstalled\", code, \"object\")\r\n\t\trec(typeof onbeforeinstallprompt, \"onbeforeinstallprompt\", code, \"object\")\r\n\t\trec(typeof openDatabase, \"openDatabase\", code, \"function\")\r\n\t\trec(typeof webkitRequestFileSystem, \"webkitRequestFileSystem\", code, \"function\")\r\n\t\trec(typeof webkitResolveLocalFileSystemURL, \"webkitResolveLocalFileSystemURL\", code, \"function\")\r\n\t\t\t// android\r\n\t\trec(typeof getDigitalGoodsService, \"getDigitalGoodsService\", code, \"function\")\r\n\t\t\t// not in Brave (or android)\r\n\t\trec(typeof showDirectoryPicker, \"showDirectoryPicker\", code, \"function\")\r\n\t\trec(typeof showOpenFilePicker, \"showOpenFilePicker\", code, \"function\")\r\n\t\trec(typeof showSaveFilePicker, \"showSaveFilePicker\", code, \"function\")\r\n\t\t\t// newish: not in v90\r\n\t\trec(typeof getScreenDetails, \"getScreenDetails\", code, \"function\")\r\n\t\trec(typeof launchQueue, \"launchQueue\", code, \"object\") // + not in Brave (or android)\r\n\t\trec(typeof onbeforexrselect, \"onbeforexrselect\", code, \"object\")\r\n\t\trec(typeof queryLocalFonts, \"queryLocalFonts\", code, \"function\")\r\n\t\trec(typeof sharedStorage, \"sharedStorage\", code, \"object\")\r\n\t\t// newish: since last check; testing chrome 119\r\n\t\trec(typeof fence, \"fence\", code, \"object\")\r\n\t\trec(typeof credentialless, \"credentialless\", code, \"boolean\")\r\n\t\t\t// other\r\n\t\trec(typeof opr, \"opr\", code, \"object\") // OPERA (desktop at least)\r\n\t\trec(typeof webkitStorageInfo, \"webkitStorageInfo\", code, \"object\") // to-be-deprecated see console\r\n\t\t// stuff showing up in scrape > new\r\n\t\trec(typeof fetchLater, \"fetchLater\", code, \"function\")\r\n\t\trec(typeof onpagereveal, \"onpagereveal\", code, \"object\")\r\n\t\trec(typeof onpageswap, \"onpageswap\", code, \"object\")\r\n\t\trec(typeof onscrollsnapchange, \"onscrollsnapchange\", code, \"object\")\r\n\t\trec(typeof onscrollsnapchanging, \"onscrollsnapchanging\", code, \"object\")\r\n\t\trec(typeof viewport, \"viewport\", code, \"object\")\r\n\t\trec(typeof when, \"when\", code, \"function\")\r\n\r\n\t\t// edgeHTML only\r\n\t\tcode = 11\r\n\t\t/* notes: has\r\n\t\t\tblink only: chrome, offscreenBuffering\r\n\t\t\tgecko only: ondevicelight, onvrdisplayactivate, onvrdisplayconnect, onvrdisplaydeactivate, onvrdisplaydisconnect, onvrdisplaypresentchange\r\n\t\t\twebkit only: browser, getMatchedCSSRules, webkitConvertPointFromNodeToPage, webkitConvertPointFromPageToNode\r\n\t\t\talso pairs are a mess\r\n\t\t*/\r\n\t\trec(typeof clearImmediate, \"clearImmediate\", code, \"function\")\r\n\t\trec(typeof msWriteProfilerMark, \"msWriteProfilerMark\", code, \"function\")\r\n\t\trec(typeof oncompassneedscalibration, \"oncompassneedscalibration\", code, \"object\")\r\n\t\trec(typeof onmsgesturechange, \"onmsgesturechange\", code, \"object\")\r\n\t\trec(typeof onmsgesturedoubletap, \"onmsgesturedoubletap\", code, \"object\")\r\n\t\trec(typeof onmsgestureend, \"onmsgestureend\", code, \"object\")\r\n\t\trec(typeof onmsgesturehold, \"onmsgesturehold\", code, \"object\")\r\n\t\trec(typeof onmsgesturestart, \"onmsgesturestart\", code, \"object\")\r\n\t\trec(typeof onmsgesturetap, \"onmsgesturetap\", code, \"object\")\r\n\t\trec(typeof onmsinertiastart, \"onmsinertiastart\", code, \"object\")\r\n\t\trec(typeof onreadystatechange, \"onreadystatechange\", code, \"object\")\r\n\t\trec(typeof onvrdisplayblur, \"onvrdisplayblur\", code, \"object\")\r\n\t\trec(typeof onvrdisplayfocus, \"onvrdisplayfocus\", code, \"object\")\r\n\t\trec(typeof onvrdisplaypointerrestricted, \"onvrdisplaypointerrestricted\", code, \"object\")\r\n\t\trec(typeof onvrdisplaypointerunrestricted, \"onvrdisplaypointerunrestricted\", code, \"object\")\r\n\t\trec(typeof setImmediate, \"setImmediate\", code, \"function\")\r\n\r\n\t\t// gecko only\r\n\t\tcode = 12\r\n\t\t\t// stable FF52+\r\n\t\trec(typeof KeyEvent, \"KeyEvent\", code, \"function\")\r\n\t\trec(typeof dump, \"dump\", code, \"function\")\r\n\t\trec(typeof fullScreen, \"fullScreen\", code, \"boolean\")\r\n\t\trec(typeof getDefaultComputedStyle, \"getDefaultComputedStyle\", code, \"function\")\r\n\t\trec(typeof mozInnerScreenX, \"mozInnerScreenX\", code, \"number\")\r\n\t\trec(typeof mozInnerScreenY, \"mozInnerScreenY\", code, \"number\")\r\n\t\trec(typeof onmozfullscreenchange, \"onmozfullscreenchange\", code, \"object\")\r\n\t\trec(typeof onmozfullscreenerror, \"onmozfullscreenerror\", code, \"object\")\r\n\t\trec(typeof scrollByLines, \"scrollByLines\", code, \"function\")\r\n\t\trec(typeof scrollByPages, \"scrollByPages\", code, \"function\")\r\n\t\trec(typeof scrollMaxX, \"scrollMaxX\", code, \"number\")\r\n\t\trec(typeof scrollMaxY, \"scrollMaxY\", code, \"number\")\r\n\t\trec(typeof setResizable, \"setResizable\", code, \"function\")\r\n\t\trec(typeof updateCommands, \"updateCommands\", code, \"function\")\r\n\t\t\t// deprecated\r\n\t\trec(typeof mozPaintCount, \"mozPaintCount\", code, \"number\") // removed FF72\r\n\t\trec(typeof onshow, \"onshow\", code, \"object\") // removed FF85 (+ not goanna or Waterfox Classic)\r\n\t\trec(typeof ondevicelight, \"ondevicelight\", code, \"object\") // removed FF89\r\n\t\trec(typeof ondeviceproximity, \"ondeviceproximity\", code, \"object\") // removed FF89\r\n\t\trec(typeof onuserproximity, \"onuserproximity\", code, \"object\") // removed FF89\r\n\t\trec(typeof content, \"content\", code, \"object\") // removed FF101\r\n\t\trec(typeof sidebar, \"sidebar\", code, \"object\") // removed FF102\r\n\t\trec(typeof onloadend, \"onloadend\", code, \"object\") // removed FF107 1574487\r\n\t\trec(typeof InstallTrigger, \"InstallTrigger\", code, \"object\") // to-be-deprecated (+prefs)\r\n\t\trec(typeof onabsolutedeviceorientation, \"onabsolutedeviceorientation\", code, \"object\") // removed FF110 1689631\r\n\t\trec(typeof sizeToContent, \"sizeToContent\", code, \"function\") // removed nightly FF117+ 1832733 / 1600400\r\n\r\n\t\t\t// prefs\r\n\t\trec(typeof onvrdisplayactivate, \"onvrdisplayactivate\", code, \"object\") // dom.vr.enabled (+ not in FF52)\r\n\t\trec(typeof onvrdisplayconnect, \"onvrdisplayconnect\", code, \"object\")\r\n\t\trec(typeof onvrdisplaydeactivate, \"onvrdisplaydeactivate\", code, \"object\") // (+ not in FF52)\r\n\t\trec(typeof onvrdisplaydisconnect, \"onvrdisplaydisconnect\", code, \"object\")\r\n\t\trec(typeof onvrdisplaypresentchange, \"onvrdisplaypresentchange\", code, \"object\")\r\n\t\trec(typeof u2f, \"u2f\", code, \"object\") // security.webauth.u2f | pref removed in FF114\r\n\r\n\t\t// webkit only\r\n\t\tcode = 14\r\n\t\trec(typeof browser, \"browser\", code, \"object\")\r\n\t\trec(typeof getMatchedCSSRules, \"getMatchedCSSRules\", code, \"function\")\r\n\t\trec(typeof safari, \"safari\", code, \"object\")\r\n\t\trec(typeof showModalDialog, \"showModalDialog\", code, \"function\")\r\n\t\trec(typeof webkitCancelRequestAnimationFrame, \"webkitCancelRequestAnimationFrame\", code, \"function\")\r\n\t\trec(typeof webkitConvertPointFromNodeToPage, \"webkitConvertPointFromNodeToPage\", code, \"function\")\r\n\t\trec(typeof webkitConvertPointFromPageToNode, \"webkitConvertPointFromPageToNode\", code, \"function\")\r\n\t\trec(typeof webkitIndexedDB, \"webkitIndexedDB\", code, \"object\")\r\n\r\n\t\t// blink + gecko (not seen in webkit)\r\n\t\tcode = 50\r\n\t\trec(typeof cancelIdleCallback, \"cancelIdleCallback\", code, \"function\")\r\n\t\trec(typeof external, \"external\", code, \"object\")\r\n\t\trec(typeof onbeforematch, \"onbeforematch\", code, \"object\") // FF139+ 1761043\r\n\t\trec(typeof oncommand, \"oncommand\", code, \"object\") // FF144+\r\n\t\trec(typeof ondeviceorientationabsolute, \"ondeviceorientationabsolute\", code, \"object\") // FF110+ 1689631\r\n\t\trec(typeof ondragexit, \"ondragexit\", code, \"object\") // touch\r\n\t\trec(typeof onorientationchange, \"onorientationchange\", code, \"object\") // mobile/tablet\r\n\t\trec(typeof navigation, \"navigation\", code, \"object\") // // FF146 nightly 1979288\r\n\t\trec(typeof onscrollend, \"onscrollend\", code, \"object\")\r\n\t\trec(typeof ontouchcancel, \"ontouchcancel\", code, \"object\")\r\n\t\trec(typeof ontouchend, \"ontouchend\", code, \"object\")\r\n\t\trec(typeof ontouchmove, \"ontouchmove\", code, \"object\")\r\n\t\trec(typeof ontouchstart, \"ontouchstart\", code, \"object\")\r\n\t\trec(typeof orientation, \"orientation\", code, \"number\")\r\n\t\trec(typeof requestIdleCallback, \"requestIdleCallback\", code, \"function\")\r\n\t\trec(typeof scheduler, \"scheduler\", code, \"object\")\r\n\t\trec(typeof trustedTypes, \"trustedTypes\", code, \"object\") // FF145 nightly 1955251 \r\n\t\trec(typeof oncontextlost, \"oncontextlost\", code, \"object\")\r\n\t\trec(typeof oncontextrestored, \"oncontextrestored\", code, \"object\")\r\n\t\trec(typeof originAgentCluster, \"originAgentCluster\", code, \"boolean\") // FF138+ 1665474\r\n\t\trec(typeof onpointerrawupdate, \"onpointerrawupdate\", code, \"object\") // FF140+ 1550462\r\n\t\trec(typeof documentPictureInPicture, \"documentPictureInPicture\", code, \"object\") // FF148n 1858562\r\n\r\n\t\t// blink + webkit (not seen in gecko)\r\n\t\tcode = 52\r\n\t\trec(typeof defaultstatus, \"defaultstatus\", code, \"string\")\r\n\t\trec(typeof defaultStatus, \"defaultStatus\", code, \"string\")\r\n\t\trec(typeof offscreenBuffering, \"offscreenBuffering\", code, \"boolean\") // edgeHTML\r\n\t\trec(typeof onmousewheel, \"onmousewheel\", code, \"object\")\r\n\t\trec(typeof onsearch, \"onsearch\", code, \"object\")\r\n\t\trec(typeof styleMedia, \"styleMedia\", code, \"object\")\r\n\t\trec(typeof webkitCancelAnimationFrame, \"webkitCancelAnimationFrame\", code, \"function\")\r\n\t\trec(typeof webkitRequestAnimationFrame, \"webkitRequestAnimationFrame\", code, \"function\")\r\n\r\n\t\t// gecko + webkit (not seen in blink)\r\n\t\tcode = 54\r\n\t\trec(typeof onanimationcancel, \"onanimationcancel\", code, \"object\")\r\n\t\trec(typeof oncopy, \"oncopy\", code, \"object\") // FF110+\r\n\t\trec(typeof oncut, \"oncut\", code, \"object\") // FF110+\r\n\t\trec(typeof ongamepadconnected, \"ongamepadconnected\", code, \"object\") // FF89+\r\n\t\trec(typeof ongamepaddisconnected, \"ongamepaddisconnected\", code, \"object\") // FF89+\r\n\t\trec(typeof onpaste, \"onpaste\", code, \"object\") // FF110+\r\n\r\n\t\t// I: IGNORE: confirmed common in blink, gecko, webkit\r\n\t\tcode = 99\r\n\t\trec(typeof addEventListener, \"addEventListener\", code, \"function\")\r\n\t\trec(typeof alert, \"alert\", code, \"function\")\r\n\t\trec(typeof applicationCache, \"applicationCache\", code, \"object\")\r\n\t\t\t// ^ gecko confirmed: pref-to-be-deprecated (appCache itself is deprecated: the API does nothing)\r\n\t\t\t// ^ ignore: appCache would have been in other older engine(s) releases\r\n\t\trec(typeof atob, \"atob\", code, \"function\")\r\n\t\trec(typeof blur, \"blur\", code, \"function\")\r\n\t\trec(typeof btoa, \"btoa\", code, \"function\")\r\n\t\trec(typeof caches, \"caches\", code, \"object\")\r\n\t\trec(typeof cancelAnimationFrame, \"cancelAnimationFrame\", code, \"function\")\r\n\t\trec(typeof captureEvents, \"captureEvents\", code, \"function\")\r\n\t\trec(typeof clearInterval, \"clearInterval\", code, \"function\")\r\n\t\trec(typeof clearTimeout, \"clearTimeout\", code, \"function\")\r\n\t\trec(typeof clientInformation, \"clientInformation\", code, \"object\")\r\n\t\trec(typeof close, \"close\", code, \"function\")\r\n\t\trec(typeof closed, \"closed\", code, \"boolean\")\r\n\t\trec(typeof confirm, \"confirm\", code, \"function\")\r\n\t\trec(typeof cookieStore, \"cookieStore\", code, \"object\") // FF132 nightly 1918643\r\n\t\trec(typeof createImageBitmap, \"createImageBitmap\", code, \"function\")\r\n\t\trec(typeof crossOriginIsolated, \"crossOriginIsolated\", code, \"boolean\")\r\n\t\trec(typeof crypto, \"crypto\", code, \"object\")\r\n\t\trec(typeof customElements, \"customElements\", code, \"object\")\r\n\t\trec(typeof devicePixelRatio, \"devicePixelRatio\", code, \"number\")\r\n\t\trec(typeof dispatchEvent, \"dispatchEvent\", code, \"function\")\r\n\t\trec(typeof document, \"document\", code, \"object\")\r\n\t\trec(typeof fetch, \"fetch\", code, \"function\")\r\n\t\trec(typeof find, \"find\", code, \"function\")\r\n\t\trec(typeof focus, \"focus\", code, \"function\")\r\n\t\trec(typeof frameElement, \"frameElement\", code, \"object\")\r\n\t\trec(typeof frames, \"frames\", code, \"object\")\r\n\t\trec(typeof getComputedStyle, \"getComputedStyle\", code, \"function\")\r\n\t\trec(typeof getSelection, \"getSelection\", code, \"function\")\r\n\t\trec(typeof history, \"history\", code, \"object\")\r\n\t\trec(typeof indexedDB, \"indexedDB\", code, \"object\")\r\n\t\trec(typeof innerHeight, \"innerHeight\", code, \"number\")\r\n\t\trec(typeof innerWidth, \"innerWidth\", code, \"number\")\r\n\t\trec(typeof isSecureContext, \"isSecureContext\", code, \"boolean\")\r\n\t\trec(typeof KeyboardEvent, \"KeyboardEvent\", code, \"function\")\r\n\t\trec(typeof KeyframeEffect, \"KeyframeEffect\", code, \"function\")\r\n\t\trec(typeof length, \"length\", code, \"number\")\r\n\t\trec(typeof localStorage, \"localStorage\", code, \"object\")\r\n\t\trec(typeof location, \"location\", code, \"object\")\r\n\t\trec(typeof locationbar, \"locationbar\", code, \"object\")\r\n\t\trec(typeof matchMedia, \"matchMedia\", code, \"function\")\r\n\t\trec(typeof menubar, \"menubar\", code, \"object\")\r\n\t\trec(typeof moveBy, \"moveBy\", code, \"function\")\r\n\t\trec(typeof moveTo, \"moveTo\", code, \"function\")\r\n\t\trec(typeof name, \"name\", code, \"string\")\r\n\t\trec(typeof navigator, \"navigator\", code, \"object\")\r\n\t\trec(typeof onabort, \"onabort\", code, \"object\")\r\n\t\trec(typeof onafterprint, \"onafterprint\", code, \"object\")\r\n\t\trec(typeof onanimationend, \"onanimationend\", code, \"object\")\r\n\t\trec(typeof onanimationiteration, \"onanimationiteration\", code, \"object\")\r\n\t\trec(typeof onanimationstart, \"onanimationstart\", code, \"object\")\r\n\t\trec(typeof onauxclick, \"onauxclick\", code, \"object\")\r\n\t\trec(typeof onbeforeinput, \"onbeforeinput\", code, \"object\")\r\n\t\trec(typeof onbeforeprint, \"onbeforeprint\", code, \"object\")\r\n\t\trec(typeof onbeforetoggle, \"onbeforetoggle\", code, \"object\") // FF122+ 1867326 (and 1823359)\r\n\t\trec(typeof onbeforeunload, \"onbeforeunload\", code, \"object\")\r\n\t\trec(typeof onblur, \"onblur\", code, \"object\")\r\n\t\trec(typeof oncancel, \"oncancel\", code, \"object\")\r\n\t\trec(typeof oncanplay, \"oncanplay\", code, \"object\")\r\n\t\trec(typeof oncanplaythrough, \"oncanplaythrough\", code, \"object\")\r\n\t\trec(typeof onchange, \"onchange\", code, \"object\")\r\n\t\trec(typeof onclick, \"onclick\", code, \"object\")\r\n\t\trec(typeof onclose, \"onclose\", code, \"object\")\r\n\t\trec(typeof oncontentvisibilityautostatechange, \"oncontentvisibilityautostatechange\", code, \"object\")\r\n\t\trec(typeof oncontextmenu, \"oncontextmenu\", code, \"object\")\r\n\t\trec(typeof oncuechange, \"oncuechange\", code, \"object\")\r\n\t\trec(typeof ondblclick, \"ondblclick\", code, \"object\")\r\n\t\trec(typeof ondevicemotion, \"ondevicemotion\", code, \"object\")\r\n\t\trec(typeof ondeviceorientation, \"ondeviceorientation\", code, \"object\")\r\n\t\trec(typeof ondrag, \"ondrag\", code, \"object\")\r\n\t\trec(typeof ondragend, \"ondragend\", code, \"object\")\r\n\t\trec(typeof ondragenter, \"ondragenter\", code, \"object\")\r\n\t\trec(typeof ondragleave, \"ondragleave\", code, \"object\")\r\n\t\trec(typeof ondragover, \"ondragover\", code, \"object\")\r\n\t\trec(typeof ondragstart, \"ondragstart\", code, \"object\")\r\n\t\trec(typeof ondrop, \"ondrop\", code, \"object\")\r\n\t\trec(typeof ondurationchange, \"ondurationchange\", code, \"object\")\r\n\t\trec(typeof onemptied, \"onemptied\", code, \"object\")\r\n\t\trec(typeof onended, \"onended\", code, \"object\")\r\n\t\trec(typeof onerror, \"onerror\", code, \"object\")\r\n\t\trec(typeof onfocus, \"onfocus\", code, \"object\")\r\n\t\trec(typeof onformdata, \"onformdata\", code, \"object\")\r\n\t\trec(typeof ongotpointercapture, \"ongotpointercapture\", code, \"object\")\r\n\t\trec(typeof onhashchange, \"onhashchange\", code, \"object\")\r\n\t\trec(typeof oninput, \"oninput\", code, \"object\")\r\n\t\trec(typeof oninvalid, \"oninvalid\", code, \"object\")\r\n\t\trec(typeof onkeydown, \"onkeydown\", code, \"object\")\r\n\t\trec(typeof onkeypress, \"onkeypress\", code, \"object\")\r\n\t\trec(typeof onkeyup, \"onkeyup\", code, \"object\")\r\n\t\trec(typeof onlanguagechange, \"onlanguagechange\", code, \"object\")\r\n\t\trec(typeof onload, \"onload\", code, \"object\")\r\n\t\trec(typeof onloadeddata, \"onloadeddata\", code, \"object\")\r\n\t\trec(typeof onloadedmetadata, \"onloadedmetadata\", code, \"object\")\r\n\t\trec(typeof onloadstart, \"onloadstart\", code, \"object\")\r\n\t\trec(typeof onlostpointercapture, \"onlostpointercapture\", code, \"object\")\r\n\t\trec(typeof onmessage, \"onmessage\", code, \"object\")\r\n\t\trec(typeof onmessageerror, \"onmessageerror\", code, \"object\")\r\n\t\trec(typeof onmousedown, \"onmousedown\", code, \"object\")\r\n\t\trec(typeof onmouseenter, \"onmouseenter\", code, \"object\")\r\n\t\trec(typeof onmouseleave, \"onmouseleave\", code, \"object\")\r\n\t\trec(typeof onmousemove, \"onmousemove\", code, \"object\")\r\n\t\trec(typeof onmouseout, \"onmouseout\", code, \"object\")\r\n\t\trec(typeof onmouseover, \"onmouseover\", code, \"object\")\r\n\t\trec(typeof onmouseup, \"onmouseup\", code, \"object\")\r\n\t\trec(typeof onoffline, \"onoffline\", code, \"object\")\r\n\t\trec(typeof ononline, \"ononline\", code, \"object\")\r\n\t\trec(typeof onpagehide, \"onpagehide\", code, \"object\")\r\n\t\trec(typeof onpageshow, \"onpageshow\", code, \"object\")\r\n\t\trec(typeof onpause, \"onpause\", code, \"object\")\r\n\t\trec(typeof onplay, \"onplay\", code, \"object\")\r\n\t\trec(typeof onplaying, \"onplaying\", code, \"object\")\r\n\t\trec(typeof onpointercancel, \"onpointercancel\", code, \"object\")\r\n\t\trec(typeof onpointerdown, \"onpointerdown\", code, \"object\")\r\n\t\trec(typeof onpointerenter, \"onpointerenter\", code, \"object\")\r\n\t\trec(typeof onpointerleave, \"onpointerleave\", code, \"object\")\r\n\t\trec(typeof onpointermove, \"onpointermove\", code, \"object\")\r\n\t\trec(typeof onpointerout, \"onpointerout\", code, \"object\")\r\n\t\trec(typeof onpointerover, \"onpointerover\", code, \"object\")\r\n\t\trec(typeof onpointerup, \"onpointerup\", code, \"object\")\r\n\t\trec(typeof onpopstate, \"onpopstate\", code, \"object\")\r\n\t\trec(typeof onprogress, \"onprogress\", code, \"object\")\r\n\t\trec(typeof onratechange, \"onratechange\", code, \"object\")\r\n\t\trec(typeof onrejectionhandled, \"onrejectionhandled\", code, \"object\")\r\n\t\trec(typeof onreset, \"onreset\", code, \"object\")\r\n\t\trec(typeof onresize, \"onresize\", code, \"object\")\r\n\t\trec(typeof onscroll, \"onscroll\", code, \"object\")\r\n\t\trec(typeof onsecuritypolicyviolation, \"onsecuritypolicyviolation\", code, \"object\")\r\n\t\trec(typeof onseeked, \"onseeked\", code, \"object\")\r\n\t\trec(typeof onseeking, \"onseeking\", code, \"object\")\r\n\t\trec(typeof onselect, \"onselect\", code, \"object\")\r\n\t\trec(typeof onselectionchange, \"onselectionchange\", code, \"object\")\r\n\t\trec(typeof onselectstart, \"onselectstart\", code, \"object\")\r\n\t\trec(typeof onslotchange, \"onslotchange\", code, \"object\")\r\n\t\trec(typeof onstalled, \"onstalled\", code, \"object\")\r\n\t\trec(typeof onstorage, \"onstorage\", code, \"object\")\r\n\t\trec(typeof onsubmit, \"onsubmit\", code, \"object\")\r\n\t\trec(typeof onsuspend, \"onsuspend\", code, \"object\")\r\n\t\trec(typeof ontimeupdate, \"ontimeupdate\", code, \"object\")\r\n\t\trec(typeof ontoggle, \"ontoggle\", code, \"object\")\r\n\t\trec(typeof ontransitioncancel, \"ontransitioncancel\", code, \"object\")\r\n\t\trec(typeof ontransitionend, \"ontransitionend\", code, \"object\")\r\n\t\trec(typeof ontransitionrun, \"ontransitionrun\", code, \"object\")\r\n\t\trec(typeof ontransitionstart, \"ontransitionstart\", code, \"object\")\r\n\t\trec(typeof onunhandledrejection, \"onunhandledrejection\", code, \"object\")\r\n\t\trec(typeof onunload, \"onunload\", code, \"object\")\r\n\t\trec(typeof onvolumechange, \"onvolumechange\", code, \"object\")\r\n\t\trec(typeof onwaiting, \"onwaiting\", code, \"object\")\r\n\t\trec(typeof onwebkitanimationend, \"onwebkitanimationend\", code, \"object\")\r\n\t\trec(typeof onwebkitanimationiteration, \"onwebkitanimationiteration\", code, \"object\")\r\n\t\trec(typeof onwebkitanimationstart, \"onwebkitanimationstart\", code, \"object\")\r\n\t\trec(typeof onwebkittransitionend, \"onwebkittransitionend\", code, \"object\")\r\n\t\trec(typeof onwheel, \"onwheel\", code, \"object\")\r\n\t\trec(typeof open, \"open\", code, \"function\")\r\n\t\trec(typeof opener, \"opener\", code, \"object\")\r\n\t\trec(typeof origin, \"origin\", code, \"string\")\r\n\t\trec(typeof outerHeight, \"outerHeight\", code, \"number\")\r\n\t\trec(typeof outerWidth, \"outerWidth\", code, \"number\")\r\n\t\trec(typeof pageXOffset, \"pageXOffset\", code, \"number\")\r\n\t\trec(typeof pageYOffset, \"pageYOffset\", code, \"number\")\r\n\t\trec(typeof parent, \"parent\", code, \"object\")\r\n\t\trec(typeof performance, \"performance\", code, \"object\")\r\n\t\trec(typeof personalbar, \"personalbar\", code, \"object\")\r\n\t\trec(typeof postMessage, \"postMessage\", code, \"function\")\r\n\t\trec(typeof print, \"print\", code, \"function\")\r\n\t\trec(typeof prompt, \"prompt\", code, \"function\")\r\n\t\trec(typeof queueMicrotask, \"queueMicrotask\", code, \"function\")\r\n\t\trec(typeof releaseEvents, \"releaseEvents\", code, \"function\")\r\n\t\trec(typeof removeEventListener, \"removeEventListener\", code, \"function\")\r\n\t\trec(typeof reportError, \"reportError\", code, \"function\")\r\n\t\trec(typeof requestAnimationFrame, \"requestAnimationFrame\", code, \"function\")\r\n\t\trec(typeof resizeBy, \"resizeBy\", code, \"function\")\r\n\t\trec(typeof resizeTo, \"resizeTo\", code, \"function\")\r\n\t\trec(typeof screen, \"screen\", code, \"object\")\r\n\t\trec(typeof screenLeft, \"screenLeft\", code, \"number\")\r\n\t\trec(typeof screenTop, \"screenTop\", code, \"number\")\r\n\t\trec(typeof screenX, \"screenX\", code, \"number\")\r\n\t\trec(typeof screenY, \"screenY\", code, \"number\")\r\n\t\trec(typeof scroll, \"scroll\", code, \"function\")\r\n\t\trec(typeof scrollBy, \"scrollBy\", code, \"function\")\r\n\t\trec(typeof scrollTo, \"scrollTo\", code, \"function\")\r\n\t\trec(typeof scrollX, \"scrollX\", code, \"number\")\r\n\t\trec(typeof scrollY, \"scrollY\", code, \"number\")\r\n\t\trec(typeof scrollbars, \"scrollbars\", code, \"object\")\r\n\t\trec(typeof self, \"self\", code, \"object\")\r\n\t\trec(typeof sessionStorage, \"sessionStorage\", code, \"object\")\r\n\t\trec(typeof setInterval, \"setInterval\", code, \"function\")\r\n\t\trec(typeof setTimeout, \"setTimeout\", code, \"function\")\r\n\t\trec(typeof speechSynthesis, \"speechSynthesis\", code, \"object\")\r\n\t\trec(typeof status, \"status\", code, \"string\")\r\n\t\trec(typeof statusbar, \"statusbar\", code, \"object\")\r\n\t\trec(typeof stop, \"stop\", code, \"function\")\r\n\t\trec(typeof structuredClone, \"structuredClone\", code, \"function\")\r\n\t\trec(typeof toolbar, \"toolbar\", code, \"object\")\r\n\t\trec(typeof top, \"top\", code, \"object\")\r\n\t\trec(typeof visualViewport, \"visualViewport\", code, \"object\")\r\n\t\trec(typeof window, \"window\", code, \"object\")\r\n\r\n\t\t// LEVEL 2\r\n\t\t// I don't think we need bother with level 2 for a mini isEngine\r\n\t\t/*\r\n\t\tif (\"undefined\" !== typeof caches) {\r\n\t\t\trec(typeof caches.delete, \"caches.delete\")\r\n\t\t\trec(typeof caches.has, \"caches.has\")\r\n\t\t\trec(typeof caches.keys, \"caches.keys\")\r\n\t\t\trec(typeof caches.match, \"caches.match\")\r\n\t\t\trec(typeof caches.open, \"caches.open\")\r\n\t\t}\r\n\t\t*/\r\n\t\toutput()\r\n\t} catch(e) {\r\n\t\tconsole.error(e.name, e.message)\r\n\t}\r\n}\r\n\r\nfunction scrape() {\r\n\tdomresult.innerHTML = \"<br>... running\"\r\n\r\n\taNew = []\r\n\t// delay so user sees changes\r\n\tsetTimeout(function() {\r\n\t\ttstart = performance.now()\r\n\t\tlet result = collectProps().window\r\n\r\n\t\t// cleanup\r\n\t\tdelete result.event // caused by calling event in the scrape function\r\n\t\tdelete result.dom // just in case\r\n\t\tdelete result.collectProps\r\n\t\tdelete result.compare\r\n\t\tdelete result.copyclip\r\n\t\tdelete result.get_engineItem\r\n\t\tdelete result.get_engineProp\r\n\t\tdelete result.get_miniEngine\r\n\t\tdelete result.json_highlight\r\n\t\tdelete result.json_stringify\r\n\t\tdelete result.logConsole\r\n\t\tdelete result.mini\r\n\t\tdelete result.output\r\n\t\tdelete result.rec\r\n\t\tdelete result.rerun\r\n\t\tdelete result.reset\r\n\t\tdelete result.run\r\n\t\tdelete result.scrape\r\n\t\t//console.log(result)\r\n\r\n\t\t// sanitize localStorage\r\n\t\tlet aDelLS = []\r\n\t\tlet sessionGood = [\"clear\",\"getItem\",\"key\",\"length\",\"removeItem\",\"setItem\"]\r\n\t\ttry {\r\n\t\t\tfor (const ls of Object.keys(result[\"localStorage\"][\"properties\"])) {\r\n\t\t\t\tif (!sessionGood.includes(ls)) {\r\n\t\t\t\t\tdelete result[\"localStorage\"][\"properties\"][ls]\r\n\t\t\t\t\taDelSL.push(ls)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\t// sanitize sessionStorage\r\n\t\tlet aDelSS = []\r\n\t\ttry {\r\n\t\t\tfor (const ss of Object.keys(result[\"sessionStorage\"][\"properties\"])) {\r\n\t\t\t\tif (!sessionGood.includes(ss)) {\r\n\t\t\t\t\tdelete result[\"sessionStorage\"][\"properties\"][ss]\r\n\t\t\t\t\taDelSS.push(ss)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\t// silently record sanitized\r\n\t\tlet strDel = \"\"\r\n\t\tif (aDelSS.length || aDelLS.length) {\r\n\t\t\tstrDel = sb +\"REMOVED: \"+ sc +\"random state variables<br><br>\"\r\n\t\t\t\t+ (aDelLS.length ? \"localStorage  : \" + aDelLS.join(\", \") +\"<br>\" : \"\")\r\n\t\t\t\t+ (aDelSS.length ? \"sessionStorage: \" + aDelSS.join(\", \") +\"<br>\" : \"\")\r\n\t\t\t\t+\"<br>\"\r\n\t\t}\r\n\r\n\t\t// hash it for easier compare\r\n\t\tlet hash = mini(result)\r\n\t\tdomperf.innerHTML = Math.round((performance.now() - tstart)) +\" ms\"\r\n\r\n\t\t// check if any first level items are not in aList\r\n\t\tlet strNew = \"\"\r\n\t\tif (aList.length) {\r\n\t\t\taNew = Object.keys(result)\r\n\t\t\taNew = aNew.filter(x => !aList.includes(x))\r\n\t\t\tif (aNew.length) {\r\n\t\t\t\tstrNew = sb +\"NEW:\"+ sc + \"<br><br>\" + aNew.join(\", \") + sc +\"<br><br>\"\r\n\t\t\t}\r\n\t\t}\r\n\t\tdomresult.innerHTML = strNew + strDel + s3 + \"HASH: \"+ sc + hash +\"<br><br>\"\r\n\t\t\t+ json_highlight(result)\r\n\r\n\t}, 100)\r\n}\r\n\r\nfunction compare() {\r\n\t// examples: just paste in scrape results\r\n\tlet sBlink = {\"all3\": [], \"blink\": [], \"blink-gecko\": [], \"blink-webkit\": []}\r\n\tlet sGecko = {\"all3\": [], \"gecko\": [], \"blink-gecko\": [], \"gecko-webkit\": []}\r\n\tlet sWebkit = {\"all3\": [], \"webkit\": [], \"blink-webkit\": [], \"gecko-webkit\": []}\r\n\r\n\tlet aBlink = Object.keys(sBlink)\r\n\tlet aGecko = Object.keys(sGecko)\r\n\tlet aWebkit = Object.keys(sWebkit)\r\n\r\n\tlet commonBG = aBlink.filter(x => aGecko.includes(x))\r\n\tlet commonBW = aBlink.filter(x => aWebkit.includes(x))\r\n\tlet commonGW = aGecko.filter(x => aWebkit.includes(x))\r\n\tlet common = commonBG.concat(commonBW)\r\n\tcommon = common.concat(commonGW)\r\n\tcommon = common.filter(function(item, position) {return common.indexOf(item) === position})\r\n\tcommon.sort()\r\n\tconsole.log(\"NOT UNIQUE: \"+ common.length +\"\\n\" + common.join(\"\\n\"))\r\n\r\n\tlet common3 = commonBG\r\n\tcommon3 = commonBG.filter(x => aWebkit.includes(x))\r\n\tcommon3 = common3.filter(function(item, position) {return common3.indexOf(item) === position})\r\n\tcommon3.sort()\r\n\tconsole.log(\"COMMON IN ALL THREE: \"+ common3.length +\"\\n\" + common3.join(\"\\n\"))\r\n\r\n\tlet BGonly = commonBG.filter(x => !common3.includes(x))\r\n\tBGonly.sort()\r\n\tconsole.log(\"COMMON IN BLINK + GECKO ONLY: \"+ BGonly.length +\"\\n\" + BGonly.join(\"\\n\"))\r\n\r\n\tlet BWonly = commonBW.filter(x => !common3.includes(x))\r\n\tBWonly.sort()\r\n\tconsole.log(\"COMMON IN BLINK + WEBKIT ONLY: \"+ BWonly.length +\"\\n\" + BWonly.join(\"\\n\"))\r\n\r\n\tlet GWonly = commonGW.filter(x => !common3.includes(x))\r\n\tGWonly.sort()\r\n\tconsole.log(\"COMMON IN GECKO + WEBKIT ONLY: \"+ GWonly.length +\"\\n\" + GWonly.join(\"\\n\"))\r\n}\r\n//compare()\r\n\r\nfunction get_miniEngine() {\r\n\tlet tstart = performance.now()\r\n\tlet oEngines = {\r\n\t\t\"blink\": [\r\n\t\t\t\"number\" === typeof TEMPORARY,\r\n\t\t\t\"number\" === typeof PERSISTENT,\r\n\t\t\t\"object\" === typeof onappinstalled,\r\n\t\t\t\"object\" === typeof onbeforeinstallprompt,\r\n\t\t\t//\"object\" === typeof onpointerrawupdate,\r\n\t\t\t//\"object\" === typeof onsearch,\r\n\t\t\t//\"boolean\" === typeof originAgentCluster,\r\n\t\t\t//\"object\" === typeof trustedTypes,\r\n\t\t\t\"function\" === typeof webkitResolveLocalFileSystemURL,\r\n\t\t],\r\n\t\t\"webkit\": [\r\n\t\t\t\"object\" === typeof browser,\r\n\t\t\t//\"function\" === typeof getMatchedCSSRules,\r\n\t\t\t\"object\" === typeof safari,\r\n\t\t\t//\"function\" === typeof showModalDialog,\r\n\t\t\t\"function\" === typeof webkitConvertPointFromNodeToPage,\r\n\t\t\t\"function\" === typeof webkitCancelRequestAnimationFrame,\r\n\t\t\t\"object\" === typeof webkitIndexedDB,\r\n\t\t],\r\n\t\t\"gecko\": [\r\n\t\t\t\"function\" === typeof dump,\r\n\t\t\t\"boolean\" === typeof fullScreen,\r\n\t\t\t\"number\" === typeof mozInnerScreenX,\r\n\t\t\t\"function\" === typeof scrollByLines,\r\n\t\t\t\"number\" === typeof scrollMaxY,\r\n\t\t\t\"function\" === typeof setResizable,\r\n\t\t\t//\"function\" === typeof sizeToContent, // removed nightly FF117+ 1832733 / 1600400\r\n\t\t\t\"function\" === typeof updateCommands,\r\n\t\t],\r\n\t\t\"edgeHTML\": [\r\n\t\t\t\"function\" === typeof clearImmediate,\r\n\t\t\t\"function\" === typeof msWriteProfilerMark,\r\n\t\t\t\"object\" === typeof oncompassneedscalibration,\r\n\t\t\t\"object\" === typeof onmsgesturechange,\r\n\t\t\t\"object\" === typeof onmsinertiastart,\r\n\t\t\t\"object\" === typeof onreadystatechange,\r\n\t\t\t//\"object\" === typeof onvrdisplayfocus,\r\n\t\t\t\"function\" === typeof setImmediate,\r\n\t\t]\r\n\t}\r\n\t// array engine matches, so subsequent results doesn't override prev\r\n\tlet aEngine = []\r\n\tfor (const engine of Object.keys(oEngines).sort()) {\r\n\t\tlet sumE = oEngines[engine].reduce((prev, current) => prev + current, 0)\r\n\t\tif (sumE > (oEngines[engine].length/2)) {aEngine.push(engine)}\r\n\t}\r\n\tif (aEngine.length == 1) {isEngine = aEngine[0]} // valid one result\r\n\t// perf\r\n\tlet tend = performance.now()\r\n\t// re-tidy vars\r\n\tif (isEngine == \"gecko\") {\r\n\t\t// check for PM28+ : fails 53\r\n\t\tif (\"function\" !== typeof CSSMozDocumentRule) {\r\n\t\t\tisEngine = \"goanna\"\r\n\t\t}\r\n\t}\r\n\t// build a pretty display\r\n\tlet displayAll = []\r\n\tfor (const engine of Object.keys(oEngines).sort()) {\r\n\t\tlet displayE = []\r\n\t\toEngines[engine].forEach(function(check) {\r\n\t\t\tdisplayE.push(check ? green_tick : red_cross)\r\n\t\t})\r\n\t\tdisplayAll.push(displayE.join(\"\"))\r\n\t}\r\n\tisEnginePretty = Math.round(tend-tstart) +\" ms |\"+ displayAll.join(\" |\")\r\n\t\t+ \" | \" + (isEngine == \"\" ? \"UNKNOWN\" : isEngine.toUpperCase())\r\n}\r\n\r\nlet isEnginePretty = \"\"\r\nlet isEngine = \"\"\r\nPromise.all([\r\n\tget_miniEngine()\r\n]).then(function(){\r\n\treset()\r\n\trun()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/fontasync.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>font async</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 580px;}\r\n\t\t#ugSpan {font-size: 22000px;}\r\n\t\t.changed {\r\n\t\t\tbackground: rgba(142, 142, 145, 0.3);\r\n\t\t}\r\n\t\t.zero {\r\n\t\t\tbackground: rgba(220, 121, 189, 0.3);\r\n\t\t}\r\n\t\t.changedzero {\r\n\t\t\tbackground: linear-gradient(rgba(142, 142, 145, 0.4), rgba(142, 142, 145, 0.2), rgba(220, 121, 189, 0.4));\r\n\t\t}\r\n\t\tspan.box {\r\n\t\t\tdisplay: inline-block;\r\n\t\t\tposition: relative;\r\n\t\t\tmargin-top: 2px;\r\n\t\t\twidth: 47px;\r\n\t\t\theight: 60px;\r\n\t\t\tborder: 1px solid grey;\r\n\t\t\tfont-size: 24px;\r\n\t\t\ttext-align: center;\r\n\t\t\tvertical-align: top;\r\n\t\t}\r\n\t\tspan.info {\r\n\t\t\tdisplay: block;\r\n\t\t\tposition: relative;\r\n\t\t\tpadding-top: 2px;\r\n\t\t\twidth: 48px;\r\n\t\t\theight: 15px;\r\n\t\t\tfont-size: 10px;\r\n\t\t\ttext-align: center;\r\n\t\t\tborder-bottom: 1px solid grey;\r\n\t\t\tfont-family: sans-serif;\r\n\t\t}\r\n\t\tdiv.glyph {\r\n\t\t\tdisplay: block;\r\n\t\t\tposition: relative;\r\n\t\t\tpadding-top: 4px;\r\n\t\t\twidth: 48px;\r\n\t\t\tfont-size: 24px;\r\n\t\t\ttext-align: center;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div id=\"element-fp\"><span id=\"glyphs-span\" style=\"font-size: 22000px;\"><span id=\"glyphs-slot\"></span></span></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"15%\"><col width=\"85%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">font async\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"locale\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"no_color\">\r\n\t\t\t\tTesting <a target=\"_blank\" class=\"blue\" href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1676966#c54\">async font fallback</a>.\r\n\t\t\t\tYou need a new browser session and no history.<br>\r\n\t\t\t\t<div class=\"mono\">DISPLAY: \r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"cursive\" onClick='rebuild()'><label> cursive </label>\r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"math\" onClick='rebuild()'><label> math </label>\r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"monospace\" onClick='rebuild()'><label> monospace </label>\r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"sans-serif\" onClick='rebuild()' checked><label> sans-serif </label>\r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"serif\" onClick='rebuild()'><label> serif </label>\r\n\t\t\t\t\t<input type=\"radio\" id=\"family\" name=\"family\" value=\"system-ui\" onClick='rebuild()'><label> system-ui </label>\r\n\t\t\t\t</div>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\r\n\t\t\t<span class=\"no_color\" id=\"glyphs\"></span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\r\n\t\t\t<br>SUMMARY: &nbsp; <span class=\"no_color c mono spaces\" id=\"results\"></span>\r\n\t\t\t<br>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\r\n\t\t\t<br>DETAILS: &nbsp; <span class=\"btn0 btnc\" onclick=\"copyclip(`details`)\">[COPY]</span>\r\n\t\t\t<br><br><span class=\"no_color c mono spaces\" id=\"details\"></span>\r\n\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n<script>\r\n'use strict';\r\n\r\nlet fntCodes = [\r\n\t// added 0x6E2F\r\n\t// sorted\r\n\t'0x007F','0x0218','0x058F','0x05C6','0x061C','0x0700','0x08E4','0x097F','0x09B3',\r\n\t'0x0B82','0x0D02','0x10A0','0x115A','0x17DD','0x1950','0x1C50','0x1CDA','0x1D790',\r\n\t'0x1E9E','0x20B0','0x20B8','0x20B9','0x20BA','0x20BD','0x20E3','0x21E4','0x23AE',\r\n\t'0x2425','0x2581','0x2619','0x2B06','0x2C7B','0x302E','0x3095','0x532D','0x6E2F',\r\n\t'0xA73D','0xA830','0xF003','0xF810','0xFBEE',\r\n\t/* ignore replacement characters: https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character\r\n\t\tthese seem to be problematic at least on windows on first use\r\n\t\t'0xFFF9','0xFFFD',\r\n\t//*/\r\n\t'0xFFFF', // tofu\r\n]\r\n\r\nlet styles = [\"cursive\",\"math\",\"monospace\",\"sans-serif\",\"serif\",\"system-ui\"]\r\nlet oData = {}, counter = 0\r\nlet div = dom['element-fp'], span = dom['glyphs-span'], slot = dom['glyphs-slot']\r\n\r\nfunction build(style) {\r\n\tlet tmpArray = []\r\n\tfntCodes.sort()\r\n\tfntCodes.forEach(function(code) {\r\n\t\tlet id = code.slice(2)\r\n\t\tlet string = \"<span class='box \" + style +\"' id='b\"+ id +\"'><span class='info'>\"+ id +\"</span><div class='glyph'><span>&#x\"+ id +\";</span></div></span>\\n\"\r\n\t\ttmpArray.push(string)\r\n\t})\r\n\t//console.log(tmpArray[0])\r\n\tdom.glyphs.innerHTML = tmpArray.join(\"\")\r\n}\r\n\r\nfunction get_locale() {\r\n\tlet locale\r\n\ttry {\r\n\t\tlocale = Intl.DateTimeFormat().resolvedOptions().locale\r\n\t} catch(e) {\r\n\t\tlocale = zErr\r\n\t}\r\n\tdom.locale.innerHTML = locale\r\n}\r\nget_locale()\r\n\r\nfunction rebuild() {\r\n\tlet style = document.querySelector('input[name=\"family\"]:checked').value\r\n\t// build so everything is clear\r\n\tbuild(style)\r\n\t// color stuff\r\n\tif (isColor) {\r\n\t\tlet data = oData.displayChanges[style]\r\n\t\tfor (const k of Object.keys(data.text)) {\r\n\t\t\tdom[\"b\"+k].classList.add(data.text[k])\r\n\t\t}\r\n\t\tdata.bg.forEach(function(id) {\r\n\t\t\tdom[id].classList.add('changed')\r\n\t\t})\r\n\t\tdata.zero.forEach(function(id) {\r\n\t\t\tlet style = (data.bg.includes(\"b\"+id)) ? \"changedzero\" : \"zero\"\r\n\t\t\tdom[\"b\"+id].classList.add(style)\r\n\t\t})\r\n\t}\r\n}\r\n\r\nfunction display(name) {\r\n\tlet data = oData[name]\r\n\tlet hash = mini(data)\r\n\tif (name == \"1data\") {name = \"1st test\"\r\n\t} else if (name == \"2data\") {name = \"2nd test\"\r\n\t} else if (name == \"sizeChanges\") {name = \"differences\"}\r\n\tdom.details.innerHTML = name +\": \"+ hash +\"<br>\"+ json_highlight(data, 130)\r\n\r\n}\r\n\r\nfunction output() {\r\n\t// analyse\r\n\toData[\"2tofu\"] = {}\r\n\toData[\"sizeChanges\"] = {}\r\n\toData[\"displayChanges\"] = {}\r\n\toData[\"groupChanges\"] = {}\r\n\tfor (const style of Object.keys(oData[\"2data\"])) {\r\n\t\toData[\"sizeChanges\"][style] = {}\r\n\t\toData[\"groupChanges\"][style] = {1:[], 2:[], 3:[], 4:[]}\r\n\t\toData[\"displayChanges\"][style] = {\r\n\t\t\t\"bg\": [], \"text\": {}, \"zero\": []\r\n\t\t}\r\n\r\n\t\tlet tofuSize = oData[\"tofuSize\"][style]\r\n\t\t// match any tofu sizes\r\n\t\tfor (const code of Object.keys(oData[\"2data\"][style])) {\r\n\t\t\t// tofu size info\r\n\t\t\tlet run2 = oData[\"2data\"][style][code]\r\n\t\t\tlet run2String = run2.join(\" x \")\r\n\t\t\tlet isTofu = run2String == tofuSize\r\n\t\t\tif (isTofu) {\r\n\t\t\t\tif (oData[\"2tofu\"][style] == undefined) {oData[\"2tofu\"][style] = []}\r\n\t\t\t\toData[\"2tofu\"][style].push(code)\r\n\t\t\t}\r\n\t\t\t// all size changes\r\n\t\t\tlet id = code.slice(2)\r\n\t\t\tlet run1 = oData[\"1data\"][style][code]\r\n\t\t\tlet run1String = run1.join(\" x \")\r\n\t\t\tlet isChanged = run1String !== run2String\r\n\t\t\tif (isChanged) {\r\n\t\t\t\tlet firstchange = \"n/a\"\r\n\t\t\t\toData[\"displayChanges\"][style][\"bg\"].push(\"b\"+id)\r\n\t\t\t\t// non-tofu items that change are not recorded\r\n\t\t\t\ttry {\r\n\t\t\t\t\tfirstchange = Math.round(oData[\"3data\"][style][code][run2String])\r\n\t\t\t\t} catch(e) {}\r\n\t\t\t\toData[\"sizeChanges\"][style][code] = [run1String, run2String, firstchange]\r\n\t\t\t}\r\n\t\t\tlet state = oData[\"1tofu\"][style].includes(code) * 1\r\n\t\t\tstate += \"\"+(isTofu) * 1\r\n\t\t\tif (state == \"00\") {\r\n\t\t\t\t// not tofu both times\r\n\t\t\t\tif (isChanged) {\r\n\t\t\t\t\toData[\"displayChanges\"][style][\"text\"][id] = 'bad'\r\n\t\t\t\t\toData[\"groupChanges\"][style][3].push(code)\r\n\t\t\t\t}\r\n\t\t\t} else if (state == \"11\") {\r\n\t\t\t\t// tofu both times\r\n\t\t\t\toData[\"displayChanges\"][style][\"text\"][id] = 'good'\r\n\t\t\t\toData[\"groupChanges\"][style][1].push(code)\r\n\t\t\t} else if (state == \"10\") {\r\n\t\t\t\t// was tofu then fellback\r\n\t\t\t\toData[\"displayChanges\"][style][\"text\"][id] = 's12'\r\n\t\t\t\toData[\"groupChanges\"][style][2].push(code)\r\n\t\t\t} else {\r\n\t\t\t\t// last state can only be 01\r\n\t\t\t\t// wasn't tofu but then was\r\n\t\t\t\t\t// could be possible that a fallback legit font == tofu size\r\n\t\t\t\toData[\"displayChanges\"][style][\"text\"][id] = 's3'\r\n\t\t\t\toData[\"groupChanges\"][style][4].push(code)\r\n\t\t\t}\r\n\t\t\t// run2: zero width or height\r\n\t\t\tif (run2[0] === 0 || run2[1] === 0) {\r\n\t\t\t\toData[\"displayChanges\"][style][\"zero\"].push(id)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/* test: mixed gradients: changed AND final zero-width\r\n\toData[\"displayChanges\"][\"sans-serif\"][\"bg\"].push('b097F')\r\n\toData[\"displayChanges\"][\"sans-serif\"][\"zero\"].push('097F')\r\n\t//*/\r\n\tconsole.log(oData)\r\n\tisColor = true\r\n\trebuild()\r\n\r\n\t// output data\r\n\t\t// clickable links\r\n\t\t// diffs including _when_ they changed\r\n\tlet display = \"\"\r\n\tlet hash1 = mini(oData[\"1data\"])\r\n\tlet hash2 = mini(oData[\"2data\"])\r\n\r\n// \" <span class='btn4 btnc' onclick='display(`\" + item + \"`)'>[\" + array.length +\"]</span>\"\r\n\tif (hash1 == hash2) {\r\n\t\tdisplay =\"both tests: <span class='btn12 btnc' onclick='display(`1data`)'>\" + hash1 +\"</span>\"\r\n\t} else {\r\n\t\tdisplay = \"1st test: <span class='btn12 btnc' onclick='display(`1data`)'>\" + hash1 +\"</span> | \"\r\n\t\t\t+\"2nd test: <span class='btn12 btnc' onclick='display(`2data`)'>\" + hash2 +\"</span> | \"\r\n\t\t\t+\"<span class='btn12 btnc' onclick='display(`sizeChanges`)'>diffs</span>\"\r\n\t}\r\n\tdom.results.innerHTML = display\r\n}\r\n\r\nconst run = (type) => new Promise(resolve => {\r\n\t// just grab data\r\n\ttry {\r\n\t\tlet aCodes = fntCodes\r\n\t\tlet key\r\n\t\tif (type == \"3data\") {\r\n\t\t\tcounter++\r\n\t\t}\r\n\t\tif (oData[type] == undefined) {oData[type] = {}}\r\n\r\n\t\tstyles.forEach(function(style) {\r\n\t\t\tslot.style.fontFamily = style\r\n\t\t\tif (oData[type][style] == undefined) {oData[type][style] = {}}\r\n\t\t\tif (type == \"3data\") {\r\n\t\t\t\taCodes = oData[\"1tofu\"][style]\r\n\t\t\t\t//oData[type][style][key] = {}\r\n\t\t\t}\r\n\t\t\taCodes.forEach(function(code) {\r\n\t\t\t\tlet item = String.fromCodePoint(code)\r\n\t\t\t\tslot.textContent = item\r\n\t\t\t\t// use clientrect for precision\r\n\t\t\t\tlet width = span.getBoundingClientRect().width,\r\n\t\t\t\t\theight = div.getBoundingClientRect().height\r\n\t\t\t\tif (type == \"3data\") {\r\n\t\t\t\t\tif (oData[type][style][code] == undefined) {oData[type][style][code] = {}}\r\n\t\t\t\t\tlet key = width +\" x \"+ height\r\n\t\t\t\t\tif (oData[type][style][code][key] == undefined) {\r\n\t\t\t\t\t\toData[type][style][code][key] = performance.now()\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[type][style][code] = [width, height]\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t})\r\n\t\tslot.textContent = \"\"\r\n\t\tif (type == \"2data\") {\r\n\t\t\toutput()\r\n\t\t}\r\n\t\treturn resolve(true)\r\n\t} catch(e) {\r\n\t\tslot.textContent = \"\"\r\n\t\tdom.results.innerHTML = e\r\n\t\treturn resolve(e+\"\")\r\n\t}\r\n})\r\n\r\n// run immediately, then after a delay\r\nlet timer = 1500\r\nlet delay = 16\r\nlet isColor = false\r\nfunction runall() {\r\n\t// reset\r\n\tisColor = false\r\n\tdom.results = \"\"\r\n\tdom.details = \"\"\r\n\trebuild()\r\n\toData = {}\r\n\toData = {\r\n\t\t\"1start\": performance.now()\r\n\t}\r\n\tcounter = 0\r\n\r\n\t// do it\r\n\tPromise.all([\r\n\t\trun(\"1data\")\r\n\t]).then(function(results){\r\n\t\tdom.results.innerHTML = sg + \"1st test: completed\" + sc\r\n\t\t\t+ \" ... 2nd test: will run in <span id='countdown'>2000</span> ms\"\r\n\t\tlet target = dom.countdown\r\n\t\tlet t0 = performance.now()\r\n\t\tif (results[0]) {\r\n\t\t\t// build tofu list\r\n\t\t\toData[\"tofuSize\"] = {}\r\n\t\t\toData[\"1tofu\"] = {}\r\n\t\t\tfor (const style of Object.keys(oData[\"1data\"])) {\r\n\t\t\t\t// get tofu size PER style\r\n\t\t\t\tlet array = oData[\"1data\"][style]['0xFFFF']\r\n\t\t\t\tlet tofuSize = array.join(\" x \")\r\n\t\t\t\toData[\"tofuSize\"][style] = tofuSize \r\n\t\t\t\t// match any tofu sizes\r\n\t\t\t\tfor (const code of Object.keys(oData[\"1data\"][style])) {\r\n\t\t\t\t\tarray = oData[\"1data\"][style][code]\r\n\t\t\t\t\tif (array.join(\" x \") == tofuSize) {\r\n\t\t\t\t\t\tif (oData[\"1tofu\"][style] == undefined) {oData[\"1tofu\"][style] = []}\r\n\t\t\t\t\t\toData[\"1tofu\"][style].push(code)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfunction measure() {\r\n\t\t\t\tlet time = (timer - Math.round(performance.now() - t0))\r\n\t\t\t\ttarget.innerHTML = time\r\n\t\t\t\tif (time < delay) {\r\n\t\t\t\t\tclearInterval(loop)\r\n\t\t\t\t\toData[\"2start\"] = performance.now()\r\n\t\t\t\t\tdom.results.innerHTML = \"\"\r\n\t\t\t\t\trun(\"2data\")\r\n\t\t\t\t} else {\r\n\t\t\t\t\trun(\"3data\")\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tlet loop = setInterval(measure, delay)\r\n\t\t} else {\r\n\t\t\tdom.results.innerHTML = results[0]\r\n\t\t}\r\n\t})\r\n}\r\nrunall()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/fontdebug.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=700\">\r\n\t<title>font debug</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 730px;}\r\n\t\t.visual {\r\n\t\t\tcolor: #b3b3b3;\r\n\t\t\tfont-size: 96px !important;\r\n\t\t\tfont-style: normal !important;\r\n\t\t\tletter-spacing: normal !important;\r\n\t\t\tline-break: auto !important;\r\n\t\t\tline-height: 50% !important;\r\n\t\t\ttext-transform: none !important;\r\n\t\t\ttext-align: left !important;\r\n\t\t\t/*\r\n\t\t\t\tthis is just a visual: we already strip out spaces\r\n\t\t\t\tand I want a gap between unstyled and styled\r\n\t\t\twhite-space: normal !important;\r\n\t\t\t*/\r\n\t\t\tword-break: normal !important;\r\n\t\t\tword-spacing: normal !important;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"hidden\"><input type=\"reset\" value=\"\" id=\"widget0\"></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"20%\"><col width=\"80%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">font debug\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">font </span>\r\n\t\t\t<div class=\"ttip\"><span class=\"icon\" style=\"font-size: 1.2em;\">[ i ]</span>\r\n\t\t\t\t<span class=\"ttxt\">\r\n\t\t\t\t<b><u>examples</u><br><code>caption</code><br><code>-moz-info</code><br><code>arial</code><br>\r\n\t\t\t\t<code>MS Shell Dlg \\32</code></b>\r\n\t\t\t\t</span>\r\n\t\t\t</div>\r\n\t\t\t&nbsp; <input id=\"valueF\" type=\"text\" style=\"width: 180px;\" value=\"arial\"> &nbsp;\r\n\t\t\t<span class=\"no_color\"> text </span>\r\n\t\t\t&nbsp; <input id=\"valueT\" type=\"text\" style=\"width: 80px;\">\r\n\t\t\t&nbsp; <select name=\"weight\" id=\"weight\" style=\"width: 190px;\"><option></option></select>\r\n\t\t\t&nbsp; <select name=\"styles\" id=\"styles\" style=\"width: 100px;\"><option></option></select>\r\n\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn12 btnfirst\" onClick=\"run('family')\">[ font-family ]</span>\r\n\t\t\t<span class=\"btn12 btnfirst\" onClick=\"run('face')\">[ FontFace ]</span>\r\n\t\t\t<span class=\"spaces faint\">some characters: M  ō  á  Ω  -  ? &#xFFFF;</span>\r\n\t\t\t<hr>\r\n\t\t\t<br>\r\n\t\t\t<div class=\"spaces\" style=\"color: #b3b3b3;\" id=\"base\"></div>\r\n\t\t\t<div class=\"spaces\" style=\"color: #b3b3b3;\" id=\"info\"></div>\r\n\t\t\t<div class=\"spaces visual\" id=\"visual\"></div>\r\n\t\t\t<div class=\"spaces\" style=\"color: #b3b3b3;\" id=\"font\"></div>\r\n\t\t\t<div class=\"spaces\" style=\"color: #b3b3b3;\" id=\"detail\"></div>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet fntList = [],\r\n\ttofu = '\\uffff',\r\n\tbaseFonts = ['monospace','sans-serif','serif','system-ui','cursive','fangsong'],\r\n\tctrlFonts = ['monospace','sans-serif','serif'],\r\n\tfntString = \"Mō-\"+ tofu,\r\n\tfntStringUsed,\r\n\tfntStyle,\r\n\tfntWeight,\r\n\tfntSize = \"512px\",\r\n\tfntEverDetected = false,\r\n\tfntTestType = 'family' // default\r\n\r\nlet aFaces = [] // history\r\n\r\nlet fntSystem = ['caption','icon','menu','message-box','small-caption','status-bar',\r\n\t'-moz-window', '-moz-desktop', '-moz-document', '-moz-workspace', '-moz-info',\r\n\t'-moz-pull-down-menu', '-moz-dialog', '-moz-button', '-moz-list', '-moz-field',\r\n]\r\nlet fntFamilies = [\r\n\t'monospace','sans-serif','serif','system-ui','cursive','fangsong',\r\n\t'emoji','math','ui-rounded','ui-monospsce','ui-sans-serif','ui-serif',\r\n]\r\n\r\nfunction get_fontFace(font) {\r\n\ttry {\r\n\t\tasync function testLocalFontFamily(font) {\r\n\t\t\ttry {\r\n\t\t\t\tconst fontFace = new FontFace(font, `local(\"${font}\")`)\r\n\t\t\t\tawait fontFace.load()\r\n\t\t\t\treturn font\r\n\t\t\t} catch(e) {\r\n\t\t\t\treturn e+''\r\n\t\t\t}\r\n\t\t}\r\n\t\tPromise.all([\r\n\t\t\ttestLocalFontFamily(font),\r\n\t\t]).then(function(res){\r\n\t\t\tlet item = res[0], isDetected = true, strErr = ''\r\n\t\t\tif (font !== item) {\r\n\t\t\t\tisDetected = false\r\n\t\t\t\tif ('NetworkError: A network error occurred.' !== item) {strErr = item}\r\n\t\t\t}\r\n\t\t\tlet str = isDetected ? green_tick : red_cross\r\n\t\t\taFaces.push(str +' '+ font +' '+ strErr)\r\n\t\t\tdom.base.innerHTML = s12 + 'FONT FACE: results history'+ sc +'<br><br>'+ aFaces.join('<br>')\r\n\t\t})\r\n\t} catch(e) {\r\n\t\tdom.base = e+''\r\n\t}\r\n}\r\n\r\nfunction get_fontFamily(font) {\r\n\t//https://developer.mozilla.org/en-US/docs/Web/CSS/font\r\n\tdom.visual.style.font = ''\r\n\tdom.visual.style.setProperty('--font', '')\r\n\tif (fntSystem.includes(font)) {\r\n\t\tdom.visual.style.font = font\r\n\t} else {\r\n\t\t// if a generic font family, we don't wrap in quotes\r\n\t\tlet fontString = fntFamilies.includes(font) ? font : '\\''+ font + '\\''\r\n\t\tdom.visual.style.fontFamily = fontString\r\n\t}\r\n\tdom.info.innerHTML = '<br><hr>unstyled | styled | '+ font\r\n\tdom.visual.innerHTML = \"<br><span>\"+ fntStringUsed +\"  </span><span style ='font-weight: \"\r\n\t\t+ fntWeight +\"; font-style:\" + fntStyle + \";'>\"+ fntStringUsed +\"</span>\"\r\n\tdom.valueF.value = font\r\n\tfntList = [font]\r\n\tgetFonts()\r\n\tlet strDetected = (fntEverDetected ? sg +' [' : sb +' [NO ') + 'CHANGE DETECTED]' + sc\r\n\tdom.font.innerHTML = '<br><br><hr><br>'+ s12 +'FONT: '+ sc + font\r\n\t\t+' [font-weight: '+ s3 + fntWeight + sc +' | font-style: ' + s3 + fntStyle + sc +'] '+ strDetected\r\n}\r\n\r\nfunction run(type) {\r\n\t// if enter key use last test type\r\n\tif ('enter' == type) {type = fntTestType}\r\n\r\n\t// set fntStringUsed\r\n\tif ((dom.valueT.value).trim() == '') {\r\n\t\tfntStringUsed = fntString\r\n\t} else {\r\n\t\tfntStringUsed = (dom.valueT.value).trim()\r\n\t}\r\n\t// replace multiple spaces\r\n\t//fntStringUsed = fntStringUsed.replace(/\\s\\s+/g, ' ');\r\n\t// remove all spaces\r\n\tfntStringUsed = fntStringUsed.replace(/ /g,'')\r\n\tdom.valueT.value = fntStringUsed\r\n\r\n\tlet isStillFace = false, delay = 150\r\n\tif ('face' == type && type == fntTestType) {\r\n\t\tisStillFace = true, delay = 0\r\n\t}\r\n\tfntTestType = type\r\n\r\n\t// reset\r\n\tfntList = []\r\n\tdom.font = ''\r\n\tdom.info = ''\r\n\tif (isStillFace) {dom.base = ''}\r\n\tdom.visual = ''\r\n\tdom.detail = ''\r\n\tfntEverDetected = false\r\n\tfntWeight = dom.weight.value\r\n\tfntStyle = dom.styles.value\r\n\r\n\t// make sure we have a font\r\n\tlet valueF = dom.valueF.value\r\n\tvalueF = valueF.replace(/['\"]+/g, \"\") // remove all quote marks\r\n\tvalueF = valueF.trim()\r\n\r\n\tif (valueF.length) {\r\n\t\t// only get the first font if multiple\r\n\t\tlet tmpArr = valueF.split(\",\")\r\n\t\tfor (let i = 0 ; i < tmpArr.length; i++) {\r\n\t\t\tlet trimmed = tmpArr[i].trim()\r\n\t\t\tif (trimmed.length) {\r\n\t\t\t\tfntList.push(trimmed)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// make sure we have at least one item\r\n\t\tfntList = fntList.filter(function (item, position) {\r\n\t\t\treturn fntList.indexOf(item) === position\r\n\t\t})\r\n\t\tif (!fntList.length) {\r\n\t\t\tdom.base = 'aww, snap! try adding a font'\r\n\t\t\treturn\r\n\t\t} else {\r\n\t\t\tlet font = fntList[0]\r\n\t\t\tsetTimeout(function() {\r\n\t\t\t\tif ('family' == type) {\r\n\t\t\t\t\tget_fontFamily(font)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tget_fontFace(font)\r\n\t\t\t\t}\r\n\t\t\t}, delay)\r\n\t\t}\r\n\t} else {\r\n\t\tdom.base = 'aww, snap! try adding a font'\r\n\t\treturn\r\n\t}\r\n}\r\n\r\nfunction run_once() {\r\n\t// populate font weights\r\n\tlet fntWeights = {\r\n\t\t100: 'Thin (Hairline)',\r\n\t\t200: 'Extra/Ultra Light',\r\n\t\t300: 'Light',\r\n\t\t400: 'Normal (Regular)',\r\n\t\t500: 'Medium',\r\n\t\t600: 'Semi/Demi Bold',\r\n\t\t700: 'Bold',\r\n\t\t800: 'Extra/Ultra Bold',\r\n\t\t900: 'Black (Heavy)',\r\n\t}\r\n\tlet aWeights = []\r\n\tfor (const k of Object.keys(fntWeights)) {\r\n\t\taWeights.push(\"<option value = '\"+ k +\"'>\"+ k +': '+ fntWeights[k] +\"</option>\")\r\n\t}\r\n\tdom.weight.innerHTML = aWeights.join('')\r\n\tdom.weight.value = '400'\r\n\r\n\t// populate fnt styles\r\n\tlet aStyles = []\r\n\tlet fntStyles = ['italic','normal','oblique']\r\n\tfor (const k of fntStyles) {\r\n\t\taStyles.push(\"<option value = '\"+ k +\"'>\"+ k +\"</option>\")\r\n\t}\r\n\tdom.styles.innerHTML = aStyles.join('')\r\n\tdom.styles.value = 'normal'\r\n\r\n\t// tweak fntString to semi match what we get on TZP\r\n\t\t// FF windows: MōΩ + tofu\r\n\t\t//     FF mac: Mō- + tofu\r\n\t\t// linux/android/TB: - + tofu <- boring/hard to inspect visually so instead just use mac's string\r\n\r\n\t// add platform specific fonts\r\n\ttry {\r\n\t\tif ('Win32' == navigator.platform) {\r\n\t\t\tfntString = 'MōΩ'+ tofu\r\n\t\t\tbaseFonts.push('MS Shell Dlg \\\\32')\r\n\t\t}\r\n\t} catch(e) {}\r\n\ttry {\r\n\t\tlet ua = navigator.userAgent\r\n\t\tif (ua.includes('Macintosh') || ua.includes('Mac OS')) {\r\n\t\t\tbaseFonts.push('-apple-system')\r\n\t\t} else if (ua.includes('Android')) {\r\n\t\t\tbaseFonts.push('Dancing Script')\r\n\t\t}\r\n\t} catch(e) {}\r\n\t// set string\r\n\tif ((dom.valueT.value).trim() == '') {dom.valueT.value = fntString}\r\n\r\n\t// another platform specific font, dedupe, sort\r\n\tlet el = dom.widget0\r\n\ttry {\r\n\t\tlet font = getComputedStyle(el).getPropertyValue(\"font-family\")\r\n\t\tbaseFonts.push(font.trim())\r\n\t\tbaseFonts = baseFonts.filter(function(item, position) {return baseFonts.indexOf(item) === position})\r\n\t\tbaseFonts.sort()\r\n\t} catch(e) {\r\n\t}\r\n\t// add enter key event to font field\r\n\tdom.valueF.addEventListener(\"keypress\", function(event) {if (event.key === \"Enter\") {run('enter')}})\r\n}\r\nrun_once()\r\n\r\nfunction getFonts() {\r\n\tconst id = 'element-fp'\r\n\ttry {\r\n\t\tconst doc = document\r\n\t\tconst div = doc.createElement('div')\r\n\t\tdiv.setAttribute('id', id)\r\n\t\tdoc.body.appendChild(div)\r\n\t\tset_element(id)\r\n\r\n\t\tconst span = doc.getElementById(`${id}-detector`)\r\n\t\tconst style = getComputedStyle(span)\r\n\t\tconst pixelsToNumber = pixels => +pixels.replace('px','')\r\n\t\tconst originPixelsToNumber = pixels => 2*pixels.replace('px', '')\r\n\t\tconst getDimensions = (span, style) => {\r\n\t\t\tconst dimensions = {\r\n\t\t\t\twidth: span.getBoundingClientRect().width,\r\n\t\t\t\theight: span.getBoundingClientRect().height,\r\n\t\t\t}\r\n\t\t\treturn dimensions\r\n\t\t}\r\n\r\n\t\t// base sizes\r\n\t\tlet baseDisplay = {}\r\n\t\tlet fntStringBase = fntStringUsed //.slice(0, 12)\r\n\t\tbaseFonts.sort()\r\n\t\tconst base = baseFonts.reduce((acc, font) => {\r\n\t\t\tspan.style.font =''\r\n\t\t\tspan.style.setProperty('--font', font)\r\n\t\t\t// if a generic font family, we don't wrap in quotes\r\n\t\t\tlet fontString = fntFamilies.includes(font) ? font : '\\''+ font + '\\''\r\n\t\t\tbaseDisplay[font] = '<span style=\"white-space: nowrap; font-size: 24px; font-weight: ' + fntWeight\r\n\t\t\t\t+ '; font-style: ' + fntStyle + '; font-family: '+ fontString + ';\">'+ fntStringBase +'<span>'\r\n\t\t\tconst dimensions = getDimensions(span, style)\r\n\t\t\tacc[font.split(',')[0]] = dimensions // use only first name, i.e w/o fallback\r\n\t\t\treturn acc\r\n\t\t}, {})\r\n\t\tspan.style.font ='' // reset\r\n\r\n\t\t// display base\r\n\t\tlet display = []\r\n\t\tfor (const k of Object.keys(base).sort()) {\r\n\t\t\tdisplay.push('<p>'+ s3 + k.padStart(20) +': '+ sc + (base[k].width +'').padStart(20)\r\n\t\t\t\t+ ' x '+ base[k].height\r\n\t\t\t\t+ ' &nbsp; '+ baseDisplay[k]\r\n\t\t\t\t+'</p>'\r\n\t\t\t)\r\n\t\t}\r\n\t\tlet hash = mini(base)\r\n\t\tdom.base.innerHTML = s12+ 'BASEFONTS: '+ sc + hash + display.join('')\r\n\r\n\t\t// measure\r\n\t\tlet results = {}\r\n\t\tfntList.forEach(font => {\r\n\t\t\t// we're only testing a single font, we don't need a font key\r\n\t\t\tctrlFonts.forEach(basefont => {\r\n\t\t\t\tif (fntSystem.includes(font)) {\r\n\t\t\t\t\tspan.style.setProperty('--font', \"\")\r\n\t\t\t\t\tspan.style.font = font\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// if a generic font family, we don't wrap in quotes\r\n\t\t\t\t\tlet baseString = fntFamilies.includes(basefont) ? basefont : '\\''+ basefont + '\\''\r\n\t\t\t\t\tconst family = \"'\"+ font +\"', \"+ basefont\r\n\t\t\t\t\tspan.style.font = \"\"\r\n\t\t\t\t\tspan.style.setProperty('--font', family)\r\n\t\t\t\t}\r\n\t\t\t\tconst style = getComputedStyle(span)\r\n\t\t\t\tconst dimensions = getDimensions(span, style)\r\n\t\t\t\tbasefont = basefont.split(\",\")[0] // switch to short generic name\r\n\t\t\t\tresults[basefont] = {'width': dimensions.width, 'height': dimensions.height}\r\n\t\t\t\treturn\r\n\t\t\t})\r\n\t\t})\r\n\t\tremoveElementFn(id)\r\n\r\n\t\t// DETAIL\r\n\t\t//console.debug('base', mini(base), base)\r\n\t\t//console.debug('results', mini(results), results)\r\n\t\tdisplay = []\r\n\t\tfor (const k of Object.keys(results).sort()) {\r\n\t\t\tlet aCtrl = base[k], aTest = results[k]\r\n\t\t\tlet isDetected = mini(aCtrl) !== mini(aTest)\r\n\t\t\tlet strResult = (aTest.width+'').padStart(20) +' x ' + aTest.height\r\n\t\t\tif (isDetected) {\r\n\t\t\t\tstrResult = sg + strResult + sc\r\n\t\t\t\tfntEverDetected = true\r\n\t\t\t}\r\n\t\t\tdisplay.push('<p>'+ s3 + k.padStart(20) +\": \"+ sc + strResult +'</p>')\r\n\t\t}\r\n\t\tdom.detail.innerHTML = '<br>' + s12 +'DETAIL: '+ sc + mini(results) + display.join('')\r\n\t} catch(e) {\r\n\t\tremoveElementFn(id)\r\n\t\tconsole.debug(e.name, e.message)\r\n\t}\r\n}\r\n\r\nfunction set_element(id) {\r\n\tdocument.getElementById(id).innerHTML = `\r\n\t\t<style>\r\n\t\t#${id}-detector {\r\n\t\t\t--font: '';\r\n\t\t\tposition: absolute !important;\r\n\t\t\tleft: -9999px!important;\r\n\t\t\tfont-size: ` + fntSize + ` !important;\r\n\t\t\tfont-style: ` + fntStyle + ` !important;\r\n\t\t\tfont-weight: ` + fntWeight + ` !important;\r\n\t\t\tletter-spacing: normal !important;\r\n\t\t\tline-break: auto !important;\r\n\t\t\tline-height: normal !important;\r\n\t\t\ttext-transform: none !important;\r\n\t\t\ttext-align: left !important;\r\n\t\t\ttext-decoration: none !important;\r\n\t\t\ttext-shadow: none !important;\r\n\t\t\twhite-space: normal !important;\r\n\t\t\tword-break: normal !important;\r\n\t\t\tword-spacing: normal !important;\r\n\t\t\t/* in order to test scrollWidth, clientWidth, etc. */\r\n\t\t\tpadding: 0 !important;\r\n\t\t\tmargin: 0 !important;\r\n\t\t\t/* in order to test inlineSize and blockSize */\r\n\t\t\twriting-mode: horizontal-tb !important;\r\n\t\t\t/* for transform and perspective */\r\n\t\t\ttransform-origin: unset !important;\r\n\t\t\tperspective-origin: unset !important;\r\n\t\t}\r\n\t\t#${id}-detector::after {\r\n\t\t\tfont-family: var(--font);\r\n\t\t\tcontent: '` + fntStringUsed + `';\r\n\t\t}\r\n\t\t</style>\r\n\t\t<span id=\"${id}-detector\"></span>`\r\n}\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/fontdefaults.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>script defaults</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 480px;}\n\t</style>\n</head>\n\n<body>\n\t<div class=\"offscreen\">\n\t\t<div class=\"normalized\"><span id=\"dfsize\"></span></div>\n\t\t<div><span id=\"dfproportion\"></span></div>\n\t</div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb12\">\n\t\t<thead><tr><th>\n\t\t\t<div class=\"nav-title\">script defaults</div>\n\t\t</th></tr></thead>\n\t\t<tr><td class=\"intro\"><span class=\"no_color\">\n\t\t\tTesting default proportional font-family, and sizes per\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://en.wikipedia.org/wiki/Writing_system\">writing system</a>\n\t\t\tas per <code>Settings</code> > <code>General</code> > <code> Language and Appearance</code> > <code> Fonts</code> > <code> Advanced</code>\n\t\t</span></td></tr>\n\t\t<tr><td><hr><br></td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono\" style=\"text-align: left\">\n\n\t\t\t<span class=\"spaces no_color\" id=\"results\"></span>\n\t\t\t</td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nfunction run() {\n\n\tconst styles = [\"monospace\",\"sans-serif\",\"serif\"]\n\tconst scripts = {\n\t\tarabic: \"ar\", armenian: \"hy\", bengali: \"bn\", cyrillic: \"ru\", devanagari: \"hi\", ethiopic: \"gez\",\n\t\tgeorgian: \"ka\", greek: \"el\", gujurati: \"gu\", gurmukhi: \"pa\", hebrew: \"he\", japanese: \"ja\",\n\t\tkannada: \"kn\", khmer: \"km\", korean: \"ko\", latin: \"en\", malayalam: \"ml\", mathematics: \"x-math\",\n\t\todia: \"or\", other: \"my\", \"simplified chinese\": \"zh-CN\", sinhala: \"si\", tamil: \"ta\", telugu: \"te\",\n\t\tthai: \"th\", tibetan: \"bo\",\"traditional chinese (hong kong)\": \"zh-HK\",\n\t\t\"traditional chinese (taiwan)\": \"zh-TW\",\"unified canadian syllabary\": \"cr\",\n\t}\n\ttry {\n\t\tconst el = dom.dfsize,\n\t\t\telpro = dom.dfproportion\n\t\tlet data = {}\n\t\tfor (const k of Object.keys(scripts)) {\n\t\t\tlet lang = scripts[k]\n\t\t\telpro.style.fontFamily = \"\"\n\t\t\telpro.setAttribute('lang', lang)\n\t\t\tlet font = getComputedStyle(elpro).getPropertyValue(\"font-family\")\n\t\t\tlet tmp = [font]\n\t\t\tel.setAttribute('lang', lang)\n\t\t\tstyles.forEach(function(style) {\n\t\t\t\t// always clear\n\t\t\t\tel.style.fontSize = \"\"\n\t\t\t\tel.removeAttribute('font-family')\n\t\t\t\tel.style.fontFamily = \"\"\n\t\t\t\tel.style.fontFamily = style\n\t\t\t\tlet size = getComputedStyle(el).getPropertyValue(\"font-size\").slice(0,-2)\n\t\t\t\ttmp.push(size)\n\t\t\t})\n\t\t\tlet key = tmp.join(\"-\")\n\t\t\tif (data[key] == undefined) {data[key] = [k]} else {data[key].push(k)}\n\t\t}\n\t\tlet newobj = {}\n\t\tfor (const k of Object.keys(data).sort()) {newobj[k] = data[k]} // sort obj\n\t\tlet hash = mini(newobj)\n\t\tdom.results.innerHTML = s12+ hash + sc +\" <span class='btn0 btnc' onclick='copyclip(`results`)'>[COPY]</span><br><br>\"+ json_highlight(newobj)\n\n\t} catch(e) {\n\t\tdom.results.innerHTML = s12 + e.name +\": \"+ sc + e.message\n\t}\n}\nrun()\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/fontscripts.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>scripts</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 950px}\r\n\t\t#ugSpan {font-size: 10000%;}\r\n\t\ta.sb {color: #dc8c8c;}\r\n\t\t.groupleft {float: left;}\r\n\t\t.groupright {float: right;}\r\n\t\t.script {line-height: 2.4; font-family: monospace, \"Courier New\";}\r\n\t\tnav {\r\n\t\t\tposition: absolute;\r\n\t\t\tpadding: 15px;\r\n\t\t\tbackground-color: #161b22;\r\n\t\t}\r\n\t\tnav div {\r\n\t\t\twhite-space: nowrap;\r\n\t\t\tmargin-bottom: 12px;\r\n\t\t}\r\n\t\tdiv.nav-down {width: 300px;}\r\n\t\tdiv.nav-up {width: 300px;}\r\n\t\tcode.purple {\r\n\t\t\tbackground-color: rgba(150, 100, 240, 0.4) !important;\r\n\t\t\tpadding-left: 2px !important;\r\n\t\t\tpadding-right: 2px !important;\r\n\t\t\tcolor: #dcdcdc;\r\n\t\t\tfont-size: 11px;\r\n\t\t}\r\n\t\tdiv.chars {\r\n\t\t\tdirection:ltr;\r\n\t\t\tunicode-bidi:bidi-override;\r\n\t\t}\r\n\t\tli {\r\n\t\t\tmargin-left: 20px;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"offscreen\">\r\n\t\t<div id=\"ugDiv\" class=\"normalized\" style='font-size: 22pt;'><span id=\"ugSpan\"><span id=\"ugSlot\"></span></span></div>\r\n\t</div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"25%\"><col width=\"75%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">scripts\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\">waiting on reflow ...</span></div>\r\n\t\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"tofuSize\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Tests font vis vs script support by checking up to\r\n\t\t\t\t<code>x</code> characters per script for matching tofu sizes. Allow 2-3 secs before testing to ensure\r\n\t\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1676966#c54\">asynchronous font fallback</a>.\r\n\t\t\t\tTofu results are colored coded as <span class=\"bad\">80+%</span>, <span class=\"s2\">20-80%</span>, <span class=\"s4\">20% or lower</span>.\r\n\t\t\t\tNOTE: <span class=\"s14\">[RANGES...]</span> used can be <code class=\"purple\">P</code> partial and/or <code class=\"purple\">S</code>\r\n\t\t\t\tselective [uses some blocks in that range]. TIP: Click script names in the display to logs code points to console.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td style=\"text-align: left; vertical-align: bottom;\">\r\n\t\t\t\t<span id=\"toggleScript\" class=\"btn12 btnfirst\" onClick=\"toggleitem(`legend`)\">&#9660 index</span> <span id=\"countScript\"></span>\r\n\t\t\t</td>\r\n\t\t\t<td style=\"text-align: left; vertical-align: bottom;\">\r\n\t\t\t\t<span class=\"btn12 btnfirst\" onClick=\"run('test')\">[ run ]</span>\r\n\t\t\t\t<span class=\"btn12 btn\" onClick=\"toggleitem(`navmenu`)\" style=\"position: relative;\">\r\n\t\t\t\t\t[ list &#9660; ]\r\n\t\t\t\t\t<nav id=\"navmenu\" class=\"hidden\" style=\"cursor: auto;\">\r\n\t\t\t\t\t\t<span class=\"no_color\" style=\"font-size: 11px;\">logs to console<br><br></span>\r\n\t\t\t\t\t\t<span style=\"cursor: pointer;\">\r\n\t\t\t\t\t\t<div onClick=\"logConsole('rangesreduced')\">ranges [reduced]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('ranges')\">ranges [used]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('reserved')\">code points [reserved]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('ignore')\">code points [ignored]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('max')\">code points [max used]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('reduce')\">code points [reduce]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('min')\">code points [min used]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('version')\">unicode [min versions]</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('wiki')\">wiki links</div>\r\n\t\t\t\t\t\t<div onClick=\"logConsole('everything')\">everything</div>\r\n\t\t\t\t\t\t</span>\r\n\t\t\t\t\t</nav>\r\n\t\t\t\t</span>\r\n\t\t\t\t<span class=\"s12 mono\" onClick='reset(true, false)'> depth\r\n\t\t\t\t\t<input type=\"radio\" name=\"depth\" value=20 checked> 20\r\n\t\t\t\t\t<input type=\"radio\" name=\"depth\" value=50> 50\r\n\t\t\t\t\t<input type=\"radio\" name=\"depth\" value=\"ALL\"> ALL\r\n\t\t\t\t</span> | &nbsp;\r\n\t\t\t\t<span class=\"s12 mono\">\r\n\t\t\t\t\t<input type=\"checkbox\" name=\"allassigned\" style=\"margin: 0; height: 12px\" onClick='reset()'> max\r\n\t\t\t\t\t<div class=\"ttip\"><span class=\"icon\">[ i ]</span>\r\n\t\t\t\t\t\t<span class=\"ttxtb\" style=\"font-style: normal; font-family: serif;\">\r\n\t\t\t\t\t\t<br>When unchecked, some scripts<br>will use earlier unicode versions<br>\r\n\t\t\t\t\t\t<br>e.g. Armenian will use <b>v3.0</b> (1999)\r\n\t\t\t\t\t\t<br>ignoring <b>U+058F</b> (v6.1, 2012)<br><b>U+058D, U+058E</b> (v7.0, 2014)<br><b>U+0560, U+0588</b> (v11.0, 2018)<br>\r\n\t\t\t\t\t\t<br>Reduces tofu noise on platforms<br>with older font versions e.g. if<br>just checking basic script support<br><br></span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</span>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span class=\"mono spaces hidden\" id=\"legend\" style=\"color: #b3b3b3; font-size: 11px;\"></span>\r\n\t\t\t\t<span class=\"mono spaces\" id=\"summary\" style=\"color: #b3b3b3; font-size: 11px;\"></span>\r\n\t\t\t</td>\r\n\t\t\t<td style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span class=\"spaces\" style=\"direction:ltr;unicode-bidi:bidi-override; font-size: 12px;\" id=\"visual\">\r\n\t\t\t\t\t<br>waiting on reflow ...\r\n\t\t\t\t</span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://en.wikipedia.org/wiki/List_of_Unicode_characters\r\n// https://www.cogsci.ed.ac.uk/~richard/unicode-sample-3-2.html\r\n\r\nlet aPartial = [\r\n\t// partial range: i.e start..end is not the full assigned range\r\n\t\"unified canadian aboriginal syllabics\", // 640 code points\r\n\t\"arabic presentation forms-a\", // 631 code points\r\n\t\"basic latin\", // C0 controls ignored\r\n\t\"general punctuation\", // 200x and 206x ignored, e.g. birectional, non-printing etc\r\n\t\"latin-1 supplement\", // 008x + 009x ignored, control codes\r\n\t\"mathematical alphanumeric symbols\", // 996 code points\r\n\r\n\t// partial: reduce entropy duplicity\r\n\t\t// i.e a lot of chars are the same measurements: east asian languages\r\n\t\"cjk compatibility ideographs\",\r\n\t\"cjk compatibility ideographs supplement\",\r\n\t\"enclosed cjk letters and months\",\r\n\t\"hangul compatibility jamo\",\r\n\t\"hangul jamo\",\r\n\t\"hiragana\",\r\n\r\n\t// partial! doh! way too big\r\n\t\t// also east asian measurments seem to all be the same per script\r\n\t\"cjk unified ideographs\", // 20,992 code points\r\n\t\"cjk unified ideographs extension-a\", // 6,592 code points\r\n\t\"cjk unified ideographs extension-b\", // 42,720 code points\r\n\t\"hangul syllables\", // 11,172 code points\r\n\t\"yi syllables\",  // 1,165 code points\r\n]\r\n\r\nlet aSelective = [\r\n\t// not all blocks in the range is used: i.e not \"contiguous\"\r\n\t\t// be careful to not exclude later assigned code points\r\n\t\t// or reduce size entropy per script\r\n\t\"cjk compatibility ideographs\",\r\n\t\"cjk compatibility ideographs supplement\",\r\n\t\"enclosed cjk letters and months\",\r\n\t\"hangul jamo\",\r\n\t\"unified canadian aboriginal syllabics\",\r\n]\r\n\r\nlet aSpacer = [\r\n\t\"combining diacritical marks\",\r\n\t\"combining diacritical marks for symbols\",\r\n\t\"combining diacritical marks supplement\",\r\n\t\"cyrillic extended-a\",\r\n]\r\n\r\nlet oEnlarge = {\r\n\t\"alphabetic presentation forms\": [14],\r\n\t\"arabic\": [16],\r\n\t\"arabic presentation forms-a\": [16],\r\n\t\"arabic supplement\": [16],\r\n\t\"arrows\": [14],\r\n\t\"bengali\": [14],\r\n\t\"buginese\": [16],\r\n\t\"buhid\": [16],\r\n\t\"bopomofo extended\": [14],\r\n\t\"cherokee supplement\": [16],\r\n\t\"combining diacritical marks\": [20],\r\n\t\"combining diacritical marks for symbmols\": [20],\r\n\t\"combining diacritical marks supplement\": [20],\r\n\t\"control pictures\": [16],\r\n\t\"cyrillic extended-a\": [22],\r\n\t\"deseret\": [14],\r\n\t\"devanagari\": [14],\r\n\t\"ethiopic\": [14],\r\n\t\"ethiopic supplement\": [16],\r\n\t\"general punctuation\": [16],\r\n\t\"glagolitic\": [14],\r\n\t\"hangul jamo\": [14],\r\n\t\"hebrew\": [18],\r\n\t\"ipa extensions\": [14],\r\n\t\"kanbun\": [16],\r\n\t\"katakana phonetic extensions\": [16],\r\n\t\"mahajani\": [16],\r\n\t\"mongolian\": [16],\r\n\t\"ogham\": [18],\r\n\t\"old turkic\": [14],\r\n\t\"optical character recognition\": [18],\r\n\t\"oriya\": [14],\r\n\t\"phonetic extensions\": [16],\r\n\t\"phonetic extensions supplement\": [16],\r\n\t\"runic\": [14],\r\n\t\"sinhala\": [16],\r\n\t\"sora sompeng\": [16],\r\n\t\"spacing modifier letters\": [18],\r\n\t\"superscripts and subscripts\": [18],\r\n\t\"supplemental arrows-a\": [14],\r\n\t\"supplemental arrows-b\": [14],\r\n\t\"syriac\": [16],\r\n\t\"tai le\": [14],\r\n\t\"telugu\": [16],\r\n\t\"thai\": [14],\r\n\t\"thanaa\": [16],\r\n\t\"tibetan\": [18],\r\n\t\"vertical forms\": [16],\r\n\t\"yi syllables\": [16],\r\n}\r\n\r\nlet\tuv1x0x0 = \"1.0.0 (1991)\",\r\n\tuv1x0x1 = \"1.0.1 (1992)\",\r\n\tuv1x1 = \"1.1 (1993)\",\r\n\tuv2x0 = \"2.0 (1996)\",\r\n\tuv3x0 = \"3.0 (1999)\",\r\n\tuv3x1 = \"3.1 (2001)\",\r\n\tuv3x2 = \"3.2 (2002)\",\r\n\tuv4x0 = \"4.0 (2003)\",\r\n\tuv4x1 = \"4.1 (2005)\",\r\n\tuv5x0 = \"5.0 (2006)\",\r\n\tuv5x1 = \"5.1 (2008)\",\r\n\tuv5x2 = \"5.2 (2009)\",\r\n\tuv6x1 = \"6.1 (2012)\",\r\n\tuv7x0 = \"7.0 (2014)\",\r\n\tuv8x0 = \"8.0 (2015)\",\r\n\tuv9x0 = \"9.0 (2016)\"\r\n\r\nlet aBlocks = [\r\n\t// note: first item needs to be a group name for the top anchor to work\r\n\r\n\t// schema:\r\n\t// name, prefix, [block range], remove last x, [reserved], url, [reduce], reduced-version, [ignore]\r\n\r\n\t// AFRICAN\r\n\t[\"african\"],\r\n\t[\"adlam\", \"1E9\", [0,1,2,3,4,5], 0, [\"4C\",\"4D\",\"4E\",\"4F\",\"5A\",\"5B\",\"5C\",\"5D\"], \"Adlam_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"4B\", // 12.0\r\n\t\t], uv9x0\r\n\t],\r\n\t[\"bamum\", \"A6\", [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -8, , \"Bamum_(Unicode_block)\"],\r\n\t[\"bassa vah\", \"16A\", [\"D\",\"E\",\"F\"], -10, [\"EE\",\"EF\"], \"Bassa_Vah_(Unicode_block)\"],\r\n\t[\"ethiopic\", 1, [20,21,22,23,24,25,26,27,28,29,\"2A\",\"2B\",\"2C\",\"2D\",\"2E\",\"2F\",30,31,32,33,34,35,36,37], -3,\r\n\t\t[\"249\",\"24E\",\"24F\",\"257\",\"259\",\"25E\",\"25F\",\"289\",\"28E\",\"28F\",\"2B1\",\"2B6\",\"2B7\",\"2BF\",\r\n\t\t\"2C1\",\"2C6\",\"2C7\",\"2D7\",\"311\",\"316\",\"317\",\"35B\",\"35C\"], \"Ethiopic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"35D\",\"35E\", // 6.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"ethiopic supplement\", \"13\", [8,9], -6, , \"Ethiopic_Supplement\"],\r\n\t//[\"medefaidrin\", \"16E\", [4,5,6,7,8,9], -5, , \"Medefaidrin_(Unicode_block)\"], // noone supports this, reduce overhead/perf\r\n\t[\"mende_kikakui\", \"1E8\", [0,1,2,3,4,5,6,7,8,6,\"A\",\"B\",\"C\",\"D\"], -9, [\"C5\",\"C6\"], \"Mende_Kikakui_(Unicode_block)\"],\r\n\t[\"nko\", \"07\", [\"C\",\"D\",\"E\",\"F\"], 0, [\"FB\",\"FC\"], \"NKo_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"FD\",\"FE\",\"FF\", // 11.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"osmanya\", \"104\", [8,9,\"A\"], -6, [\"9E\",\"9F\"], \"Osmanya_(Unicode_block)\"],\r\n\t[\"tifinagh\", \"2D\", [3,4,5,6,7], 0,\r\n\t\t[\"68\",\"69\",\"6A\",\"6B\",\"6C\",\"6D\",\"6E\",\"71\",\"72\",\"73\",\"74\",\"75\",\"76\",\"77\",\"78\",\"79\",\"7A\",\"7B\",\"7C\",\"7D\",\"7E\"],\r\n\t\t\"Tifinagh_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"70\", // 6.0\r\n\t\t\t\"66\",\"67\", // 6.1\r\n\t\t], uv4x1,\r\n\t\t[\"7F\"] // ignore: tifinagh consonant joiner\r\n\t],\r\n\t[\"vai\", \"A\", [50,51,52,53,54,55,56,57,58,59,\"5A\",\"5B\",\"5C\",\"5D\",\"5E\",\"5F\",60,61,62,63], -20, , \"Vai_(Unicode_block)\"],\r\n\r\n\t// AMERICAN\r\n\t[\"american\"],\r\n\t[\"cherokee\", 13, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\",], -2, [\"F6\",\"F7\"], \"Cherokee_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"F5\",\"F8\",\"F9\",\"FA\",\"FB\",\"FC\",\"FD\", // 8.0\r\n\t\t], uv3x0\r\n\t],\r\n\t[\"cherokee supplement\", \"AB\", [7,8,9,\"A\",\"B\"], , , \"Cherokee_Supplement\"],\r\n\t[\"deseret\", \"104\", [0,1,2,3,4], , , \"Deseret_(Unicode_block)\"],\r\n\t[\"osage\", \"104\", [\"B\",\"C\",\"D\",\"E\",\"F\"], -4, [\"D4\",\"D5\",\"D6\",\"D7\"], \"Osage_(Unicode_block)\"],\r\n\r\n\t[\"unified canadian aboriginal syllabics\", 1, \r\n\t\t//[40,41,42,43,44,45,46,47,48,49,\"4A\",\"4B\",\"4C\",\"4D\",\"4E\",\"4F\",50,51,52,53,54,55,56,57,58,59,\"5A\",\"5B\",\"5C\",\"5D\",\"5E\",\"5F\",60,61,62,63,64,65,66,67], , , // full\r\n\t\t[40,41,42,43,44,45,67], , , // selective\r\n\t\t\"Unified_Canadian_Aboriginal_Syllabics_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"400\",\"677\",\"678\",\"679\",\"67A\",\"67B\",\"67C\",\"67D\",\"67E\",\"67F\", // 5.2\r\n\t\t], uv3x0\r\n\t],\r\n\r\n\t// ANCIENT\r\n\t[\"ancient and historic\"],\r\n\t[\"gothic\", 103, [3,4], -5, , \"Gothic_(Unicode_block)\"],\r\n\t[\"ogham\", 16, [8,9], -3, , \"Ogham_(Unicode_block)\"],\r\n\t[\"old italic\", 103, [0,1,2], ,\r\n\t\t[\"24\",\"25\",\"26\",\"27\",\"28\",\"29\",\"2A\",\"2B\",\"2C\"], \"Old_Italic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"1F\", // 7.0\r\n\t\t\t\"2D\",\"2E\",\"2F\" // 10.0\r\n\t\t], uv3x1\r\n\t],\r\n\t[\"old turkic\", \"10C\", [0,1,2,3,4], -7, , \"Old_Turkic_(Unicode_block)\"],\r\n\t[\"runic\", 16, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -7, , \"Runic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"F1\",\"F2\",\"F3\",\"F4\",\"F5\",\"F6\",\"F7\",\"F8\",  // 7.0\r\n\t\t], uv3x0\r\n\t],\r\n\r\n\t// BRAHMIC\r\n\t[\"brahmic\"],\r\n\t[\"balinese\", \"1B\", [0,1,2,3,4,5,6,7], -1, [\"4D\",\"4E\",\"4F\"], \"Balinese_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"4C\",\"7D\",\"7E\" // 14.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"bengali\", \"09\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -1,\r\n\t\t[\"84\",\"8D\",\"8E\",\"91\",\"92\",\"A9\",\"B1\",\"B3\",\"B4\",\"B5\",\"BA\",\"BB\",\"C5\",\"C6\",\"C9\",\"CA\",\"CF\",\r\n\t\t\"D0\",\"D1\",\"D2\",\"D3\",\"D4\",\"D5\",\"D6\",\"D8\",\"D9\",\"DA\",\"DB\",\"DE\",\"E4\",\"E5\"], \"Bengali_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"FB\", // 5.2\r\n\t\t\t\"80\", // 7.0\r\n\t\t\t\"FC\",\"FD\", // 10.0\r\n\t\t\t\"FE\", // 11.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"buginese\", \"1A\", [0,1], 0, [\"1C\",\"1D\"], \"Buginese_(Unicode_block)\"],\r\n\t[\"buhid\", 17, [4,5], -12, , \"Buhid_(Unicode_block)\"],\r\n\t[\"chakma\", 111, [0,1,2,3,4], -8, [\"35\"], \"Chakma_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"44\",\"45\",\"46\", // 11.0\r\n\t\t\t\"47\" // 13.0\r\n\t\t], uv6x1\r\n\t],\r\n\t[\"cham\", \"AA\", [0,1,2,3,4,5], , [\"37\",\"38\",\"39\",\"3A\",\"3B\",\"3C\",\"3D\",\"3E\",\"3F\",\"4E\",\"4F\",\"5A\",\"5B\"], \"Cham_(Unicode_block)\"],\r\n\t[\"common indic number forms\", \"A8\", [3], -6, , \"Common_Indic_Number_Forms\"],\r\n\t[\"devanagari\", \"09\", [0,1,2,3,4,5,6,7], , , \"Devanagari_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"71\",\"72\", // 5.1\r\n\t\t\t\"00\",\"4E\",\"55\",\"79\",\"7A\", // 5.2\r\n\t\t\t\"3A\",\"3B\",\"4F\",\"56\",\"57\",\"73\",\"74\",\"75\",\"76\",\"77\", // 6.0\r\n\t\t\t\"78\" // 7.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"dives akuru\", 119, [0,1,2,3,4,5], -6,\r\n\t\t[\"07\",\"08\",\"0A\",\"0B\",\"14\",\"17\",\"36\",\"39\",\"3A\",\"47\",\"48\",\"49\",\"4A\",\"4B\",\"4C\",\"4D\",\"4E\",\"4F\",],\r\n\t\t\"Dives_Akuru_(Unicode_block)\"\r\n\t],\r\n\t[\"dogra\", 118, [0,1,2,3,4], -20, , \"Dogra_(Unicode_block)\"],\r\n\t[\"gujarati\", \"0A\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], 0,\r\n\t\t[\"80\",\"84\",\"8E\",\"92\",\"A9\",\"B1\",\"B4\",\"BA\",\"BB\",\"C6\",\"CA\",\"CE\",\"CF\",\r\n\t\t\"D1\",\"D2\",\"D3\",\"D4\",\"D5\",\"D6\",\"D7\",\"D8\",\"D9\",\"DA\",\"DB\",\"DC\",\"DD\",\"DE\",\"DF\",\r\n\t\t\"E4\",\"E5\",\"F2\",\"F3\",\"F4\",\"F5\",\"F6\",\"F7\",\"F8\"],\r\n\t\t\"Gujarati_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"F0\", // 6.1\r\n\t\t\t\"F9\", // 8.0\r\n\t\t\t\"FA\",\"FB\",\"FC\",\"FD\",\"FE\",\"FF\", // 10.0\r\n\t\t], uv4x0\r\n\t],\r\n\t[\"gurmukhi\", \"0A\", [0,1,2,3,4,5,6,7], -9,\r\n\t\t[\"00\",\"04\",\"0B\",\"0C\",\"0D\",\"0E\",\"11\",\"12\",\"29\",\"31\",\"34\",\"37\",\"3A\",\"3B\",\"3D\",\r\n\t\t\"43\",\"44\",\"45\",\"46\",\"49\",\"4A\",\"4E\",\"4F\",\"50\",\"52\",\"53\",\"54\",\"55\",\"56\",\"57\",\"58\",\r\n\t\t\"5D\",\"5F\",\"60\",\"61\",\"62\",\"63\",\"64\",\"65\"], \"Gurmukhi_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"51\",\"75\", // 5.1\r\n\t\t\t\"76\", // 11.0\r\n\t\t], uv4x0\r\n\t],\r\n\t[\"hanifi rohingya\", \"10D\", [0,1,2,3], -6, [\"28\",\"29\",\"2A\",\"2B\",\"2C\",\"2D\",\"2E\",\"2F\"], \"Hanifi_Rohingya__(Unicode_block)\"],\r\n\t[\"hanunoo\", 17, [2,3], -9, , \"Hanunoo_(Unicode_block)\"],\r\n\t[\"javanese\", \"A9\", [8,9,\"A\",\"B\",\"C\",\"D\"], 0, [\"CE\",\"DA\",\"DB\",\"DC\",\"DD\"], \"Javanese_(Unicode_block)\"],\r\n\t[\"kaithi\", 110, [8,9,\"A\",\"B\",\"C\"], -2, [\"C3\",\"C4\",\"C5\",\"C6\",\"C7\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\"], \"Kaithi_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"CD\", // 11.0\r\n\t\t\t\"C2\", // 14.0\r\n\t\t], uv5x2\r\n\t],\r\n\t[\"kannada\", \"0C\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -12,\r\n\t\t[\"8D\",\"91\",\"A9\",\"B4\",\"BA\",\"BB\",\"C5\",\"C9\",\"CE\",\"CF\",\r\n\t\t\"D0\",\"D1\",\"D2\",\"D3\",\"D4\",\"D7\",\"D8\",\"D9\",\"DA\",\"DB\",\"DC\",\"DF\",\"E4\",\"E5\",\"F0\"],\r\n\t\t\"Kannada_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"81\", // 7.0\r\n\t\t\t\"80\", // 9.0\r\n\t\t\t\"84\", // 11.0\r\n\t\t\t\"DD\", // 14.0\r\n\t\t\t\"F3\", // 15.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"kawi\", \"11F\", [0,1,2,3,4,5], -6, [\"11\",\"3B\",\"3C\",\"3D\"], \"Kawi_(Unicode_block)\"],\r\n\t[\"kayah li\", \"A9\", [0,1,2], , , \"Kayah_Li_(Unicode_block)\"],\r\n\t[\"khmer\", 17, [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -6,\r\n\t\t[\"DE\",\"DF\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\"], \"Khmer_(Unicode_block)\", , ,\r\n\t\t[\"B4\",\"B5\"] // ignore: KIV AQ, KIV AA\r\n\t],\r\n\t[\"khmer symbols\", 19, [\"E\",\"F\"], , , \"Khmer_Symbols\"],\r\n\t[\"khojki\", 112, [0,1,2,3,4], -14, [\"12\"], \"Khojki_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"3E\", // 9.0\r\n\t\t\t\"3F\",\"40\",\"41\", // 15.0\r\n\t\t], uv7x0\r\n\t],\r\n\t[\"khudawadi\", 112, [\"B\",\"C\",\"D\",\"E\",\"F\"], -6, [\"EB\",\"EC\",\"ED\",\"EE\",\"EF\"], \"Khudawadi_(Unicode_block)\"],\r\n\t[\"lao\", \"0E\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -32,\r\n\t\t[\"80\",\"83\",\"85\",\"8B\",\"A4\",\"A6\",\"BE\",\"BF\",\"C5\",\"C7\",\"CF\",\"DA\",\"DB\"],\r\n\t\t\"Lao_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"DE\",\"DF\", // 6.1\r\n\t\t\t\"86\",\"89\",\"8C\",\"8E\",\"8F\",\"90\",\"91\",\"92\",\"93\",\"98\",\"A0\",\"A8\",\"A9\",\"AC\",\"BA\", // 12.0\r\n\t\t\t\"CE\", // 15.0\r\n\t\t], uv1x0x1\r\n\t],\r\n\t[\"lepcha\", \"1C\", [0,1,2,3,4], , [\"38\",\"39\",\"3A\",\"4A\",\"4B\",\"4C\"], \"Lepcha_(Unicode_block)\"],\r\n\t[\"limbu\", 19, [0,1,2,3,4], , [\"1F\",\"2C\",\"2D\",\"2E\",\"2F\",\"3C\",\"3D\",\"3E\",\"3F\",\"41\",\"42\",\"43\"], \"Limbu_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"1D\",\"1E\", // 7.0\r\n\t\t], uv4x0\r\n\t],\r\n\t[\"mahajani\", 111, [5,6,7], -9, , \"Mahajani_(Unicode_block)\"],\r\n\t[\"malayalam\", \"0D\", [0,1,2,3,4,5,6,7], 0,\r\n\t\t[\"0D\",\"11\",\"45\",\"49\",\"50\",\"51\",\"52\",\"53\",\"64\",\"65\"], \"Malayalam_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"29\",\"3A\",\"4E\", // 6.0\r\n\t\t\t\"01\", // 7.0\r\n\t\t\t\"5F\", // 8.0\r\n\t\t\t\"4F\",\"54\",\"55\",\"56\",\"58\",\"59\",\"5A\",\"5B\",\"5C\",\"5D\",\"5E\",\"76\",\"77\",\"78\", // 9.0\r\n\t\t\t\"00\",\"3B\",\"3C\", // 10.0\r\n\t\t\t\"04\", // 13.0\r\n\t\t], uv5x1,\r\n\t\t[\"4E\"] // ignore: letter dot reph\r\n\t],\r\n\t[\"meetei mayek\", \"AB\", [\"C\",\"D\",\"E\",\"F\"], -6, [\"EE\",\"EF\"], \"Meetei_Mayek_(Unicode_block)\"],\r\n\t[\"modi\", 116, [0,1,2,3,4,5], -6, [\"45\",\"46\",\"47\",\"48\",\"49\",\"4A\",\"4B\",\"4C\",\"4D\",\"4E\",\"4F\"], \"Modi_(Unicode_block)\"],\r\n\t[\"mro\", \"16A\", [4,5,6], , [\"5F\",\"6A\",\"6B\",\"6C\",\"6D\"], \"Mro_(Unicode_block)\"],\r\n\t[\"multani\", 112, [8,9,\"A\"], -6, [\"87\",\"89\",\"8E\",\"9E\"], \"Multani_(Unicode_block)\"],\r\n\t[\"myanmar\", 10, [0,1,2,3,4,5,6,7,8,9], 0, , \"Myanmar_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"9A\",\"9B\",\"9C\",\"9D\", // 5.2\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"myanmar extended-a\", \"AA\", [6,7], , , \"Myanmar_Extended-A\",\r\n\t\t[\r\n\t\t\t\"7C\",\"7D\",\"7E\",\"7F\", // 7.0\r\n\t\t], uv5x2\r\n\t],\r\n\t[\"myanmar extended-b\", \"A9\", [\"E\",\"F\"], -1, , \"Myanmar_Extended-B\"],\r\n\t[\"nag mundari\", \"1E4\", [\"D\",\"E\",\"F\"], -6, , \"Nag_Mundari_(Unicode_block)\"],\r\n\t[\"new tai lue\", 19, [8,9,\"A\",\"B\",\"C\",\"D\"], 0,\r\n\t\t[\"AC\",\"AD\",\"AE\",\"AF\",\"CA\",\"CB\",\"CC\",\"CD\",\"CE\",\"CF\",\"DB\",\"DC\",\"DD\"],\r\n\t\t\"New_Tai_Lue_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"AA\",\"AB\",\"DA\", // 5.2\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"newa\", 114, [0,1,2,3,4,5,6,7], -30, [\"5C\",], \"Newa_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"5E\", // 11.0\r\n\t\t\t\"5F\", // 12.0\r\n\t\t\t\"5A\",\"60\",\"61\", // 13.0\r\n\t\t], uv9x0\r\n\t],\r\n\t[\"ol chiki\", \"1C\", [5,6,7], , , \"Ol_Chiki_(Unicode_block)\"],\r\n\t[\"oriya\", \"0B\", [0,1,2,3,4,5,6,7], -8,\r\n\t\t[\"00\",\"04\",\"0D\",\"0E\",\"11\",\"12\",\"29\",\"31\",\"34\",\"3A\",\"3B\",\"45\",\"46\",\"49\",\"4A\",\"4E\",\"4F\",\r\n\t\t\"50\",\"51\",\"52\",\"53\",\"54\",\"58\",\"59\",\"5A\",\"5B\",\"5E\",\"64\",\"65\"],\r\n\t\t\"Oriya_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"72\",\"73\",\"74\",\"75\",\"76\",\"77\", // 6.0\r\n\t\t\t\"55\", // 13.0\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"pau cin hau\", \"11A\", [\"C\",\"D\",\"E\",\"F\"], -7, , \"Pau_Cin_Hau_(Unicode_block)\"],\r\n\t[\"phags-pa\", \"A8\", [4,5,6,7], -8, , \"Phags-pa_(Unicode_block)\"],\r\n\t[\"saurashtra\", \"A8\", [8,9,\"A\",\"B\",\"C\",\"D\"], -6, [\"C6\",\"C7\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CD\"], \"Saurashtra_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"C5\", // 9.0\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"sinhala\", \"0D\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -11,\r\n\t\t[\"80\",\"84\",\"97\",\"98\",\"99\",\"B2\",\"BC\",\"BE\",\"BF\",\"C7\",\"C8\",\"C9\",\"CB\",\"CC\",\"CD\",\"CE\",\"D5\",\"D7\",\r\n\t\t\"E0\",\"E1\",\"E2\",\"E3\",\"E4\",\"E5\",\"F0\",\"F1\"],\r\n\t\t\"Sinhala_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"E6\",\"E7\",\"E8\",\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\", // 7.0\r\n\t\t\t\"81\", // 13.0\r\n\t\t], uv3x0\r\n\t],\r\n\t[\"sora sompeng\", 110, [\"D\",\"E\",\"F\"], -6, [\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\"], \"Sora_Sompeng_(Unicode_block)\"],\r\n\t[\"sundanese\", \"1B\", [8,9,\"A\",\"B\"], , ,\r\n\t\t\"Sundanese_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"AB\",\"AC\",\"AD\",\"BA\",\"BB\",\"BC\",\"BD\",\"BE\",\"BF\", // 6.1\r\n\t\t], uv5x1,\r\n\t\t[\"AB\"] // ignore: sign virama\r\n\t],\r\n\t[\"sundanese supplement\", \"1C\", [\"C\"], -8, , \"Sundanese_Supplement\"],\r\n\t[\"syloti nagri\", \"A8\", [0,1,2], -3, , \"Syloti_Nagri_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"2C\", // 13.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"tagalog\", 17, [0,1,], 0, [\"16\",\"17\",\"18\",\"19\",\"1A\",\"1B\",\"1C\",\"1D\",\"1E\"],\r\n\t\t\"Tagalog_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"0D\",\"15\",\"1F\" // 14.0\r\n\t\t], uv3x2\r\n\t],\r\n\t[\"tagbanwa\", 17, [6,7], -12, [\"6D\",\"71\"], \"Tagbanwa_(Unicode_block)\"],\r\n\t[\"tai le\", 19, [5,6,7], -11, [\"6E\",\"6F\"], \"Tai_Le_(Unicode_block)\"],\r\n\t[\"tai tham\", \"1A\", [2,3,4,5,6,7,8,9,\"A\"], -2,\r\n\t\t[\"5F\",\"7D\",\"7E\",\"8A\",\"8B\",\"8C\",\"8D\",\"8E\",\"8F\",\"9A\",\"9B\",\"9C\",\"9D\",\"9E\",\"9F\"], \"Tai_Tham_(Unicode_block)\"\r\n\t],\r\n\t[\"tai viet\", \"AA\", [8,9,\"A\",\"B\",\"C\",\"D\"], ,\r\n\t\t[\"C3\",\"C4\",\"C5\",\"C6\",\"C7\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CD\",\"CE\",\"CF\",\"D0\",\"D1\",\"D2\",\"D3\",\"D4\",\"D5\",\"D6\",\"D7\",\"D8\",\"D9\",\"DA\"], \"Tai_Viet_(Unicode_block)\"\r\n\t],\r\n\t[\"takri\", 116, [8,9,\"A\",\"B\",\"C\"], -6, [\"BA\",\"BB\",\"BC\",\"BD\",\"BE\",\"BF\"], \"Takri_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"B8\", // 12.0\r\n\t\t\t\"B9\", // 14.0\r\n\t\t], uv6x1\r\n\t],\r\n\t[\"tamil\", \"0B\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -5,\r\n\t\t[\"80\",\"81\",\"84\",\"8B\",\"8C\",\"8D\",\"91\",\"96\",\"97\",\"98\",\"9B\",\"9D\",\r\n\t\t\"A0\",\"A1\",\"A2\",\"A5\",\"A6\",\"A7\",\"AB\",\"AC\",\"AD\",\"BA\",\"BB\",\"BC\",\"BD\",\r\n\t\t\"C3\",\"C4\",\"C5\",\"C9\",\"CE\",\"CF\",\r\n\t\t\"D1\",\"D2\",\"D3\",\"D4\",\"D5\",\"D6\",\"D8\",\"D9\",\"DA\",\"DB\",\"DC\",\"DD\",\"DE\",\"DF\",\r\n\t\t\"E0\",\"E1\",\"E2\",\"E3\",\"E4\",\"E5\"], \"Tamil_(Unicode_block)\"\r\n\t],\r\n\t[\"tamil supplement\", \"11F\", [\"C\",\"D\",\"E\",\"F\"], ,\r\n\t\t[\"F2\",\"F3\",\"F4\",\"F5\",\"F6\",\"F7\",\"F8\",\"F9\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\"], \"Tamil_Supplement\"\r\n\t],\r\n\t[\"telugu\", \"0C\", [0,1,2,3,4,5,6,7], 0,\r\n\t\t[\"0D\",\"11\",\"29\",\"3A\",\"3B\",\"45\",\"49\",\"4E\",\"4F\",\r\n\t\t\"50\",\"51\",\"52\",\"53\",\"54\",\"57\",\"5B\",\"5C\",\"5E\",\"5F\",\r\n\t\t\"64\",\"65\",\"70\",\"71\",\"72\",\"73\",\"74\",\"75\",\"76\"], \"Telugu_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"00\",\"34\", // 7.0\r\n\t\t\t\"5A\", // 8.0\r\n\t\t\t\"04\", // 11.0\r\n\t\t\t\"77\", // 12.0\r\n\t\t\t\"3C\",\"5D\", // 14.0\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"thai\", \"0E\", [0,1,2,3,4,5,6,7], -36, [\"00\",\"3B\",\"3C\",\"3D\",\"3E\"], \"Thai_(Unicode_block)\"],\r\n\t[\"tibetan\", \"0F\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -37,\r\n\t\t[\"48\",\"6D\",\"6E\",\"6F\",\"70\",\"98\",\"BD\",\"CD\"],\r\n\t\t\"Tibetan_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"D0\",\"D1\", // 4.1\r\n\t\t\t\"6B\",\"6C\",\"CE\",\"D2\",\"D3\",\"D4\", // 5.1\r\n\t\t\t\"D5\",\"D6\",\"D7\",\"D8\", // 5.2\r\n\t\t\t\"8C\",\"8D\",\"8E\",\"8F\",\"D9\",\"DA\", // 6.0\r\n\t\t], uv3x0,\r\n\t\t[\"0C\"] // ignore: mark delimiter tsheg bstar\r\n\t],\r\n\t[\"tirhuta\", 114, [8,9,\"A\",\"B\",\"C\",\"D\"], -6, [\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CD\",\"CE\",\"CF\"], \"Tirhuta_(Unicode_block)\"],\r\n\t[\"warang citi\", 118, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , [\"F3\",\"F4\",\"F5\",\"F6\",\"F7\",\"F8\",\"F9\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\"], \"Warang_Citi_(Unicode_block)\"],\r\n\r\n\t// CYRILLIC\r\n\t[\"cyrillic\"],\r\n\t[\"cyrillic\", \"04\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Cyrillic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"CF\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\",\"FF\", // 5.0\r\n\t\t\t\"87\", // 5.1\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"cyrillic extended-a\", \"2D\", [\"E\",\"F\"], , , \"Cyrillic_Extended-A\"],\r\n\t[\"cyrillic extended-b\", \"A6\", [4,5,6,7,8,9], , ,\r\n\t\t\"Cyrillic_Extended-B\",\r\n\t\t[\r\n\t\t\t\"60\",\"61\", // 6.0\r\n\t\t\t\"74\",\"75\",\"76\",\"77\",\"78\",\"79\",\"7A\",\"7B\",\"9F\", // 6.1\r\n\t\t\t\"98\",\"99\",\"9A\",\"9B\",\"9C\",\"9D\", // 7.0\r\n\t\t\t\"9E\", // 8.0\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"cyrillic extended-c\", \"1C\", [8], -7, , \"Cyrillic_Extended-C\"],\r\n\t[\"cyrillic supplement\", \"05\", [0,1,2], , ,\r\n\t\t\"Cyrillic_Supplement\",\r\n\t\t[\r\n\t\t\t\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\"1A\",\"1B\",\"1C\",\"1D\",\"1E\",\"1F\",\"20\",\"21\",\"22\",\"23\", // 5.1\r\n\t\t\t\"24\",\"25\", // 5.2\r\n\t\t\t\"26\",\"27\", // 6.0\r\n\t\t\t\"28\",\"29\",\"2A\",\"2B\",\"2C\",\"2D\",\"2E\",\"2F\", // 7.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"glagolitic\", \"2C\", [0,1,2,3,4,5], , , \"Glagolitic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"2F\",\"5F\" // 14.0\r\n\t\t], uv4x1\r\n\t],\r\n\r\n\t// EAST ASIAN\r\n\t[\"east asian\"],\r\n\t[\"bopomofo\", 31, [0,1,2], 0, [\"00\",\"01\",\"02\",\"03\",\"04\"], \"Bopomofo_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"2D\", // 5.1\r\n\t\t\t\"2E\", // 10.0\r\n\t\t\t\"2F\", // 11.0\r\n\t\t], uv1x0x0\r\n\t],\r\n\t[\"bopomofo extended\", 31, [\"A\",\"B\"], , , \"Bopomofo_Extended\",\r\n\t\t[\r\n\t\t\t\"B8\",\"B9\",\"BA\", // 6.0\r\n\t\t\t\"BB\",\"BC\",\"BD\",\"BE\",\"BF\", // 13.0\r\n\t\t], uv3x0\r\n\t],\r\n\t[\"cjk compatibility\", 33, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"CJK_Compatibility\",\r\n\t\t[\r\n\t\t\t\"77\",\"78\",\"79\",\"7A\",\"DE\",\"DF\",\"FF\", // 4.0\r\n\t\t], uv1x1\r\n\t],\r\n\t[\"cjk compatibility forms\", \"FE\", [3,4], , , \"CJK_Compatibility_Forms\"],\r\n\t[\"cjk compatibility ideographs\", \"F\",\r\n\t\t//[90,91,92,93,94,95,96,97,98,99,\"9A\",\"9B\",\"9C\",\"9D\",\"9E\",\"9F\",\"A0\",\"A1\",\"A2\",\"A3\",\"A4\",\"A5\",\"A6\",\"A7\",\"A8\",\"A9\",\"AA\",\"AB\",\"AC\",\"AD\",\"AE\",\"AF\"], -38, [\"A6E\",\"A6F\"], // full\r\n\t\t[90,91,92,93,94,\"A2\",\"A6\",\"AF\"], -6, [\"A6E\",\"A6F\"], // selective\r\n\t\t\"CJK_Compatibility_Ideographs\",\r\n\t\t[\r\n\t\t\t\"A6B\",\"A6C\",\"A6D\", // 5.2\r\n\t\t\t\"A2E\",\"A2F\", // 6.1\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"cjk compatibility ideographs supplement\", \"2F\",\r\n\t\t//[80,81,82,83,84,85,86,87,88,89,\"8A\",\"8B\",\"8C\",\"8D\",\"8E\",\"8F\", 90,91,92,93,94,95,96,97,98,\"9A\",\"9B\",\"9C\",\"9D\",\"9E\",\"9F\",\"A0\",\"A1\"], -2, , // full\r\n\t\t[82,83,84,87,88,89,\"9B\",], , , // selective\r\n\t\t\"CJK_Compatibility_Ideographs_Supplement\"\r\n\t],\r\n\t[\"cjk symbols and punctuation\", 30, [0,1,2,3], , , \"CJK_Symbols_and_Punctuation\", , ,\r\n\t\t[\"00\"] // ignore: ID SP\r\n\t],\r\n\t[\"cjk radicals supplement\", \"2E\", [8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -12, [\"9A\"], \"CJK_Radicals_Supplement\"],\r\n\r\n\t[\"cjk unified ideographs\", \"4E\", [0,1,2,3], , , \"CJK_Unified_Ideographs_(Unicode_block)\"],\r\n\t[\"cjk unified ideographs extension-a\", 34, [0,1,2,3], , , \"CJK_Unified_Ideographs_Extension_A\"],\r\n\t[\"cjk unified ideographs extension-b\", 200, [0,1,2,3], , , \"CJK_Unified_Ideographs_Extension_B\"],\r\n\r\n\t[\"enclosed cjk letters and months\", \"32\",\r\n\t\t// [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , [\"1F\"], // full\r\n\t\t[1,2,4,5,7,\"A\",\"C\",\"F\"], , [\"1F\"], // selective\r\n\t\t\"Enclosed_CJK_Letters_and_Months\",\r\n\t\t[\r\n\t\t\t\"1D\",\"1E\",\"50\",\"7C\",\"7D\",\"CC\",\"CD\",\"CE\",\"CF\", // 4.0\r\n\t\t\t\"7E\", // 4.1\r\n\t\t\t\"44\",\"45\",\"46\",\"47\",\"48\",\"49\",\"4A\",\"4B\",\"4C\",\"4D\",\"4E\",\"4F\", // 5.2\r\n\t\t\t\"FF\", // 12.1\r\n\t\t], uv3x2\r\n\t],\r\n\t[\"hangul compatibility jamo\", 31,\r\n\t\t// [3,4,5,6,7,8], , [\"30\"], \"Hangul_Compatibility_Jamo\", , , // full\r\n\t\t[3,4,5,6], , [\"30\",\"8F\"], \"Hangul_Compatibility_Jamo\", , , // selective\r\n\t\t[\"64\"] // ignore: HF\r\n\t],\r\n\t[\"hangul jamo\", 11,\r\n\t\t// [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , // full\r\n\t\t[5,\"A\",\"B\",\"F\"], , , // selective\r\n\t\t\"Hangul_Jamo_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"5A\",\"5B\",\"5C\",\"5D\",\"5E\",\"A3\",\"A4\",\"A5\",\"A6\",\"A7\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\",\"FF\", // 5.2\r\n\t\t], uv1x1,\r\n\t\t[\"5F\",\"60\"] // ignore: HCF hangul choseong filler, HJF hangul jungseong filler\r\n\t],\r\n\t[\"hangul syllables\", \"AC\", [0,1,2,3], , , \"Hangul_Syllables\"],\r\n\t[\"hiragana\", 30,\r\n\t\t// [4,5,6,7,8,9], , // full\r\n\t\t[6,7,8,9], , // selective\r\n\t\t[\"40\",\"97\",\"98\"], \"Hiragana_(Unicode_block)\"\r\n\t],\r\n\t[\"ideographic description characters\", \"2F\", [\"F\"], -4, , \"Ideographic_Description_Characters_(Unicode_block)\"],\r\n\t[\"kanbun\", 31, [9], , , \"Kanbun_(Unicode_block)\"],\r\n\t[\"kangxi radicals\", \"2F\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\"], -10, , \"Kangxi_Radicals_(Unicode_block)\"],\r\n\t[\"katakana\", 30, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Katakana_(Unicode_block)\"],\r\n\t[\"katakana phonetic extensions\", 31, [\"F\"], , , \"Katakana_Phonetic_Extensions\"],\r\n\t[\"lisu\", \"A4\", [\"D\",\"E\",\"F\"], , , \"Lisu_(Unicode_block)\"],\r\n\t[\"vertical forms\", \"FE\", [1], -6, , \"Vertical_Forms\"],\r\n\t[\"yi radicals\", \"A4\", [9,\"A\",\"B\",\"C\"], -9, , \"Yi_Radicals\"],\r\n\t[\"yi syllables\", \"A0\", [0,1,2,3], , , \"Yi_Syllables\"],\r\n\r\n\t// GEORGIAN\r\n\t[\"georgian\"],\r\n\t[\"georgian\", 10, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], ,\r\n\t\t[\"C6\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CE\",\"CF\"], \"Georgian_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"C7\",\"CD\",\"FD\",\"FE\",\"FF\", // 6.1\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"georgian extended\", \"1C\", [9,\"A\",\"B\"], , [\"BB\",\"BC\"], \"Georgian_Extended\"],\r\n\t[\"georgian supplement\", \"2D\", [0,1,2], -2, [\"26\",\"28\",\"29\",\"2A\",\"2B\",\"2C\"],\r\n\t\t\"Georgian_Supplement\",\r\n\t\t[\r\n\t\t\t\"27\",\"2D\" // 6.1\r\n\t\t], uv4x1\r\n\t],\r\n\r\n\t// GREEK\r\n\t[\"greek\"],\r\n\t[\"greek and coptic\", \"03\", [7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], ,\r\n\t\t[\"78\",\"79\",\"80\",\"81\",\"82\",\"83\",\"8B\",\"8D\",\"A2\"], \"Greek_and_Coptic\",\r\n\t\t[\r\n\t\t\t\"70\",\"71\",\"72\",\"73\",\"76\",\"77\",\"CF\", // 5.1\r\n\t\t\t\"7F\", // 7.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"greek extended\", \"1F\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -1,\r\n\t\t[\"16\",\"17\",\"1E\",\"1F\",\"46\",\"47\",\"4E\",\"4F\",\"58\",\"5A\",\"5C\",\"5E\",\"7E\",\"7F\",\"B5\",\"C5\",\"D4\",\"D5\",\"DC\",\"F0\",\"F1\",\"F5\"],\r\n\t\t\"Greek_Extended\"\r\n\t],\r\n\r\n\t// LATIN\r\n\t[\"latin\"],\r\n\t[\"basic latin\", \"00\", [2,3,4,5,6,7], , , \"Basic_Latin_(Unicode_block)\", , ,\r\n\t\t[\"20\",\"7F\"] // ignore: SP, DEL\r\n\t],\r\n\t[\"latin-1 supplement\", \"00\", [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Latin-1_Supplement\", , ,\r\n\t\t[\"A0\",\"AD\"], // ignore: NBSP, SHY\r\n\t],\r\n\t[\"latin extended-a\", \"01\", [0,1,2,3,4,5,6,7], , , \"Latin_Extended-A\"],\r\n\t[\"latin extended-b\", 0, [18,19,\"1A\",\"1B\",\"1C\",\"1D\",\"1E\",\"1F\",20,21,22,23,24], , , \"Latin_Extended-B\"],\r\n\t[\"latin extended-c\", \"2C\", [6,7], , , \"Latin_Extended-C\"],\r\n\t[\"latin extended-d\", \"A7\", [2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , \r\n\t\t[\r\n\t\t\t\"CB\",\"CC\",\"CD\",\"CE\",\"CF\",\"D2\",\"D4\",\"DA\",\"DB\",\"DC\",\"DD\",\"DE\",\"DF\",\r\n\t\t\t\"E0\",\"E1\",\"E2\",\"E3\",\"E4\",\"E5\",\"E6\",\"E7\",\"E8\",\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\",\r\n\t\t\t\"F0\",\"F1\"\r\n\t\t], \"Latin_Extended-D\",\r\n\t\t[\r\n\t\t\t\"AE\", // 9.0\r\n\t\t\t\"A7\",\"B8\",\"B9\", // 11.0\r\n\t\t\t\"BA\",\"BB\",\"BC\",\"BD\",\"BE\",\"BF\",\"C2\",\"C3\",\"C4\",\"C5\",\"C6\", // 12.0\r\n\t\t\t\"C7\",\"C8\",\"C9\",\"CA\",\"F5\",\"F6\", // 13.0\r\n\t\t\t\"C0\",\"C1\",\"D0\",\"D1\",\"D3\",\"D5\",\"D6\",\"D7\",\"D8\",\"D9\",\"F2\",\"F3\",\"F4\", // 14.0\r\n\t\t], uv8x0\r\n\t],\r\n\t[\"latin extended-e\", \"AB\", [3,4,5,6], -4 , , \"Latin_Extended-E\",\r\n\t\t[\r\n\t\t\t\"66\",\"67\", // 12.0\r\n\t\t\t\"68\",\"69\",\"6A\",\"6B\", // 13.0\r\n\t\t], uv8x0\r\n\t],\r\n\t[\"latin extended-f\", 107, [8,9,\"A\",\"B\"], -5 , [\"86\",\"B1\"], \"Latin_Extended-F\"],\r\n\t[\"latin extended-g\", \"1DF\", [0,1,2], -5 , [\"1F\",\"20\",\"21\",\"22\",\"23\",\"24\"], \"Latin_Extended-G\"],\r\n\t[\"latin extended additional\", \"1E\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Latin_Extended_Additional\",\r\n\t\t[\r\n\t\t\t\"9C\",\"9D\",\"9E\",\"9F\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\",\"FF\", // 5.1\r\n\t\t], uv2x0\r\n\t],\r\n\r\n\t// PHONETIC\r\n\t[\"phonetic\"],\r\n\t[\"ipa extensions\", \"02\", [5,6,7,8,9,\"A\"], , , \"IPA_Extensions\"],\r\n\t[\"phonetic extensions\", \"1D\", [0,1,2,3,4,5,6,7], , , \"Phonetic_Extensions\"],\r\n\t[\"phonetic extensions supplement\", \"1D\", [8,9,\"A\",\"B\"], , , \"Phonetic_Extensions_Supplement\"],\r\n\t[\"spacing modifier letters\", \"02\", [\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Spacing_Modifier_Letters\"],\r\n\r\n\t// SEMITIC\r\n\t[\"semitic\"],\r\n\t[\"arabic\", \"06\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Arabic_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"20\",\"5F\", // 6.0\r\n\t\t\t\"04\", // 6.1\r\n\t\t\t\"1C\", // 6.3\r\n\t\t\t\"05\", // 7.0\r\n\t\t\t\"1D\", // 14.0\r\n\t\t], uv5x1,\r\n\t\t[\"1C\"] // ALM\r\n\t],\r\n\t[\"arabic presentation forms-a\", \"FB\", [5,6,7,8,9,\"A\"], , , \"Arabic_Presentation_Forms-A\"],\r\n\t[\"arabic supplement\", \"07\", [5,6,7], , , \"Arabic_Supplement\",\r\n\t\t[\r\n\t\t\t\"6E\",\"6F\",\"70\",\"71\",\"72\",\"73\",\"74\",\"75\",\"76\",\"77\",\"78\",\"79\",\"7A\",\"7B\",\"7C\",\"7D\",\"7E\",\"7F\", // 5.1\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"hebrew\", \"05\", [9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], -11,\r\n\t\t[\"90\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CD\",\"CE\",\"CF\",\"EB\",\"EC\",\"ED\",\"EE\"],\r\n\t\t\"Hebrew_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"EF\", // 11.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"mandaic\", \"08\", [4,5], -1, [\"5C\",\"5D\"], \"Mandaic_(Unicode_block)\"],\r\n\t[\"samaritan\", \"08\", [0,1,2,3], -1, [\"2E\",\"2F\"], \"Samaritan_(Unicode_block)\"],\r\n\t[\"syriac\", \"07\", [0,1,2,3,4], 0, [\"0E\",\"4B\",\"4C\"], \"Syriac_(Unicode_block)\"],\r\n\r\n\t// symbols\r\n\t[\"symbols\"],\r\n\t[\"alphabetic presentation forms\", \"FB\", [0,1,2,3,4], ,\r\n\t\t[\"07\",\"08\",\"09\",\"0A\",\"0B\",\"0C\",\"0D\",\"0E\",\"0F\",\"10\",\"11\",\"12\",\"18\",\"19\",\"1A\",\"1B\",\"1C\",\"37\",\"3D\",\"3F\",\"42\",\"45\"],\r\n\t\t\"Alphabetic_Presentation_Forms\"\r\n\t],\r\n\t[\"arrows\", 21, [9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Arrows_(Unicode_block)\"],\r\n\t[\"block elements\", 25, [8,9], , , \"Block_Elements\"],\r\n\t[\"box drawing\", 25, [0,1,2,3,4,5,6,7], , , \"Box_Drawing\"],\r\n\t[\"braille patterns\", 28, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\"Braille_Patterns\"],\r\n\t[\"currency symbols\", 20, [\"A\",\"B\",\"C\"], -15, , \"Currency_Symbols_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"B6\",\"B7\",\"B8\", // 5.2\r\n\t\t\t\"B9\", // 6.0\r\n\t\t\t\"BA\", // 6.2\r\n\t\t\t\"BB\",\"BC\",\"BD\", // 7.0\r\n\t\t\t\"BE\", // 8.0\r\n\t\t\t\"BF\", // 10.0\r\n\t\t\t\"C0\", // 14.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"dingbats\", 27, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\"], , , \"Dingbat\",\r\n\t\t[\r\n\t\t\t\"57\", // 5.2\r\n\t\t\t\"05\",\"0A\",\"0B\",\"28\",\"4C\",\"4E\",\"53\",\"54\",\"55\",\"5F\",\"60\",\"95\",\"96\",\"97\",\"B0\",\"BF\", // 6.0\r\n\t\t\t\"00\", // 7.0\r\n\t\t], uv3x2\r\n\t],\r\n\t[\"enclosed alphanumerics\", 24, [6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Enclosed_Alphanumerics\"],\r\n\t[\"general punctuation\", 20, [1,2,3,4,5], , , \"General_Punctuation\", , ,\r\n\t\t[\r\n\t\t\t\"11\", // NB\r\n\t\t\t\"28\",\"29\", // L SEP, P SEP\r\n\t\t\t\"2A\",\"2B\", // LRE, RLE\r\n\t\t\t\"2C\", // PDF\r\n\t\t\t\"2D\",\"2E\", // LRO, RLO\r\n\t\t\t\"2F\", // NNB SP\r\n\t\t\t\"5F\", // MM SP\r\n\t\t],\r\n\t],\r\n\t[\"geometric shapes\", 25, [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , , \"Geometric_Shapes_(Unicode_block)\"],\r\n\t[\"letterlike symbols\", 21, [0,1,2,3,4], , , \"Letterlike_Symbols\",\r\n\t\t[\r\n\t\t\t\"3C\",\"4C\", // 4.1\r\n\t\t\t\"4D\",\"4E\", // 5.0\r\n\t\t\t\"4F\", // 5.1\r\n\t\t], uv4x0\r\n\t],\r\n\t[\"mathematical alphanumeric symbols\", \"1D4\", [0,1,2,3,4,5,6,7,8,9], , [\"55\",\"9D\"],\r\n\t\t\"Mathematical_Alphanumeric_Symbols\"\r\n\t],\r\n\t[\"mathematical operators\", 22, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Mathematical_Operators_(Unicode_block)\"\r\n\t],\r\n\t[\"miscellaneous mathematical symbols-a\", 27, [\"C\",\"D\",\"E\"], , ,\r\n\t\t\"Miscellaneous_Mathematical_Symbols-A\",\r\n\t\t[\r\n\t\t\t\"C0\",\"C1\",\"C2\",\"C3\",\"C4\",\"C5\",\"C6\", // 4.1\r\n\t\t\t\"CA\",\"C7\",\"C8\",\"C9\", // 5.0\r\n\t\t\t\"CC\",\"EC\",\"ED\",\"EE\",\"EF\", // 5.1\r\n\t\t\t\"CE\",\"CF\", // 6.0\r\n\t\t\t\"CB\",\"CD\", // 6.1\r\n\t\t], uv3x2\r\n\t],\r\n\t[\"miscellaneous mathematical symbols-b\", 29, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Miscellaneous_Mathematical_Symbols-B\"\r\n\t],\r\n\t[\"miscellaneous symbols\", 26, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Miscellaneous_Symbols\",\r\n\t\t[\r\n\t\t\t\"B2\", // 5.0\r\n\t\t\t\"9D\",\"B3\",\"B4\",\"B5\",\"B6\",\"B7\",\"B8\",\"B9\",\"BA\",\"BB\",\"BC\",\"C0\",\"C1\",\"C2\",\"C3\", // 5.1\r\n\t\t\t\"9E\",\"9F\",\"BD\",\"BE\",\"BF\",\"C4\",\"C5\",\"C6\",\"C7\",\"C8\",\"C9\",\"CA\",\"CB\",\"CC\",\"CD\",\"CF\",\r\n\t\t\t\"D0\",\"D1\",\"D2\",\"D3\",\"D4\",\"D5\",\"D6\",\"D7\",\"D8\",\"D9\",\"DA\",\"DB\",\"DC\",\"DD\",\"DE\",\"DF\",\r\n\t\t\t\"E0\",\"E1\",\"E3\",\"E8\",\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\",\r\n\t\t\t\"F0\",\"F1\",\"F2\",\"F3\",\"F4\",\"F5\",\"F6\",\"F7\",\"F8\",\"F9\",\"FA\",\"FB\",\"FC\",\"FD\",\"FE\",\"FF\", // 5.2\r\n\t\t\t\"CE\",\"E2\",\"E4\",\"E5\",\"E6\",\"E7\", // 6.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"miscellaneous technical\", 23, [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], ,\r\n\t\t[\"29\",\"2A\"], // 2 deprecated 5.2\r\n\t\t\"Miscellaneous_Technical\",\r\n\t\t[\r\n\t\t\t\"DC\",\"DD\",\"DE\",\"DF\",\"E0\",\"E1\",\"E2\",\"E3\",\"E4\",\"E5\",\"E6\",\"E7\", // 5.0\r\n\t\t\t\"E8\", // 5.2\r\n\t\t\t\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\",\"F0\",\"F1\",\"F2\",\"F3\", // 6.0\r\n\t\t\t\"F4\",\"F5\",\"F6\",\"F7\",\"F8\",\"F9\",\"FA\", // 7.0\r\n\t\t\t\"FB\",\"FC\",\"FD\",\"FE\", // 9.0\r\n\t\t\t\"FF\", // 10.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"number forms\", 21, [5,6,7,8], -4, , \"Number_Forms\",\r\n\t\t[\r\n\t\t\t\"85\",\"86\",\"87\",\"88\", // \r\n\t\t\t\"50\",\"51\",\"52\",\"89\", // 5.2\r\n\t\t\t\"8A\",\"8B\", // 8.0\r\n\t\t], uv5x0\r\n\t],\r\n\t[\"optical character recognition\", 24, [4,5], -21, ,\"Optical_Character_Recognition_(Unicode_block)\"],\r\n\t[\"superscripts and subscripts\", 20, [7,8,9], -3, [\"72\",\"73\",\"8F\"],\r\n\t\t\"Superscripts_and_Subscripts_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"95\",\"96\",\"97\",\"98\",\"99\",\"9A\",\"9B\",\"9C\", // 6.0\r\n\t\t], uv4x1\r\n\t],\r\n\t[\"supplemental arrows-a\", 27, [\"F\"], , ,\"Supplemental_Arrows-A\"],\r\n\t[\"supplemental arrows-b\", 29, [0,1,2,3,4,5,6,7], , ,\"Supplemental_Arrows-B\"],\r\n\t[\"supplemental mathematical operators\", \"2A\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Supplemental_Mathematical_Operators\"\r\n\t],\r\n\r\n\t// MISC\r\n\t[\"misc\"],\r\n\t[\"armenian\", \"05\", [3,4,5,6,7,8], , [\"30\",\"57\",\"58\",\"8B\",\"8C\"],\r\n\t\t\"Armenian_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"8F\", // 6.1\r\n\t\t\t\"8D\",\"8E\", // 7.0\r\n\t\t\t\"60\",\"88\", // 11.0\r\n\t\t], uv3x0\r\n\t],\r\n\t[\"combining diacritical marks\", \"03\", [0,1,2,3,4,5,6], , , \"Combining_Diacritical_Marks\", , ,\r\n\t\t[\"4F\"] // ignore: CGJ\r\n\t],\r\n\t[\"combining diacritical marks for symbols\", 20, [\"D\",\"E\",\"F\"], -15, ,\r\n\t\t\"Combining_Diacritical_Marks_for_Symbols\",\r\n\t\t[\r\n\t\t\t\"E2\",\"E3\", // 3.0\r\n\t\t\t\"E4\",\"E5\",\"E6\",\"E7\",\"E8\",\"E9\",\"EA\", // 3.2\r\n\t\t\t\"EB\", // 4.1\r\n\t\t\t\"EC\",\"ED\",\"EE\",\"EF\", // 5.0\r\n\t\t\t\"F0\", // 5.1\r\n\t\t], uv1x0x0\r\n\t],\r\n\t[\"combining diacritical marks supplement\", \"1D\", [\"C\",\"D\",\"E\",\"F\"], , ,\r\n\t\t\"Combining_Diacritical_Marks_Supplement\",\r\n\t\t[\r\n\t\t\t\"\", // 5.1\r\n\t\t\t\"FD\", // 5.2\r\n\t\t\t\"FC\", // 6.0\r\n\t\t\t\"E7\",\"E8\",\"E9\",\"EA\",\"EB\",\"EC\",\"ED\",\"EE\",\"EF\",\"F0\",\"F1\",\"F2\",\"F3\",\"F4\",\"F5\", // 7.0\r\n\t\t\t\"FB\", // 9.0\r\n\t\t\t\"F6\",\"F7\",\"F8\",\"F9\", // 10.0\r\n\t\t\t\"FA\", // 14.0\r\n\t\t], uv5x1\r\n\t],\r\n\t[\"control pictures\", 24, [0,1,2,3], -25, , \"Control_Pictures\"],\r\n\t[\"halfwidth and fullwidth forms\", \"FF\", [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\"], -1,\r\n\t\t[\"00\",\"BF\",\"C0\",\"C1\",\"C8\",\"C9\",\"D0\",\"D1\",\"D8\",\"D9\",\"DD\",\"DE\",\"DF\",\"E7\"],\r\n\t\t\"Halfwidth_and_Fullwidth_Forms_(Unicode_block)\", , ,\r\n\t\t[\"A0\"] // ignore: HW HF\r\n\t],\r\n\t[\"mongolian\", 18, [0,1,2,3,4,5,6,7,8,9,\"A\"], -5,\r\n\t\t[\"1A\",\"1B\",\"1C\",\"1D\",\"1E\",\"1F\",\"79\",\"7A\",\"7B\",\"7C\",\"7D\",\"7E\",\"7F\"],\r\n\t\t\"Mongolian_(Unicode_block)\",\r\n\t\t[\r\n\t\t\t\"AA\", // 5.1\r\n\t\t\t\"78\", // 11.0\r\n\t\t\t\"05\", // 14.0\r\n\t\t], uv3x0,\r\n\t\t[\"0B\",\"0C\",\"0D\",\"0E\",\"0F\"] // ignore: FVS1, FVS2, FVS3, MVS, FVS4\r\n\t],\r\n\t[\"thanaa\", \"07\", [8,9,\"A\",\"B\"], -14, , \"Thaana_(Unicode_block)\"],\r\n]\r\n\r\nlet aBlocksSkip = [\r\n\t// to easily turn scripts on/off\r\n\r\n\t// not for production as headers can remain and anchors get fucked up?\r\n\t// all 129 scripts are listed here in different buckets for easily\r\n\t// generating different everyCode lists\r\n\r\n\t/* everything not in the other list\r\n\t'adlam',\r\n\t'bamum',\r\n\t'bassa vah',\r\n\t'ethiopic',\r\n\t'mende kikakui',\r\n\t'nko',\r\n\t'osmanya',\r\n\t'tifinagh',\r\n\t'vai',\r\n\t'cherokee',\r\n\t'cherokee supplement',\r\n\t'chakma',\r\n\t'deseret',\r\n\t'osage',\r\n\t'unified canadian aboriginal syllabics',\r\n\t'old italic',\r\n\r\n\t'balinese',\r\n\t'bengali',\r\n\t'buginese',\r\n\t'buhid',\r\n\t'cham',\r\n\t'common indic number forms',\r\n\t'devanagari',\r\n\t'dives akuru',\r\n\t'dogra',\r\n\t'gujarati',\r\n\t'gurmukhi',\r\n\t'hanifi rohingya',\r\n\t'hanunoo',\r\n\t'javanese',\r\n\t'kaithi',\r\n\t'kannada',\r\n\t'kawi',\r\n\t'kayah li',\r\n\t'khmer',\r\n\t'khmer symbols',\r\n\t'khojki',\r\n\t'khudawadi',\r\n\t'lao',\r\n\t'lepcha',\r\n\t'limbu',\r\n\t'mahajani',\r\n\t'malayalam',\r\n\t'meetei mayek',\r\n\t'modi',\r\n\t'mro',\r\n\t'multani',\r\n\t'myanmar',\r\n\t'myanmar extended-a',\r\n\t'myanmar extended-b',\r\n\t'nag mundari',\r\n\t'new tai lue',\r\n\t'newa',\r\n\t'ol chiki',\r\n\t'oriya',\r\n\t'pau cin hau',\r\n\t'phags-pa',\r\n\t'saurashtra',\r\n\t'sinhala',\r\n\t'sora sompeng',\r\n\t'sundanese',\r\n\t'sundanese supplement',\r\n\t'syloti nagri',\r\n\t'tagalog',\r\n\t'tagbanwa',\r\n\t'tai le',\r\n\t'tai tham',\r\n\t'tai viet',\r\n\t'takri',\r\n\t'tamil',\r\n\t'telugu',\r\n\t'thai',\r\n\t'tibetan',\r\n\t'tirhuta',\r\n\t'warang citi',\r\n\r\n\t'cyrillic supplement',\r\n\t'bopomofo',\r\n\t'bopomofo extended',\r\n\t'cjk compatibility',\r\n\t'cjk compatibility forms',\r\n\t'cjk symbols and punctuation',\r\n\t'cjk radicals supplement',\r\n\t'cjk unified ideographs',\r\n\t'cjk unified ideographs extension-a',\r\n\t'enclosed cjk letters and months',\r\n\t'hangul compatibility jamo',\r\n\t'hangul jamo',\r\n\t'hangul syllables',\r\n\t'hiragana',\r\n\t'ideographic description characters',\r\n\t'kanbun',\r\n\t'katakana',\r\n\t'katakana phonetic extensions',\r\n\t'lisu',\r\n\t'vertical forms',\r\n\t'yi radicals',\r\n\t'yi syllables',\r\n\t'georgian',\r\n\t'georgian extended',\r\n\t'georgian supplement',\r\n\t'greek and coptic',\r\n\t'greek extended',\r\n\t'armenian',\r\n\t'halfwidth and fullwidth forms',\r\n\t'mongolian',\r\n\t'thanaa',\r\n\t//*/\r\n\t\r\n\t/* list to reduce collection of all everyCode for fntString codepoints\r\n\t// list excludes some obvious crap like modifiers, combining, blocks, latin\r\n\t\"ethiopic supplement\",\r\n\t\"gothic\",\r\n\t\"ogham\",\r\n\t\"old turkic\",\r\n\t\"runic\",\r\n\t\"tamil supplement\",\r\n\t\"cyrillic\",\r\n\t\"cyrillic extended-a\",\r\n\t\"cyrillic extended-b\",\r\n\t\"cyrillic extended-c\",\r\n\t\"glagolitic\",\r\n\t\"cjk compatibility ideographs\",\r\n\t\"cjk compatibility ideographs supplement\",\r\n\t\"cjk unified ideographs extension-b\",\r\n\t\"kangxi radicals\",\r\n\t\"basic latin\",\r\n\t\"latin-1 supplement\",\r\n\t\"latin extended-a\",\r\n\t\"latin extended-b\",\r\n\t\"latin extended-c\",\r\n\t\"latin extended-d\",\r\n\t\"latin extended-e\",\r\n\t\"latin extended-f\",\r\n\t\"latin extended-g\",\r\n\t\"latin extended additional\",\r\n\t\"ipa extensions\",\r\n\t\"phonetic extensions\",\r\n\t\"phonetic extensions supplement\",\r\n\t\"spacing modifier letters\",\r\n\t\"arabic\",\r\n\t\"arabic presentation forms-a\",\r\n\t\"arabic supplement\",\r\n\t\"hebrew\",\r\n\t\"mandaic\",\r\n\t\"samaritan\",\r\n\t\"syriac\",\r\n\t\"alphabetic presentation forms\",\r\n\t\"arrows\",\r\n\t\"block elements\",\r\n\t\"box drawing\",\r\n\t\"braille patterns\",\r\n\t\"currency symbols\",\r\n\t\"dingbats\",\r\n\t\"enclosed alphanumerics\",\r\n\t\"general punctuation\",\r\n\t\"geometric shapes\",\r\n\t\"letterlike symbols\",\r\n\t\"mathematical alphanumeric symbols\",\r\n\t\"mathematical operators\",\r\n\t\"miscellaneous mathematical symbols-a\",\r\n\t\"miscellaneous mathematical symbols-b\",\r\n\t\"miscellaneous symbols\",\r\n\t\"miscellaneous technical\",\r\n\t\"number forms\",\r\n\t\"optical character recognition\",\r\n\t\"superscripts and subscripts\",\r\n\t\"supplemental arrows-a\",\r\n\t\"supplemental arrows-b\",\r\n\t\"supplemental mathematical operators\",\r\n\t\"combining diacritical marks\",\r\n\t\"combining diacritical marks for symbols\",\r\n\t\"combining diacritical marks supplement\",\r\n\t\"control pictures\",\r\n\t//*/\r\n]\r\n\r\nlet aTest = [\r\n\t// these are the \"reduce\" code points in some scripts\r\n\t// known scripts where if the additonal code point is not supported, subsequent code points may not render\r\n\t// lets test each in a div or new line against the first min set\r\n\t\"unified canadian aboriginal syllabics\",\r\n\t\"bengali\",\"devanagari\",\"kannada\",\"malayalam\",\"sinhala\",\"telugu\",\r\n\t\"miscellaneous mathematical symbols-a\",\"superscripts and subscripts\",\r\n\t// or use this for any small set tests\r\n\t// e.g. aTest = aSpacer\r\n]\r\nlet isTest = false\r\n\r\nlet oCodesMin = {},\r\n\toCodesMinVersion = {},\r\n\toCodesMax = {},\r\n\toCodesReduce = {},\r\n\toCodesReserved = {},\r\n\toCodesIgnore = {}, // bidirectional, non-printing, control codes etc\r\n\toCodesCounts = {},\r\n\toRanges = {},\r\n\toRangesReduced = {},\r\n\toWiki = {},\r\n\toGroupHeader = {},\r\n\toScriptHeader = {},\r\n\taLegendClean = [],\r\n\taNav = [],\r\n\taDisplay = [],\r\n\taDisplayMax = [],\r\n\toTofuSizes = {},\r\n\ttofuSize,\r\n\ttestParams = \"\"\r\n\r\nlet ZWNJ = String.fromCodePoint(\"0x200C\") +\" \"\r\n\r\nlet styles = [\"none\",\"sans-serif\",\"serif\",\"monospace\",\"cursive\",\"fantasy\"]\r\n\r\n// FP\r\nlet fpTofuData = {},\r\n\t\tfpTofuHashes = [],\r\n\t\t// size\r\n\t\tfpSizeDataClient = {},\r\n\t\t// hashes\r\n\t\tfpSizeHashesClient = [],\r\n\t\t// unique sizes\r\n\t\tfpSizeClientUnique = []\r\n\r\n// everything\r\nlet oCodePoints = {},\r\n\toTempData = {}\r\n\r\nfunction strip(value) {return value.replace(/ /g,\"\")} \t// strip all spaces\r\n\r\nfunction hyperlink(string) {return \" <a target='blank' class='blue' href='\"+ string +\"'>wiki</a>\"}\r\n\r\nfunction toggleitem(item) {\r\n\tlet\tel = document.getElementById(item)\r\n\tlet style = el.getAttribute(\"class\")\r\n\tif (style.includes(\"hidden\")) {\r\n\t\tel.classList.remove(\"hidden\")\r\n\t} else {\r\n\t\tel.classList.add(\"hidden\")\r\n\t}\r\n}\r\n\r\nfunction get_count(prefix) {\r\n\tlet name = (dom.allassigned.checked ? \"max\" : \"min\")\r\n\t\t+ document.querySelector('input[name=\"depth\"]:checked').value\r\n\tlet value = oCodesCounts[name]\r\n\tlet maxValue = oCodesCounts[\"maxALL\"]\r\n\treturn prefix + (name == \"maxALL\" ? \"all \" : \"\")\r\n\t\t+ value + (name == \"maxALL\" ? \"\" : \" of \"+ maxValue) //+\" max chars\"\r\n}\r\n\r\nfunction build_title(name, anchor, nameColor, grouptitle, intCodes, intCodesMax) {\r\n\t// builds the html code for each script header, including if we need a script group\r\n\tlet count = intCodes\r\n\tlet strVersion = oTempData[name][\"min version\"]\r\n\tif (strVersion !== undefined) {\r\n\t\tif (intCodes == intCodesMax) {\r\n\t\t\tcount += sb + \" <sup>counts match</sup></span> | \" + intCodesMax\r\n\t\t} else {\r\n\t\t\tcount += \" <span class='no_color'><sup>\"+ strVersion +\"</sup></span> | \" + intCodesMax\r\n\t\t}\r\n\t} else if (intCodes !== intCodesMax) {\r\n\t\tcount += sb + \" <sup>version</sup>\"+ sc +\" | \"+ intCodesMax\r\n\t}\r\n\tlet strWiki = oTempData[name][\"wiki\"]\r\n\tstrWiki = strWiki == undefined ? \"\" : hyperlink(strWiki)\r\n\tlet strRange = oTempData[name][\"range\"]\r\n\tif (strRange == undefined) {\r\n\t\tstrRange = sb +\"[range]\"+ sc\r\n\t} else {\r\n\t\tstrRange =  s14 +\" [\"+ strRange +\"]\"+ sc\r\n\t\t\t+ (aPartial.includes(name) ? \" <code class='purple'>P</code>\" : \"\")\r\n\t\t\t+ (aSelective.includes(name) ? \" <code class='purple'>S</code>\" : \"\")\r\n\t}\r\n\r\n\treturn (grouptitle !== \"\" ? oGroupHeader[grouptitle][0] : \"\") +\r\n\t\t//\"<span class='mono script'>\"\r\n\t\t\"<span class='script'>\"\r\n\t\t\t// name\r\n\t\t\t+ nameColor + \"<a name='\"+ anchor + \"'></a>\"\r\n\t\t\t\t+ \"<span style='cursor: pointer;' onClick='logConsole(`\"+ name + \"`)'>\" + name + sc + sc\r\n\t\t\t// char count\r\n\t\t\t+ s12 + \" [\"+ count +\"]\"+ sc + strRange + strWiki\r\n\t\t+ sc\r\n}\r\n\r\nfunction get_tofu_size() {\r\n\t// measure some known unassigned + reserved unicode glyphs\r\n\treturn new Promise(resolve => {\r\n\t\t// list: some reserved code points\r\n\t\t// use multiple scripts to reduce change in greatest occurence over time\r\n\t\tlet list = [\r\n\t\t\t'0x0402', // control: cyrillic: capital Dje\r\n\t\t\t'0x0386', // control: greek: capital A with acute\r\n\t\t\t'0x09E5', // bengali\r\n\t\t\t'0x135C', // ethiopic\r\n\t\t\t'0x10CF', // georgian\r\n\t\t\t'0x0AF8', // gujarati\r\n\t\t\t'0x0A65', // gurmukhi\r\n\t\t\t'0x03A2', // greek\r\n\t\t\t'0x0EDB', // lao\r\n\t\t\t'0x0D65', // malayam\r\n\t\t\t/* \r\n\t\t\t'0x187F', // mongolian\r\n\t\t\t'0x0DF1', // sinhala\r\n\t\t\t'0x0BE5', // tamil\r\n\t\t\t'0x2D7E', // tifinagh\r\n\t\t\t*/\r\n\t\t]\r\n\t\tlet div = dom.ugDiv, span = dom.ugSpan, slot = dom.ugSlot\r\n\t\tlet res = {}\r\n\t\tstyles.forEach(function(style) {res[style] = []})\r\n\t\toTofuSizes[\"test array\"] = []\r\n\r\n\t\tlist.forEach(function(codepoint) {\r\n\t\t\toTofuSizes[\"test array\"].push(codepoint +\" \"+ String.fromCodePoint(codepoint))\r\n\t\t\tstyles.forEach(function(style) {\r\n\t\t\t\tslot.style.fontFamily = style\r\n\t\t\t\tslot.textContent = String.fromCodePoint(codepoint)\r\n\t\t\t\t// use clientrects (more precision): w=span, h=div\r\n\t\t\t\tlet cDiv = div.getClientRects()\r\n\t\t\t\tlet cSpan = span.getClientRects()\r\n\t\t\t\tlet whClient = cSpan[0].width +\" x \"+ cDiv[0].height\r\n\t\t\t\tres[style].push(whClient)\r\n\t\t\t})\r\n\t\t})\r\n\t\t// reset slot so we don't end up with unwanted scrollbars\r\n\t\tslot.textContent = \"\"\r\n\r\n\t\t// get most common size\r\n\t\tconst names = Object.keys(res)\r\n\t\tfor (const k of names) {\r\n\t\t\tlet aRes = res[k]\r\n\t\t\tlet getGreatestOccurrence = aRes => aRes.reduce((greatest, currentValue, index, res) => {\r\n\t\t\t\tlet count = res.filter(item => JSON.stringify(item) == JSON.stringify(currentValue)).length\r\n\t\t\t\tif (count > greatest.count) {\r\n\t\t\t\t\treturn {count, item: currentValue}\r\n\t\t\t\t}\r\n\t\t\t\treturn greatest\r\n\t\t\t}, { count: 0, item: undefined })\r\n\t\t\tlet greatest = getGreatestOccurrence(aRes)\r\n\t\t\toTofuSizes[k] = [greatest.item, aRes.join(\", \")]\r\n\t\t}\r\n\t\ttofuSize = oTofuSizes[\"none\"][0]\r\n\t\tdom.tofuSize.innerHTML = \"<span style='cursor: pointer;' onClick='logConsole(`tofusize`)'>\" + tofuSize +\"</span>\"\r\n\t\treturn resolve()\r\n\t})\r\n}\r\n\r\nfunction prettyObjectArray(json) {\r\n  if (typeof json === 'string') {\r\n    json = JSON.parse(json);\r\n  }\r\n  let output = JSON.stringify(json, function(k,v) {\r\n    if(v instanceof Array)\r\n      return JSON.stringify(v);\r\n    return v;\r\n  }, 3).replace(/\\\\/g, '')\r\n        .replace(/\\\"\\[/g, '[')\r\n        .replace(/\\]\\\"/g,']')\r\n        .replace(/\\\"\\{/g, '{')\r\n        .replace(/\\}\\\"/g,'}')\r\n        .replace(/,/g,', ');\r\n  return output;\r\n}\r\n\r\nfunction logConsole(type, name) {\r\n\t// objects\r\n\tlet oUsed = {}, logStr = \"\"\r\n\tif (type == \"everything\") {oUsed = oCodePoints; logStr = \"everything\"\r\n\t} else if (type == \"wiki\") {oUsed = oWiki; logStr = \"wikipedia links\"\r\n\t} else if (type == \"ranges\") {oUsed = oRanges; logStr = \"ranges used [not all blocks may be used]\"\r\n\t} else if (type == \"rangesreduced\") {\r\n\t\toUsed = oRangesReduced\r\n\t\tlogStr = \"ranges [reduced] [P: partial | S: selective blocks used from the range]\"\r\n\t} else if (type == \"ignore\") {oUsed = oCodesIgnore; logStr = \"code points [ignored]\"\r\n\t} else if (type == \"max\") {oUsed = oCodesMax; logStr = \"code points [max used]\"\r\n\t} else if (type == \"min\") {oUsed = oCodesMin; logStr = \"code points [min used if reduced]\"\r\n\t} else if (type == \"reduce\") {oUsed = oCodesReduce; logStr = \"code points [reduce]\"\r\n\t} else if (type == \"reserved\") {oUsed = oCodesReserved; logStr = \"code points [reserved]\"\r\n\t} else if (type == \"version\") {oUsed = oCodesMinVersion; logStr = \"unicode [min version if reduced]\"\r\n\t} else if (type == \"fpTofuData\") {\r\n\t\toUsed = fpTofuData; logStr = \"tofu | \"+ testParams\r\n\t} else if (type == \"fpSizeDataClient\") {\r\n\t\toUsed = fpSizeDataClient; logStr = \"getClientRects | \"+ testParams\r\n\t} else if (type == \"fptofuscript\") {\r\n\t\toUsed = fpTofuData[name]; logStr=\"tofu [\"+ name +\"] | \"+ testParams\r\n\t}\r\n\tif (logStr !== \"\") {\r\n\t\tconsole.log(logStr+\"\\n\", prettyObjectArray(oUsed))\r\n\t\treturn\r\n\t}\r\n\t// combine\r\n\tif (type == \"fpsizescript\") {\r\n\t\tlogStr=\"sizes [\"+ name +\"] | \"+ testParams\r\n\t\tconsole.log(\r\n\t\t\tlogStr+\"\\n\"+\r\n\t\t\t\"\\ngetClientRects\\n\", prettyObjectArray(fpSizeDataClient[name])\r\n\t\t)\r\n\t\treturn\r\n\t}\r\n\r\n\t// arrays\r\n\tif (type == \"fptofu\") {\r\n\t\tlogStr = \"tofu fingerprint | \"+ testParams +\" | \"+ mini(fpTofuHashes.join()) +\"\\n  \"+ fpTofuHashes.join(\"\\n  \")\r\n\t} else if (type == \"fpSizeClient\") {\r\n\t\tlogStr = \"getClientRects | \"+ testParams +\" | \"+ mini(fpSizeHashesClient.join()) +\"\\n  \"+ fpSizeHashesClient.join(\"\\n  \")\r\n\t}\r\n\tif (logStr !== \"\") {\r\n\t\tconsole.log(logStr)\r\n\t\treturn\r\n\t}\r\n\r\n\t// pretty\r\n\tif (type == \"tofusize\") {\r\n\t\tlogStr = \"calculated tofu sizes [greatest occurence, results]\"\r\n\t\tlogStr += \"\\n===========\"\r\n\t\tconst names = Object.keys(oTofuSizes).sort()\r\n\t\tfor (const k of names) {\r\n\t\t\tlogStr += \"\\n\"+ k\r\n\t\t\t+ (k == \"test array\" ? \"\\n - \"+ oTofuSizes[k].join(\", \") : \"\\n - \"+ oTofuSizes[k][0])\r\n\t\t\t+ (k == \"test array\" ? \"\" : \"\\n - \"+ oTofuSizes[k][1])\r\n\t\t}\r\n\t\tconsole.log(logStr)\r\n\t\treturn\r\n\t}\r\n\t// script\r\n\tif (oCodePoints[type] !== undefined) {\r\n\t\tconsole.log(type, prettyObjectArray(oCodePoints[type]))\r\n\t}\r\n}\r\n\r\nfunction generate() {\r\n\t// do once\r\n\t// two sets of codes and characters and clean displays\r\n\t// reduce codes/chars\r\n\t// reserved code/chars\r\n\t// ignore codes\r\n\t// range info\r\n\t// clean legend\r\n\t// get char counts\r\n\treturn new Promise(resolve => {\r\n\t\tlet t0 = performance.now()\r\n\t\taBlocks.forEach(function(data) {\r\n\t\t\tlet name = data[0].toLowerCase()\r\n\t\t\tlet anchor = strip(name) +\"group\"\r\n\t\t\tif (data[1] == undefined) {\r\n\t\t\t\taNav.push(anchor)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// FP lines\r\n\t\tlet strFP = \"<span class='mono'>\"\r\n\t\t\t+ s12 + \"tofu size: \".padStart(16) + sc +\"<span id='runningTofuSize'></span><br>\"\r\n\t\t\t+ s12 + \"parameters: \".padStart(16) + sc +\"<span id='runningParameters'></span><br>\"\r\n\t\t\t+ s12 +\"tofu: \".padStart(16) + sc +\"<br>\"\r\n\t\t\t+ s12 +\"getClientRects: \".padStart(16) + sc + \"<span id='runningStatus'></span></span><br>\"\r\n\t\taDisplay.push(strFP)\r\n\t\taDisplayMax.push(strFP)\r\n\r\n\t\tlet aSuffix = [0,1,2,3,4,5,6,7,8,9,\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"]\r\n\t\tlet navIndex = 0,\r\n\t\t\tnavString =\"\",\r\n\t\t\tnavTop = \"<a class='blue' href='#\" + aBlocks[0][0].toLowerCase() +\"group'> &#9650 TOP</a> &nbsp \",\r\n\t\t\tnavEnd = \"<a class='blue' href='#end'> &#9660 END</a></p>\",\r\n\t\t\tgrouptitle = \"\"\r\n\r\n\t\tlet aCountsMin = []\r\n\t\tlet aCountsMax = []\r\n\t\tlet namesDebug = []\r\n\t\taBlocks.forEach(function(data) {\r\n\t\t\tlet name = data[0].toLowerCase()\r\n\t\t\tlet anchor = strip(name)\r\n\t\t\tif (data[1] == undefined) {\r\n\t\t\t\t// group headers\r\n\t\t\t\tname = name.toUpperCase()\r\n\t\t\t\tanchor += \"group\"\r\n\t\t\t\tnavString = \"<p class='groupright'>\"\r\n\t\t\t\tif (anchor !== aNav[0]) {\r\n\t\t\t\t\tnavString += navTop + \"<a class='blue' href='#\"+ aNav[navIndex - 1] +\"'> &#9664 PREV</a> &nbsp \"\r\n\t\t\t\t}\r\n\t\t\t\tif (aNav[navIndex + 1] !== undefined) {\r\n\t\t\t\t\tnavString += \"<a class='blue' href='#\"+ aNav[navIndex + 1] +\"'> &#9654 NEXT</a> &nbsp \"\r\n\t\t\t\t}\r\n\t\t\t\tnavString += navEnd\r\n\t\t\t\taLegendClean.push((navIndex == 0 ? \"\": \"<br>\") +\"<a class='blue' href='#\"+ anchor +\"'>\"+ name +\"</a><br>\")\r\n\t\t\t\tnavIndex++\r\n\r\n\t\t\t\tlet groupStr = \"<div><a name='\"+ anchor + \"'></a>\"\r\n\t\t\t\t\t+ \"<hr><p class='groupleft'>\"\r\n\t\t\t\t\t+ \"<u><b><a class='no_color' href='#\"+ anchor +\"'>\"+ name +\"</a></b></u>\"\r\n\t\t\t\t\t+ \"</p>\"\r\n\t\t\t\t\t+ navString\r\n\t\t\t\t\t+ \"<div style='clear: both;'></div>\"\r\n\t\t\t\t\t+ \"</div>\"\r\n\r\n\t\t\t\tgrouptitle = strip(name).toLowerCase()\r\n\t\t\t\toGroupHeader[grouptitle] = [groupStr]\r\n\t\t\t} else {\r\n\t\t\t\tname = data[0].toLowerCase()\r\n\t\t\t\tnamesDebug.push(name)\r\n\t\t\t\tlet go = !isTest\r\n\t\t\t\tif (aBlocksSkip.includes(name)) {\r\n\t\t\t\t\tgo = false\r\n\t\t\t\t} else {\r\n\t\t\t\t\tgo = true\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (isTest && aTest.includes(name)) {go = true}\r\n\t\t\t\tif (go) {\r\n\t\t\t\t\toTempData[name] = {}\r\n\t\t\t\t\tlet prefix = data[1],\r\n\t\t\t\t\t\trange = data[2],\r\n\t\t\t\t\t\tremove = data[3],\r\n\t\t\t\t\t\tnonassigned = data[4],\r\n\t\t\t\t\t\turl = data[5],\r\n\t\t\t\t\t\treduce = data[6],\r\n\t\t\t\t\t\tversion = data[7],\r\n\t\t\t\t\t\tignore = data[8]\r\n\t\t\t\t\t// fixup vars\r\n\t\t\t\t\tif (nonassigned == undefined) {nonassigned = []}\r\n\t\t\t\t\tif (reduce == undefined) {reduce = []}\r\n\t\t\t\t\tif (ignore == undefined) {ignore = []}\r\n\t\t\t\t\tif (remove == undefined) {remove = 0}\r\n\t\t\t\t\t// range info\r\n\t\t\t\t\tlet rangeString = \"\"+ prefix + range[0] +\"0..\"+ prefix + range[range.length-1] +\"F\"\r\n\t\t\t\t\toTempData[name][\"range\"] = rangeString\r\n\t\t\t\t\t// partial\r\n\t\t\t\t\tlet isPartial = []\r\n\t\t\t\t\tif (aPartial.includes(name)) {isPartial.push(\"P\")}\r\n\t\t\t\t\tif (aSelective.includes(name)) {isPartial.push(\"S\")}\r\n\t\t\t\t\toTempData[name][\"partial\"] = isPartial.length ? isPartial.join(\", \") : false\r\n\t\t\t\t\t// legend\r\n\t\t\t\t\taLegendClean.push(\"<li><a class='no_color' href='#\"+ anchor +\"'>\"+ name +\"</a></li></span>\")\r\n\r\n\t\t\t\t\tlet aCodesMin = [],\r\n\t\t\t\t\t\taCodesMax = [],\r\n\t\t\t\t\t\taCodesReduce = [],\r\n\t\t\t\t\t\taCodesReserved = [],\r\n\t\t\t\t\t\taCodesIgnore = [],\r\n\t\t\t\t\t\taCharsMin = [],\r\n\t\t\t\t\t\taCharsMax = []\r\n\r\n\t\t\t\t\t// loop\r\n\t\t\t\t\tfor (let i = 0; i < range.length; i++) {\r\n\t\t\t\t\t\tfor (let j = 0; j < aSuffix.length; j++) {\r\n\t\t\t\t\t\t\tlet string = \"0x\"+ prefix + range[i] + aSuffix[j]\r\n\t\t\t\t\t\t\tlet match = \"\"+ range[i] + aSuffix[j]\r\n\t\t\t\t\t\t\tif (ignore.includes(match)) {\r\n\t\t\t\t\t\t\t\taCodesIgnore.push(string)\r\n\t\t\t\t\t\t\t} else if (!nonassigned.includes(match)) {\r\n\t\t\t\t\t\t\t\taCodesMax.push(string)\r\n\t\t\t\t\t\t\t\taCharsMax.push(String.fromCodePoint(string))\r\n\t\t\t\t\t\t\t\tif (!reduce.includes(match)) {\r\n\t\t\t\t\t\t\t\t\taCodesMin.push(string)\r\n\t\t\t\t\t\t\t\t\taCharsMin.push(String.fromCodePoint(string))\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\taCodesReduce.push(string)\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\taCodesReserved.push(string)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// trim results\r\n\t\t\t\t\tif (remove < 0) {\r\n\t\t\t\t\t\t// add trimmed items to reserved\r\n\t\t\t\t\t\tlet trimReserved = aCodesMin.slice(aCodesMin.length + remove, aCodesMin.length)\r\n\t\t\t\t\t\taCodesReserved = aCodesReserved.concat(trimReserved)\r\n\t\t\t\t\t\t// slice results\r\n\t\t\t\t\t\taCodesMin = aCodesMin.slice(0, remove)\r\n\t\t\t\t\t\taCodesMax = aCodesMax.slice(0, remove)\r\n\t\t\t\t\t\taCharsMin = aCharsMin.slice(0, remove)\r\n\t\t\t\t\t\taCharsMax = aCharsMax.slice(0, remove)\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// record\r\n\t\t\t\t\tif (aCodesReserved.length) {oTempData[name][\"reserved\"] = aCodesReserved}\r\n\t\t\t\t\tif (aCodesIgnore.length) {oTempData[name][\"ignored\"] = aCodesIgnore}\r\n\t\t\t\t\toTempData[name][\"max count\"] = aCodesMax.length\r\n\t\t\t\t\toTempData[name][\"max used\"] = aCodesMax\r\n\t\t\t\t\tif (aCodesReduce.length) {\r\n\t\t\t\t\t\toTempData[name][\"reduce\"] = aCodesReduce\r\n\t\t\t\t\t\toTempData[name][\"min count\"] = aCodesMin.length\r\n\t\t\t\t\t\toTempData[name][\"min used\"] = aCodesMin\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (version !== undefined && version !== \"\") {oTempData[name][\"min version\"] = version}\r\n\t\t\t\t\tif (url !== undefined && url !== \"\") {oTempData[name][\"wiki\"] = \"https://en.wikipedia.org/wiki/\"+ url}\r\n\r\n\t\t\t\t\t// counts\r\n\t\t\t\t\taCountsMin.push(aCodesMin.length)\r\n\t\t\t\t\taCountsMax.push(aCodesMax.length)\r\n\r\n\t\t\t\t\t// build clean displays\r\n\t\t\t\t\tlet nameColor = s12\r\n\t\t\t\t\tlet scriptheader = build_title(name, anchor, nameColor, grouptitle, aCodesMin.length, aCodesMax.length)\r\n\t\t\t\t\toScriptHeader[name] = [scriptheader]\r\n\t\t\t\t\taDisplay.push(scriptheader)\r\n\t\t\t\t\taDisplayMax.push(scriptheader)\r\n\t\t\t\t\tlet strStart = \"\", strEnd = \"\"\r\n\t\t\t\t\tif (oEnlarge[name] !== undefined) {\r\n\t\t\t\t\t\tstrStart = \"<span style='font-size: \"+ oEnlarge[name][0] +\"px'>\"\r\n\t\t\t\t\t\tstrEnd = sc\r\n\t\t\t\t\t}\r\n\t\t\t\t\tlet spacer = aSpacer.includes(name) ? \" \" : \"\"\r\n\t\t\t\t\taDisplay.push(\"<div class='chars'>\"+ strStart + spacer + aCharsMin.join(ZWNJ + spacer) + strEnd +\"</div>\")\r\n\t\t\t\t\taDisplayMax.push(\"<div class='chars'>\"+ strStart + spacer + aCharsMax.join(ZWNJ + spacer) + strEnd +\"</div>\")\r\n\t\t\t\t\t// reset grouptitle\r\n\t\t\t\t\tgrouptitle = \"\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t})\r\n\t\t//console.log(\"'\"+ namesDebug.join(\"',\\n'\") +\"',\")\r\n\t\taDisplay.push(\"<a name='end'></a>\")\r\n\t\taDisplayMax.push(\"<a name='end'></a>\")\r\n\r\n\t\t// counts\r\n\t\tlet min20 =0, min50=0, minAll=0, max20=0, max50=0, maxAll=0\r\n\t\tfor (let i=0; i < aCountsMin.length; i++) {\r\n\t\t\tlet minValue = aCountsMin[i],\r\n\t\t\t\tmaxValue = aCountsMax[i]\r\n\t\t\tmin20 += (minValue > 19 ? 20 : minValue)\r\n\t\t\tmin50 += (minValue > 49 ? 50 : minValue)\r\n\t\t\tminAll += minValue\r\n\t\t\tmax20 += (maxValue > 19 ? 20 : maxValue)\r\n\t\t\tmax50 += (maxValue > 49 ? 50 : maxValue)\r\n\t\t\tmaxAll += maxValue\r\n\t\t}\r\n\t\toCodesCounts[\"min20\"] = min20\r\n\t\toCodesCounts[\"min50\"] = min50\r\n\t\toCodesCounts[\"minALL\"] = minAll\r\n\t\toCodesCounts[\"max20\"] = max20\r\n\t\toCodesCounts[\"max50\"] = max50\r\n\t\toCodesCounts[\"maxALL\"] = maxAll\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = \"setup: \"+ Math.round(performance.now() - t0) +\" ms | \" + get_count(\"test \")\r\n\r\n\t\t// sort oTempData => sorted objects\r\n\t\tconst names = Object.keys(oTempData).sort()\r\n\t\tdom.countScript.innerHTML = \"[\"+ names.length +\"]\"\r\n\t\tvar everyCode = []\r\n\t\tfor (const k of names) {\r\n\t\t\toCodePoints[k] = oTempData[k]\r\n\t\t\tif (oTempData[k][\"range\"] !== undefined) {\r\n\t\t\t\toRanges[k] = oTempData[k][\"range\"]\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"partial\"] !== undefined) {\r\n\t\t\t\tlet rangeChk = oTempData[k][\"partial\"]\r\n\t\t\t\tif (false !== rangeChk) { // ignore false\r\n\t\t\t\t\toRangesReduced[k] = oTempData[k][\"partial\"]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"max used\"] !== undefined) {\r\n\t\t\t\toCodesMax[k] = oTempData[k][\"max used\"]\r\n\t\t\t\tif (aBlocksSkip.length > 0) {\r\n\t\t\t\t\teveryCode = everyCode.concat(oCodesMax[k])\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"ignored\"] !== undefined) {\r\n\t\t\t\toCodesIgnore[k] = oTempData[k][\"ignored\"]\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"reserved\"] !== undefined) {\r\n\t\t\t\toCodesReserved[k] = oTempData[k][\"reserved\"]\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"wiki\"] !== undefined) {\r\n\t\t\t\toWiki[k] = oTempData[k][\"wiki\"]\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"reduce\"] !== undefined) {\r\n\t\t\t\toCodesReduce[k] = oTempData[k][\"reduce\"]\r\n\t\t\t\tif (oTempData[k][\"min used\"] !== undefined) {\r\n\t\t\t\t\toCodesMin[k] = oTempData[k][\"min used\"]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (oTempData[k][\"min version\"] !== undefined) {\r\n\t\t\t\toCodesMinVersion[k] = oTempData[k][\"min version\"]\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (aBlocksSkip.length > 0) {\r\n\t\t\tconsole.log(everyCode.length, everyCode)\r\n\t\t\t//console.log(\"['\"+ everyCode.join(\"','\") +\"']\")\r\n\t\t}\r\n\t\t// return\r\n\t\treturn resolve()\r\n\t})\r\n}\r\n\r\nfunction reset(resetperf = true, resetdisplay = true) {\r\n\t// reset/display everything to make sure all code points have time to hopefully render\r\n\tif (resetperf) {\r\n\t\tdom.perf.innerHTML = get_count(\"test \")\r\n\t}\r\n\tif (resetdisplay) {\r\n\t\tdom.legend.innerHTML = aLegendClean.join(\"\")\r\n\t\tdom.visual.innerHTML = dom.allassigned.checked ? aDisplayMax.join(\"<br>\") : aDisplay.join(\"<br>\")\r\n\t\tdom.summary.innerHTML = \"\"\r\n\t}\r\n\tif (!resetperf) {isRunning = false}\r\n}\r\n\r\nfunction run() {\r\n\tif (!isRunning) {\r\n\t\t// we need to retest tofu size in case user zoomed\r\n\t\tPromise.all([\r\n\t\t\tget_tofu_size(),\r\n\t\t\treset(),\r\n\t\t]).then(function(results){\r\n\t\t\ttry {\r\n\t\t\t\tisRunning = true\r\n\t\t\t\tdom.perf.innerHTML = \"running ...\"\r\n\t\t\t\tdom.runningStatus = \"running ... \"+ get_count(\"testing \")\r\n\t\t\t\tdom.runningTofuSize.innerHTML = tofuSize + \" <span class='btn12 btnc' onClick='logConsole(`tofusize`)'>[details]</span>\"\r\n\t\t\t\tlet testDepth = document.querySelector('input[name=\"depth\"]:checked').value\r\n\t\t\t\ttestParams = (testDepth == \"ALL\" ? \"all\" : \"up to \"+ testDepth)\r\n\t\t\t\t\t+\" | \"+ (dom.allassigned.checked ? \"max\" : \"reduced\")\r\n\t\t\t\tdom.runningParameters = testParams\r\n\t\t\t\tsetTimeout(function() {\r\n\t\t\t\t\ttest(testDepth)\r\n\t\t\t\t}, 1)\r\n\t\t\t} catch(e) {\r\n\t\t\t\tconsole.log(e)\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n}\r\n\r\nfunction test(testDepth) {\r\n\tdom.perf.innerHTML = \"&nbsp\"\r\n\t// vars\r\n\tlet aVisual = [],\r\n\t\taLegend = [],\r\n\t\taSummary = [],\r\n\t\tcountTested = 0,\r\n\t\tcountTofu = 0\r\n\tlet div = dom.ugDiv, span = dom.ugSpan, slot = dom.ugSlot\r\n\tlet t0 = performance.now()\r\n\tslot.style.fontFamily = \"none\"\r\n\tif (testDepth == \"ALL\") {testDepth = 800}\r\n\tlet aRed = [], aOrange = [], aYellow = []\r\n\r\n\t// reset tofu\r\n\tfpTofuData = {}\r\n\tfpTofuHashes = []\r\n\t// reset size data\r\n\tfpSizeDataClient = {}\r\n\t// reset size hashes\r\n\tfpSizeHashesClient = []\r\n\t// reset unique sizes\r\n\tfpSizeClientUnique = []\r\n\t\r\n\tlet oTempTofu = {},\r\n\t\toTempSizeClient = {},\r\n\t\toTempSizeClientUnique = {}\r\n\r\n\taBlocks.forEach(function(data) {\r\n\t\tlet name = data[0].toLowerCase()\r\n\t\tlet anchor = strip(name)\r\n\t\tif (data[1] == undefined) {\r\n\t\t\tlet firstItem = aBlocks[0].join()\r\n\t\t\taLegend.push((name.toLowerCase() == firstItem.toLowerCase() ? \"\": \"<br>\")\r\n\t\t\t\t+\"<a class='blue' href='#\"+ anchor +\"group'>\"+ name.toUpperCase() +\"</a><br>\")\r\n\t\t} else {\r\n\t\t\tlet go = !isTest\r\n\t\t\tif (isTest && aTest.includes(name)) {go = true}\r\n\t\t\tif (go) {\r\n\t\t\t\tlet codes = dom.allassigned.checked ? oCodePoints[name][\"max used\"] : oCodePoints[name][\"min used\"]\r\n\t\t\t\tif (oCodePoints[name][\"min used\"] == undefined) {codes = oCodePoints[name][\"max used\"]}\r\n\r\n\t\t\t\tlet displayA = []\r\n\t\t\t\tlet aTofu = []\r\n\t\t\t\tlet aSizeClient = []\r\n\t\t\t\tlet anchorColor = \"no_color\", nameColor = s12, partial = \"\"\r\n\t\t\t\tlet\tmaxDepth = (testDepth > codes.length ? codes.length : testDepth)\r\n\t\t\t\tmaxDepth = maxDepth * 1\r\n\r\n\t\t\t\t// split into test and remainder\r\n\t\t\t\tlet codesA = codes.slice(0, maxDepth)\r\n\t\t\t\tlet codesB = codes.slice(maxDepth, codes.length)\r\n\t\t\t\tcountTested += codesA.length\r\n\r\n\t\t\t\t// test\r\n\t\t\t\tlet aSpanBlock = [], aNonSpanBlock = []\r\n\t\t\t\tlet spacer = aSpacer.includes(name) ? \" \" : \"\"\r\n\r\n\t\t\t\tfor (let i=0; i < codesA.length; i++) {\r\n\t\t\t\t\tlet character = String.fromCodePoint(codesA[i])\r\n\t\t\t\t\tslot.textContent = character\r\n\r\n\t\t\t\t\t// client: w=span, h=div\r\n\t\t\t\t\tlet cDiv = div.getClientRects()\r\n\t\t\t\t\tlet cSpan = span.getClientRects()\r\n\t\t\t\t\tlet wClient = cSpan[0].width,\r\n\t\t\t\t\t\thClient = cDiv[0].height\r\n\t\t\t\t\tlet whClient = wClient +\" x \"+ hClient\r\n\t\t\t\t\taSizeClient.push(codesA[i] +\": \"+ whClient)\r\n\t\t\t\t\tif (whClient == tofuSize) {\r\n\t\t\t\t\t\taTofu.push(codesA[i])\r\n\t\t\t\t\t\taSpanBlock.push(character)\r\n\t\t\t\t\t\tif (aNonSpanBlock.length) {\r\n\t\t\t\t\t\t\tdisplayA.push(spacer + aNonSpanBlock.join(ZWNJ + spacer) + ZWNJ)\r\n\t\t\t\t\t\t\taNonSpanBlock = [] // reset\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (aSpanBlock.length) {\r\n\t\t\t\t\t\t\tdisplayA.push(sb + spacer + aSpanBlock.join(ZWNJ + spacer) + sc + ZWNJ)\r\n\t\t\t\t\t\t\taSpanBlock = [] // reset\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\taNonSpanBlock.push(character)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// global unique sizes\r\n\t\t\t\t\tif (oTempSizeClientUnique[wClient] == undefined) {\r\n\t\t\t\t\t\toTempSizeClientUnique[wClient] = [hClient]\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\toTempSizeClientUnique[wClient].push(hClient)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// ToDo: sizes + count per script\r\n\r\n\t\t\t\t}\r\n\t\t\t\t// add final bit\r\n\t\t\t\tif (aSpanBlock.length) {\r\n\t\t\t\t\tdisplayA.push(sb + spacer + aSpanBlock.join(ZWNJ + spacer) + sc + ZWNJ)\r\n\t\t\t\t}\r\n\t\t\t\tif (aNonSpanBlock.length) {\r\n\t\t\t\t\tdisplayA.push(spacer + aNonSpanBlock.join(ZWNJ + spacer) + ZWNJ)\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// track tofu\r\n\t\t\t\tif (aTofu.length) {\r\n\t\t\t\t\tcountTofu += aTofu.length\r\n\t\t\t\t\tlet tofuHash = mini(aTofu.join())\r\n\t\t\t\t\toTempTofu[name] = {\"hash\": tofuHash, \"data\": aTofu}\r\n\t\t\t\t}\r\n\r\n\t\t\t\toTempSizeClient[name] = {\"hash\": mini(aSizeClient.join()), \"data\": aSizeClient}\r\n\r\n\t\t\t\t// remainder\r\n\t\t\t\tif (codesB.length > 0) {\r\n\t\t\t\t\tlet aRemainderBlock = []\r\n\t\t\t\t\tfor (let i=0; i < codesB.length; i++) {\r\n\t\t\t\t\t\tlet character = String.fromCodePoint(codesB[i])\r\n\t\t\t\t\t\taRemainderBlock.push(character)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdisplayA.push(spacer + aRemainderBlock.join(ZWNJ + spacer) + ZWNJ)\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// colors\r\n\t\t\t\tlet percent = (aTofu.length/maxDepth) * 100\r\n\t\t\t\tlet strA = displayA.join(\"\")\r\n\t\t\t\tif (percent >= 80) {\r\n\t\t\t\t\tanchorColor = \"sb\"\r\n\t\t\t\t\tnameColor = sb\r\n\t\t\t\t\tif (percent !== 100) {partial = \" [\"+ aTofu.length +\"/\" + maxDepth +\"]\"}\r\n\t\t\t\t\taRed.push(\"<a class='no_color' href='#\"+ anchor +\"'>\"+ name +\"</a>\" + sb + partial + sc)\r\n\t\t\t\t} else if (percent >= 20) {\r\n\t\t\t\t\tnameColor = s2\r\n\t\t\t\t\tpartial = s2 +\" [\"+ aTofu.length +\"/\" + maxDepth +\"]\"+ sc\r\n\t\t\t\t\tstrA = strA.replace(/bad/g, \"s2\")\r\n\t\t\t\t\taOrange.push(\"<a class='no_color' href='#\"+ anchor +\"'>\"+ name +\"</a>\"+ partial)\r\n\t\t\t\t} else if (percent > 0) {\r\n\t\t\t\t\t//anchorColor = \"s4\" // too noisy, just let the count be colored\r\n\t\t\t\t\tnameColor = s4\r\n\t\t\t\t\tpartial = s4 +\" [\"+ aTofu.length +\"/\" + maxDepth +\"]\"+ sc\r\n\t\t\t\t\tstrA = strA.replace(/bad/g, \"s4\")\r\n\t\t\t\t\taYellow.push(\"<a class='no_color' href='#\"+ anchor +\"'>\"+ name +\"</a>\"+ partial)\r\n\t\t\t\t}\r\n\t\t\t\t// display\r\n\t\t\t\taLegend.push(\"<li><a class='\"+ anchorColor +\"' href='#\"+ anchor +\"'>\"+ name +\"</a>\" + partial +\"</li>\")\r\n\r\n\t\t\t\t// append per script tofu + size clickables\r\n\t\t\t\tlet fpBtnTofu = \"\"\r\n\t\t\t\tif (aTofu.length) {\r\n\t\t\t\t\tfpBtnTofu = \" &nbsp <span class='btn14 btnc mono' onClick='logConsole(`fptofuscript`,`\"+ name +\"`)'>[tofu]</span>\"\r\n\t\t\t\t}\r\n\t\t\t\tlet fpBtnSize = \" &nbsp <span class='btn12 btnc mono' onClick='logConsole(`fpsizescript`,`\"+ name +\"`)'>[sizes]</span>\"\r\n\r\n\t\t\t\taVisual.push(oScriptHeader[name][0] + fpBtnTofu + fpBtnSize)\r\n\t\t\t\tlet strStart = \"\", strEnd = \"\"\r\n\t\t\t\tif (oEnlarge[name] !== undefined) {\r\n\t\t\t\t\tstrStart = \"<span style='font-size: \"+ oEnlarge[name][0] +\"px'>\"\r\n\t\t\t\t\tstrEnd = sc\r\n\t\t\t\t}\r\n\t\t\t\taVisual.push(\"<div class='chars'>\" + strStart + strA + strEnd +\"</div>\")\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n\t// reset slot so we don't end up with unwanted scrollbars\r\n\tslot.textContent = \"\"\r\n\t// sort tofu\r\n\tconst namesTofu = Object.keys(oTempTofu).sort()\r\n\tfor (const nameT of namesTofu) {\r\n\t\tfpTofuData[nameT] = oTempTofu[nameT]\r\n\t\tfpTofuHashes.push(nameT +\": \"+ oTempTofu[nameT][\"hash\"])\r\n\t}\r\n\t// sort sizes\r\n\tconst namesClient = Object.keys(oTempSizeClient).sort()\r\n\tfor (const nameC of namesClient) {\r\n\t\tfpSizeDataClient[nameC] = oTempSizeClient[nameC]\r\n\t\tfpSizeHashesClient.push(nameC +\": \"+ oTempSizeClient[nameC][\"hash\"])\r\n\t}\r\n\t// unique sizes\r\n\tconst namesUniqueSizes = Object.keys(oTempSizeClientUnique).sort((a,b) => a-b)\r\n\tfor (const nameU of namesUniqueSizes) {\r\n\t\tlet aHeight = oTempSizeClientUnique[nameU]\r\n\t\taHeight = aHeight.filter(function(item, position) {return aHeight.indexOf(item) === position})\r\n\t\taHeight.sort((a,b) => a-b)\r\n\t\taHeight.forEach(function(h) {\r\n\t\t\tfpSizeClientUnique.push(nameU +\" x \" + h)\r\n\t\t})\r\n\t}\r\n\r\n\t// FP lines\r\n\tlet tofuSizeBtn = \" <span class='btn12 btnc' onClick='logConsole(`tofusize`)'>[details]</span>\"\r\n\tlet tofuFP = mini(fpTofuHashes.join())\r\n\t\t+ \" <span class='btn12 btnc' onClick='logConsole(`fptofu`)'>[summary]</span>\"\r\n\t\t+ \" <span class='btn12 btnc' onClick='logConsole(`fpTofuData`)'>[\"+ countTofu +\"]</span> \"\r\n\t\t+ ((countTofu/countTested) * 100).toFixed(2) + \"%\"\r\n\r\n\tif (countTofu == 0) {\r\n\t\ttofuFP = testParams == \"all | max\" ? sg + \"OMG! CONGRATS: NO TOFU\"+ sc : sg +\"none\"+ sc +\": up your test parameters\"\r\n\t}\r\n\tlet sizeFPClient = mini(fpSizeHashesClient.join())\r\n\t\t+ \" <span class='btn12 btnc' onClick='logConsole(`fpSizeClient`)'>[summary]</span>\"\r\n\t\t+ \" <span class='btn12 btnc' onClick='logConsole(`fpSizeDataClient`)'>[\"+ countTested +\"]</span> \"\r\n\t\t+ \" <span class='btn12 btnc' onClick='logConsole(`fpSizeDataClientUnique`)'>[\"+ fpSizeClientUnique.length +\" sizes]</span> \"\r\n\r\n\tlet strFP = \"<span class='mono'>\"\r\n\t\t+ s12 +\"tofu size: \".padStart(16) + sc + tofuSize + tofuSizeBtn + \"<br>\"\r\n\t\t+ s12 +\"parameters: \".padStart(16) + sc + testParams +\"<br>\"\r\n\t\t+ s12 +\"tofu: \".padStart(16) + sc + tofuFP +\"<br>\"\r\n\t\t+ s12 +\"getClientRects: \".padStart(16) + sc + sizeFPClient +\"</span><br>\"\r\n\r\n\t// display visuals\r\n\taVisual.push(\"<a name='end'></a>\")\r\n\tdom.visual.innerHTML = strFP +\"<br>\"+ aVisual.join(\"<br>\")\r\n\tdom.legend.innerHTML = aLegend.join(\"\") +\"<br><hr><br>\"\r\n\r\n\t// summary\r\n\tif (aRed.length) {\r\n\t\taRed.sort()\r\n\t\taSummary.push(\"<br>\"+ sb + \"unsupported [\" + aRed.length +\"]\"+ sc +\"<br>\")\r\n\t\taSummary.push(\"<br><li>\"+ aRed.join(\"</li><li>\") +\"</li>\")\r\n\t}\r\n\tif (aOrange.length) {\r\n\t\taOrange.sort()\r\n\t\taSummary.push(\"<br>\"+ s2 + \"mixed [\" + aOrange.length +\"]\"+ sc +\"<br>\")\r\n\t\taSummary.push(\"<br><li>\" + aOrange.join(\"</li><li>\") +\"</li>\")\r\n\t}\r\n\tif (aYellow.length) {\r\n\t\taYellow.sort()\r\n\t\taSummary.push(\"<br>\"+ s4 + \"partial [\" + aYellow.length +\"]\"+ sc +\"<br>\")\r\n\t\taSummary.push(\"<br><li>\"+ aYellow.join(\"</li><li>\") +\"</li>\")\r\n\t}\r\n\tdom.summary.innerHTML = \"<span class='s12' style='font-size: 12px;'><b><u>\"\r\n\t\t+ \"SUMMARY</u></b>\"+ sc +\"<br>\"+ aSummary.join(\"\")\r\n\r\n\t// perf\r\n\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms | \" + get_count(\"\")\r\n\tisRunning = false\r\n}\r\n\r\n//dom.allassigned.checked = true\r\nlet isRunning = true\r\n\r\nsetTimeout(function() {\r\n\tPromise.all([\r\n\t\tget_tofu_size(),\r\n\t\tgenerate(),\r\n\t]).then(function(){\r\n\t\treset(false) // false: leave the generate perf value there\r\n\t})\r\n}, 1)\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/fontsmac.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=500\">\n\t<title>mac fonts</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 780px;}\n\t\tindent {margin-left: 20px;}\n\t</style>\n</head>\n\n<body>\n\t<div class=\"offscreen\">\n\t\t<div class=\"normalized\"><span id=\"dfsize\"></span></div>\n\t\t<div><span id=\"dfproportion\"></span></div>\n\t</div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb12\">\n\t\t<thead><tr><th>\n\t\t\t<div class=\"nav-title\">mac fonts</div>\n\t\t</th></tr></thead>\n\t\t<tr><td class=\"intro\"><span class=\"no_color\">\n\t\t\tProgrammatically make sense of Apple's macOS font lists. The <code>group</code>\n\t\t\toption is to aid visual parsing, and does <b>not</b> signify <code>font-families</code>.</br><br>\n\t\t\t\t<span class=\"btn12 btnfirst\" onclick=\"get_links()\">links</span> |\n\t\t\t\t<span class=\"btn12 btn\" onclick=\"get_list()\">list</span>\n\t\t\t\t&nbsp; <select id=\"optList\" name=\"optList\" onchange=\"get_list()\"></select>\n\t\t\t\t&nbsp; <input id=\"optGroup\" type=\"checkbox\" onclick=\"get_list()\" checked> group\n\t\t\t\t&nbsp; | <span class=\"btn12 btn\" onclick=\"analyse()\">analyse since</span>\n\t\t\t\t&nbsp;<select id=\"optAnalyze\" name=\"optAnalyse\" onchange=\"analyse()\"></select>\n\t\t\t\t&nbsp; | <span class=\"btn12 btn\" id=\"txtSearch\" onclick=\"search()\">search</span>\n\t\t\t\t&nbsp;<input type=\"text\" id=\"optSearch\" style=\"width:100px;\"></input>\n\t\t\t\t&nbsp; <span id=\"alert\" class='bad mono'></span>\n\t\t</span></td></tr>\n\t\t<tr><td><hr><br></td></tr>\n\t\t<tr>\n\t\t\t<td class=\"mono\" style=\"text-align: left\">\n\t\t\t<span class=\"spaces no_color\" id=\"results\"></span>\n\t\t\t</td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\nlet fntAll = {list: [], group: []}\nlet aLinks = []\nlet currentDisplay = ''\n\nfunction analyse() {\n\tlet key = dom.optAnalyse.value\n\tlet newDisplay = key +'analyse'\n\tif (currentDisplay == newDisplay) {return}\n\tlet aDisplay = []\n\n\tlet isBase = false, start='', end=''\n\tlet fntsEncountered = []\n\tlet fntDiffs = {}\n\n\tlet aCommon = [], aBase = []\n\tfor (const k of Object.keys(fntData).sort()) {\n\t\tif (k == key) {\n\t\t\t// base starting data\n\t\t\tisBase = true\n\t\t\tstart = fntData[k].version +' '+ fntData[k].name\n\t\t\tfntsEncountered = fntData[k].list\n\t\t\taCommon = fntData[k].list\n\t\t\taBase = fntData[k].list\n\t\t} else {\n\t\t\t// analyse items after base\n\t\t\tlet aNew = fntData[k].list\n\t\t\tif (isBase && undefined !== aNew && aNew.length) {\n\t\t\t\tend = fntData[k].version +' '+ fntData[k].name\n\t\t\t\tfntsEncountered = fntsEncountered.concat(fntData[k].list)\n\t\t\t\tfntDiffs[k] = {}\n\t\t\t\t// common\n\t\t\t\taCommon = aCommon.filter(x => aNew.includes(x))\n\t\t\t\t// use previous aBase to compare\n\t\t\t\tfntDiffs[k].removed = aBase.filter(x => !aNew.includes(x))\n\t\t\t\tfntDiffs[k].added = aNew.filter(x => !aBase.includes(x))\n\t\t\t\t// set aBase for next k\n\t\t\t\taBase = fntData[k].list\n\t\t\t}\n\t\t}\n\t}\n\tfntsEncountered = fntsEncountered.filter(function(item, position) {return fntsEncountered.indexOf(item) === position})\n\taDisplay.push(s14 +'ANALYSIS: '+ sc + s12 + start + sc + ' to '+ s12 + end + sc +' ['+ fntsEncountered.length +']<br>')\n\n\t// TB\n\tlet aTB = fntTB.list\n\tlet aNotInTB = aTB.filter(x => !aCommon.includes(x))\n\tlet strTB = aNotInTB.length ? '' : sg +' ['+ green_tick +' all fonts in common]'+ sc\n\t// common\n\tlet objCommon = group(aCommon)\n\tlet aCommonDisplay = group_display(objCommon)\n\taDisplay.push('<details><summary>'+ s12 +'IN COMMON ' + sc + '['+ aCommon.length\n\t\t+']</summary><p>' + aCommonDisplay.join('<br>') +'</p></details>')\n\taDisplay.push('<details><summary>'+ s12 +'TB SYSTEM FONTS ' + sc +'['+ fntTB.list.length\n\t\t+ strTB +']</summary><p>' + fntTB.group.join('<br>') +'</p></details>')\n\t// TB missing\n\tif (aNotInTB.length) {\n\t\tlet objNotTB = group(aNotInTB)\n\t\tlet aNotTB = group_display(objNotTB)\n\t\taDisplay.push(sb +'TB SYSTEM FONTS not IN COMMON '+ sc\n\t\t\t+ '['+ aNotInTB.length +']<br><br>' + aNotTB.join('<br>') +'<br>'\n\t\t)\n\t}\n\t// DIFFS\n\taDisplay.push('<hr></br>' + s14 +'DIFFS'+ sc +'<br>')\n\t//console.log(fntDiffs)\n\tfor (const k of Object.keys(fntDiffs)) {\n\t\t// assume some changes\n\t\taDisplay.push(s12 + fntData[k].version +' '+ fntData[k].name + sc +'<br>')\n\t\tfor (const j of Object.keys(fntDiffs[k]).sort()) {\n\t\t\tlet list = fntDiffs[k][j]\n\t\t\tif (list.length) {\n\t\t\t\tlet objSub = group(list)\n\t\t\t\tlet aSub = group_display(objSub)\n\t\t\t\tif (list.length < 40) {\n\t\t\t\t\taDisplay.push(j +' ['+ list.length +']<br><span class=\"indent\">'\n\t\t\t\t\t\t+ aSub.join('<br>') +'</span><br>'\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\taDisplay.push('<details><summary>'+ j +' ['+ list.length +']</summary><p>'\n\t\t\t\t\t\t+ aSub.join('<br>') +'</p></details>')\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// ALL\n\tlet objEncountered = group(fntsEncountered)\n\tlet aEncountered = group_display(objEncountered)\n\taDisplay.push('<hr></br>')\n\taDisplay.push('<details><summary>'+ s14 + 'ALL FONTS'+ sc +' since '\n\t\t\t+ s14 + start +sc +' ['+ fntsEncountered.length +']</summary><p>'\n\t\t\t+ aEncountered.join('<br>') +'</p></details>')\n\t\n\tcurrentDisplay = newDisplay\n\tdom.results.innerHTML = aDisplay.join('<br>')\n\t//console.log('displaying:', newDisplay)\n}\n\nfunction get_links() {\n\tdom.results.innerHTML = aLinks.join('<br>')\n\tcurrentDisplay = 'links'\n}\n\nfunction get_list() {\n\tlet k = dom.optList.value\n\tlet newDisplay = k +'list'+ dom.optGroup.checked\n\tif (currentDisplay == newDisplay) {return}\n\n\tlet aDisplay = [], obj\n\tif ('all' == k) {\n\t\tlet i = 0, start = '', end = ''\n\t\tfor (const k of Object.keys(fntData).sort()) {\n\t\t\ti++\n\t\t\tif (1 == i) {start = fntData[k].version +' '+ fntData[k].name\n\t\t\t} else { end = fntData[k].version +' '+ fntData[k].name\n\t\t\t}\n\t\t}\n\t\taDisplay.push(s12 + start + sc + ' to '+ s12 + end + sc +' ['+ fntAll.list.length +']<br>')\n\t\tobj = fntAll\n\t} else {\n\t\tlet link = '<a class=\"blue\" target=\"_blank\" href=\"'+ fntData[k].url+ '\">support link</a>'\n\t\taDisplay.push(s12 + fntData[k].version +' '+ fntData[k].name + sc +' ['+ fntData[k].count +'] '+ link +'<br>')\n\t\tobj = fntData[k]\n\t}\n\n\tlet data = dom.optGroup.checked ? obj.group : obj.list\n\tif (undefined !== data) {\n\t\taDisplay.push(data.join('<br>'))\n\t\tdom.results.innerHTML = aDisplay.join('<br>')\n\t\tcurrentDisplay = newDisplay\n\t\t//console.log('displaying:', newDisplay)\n\t}\n}\n\nfunction group(array) {\n\t// return obj\n\tlet obj = {}\n\tarray.forEach(function(font){\n\t\tlet key = font.split(' ')[0]\n\t\tlet key5 = key.slice(0,5)\n\t\tif ('Noto' == key) {\n\t\t\tkey += ' '+ font.split(' ')[1]\n\t\t} else if ('Nanum' == key5 || 'Akaya' == key5 || 'Apple' == key5 || 'Chalk' == key5) {\n\t\t\tkey = key5\n\t\t} else if ('STIX' == key.slice(0,4)) {\n\t\t\tkey = key.slice(0,4)\n\t\t} else if ('BiauKai' == key.slice(0,7)) {\n\t\t\tkey = key.slice(0,7)\n\t\t} else {\n\t\t\tlet key2 = font.split('-')[0]\n\t\t\tif (key2.length < key.length) {key = key2}\n\t\t}\n\t\tif (undefined == obj[key]) {obj[key] = [font]} else {obj[key].push(font)}\n\t})\n\treturn obj\n}\n\nfunction group_display(obj, k) {\n\tlet array = []\n\tfor (const j of (Object.keys(obj).sort())) {\n\t\tarray.push(s6 + j + sc +' {<br><span class=\"faint indent\">'+ obj[j].join(', ') +'</span><br>}')\n\t\t// if k provided then we also build fntList data\n\t\tif (undefined !== k) {fntData[k].data[j] = obj[j]}\n\t}\n\treturn array\n}\n\nfunction run_once() {\n\t// build lists for analysis\n\tlet aOptionsList = []\n\tlet allSet = new Set() // all unique font names: so we can help track we don't \"dupe\" across releases e.g. case, spaces\n\tlet allLower = new Set()\n\tfntAll = {list: [], group: []}\n\taLinks = [s12 +'SUPPORT LINKS'+ sc +'<br>']\n\n\tlet aAll = []\n\tfor (const k of Object.keys(fntData).sort()) {\n\t\tif (fntData[k].list.length) {\n\t\t\taOptionsList.push('<option value=\"'+ k +'\">' + fntData[k].name +' ('+ fntData[k].version +')</option>')\n\t\t\tlet array = fntData[k].list.sort()\n\t\t\taAll = aAll.concat(array)\n\t\t\tfntData[k].count = array.length\n\t\t\t// unique lower case\n\t\t\tarray.forEach(function(font){allLower.add(font.toLowerCase())})\n\t\t\t// group\n\t\t\tlet obj = group(array)\n\t\t\tfntData[k].data = {}\n\t\t\tfntData[k].group = group_display(obj, k)\n\t\t\t/* tidy up into lines for fntData[k].list\n\t\t\tlet aPlain = []\n\t\t\tfor (const g of Object.keys(obj).sort()) {aPlain.push(\"'\" + obj[g].join(\"','\") +\"'\")}\n\t\t\tconsole.log(k +'\\n', aPlain.join(\",\\n\") + \",\")\n\t\t\t//*/\n\t\t\t// links\n\t\t\tlet item = fntData[k].version +' '+ fntData[k].name\n\t\t\titem = item.padStart(15) +': '\n\t\t\titem += '<a class=\"blue\" href=\"'+ fntData[k].url +'\" target=\"_blank\">' + fntData[k].url +'</a>'\n\t\t\taLinks.push(item + '<br>')\n\t\t}\n\t}\n\t// dedupe all unique names into a sorted array\n\taAll = aAll.filter(function(item, position) {return aAll.indexOf(item) === position})\n\taAll.sort()\n\tfntAll['list'] = aAll\n\tlet aAllLower = Array.from(allLower)\n\taAllLower.sort()\n\tif (aAll.length !== aAllLower.length) {\n\t\tconsole.error('all length ', aAll.length, '!== all lowercase length', aAllLower.length)\n\t\tconsole.log(aAll.join('\\n'))\n\t\tdom.alert.innerHTML = '[dupes detected]'\n\t}\n\t// group all\n\tlet objall = group(aAll)\n\tfntAll.group = group_display(objall)\n\n\t// populate options\n\taOptionsList.push('<option value=\"all\">ALL</option>')\n\tdom.optList.innerHTML = aOptionsList.join('')\n\tlet aOptionsAnalyse = aOptionsList.slice(0, aOptionsList.length - 2)\n\tdom.optAnalyse.innerHTML = aOptionsAnalyse.join('')\n\n\t// TB fonts\n\tfntTB.list.sort()\n\tlet objTB = group(fntTB.list)\n\tfntTB.group = group_display(objTB)\n\n\t// add search listener\n\tlet target = dom.optSearch\n\ttarget.addEventListener(\"keypress\", function(event) {\n\t\tif (event.key === \"Enter\") {search()}\n\t})\n\n\t// display something\n\tanalyse()\n}\n\nfunction search() {\n\tlet term = (dom.optSearch.value).trim()\n\tif (0 == term.length) {return}\n\tcurrentDisplay = 'search'\n\ttry {\n\t\tlet search = term.toLowerCase() // case-insenstive\n\t\tlet oData = {}, isFound = false\n\t\tfor (const k of Object.keys(fntData).sort()) {\n\t\t\tif (fntData[k].list.length) {\n\t\t\t\tlet aFound = []\n\t\t\t\tlet aSearch = fntData[k].list.sort()\n\t\t\t\taSearch.forEach(function(item){\n\t\t\t\t\tlet value = item.toLowerCase()\n\t\t\t\t\tif (value.includes(search)) {aFound.push(item)}\n\t\t\t\t})\n\t\t\t\tif (aFound.length) {\n\t\t\t\t\tisFound = true\n\t\t\t\t\toData[fntData[k].version +' '+ fntData[k].name] = aFound\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet aDisplay = [s14 +'SEARCH'+ sc +': ' + term +'<br>']\n\t\tif (isFound) {\n\t\t\t//console.log(oData)\n\t\t\tfor (const k of Object.keys(oData).sort()) {\n\t\t\t\taDisplay.push(s12 + k + sc)\n\t\t\t\taDisplay.push('<br><div class=\"indent\">'+ oData[k].join(', ') +'</div><br>')\n\t\t\t}\n\t\t} else {\n\t\t\taDisplay.push('no results')\n\t\t}\n\t\tdom.results.innerHTML = aDisplay.join('<br>')\n\t} catch(e) {\n\t\tdom.results.innerHTML = e+''\n\t}\n}\n\nlet fntTB = {\n\t// system fonts\n\tlist: [\n\t\t// allowlist\n\t\t'AppleGothic','Apple Color Emoji','Arial','Arial Black','Arial Narrow','Courier','Courier New',\n\t\t'Geneva','Georgia',\n\t\t//'Heiti TC', // this is not a font per se, it comes in light + medium\n\t\t'Helvetica','Helvetica Neue','Hiragino Kaku Gothic ProN',\n\t\t'Kailasa','Lucida Grande','Menlo','Monaco','PingFang HK','PingFang SC','PingFang TC','Songti SC',\n\t\t'Songti TC','Tahoma','Thonburi','Times','Times New Roman','Verdana',\n\t\t// weighted/styles\n\t\t'Arial Bold','Arial Bold Italic','Arial Italic',\n\t\t'Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic',\n\t\t'Courier Bold','Courier Bold Oblique','Courier Oblique',\n\t\t'Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t'Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t'Heiti TC Light','Heiti TC Medium',\n\t\t'Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Oblique',\n\t\t'Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Italic','Helvetica Neue Light',\n\t\t\t'Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin',\n\t\t\t'Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic',\n\t\t'Hiragino Kaku Gothic ProN W3','Hiragino Kaku Gothic ProN W6',\n\t\t'Kailasa Bold',\n\t\t'Lucida Grande Bold',\n\t\t'Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t'PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight',\n\t\t'PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight',\n\t\t'PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t'Songti SC Black','Songti SC Bold','Songti SC Light',\n\t\t'Songti TC Bold','Songti TC Light',\n\t\t'Tahoma Bold',\n\t\t'Thonburi Bold','Thonburi Light',\n\t\t'Times Bold','Times Bold Italic','Times Italic',\n\t\t'Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t'Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t]\n\n}\n\n/*\n\tNOTE: except 10.15 + 11, lists don't distinguish between built-in vs downloadable\n\t  catalina  10.15: https://support.apple.com/en-us/101429\n \t   big sur     11: https://support.apple.com/en-us/101430\n\t  monterey     12: https://support.apple.com/en-us/103203\n\t   ventura     13: https://support.apple.com/en-nz/103197\n\t    sonoma     14: https://support.apple.com/en-nz/108939\n\t   sequoia     15: https://support.apple.com/en-us/120414\n\t     tahoe     26: https://support.apple.com/en-us/122869\n\n\tgroup by first part of font name: i.e don't distinguish between Avenir\n\tvs Avenir Book: to break it up somewhat for easier analysis/parsing\n*/\nlet fntData = {\n\t'v10.15': {\n\t\tname: 'Catalina',\n\t\tversion: 10.15,\n\t\turl: 'https://support.apple.com/en-us/101429',\n\t\tlist: [\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'Baghdad',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier','Courier Bold','Courier Bold Oblique','Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic','Courier Oblique',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Nadeem',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Javanese','Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans Oriya','Noto Sans Oriya Bold',\n\t\t\t'Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STIXGeneral','STIXGeneral-Bold','STIXGeneral-BoldItalic','STIXGeneral-Italic','STIXIntegralsD','STIXIntegralsD-Bold','STIXIntegralsSm','STIXIntegralsSm-Bold','STIXIntegralsUp','STIXIntegralsUp-Bold','STIXIntegralsUpD','STIXIntegralsUpD-Bold','STIXIntegralsUpSm','STIXIntegralsUpSm-Bold','STIXNonUnicode','STIXNonUnicode-Bold','STIXNonUnicode-BoldItalic','STIXNonUnicode-Italic','STIXSizeFiveSym','STIXSizeFourSym','STIXSizeFourSym-Bold','STIXSizeOneSym','STIXSizeOneSym-Bold','STIXSizeThreeSym','STIXSizeThreeSym-Bold','STIXSizeTwoSym','STIXSizeTwoSym-Bold','STIXVariants','STIXVariants-Bold',\n\t\t\t'STSong',\n\t\t\t'Sana',\n\t\t\t'Sathu',\n\t\t\t'Savoye LET',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Bold',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times Bold','Times Bold Italic','Times Italic','Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic','Times Roman',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Webdings',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v11': {\n\t\tname: 'Big Sur',\n\t\tversion: 11,\n\t\turl: 'https://support.apple.com/en-us/101430',\n\t\tlist: [\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple Color Emoji','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'Baghdad',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier','Courier Bold','Courier Bold Oblique','Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic','Courier Oblique',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Nadeem',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans Oriya','Noto Sans Oriya Bold',\n\t\t\t'Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STIXGeneral','STIXGeneral-Bold','STIXGeneral-BoldItalic','STIXGeneral-Italic','STIXIntegralsD','STIXIntegralsD-Bold','STIXIntegralsSm','STIXIntegralsSm-Bold','STIXIntegralsUp','STIXIntegralsUp-Bold','STIXIntegralsUpD','STIXIntegralsUpD-Bold','STIXIntegralsUpSm','STIXIntegralsUpSm-Bold','STIXNonUnicode','STIXNonUnicode-Bold','STIXNonUnicode-BoldItalic','STIXNonUnicode-Italic','STIXSizeFiveSym','STIXSizeFourSym','STIXSizeFourSym-Bold','STIXSizeOneSym','STIXSizeOneSym-Bold','STIXSizeThreeSym','STIXSizeThreeSym-Bold','STIXSizeTwoSym','STIXSizeTwoSym-Bold','STIXVariants','STIXVariants-Bold',\n\t\t\t'STSong',\n\t\t\t'Sana',\n\t\t\t'Sathu',\n\t\t\t'Savoye LET',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Bold',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times Bold','Times Bold Italic','Times Italic','Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic','Times Roman',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Webdings',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v12': {\n\t\tname: 'Monterey',\n\t\tversion: 12,\n\t\turl: 'https://support.apple.com/en-us/103203',\n\t\tlist: [\n\t\t\t'Academy Engraved LET',\n\t\t\t'Adelle Sans Devanagari','Adelle Sans Devanagari Bold','Adelle Sans Devanagari Extrabold','Adelle Sans Devanagari Heavy','Adelle Sans Devanagari Light','Adelle Sans Devanagari Semibold','Adelle Sans Devanagari Thin',\n\t\t\t'AkayaKanadaka','AkayaTelivigala',\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Annai MN',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple Color Emoji','Apple LiGothic Medium','Apple LiSung Light','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Arima Koshi','Arima Koshi Black','Arima Koshi Bold','Arima Koshi ExtraBold','Arima Koshi ExtraLight','Arima Koshi Light','Arima Koshi Medium','Arima Koshi Thin','Arima Madurai','Arima Madurai Black','Arima Madurai Bold','Arima Madurai ExtraLight','Arima Madurai Light','Arima Madurai Medium','Arima Madurai Semi Bold','Arima Madurai Thin',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Baghdad',\n\t\t\t'Bai Jamjuree','Bai Jamjuree Bold','Bai Jamjuree Bold Italic','Bai Jamjuree ExtraLight','Bai Jamjuree ExtraLight Italic','Bai Jamjuree Italic','Bai Jamjuree Light','Bai Jamjuree Light Italic','Bai Jamjuree Medium','Bai Jamjuree Medium Italic','Bai Jamjuree SemiBold','Bai Jamjuree SemiBold Italic',\n\t\t\t'Baloo 2','Baloo 2 Bold','Baloo 2 ExtraBold','Baloo 2 Medium','Baloo 2 SemiBold','Baloo Bhai 2','Baloo Bhai 2 Bold','Baloo Bhai 2 ExtraBold','Baloo Bhai 2 Medium','Baloo Bhai 2 SemiBold','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Bhaina 2 Bold','Baloo Bhaina 2 ExtraBold','Baloo Bhaina 2 Medium','Baloo Bhaina 2 SemiBold','Baloo Chettan 2','Baloo Chettan 2 Bold','Baloo Chettan 2 ExtraBold','Baloo Chettan 2 Medium','Baloo Chettan 2 SemiBold','Baloo Da 2','Baloo Da 2 Bold','Baloo Da 2 ExtraBold','Baloo Da 2 Medium','Baloo Da 2 SemiBold','Baloo Paaji 2','Baloo Paaji 2 Bold','Baloo Paaji 2 ExtraBold','Baloo Paaji 2 Medium','Baloo Paaji 2 SemiBold','Baloo Tamma 2','Baloo Tamma 2 Bold','Baloo Tamma 2 ExtraBold','Baloo Tamma 2 Medium','Baloo Tamma 2 SemiBold','Baloo Tammudu 2','Baloo Tammudu 2 Bold','Baloo Tammudu 2 ExtraBold','Baloo Tammudu 2 Medium','Baloo Tammudu 2 SemiBold','Baloo Thambi 2','Baloo Thambi 2 Bold','Baloo Thambi 2 ExtraBold','Baloo Thambi 2 Medium','Baloo Thambi 2 SemiBold',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baoli SC','Baoli TC',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'BiauKai',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Cambay Devanagari','Cambay Devanagari Bold','Cambay Devanagari Bold Oblique','Cambay Devanagari Oblique',\n\t\t\t'Canela','Canela Bold','Canela Bold Italic','Canela Deck','Canela Deck Bold','Canela Deck Bold Italic','Canela Deck Medium','Canela Deck Medium Italic','Canela Deck Italic','Canela Italic','Canela Text','Canela Text Bold','Canela Text Bold Italic','Canela Text Medium','Canela Text Medium Italic','Canela Text Italic',\n\t\t\t'Catamaran','Catamaran Black','Catamaran Bold','Catamaran ExtraBold','Catamaran ExtraLight','Catamaran Light','Catamaran Medium','Catamaran SemiBold','Catamaran Thin',\n\t\t\t'Chakra Petch','Chakra Petch Bold','Chakra Petch Bold Italic','Chakra Petch ExtraLight','Chakra Petch ExtraLight Italic','Chakra Petch Italic','Chakra Petch Light','Chakra Petch Light Italic','Chakra Petch Medium','Chakra Petch Medium Italic','Chakra Petch SemiBold','Chakra Petch SemiBold Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charm','Charm Bold',\n\t\t\t'Charmonman','Charmonman Bold',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Domaine Display','Domaine Display Bold','Domaine Display Bold Italic','Domaine Display Italic','Domaine Display Medium','Domaine Display Medium Italic',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Fahkwang','Fahkwang Bold','Fahkwang Bold Italic','Fahkwang ExtraLight','Fahkwang ExtraLight Italic','Fahkwang Italic','Fahkwang Light','Fahkwang Light Italic','Fahkwang Medium','Fahkwang Medium Italic','Fahkwang SemiBold','Fahkwang SemiBold Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Founders Grotesk','Founders Grotesk Bold','Founders Grotesk Bold Italic','Founders Grotesk Condensed','Founders Grotesk Condensed Bold','Founders Grotesk Condensed Semibold','Founders Grotesk Light','Founders Grotesk Light Italic','Founders Grotesk Medium','Founders Grotesk Medium Italic','Founders Grotesk Italic','Founders Grotesk Semibold','Founders Grotesk Semibold Italic','Founders Grotesk Text','Founders Grotesk Text Bold','Founders Grotesk Text Bold Italic','Founders Grotesk Text Italic',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gotu',\n\t\t\t'Grantha Sangam MN','Grantha Sangam MN Black','Grantha Sangam MN Bold','Grantha Sangam MN DemiBold','Grantha Sangam MN Light','Grantha Sangam MN Medium',\n\t\t\t'Graphik','Graphik Bold','Graphik Bold Italic','Graphik Compact','Graphik Compact Bold','Graphik Compact Bold Italic','Graphik Compact Medium','Graphik Compact Medium Italic','Graphik Compact Italic','Graphik Compact Semibold','Graphik Compact Semibold Italic','Graphik Light','Graphik Light Italic','Graphik Medium','Graphik Medium Italic','Graphik Italic','Graphik Semibold','Graphik Semibold Italic',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'GungSeo',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Hannotate SC','Hannotate SC Bold','Hannotate TC','Hannotate TC Bold',\n\t\t\t'HanziPen SC','HanziPen SC Bold','HanziPen TC','HanziPen TC Bold',\n\t\t\t'HeadLineA',\n\t\t\t'Hei',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans CNS W3','Hiragino Sans CNS W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'Hubballi',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','K2D Bold','K2D Bold Italic','K2D ExtraBold','K2D ExtraBold Italic','K2D ExtraLight','K2D ExtraLight Italic','K2D Italic','K2D Light','K2D Light Italic','K2D Medium','K2D Medium Italic','K2D SemiBold','K2D SemiBold Italic','K2D Thin','K2D Thin Italic',\n\t\t\t'Kai',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kaiti SC','Kaiti SC Black','Kaiti SC Bold','Kaiti TC','Kaiti TC Black','Kaiti TC Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Katari','Katari Black','Katari Black Italic','Katari Bold','Katari Bold Italic','Katari Italic','Katari Medium','Katari Medium Italic',\n\t\t\t'Kavivanar',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'KoHo','KoHo Bold','KoHo Bold Italic','KoHo ExtraLight','KoHo ExtraLight Italic','KoHo Italic','KoHo Light','KoHo Light Italic','KoHo Medium','KoHo Medium Italic','KoHo SemiBold','KoHo SemiBold Italic',\n\t\t\t'Kodchasan','Kodchasan Bold','Kodchasan Bold Italic','Kodchasan ExtraLight','Kodchasan ExtraLight Italic','Kodchasan Italic','Kodchasan Light','Kodchasan Light Italic','Kodchasan Medium','Kodchasan Medium Italic','Kodchasan SemiBold','Kodchasan SemiBold Italic',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krub','Krub Bold','Krub Bold Italic','Krub ExtraLight','Krub ExtraLight Italic','Krub Italic','Krub Light','Krub Light Italic','Krub Medium','Krub Medium Italic','Krub SemiBold','Krub SemiBold Italic',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lahore Gurmukhi','Lahore Gurmukhi Bold','Lahore Gurmukhi Light','Lahore Gurmukhi Medium','Lahore Gurmukhi SemiBold',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy','Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lava Devanagari','Lava Devanagari Bold','Lava Devanagari Heavy','Lava Devanagari Medium','Lava Kannada','Lava Kannada Bold','Lava Kannada Heavy','Lava Kannada Medium','Lava Telugu','Lava Telugu Bold','Lava Telugu Heavy','Lava Telugu Medium',\n\t\t\t'LiHei Pro',\n\t\t\t'LiSong Pro',\n\t\t\t'Libian SC','Libian TC',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Maku','Maku Bold',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Mali','Mali Bold','Mali Bold Italic','Mali ExtraLight','Mali ExtraLight Italic','Mali Italic','Mali Light','Mali Light Italic','Mali Medium','Mali Medium Italic','Mali SemiBold','Mali SemiBold Italic',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Modak',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta','Mukta Bold','Mukta ExtraBold','Mukta ExtraLight','Mukta Light','Mukta Malar','Mukta Malar Bold','Mukta Malar ExtraBold','Mukta Malar ExtraLight','Mukta Malar Light','Mukta Malar Medium','Mukta Malar SemiBold','Mukta Medium','Mukta SemiBold','Mukta Vaani','Mukta Vaani Bold','Mukta Vaani ExtraBold','Mukta Vaani ExtraLight','Mukta Vaani Light','Mukta Vaani Medium','Mukta Vaani SemiBold',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Myriad Arabic','Myriad Arabic Black','Myriad Arabic Black Italic','Myriad Arabic Bold','Myriad Arabic Bold Italic','Myriad Arabic Italic','Myriad Arabic Light','Myriad Arabic Light Italic','Myriad Arabic Semibold','Myriad Arabic Semibold Italic',\n\t\t\t'Nadeem',\n\t\t\t'Nanum Brush Script','Nanum Pen Script','Nanum Gothic','Nanum Gothic Bold','Nanum Gothic ExtraBold','Nanum Myeongjo','Nanum Myeongjo Bold','Nanum Myeongjo ExtraBold',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Niramit','Niramit Bold','Niramit Bold Italic','Niramit ExtraLight','Niramit ExtraLight Italic','Niramit Italic','Niramit Light','Niramit Light Italic','Niramit Medium','Niramit Medium Italic','Niramit SemiBold','Niramit SemiBold Italic',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans Oriya','Noto Sans Oriya Bold',\n\t\t\t'Noto Serif Kannada','Noto Serif Kannada Black','Noto Serif Kannada Bold','Noto Serif Kannada ExtraBold','Noto Serif Kannada ExtraLight','Noto Serif Kannada Light','Noto Serif Kannada Medium','Noto Serif Kannada SemiBold','Noto Serif Kannada Thin','Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'October Compressed Devanagari','October Compressed Devanagari Black','October Compressed Devanagari Bold','October Compressed Devanagari ExtraLight','October Compressed Devanagari Hairline','October Compressed Devanagari Heavy','October Compressed Devanagari Light','October Compressed Devanagari Medium','October Compressed Devanagari Thin','October Compressed Tamil','October Compressed Tamil Black','October Compressed Tamil Bold','October Compressed Tamil ExtraLight','October Compressed Tamil Hairline','October Compressed Tamil Heavy','October Compressed Tamil Light','October Compressed Tamil Medium','October Compressed Tamil Thin','October Condensed Devanagari','October Condensed Devanagari Black','October Condensed Devanagari Bold','October Condensed Devanagari ExtraLight','October Condensed Devanagari Hairline','October Condensed Devanagari Heavy','October Condensed Devanagari Light','October Condensed Devanagari Medium','October Condensed Devanagari Thin','October Condensed Tamil','October Condensed Tamil Black','October Condensed Tamil Bold','October Condensed Tamil ExtraLight','October Condensed Tamil Hairline','October Condensed Tamil Heavy','October Condensed Tamil Light','October Condensed Tamil Medium','October Condensed Tamil Thin','October Devanagari','October Devanagari Black','October Devanagari Bold','October Devanagari ExtraLight','October Devanagari Hairline','October Devanagari Heavy','October Devanagari Light','October Devanagari Medium','October Devanagari Thin','October Tamil','October Tamil Black','October Tamil Bold','October Tamil ExtraLight','October Tamil Hairline','October Tamil Heavy','October Tamil Light','October Tamil Medium','October Tamil Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'PCMyungjo',\n\t\t\t'PSL Ornanong Pro','PSL Ornanong Pro Bold','PSL Ornanong Pro Bold Italic','PSL Ornanong Pro Demibold','PSL Ornanong Pro Demibold Italic','PSL Ornanong Pro Italic','PSL Ornanong Pro Light','PSL Ornanong Pro Light Italic',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Padyakke Expanded One',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Party LET',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PilGi',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Produkt','Produkt Extralight','Produkt Extralight Italic','Produkt Light','Produkt Light Italic','Produkt Medium','Produkt Medium Italic','Produkt Italic',\n\t\t\t'Proxima Nova','Proxima Nova Bold','Proxima Nova Bold It','Proxima Nova Extrabold','Proxima Nova Extrabold It','Proxima Nova It','Proxima Nova Light','Proxima Nova Light It','Proxima Nova Medium','Proxima Nova Medium It','Proxima Nova Semibold','Proxima Nova Semibold It',\n\t\t\t'Publico Headline Black','Publico Headline Black Italic','Publico Headline Bold','Publico Headline Bold Italic','Publico Headline Italic','Publico Headline Roman','Publico Text Bold','Publico Text Bold Italic','Publico Text Italic','Publico Text Roman','Publico Text Semibold','Publico Text Semibold Italic',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STFangsong',\n\t\t\t'STHeiti',\n\t\t\t'STIX Two Math','STIX Two Text','STIX Two Text Bold','STIX Two Text Bold Italic','STIX Two Text Italic','STIXGeneral','STIXGeneral-Bold','STIXGeneral-BoldItalic','STIXGeneral-Italic','STIXIntegralsD','STIXIntegralsD-Bold','STIXIntegralsSm','STIXIntegralsSm-Bold','STIXIntegralsUp','STIXIntegralsUp-Bold','STIXIntegralsUpD','STIXIntegralsUpD-Bold','STIXIntegralsUpSm','STIXIntegralsUpSm-Bold','STIXNonUnicode','STIXNonUnicode-Bold','STIXNonUnicode-BoldItalic','STIXNonUnicode-Italic','STIXSizeFiveSym','STIXSizeFourSym','STIXSizeFourSym-Bold','STIXSizeOneSym','STIXSizeOneSym-Bold','STIXSizeThreeSym','STIXSizeThreeSym-Bold','STIXSizeTwoSym','STIXSizeTwoSym-Bold','STIXVariants','STIXVariants-Bold',\n\t\t\t'STKaiti',\n\t\t\t'STSong',\n\t\t\t'STXihei',\n\t\t\t'Sama Devanagari','Sama Devanagari Bold','Sama Devanagari Book','Sama Devanagari ExtraBold','Sama Devanagari Medium','Sama Devanagari SemiBold','Sama Gujarati','Sama Gujarati Bold','Sama Gujarati Book','Sama Gujarati ExtraBold','Sama Gujarati Medium','Sama Gujarati SemiBold','Sama Gurmukhi','Sama Gurmukhi Bold','Sama Gurmukhi Book','Sama Gurmukhi ExtraBold','Sama Gurmukhi Medium','Sama Gurmukhi SemiBold','Sama Kannada','Sama Kannada Bold','Sama Kannada Book','Sama Kannada ExtraBold','Sama Kannada Medium','Sama Kannada SemiBold','Sama Malayalam','Sama Malayalam Bold','Sama Malayalam Book','Sama Malayalam ExtraBold','Sama Malayalam Medium','Sama Malayalam SemiBold','Sama Tamil','Sama Tamil Bold','Sama Tamil Book','Sama Tamil ExtraBold','Sama Tamil Medium','Sama Tamil SemiBold',\n\t\t\t'Sana',\n\t\t\t'Sarabun','Sarabun Bold','Sarabun Bold Italic','Sarabun ExtraBold','Sarabun ExtraBold Italic','Sarabun ExtraLight','Sarabun ExtraLight Italic','Sarabun Italic','Sarabun Light','Sarabun Light Italic','Sarabun Medium','Sarabun Medium Italic','Sarabun SemiBold','Sarabun SemiBold Italic','Sarabun Thin','Sarabun Thin Italic',\n\t\t\t'Sathu',\n\t\t\t'Sauber Script',\n\t\t\t'Savoye LET',\n\t\t\t'Shobhika','Shobhika Bold',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'SimSong','SimSong Bold',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Spot Mono','Spot Mono Bold','Spot Mono Medium',\n\t\t\t'Srisakdi','Srisakdi Bold',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Black','Tamil Sangam MN Bold','Tamil Sangam MN Demibold','Tamil Sangam MN Light','Tamil Sangam MN Medium',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Tiro Bangla','Tiro Bangla Italic','Tiro Devanagari Hindi','Tiro Devanagari Hindi Italic','Tiro Devanagari Marathi','Tiro Devanagari Marathi Italic','Tiro Devanagari Sanskrit','Tiro Devanagari Sanskrit Italic','Tiro Gurmukhi','Tiro Gurmukhi Italic','Tiro Kannada','Tiro Kannada Italic','Tiro Tamil','Tiro Tamil Italic','Tiro Telugu','Tiro Telugu Italic',\n\t\t\t'Toppan Bunkyu Gothic','Toppan Bunkyu Gothic Demibold','Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold','Toppan Bunkyu Mincho',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Tsukushi A Round Gothic','Tsukushi A Round Gothic Bold','Tsukushi B Round Gothic','Tsukushi B Round Gothic Bold',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Webdings',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light','Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium','YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium','YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t'Yuanti SC','Yuanti SC Bold','Yuanti SC Light','Yuanti TC','Yuanti TC Bold','Yuanti TC Light',\n\t\t\t'Yuppy SC','Yuppy TC',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v13': {\n\t\tname: 'Ventura',\n\t\tversion: 13,\n\t\turl: 'https://support.apple.com/en-us/103197',\n\t\tlist: [\n\t\t\t'Academy Engraved LET',\n\t\t\t'Adelle Sans Devanagari','Adelle Sans Devanagari Bold','Adelle Sans Devanagari Extrabold','Adelle Sans Devanagari Heavy','Adelle Sans Devanagari Light','Adelle Sans Devanagari Semibold','Adelle Sans Devanagari Thin',\n\t\t\t'AkayaKanadaka','AkayaTelivigala',\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Annai MN',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple Color Emoji','Apple LiGothic Medium','Apple LiSung Light','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Arima Koshi','Arima Koshi Black','Arima Koshi Bold','Arima Koshi ExtraBold','Arima Koshi ExtraLight','Arima Koshi Light','Arima Koshi Medium','Arima Koshi Thin','Arima Madurai','Arima Madurai Black','Arima Madurai Bold','Arima Madurai ExtraLight','Arima Madurai Light','Arima Madurai Medium','Arima Madurai Semi Bold','Arima Madurai Thin',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Baghdad',\n\t\t\t'Bai Jamjuree','Bai Jamjuree Bold','Bai Jamjuree Bold Italic','Bai Jamjuree ExtraLight','Bai Jamjuree ExtraLight Italic','Bai Jamjuree Italic','Bai Jamjuree Light','Bai Jamjuree Light Italic','Bai Jamjuree Medium','Bai Jamjuree Medium Italic','Bai Jamjuree SemiBold','Bai Jamjuree SemiBold Italic',\n\t\t\t'Baloo 2','Baloo 2 Bold','Baloo 2 ExtraBold','Baloo 2 Medium','Baloo 2 SemiBold','Baloo Bhai 2','Baloo Bhai 2 Bold','Baloo Bhai 2 ExtraBold','Baloo Bhai 2 Medium','Baloo Bhai 2 SemiBold','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Bhaina 2 Bold','Baloo Bhaina 2 ExtraBold','Baloo Bhaina 2 Medium','Baloo Bhaina 2 SemiBold','Baloo Chettan 2','Baloo Chettan 2 Bold','Baloo Chettan 2 ExtraBold','Baloo Chettan 2 Medium','Baloo Chettan 2 SemiBold','Baloo Da 2','Baloo Da 2 Bold','Baloo Da 2 ExtraBold','Baloo Da 2 Medium','Baloo Da 2 SemiBold','Baloo Paaji 2','Baloo Paaji 2 Bold','Baloo Paaji 2 ExtraBold','Baloo Paaji 2 Medium','Baloo Paaji 2 SemiBold','Baloo Tamma 2','Baloo Tamma 2 Bold','Baloo Tamma 2 ExtraBold','Baloo Tamma 2 Medium','Baloo Tamma 2 SemiBold','Baloo Tammudu 2','Baloo Tammudu 2 Bold','Baloo Tammudu 2 ExtraBold','Baloo Tammudu 2 Medium','Baloo Tammudu 2 SemiBold','Baloo Thambi 2','Baloo Thambi 2 Bold','Baloo Thambi 2 ExtraBold','Baloo Thambi 2 Medium','Baloo Thambi 2 SemiBold',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baoli SC','Baoli TC',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'BiauKai',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Cambay Devanagari','Cambay Devanagari Bold','Cambay Devanagari Bold Oblique','Cambay Devanagari Oblique',\n\t\t\t'Canela','Canela Bold','Canela Bold Italic','Canela Deck','Canela Deck Bold','Canela Deck Bold Italic','Canela Deck Medium','Canela Deck Medium Italic','Canela Deck Italic','Canela Italic','Canela Text','Canela Text Bold','Canela Text Bold Italic','Canela Text Medium','Canela Text Medium Italic','Canela Text Italic',\n\t\t\t'Catamaran','Catamaran Black','Catamaran Bold','Catamaran ExtraBold','Catamaran ExtraLight','Catamaran Light','Catamaran Medium','Catamaran SemiBold','Catamaran Thin',\n\t\t\t'Chakra Petch','Chakra Petch Bold','Chakra Petch Bold Italic','Chakra Petch ExtraLight','Chakra Petch ExtraLight Italic','Chakra Petch Italic','Chakra Petch Light','Chakra Petch Light Italic','Chakra Petch Medium','Chakra Petch Medium Italic','Chakra Petch SemiBold','Chakra Petch SemiBold Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charm','Charm Bold',\n\t\t\t'Charmonman','Charmonman Bold',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Domaine Display','Domaine Display Bold','Domaine Display Bold Italic','Domaine Display Italic','Domaine Display Medium','Domaine Display Medium Italic',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Fahkwang','Fahkwang Bold','Fahkwang Bold Italic','Fahkwang ExtraLight','Fahkwang ExtraLight Italic','Fahkwang Italic','Fahkwang Light','Fahkwang Light Italic','Fahkwang Medium','Fahkwang Medium Italic','Fahkwang SemiBold','Fahkwang SemiBold Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Founders Grotesk','Founders Grotesk Bold','Founders Grotesk Bold Italic','Founders Grotesk Condensed','Founders Grotesk Condensed Bold','Founders Grotesk Condensed Semibold','Founders Grotesk Light','Founders Grotesk Light Italic','Founders Grotesk Medium','Founders Grotesk Medium Italic','Founders Grotesk Italic','Founders Grotesk Semibold','Founders Grotesk Semibold Italic','Founders Grotesk Text','Founders Grotesk Text Bold','Founders Grotesk Text Bold Italic','Founders Grotesk Text Italic',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gotu',\n\t\t\t'Grantha Sangam MN','Grantha Sangam MN Black','Grantha Sangam MN Bold','Grantha Sangam MN DemiBold','Grantha Sangam MN Light','Grantha Sangam MN Medium',\n\t\t\t'Graphik','Graphik Bold','Graphik Bold Italic','Graphik Compact','Graphik Compact Bold','Graphik Compact Bold Italic','Graphik Compact Medium','Graphik Compact Medium Italic','Graphik Compact Italic','Graphik Compact Semibold','Graphik Compact Semibold Italic','Graphik Light','Graphik Light Italic','Graphik Medium','Graphik Medium Italic','Graphik Italic','Graphik Semibold','Graphik Semibold Italic',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'GungSeo',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Hannotate SC','Hannotate SC Bold','Hannotate TC','Hannotate TC Bold',\n\t\t\t'HanziPen SC','HanziPen SC Bold','HanziPen TC','HanziPen TC Bold',\n\t\t\t'HeadLineA',\n\t\t\t'Hei',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans CNS W3','Hiragino Sans CNS W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'Hubballi',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','K2D Bold','K2D Bold Italic','K2D ExtraBold','K2D ExtraBold Italic','K2D ExtraLight','K2D ExtraLight Italic','K2D Italic','K2D Light','K2D Light Italic','K2D Medium','K2D Medium Italic','K2D SemiBold','K2D SemiBold Italic','K2D Thin','K2D Thin Italic',\n\t\t\t'Kai',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kaiti SC','Kaiti SC Black','Kaiti SC Bold','Kaiti TC','Kaiti TC Black','Kaiti TC Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Katari','Katari Black','Katari Black Italic','Katari Bold','Katari Bold Italic','Katari Italic','Katari Medium','Katari Medium Italic',\n\t\t\t'Kavivanar',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'KoHo','KoHo Bold','KoHo Bold Italic','KoHo ExtraLight','KoHo ExtraLight Italic','KoHo Italic','KoHo Light','KoHo Light Italic','KoHo Medium','KoHo Medium Italic','KoHo SemiBold','KoHo SemiBold Italic',\n\t\t\t'Kodchasan','Kodchasan Bold','Kodchasan Bold Italic','Kodchasan ExtraLight','Kodchasan ExtraLight Italic','Kodchasan Italic','Kodchasan Light','Kodchasan Light Italic','Kodchasan Medium','Kodchasan Medium Italic','Kodchasan SemiBold','Kodchasan SemiBold Italic',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krub','Krub Bold','Krub Bold Italic','Krub ExtraLight','Krub ExtraLight Italic','Krub Italic','Krub Light','Krub Light Italic','Krub Medium','Krub Medium Italic','Krub SemiBold','Krub SemiBold Italic',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lahore Gurmukhi','Lahore Gurmukhi Bold','Lahore Gurmukhi Light','Lahore Gurmukhi Medium','Lahore Gurmukhi SemiBold',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy','Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lava Devanagari','Lava Devanagari Bold','Lava Devanagari Heavy','Lava Devanagari Medium','Lava Kannada','Lava Kannada Bold','Lava Kannada Heavy','Lava Kannada Medium','Lava Telugu','Lava Telugu Bold','Lava Telugu Heavy','Lava Telugu Medium',\n\t\t\t'LiHei Pro',\n\t\t\t'LiSong Pro',\n\t\t\t'Libian SC','Libian TC',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Maku','Maku Bold',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Mali','Mali Bold','Mali Bold Italic','Mali ExtraLight','Mali ExtraLight Italic','Mali Italic','Mali Light','Mali Light Italic','Mali Medium','Mali Medium Italic','Mali SemiBold','Mali SemiBold Italic',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Modak',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta','Mukta Bold','Mukta ExtraBold','Mukta ExtraLight','Mukta Light','Mukta Malar','Mukta Malar Bold','Mukta Malar ExtraBold','Mukta Malar ExtraLight','Mukta Malar Light','Mukta Malar Medium','Mukta Malar SemiBold','Mukta Medium','Mukta SemiBold','Mukta Vaani','Mukta Vaani Bold','Mukta Vaani ExtraBold','Mukta Vaani ExtraLight','Mukta Vaani Light','Mukta Vaani Medium','Mukta Vaani SemiBold',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Myriad Arabic','Myriad Arabic Black','Myriad Arabic Black Italic','Myriad Arabic Bold','Myriad Arabic Bold Italic','Myriad Arabic Italic','Myriad Arabic Light','Myriad Arabic Light Italic','Myriad Arabic Semibold','Myriad Arabic Semibold Italic',\n\t\t\t'Nadeem',\n\t\t\t'Nanum Brush Script','Nanum Pen Script','Nanum Gothic','Nanum Gothic Bold','Nanum Gothic ExtraBold','Nanum Myeongjo','Nanum Myeongjo Bold','Nanum Myeongjo ExtraBold',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Niramit','Niramit Bold','Niramit Bold Italic','Niramit ExtraLight','Niramit ExtraLight Italic','Niramit Italic','Niramit Light','Niramit Light Italic','Niramit Medium','Niramit Medium Italic','Niramit SemiBold','Niramit SemiBold Italic',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans Oriya','Noto Sans Oriya Bold',\n\t\t\t'Noto Serif Kannada','Noto Serif Kannada Black','Noto Serif Kannada Bold','Noto Serif Kannada ExtraBold','Noto Serif Kannada ExtraLight','Noto Serif Kannada Light','Noto Serif Kannada Medium','Noto Serif Kannada SemiBold','Noto Serif Kannada Thin','Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'October Compressed Devanagari','October Compressed Devanagari Black','October Compressed Devanagari Bold','October Compressed Devanagari ExtraLight','October Compressed Devanagari Hairline','October Compressed Devanagari Heavy','October Compressed Devanagari Light','October Compressed Devanagari Medium','October Compressed Devanagari Thin','October Compressed Tamil','October Compressed Tamil Black','October Compressed Tamil Bold','October Compressed Tamil ExtraLight','October Compressed Tamil Hairline','October Compressed Tamil Heavy','October Compressed Tamil Light','October Compressed Tamil Medium','October Compressed Tamil Thin','October Condensed Devanagari','October Condensed Devanagari Black','October Condensed Devanagari Bold','October Condensed Devanagari ExtraLight','October Condensed Devanagari Hairline','October Condensed Devanagari Heavy','October Condensed Devanagari Light','October Condensed Devanagari Medium','October Condensed Devanagari Thin','October Condensed Tamil','October Condensed Tamil Black','October Condensed Tamil Bold','October Condensed Tamil ExtraLight','October Condensed Tamil Hairline','October Condensed Tamil Heavy','October Condensed Tamil Light','October Condensed Tamil Medium','October Condensed Tamil Thin','October Devanagari','October Devanagari Black','October Devanagari Bold','October Devanagari ExtraLight','October Devanagari Hairline','October Devanagari Heavy','October Devanagari Light','October Devanagari Medium','October Devanagari Thin','October Tamil','October Tamil Black','October Tamil Bold','October Tamil ExtraLight','October Tamil Hairline','October Tamil Heavy','October Tamil Light','October Tamil Medium','October Tamil Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'PCMyungjo',\n\t\t\t'PSL Ornanong Pro','PSL Ornanong Pro Bold','PSL Ornanong Pro Bold Italic','PSL Ornanong Pro Demibold','PSL Ornanong Pro Demibold Italic','PSL Ornanong Pro Italic','PSL Ornanong Pro Light','PSL Ornanong Pro Light Italic',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Padyakke Expanded One',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Party LET',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PilGi',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Produkt','Produkt Extralight','Produkt Extralight Italic','Produkt Light','Produkt Light Italic','Produkt Medium','Produkt Medium Italic','Produkt Italic',\n\t\t\t'Proxima Nova','Proxima Nova Bold','Proxima Nova Bold It','Proxima Nova Extrabold','Proxima Nova Extrabold It','Proxima Nova It','Proxima Nova Light','Proxima Nova Light It','Proxima Nova Medium','Proxima Nova Medium It','Proxima Nova Semibold','Proxima Nova Semibold It',\n\t\t\t'Publico Headline Black','Publico Headline Black Italic','Publico Headline Bold','Publico Headline Bold Italic','Publico Headline Italic','Publico Headline Roman','Publico Text Bold','Publico Text Bold Italic','Publico Text Italic','Publico Text Roman','Publico Text Semibold','Publico Text Semibold Italic',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STFangsong',\n\t\t\t'STHeiti',\n\t\t\t'STIX Two Math','STIX Two Text','STIX Two Text Bold','STIX Two Text Bold Italic','STIX Two Text Italic','STIX Two Text Medium','STIX Two Text Medium Italic','STIX Two Text SemiBold','STIX Two Text SemiBold Italic',\n\t\t\t'STKaiti',\n\t\t\t'STSong',\n\t\t\t'STXihei',\n\t\t\t'Sama Devanagari','Sama Devanagari Bold','Sama Devanagari Book','Sama Devanagari ExtraBold','Sama Devanagari Medium','Sama Devanagari SemiBold','Sama Gujarati','Sama Gujarati Bold','Sama Gujarati Book','Sama Gujarati ExtraBold','Sama Gujarati Medium','Sama Gujarati SemiBold','Sama Gurmukhi','Sama Gurmukhi Bold','Sama Gurmukhi Book','Sama Gurmukhi ExtraBold','Sama Gurmukhi Medium','Sama Gurmukhi SemiBold','Sama Kannada','Sama Kannada Bold','Sama Kannada Book','Sama Kannada ExtraBold','Sama Kannada Medium','Sama Kannada SemiBold','Sama Malayalam','Sama Malayalam Bold','Sama Malayalam Book','Sama Malayalam ExtraBold','Sama Malayalam Medium','Sama Malayalam SemiBold','Sama Tamil','Sama Tamil Bold','Sama Tamil Book','Sama Tamil ExtraBold','Sama Tamil Medium','Sama Tamil SemiBold',\n\t\t\t'Sana',\n\t\t\t'Sarabun','Sarabun Bold','Sarabun Bold Italic','Sarabun ExtraBold','Sarabun ExtraBold Italic','Sarabun ExtraLight','Sarabun ExtraLight Italic','Sarabun Italic','Sarabun Light','Sarabun Light Italic','Sarabun Medium','Sarabun Medium Italic','Sarabun SemiBold','Sarabun SemiBold Italic','Sarabun Thin','Sarabun Thin Italic',\n\t\t\t'Sathu',\n\t\t\t'Sauber Script',\n\t\t\t'Savoye LET',\n\t\t\t'Shobhika','Shobhika Bold',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'SimSong','SimSong Bold',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Spot Mono','Spot Mono Bold','Spot Mono Medium',\n\t\t\t'Srisakdi','Srisakdi Bold',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Black','Tamil Sangam MN Bold','Tamil Sangam MN Demibold','Tamil Sangam MN Light','Tamil Sangam MN Medium',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Tiro Bangla','Tiro Bangla Italic','Tiro Devanagari Hindi','Tiro Devanagari Hindi Italic','Tiro Devanagari Marathi','Tiro Devanagari Marathi Italic','Tiro Devanagari Sanskrit','Tiro Devanagari Sanskrit Italic','Tiro Gurmukhi','Tiro Gurmukhi Italic','Tiro Kannada','Tiro Kannada Italic','Tiro Tamil','Tiro Tamil Italic','Tiro Telugu','Tiro Telugu Italic',\n\t\t\t'Toppan Bunkyu Gothic','Toppan Bunkyu Gothic Demibold','Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold','Toppan Bunkyu Mincho',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Tsukushi A Round Gothic','Tsukushi A Round Gothic Bold','Tsukushi B Round Gothic','Tsukushi B Round Gothic Bold',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Webdings',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light','Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium','YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium','YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t'Yuanti SC','Yuanti SC Bold','Yuanti SC Light','Yuanti TC','Yuanti TC Bold','Yuanti TC Light',\n\t\t\t'Yuppy SC','Yuppy TC',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v14': {\n\t\tname: 'Sonoma',\n\t\tversion: 14,\n\t\turl: 'https://support.apple.com/en-us/108939',\n\t\tlist: [\n\t\t\t'Academy Engraved LET',\n\t\t\t'Adelle Sans Devanagari','Adelle Sans Devanagari Bold','Adelle Sans Devanagari Extrabold','Adelle Sans Devanagari Heavy','Adelle Sans Devanagari Light','Adelle Sans Devanagari Semibold','Adelle Sans Devanagari Thin',\n\t\t\t'AkayaKanadaka','AkayaTelivigala',\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Annai MN',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple Color Emoji','Apple LiGothic Medium','Apple LiSung Light','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Arima Koshi','Arima Koshi Black','Arima Koshi Bold','Arima Koshi ExtraBold','Arima Koshi ExtraLight','Arima Koshi Light','Arima Koshi Medium','Arima Koshi Thin','Arima Madurai','Arima Madurai Black','Arima Madurai Bold','Arima Madurai ExtraLight','Arima Madurai Light','Arima Madurai Medium','Arima Madurai Semi Bold','Arima Madurai Thin',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'BIZ UDGothic','BIZ UDGothic Bold','BIZ UDMincho',\n\t\t\t'BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Baghdad',\n\t\t\t'Bai Jamjuree','Bai Jamjuree Bold','Bai Jamjuree Bold Italic','Bai Jamjuree ExtraLight','Bai Jamjuree ExtraLight Italic','Bai Jamjuree Italic','Bai Jamjuree Light','Bai Jamjuree Light Italic','Bai Jamjuree Medium','Bai Jamjuree Medium Italic','Bai Jamjuree SemiBold','Bai Jamjuree SemiBold Italic',\n\t\t\t'Baloo 2','Baloo 2 Bold','Baloo 2 ExtraBold','Baloo 2 Medium','Baloo 2 SemiBold','Baloo Bhai 2','Baloo Bhai 2 Bold','Baloo Bhai 2 ExtraBold','Baloo Bhai 2 Medium','Baloo Bhai 2 SemiBold','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Bhaina 2 Bold','Baloo Bhaina 2 ExtraBold','Baloo Bhaina 2 Medium','Baloo Bhaina 2 SemiBold','Baloo Chettan 2','Baloo Chettan 2 Bold','Baloo Chettan 2 ExtraBold','Baloo Chettan 2 Medium','Baloo Chettan 2 SemiBold','Baloo Da 2','Baloo Da 2 Bold','Baloo Da 2 ExtraBold','Baloo Da 2 Medium','Baloo Da 2 SemiBold','Baloo Paaji 2','Baloo Paaji 2 Bold','Baloo Paaji 2 ExtraBold','Baloo Paaji 2 Medium','Baloo Paaji 2 SemiBold','Baloo Tamma 2','Baloo Tamma 2 Bold','Baloo Tamma 2 ExtraBold','Baloo Tamma 2 Medium','Baloo Tamma 2 SemiBold','Baloo Tammudu 2','Baloo Tammudu 2 Bold','Baloo Tammudu 2 ExtraBold','Baloo Tammudu 2 Medium','Baloo Tammudu 2 SemiBold','Baloo Thambi 2','Baloo Thambi 2 Bold','Baloo Thambi 2 ExtraBold','Baloo Thambi 2 Medium','Baloo Thambi 2 SemiBold',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baoli SC','Baoli TC',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'BiauKaiHK','BiauKaiTC',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brill Bold','Brill Bold Italic','Brill Italic','Brill Roman',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Cambay Devanagari','Cambay Devanagari Bold','Cambay Devanagari Bold Oblique','Cambay Devanagari Oblique',\n\t\t\t'Canela','Canela Bold','Canela Bold Italic','Canela Deck','Canela Deck Bold','Canela Deck Bold Italic','Canela Deck Medium','Canela Deck Medium Italic','Canela Deck Italic','Canela Italic','Canela Text','Canela Text Bold','Canela Text Bold Italic','Canela Text Medium','Canela Text Medium Italic','Canela Text Italic',\n\t\t\t'Catamaran','Catamaran Black','Catamaran Bold','Catamaran ExtraBold','Catamaran ExtraLight','Catamaran Light','Catamaran Medium','Catamaran SemiBold','Catamaran Thin',\n\t\t\t'Chakra Petch','Chakra Petch Bold','Chakra Petch Bold Italic','Chakra Petch ExtraLight','Chakra Petch ExtraLight Italic','Chakra Petch Italic','Chakra Petch Light','Chakra Petch Light Italic','Chakra Petch Medium','Chakra Petch Medium Italic','Chakra Petch SemiBold','Chakra Petch SemiBold Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charm','Charm Bold',\n\t\t\t'Charmonman','Charmonman Bold',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Domaine Display','Domaine Display Bold','Domaine Display Bold Italic','Domaine Display Italic','Domaine Display Medium','Domaine Display Medium Italic',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Fahkwang','Fahkwang Bold','Fahkwang Bold Italic','Fahkwang ExtraLight','Fahkwang ExtraLight Italic','Fahkwang Italic','Fahkwang Light','Fahkwang Light Italic','Fahkwang Medium','Fahkwang Medium Italic','Fahkwang SemiBold','Fahkwang SemiBold Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Founders Grotesk','Founders Grotesk Bold','Founders Grotesk Bold Italic','Founders Grotesk Condensed','Founders Grotesk Condensed Bold','Founders Grotesk Condensed Semibold','Founders Grotesk Light','Founders Grotesk Light Italic','Founders Grotesk Medium','Founders Grotesk Medium Italic','Founders Grotesk Italic','Founders Grotesk Semibold','Founders Grotesk Semibold Italic','Founders Grotesk Text','Founders Grotesk Text Bold','Founders Grotesk Text Bold Italic','Founders Grotesk Text Italic',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gotu',\n\t\t\t'Grantha Sangam MN','Grantha Sangam MN Black','Grantha Sangam MN Bold','Grantha Sangam MN DemiBold','Grantha Sangam MN Light','Grantha Sangam MN Medium',\n\t\t\t'Graphik','Graphik Bold','Graphik Bold Italic','Graphik Compact','Graphik Compact Bold','Graphik Compact Bold Italic','Graphik Compact Medium','Graphik Compact Medium Italic','Graphik Compact Italic','Graphik Compact Semibold','Graphik Compact Semibold Italic','Graphik Light','Graphik Light Italic','Graphik Medium','Graphik Medium Italic','Graphik Italic','Graphik Semibold','Graphik Semibold Italic',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'GungSeo',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Hannotate SC','Hannotate SC Bold','Hannotate TC','Hannotate TC Bold',\n\t\t\t'HanziPen SC','HanziPen SC Bold','HanziPen TC','HanziPen TC Bold',\n\t\t\t'HeadLineA',\n\t\t\t'Hei',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans CNS W3','Hiragino Sans CNS W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'Hubballi',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','K2D Bold','K2D Bold Italic','K2D ExtraBold','K2D ExtraBold Italic','K2D ExtraLight','K2D ExtraLight Italic','K2D Italic','K2D Light','K2D Light Italic','K2D Medium','K2D Medium Italic','K2D SemiBold','K2D SemiBold Italic','K2D Thin','K2D Thin Italic',\n\t\t\t'Kai',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kaiti SC','Kaiti SC Black','Kaiti SC Bold','Kaiti TC','Kaiti TC Black','Kaiti TC Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Katari','Katari Black','Katari Black Italic','Katari Bold','Katari Bold Italic','Katari Italic','Katari Medium','Katari Medium Italic',\n\t\t\t'Kavivanar',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Kigelia','Kigelia Arabic','Kigelia Arabic Bold','Kigelia Arabic Extrabold','Kigelia Arabic Light','Kigelia Arabic Semibold','Kigelia Bold','Kigelia Bold Italic','Kigelia Extrabold','Kigelia Extrabold Italic','Kigelia Italic','Kigelia Light','Kigelia Light Italic','Kigelia Semibold','Kigelia Semibold Italic',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'KoHo','KoHo Bold','KoHo Bold Italic','KoHo ExtraLight','KoHo ExtraLight Italic','KoHo Italic','KoHo Light','KoHo Light Italic','KoHo Medium','KoHo Medium Italic','KoHo SemiBold','KoHo SemiBold Italic',\n\t\t\t'Kodchasan','Kodchasan Bold','Kodchasan Bold Italic','Kodchasan ExtraLight','Kodchasan ExtraLight Italic','Kodchasan Italic','Kodchasan Light','Kodchasan Light Italic','Kodchasan Medium','Kodchasan Medium Italic','Kodchasan SemiBold','Kodchasan SemiBold Italic',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krub','Krub Bold','Krub Bold Italic','Krub ExtraLight','Krub ExtraLight Italic','Krub Italic','Krub Light','Krub Light Italic','Krub Medium','Krub Medium Italic','Krub SemiBold','Krub SemiBold Italic',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lahore Gurmukhi','Lahore Gurmukhi Bold','Lahore Gurmukhi Light','Lahore Gurmukhi Medium','Lahore Gurmukhi SemiBold',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy','Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lava Devanagari','Lava Devanagari Bold','Lava Devanagari Heavy','Lava Devanagari Medium','Lava Kannada','Lava Kannada Bold','Lava Kannada Heavy','Lava Kannada Medium','Lava Telugu','Lava Telugu Bold','Lava Telugu Heavy','Lava Telugu Medium',\n\t\t\t'LiHei Pro',\n\t\t\t'LiSong Pro',\n\t\t\t'Libian SC','Libian TC',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Maku','Maku Bold',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Mali','Mali Bold','Mali Bold Italic','Mali ExtraLight','Mali ExtraLight Italic','Mali Italic','Mali Light','Mali Light Italic','Mali Medium','Mali Medium Italic','Mali SemiBold','Mali SemiBold Italic',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Modak',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta','Mukta Bold','Mukta ExtraBold','Mukta ExtraLight','Mukta Light','Mukta Malar','Mukta Malar Bold','Mukta Malar ExtraBold','Mukta Malar ExtraLight','Mukta Malar Light','Mukta Malar Medium','Mukta Malar SemiBold','Mukta Medium','Mukta SemiBold','Mukta Vaani','Mukta Vaani Bold','Mukta Vaani ExtraBold','Mukta Vaani ExtraLight','Mukta Vaani Light','Mukta Vaani Medium','Mukta Vaani SemiBold',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Myriad Arabic','Myriad Arabic Black','Myriad Arabic Black Italic','Myriad Arabic Bold','Myriad Arabic Bold Italic','Myriad Arabic Italic','Myriad Arabic Light','Myriad Arabic Light Italic','Myriad Arabic Semibold','Myriad Arabic Semibold Italic',\n\t\t\t'Nadeem',\n\t\t\t'Nanum Brush Script','Nanum Pen Script','Nanum Gothic','Nanum Gothic Bold','Nanum Gothic ExtraBold','Nanum Myeongjo','Nanum Myeongjo Bold','Nanum Myeongjo ExtraBold',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Niramit','Niramit Bold','Niramit Bold Italic','Niramit ExtraLight','Niramit ExtraLight Italic','Niramit Italic','Niramit Light','Niramit Light Italic','Niramit Medium','Niramit Medium Italic','Niramit SemiBold','Niramit SemiBold Italic',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Batak','Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans NKo','Noto Sans Oriya','Noto Sans Oriya Bold','Noto Sans Tagalog',\n\t\t\t'Noto Serif Kannada','Noto Serif Kannada Black','Noto Serif Kannada Bold','Noto Serif Kannada ExtraBold','Noto Serif Kannada ExtraLight','Noto Serif Kannada Light','Noto Serif Kannada Medium','Noto Serif Kannada SemiBold','Noto Serif Kannada Thin','Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'October Compressed Devanagari','October Compressed Devanagari Black','October Compressed Devanagari Bold','October Compressed Devanagari ExtraLight','October Compressed Devanagari Hairline','October Compressed Devanagari Heavy','October Compressed Devanagari Light','October Compressed Devanagari Medium','October Compressed Devanagari Thin','October Compressed Gujarati','October Compressed Gujarati Black','October Compressed Gujarati Bold','October Compressed Gujarati ExtraLight','October Compressed Gujarati Hairline','October Compressed Gujarati Heavy','October Compressed Gujarati Light','October Compressed Gujarati Medium','October Compressed Gujarati Thin','October Compressed Gurmukhi','October Compressed Gurmukhi Black','October Compressed Gurmukhi Bold','October Compressed Gurmukhi ExtraLight','October Compressed Gurmukhi Hairline','October Compressed Gurmukhi Heavy','October Compressed Gurmukhi Light','October Compressed Gurmukhi Medium','October Compressed Gurmukhi Thin','October Compressed Kannada','October Compressed Kannada Black','October Compressed Kannada Bold','October Compressed Kannada ExtraLight','October Compressed Kannada Hairline','October Compressed Kannada Heavy','October Compressed Kannada Light','October Compressed Kannada Medium','October Compressed Kannada Thin','October Compressed Meetei Mayek','October Compressed Meetei Mayek Black','October Compressed Meetei Mayek Bold','October Compressed Meetei Mayek ExtraLight','October Compressed Meetei Mayek Hairline','October Compressed Meetei Mayek Heavy','October Compressed Meetei Mayek Light','October Compressed Meetei Mayek Medium','October Compressed Meetei Mayek Thin','October Compressed Odia','October Compressed Odia Black','October Compressed Odia Bold','October Compressed Odia ExtraLight','October Compressed Odia Hairline','October Compressed Odia Heavy','October Compressed Odia Light','October Compressed Odia Medium','October Compressed Odia Thin','October Compressed Ol Chiki','October Compressed Ol Chiki Black','October Compressed Ol Chiki Bold','October Compressed Ol Chiki ExtraLight','October Compressed Ol Chiki Hairline','October Compressed Ol Chiki Heavy','October Compressed Ol Chiki Light','October Compressed Ol Chiki Medium','October Compressed Ol Chiki Thin','October Compressed Tamil','October Compressed Tamil Black','October Compressed Tamil Bold','October Compressed Tamil ExtraLight','October Compressed Tamil Hairline','October Compressed Tamil Heavy','October Compressed Tamil Light','October Compressed Tamil Medium','October Compressed Tamil Thin','October Compressed Telugu','October Compressed Telugu Black','October Compressed Telugu Bold','October Compressed Telugu ExtraLight','October Compressed Telugu Hairline','October Compressed Telugu Heavy','October Compressed Telugu Light','October Compressed Telugu Medium','October Compressed Telugu Thin','October Condensed Devanagari','October Condensed Devanagari Black','October Condensed Devanagari Bold','October Condensed Devanagari ExtraLight','October Condensed Devanagari Hairline','October Condensed Devanagari Heavy','October Condensed Devanagari Light','October Condensed Devanagari Medium','October Condensed Devanagari Thin','October Condensed Gujarati','October Condensed Gujarati Black','October Condensed Gujarati Bold','October Condensed Gujarati ExtraLight','October Condensed Gujarati Hairline','October Condensed Gujarati Heavy','October Condensed Gujarati Light','October Condensed Gujarati Medium','October Condensed Gujarati Thin','October Condensed Gurmukhi','October Condensed Gurmukhi Black','October Condensed Gurmukhi Bold','October Condensed Gurmukhi ExtraLight','October Condensed Gurmukhi Hairline','October Condensed Gurmukhi Heavy','October Condensed Gurmukhi Light','October Condensed Gurmukhi Medium','October Condensed Gurmukhi Thin','October Condensed Kannada','October Condensed Kannada Black','October Condensed Kannada Bold','October Condensed Kannada ExtraLight','October Condensed Kannada Hairline','October Condensed Kannada Heavy','October Condensed Kannada Light','October Condensed Kannada Medium','October Condensed Kannada Thin','October Condensed Meetei Mayek','October Condensed Meetei Mayek Black','October Condensed Meetei Mayek Bold','October Condensed Meetei Mayek ExtraLight','October Condensed Meetei Mayek Hairline','October Condensed Meetei Mayek Heavy','October Condensed Meetei Mayek Light','October Condensed Meetei Mayek Medium','October Condensed Meetei Mayek Thin','October Condensed Odia','October Condensed Odia Black','October Condensed Odia Bold','October Condensed Odia ExtraLight','October Condensed Odia Hairline','October Condensed Odia Heavy','October Condensed Odia Light','October Condensed Odia Medium','October Condensed Odia Thin','October Condensed Ol Chiki','October Condensed Ol Chiki Black','October Condensed Ol Chiki Bold','October Condensed Ol Chiki ExtraLight','October Condensed Ol Chiki Hairline','October Condensed Ol Chiki Heavy','October Condensed Ol Chiki Light','October Condensed Ol Chiki Medium','October Condensed Ol Chiki Thin','October Condensed Tamil','October Condensed Tamil Black','October Condensed Tamil Bold','October Condensed Tamil ExtraLight','October Condensed Tamil Hairline','October Condensed Tamil Heavy','October Condensed Tamil Light','October Condensed Tamil Medium','October Condensed Tamil Thin','October Condensed Telugu','October Condensed Telugu Black','October Condensed Telugu Bold','October Condensed Telugu ExtraLight','October Condensed Telugu Hairline','October Condensed Telugu Heavy','October Condensed Telugu Light','October Condensed Telugu Medium','October Condensed Telugu Thin','October Devanagari','October Devanagari Black','October Devanagari Bold','October Devanagari ExtraLight','October Devanagari Hairline','October Devanagari Heavy','October Devanagari Light','October Devanagari Medium','October Devanagari Thin','October Gujarati','October Gujarati Black','October Gujarati Bold','October Gujarati ExtraLight','October Gujarati Hairline','October Gujarati Heavy','October Gujarati Light','October Gujarati Medium','October Gujarati Thin','October Gurmukhi','October Gurmukhi Black','October Gurmukhi Bold','October Gurmukhi ExtraLight','October Gurmukhi Hairline','October Gurmukhi Heavy','October Gurmukhi Light','October Gurmukhi Medium','October Gurmukhi Thin','October Kannada','October Kannada Black','October Kannada Bold','October Kannada ExtraLight','October Kannada Hairline','October Kannada Heavy','October Kannada Light','October Kannada Medium','October Kannada Thin','October Meetei Mayek','October Meetei Mayek Black','October Meetei Mayek Bold','October Meetei Mayek ExtraLight','October Meetei Mayek Hairline','October Meetei Mayek Heavy','October Meetei Mayek Light','October Meetei Mayek Medium','October Meetei Mayek Thin','October Odia','October Odia Black','October Odia Bold','October Odia ExtraLight','October Odia Hairline','October Odia Heavy','October Odia Light','October Odia Medium','October Odia Thin','October Ol Chiki','October Ol Chiki Black','October Ol Chiki Bold','October Ol Chiki ExtraLight','October Ol Chiki Hairline','October Ol Chiki Heavy','October Ol Chiki Light','October Ol Chiki Medium','October Ol Chiki Thin','October Tamil','October Tamil Black','October Tamil Bold','October Tamil ExtraLight','October Tamil Hairline','October Tamil Heavy','October Tamil Light','October Tamil Medium','October Tamil Thin','October Telugu','October Telugu Black','October Telugu Bold','October Telugu ExtraLight','October Telugu Hairline','October Telugu Heavy','October Telugu Light','October Telugu Medium','October Telugu Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'PCMyungjo',\n\t\t\t'PSL Ornanong Pro','PSL Ornanong Pro Bold','PSL Ornanong Pro Bold Italic','PSL Ornanong Pro Demibold','PSL Ornanong Pro Demibold Italic','PSL Ornanong Pro Italic','PSL Ornanong Pro Light','PSL Ornanong Pro Light Italic',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Padyakke Expanded One',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Party LET',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PilGi',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Produkt','Produkt Extralight','Produkt Extralight Italic','Produkt Light','Produkt Light Italic','Produkt Medium','Produkt Medium Italic','Produkt Italic',\n\t\t\t'Proxima Nova','Proxima Nova Bold','Proxima Nova Bold It','Proxima Nova Extrabold','Proxima Nova Extrabold It','Proxima Nova It','Proxima Nova Light','Proxima Nova Light It','Proxima Nova Medium','Proxima Nova Medium It','Proxima Nova Semibold','Proxima Nova Semibold It',\n\t\t\t'Publico Headline Black','Publico Headline Black Italic','Publico Headline Bold','Publico Headline Bold Italic','Publico Headline Italic','Publico Headline Roman','Publico Text Bold','Publico Text Bold Italic','Publico Text Italic','Publico Text Roman','Publico Text Semibold','Publico Text Semibold Italic',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STFangsong',\n\t\t\t'STHeiti',\n\t\t\t'STIX Two Math','STIX Two Text','STIX Two Text Bold','STIX Two Text Bold Italic','STIX Two Text Italic','STIX Two Text Medium','STIX Two Text Medium Italic','STIX Two Text SemiBold','STIX Two Text SemiBold Italic',\n\t\t\t'STKaiti',\n\t\t\t'STSong',\n\t\t\t'STXihei',\n\t\t\t'Sama Devanagari','Sama Devanagari Bold','Sama Devanagari Book','Sama Devanagari ExtraBold','Sama Devanagari Medium','Sama Devanagari SemiBold','Sama Gujarati','Sama Gujarati Bold','Sama Gujarati Book','Sama Gujarati ExtraBold','Sama Gujarati Medium','Sama Gujarati SemiBold','Sama Gurmukhi','Sama Gurmukhi Bold','Sama Gurmukhi Book','Sama Gurmukhi ExtraBold','Sama Gurmukhi Medium','Sama Gurmukhi SemiBold','Sama Kannada','Sama Kannada Bold','Sama Kannada Book','Sama Kannada ExtraBold','Sama Kannada Medium','Sama Kannada SemiBold','Sama Malayalam','Sama Malayalam Bold','Sama Malayalam Book','Sama Malayalam ExtraBold','Sama Malayalam Medium','Sama Malayalam SemiBold','Sama Tamil','Sama Tamil Bold','Sama Tamil Book','Sama Tamil ExtraBold','Sama Tamil Medium','Sama Tamil SemiBold',\n\t\t\t'Sana',\n\t\t\t'Sarabun','Sarabun Bold','Sarabun Bold Italic','Sarabun ExtraBold','Sarabun ExtraBold Italic','Sarabun ExtraLight','Sarabun ExtraLight Italic','Sarabun Italic','Sarabun Light','Sarabun Light Italic','Sarabun Medium','Sarabun Medium Italic','Sarabun SemiBold','Sarabun SemiBold Italic','Sarabun Thin','Sarabun Thin Italic',\n\t\t\t'Sathu',\n\t\t\t'Sauber Script',\n\t\t\t'Savoye LET',\n\t\t\t'Shobhika','Shobhika Bold',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'SimSong','SimSong Bold',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Spot Mono','Spot Mono Bold','Spot Mono Medium',\n\t\t\t'Srisakdi','Srisakdi Bold',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Black','Tamil Sangam MN Bold','Tamil Sangam MN Demibold','Tamil Sangam MN Light','Tamil Sangam MN Medium',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Tiro Bangla','Tiro Bangla Italic','Tiro Devanagari Hindi','Tiro Devanagari Hindi Italic','Tiro Devanagari Marathi','Tiro Devanagari Marathi Italic','Tiro Devanagari Sanskrit','Tiro Devanagari Sanskrit Italic','Tiro Gurmukhi','Tiro Gurmukhi Italic','Tiro Kannada','Tiro Kannada Italic','Tiro Tamil','Tiro Tamil Italic','Tiro Telugu','Tiro Telugu Italic',\n\t\t\t'Toppan Bunkyu Gothic','Toppan Bunkyu Gothic Demibold','Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold','Toppan Bunkyu Mincho',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Tsukushi A Round Gothic','Tsukushi A Round Gothic Bold','Tsukushi B Round Gothic','Tsukushi B Round Gothic Bold',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Webdings',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light','Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium','YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium','YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t'Yuanti SC','Yuanti SC Bold','Yuanti SC Light','Yuanti TC','Yuanti TC Bold','Yuanti TC Light',\n\t\t\t'Yuppy SC','Yuppy TC',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v15': {\n\t\tname: 'Sequoia',\n\t\tversion: 15,\n\t\turl: 'https://support.apple.com/en-us/120414',\n\t\tlist: [\n\t\t\t'Academy Engraved LET',\n\t\t\t'Adelle Sans Devanagari','Adelle Sans Devanagari Bold','Adelle Sans Devanagari Extrabold','Adelle Sans Devanagari Heavy','Adelle Sans Devanagari Light','Adelle Sans Devanagari Semibold','Adelle Sans Devanagari Thin',\n\t\t\t'AkayaKanadaka','AkayaTelivigala',\n\t\t\t'Al Bayan Bold','Al Bayan','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Annai MN',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot','Apple Chancery','Apple Color Emoji','Apple LiGothic Medium','Apple LiSung Light','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Arima Koshi','Arima Koshi Black','Arima Koshi Bold','Arima Koshi ExtraBold','Arima Koshi ExtraLight','Arima Koshi Light','Arima Koshi Medium','Arima Koshi Thin','Arima Madurai','Arima Madurai Black','Arima Madurai Bold','Arima Madurai ExtraLight','Arima Madurai Light','Arima Madurai Medium','Arima Madurai Semi Bold','Arima Madurai Thin',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'BIZ UDGothic','BIZ UDGothic Bold','BIZ UDMincho',\n\t\t\t'BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Baghdad',\n\t\t\t'Bai Jamjuree','Bai Jamjuree Bold','Bai Jamjuree Bold Italic','Bai Jamjuree ExtraLight','Bai Jamjuree ExtraLight Italic','Bai Jamjuree Italic','Bai Jamjuree Light','Bai Jamjuree Light Italic','Bai Jamjuree Medium','Bai Jamjuree Medium Italic','Bai Jamjuree SemiBold','Bai Jamjuree SemiBold Italic',\n\t\t\t'Baloo 2','Baloo 2 Bold','Baloo 2 ExtraBold','Baloo 2 Medium','Baloo 2 SemiBold','Baloo Bhai 2','Baloo Bhai 2 Bold','Baloo Bhai 2 ExtraBold','Baloo Bhai 2 Medium','Baloo Bhai 2 SemiBold','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Bhaina 2 Bold','Baloo Bhaina 2 ExtraBold','Baloo Bhaina 2 Medium','Baloo Bhaina 2 SemiBold','Baloo Chettan 2','Baloo Chettan 2 Bold','Baloo Chettan 2 ExtraBold','Baloo Chettan 2 Medium','Baloo Chettan 2 SemiBold','Baloo Da 2','Baloo Da 2 Bold','Baloo Da 2 ExtraBold','Baloo Da 2 Medium','Baloo Da 2 SemiBold','Baloo Paaji 2','Baloo Paaji 2 Bold','Baloo Paaji 2 ExtraBold','Baloo Paaji 2 Medium','Baloo Paaji 2 SemiBold','Baloo Tamma 2','Baloo Tamma 2 Bold','Baloo Tamma 2 ExtraBold','Baloo Tamma 2 Medium','Baloo Tamma 2 SemiBold','Baloo Tammudu 2','Baloo Tammudu 2 Bold','Baloo Tammudu 2 ExtraBold','Baloo Tammudu 2 Medium','Baloo Tammudu 2 SemiBold','Baloo Thambi 2','Baloo Thambi 2 Bold','Baloo Thambi 2 ExtraBold','Baloo Thambi 2 Medium','Baloo Thambi 2 SemiBold',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baoli SC','Baoli TC',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'BiauKaiHK','BiauKaiTC',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brill Bold Italic','Brill Italic','Brill Medium Italic','Brill Roman','Brill Roman Bold','Brill Roman Medium','Brill Roman Semibold','Brill Semibold Italic',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Cambay Devanagari','Cambay Devanagari Bold','Cambay Devanagari Bold Oblique','Cambay Devanagari Oblique',\n\t\t\t'Canela','Canela Bold','Canela Bold Italic','Canela Deck','Canela Deck Bold','Canela Deck Bold Italic','Canela Deck Medium','Canela Deck Medium Italic','Canela Deck Italic','Canela Italic','Canela Text','Canela Text Bold','Canela Text Bold Italic','Canela Text Medium','Canela Text Medium Italic','Canela Text Italic',\n\t\t\t'Catamaran','Catamaran Black','Catamaran Bold','Catamaran ExtraBold','Catamaran ExtraLight','Catamaran Light','Catamaran Medium','Catamaran SemiBold','Catamaran Thin',\n\t\t\t'Chakra Petch','Chakra Petch Bold','Chakra Petch Bold Italic','Chakra Petch ExtraLight','Chakra Petch ExtraLight Italic','Chakra Petch Italic','Chakra Petch Light','Chakra Petch Light Italic','Chakra Petch Medium','Chakra Petch Medium Italic','Chakra Petch SemiBold','Chakra Petch SemiBold Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charm','Charm Bold',\n\t\t\t'Charmonman','Charmonman Bold',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'DFKaiShu-SB-Estd-BF',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'Dash','Dash Practice',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Domaine Display','Domaine Display Bold','Domaine Display Bold Italic','Domaine Display Italic','Domaine Display Medium','Domaine Display Medium Italic',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Fahkwang','Fahkwang Bold','Fahkwang Bold Italic','Fahkwang ExtraLight','Fahkwang ExtraLight Italic','Fahkwang Italic','Fahkwang Light','Fahkwang Light Italic','Fahkwang Medium','Fahkwang Medium Italic','Fahkwang SemiBold','Fahkwang SemiBold Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Founders Grotesk','Founders Grotesk Bold','Founders Grotesk Bold Italic','Founders Grotesk Condensed','Founders Grotesk Condensed Bold','Founders Grotesk Condensed Semibold','Founders Grotesk Light','Founders Grotesk Light Italic','Founders Grotesk Medium','Founders Grotesk Medium Italic','Founders Grotesk Italic','Founders Grotesk Semibold','Founders Grotesk Semibold Italic','Founders Grotesk Text','Founders Grotesk Text Bold','Founders Grotesk Text Bold Italic','Founders Grotesk Text Italic',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gotu',\n\t\t\t'Grantha Sangam MN','Grantha Sangam MN Black','Grantha Sangam MN Bold','Grantha Sangam MN DemiBold','Grantha Sangam MN Light','Grantha Sangam MN Medium',\n\t\t\t'Graphik','Graphik Bold','Graphik Bold Italic','Graphik Compact','Graphik Compact Bold','Graphik Compact Bold Italic','Graphik Compact Medium','Graphik Compact Medium Italic','Graphik Compact Italic','Graphik Compact Semibold','Graphik Compact Semibold Italic','Graphik Light','Graphik Light Italic','Graphik Medium','Graphik Medium Italic','Graphik Italic','Graphik Semibold','Graphik Semibold Italic',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'GungSeo',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Hannotate SC','Hannotate SC Bold','Hannotate TC','Hannotate TC Bold',\n\t\t\t'HanziPen SC','HanziPen SC Bold','HanziPen TC','HanziPen TC Bold',\n\t\t\t'HeadLineA',\n\t\t\t'Hei',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans CNS W3','Hiragino Sans CNS W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans TC W3','Hiragino Sans TC W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'Hubballi',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','K2D Bold','K2D Bold Italic','K2D ExtraBold','K2D ExtraBold Italic','K2D ExtraLight','K2D ExtraLight Italic','K2D Italic','K2D Light','K2D Light Italic','K2D Medium','K2D Medium Italic','K2D SemiBold','K2D SemiBold Italic','K2D Thin','K2D Thin Italic',\n\t\t\t'Kai',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kaiti SC','Kaiti SC Black','Kaiti SC Bold','Kaiti TC','Kaiti TC Black','Kaiti TC Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Katari','Katari Black','Katari Black Italic','Katari Bold','Katari Bold Italic','Katari Italic','Katari Medium','Katari Medium Italic',\n\t\t\t'Kavivanar',\n\t\t\t'Kefa','Kefa Bold',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Kigelia','Kigelia Arabic','Kigelia Arabic Bold','Kigelia Arabic Extrabold','Kigelia Arabic Light','Kigelia Arabic Semibold','Kigelia Bold','Kigelia Bold Italic','Kigelia Extrabold','Kigelia Extrabold Italic','Kigelia Italic','Kigelia Light','Kigelia Light Italic','Kigelia Semibold','Kigelia Semibold Italic',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'KoHo','KoHo Bold','KoHo Bold Italic','KoHo ExtraLight','KoHo ExtraLight Italic','KoHo Italic','KoHo Light','KoHo Light Italic','KoHo Medium','KoHo Medium Italic','KoHo SemiBold','KoHo SemiBold Italic',\n\t\t\t'Kodchasan','Kodchasan Bold','Kodchasan Bold Italic','Kodchasan ExtraLight','Kodchasan ExtraLight Italic','Kodchasan Italic','Kodchasan Light','Kodchasan Light Italic','Kodchasan Medium','Kodchasan Medium Italic','Kodchasan SemiBold','Kodchasan SemiBold Italic',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krub','Krub Bold','Krub Bold Italic','Krub ExtraLight','Krub ExtraLight Italic','Krub Italic','Krub Light','Krub Light Italic','Krub Medium','Krub Medium Italic','Krub SemiBold','Krub SemiBold Italic',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lahore Gurmukhi','Lahore Gurmukhi Bold','Lahore Gurmukhi Light','Lahore Gurmukhi Medium','Lahore Gurmukhi SemiBold',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy','Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lava Devanagari','Lava Devanagari Bold','Lava Devanagari Heavy','Lava Devanagari Medium','Lava Kannada','Lava Kannada Bold','Lava Kannada Heavy','Lava Kannada Medium','Lava Telugu','Lava Telugu Bold','Lava Telugu Heavy','Lava Telugu Medium',\n\t\t\t'LiHei Pro',\n\t\t\t'LiSong Pro',\n\t\t\t'Libian SC','Libian TC',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Maku','Maku Bold',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Mali','Mali Bold','Mali Bold Italic','Mali ExtraLight','Mali ExtraLight Italic','Mali Italic','Mali Light','Mali Light Italic','Mali Medium','Mali Medium Italic','Mali SemiBold','Mali SemiBold Italic',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Modak',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta','Mukta Bold','Mukta ExtraBold','Mukta ExtraLight','Mukta Light','Mukta Malar','Mukta Malar Bold','Mukta Malar ExtraBold','Mukta Malar ExtraLight','Mukta Malar Light','Mukta Malar Medium','Mukta Malar SemiBold','Mukta Medium','Mukta SemiBold','Mukta Vaani','Mukta Vaani Bold','Mukta Vaani ExtraBold','Mukta Vaani ExtraLight','Mukta Vaani Light','Mukta Vaani Medium','Mukta Vaani SemiBold',\n\t\t\t'Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Myriad Arabic','Myriad Arabic Black','Myriad Arabic Black Italic','Myriad Arabic Bold','Myriad Arabic Bold Italic','Myriad Arabic Italic','Myriad Arabic Light','Myriad Arabic Light Italic','Myriad Arabic Semibold','Myriad Arabic Semibold Italic',\n\t\t\t'Nadeem',\n\t\t\t'Nanum Brush Script','Nanum Pen Script','Nanum Gothic','Nanum Gothic Bold','Nanum Gothic ExtraBold','Nanum Myeongjo','Nanum Myeongjo Bold','Nanum Myeongjo ExtraBold',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Niramit','Niramit Bold','Niramit Bold Italic','Niramit ExtraLight','Niramit ExtraLight Italic','Niramit Italic','Niramit Light','Niramit Light Italic','Niramit Medium','Niramit Medium Italic','Niramit SemiBold','Niramit SemiBold Italic',\n\t\t\t'Nom Na Tong',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Batak','Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans NKo','Noto Sans Oriya','Noto Sans Oriya Bold','Noto Sans Syriac','Noto Sans Syriac Black','Noto Sans Syriac Bold','Noto Sans Syriac ExtraBold','Noto Sans Syriac ExtraLight','Noto Sans Syriac Light','Noto Sans Syriac Medium','Noto Sans Syriac SemiBold','Noto Sans Syriac Thin','Noto Sans Tagalog',\n\t\t\t'Noto Serif Kannada','Noto Serif Kannada Black','Noto Serif Kannada Bold','Noto Serif Kannada ExtraBold','Noto Serif Kannada ExtraLight','Noto Serif Kannada Light','Noto Serif Kannada Medium','Noto Serif Kannada SemiBold','Noto Serif Kannada Thin','Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'November Bangla Traditional','November Bangla Traditional Black','November Bangla Traditional Bold','November Bangla Traditional Compressed','November Bangla Traditional Compressed Black','November Bangla Traditional Compressed Bold','November Bangla Traditional Compressed Extralight','November Bangla Traditional Compressed Hairline','November Bangla Traditional Compressed Heavy','November Bangla Traditional Compressed Light','November Bangla Traditional Compressed Medium','November Bangla Traditional Compressed Thin','November Bangla Traditional Condensed','November Bangla Traditional Condensed Black','November Bangla Traditional Condensed Bold','November Bangla Traditional Condensed Extralight','November Bangla Traditional Condensed Hairline','November Bangla Traditional Condensed Heavy','November Bangla Traditional Condensed Light','November Bangla Traditional Condensed Medium','November Bangla Traditional Condensed Thin','November Bangla Traditional Extralight','November Bangla Traditional Hairline','November Bangla Traditional Heavy','November Bangla Traditional Light','November Bangla Traditional Medium','November Bangla Traditional Thin',\n\t\t\t'October Compressed Devanagari','October Compressed Devanagari Black','October Compressed Devanagari Bold','October Compressed Devanagari ExtraLight','October Compressed Devanagari Hairline','October Compressed Devanagari Heavy','October Compressed Devanagari Light','October Compressed Devanagari Medium','October Compressed Devanagari Thin','October Compressed Gujarati','October Compressed Gujarati Black','October Compressed Gujarati Bold','October Compressed Gujarati ExtraLight','October Compressed Gujarati Hairline','October Compressed Gujarati Heavy','October Compressed Gujarati Light','October Compressed Gujarati Medium','October Compressed Gujarati Thin','October Compressed Gurmukhi','October Compressed Gurmukhi Black','October Compressed Gurmukhi Bold','October Compressed Gurmukhi ExtraLight','October Compressed Gurmukhi Hairline','October Compressed Gurmukhi Heavy','October Compressed Gurmukhi Light','October Compressed Gurmukhi Medium','October Compressed Gurmukhi Thin','October Compressed Kannada','October Compressed Kannada Black','October Compressed Kannada Bold','October Compressed Kannada ExtraLight','October Compressed Kannada Hairline','October Compressed Kannada Heavy','October Compressed Kannada Light','October Compressed Kannada Medium','October Compressed Kannada Thin','October Compressed Meetei Mayek','October Compressed Meetei Mayek Black','October Compressed Meetei Mayek Bold','October Compressed Meetei Mayek ExtraLight','October Compressed Meetei Mayek Hairline','October Compressed Meetei Mayek Heavy','October Compressed Meetei Mayek Light','October Compressed Meetei Mayek Medium','October Compressed Meetei Mayek Thin','October Compressed Odia','October Compressed Odia Black','October Compressed Odia Bold','October Compressed Odia ExtraLight','October Compressed Odia Hairline','October Compressed Odia Heavy','October Compressed Odia Light','October Compressed Odia Medium','October Compressed Odia Thin','October Compressed Ol Chiki','October Compressed Ol Chiki Black','October Compressed Ol Chiki Bold','October Compressed Ol Chiki ExtraLight','October Compressed Ol Chiki Hairline','October Compressed Ol Chiki Heavy','October Compressed Ol Chiki Light','October Compressed Ol Chiki Medium','October Compressed Ol Chiki Thin','October Compressed Tamil','October Compressed Tamil Black','October Compressed Tamil Bold','October Compressed Tamil ExtraLight','October Compressed Tamil Hairline','October Compressed Tamil Heavy','October Compressed Tamil Light','October Compressed Tamil Medium','October Compressed Tamil Thin','October Compressed Telugu','October Compressed Telugu Black','October Compressed Telugu Bold','October Compressed Telugu ExtraLight','October Compressed Telugu Hairline','October Compressed Telugu Heavy','October Compressed Telugu Light','October Compressed Telugu Medium','October Compressed Telugu Thin','October Condensed Devanagari','October Condensed Devanagari Black','October Condensed Devanagari Bold','October Condensed Devanagari ExtraLight','October Condensed Devanagari Hairline','October Condensed Devanagari Heavy','October Condensed Devanagari Light','October Condensed Devanagari Medium','October Condensed Devanagari Thin','October Condensed Gujarati','October Condensed Gujarati Black','October Condensed Gujarati Bold','October Condensed Gujarati ExtraLight','October Condensed Gujarati Hairline','October Condensed Gujarati Heavy','October Condensed Gujarati Light','October Condensed Gujarati Medium','October Condensed Gujarati Thin','October Condensed Gurmukhi','October Condensed Gurmukhi Black','October Condensed Gurmukhi Bold','October Condensed Gurmukhi ExtraLight','October Condensed Gurmukhi Hairline','October Condensed Gurmukhi Heavy','October Condensed Gurmukhi Light','October Condensed Gurmukhi Medium','October Condensed Gurmukhi Thin','October Condensed Kannada','October Condensed Kannada Black','October Condensed Kannada Bold','October Condensed Kannada ExtraLight','October Condensed Kannada Hairline','October Condensed Kannada Heavy','October Condensed Kannada Light','October Condensed Kannada Medium','October Condensed Kannada Thin','October Condensed Meetei Mayek','October Condensed Meetei Mayek Black','October Condensed Meetei Mayek Bold','October Condensed Meetei Mayek ExtraLight','October Condensed Meetei Mayek Hairline','October Condensed Meetei Mayek Heavy','October Condensed Meetei Mayek Light','October Condensed Meetei Mayek Medium','October Condensed Meetei Mayek Thin','October Condensed Odia','October Condensed Odia Black','October Condensed Odia Bold','October Condensed Odia ExtraLight','October Condensed Odia Hairline','October Condensed Odia Heavy','October Condensed Odia Light','October Condensed Odia Medium','October Condensed Odia Thin','October Condensed Ol Chiki','October Condensed Ol Chiki Black','October Condensed Ol Chiki Bold','October Condensed Ol Chiki ExtraLight','October Condensed Ol Chiki Hairline','October Condensed Ol Chiki Heavy','October Condensed Ol Chiki Light','October Condensed Ol Chiki Medium','October Condensed Ol Chiki Thin','October Condensed Tamil','October Condensed Tamil Black','October Condensed Tamil Bold','October Condensed Tamil ExtraLight','October Condensed Tamil Hairline','October Condensed Tamil Heavy','October Condensed Tamil Light','October Condensed Tamil Medium','October Condensed Tamil Thin','October Condensed Telugu','October Condensed Telugu Black','October Condensed Telugu Bold','October Condensed Telugu ExtraLight','October Condensed Telugu Hairline','October Condensed Telugu Heavy','October Condensed Telugu Light','October Condensed Telugu Medium','October Condensed Telugu Thin','October Devanagari','October Devanagari Black','October Devanagari Bold','October Devanagari ExtraLight','October Devanagari Hairline','October Devanagari Heavy','October Devanagari Light','October Devanagari Medium','October Devanagari Thin','October Gujarati','October Gujarati Black','October Gujarati Bold','October Gujarati ExtraLight','October Gujarati Hairline','October Gujarati Heavy','October Gujarati Light','October Gujarati Medium','October Gujarati Thin','October Gurmukhi','October Gurmukhi Black','October Gurmukhi Bold','October Gurmukhi ExtraLight','October Gurmukhi Hairline','October Gurmukhi Heavy','October Gurmukhi Light','October Gurmukhi Medium','October Gurmukhi Thin','October Kannada','October Kannada Black','October Kannada Bold','October Kannada ExtraLight','October Kannada Hairline','October Kannada Heavy','October Kannada Light','October Kannada Medium','October Kannada Thin','October Meetei Mayek','October Meetei Mayek Black','October Meetei Mayek Bold','October Meetei Mayek ExtraLight','October Meetei Mayek Hairline','October Meetei Mayek Heavy','October Meetei Mayek Light','October Meetei Mayek Medium','October Meetei Mayek Thin','October Odia','October Odia Black','October Odia Bold','October Odia ExtraLight','October Odia Hairline','October Odia Heavy','October Odia Light','October Odia Medium','October Odia Thin','October Ol Chiki','October Ol Chiki Black','October Ol Chiki Bold','October Ol Chiki ExtraLight','October Ol Chiki Hairline','October Ol Chiki Heavy','October Ol Chiki Light','October Ol Chiki Medium','October Ol Chiki Thin','October Tamil','October Tamil Black','October Tamil Bold','October Tamil ExtraLight','October Tamil Hairline','October Tamil Heavy','October Tamil Light','October Tamil Medium','October Tamil Thin','October Telugu','October Telugu Black','October Telugu Bold','October Telugu ExtraLight','October Telugu Hairline','October Telugu Heavy','October Telugu Light','October Telugu Medium','October Telugu Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'PCMyungjo',\n\t\t\t'PSL Ornanong Pro','PSL Ornanong Pro Bold','PSL Ornanong Pro Bold Italic','PSL Ornanong Pro Demibold','PSL Ornanong Pro Demibold Italic','PSL Ornanong Pro Italic','PSL Ornanong Pro Light','PSL Ornanong Pro Light Italic',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Padyakke Expanded One',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Party LET',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PilGi',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang MO','PingFang MO Light','PingFang MO Medium','PingFang MO Semibold','PingFang MO Thin','PingFang MO Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Produkt','Produkt Extralight','Produkt Extralight Italic','Produkt Light','Produkt Light Italic','Produkt Medium','Produkt Medium Italic','Produkt Italic',\n\t\t\t'Proxima Nova','Proxima Nova Bold','Proxima Nova Bold It','Proxima Nova Extrabold','Proxima Nova Extrabold It','Proxima Nova It','Proxima Nova Light','Proxima Nova Light It','Proxima Nova Medium','Proxima Nova Medium It','Proxima Nova Semibold','Proxima Nova Semibold It',\n\t\t\t'Publico Headline Black','Publico Headline Black Italic','Publico Headline Bold','Publico Headline Bold Italic','Publico Headline Italic','Publico Headline Roman','Publico Text Bold','Publico Text Bold Italic','Publico Text Italic','Publico Text Roman','Publico Text Semibold','Publico Text Semibold Italic',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STFangsong',\n\t\t\t'STHeiti',\n\t\t\t'STIX Two Math','STIX Two Text','STIX Two Text Bold','STIX Two Text Bold Italic','STIX Two Text Italic','STIX Two Text Medium','STIX Two Text Medium Italic','STIX Two Text SemiBold','STIX Two Text SemiBold Italic',\n\t\t\t'STKaiti',\n\t\t\t'STSong',\n\t\t\t'STXihei',\n\t\t\t'Sama Devanagari','Sama Devanagari Bold','Sama Devanagari Book','Sama Devanagari ExtraBold','Sama Devanagari Medium','Sama Devanagari SemiBold','Sama Gujarati','Sama Gujarati Bold','Sama Gujarati Book','Sama Gujarati ExtraBold','Sama Gujarati Medium','Sama Gujarati SemiBold','Sama Gurmukhi','Sama Gurmukhi Bold','Sama Gurmukhi Book','Sama Gurmukhi ExtraBold','Sama Gurmukhi Medium','Sama Gurmukhi SemiBold','Sama Kannada','Sama Kannada Bold','Sama Kannada Book','Sama Kannada ExtraBold','Sama Kannada Medium','Sama Kannada SemiBold','Sama Malayalam','Sama Malayalam Bold','Sama Malayalam Book','Sama Malayalam ExtraBold','Sama Malayalam Medium','Sama Malayalam SemiBold','Sama Tamil','Sama Tamil Bold','Sama Tamil Book','Sama Tamil ExtraBold','Sama Tamil Medium','Sama Tamil SemiBold',\n\t\t\t'Sana',\n\t\t\t'Sarabun','Sarabun Bold','Sarabun Bold Italic','Sarabun ExtraBold','Sarabun ExtraBold Italic','Sarabun ExtraLight','Sarabun ExtraLight Italic','Sarabun Italic','Sarabun Light','Sarabun Light Italic','Sarabun Medium','Sarabun Medium Italic','Sarabun SemiBold','Sarabun SemiBold Italic','Sarabun Thin','Sarabun Thin Italic',\n\t\t\t'Sathu',\n\t\t\t'Sauber Script',\n\t\t\t'Savoye LET',\n\t\t\t'Shobhika','Shobhika Bold',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'SimSong','SimSong Bold',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light','Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Spot Mono','Spot Mono Bold','Spot Mono Medium',\n\t\t\t'Srisakdi','Srisakdi Bold',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Black','Tamil Sangam MN Bold','Tamil Sangam MN Demibold','Tamil Sangam MN Light','Tamil Sangam MN Medium',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Tiro Bangla','Tiro Bangla Italic','Tiro Devanagari Hindi','Tiro Devanagari Hindi Italic','Tiro Devanagari Marathi','Tiro Devanagari Marathi Italic','Tiro Devanagari Sanskrit','Tiro Devanagari Sanskrit Italic','Tiro Gurmukhi','Tiro Gurmukhi Italic','Tiro Kannada','Tiro Kannada Italic','Tiro Tamil','Tiro Tamil Italic','Tiro Telugu','Tiro Telugu Italic',\n\t\t\t'Toppan Bunkyu Gothic','Toppan Bunkyu Gothic Demibold','Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold','Toppan Bunkyu Mincho',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Tsukushi A Round Gothic','Tsukushi A Round Gothic Bold','Tsukushi B Round Gothic','Tsukushi B Round Gothic Bold',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Webdings',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light','Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium','YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium','YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t'Yuanti SC','Yuanti SC Bold','Yuanti SC Light','Yuanti TC','Yuanti TC Bold','Yuanti TC Light',\n\t\t\t'Yuppy SC','Yuppy TC',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n\t'v26': {\n\t\tname: 'Tahoe',\n\t\tversion: 26,\n\t\turl: 'https://support.apple.com/en-us/122869',\n\t\tlist: [\n\t\t\t'Academy Engraved LET',\n\t\t\t'Adelle Sans Devanagari','Adelle Sans Devanagari Bold','Adelle Sans Devanagari Extrabold','Adelle Sans Devanagari Heavy','Adelle Sans Devanagari Light','Adelle Sans Devanagari Semibold','Adelle Sans Devanagari Thin',\n\t\t\t'AkayaKanadaka','AkayaTelivigala',\n\t\t\t'Al Bayan','Al Bayan Bold','Al Nile','Al Nile Bold','Al Tarikh',\n\t\t\t'American Typewriter','American Typewriter Bold','American Typewriter Condensed','American Typewriter Condensed Bold','American Typewriter Condensed Light','American Typewriter Light','American Typewriter Semibold',\n\t\t\t'Andale Mono',\n\t\t\t'Annai MN',\n\t\t\t'Apple Braille','Apple Braille Outline 6 Dot','Apple Braille Outline 8 Dot','Apple Braille Pinpoint 6 Dot','Apple Braille Pinpoint 8 Dot',\n\t\t\t'Apple Chancery','Apple Color Emoji','Apple LiGothic Medium',\n\t\t\t'Apple LiSung Light','Apple SD Gothic Neo','Apple SD Gothic Neo Bold','Apple SD Gothic Neo ExtraBold','Apple SD Gothic Neo Heavy','Apple SD Gothic Neo Light','Apple SD Gothic Neo Medium','Apple SD Gothic Neo SemiBold','Apple SD Gothic Neo Thin','Apple SD Gothic Neo UltraLight','Apple Symbols','AppleGothic','AppleMyungjo',\n\t\t\t'Arial','Arial Black','Arial Bold','Arial Bold Italic','Arial Hebrew','Arial Hebrew Bold','Arial Hebrew Light','Arial Hebrew Scholar','Arial Hebrew Scholar Bold','Arial Hebrew Scholar Light','Arial Italic','Arial Narrow','Arial Narrow Bold','Arial Narrow Bold Italic','Arial Narrow Italic','Arial Rounded MT Bold','Arial Unicode MS',\n\t\t\t'Arima Koshi','Arima Koshi Black','Arima Koshi Bold','Arima Koshi ExtraBold','Arima Koshi ExtraLight','Arima Koshi Light','Arima Koshi Medium','Arima Koshi Thin','Arima Madurai','Arima Madurai Black','Arima Madurai Bold','Arima Madurai ExtraLight','Arima Madurai Light','Arima Madurai Medium','Arima Madurai Semi Bold','Arima Madurai Thin',\n\t\t\t'Avenir Black','Avenir Black Oblique','Avenir Book','Avenir Book Oblique','Avenir Heavy','Avenir Heavy Oblique','Avenir Light','Avenir Light Oblique','Avenir Medium','Avenir Medium Oblique','Avenir Next Bold','Avenir Next Bold Italic','Avenir Next Condensed','Avenir Next Condensed Bold','Avenir Next Condensed Bold Italic','Avenir Next Condensed Demi Bold','Avenir Next Condensed Demi Bold Italic','Avenir Next Condensed Heavy','Avenir Next Condensed Heavy Italic','Avenir Next Condensed Italic','Avenir Next Condensed Medium','Avenir Next Condensed Medium Italic','Avenir Next Condensed Ultra Light','Avenir Next Condensed Ultra Light Italic','Avenir Next','Avenir Next Demi Bold','Avenir Next Demi Bold Italic','Avenir Next Heavy','Avenir Next Heavy Italic','Avenir Next Italic','Avenir Next Medium','Avenir Next Medium Italic','Avenir Next Ultra Light','Avenir Next Ultra Light Italic','Avenir Oblique','Avenir Roman',\n\t\t\t'Ayuthaya',\n\t\t\t'Baghdad',\n\t\t\t'Bai Jamjuree','Bai Jamjuree Bold','Bai Jamjuree Bold Italic','Bai Jamjuree ExtraLight','Bai Jamjuree ExtraLight Italic','Bai Jamjuree Italic','Bai Jamjuree Light','Bai Jamjuree Light Italic','Bai Jamjuree Medium','Bai Jamjuree Medium Italic','Bai Jamjuree SemiBold','Bai Jamjuree SemiBold Italic',\n\t\t\t'BIZ UDGothic','BIZ UDGothic Bold','BIZ UDMincho',\n\t\t\t'BM DoHyeon','BM Hanna 11yrs Old','BM Hanna Air','BM Hanna Pro','BM Jua','BM Kirang Haerang','BM Yeonsung',\n\t\t\t'Baloo 2','Baloo 2 Bold','Baloo 2 ExtraBold','Baloo 2 Medium','Baloo 2 SemiBold','Baloo Bhai 2','Baloo Bhai 2 Bold','Baloo Bhai 2 ExtraBold','Baloo Bhai 2 Medium','Baloo Bhai 2 SemiBold','Baloo Bhaijaan','Baloo Bhaina 2','Baloo Bhaina 2 Bold','Baloo Bhaina 2 ExtraBold','Baloo Bhaina 2 Medium','Baloo Bhaina 2 SemiBold','Baloo Chettan 2','Baloo Chettan 2 Bold','Baloo Chettan 2 ExtraBold','Baloo Chettan 2 Medium','Baloo Chettan 2 SemiBold','Baloo Da 2','Baloo Da 2 Bold','Baloo Da 2 ExtraBold','Baloo Da 2 Medium','Baloo Da 2 SemiBold','Baloo Paaji 2','Baloo Paaji 2 Bold','Baloo Paaji 2 ExtraBold','Baloo Paaji 2 Medium','Baloo Paaji 2 SemiBold','Baloo Tamma 2','Baloo Tamma 2 Bold','Baloo Tamma 2 ExtraBold','Baloo Tamma 2 Medium','Baloo Tamma 2 SemiBold','Baloo Tammudu 2','Baloo Tammudu 2 Bold','Baloo Tammudu 2 ExtraBold','Baloo Tammudu 2 Medium','Baloo Tammudu 2 SemiBold','Baloo Thambi 2','Baloo Thambi 2 Bold','Baloo Thambi 2 ExtraBold','Baloo Thambi 2 Medium','Baloo Thambi 2 SemiBold',\n\t\t\t'Bangla MN','Bangla MN Bold','Bangla Sangam MN','Bangla Sangam MN Bold',\n\t\t\t'Baoli SC','Baoli TC',\n\t\t\t'Baskerville','Baskerville Bold','Baskerville Bold Italic','Baskerville Italic','Baskerville SemiBold','Baskerville SemiBold Italic',\n\t\t\t'Beirut',\n\t\t\t'BiauKaiHK','BiauKaiTC',\n\t\t\t'Big Caslon Medium',\n\t\t\t'Bodoni 72 Bold','Bodoni 72 Book','Bodoni 72 Book Italic','Bodoni 72 Oldstyle Bold','Bodoni 72 Oldstyle Book','Bodoni 72 Oldstyle Book Italic','Bodoni 72 Smallcaps Book','Bodoni Ornaments',\n\t\t\t'Bradley Hand Bold',\n\t\t\t'Brill Bold Italic','Brill Italic','Brill Medium Italic','Brill Roman','Brill Roman Bold','Brill Roman Medium','Brill Roman Semibold','Brill Semibold Italic',\n\t\t\t'Brush Script MT Italic',\n\t\t\t'Cambay Devanagari','Cambay Devanagari Bold','Cambay Devanagari Bold Oblique','Cambay Devanagari Oblique',\n\t\t\t'Canela','Canela Bold','Canela Bold Italic','Canela Deck','Canela Deck Bold','Canela Deck Bold Italic','Canela Deck Italic','Canela Deck Medium','Canela Deck Medium Italic','Canela Italic','Canela Text','Canela Text Bold','Canela Text Bold Italic','Canela Text Italic','Canela Text Medium','Canela Text Medium Italic',\n\t\t\t'Catamaran','Catamaran Black','Catamaran Bold','Catamaran ExtraBold','Catamaran ExtraLight','Catamaran Light','Catamaran Medium','Catamaran SemiBold','Catamaran Thin',\n\t\t\t'Chakra Petch','Chakra Petch Bold','Chakra Petch Bold Italic','Chakra Petch ExtraLight','Chakra Petch ExtraLight Italic','Chakra Petch Italic','Chakra Petch Light','Chakra Petch Light Italic','Chakra Petch Medium','Chakra Petch Medium Italic','Chakra Petch SemiBold','Chakra Petch SemiBold Italic',\n\t\t\t'Chalkboard','Chalkboard Bold','Chalkboard SE','Chalkboard SE Bold','Chalkboard SE Light','Chalkduster',\n\t\t\t'Charm','Charm Bold',\n\t\t\t'Charmonman','Charmonman Bold',\n\t\t\t'Charter Black','Charter Black Italic','Charter Bold','Charter Bold Italic','Charter Italic','Charter Roman',\n\t\t\t'Cochin','Cochin Bold','Cochin Bold Italic','Cochin Italic',\n\t\t\t'Comic Sans MS','Comic Sans MS Bold',\n\t\t\t'Copperplate','Copperplate Bold','Copperplate Light',\n\t\t\t'Corsiva Hebrew','Corsiva Hebrew Bold',\n\t\t\t'Courier New','Courier New Bold','Courier New Bold Italic','Courier New Italic',\n\t\t\t'DIN Alternate Bold','DIN Condensed Bold',\n\t\t\t'Damascus','Damascus Bold','Damascus Light','Damascus Medium','Damascus Semi Bold',\n\t\t\t'Dash','Dash Practice',\n\t\t\t'DecoType Naskh',\n\t\t\t'Devanagari MT','Devanagari MT Bold','Devanagari Sangam MN','Devanagari Sangam MN Bold',\n\t\t\t'Didot','Didot Bold','Didot Italic',\n\t\t\t'Diwan Kufi','Diwan Thuluth',\n\t\t\t'Domaine Display','Domaine Display Bold','Domaine Display Bold Italic','Domaine Display Italic','Domaine Display Medium','Domaine Display Medium Italic',\n\t\t\t'Euphemia UCAS','Euphemia UCAS Bold','Euphemia UCAS Italic',\n\t\t\t'Fahkwang','Fahkwang Bold','Fahkwang Bold Italic','Fahkwang ExtraLight','Fahkwang ExtraLight Italic','Fahkwang Italic','Fahkwang Light','Fahkwang Light Italic','Fahkwang Medium','Fahkwang Medium Italic','Fahkwang SemiBold','Fahkwang SemiBold Italic',\n\t\t\t'Farah',\n\t\t\t'Farisi',\n\t\t\t'Founders Grotesk','Founders Grotesk Bold','Founders Grotesk Bold Italic','Founders Grotesk Condensed','Founders Grotesk Condensed Bold','Founders Grotesk Condensed Semibold','Founders Grotesk Italic','Founders Grotesk Light','Founders Grotesk Light Italic','Founders Grotesk Medium','Founders Grotesk Medium Italic','Founders Grotesk Semibold','Founders Grotesk Semibold Italic','Founders Grotesk Text','Founders Grotesk Text Bold','Founders Grotesk Text Bold Italic','Founders Grotesk Text Italic',\n\t\t\t'Futura Bold','Futura Condensed ExtraBold','Futura Condensed Medium','Futura Medium','Futura Medium Italic',\n\t\t\t'GB18030 Bitmap',\n\t\t\t'Galvji','Galvji Bold','Galvji Bold Oblique','Galvji Oblique',\n\t\t\t'Geeza Pro','Geeza Pro Bold',\n\t\t\t'Geneva',\n\t\t\t'Georgia','Georgia Bold','Georgia Bold Italic','Georgia Italic',\n\t\t\t'Gill Sans','Gill Sans Bold','Gill Sans Bold Italic','Gill Sans Italic','Gill Sans Light','Gill Sans Light Italic','Gill Sans SemiBold','Gill Sans SemiBold Italic','Gill Sans UltraBold',\n\t\t\t'Gotu',\n\t\t\t'Grantha Sangam MN','Grantha Sangam MN Black','Grantha Sangam MN Bold','Grantha Sangam MN DemiBold','Grantha Sangam MN Light','Grantha Sangam MN Medium',\n\t\t\t'Graphik','Graphik Bold','Graphik Bold Italic','Graphik Compact','Graphik Compact Bold','Graphik Compact Bold Italic','Graphik Compact Italic','Graphik Compact Medium','Graphik Compact Medium Italic','Graphik Compact Semibold','Graphik Compact Semibold Italic','Graphik Italic','Graphik Light','Graphik Light Italic','Graphik Medium','Graphik Medium Italic','Graphik Semibold','Graphik Semibold Italic',\n\t\t\t'Gujarati MT','Gujarati MT Bold','Gujarati Sangam MN','Gujarati Sangam MN Bold',\n\t\t\t'GungSeo',\n\t\t\t'Gurmukhi MN','Gurmukhi MN Bold','Gurmukhi MT','Gurmukhi Sangam MN','Gurmukhi Sangam MN Bold',\n\t\t\t'Hannotate SC','Hannotate SC Bold','Hannotate TC','Hannotate TC Bold',\n\t\t\t'HanziPen SC','HanziPen SC Bold','HanziPen TC','HanziPen TC Bold',\n\t\t\t'HeadLineA',\n\t\t\t'Hei',\n\t\t\t'Heiti SC Light','Heiti SC Medium','Heiti TC Light','Heiti TC Medium',\n\t\t\t'Helvetica','Helvetica Bold','Helvetica Bold Oblique','Helvetica Light','Helvetica Light Oblique','Helvetica Neue','Helvetica Neue Bold','Helvetica Neue Bold Italic','Helvetica Neue Condensed Black','Helvetica Neue Condensed Bold','Helvetica Neue Italic','Helvetica Neue Light','Helvetica Neue Light Italic','Helvetica Neue Medium','Helvetica Neue Medium Italic','Helvetica Neue Thin','Helvetica Neue Thin Italic','Helvetica Neue UltraLight','Helvetica Neue UltraLight Italic','Helvetica Oblique',\n\t\t\t'Herculanum',\n\t\t\t'Hiragino Maru Gothic ProN W4','Hiragino Mincho ProN W3','Hiragino Mincho ProN W6','Hiragino Sans GB W3','Hiragino Sans GB W6','Hiragino Sans TC W3','Hiragino Sans TC W6','Hiragino Sans W0','Hiragino Sans W1','Hiragino Sans W2','Hiragino Sans W3','Hiragino Sans W4','Hiragino Sans W5','Hiragino Sans W6','Hiragino Sans W7','Hiragino Sans W8','Hiragino Sans W9',\n\t\t\t'Hoefler Text','Hoefler Text Black','Hoefler Text Black Italic','Hoefler Text Italic','Hoefler Text Ornaments',\n\t\t\t'Hubballi',\n\t\t\t'Impact',\n\t\t\t'InaiMathi','InaiMathi Bold',\n\t\t\t'ITF Devanagari Bold','ITF Devanagari Book','ITF Devanagari Demi','ITF Devanagari Light','ITF Devanagari Marathi Bold','ITF Devanagari Marathi Book','ITF Devanagari Marathi Demi','ITF Devanagari Marathi Light','ITF Devanagari Marathi Medium','ITF Devanagari Medium',\n\t\t\t'Jaini','Jaini Purva',\n\t\t\t'K2D','K2D Bold','K2D Bold Italic','K2D ExtraBold','K2D ExtraBold Italic','K2D ExtraLight','K2D ExtraLight Italic','K2D Italic','K2D Light','K2D Light Italic','K2D Medium','K2D Medium Italic','K2D SemiBold','K2D SemiBold Italic','K2D Thin','K2D Thin Italic',\n\t\t\t'Kai',\n\t\t\t'Kailasa','Kailasa Bold',\n\t\t\t'Kaiti SC','Kaiti SC Black','Kaiti SC Bold','Kaiti TC','Kaiti TC Black','Kaiti TC Bold',\n\t\t\t'Kannada MN','Kannada MN Bold','Kannada Sangam MN','Kannada Sangam MN Bold',\n\t\t\t'Katari','Katari Black','Katari Black Italic','Katari Bold','Katari Bold Italic','Katari Italic','Katari Medium','Katari Medium Italic',\n\t\t\t'Kavivanar',\n\t\t\t'Kefa III','Kefa III Bold','Kefa III ExtraBold','Kefa III Light',\n\t\t\t'Khmer MN','Khmer MN Bold','Khmer Sangam MN',\n\t\t\t'Kigelia','Kigelia Arabic','Kigelia Arabic Bold','Kigelia Arabic Extrabold','Kigelia Arabic Light','Kigelia Arabic Semibold','Kigelia Bold','Kigelia Bold Italic','Kigelia Extrabold','Kigelia Extrabold Italic','Kigelia Italic','Kigelia Light','Kigelia Light Italic','Kigelia Semibold','Kigelia Semibold Italic',\n\t\t\t'Klee Demibold','Klee Medium',\n\t\t\t'KoHo','KoHo Bold','KoHo Bold Italic','KoHo ExtraLight','KoHo ExtraLight Italic','KoHo Italic','KoHo Light','KoHo Light Italic','KoHo Medium','KoHo Medium Italic','KoHo SemiBold','KoHo SemiBold Italic',\n\t\t\t'Kodchasan','Kodchasan Bold','Kodchasan Bold Italic','Kodchasan ExtraLight','Kodchasan ExtraLight Italic','Kodchasan Italic','Kodchasan Light','Kodchasan Light Italic','Kodchasan Medium','Kodchasan Medium Italic','Kodchasan SemiBold','Kodchasan SemiBold Italic',\n\t\t\t'Kohinoor Bangla','Kohinoor Bangla Bold','Kohinoor Bangla Light','Kohinoor Bangla Medium','Kohinoor Bangla Semibold','Kohinoor Devanagari','Kohinoor Devanagari Bold','Kohinoor Devanagari Light','Kohinoor Devanagari Medium','Kohinoor Devanagari Semibold','Kohinoor Gujarati','Kohinoor Gujarati Bold','Kohinoor Gujarati Light','Kohinoor Gujarati Medium','Kohinoor Gujarati Semibold','Kohinoor Telugu','Kohinoor Telugu Bold','Kohinoor Telugu Light','Kohinoor Telugu Medium','Kohinoor Telugu Semibold',\n\t\t\t'Kokonor',\n\t\t\t'Krub','Krub Bold','Krub Bold Italic','Krub ExtraLight','Krub ExtraLight Italic','Krub Italic','Krub Light','Krub Light Italic','Krub Medium','Krub Medium Italic','Krub SemiBold','Krub SemiBold Italic',\n\t\t\t'Krungthep',\n\t\t\t'KufiStandardGK',\n\t\t\t'Lahore Gurmukhi','Lahore Gurmukhi Bold','Lahore Gurmukhi Light','Lahore Gurmukhi Medium','Lahore Gurmukhi SemiBold',\n\t\t\t'Lantinghei SC Demibold','Lantinghei SC Extralight','Lantinghei SC Heavy','Lantinghei TC Demibold','Lantinghei TC Heavy',\n\t\t\t'Lao MN','Lao MN Bold','Lao Sangam MN',\n\t\t\t'Lava Devanagari','Lava Devanagari Bold','Lava Devanagari Heavy','Lava Devanagari Medium','Lava Kannada','Lava Kannada Bold','Lava Kannada Heavy','Lava Kannada Medium','Lava Telugu','Lava Telugu Bold','Lava Telugu Heavy','Lava Telugu Medium',\n\t\t\t'Libian SC','Libian TC',\n\t\t\t'LiHei Pro',\n\t\t\t'LingWai SC Medium','LingWai TC Medium',\n\t\t\t'LiSong Pro',\n\t\t\t'Lucida Grande','Lucida Grande Bold',\n\t\t\t'Luminari',\n\t\t\t'Maku','Maku Bold',\n\t\t\t'Malayalam MN','Malayalam MN Bold','Malayalam Sangam MN','Malayalam Sangam MN Bold',\n\t\t\t'Mali','Mali Bold','Mali Bold Italic','Mali ExtraLight','Mali ExtraLight Italic','Mali Italic','Mali Light','Mali Light Italic','Mali Medium','Mali Medium Italic','Mali SemiBold','Mali SemiBold Italic',\n\t\t\t'Marker Felt Thin','Marker Felt Wide',\n\t\t\t'Menlo','Menlo Bold','Menlo Bold Italic','Menlo Italic',\n\t\t\t'Microsoft Sans Serif',\n\t\t\t'Mishafi','Mishafi Gold',\n\t\t\t'Modak',\n\t\t\t'Monaco',\n\t\t\t'Mshtakan','Mshtakan Bold','Mshtakan BoldOblique','Mshtakan Oblique',\n\t\t\t'Mukta','Mukta Bold','Mukta ExtraBold','Mukta ExtraLight','Mukta Light','Mukta Mahee','Mukta Mahee Bold','Mukta Mahee ExtraBold','Mukta Mahee ExtraLight','Mukta Mahee Light','Mukta Mahee Medium','Mukta Mahee SemiBold','Mukta Malar','Mukta Malar Bold','Mukta Malar ExtraBold','Mukta Malar ExtraLight','Mukta Malar Light','Mukta Malar Medium','Mukta Malar SemiBold','Mukta Medium','Mukta SemiBold','Mukta Vaani','Mukta Vaani Bold','Mukta Vaani ExtraBold','Mukta Vaani ExtraLight','Mukta Vaani Light','Mukta Vaani Medium','Mukta Vaani SemiBold',\n\t\t\t'Muna','Muna Black','Muna Bold',\n\t\t\t'Myanmar MN','Myanmar MN Bold','Myanmar Sangam MN','Myanmar Sangam MN Bold',\n\t\t\t'Myriad Arabic','Myriad Arabic Black','Myriad Arabic Black Italic','Myriad Arabic Bold','Myriad Arabic Bold Italic','Myriad Arabic Italic','Myriad Arabic Light','Myriad Arabic Light Italic','Myriad Arabic Semibold','Myriad Arabic Semibold Italic',\n\t\t\t'Nadeem',\n\t\t\t'Nanum Brush Script','Nanum Gothic','Nanum Gothic Bold','Nanum Gothic ExtraBold','Nanum Myeongjo','Nanum Myeongjo Bold','Nanum Myeongjo ExtraBold','Nanum Pen Script',\n\t\t\t'New Peninim MT','New Peninim MT Bold','New Peninim MT Bold Inclined','New Peninim MT Inclined',\n\t\t\t'Niramit','Niramit Bold','Niramit Bold Italic','Niramit ExtraLight','Niramit ExtraLight Italic','Niramit Italic','Niramit Light','Niramit Light Italic','Niramit Medium','Niramit Medium Italic','Niramit SemiBold','Niramit SemiBold Italic',\n\t\t\t'Nom Na Tong',\n\t\t\t'Noteworthy Bold','Noteworthy Light',\n\t\t\t'Noto Nastaliq Urdu','Noto Nastaliq Urdu Bold',\n\t\t\t'Noto Sans Batak','Noto Sans Kannada','Noto Sans Kannada Black','Noto Sans Kannada Bold','Noto Sans Kannada ExtraBold','Noto Sans Kannada ExtraLight','Noto Sans Kannada Light','Noto Sans Kannada Medium','Noto Sans Kannada SemiBold','Noto Sans Kannada Thin','Noto Sans Myanmar','Noto Sans Myanmar Black','Noto Sans Myanmar Bold','Noto Sans Myanmar ExtraBold','Noto Sans Myanmar ExtraLight','Noto Sans Myanmar Light','Noto Sans Myanmar Medium','Noto Sans Myanmar SemiBold','Noto Sans Myanmar Thin','Noto Sans NKo','Noto Sans Oriya','Noto Sans Oriya Bold','Noto Sans Syriac','Noto Sans Syriac Black','Noto Sans Syriac Bold','Noto Sans Syriac ExtraBold','Noto Sans Syriac ExtraLight','Noto Sans Syriac Light','Noto Sans Syriac Medium','Noto Sans Syriac SemiBold','Noto Sans Syriac Thin','Noto Sans Tagalog',\n\t\t\t'Noto Serif Kannada','Noto Serif Kannada Black','Noto Serif Kannada Bold','Noto Serif Kannada ExtraBold','Noto Serif Kannada ExtraLight','Noto Serif Kannada Light','Noto Serif Kannada Medium','Noto Serif Kannada SemiBold','Noto Serif Kannada Thin','Noto Serif Myanmar','Noto Serif Myanmar Black','Noto Serif Myanmar Bold','Noto Serif Myanmar ExtraBold','Noto Serif Myanmar ExtraLight','Noto Serif Myanmar Light','Noto Serif Myanmar Medium','Noto Serif Myanmar SemiBold','Noto Serif Myanmar Thin',\n\t\t\t'November Bangla Traditional','November Bangla Traditional Black','November Bangla Traditional Bold','November Bangla Traditional Compressed','November Bangla Traditional Compressed Black','November Bangla Traditional Compressed Bold','November Bangla Traditional Compressed Extralight','November Bangla Traditional Compressed Hairline','November Bangla Traditional Compressed Heavy','November Bangla Traditional Compressed Light','November Bangla Traditional Compressed Medium','November Bangla Traditional Compressed Thin','November Bangla Traditional Condensed','November Bangla Traditional Condensed Black','November Bangla Traditional Condensed Bold','November Bangla Traditional Condensed Extralight','November Bangla Traditional Condensed Hairline','November Bangla Traditional Condensed Heavy','November Bangla Traditional Condensed Light','November Bangla Traditional Condensed Medium','November Bangla Traditional Condensed Thin','November Bangla Traditional Extralight','November Bangla Traditional Hairline','November Bangla Traditional Heavy','November Bangla Traditional Light','November Bangla Traditional Medium','November Bangla Traditional Thin',\n\t\t\t'October Compressed Devanagari','October Compressed Devanagari Black','October Compressed Devanagari Bold','October Compressed Devanagari ExtraLight','October Compressed Devanagari Hairline','October Compressed Devanagari Heavy','October Compressed Devanagari Light','October Compressed Devanagari Medium','October Compressed Devanagari Thin','October Compressed Gujarati','October Compressed Gujarati Black','October Compressed Gujarati Bold','October Compressed Gujarati ExtraLight','October Compressed Gujarati Hairline','October Compressed Gujarati Heavy','October Compressed Gujarati Light','October Compressed Gujarati Medium','October Compressed Gujarati Thin','October Compressed Gurmukhi','October Compressed Gurmukhi Black','October Compressed Gurmukhi Bold','October Compressed Gurmukhi ExtraLight','October Compressed Gurmukhi Hairline','October Compressed Gurmukhi Heavy','October Compressed Gurmukhi Light','October Compressed Gurmukhi Medium','October Compressed Gurmukhi Thin','October Compressed Kannada','October Compressed Kannada Black','October Compressed Kannada Bold','October Compressed Kannada ExtraLight','October Compressed Kannada Hairline','October Compressed Kannada Heavy','October Compressed Kannada Light','October Compressed Kannada Medium','October Compressed Kannada Thin','October Compressed Meetei Mayek','October Compressed Meetei Mayek Black','October Compressed Meetei Mayek Bold','October Compressed Meetei Mayek ExtraLight','October Compressed Meetei Mayek Hairline','October Compressed Meetei Mayek Heavy','October Compressed Meetei Mayek Light','October Compressed Meetei Mayek Medium','October Compressed Meetei Mayek Thin','October Compressed Odia','October Compressed Odia Black','October Compressed Odia Bold','October Compressed Odia ExtraLight','October Compressed Odia Hairline','October Compressed Odia Heavy','October Compressed Odia Light','October Compressed Odia Medium','October Compressed Odia Thin','October Compressed Ol Chiki','October Compressed Ol Chiki Black','October Compressed Ol Chiki Bold','October Compressed Ol Chiki ExtraLight','October Compressed Ol Chiki Hairline','October Compressed Ol Chiki Heavy','October Compressed Ol Chiki Light','October Compressed Ol Chiki Medium','October Compressed Ol Chiki Thin','October Compressed Tamil','October Compressed Tamil Black','October Compressed Tamil Bold','October Compressed Tamil ExtraLight','October Compressed Tamil Hairline','October Compressed Tamil Heavy','October Compressed Tamil Light','October Compressed Tamil Medium','October Compressed Tamil Thin','October Compressed Telugu','October Compressed Telugu Black','October Compressed Telugu Bold','October Compressed Telugu ExtraLight','October Compressed Telugu Hairline','October Compressed Telugu Heavy','October Compressed Telugu Light','October Compressed Telugu Medium','October Compressed Telugu Thin','October Condensed Devanagari','October Condensed Devanagari Black','October Condensed Devanagari Bold','October Condensed Devanagari ExtraLight','October Condensed Devanagari Hairline','October Condensed Devanagari Heavy','October Condensed Devanagari Light','October Condensed Devanagari Medium','October Condensed Devanagari Thin','October Condensed Gujarati','October Condensed Gujarati Black','October Condensed Gujarati Bold','October Condensed Gujarati ExtraLight','October Condensed Gujarati Hairline','October Condensed Gujarati Heavy','October Condensed Gujarati Light','October Condensed Gujarati Medium','October Condensed Gujarati Thin','October Condensed Gurmukhi','October Condensed Gurmukhi Black','October Condensed Gurmukhi Bold','October Condensed Gurmukhi ExtraLight','October Condensed Gurmukhi Hairline','October Condensed Gurmukhi Heavy','October Condensed Gurmukhi Light','October Condensed Gurmukhi Medium','October Condensed Gurmukhi Thin','October Condensed Kannada','October Condensed Kannada Black','October Condensed Kannada Bold','October Condensed Kannada ExtraLight','October Condensed Kannada Hairline','October Condensed Kannada Heavy','October Condensed Kannada Light','October Condensed Kannada Medium','October Condensed Kannada Thin','October Condensed Meetei Mayek','October Condensed Meetei Mayek Black','October Condensed Meetei Mayek Bold','October Condensed Meetei Mayek ExtraLight','October Condensed Meetei Mayek Hairline','October Condensed Meetei Mayek Heavy','October Condensed Meetei Mayek Light','October Condensed Meetei Mayek Medium','October Condensed Meetei Mayek Thin','October Condensed Odia','October Condensed Odia Black','October Condensed Odia Bold','October Condensed Odia ExtraLight','October Condensed Odia Hairline','October Condensed Odia Heavy','October Condensed Odia Light','October Condensed Odia Medium','October Condensed Odia Thin','October Condensed Ol Chiki','October Condensed Ol Chiki Black','October Condensed Ol Chiki Bold','October Condensed Ol Chiki ExtraLight','October Condensed Ol Chiki Hairline','October Condensed Ol Chiki Heavy','October Condensed Ol Chiki Light','October Condensed Ol Chiki Medium','October Condensed Ol Chiki Thin','October Condensed Tamil','October Condensed Tamil Black','October Condensed Tamil Bold','October Condensed Tamil ExtraLight','October Condensed Tamil Hairline','October Condensed Tamil Heavy','October Condensed Tamil Light','October Condensed Tamil Medium','October Condensed Tamil Thin','October Condensed Telugu','October Condensed Telugu Black','October Condensed Telugu Bold','October Condensed Telugu ExtraLight','October Condensed Telugu Hairline','October Condensed Telugu Heavy','October Condensed Telugu Light','October Condensed Telugu Medium','October Condensed Telugu Thin','October Devanagari','October Devanagari Black','October Devanagari Bold','October Devanagari ExtraLight','October Devanagari Hairline','October Devanagari Heavy','October Devanagari Light','October Devanagari Medium','October Devanagari Thin','October Gujarati','October Gujarati Black','October Gujarati Bold','October Gujarati ExtraLight','October Gujarati Hairline','October Gujarati Heavy','October Gujarati Light','October Gujarati Medium','October Gujarati Thin','October Gurmukhi','October Gurmukhi Black','October Gurmukhi Bold','October Gurmukhi ExtraLight','October Gurmukhi Hairline','October Gurmukhi Heavy','October Gurmukhi Light','October Gurmukhi Medium','October Gurmukhi Thin','October Kannada','October Kannada Black','October Kannada Bold','October Kannada ExtraLight','October Kannada Hairline','October Kannada Heavy','October Kannada Light','October Kannada Medium','October Kannada Thin','October Meetei Mayek','October Meetei Mayek Black','October Meetei Mayek Bold','October Meetei Mayek ExtraLight','October Meetei Mayek Hairline','October Meetei Mayek Heavy','October Meetei Mayek Light','October Meetei Mayek Medium','October Meetei Mayek Thin','October Odia','October Odia Black','October Odia Bold','October Odia ExtraLight','October Odia Hairline','October Odia Heavy','October Odia Light','October Odia Medium','October Odia Thin','October Ol Chiki','October Ol Chiki Black','October Ol Chiki Bold','October Ol Chiki ExtraLight','October Ol Chiki Hairline','October Ol Chiki Heavy','October Ol Chiki Light','October Ol Chiki Medium','October Ol Chiki Thin','October Tamil','October Tamil Black','October Tamil Bold','October Tamil ExtraLight','October Tamil Hairline','October Tamil Heavy','October Tamil Light','October Tamil Medium','October Tamil Thin','October Telugu','October Telugu Black','October Telugu Bold','October Telugu ExtraLight','October Telugu Hairline','October Telugu Heavy','October Telugu Light','October Telugu Medium','October Telugu Thin',\n\t\t\t'Optima','Optima Bold','Optima Bold Italic','Optima ExtraBlack','Optima Italic',\n\t\t\t'Oriya MN','Oriya MN Bold','Oriya Sangam MN','Oriya Sangam MN Bold',\n\t\t\t'Osaka','Osaka-Mono',\n\t\t\t'PSL Ornanong Pro','PSL Ornanong Pro Bold','PSL Ornanong Pro Bold Italic','PSL Ornanong Pro Demibold','PSL Ornanong Pro Demibold Italic','PSL Ornanong Pro Italic','PSL Ornanong Pro Light','PSL Ornanong Pro Light Italic',\n\t\t\t'PT Mono','PT Mono Bold','PT Sans','PT Sans Bold','PT Sans Bold Italic','PT Sans Caption','PT Sans Caption Bold','PT Sans Italic','PT Sans Narrow','PT Sans Narrow Bold','PT Serif','PT Serif Bold','PT Serif Bold Italic','PT Serif Caption','PT Serif Caption Italic','PT Serif Italic',\n\t\t\t'Padyakke Expanded One',\n\t\t\t'Palatino','Palatino Bold','Palatino Bold Italic','Palatino Italic',\n\t\t\t'Papyrus','Papyrus Condensed',\n\t\t\t'Party LET',\n\t\t\t'PCMyungjo',\n\t\t\t'Phosphate Inline','Phosphate Solid',\n\t\t\t'PilGi',\n\t\t\t'PingFang HK','PingFang HK Light','PingFang HK Medium','PingFang HK Semibold','PingFang HK Thin','PingFang HK Ultralight','PingFang MO','PingFang MO Light','PingFang MO Medium','PingFang MO Semibold','PingFang MO Thin','PingFang MO Ultralight','PingFang SC','PingFang SC Light','PingFang SC Medium','PingFang SC Semibold','PingFang SC Thin','PingFang SC Ultralight','PingFang TC','PingFang TC Light','PingFang TC Medium','PingFang TC Semibold','PingFang TC Thin','PingFang TC Ultralight',\n\t\t\t'Plantagenet Cherokee',\n\t\t\t'Produkt','Produkt Extralight','Produkt Extralight Italic','Produkt Italic','Produkt Light','Produkt Light Italic','Produkt Medium','Produkt Medium Italic',\n\t\t\t'Proxima Nova','Proxima Nova Bold','Proxima Nova Bold It','Proxima Nova Extrabold','Proxima Nova Extrabold It','Proxima Nova It','Proxima Nova Light','Proxima Nova Light It','Proxima Nova Medium','Proxima Nova Medium It','Proxima Nova Semibold','Proxima Nova Semibold It',\n\t\t\t'Publico Headline Black','Publico Headline Black Italic','Publico Headline Bold','Publico Headline Bold Italic','Publico Headline Italic','Publico Headline Roman','Publico Text Bold','Publico Text Bold Italic','Publico Text Italic','Publico Text Roman','Publico Text Semibold','Publico Text Semibold Italic',\n\t\t\t'Quotes Caps','Quotes Script',\n\t\t\t'Raanana','Raanana Bold',\n\t\t\t'Rockwell','Rockwell Bold','Rockwell Bold Italic','Rockwell Italic',\n\t\t\t'STFangsong',\n\t\t\t'STHeiti',\n\t\t\t'STIX Two Math','STIX Two Text','STIX Two Text Bold','STIX Two Text Bold Italic','STIX Two Text Italic','STIX Two Text Medium Italic','STIX Two Text Medium','STIX Two Text SemiBold','STIX Two Text SemiBold Italic',\n\t\t\t'STKaiti',\n\t\t\t'STSong',\n\t\t\t'STXihei',\n\t\t\t'Sama Devanagari','Sama Devanagari Bold','Sama Devanagari Book','Sama Devanagari ExtraBold','Sama Devanagari Medium','Sama Devanagari SemiBold','Sama Gujarati','Sama Gujarati Bold','Sama Gujarati Book','Sama Gujarati ExtraBold','Sama Gujarati Medium','Sama Gujarati SemiBold','Sama Gurmukhi','Sama Gurmukhi Bold','Sama Gurmukhi Book','Sama Gurmukhi ExtraBold','Sama Gurmukhi Medium','Sama Gurmukhi SemiBold','Sama Kannada','Sama Kannada Bold','Sama Kannada Book','Sama Kannada ExtraBold','Sama Kannada Medium','Sama Kannada SemiBold','Sama Malayalam','Sama Malayalam Bold','Sama Malayalam Book','Sama Malayalam ExtraBold','Sama Malayalam Medium','Sama Malayalam SemiBold','Sama Tamil','Sama Tamil Bold','Sama Tamil Book','Sama Tamil ExtraBold','Sama Tamil Medium','Sama Tamil SemiBold',\n\t\t\t'Sana',\n\t\t\t'Sarabun','Sarabun Bold','Sarabun Bold Italic','Sarabun ExtraBold','Sarabun ExtraBold Italic','Sarabun ExtraLight','Sarabun ExtraLight Italic','Sarabun Italic','Sarabun Light','Sarabun Light Italic','Sarabun Medium','Sarabun Medium Italic','Sarabun SemiBold','Sarabun SemiBold Italic','Sarabun Thin','Sarabun Thin Italic',\n\t\t\t'Sathu',\n\t\t\t'Sauber Script',\n\t\t\t'Savoye LET',\n\t\t\t'Shobhika','Shobhika Bold',\n\t\t\t'Shree Devanagari 714','Shree Devanagari 714 Bold','Shree Devanagari 714 Bold Italic','Shree Devanagari 714 Italic',\n\t\t\t'SignPainter','SignPainter Semibold',\n\t\t\t'Silom',\n\t\t\t'SimSong','SimSong Bold',\n\t\t\t'Sinhala MN','Sinhala MN Bold','Sinhala Sangam MN','Sinhala Sangam MN Bold',\n\t\t\t'Skia','Skia Black','Skia Black Condensed','Skia Black Extended','Skia Bold','Skia Condensed','Skia Extended','Skia Light','Skia Light Condensed','Skia Light Extended',\n\t\t\t'Snell Roundhand','Snell Roundhand Black','Snell Roundhand Bold',\n\t\t\t'Songti SC','Songti SC Black','Songti SC Bold','Songti SC Light',\n\t\t\t'Songti TC','Songti TC Bold','Songti TC Light',\n\t\t\t'Spot Mono','Spot Mono Bold','Spot Mono Medium',\n\t\t\t'Srisakdi','Srisakdi Bold',\n\t\t\t'Sukhumvit Set Bold','Sukhumvit Set Light','Sukhumvit Set Medium','Sukhumvit Set Semi Bold','Sukhumvit Set Text','Sukhumvit Set Thin',\n\t\t\t'Symbol',\n\t\t\t'Tahoma','Tahoma Bold',\n\t\t\t'Tamil MN','Tamil MN Bold','Tamil Sangam MN','Tamil Sangam MN Black','Tamil Sangam MN Bold','Tamil Sangam MN Demibold','Tamil Sangam MN Light','Tamil Sangam MN Medium',\n\t\t\t'Telugu MN','Telugu MN Bold','Telugu Sangam MN','Telugu Sangam MN Bold',\n\t\t\t'Thonburi','Thonburi Bold','Thonburi Light',\n\t\t\t'Times New Roman','Times New Roman Bold','Times New Roman Bold Italic','Times New Roman Italic',\n\t\t\t'Tiro Bangla','Tiro Bangla Italic','Tiro Devanagari Hindi','Tiro Devanagari Hindi Italic','Tiro Devanagari Marathi','Tiro Devanagari Marathi Italic','Tiro Devanagari Sanskrit','Tiro Devanagari Sanskrit Italic','Tiro Gurmukhi','Tiro Gurmukhi Italic','Tiro Kannada','Tiro Kannada Italic','Tiro Tamil','Tiro Tamil Italic','Tiro Telugu','Tiro Telugu Italic',\n\t\t\t'Toppan Bunkyu Gothic','Toppan Bunkyu Gothic Demibold','Toppan Bunkyu Midashi Gothic Extrabold','Toppan Bunkyu Midashi Mincho Extrabold','Toppan Bunkyu Mincho',\n\t\t\t'Trattatello',\n\t\t\t'Trebuchet MS','Trebuchet MS Bold','Trebuchet MS Bold Italic','Trebuchet MS Italic',\n\t\t\t'Tsukushi A Round Gothic','Tsukushi A Round Gothic Bold','Tsukushi B Round Gothic','Tsukushi B Round Gothic Bold',\n\t\t\t'Verdana','Verdana Bold','Verdana Bold Italic','Verdana Italic',\n\t\t\t'Waseem','Waseem Light',\n\t\t\t'Wawati SC','Wawati TC',\n\t\t\t'Webdings',\n\t\t\t'Weibei SC Bold','Weibei TC Bold',\n\t\t\t'Wingdings','Wingdings 2','Wingdings 3',\n\t\t\t'Xingkai SC Bold','Xingkai SC Light','Xingkai TC Bold','Xingkai TC Light',\n\t\t\t'Yuanti SC','Yuanti SC Bold','Yuanti SC Light','Yuanti TC','Yuanti TC Bold','Yuanti TC Light',\n\t\t\t'YuGothic Bold','YuGothic Medium',\n\t\t\t'YuKyokasho Bold','YuKyokasho Medium','YuKyokasho Yoko Bold','YuKyokasho Yoko Medium',\n\t\t\t'YuMincho +36p Kana Demibold','YuMincho +36p Kana Extrabold','YuMincho +36p Kana Medium','YuMincho Demibold','YuMincho Extrabold','YuMincho Medium',\n\t\t\t'Yuppy SC','Yuppy TC',\n\t\t\t'Zapf Dingbats',\n\t\t\t'Zapfino',\n\t\t]\n\t},\n}\n\nrun_once()\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/fontsystem.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=400\">\r\n\t<title>system fonts</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 380px; max-width: 480px;}\r\n\t\t#tb12 td {padding-right: 10px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"offscreen\">\r\n\t\t<div id=\"sysFont\">hello world</div>\r\n\t</div>\r\n\t<div class=\"hidden\">\r\n\t\t<!--widget fonts-->\r\n\t\t<input type=\"reset\" id=\"wgtbutton\">\r\n\t\t<input type=\"checkbox\" id=\"wgtcheckbox\">\r\n\t\t<input type=\"color\" id=\"wgtcolor\">\r\n\t\t<input type=\"date\" id=\"wgtdate\">\r\n\t\t<input type=\"datetime-local\" id=\"wgtdatetime-local\">\r\n\t\t<input type=\"email\" id=\"wgtemail\">\r\n\t\t<input type=\"file\" id=\"wgtfile\">\r\n\t\t<input type=\"hidden\" id=\"wgthidden\">\r\n\t\t<input type=\"image\" id=\"wgtimage\">\r\n\t\t<input type=\"month\" id=\"wgtmonth\">\r\n\t\t<input type=\"number\" id=\"wgtnumber\">\r\n\t\t<input type=\"password\" id=\"wgtpassword\">\r\n\t\t<input type=\"radio\" id=\"wgtradio\">\r\n\t\t<input type=\"range\" id=\"wgtrange\">\r\n\t\t<input type=\"reset\" id=\"wgtreset\">\r\n\t\t<input type=\"search\" id=\"wgtsearch\">\r\n\t\t<select id=\"wgtselect\"><option></option></select>\r\n\t\t<input type=\"submit\" id=\"wgtsubmit\">\r\n\t\t<input type=\"tel\" id=\"wgttel\">\r\n\t\t<input type=\"text\" id=\"wgttext\">\r\n\t\t<textarea id=\"wgttextarea\"></textarea>\r\n\t\t<input type=\"time\" id=\"wgttime\">\r\n\t\t<input type=\"url\" id=\"wgturl\">\r\n\t\t<input type=\"week\" id=\"wgtweek\">\r\n\t</div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"1%\"><col width=\"99%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">system fonts\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"perf\" id=\"language\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td></td><td class=\"mono spaces\" id=\"default\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr><td></td><td class=\"mono spaces\" id=\"moz\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr><td></td><td class=\"mono spaces\" id=\"system\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr><td></td><td class=\"mono spaces\" id=\"widgets\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\r\n\t</table>\r\n\t<br>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<col width=\"40%\"><col width=\"60%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">check getComputedStyle</div>\r\n\t\t</th></tr></thead>\r\n\t\t<!-- always get span width, div height -->\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh0\" style=\"font:caption\"><span id=\"ctrlw0\">caption</span></div></td>\r\n\t\t\t<td><div id=\"testh0\"><span id=\"testw0\">caption</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure0\"></td><td id=\"tmeasure0\"></td></tr>\r\n\t\t<tr><td id=\"hash0\"></td><td id=\"data0\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh1\" style=\"font:icon\"><span id=\"ctrlw1\">icon</span></div></td>\r\n\t\t\t<td><div id=\"testh1\"><span id=\"testw1\">icon</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure1\"></td><td id=\"tmeasure1\"></td></tr>\r\n\t\t<tr><td id=\"hash1\"></td><td id=\"data1\"></td></tr>\r\n\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh2\" style=\"font:menu\"><span id=\"ctrlw2\">menu</span></div></td>\r\n\t\t\t<td><div id=\"testh2\"><span id=\"testw2\">menu</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure2\"></td><td id=\"tmeasure2\"></td></tr>\r\n\t\t<tr><td id=\"hash2\"></td><td id=\"data2\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh3\" style=\"font:message-box\"><span id=\"ctrlw3\">message-box</span></div></td>\r\n\t\t\t<td><div id=\"testh3\"><span id=\"testw3\">message-box</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure3\"></td><td id=\"tmeasure3\"></td></tr>\r\n\t\t<tr><td id=\"hash3\"></td><td id=\"data3\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh4\" style=\"font:small-caption\"><span id=\"ctrlw4\">small-caption</span></div></td>\r\n\t\t\t<td><div id=\"testh4\"><span id=\"testw4\">small-caption</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure4\"></td><td id=\"tmeasure4\"></td></tr>\r\n\t\t<tr><td id=\"hash4\"></td><td id=\"data4\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh5\" style=\"font:status-bar\"><span id=\"ctrlw5\">status-bar</span></div></td>\r\n\t\t\t<td><div id=\"testh5\"><span id=\"testw5\">status-bar</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure5\"></td><td id=\"tmeasure5\"></td></tr>\r\n\t\t<tr><td id=\"hash5\"></td><td id=\"data5\"></td></tr>\r\n\r\n\t\t<!-- moz -->\r\n\t\t<tr><td colspan=\"2\" class=\"center\"><br>--- moz ---</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh16\" style=\"font:-moz-bullet-font\"><span id=\"ctrlw16\">-moz-bullet-font</span></div></td>\r\n\t\t\t<td><div id=\"testh16\"><span id=\"testw16\">-moz-bullet-font</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure16\"></td><td id=\"tmeasure16\"></td></tr>\r\n\t\t<tr><td id=\"hash16\"></td><td id=\"data16\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh13\" style=\"font:-moz-button\"><span id=\"ctrlw13\">-moz-button</span></div></td>\r\n\t\t\t<td><div id=\"testh13\"><span id=\"testw13\">-moz-button</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure13\"></td><td id=\"tmeasure13\"></td></tr>\r\n\t\t<tr><td id=\"hash13\"></td><td id=\"data13\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh18\" style=\"font:-moz-button-group\"><span id=\"ctrlw18\">-moz-button-group</span></div></td>\r\n\t\t\t<td><div id=\"testh18\"><span id=\"testw18\">-moz-button-group</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure18\"></td><td id=\"tmeasure18\"></td></tr>\r\n\t\t<tr><td id=\"hash18\"></td><td id=\"data18\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh6\" style=\"font:-moz-desktop\"><span id=\"ctrlw6\">-moz-desktop</span></div></td>\r\n\t\t\t<td><div id=\"testh6\"><span id=\"testw6\">-moz-desktop</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure6\"></td><td id=\"tmeasure6\"></td></tr>\r\n\t\t<tr><td id=\"hash6\"></td><td id=\"data6\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh12\" style=\"font:-moz-dialog\"><span id=\"ctrlw12\">-moz-dialog</span></div></td>\r\n\t\t\t<td><div id=\"testh12\"><span id=\"testw12\">-moz-dialog</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure12\"></td><td id=\"tmeasure12\"></td></tr>\r\n\t\t<tr><td id=\"hash12\"></td><td id=\"data12\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh7\" style=\"font:-moz-document\"><span id=\"ctrlw7\">-moz-document</span></div></td>\r\n\t\t\t<td><div id=\"testh7\"><span id=\"testw7\">-moz-document</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure7\"></td><td id=\"tmeasure7\"></td></tr>\r\n\t\t<tr><td id=\"hash7\"></td><td id=\"data7\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh14\" style=\"font:-moz-field\"><span id=\"ctrlw14\">-moz-field</span></div></td>\r\n\t\t\t<td><div id=\"testh14\"><span id=\"testw14\">-moz-field</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure14\"></td><td id=\"tmeasure14\"></td></tr>\r\n\t\t<tr><td id=\"hash14\"></td><td id=\"data14\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh8\" style=\"font:-moz-info\"><span id=\"ctrlw8\">-moz-info</span></div></td>\r\n\t\t\t<td><div id=\"testh8\"><span id=\"testw8\">-moz-info</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure8\"></td><td id=\"tmeasure8\"></td></tr>\r\n\t\t<tr><td id=\"hash8\"></td><td id=\"data8\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh15\" style=\"font:-moz-list\"><span id=\"ctrlw15\">-moz-list</span></div></td>\r\n\t\t\t<td><div id=\"testh15\"><span id=\"testw15\">-moz-list</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure15\"></td><td id=\"tmeasure15\"></td></tr>\r\n\t\t<tr><td id=\"hash15\"></td><td id=\"data15\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh17\" style=\"font:-moz-message-bar\"><span id=\"ctrlw17\">-moz-message-bar</span></div></td>\r\n\t\t\t<td><div id=\"testh17\"><span id=\"testw17\">-moz-message-bar</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure17\"></td><td id=\"tmeasure17\"></td></tr>\r\n\t\t<tr><td id=\"hash17\"></td><td id=\"data17\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh9\" style=\"font:-moz-pull-down-menu\"><span id=\"ctrlw9\">-moz-pull-down-menu</span></div></td>\r\n\t\t\t<td><div id=\"testh9\"><span id=\"testw9\">-moz-pull-down-menu</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure9\"></td><td id=\"tmeasure9\"></td></tr>\r\n\t\t<tr><td id=\"hash9\"></td><td id=\"data9\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh10\" style=\"font:-moz-window\"><span id=\"ctrlw10\">-moz-window</span></div></td>\r\n\t\t\t<td><div id=\"testh10\"><span id=\"testw10\">-moz-window</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure10\"></td><td id=\"tmeasure10\"></td></tr>\r\n\t\t<tr><td id=\"hash10\"></td><td id=\"data10\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td><div id=\"ctrlh11\" style=\"font:-moz-workspace\"><span id=\"ctrlw11\">-moz-workspace</span></div></td>\r\n\t\t\t<td><div id=\"testh11\"><span id=\"testw11\">-moz-workspace</span></div></td>\r\n\t\t</tr>\r\n\t\t<tr><td id=\"cmeasure11\"></td><td id=\"tmeasure11\"></td></tr>\r\n\t\t<tr><td id=\"hash11\"></td><td id=\"data11\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\" class=\"center\"><br>--- system-ui sample text ---</td></tr>\r\n\t\t<tr><td colspan=\"2\" style=\"text-align: left;\">\r\n\t\t\t<div class=\"no_color index\" style=\"font-family:system-ui\">Vestibulum ante ipsum primis in faucibus\r\n\t\t\t\torci tor browsera ultrima sposuere onionii curae; Nam maximus thorin erat\r\n\t\t\t\tet ante digitus printus, ac pier justo tincidunt.</div>\r\n\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet oDefault = {}, oData = {}\r\n\r\n\r\nfunction check_computed() {\r\n\tfor (let i=0; i < 19; i++) {\r\n\t\tlet ctrlW = document.getElementById(\"ctrlw\"+i),\r\n\t\t\tctrlH = document.getElementById(\"ctrlh\"+i),\r\n\t\t\ttestW = document.getElementById(\"testw\"+i),\r\n\t\t\ttestH = document.getElementById(\"testh\"+i),\r\n\t\t\thash = document.getElementById(\"hash\"+i),\r\n\t\t\tdata = document.getElementById(\"data\"+i)\r\n\r\n\t\ttry {\r\n\t\t\tlet propList = ['font-family', 'font-size', 'font-style', 'font-weight',]\r\n\t\t\tlet aData = []\r\n\t\t\tfor (const prop of propList) {\r\n\t\t\t\tlet value = getComputedStyle(ctrlW)[prop]\r\n\t\t\t\ttestW.style[prop] = value\r\n\t\t\t\taData.push(value)\r\n\t\t\t}\r\n\t\t\t// always get span width, div height\r\n\t\t\t\t// can't get div height to always match, so go with span height: seems to work\r\n\t\t\t\t// also can be slightly out in decimal precision, e.g. zoomed\r\n\t\t\t\t// lets try toFixed(4)\r\n\t\t\t// measure control\r\n\t\t\tlet cSpan = ctrlW.getBoundingClientRect()\r\n\t\t\tlet cDiv = ctrlH.getBoundingClientRect()\r\n\t\t\tlet cMeasure = (cSpan.width).toFixed(4) +\" x \"+ (cSpan.height).toFixed(4)\r\n\t\t\tdocument.getElementById(\"cmeasure\"+i).innerHTML = cMeasure\r\n\r\n\t\t\t// measure test\r\n\t\t\tlet tSpan = testW.getBoundingClientRect()\r\n\t\t\tlet tDiv = testH.getBoundingClientRect()\r\n\t\t\tlet tMeasure = (tSpan.width).toFixed(4) +\" x \"+ (tSpan.height).toFixed(4)\r\n\t\t\tdocument.getElementById(\"tmeasure\"+i).innerHTML = tMeasure\r\n\t\t\t\t// and compare\r\n\t\t\t\t+\" \"+ (cMeasure == tMeasure ? green_tick : red_cross) \r\n\r\n\t\t\t// data\r\n\t\t\thash.innerHTML = s14 + mini(aData.join()) + sc\r\n\t\t\tdata.innerHTML = aData.join(\", \")\r\n\r\n\t\t} catch(e) {\r\n\t\t\tconsole.log(e.name, e.message)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction get_moz() {\r\n\tconst METRIC = \"moz\"\r\n\ttry {\r\n\t// 1802957: FF109+: -moz no longer applied but keep for regression testing\r\n\t\t// add bogus '-default-font' to check they are falling back to actual default\r\n\t\tlet aList = [\r\n\t\t\t'-default-font','-moz-bullet-font','-moz-button','-moz-button-group','-moz-desktop','-moz-dialog','-moz-document',\r\n\t\t\t'-moz-field','-moz-info','-moz-list','-moz-message-bar','-moz-pull-down-menu','-moz-window','-moz-workspace',\r\n\t\t]\r\n\t\tlet aProps = ['font-size','font-style','font-weight','font-family']\r\n\t\tlet oRes = {}\r\n\t\tlet el = dom.sysFont\r\n\t\taList.forEach(function(name){\r\n\t\t\tlet aKeys = []\r\n\t\t\tel.style.font = \"\" // always clear in case a font is invalid/deprecated\r\n\t\t\tel.style.font = name\r\n\t\t\tfor (const k of aProps) {aKeys.push(getComputedStyle(el)[k])}\r\n\t\t\tlet key = aKeys.join(\" \")\r\n\t\t\tif (oRes[key] == undefined) {oRes[key] = [name]} else {oRes[key].push(name)}\r\n\t\t\t// defaults\r\n\t\t\ttry {\r\n\t\t\t\tlet props = getDefaultComputedStyle(el)\r\n\t\t\t\tlet tmphash = mini(props)\r\n\t\t\t\tif (oDefault[tmphash] == undefined) {\r\n\t\t\t\t\toDefault[tmphash] = {\"hash\": tmphash, \"items\": [name], \"metrics\": props}\r\n\t\t\t\t} else {\r\n\t\t\t\t\toDefault[tmphash].items.push(name)\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {}\r\n\t\t})\r\n\t\t// sort\r\n\t\tlet newobj = {}\r\n\t\tfor (const k of Object.keys(oRes).sort()) {newobj[k] = oRes[k]}\r\n\t\tlet hash = mini(newobj)\r\n\t\t// display\r\n\t\tdom.moz.innerHTML = s12 +\"MOZ: \"+ sc\r\n\t\t\t+\"<br>\"+ s14 + hash + sc +\"<br>\" + json_highlight(newobj)\r\n\t\treturn\r\n\t} catch(e) {\r\n\t\tdom.moz.innerHTML = s12 +\"MOZ: \"+ sc + e.name +\": \" + e.message\r\n\t\treturn\r\n\t}\r\n}\r\n\r\nfunction get_system() {\r\n\tconst METRIC = \"system\"\r\n\ttry {\r\n\t// 1802957: FF109+: -moz no longer applied but keep for regression testing\r\n\t\t// add bogus '-default-font' to check they are falling back to actual default\r\n\t\tlet aList = ['caption','icon','menu','message-box','small-caption','status-bar']\r\n\t\tlet aProps = ['font-size','font-style','font-weight','font-family']\r\n\t\tlet oRes = {}\r\n\t\tlet el = dom.sysFont\r\n\t\taList.forEach(function(name){\r\n\t\t\tlet aKeys = []\r\n\t\t\tel.style.font = \"\" // always clear in case a font is invalid/deprecated\r\n\t\t\tel.style.font = name\r\n\t\t\tfor (const k of aProps) {aKeys.push(getComputedStyle(el)[k])}\r\n\t\t\tlet key = aKeys.join(\" \")\r\n\t\t\tif (oRes[key] == undefined) {oRes[key] = [name]} else {oRes[key].push(name)}\r\n\t\t\t// defaults\r\n\t\t\ttry {\r\n\t\t\t\tlet props = getDefaultComputedStyle(el)\r\n\t\t\t\tlet tmphash = mini(props)\r\n\t\t\t\tif (oDefault[tmphash] == undefined) {\r\n\t\t\t\t\toDefault[tmphash] = {\"hash\": tmphash, \"items\": [name], \"metrics\": props}\r\n\t\t\t\t} else {\r\n\t\t\t\t\toDefault[tmphash].items.push(name)\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {}\r\n\t\t})\r\n\t\t// sort\r\n\t\tlet newobj = {}\r\n\t\tfor (const k of Object.keys(oRes).sort()) {newobj[k] = oRes[k]}\r\n\t\tlet hash = mini(newobj)\r\n\t\t// display\r\n\t\tdom.system.innerHTML = s12 +\"SYSTEM: \"+ sc\r\n\t\t\t+\"<br>\"+ s14 + hash + sc +\"<br>\" + json_highlight(newobj)\r\n\t\treturn\r\n\t} catch(e) {\r\n\t\tdom.system.innerHTML = s12 +\"SYSTEM: \"+ sc + e.name +\": \" + e.message\r\n\t\treturn\r\n\t}\r\n}\r\n\r\nfunction get_widget() {\r\n\tconst METRIC = \"widget\"\r\n\ttry {\r\n\t\tlet aList = [\r\n\t\t\t'button','checkbox','color','date','datetime-local','email','file','hidden','image','month',\r\n\t\t\t'number','password','radio','range','reset','search','select','submit','tel','text','textarea','time','url','week',\r\n\t\t]\r\n\t\tlet oRes = {}\r\n\t\taList.forEach(function(name) {\r\n\t\t\tlet el = dom[\"wgt\"+ name]\r\n\t\t\tlet key = getComputedStyle(el).getPropertyValue(\"font-family\")\r\n\t\t\t\t+\" \"+ getComputedStyle(el).getPropertyValue(\"font-size\")\r\n\t\t\tif (oRes[key] == undefined) {oRes[key] = [name]} else {oRes[key].push(name)}\r\n\t\t\t// defaults\r\n\t\t\ttry {\r\n\t\t\t\tlet props = getDefaultComputedStyle(el)\r\n\t\t\t\tlet tmphash = mini(props)\r\n\t\t\t\tif (oDefault[tmphash] == undefined) {\r\n\t\t\t\t\toDefault[tmphash] = {\"hash\": tmphash, \"items\": [name], \"metrics\": props}\r\n\t\t\t\t} else {\r\n\t\t\t\t\toDefault[tmphash].items.push(name)\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {}\r\n\t\t})\r\n\t\t// sort\r\n\t\tlet newobj = {}\r\n\t\tfor (const k of Object.keys(oRes).sort()) {newobj[k] = oRes[k]}\r\n\t\tlet hash = mini(newobj)\r\n\t\t// display\r\n\t\tdom.widgets.innerHTML = s12 +\"WIDGETS: \"+ sc\r\n\t\t\t+\"<br>\"+ s14 + hash + sc +\"<br>\" + json_highlight(newobj)\r\n\t\treturn\r\n\t} catch(e) {\r\n\t\tdom.widgets.innerHTML = s12 +\"WIDGETS: \"+ sc + e.name +\": \" + e.message\r\n\t\treturn\r\n\t}\r\n}\r\n\r\ntry {\r\n\tdom.language.innerHTML = navigator.language\r\n} catch(e) {\r\n\tconsole.log(e)\r\n}\r\n\r\nfunction logConsole() {\r\n\tconsole.log(oData)\r\n}\r\n\r\nPromise.all([\r\n\tget_moz(),\r\n\tget_system(),\r\n\tget_widget(),\r\n]).then(function(){\r\n\r\n\tconst METRIC = \"getDefaultComputedStyle\"\r\n\tif (Object.keys(oDefault).length) {\r\n\t\tlet tmpObj = {}\r\n\t\tfor (const k of Object.keys(oDefault).sort()) {\r\n\t\t\tlet group = oDefault[k].items.sort()\r\n\t\t\toData[k] = {\"group\": group, \"metrics\": oDefault[k][\"metrics\"]}\r\n\t\t\ttmpObj[k] = group.join(\", \")\r\n\t\t}\r\n\t\tlet defhash = mini(oData)\r\n\t\tlet btn = \" <span class='btn12 btnc' onclick='logConsole()'>[details]</span>\"\r\n\t\tdom.default.innerHTML = s12 + METRIC +\": \"+ sc\r\n\t\t\t\t+\"<br><br>\"+ s14 + defhash + sc + btn +\"<br>\" + json_highlight(tmpObj)\r\n\t} else {\r\n\t\tdom.default.innerHTML = s12 + METRIC +\": \"+ sc +\"<br>n/a\"\r\n\t}\r\n\r\n\tcheck_computed()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/fontview.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>script view</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\toptgroup {font-style:normal;}\r\n\t\t.viewchars {\r\n\t\t\tdirection:ltr;\r\n\t\t\tunicode-bidi:bidi-override;\r\n\t\t\tfont-size: 16px;\r\n\t\t}\r\n\t\t.btn-right {float: right; position: relative; right: -9px; top: 3px; text-align: right;}\r\n\t\t.btn-left {float: left; position: relative; left: -9px; top: 3px;}\r\n\t\t.type-right {float: right; position: relative; right: 0px; top: -12px}\r\n\r\n\t\t/* make sure element is super wide so text is always on one line */\r\n\t\t.measure {\r\n\t\t\tposition: absolute;\r\n\t\t\tleft: -5000px;\r\n\t\t\ttop: 0px;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div id=\"dMeasure\" class=\"measure\"><span id=\"sMeasure\"></span></div>\r\n\t<div id=\"fallback\" class=\"offscreen\"></div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#fonts\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb12\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">script/language view\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"perf\" id=\"lang\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr>\r\n\t\t\t<td style=\"text-align: center; vertical-align: bottom;\">\r\n\t\t\t\t\t<form>\r\n\t\t\t\t\t\t<!--<label for=\"scripts\"> &nbsp; script:</label>-->\r\n\t\t\t\t\t\t<select name=\"scripts\" id=\"scripts\" onChange=\"run(`scripts`)\" onClick=\"run(`scripts`)\"><option></option></select>\r\n\t\t\t\t\t\t<span class=\"btn btn12 btn-left\" onClick=\"cycle('back','scripts')\">[ &nbsp; PREV &nbsp; ]</span>\r\n\t\t\t\t\t\t<span class=\"btn btn12 btn-right\" onClick=\"cycle('forward','scripts')\">[ &nbsp; NEXT &nbsp; ]</span>\r\n\t\t\t\t\t</form><br>\r\n\t\t\t\t\t<form>\r\n\t\t\t\t\t\t<!--<label for=\"scripts\">language:</label>-->\r\n\t\t\t\t\t\t<select name=\"languages\" id=\"languages\" onChange=\"run(`languages`)\" onClick=\"run(`languages`)\"><option></option></select>\r\n\t\t\t\t\t\t<span class=\"btn btn12 btn-left\" onClick=\"cycle('back','languages')\">[ &nbsp; PREV &nbsp; ]</span>\r\n\t\t\t\t\t\t<span class=\"btn btn12 btn-right\" onClick=\"cycle('forward','languages')\">[ &nbsp; NEXT &nbsp; ]</span>\r\n\t\t\t\t\t</form><br>\r\n\t\t\t\t\t<span class=\"no_color mono\">\r\n\t\t\t\t\t\t<code>arrow</code> keys: <code>up</code>/<code>down</code> switch type | <code>left</code> prev | <code>right</code> next\r\n\t\t\t\t\t</span>\r\n\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t\t<tr><td><hr></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td>\r\n\t\t\t\t<div class=\"s14 mono\" align=\"left\" id=\"divInfo\"></div>\r\n\t\t\t\t<div class=\"s14 type-right mono spaces\" id=\"divType\"></div>\r\n\t\t\t\t<br>\r\n\t\t\t\t<div class=\"no_color\" align=\"left\">\r\n\t\t\t\t\t<div class=\"s12 mono spaces\">monospace <span class=\"no_color\" id=\"cmono\"></span></div><br>\r\n\t\t\t\t\t<div class=\"spaces monospace viewchars\" id=\"divMono\">hello world</div><br>\r\n\t\t\t\t\t<div class=\"s12 mono spaces\">sans-serif <span class=\"no_color\" id=\"csans\"></span></div><br>\r\n\t\t\t\t\t<div class=\"spaces sans-serif viewchars\" id=\"divSans\">hello world</div><br>\r\n\t\t\t\t\t<div class=\"s12 mono spaces\">serif <span class=\"no_color\" id=\"cseri\"></span></div><br>\r\n\t\t\t\t\t<div class=\"spaces serif viewchars\" id=\"divSerif\">hello world</div><br>\r\n\t\t\t\t</div>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://en.wikipedia.org/wiki/List_of_Unicode_characters\r\n// https://www.cogsci.ed.ac.uk/~richard/unicode-sample-3-2.html\r\n\r\nlet oSentence = {\r\n\t// using: https://github.com/mozilla-l10n/focusios-l10n\r\n\r\n\t// TB supported\r\n\t'ar (arabic)': 'امسح تأريخ جلسة التصفح كله و كلمات السر و الكعكات في أي وقت بنقرة واحدة.',\r\n\t'be (belarusian)': 'Сцірайце ўсю гісторыю аглядання, паролі і кукі калі захочаце ў адзін націск.',\r\n\t'bg (bulgarian)': 'Изчистване по всяко време с едно докосване на цялата сесия на разглеждане: история, пароли и бисквитки.',\r\n\t'ca (catalan)': 'Esborreu l\\'historial de navegació, les contrasenyes i les galetes quan vulgueu amb un sol toc.',\r\n\t'cs (czech)': 'Vyčistěte kdykoli celou vaši historii prohlížení, hesla a soubory cookie jedním klepnutím.',\r\n\t'da (danish)': 'Du kan når som helst slette hele din browserhistorik, dine gemte adgangskoder og dine cookies med et enkelt tryk.',\r\n\t'de (german)': 'Löschen Sie mit einer einzigen Berührung die gesamte Chronik, Passwörter und Cookies Ihrer Sitzung.',\r\n\t'el (greek)': 'Απαλοιφή ιστορικού περιήγησης, κωδικών πρόσβασης, cookies ανά πάσα στιγμή, με ένα πάτημα.',\r\n\t'en (english)': 'Clear your entire browsing session history, passwords, cookies anytime with a single tap.',\r\n\t'es-ES (spanish spain)': 'Limpia todo el historial de navegación de tu sesión, contraseñas y cookies en cualquier momento con tan solo un clic.',\r\n\t'fa (persian)': 'تمام تاریخچه،‌کلمه عبور،‌و کوکی های جلسه مرورگر خود را در هر زمانی تنها با یک لمس پاک کنید.',\r\n\t'fi (finnish)': 'Tyhjennä koko selaushistoriasi, salasanat ja evästeet milloin tahansa yhdellä napautuksella.',\r\n\t'fr (french)': 'Supprimez votre historique de navigation, vos mots de passe, vos cookies et plus encore, d’une simple pression.',\r\n\t'ga (irish)': 'Scrios fianáin chomh maith le do stair chuardaigh agus stair bhrabhsála.', // different sentence\r\n\t'he (hebrew)': 'ניתן בכל עת לנקות את כל היסטורית הגלישה שלך, הסיסמאות והעוגיות בהקשה אחת.',\r\n\t'hu (hungarian)': 'Törölje az összes böngészési előzményét, jelszavát, és sütijét bármikor, egyetlen koppintással.',\r\n\t'id (indonesian)': 'Bersihkan semua riwayat sesi penjelajahan, sandi, dan kuki Anda kapan pun dalam sekali ketuk saja.',\r\n\t'is (icelandic)': 'Hreinsa feril, lykilorð, vefkökur hvenær sem er með einni snertingu.',\r\n\t'it (italian)': 'Cancella l’intera cronologia di navigazione, le password e i cookie con un semplice tocco, in qualunque momento.',\r\n\t'ja (japanese)': 'ブラウジングセッション履歴やパスワード、Cookie を、いつでもワンタップですべて消去できます。',\r\n\t'ka (georgian)': 'გაასუფთავეთ მონახულებული გვერდები, პაროლები და ა.შ. ნებისმიერ დროს, მხოლოდ ერთი შეხებით.',\r\n\t'ko (korean)': '브라우징 세션 기록이나 비밀번호, 쿠키를 언제나 탭 한번으로 삭제합니다.',\r\n\t'lt (lithuanian)': 'Bet kuriuo metu išvalykite visą savo naršymo žurnalą, slaptažodžius ir slapukus vienu bakstelėjimu.',\r\n\t'mk (macedonian)': 'Сакам да разменам пари/натнички чекови.', // I want to exchange some money/travellers cheques.\r\n\t'ms (malay)': 'Buang seluruh sejarah pelayaran, kata laluan dan kuki dengan sekali tekan sahaja.',\r\n\t'my (burmese)': 'ရှာဖွေမှုမှတ်တမ်း၊ စကားဝှက်များနှင့် အခြား စသည်တို့ကို တစ်ခါတို့ထိရုံဖြင့် ဖျက်နိုင်သည်။',\r\n\t'nl (dutch)': 'Wis uw volledige surfgeschiedenis, wachtwoorden en cookies wanneer u wilt met één tik.',\r\n\t'nn (norwegian nynorsk)': 'Slett hele nettleserhistorikken din, passord, infokapsler når som helst med ett trykk.', // tb is actually nb-no = norwegian bokmål\r\n\t'pl (polish)': 'Usuwaj całą historię przeglądania, hasła i ciasteczka jednym stuknięciem.',\r\n\t'pt-BR (portuguese brazil)': 'Limpe todo o histórico, senhas e cookies da sessão de navegação quando quiser, com um simples toque.',\r\n\t'pt-PT (portuguese portual)': 'Limpe o seu histórico de sessão de navegação inteiro, palavras-passe, cookies a qualquer altura com um simples toque.',\r\n\t'ro (romanian)': 'Șterge întregul istoric al sesiunii, parolele și cookie-urile oricând, cu o singură atingere.',\r\n\t'ru (russian)': 'В любой момент стирайте одним нажатием всю историю просмотров, пароли и куки.',\r\n\t'sq (albanian)': 'Fshini kurdo, me një prekje të vetme, historikun e shfletimit për sesionin, fjalëkalimet, cookie-t.',\r\n\t'sv (swedish)': 'Rensa hela din webbhistorik, lösenord, kakor när som helst med ett enda tryck.',\r\n\t'th (thai)': 'ล้างประวัติ, รหัสผ่าน และคุกกี้ในวาระการท่องเว็บทั้งหมดของคุณได้ทุกเวลาด้วยการแตะครั้งเดียว',\r\n\t'tr (turkish)': 'İstediğiniz zaman tek bir dokunuşla gezinti geçmişinizi, parolaları ve çerezleri temizleyebilirsiniz.',\r\n\t'uk (ukrainian)': 'Будь-коли стирайте всю історію перегляду, паролі та куки одним дотиком.',\r\n\t'vi (vietnamese)': 'Xóa toàn bộ lịch sử duyệt web, mật khẩu, cookie bất kỳ lúc nào chỉ với một cú chạm.',\r\n\t'zh-cn (chinese china)': '随时一键清除您的所有浏览记录、密码及 Cookie。',\r\n\t'zh-tw (chinese taiwan)': '只要輕鬆一點，就能清除這次上網的所有瀏覽紀錄、儲存密碼、Cookie 等資料。',\r\n\r\n\t// other\r\n\t'am (amharic)': 'በአንድ ጊዜ መታ በማድረግ ሁሉንም የአሰሳ ክፍለ ጊዜ ታሪክዎን ፣ የይለፍ ቃሎችን ፣ ኩኪዎችን በማንኛውም ጊዜ ያጽዱ።',\r\n\t'an (aragonese)': 'Borra l\\'historial completo d\\'a sesión de navegación, claus, cookies, en qualsequier momento con un simple toque.',\r\n\t'ast (asturian)': 'Llimpia cuando quieras y con un calcu les cookies, l\\'historial de restolar y les contraseñes.',\r\n\t'az (azerbaijani)': 'Tək bir toxunuşla istədiyiniz vaxt səyahət sessiyanızın tarixçəsini, parolları və çərəzləri təmizləyin.',\r\n\t'bn (bengali)': 'আপনার সম্পূর্ণ ব্রাউজিং সেশনের ইতিহাস, পাসওয়ার্ডগুলি, কুকিকে একবার ট্যাপ করে যে কোনো সময় সাফ করুন।',\r\n\t'bs (bosnian)': 'Izbrišite svoju historiju pretraživanja, lozinke i kolačiće bilo kada s jednim dodirom.',\r\n\t'co (corsican)': 'Squassate a vostra cronolugia di navigazione, e vostre parolle d’intesa, i vostri canistrelli, à ogni mumentu, cù un’incalcata simplice.',\r\n\t'cy (welsh)': 'Cliriwch hanes pori, cyfrineiriau, cwcis eich sesiwn unrhyw bryd gydag un cyffyrddiad.',\r\n\t'dsb (lower sorbian)': 'Wulašujśo swóju póseźeńsku historiju, swóje gronidła a cookieje kuždy cas z jadnym dotyknjenim.',\r\n\t'eo (esperanto)': 'Iam ajn, per nura tuŝeto, viŝi vian tutan retuman seancan historion, pasvortojn, kuketojn.',\r\n\t'et (estonian)': 'Kustuta ühe puudutusega kogu lehitsemise seansi ajalugu, paroolid ja küpsised.',\r\n\t'eu (basque)': 'Sakatze hutsarekin, garbitu edozein unetan zure nabigatze-historia osoa, pasahitzak eta cookieak.',\r\n\t'gd (scottish gaelic)': 'Falamhaich eachdraidh an t-seisein bhrabhsaidh, na faclan-faire agus briosgaidean agad uair sam bith le aon ghnogag.',\r\n\t'gl (galician)': 'Borre todo o seu historial de sesións de navegación, contrasinais e cookies en calquera momento cun só toque.',\r\n\t'gu (gujarati)': 'કંઈક અલગ શોધી રહ્યાં છો? કોઈ અલગ શોધ એન્જિન પસંદ કરો. ગોપનીયતા સૂચના.', // mix of two different sentence\r\n\t'hi (hindi)': 'अपने ब्राउज़िंग सत्र का इतिहास, कूटशब्दो, और स्मृतिशेषों को किसी भी समय एक टैप में मिटाएँ.',\r\n\t'hsb (upper sorbian)': 'Zhašejće swoju posedźensku historiju, swoje hesła a placki kóždy čas z jednym podótkom.',\r\n\t'hy (armenian)': 'Մաքրում է դիտարկումների պատմությունը, գաղտնաբառերը և ավելին՝ մեկ սեղմամբ:',\r\n\t'ia (interlingua)': 'Vacua tote le chronologia, contrasignos e cookies de tu session de navigation, quando tu vole, con un sol tocco.',\r\n\t'kab (kabyle)': 'Kkes amazray inek tiγimit n tuinigin, awalen inek uffiren d wayen nniḍen d ayen fessusen s waṭas.',\r\n\t'kk (kazakh)': 'Шолулар тарихын, парольдерді және cookies файлдарын кез келген уақытта бір шертумен өшіріңіз.',\r\n\t'km (khmer)': 'មាត្រា ១ មនុស្សទាំងអស់ កើតមកមានសេរីភាព និងសមភាព ក្នុងផ្នែកសេចក្ដីថ្លៃថ្នូរនិងសិទ្ធិ។ មនុស្ស មានវិចារណញ្ញាណនិងសតិសម្បជញ្ញៈជាប់ពីកំណើត ហើយគប្បីប្រព្រឹត្ដចំពោះគ្នាទៅវិញទៅមក ក្នុង ស្មារតីភាតរភាពជាបងប្អូន។',\r\n\t\t// Article 1 of the Universal Declaration of Human Rights\r\n\t\t// ^ All human beings are born free and equal in dignity and rights.\r\n\t\t// ^ They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.\r\n\t'kn (kannada)': 'ನಿಮ್ಮ ಸಂಪೂರ್ಣ ಬ್ರೌಸಿಂಗ್ ಸೆಷನ್ ಇತಿಹಾಸ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಕುಕೀಸ್ ಅನ್ನು ಒಮ್ಮೆ ತಟ್ಟುವುದರಿಂದ ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ತೆರವುಗೊಳಿಸಿ.',\r\n\t'lo (lao)': 'ລືບປະຫວັດເຊສຊັນການທ່ອງເວັບ, ລະຫັດຜ່ານ ແລະ ອື່ນໆທັງຫມົດຂອງທ່ານໂດຍການແຕະບາດດຽວ.',\r\n\t'lv (latvian)': 'Tas ļauj jums uzņemt un augšupielādēt fotoattēlus. Vietnes, ko apmeklējat, var pieprasīt jūsu atrašanās vietu.', // mix of two different sentence\r\n\t'mr (marathi)': 'आपला संपूर्ण ब्राऊझिंग इतिहास, पासवर्ड आणि कुकीज केव्हाही एका टॅप मध्ये नष्ट करा.',\r\n\t'ne (nepali)': 'एक क्लिकमा नै तपाईंको सम्पुर्ण ब्राउजिङ्ग इतिहास, पासवर्डहरु, कुकिजहरु कुनैपनि समयमा खाली हुनेछ ।',\r\n\t'pa (punjabi)': 'ਆਪਣੇ ਪੂਰੇ ਬਰਾਊਜ਼ਿੰਗ ਸ਼ੈਸ਼ਨ ਅਤੀਤ, ਪਾਸਵਰਡ, ਕੂਕੀਜ਼ ਨੂੰ ਕਿਸੇ ਵੀ ਵੇਲੇ ਇੱਕ ਛੋਹ ਨਾਲ ਮਿਟਾ ਦਿਓ।',\r\n\t'ses (koyraboro senni)': 'Woo ga naŋ war ma biyey zaa k\\'i zijandi. Interneti nungey kaŋ war g\\'i naaru ga hin ka war gorodogoo hãa.', // mix of two different sentence\r\n\t'si (sinhala)': 'ඔබගේ සමස්ත පිරික්සුම් වාර ඉතිහාසය, මුරපද, දත්තකඩ ඕනෑම විටෙක තනි තට්ටු කිරීමකින් මකන්න.',\r\n\t'sk (slovak)': 'Kedykoľvek si vyčistite celú históriu prehliadania, heslá a cookies. Stačí vám na to jedno ťuknutie.',\r\n\t'sl (slovenian)': 'Kadarkoli izbrišite celotno zgodovino brskanja, gesla in piškotke z enim dotikom.',\r\n\t'ta (tamil)': 'உங்கள் ஒட்டுமொத்த உலாவல் அமர்வு வரலாறு, கடவுச்சொற்கள், நினைவிகளை எந்நேரத்திலும் ஒரே தட்டில் துடைக்கவும்.',\r\n\t'te (telugu)': 'ఎప్పుడైనా కేవలం ఒకే నొక్కుతో మీ విహరణ సెషను చరిత్ర అంతా, సంకేతపదాలు, కుకీలు తుడిచివేసుకోండి.',\r\n\t'tl (tagalog)': 'I-clear ang iyong buong kasaysayan ng session sa pagba-browse, mga password, cookies anumang oras na may isang solong tapikin.',\r\n\t'tt (tatar)': 'Бер басу аша бөтен гизү тарихын, серсүзләрне һәм кукиларны теләсә кайчан бетерә аласыз.',\r\n\t'uz (uzbek)': 'Istalgan vaqtida bir ishora bilan brauzer tarixi, parol va cookie fayllarni tozalab tashlang.',\r\n}\r\n\r\nlet oBlocks = {\r\n\t/* skipped\r\n\t\tunicode 16.0: https://www.unicode.org/charts/PDF/Unicode-16.0/\r\n\t\t\ttodhri: https://en.wikipedia.org/wiki/Todhri_(Unicode_block)\r\n\t\t\tgaray: https://en.wikipedia.org/wiki/Garay_(Unicode_block) / african\r\n\r\n\t\tunicode 17.0: https://www.unicode.org/charts/PDF/Unicode-17.0/\r\n\t*/\r\n\r\n\t// AFRICAN\r\n\t'african' : {\r\n\t\t'adlam': {link: 'Adlam_(Unicode_block)', prefix: '1E9', blocks: [0,1,2,3,4,5], reserved: ['4C','4D','4E','4F','5A','5B','5C','5D']},\r\n\t\t'bamum': {link: 'Bamum_(Unicode_block)', prefix: 'A6', blocks: ['A','B','C','D','E','F'], trim: 8},\r\n\t\t'bassa vah': {link: 'Bassa_Vah_(Unicode_block)', prefix: '16A', blocks: ['D','E','F'], trim: 10, reserved: ['EE','EF']},\r\n\t\t'ethiopic': {link: 'Ethiopic_(Unicode_block)', prefix: 1, trim: 3, enlarge: 14,\r\n\t\t\t//* partial: block 35 covers the last changes in 2010: v3 1999 = 345 | v4.1 2005 = +11 | v6 2010 = +2\r\n\t\t\tblocks: [34,35,36,37], partial: true, \r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [20,21,22,23,24,25,26,27,28,29,'2A','2B','2C','2D','2E','2F',30,31,32,33,34,35,36,37],\r\n\t\t\treserved: ['249','24E','24F','257','259','25E','25F','289','28E','28F','2B1','2B6','2B7','2BF','2C1','2C6','2C7','2D7','311','316','317','35B','35C'],\r\n\t\t\t//*/\r\n\t\t},\r\n\t\t'ethiopic supplement': {link: 'Ethiopic_Supplement', prefix: 13, blocks: [8,9], trim: 6, enlarge: 16},\r\n\t\t'medefaidrin': {link: 'Medefaidrin_(Unicode_block)', prefix: '16E', blocks: [4,5,6,7,8,9], trim: 5},\r\n\t\t'mende_kikakui': {link: 'Mende_Kikakui_(Unicode_block)', prefix: '1E8',\r\n\t\t\t//* partial: all v7 2004\r\n\t\t\tblocks: [0,1,2,3], partial: true,\r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,6,'A','B','C','D'], trim: 9, reserved: ['C5','C6']\r\n\t\t\t//*/\r\n\t\t},\r\n\t\t'nko': {link: 'NKo_(Unicode_block)', prefix: '07', blocks: ['C','D','E','F'], reserved: ['FB','FC']},\r\n\t\t'osmanya': {link: 'Osmanya_(Unicode_block)', prefix: 104, blocks: [8,9,'A'], trim: 6, reserved: ['9E','9F']},\r\n\t\t'tifinagh': {link: 'Tifinagh_(Unicode_block)', prefix: '2D', blocks: [3,4,5,6,7],\r\n\t\t\treserved: ['68','69','6A','6B','6C','6D','6E','71','72','73','74','75','76','77','78','79','7A','7B','7C','7D','7E'],\r\n\t\t\tignored: ['7F'] // tifinagh consonant joiner\r\n\t\t},\r\n\t\t'vai': {link: 'Vai_(Unicode_block)', prefix: 'A',\r\n\t\t\t//* partial: all v5.1 2008\r\n\t\t\tblocks: [50,51,52,53], partial: true,\r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [50,51,52,53,54,55,56,57,58,59,'5A','5B','5C','5D','5E','5F',60,61,62,63], trim: 20,\r\n\t\t\t//*/\r\n\t\t},\r\n\t},\r\n\t// AMERICAN\r\n\t'american': {\r\n\t\t'cherokee': {link: 'Cherokee_(Unicode_block)', prefix: 13, blocks: ['A','B','C','D','E','F',], trim: 2, reserved: ['F6','F7']},\r\n\t\t'cherokee supplement': {link: 'Cherokee_Supplement', prefix: 'AB', blocks: [7,8,9,'A','B'], enlarge: 16},\r\n\t\t'deseret': {link: 'Deseret_(Unicode_block)', prefix: '104', blocks: [0,1,2,3,4], enlarge: 14},\r\n\t\t'osage': {link: 'Osage_(Unicode_block)', prefix: 104, blocks: ['B','C','D','E','F'], trim: 4, reserved: ['D4','D5','D6','D7']},\r\n\t\t'unified canadian aboriginal syllabics': {link: 'Unified_Canadian_Aboriginal_Syllabics_(Unicode_block)', prefix: 1,\r\n\t\t\t//* partial: v3 1999 630 | v5.2 2009 + 10 (9 of these 10 covered in block 67)\r\n\t\t\tblocks: [64,65,66,67], partial: true\r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [40,41,42,43,44,45,46,47,48,49,'4A','4B','4C','4D','4E','4F',50,51,52,53,54,55,56,57,58,59,'5A','5B','5C','5D','5E','5F',60,61,62,63,64,65,66,67]\r\n\t\t\t//*/\r\n\t\t},\r\n\t},\r\n\t// ANCIENT\r\n\t'ancient and historic': {\r\n\t\t'gothic': {link: 'Gothic_(Unicode_block)', prefix: 103, blocks: [3,4], trim: 5},\r\n\t\t'ogham': {link: 'Ogham_(Unicode_block)', prefix: 16, blocks: [8,9], trim: 3, enlarge: 18},\r\n\t\t'old italic': {link: 'Old_Italic_(Unicode_block)', prefix: 103, blocks: [0,1,2], reserved: ['24','25','26','27','28','29','2A','2B','2C']},\r\n\t\t'old turkic': {link: 'Old_Turkic_(Unicode_block)', prefix: '10C', blocks: [0,1,2,3,4], trim: 7, enlarge: 14},\r\n\t\t'runic': {link: 'Runic_(Unicode_block)', prefix: 16, blocks:['A','B','C','D','E','F'], trim: 7, enlarge: 14},\r\n\t},\r\n\t// BRAHMIC\r\n\t'brahmic': {\r\n\t\t'balinese': {link: 'Balinese_(Unicode_block)', prefix: '1B', blocks: [0,1,2,3,4,5,6,7], reserved: ['4D']},\r\n\t\t'bengali': {link: 'Bengali_(Unicode_block)', prefix: '09', blocks: [8,9,'A','B','C','D','E','F'], trim: 1,\r\n\t\t\treserved: ['84','8D','8E','91','92','A9','B1','B3','B4','B5','BA','BB','C5','C6','C9','CA','CF',\r\n\t\t\t'D0','D1','D2','D3','D4','D5','D6','D8','D9','DA','DB','DE','E4','E5'], enlarge: 14\r\n\t\t},\r\n\t\t'buginese': {link: 'Buginese_(Unicode_block)', prefix: '1A', blocks: [0,1], reserved: ['1C','1D'], enlarge: 16},\r\n\t\t'buhid': {link: 'Buhid_(Unicode_block)', prefix: 17, blocks: [4,5], trim: 12, enlarge: 16},\r\n\t\t'chakma': {link: 'Chakma_(Unicode_block)', prefix: 111, blocks: [0,1,2,3,4], trim: 8, reserved: ['35']},\r\n\t\t'cham':{link: 'Cham_(Unicode_block)', prefix: 'AA', blocks: [0,1,2,3,4,5],\r\n\t\t\treserved: ['37','38','39','3A','3B','3C','3D','3E','3F','4E','4F','5A','5B']\r\n\t\t},\r\n\t\t'common indic number forms': {link: 'Common_Indic_Number_Forms', prefix: 'A8', blocks: [3], trim: 6},\r\n\t\t'devanagari': {link: 'Devanagari_(Unicode_block)', prefix: '09', blocks: [0,1,2,3,4,5,6,7], enlarge: 14},\r\n\t\t'dives akuru': {link: 'Dives_Akuru_(Unicode_block)', prefix: 119, blocks: [0,1,2,3,4,5], trim: 6,\r\n\t\t\treserved: ['07','08','0A','0B','14','17','36','39','3A','47','48','49','4A','4B','4C','4D','4E','4F']\r\n\t\t},\r\n\t\t'dogra': {link: 'Dogra_(Unicode_block)', prefix: 118, blocks: [0,1,2,3,4], trim: 20},\r\n\t\t'gujarati': {link: 'Gujarati_(Unicode_block)', prefix: '0A', blocks: [8,9,'A','B','C','D','E','F'],\r\n\t\t\treserved: ['80','84','8E','92','A9','B1','B4','BA','BB','C6','CA','CE','CF','D1','D2','D3','D4','D5',\r\n\t\t\t\t'D6','D7','D8','D9','DA','DB','DC','DD','DE','DF','E4','E5','F2','F3','F4','F5','F6','F7','F8'] \r\n\t\t},\r\n\t\t'gurmukhi': {link: 'Gurmukhi_(Unicode_block)', prefix: '0A', blocks: [0,1,2,3,4,5,6,7], trim: 9,\r\n\t\t\treserved: ['00','04','0B','0C','0D','0E','11','12','29','31','34','37','3A','3B','3D','43','44','45','46',\r\n\t\t\t\t'49','4A','4E','4F','50','52','53','54','55','56','57','58','5D','5F','60','61','62','63','64','65']\r\n\t\t},\r\n\t\t'hanifi rohingya': {link: 'Hanifi_Rohingya__(Unicode_block)', prefix: '10D', blocks: [0,1,2,3], trim: 6,\r\n\t\t\treserved: ['28','29','2A','2B','2C','2D','2E','2F']\r\n\t\t},\r\n\t\t'hanunoo': {link: 'Hanunoo_(Unicode_block)', prefix: 17, blocks: [2,3], trim: 9},\r\n\t\t'javanese': {link: 'Javanese_(Unicode_block)', prefix: 'A9', blocks: [8,9,'A','B','C','D'], reserved: ['CE','DA','DB','DC','DD']},\r\n\t\t'kaithi': {link: 'Kaithi_(Unicode_block)', prefix: 110, blocks: [8,9,'A','B','C'], trim: 2,\r\n\t\t\treserved: ['C3','C4','C5','C6','C7','C8','C9','CA','CB','CC']\r\n\t\t},\r\n\t\t'kannada': {link: 'Kannada_(Unicode_block)', prefix: '0C', blocks: [8,9,'A','B','C','D','E','F'], trim: 12,\r\n\t\t\treserved: ['8D','91','A9','B4','BA','BB','C5','C9','CE','CF','D0','D1','D2','D3','D4','D7','D8','D9','DA','DB','DF','E4','E5','F0']\r\n\t\t},\r\n\t\t'kawi': {prefix: '11F', blocks: [0,1,2,3,4,5], trim: 5, reserved: ['11','3B','3C','3D'], link: 'Kawi_(Unicode_block)'},\r\n\t\t'kayah li': {prefix: 'A9', blocks: [0,1,2], link: 'Kayah_Li_(Unicode_block)'},\r\n\t\t'khmer': {link: 'Khmer_(Unicode_block)', prefix: 17, blocks: [8,9,'A','B','C','D','E','F'], trim: 6,\r\n\t\t\treserved: ['DE','DF','EA','EB','EC','ED','EE','EF'], ignored: ['B4','B5'] // ignored: KIV AQ, KIV AA\r\n\t\t},\r\n\t\t'khmer symbols': {link: 'Khmer_Symbols', prefix: 19, blocks: ['E','F']},\r\n\t\t'khojki': {link: 'Khojki_(Unicode_block)', prefix: 112, blocks: [0,1,2,3,4], trim: 14, reserved: ['12']},\r\n\t\t'khudawadi': {link: 'Khudawadi_(Unicode_block)', prefix: 112, blocks: ['B','C','D','E','F'], trim: 6, reserved: ['EB','EC','ED','EE','EF']},\r\n\t\t'lao': {link: 'Lao_(Unicode_block)', prefix: '0E', blocks: [8,9,'A','B','C','D','E','F'], trim: 32,\r\n\t\t\treserved: ['80','83','85','8B','A4','A6','BE','BF','C5','C7','CF','DA','DB']\r\n\t\t},\r\n\t\t'lepcha': {link: 'Lepcha_(Unicode_block)', prefix: '1C', blocks: [0,1,2,3,4], reserved: ['38','39','3A','4A','4B','4C']},\r\n\t\t'limbu': {link: 'Limbu_(Unicode_block)', prefix: 19, blocks: [0,1,2,3,4], reserved: ['1F','2C','2D','2E','2F','3C','3D','3E','3F','41','42','43']},\r\n\t\t'mahajani': {link: 'Mahajani_(Unicode_block)', prefix: 111, blocks: [5,6,7], trim: 9, enlarge: 16},\r\n\t\t'malayalam': {link: 'Malayalam_(Unicode_block)', prefix: '0D', blocks: [0,1,2,3,4,5,6,7],\r\n\t\t\treserved: ['0D','11','45','49','50','51','52','53','64','65'], ignored: ['4E'] // ignored: letter dot reph\r\n\t\t},\r\n\t\t'meetei mayek': {link: 'Meetei_Mayek_(Unicode_block)', prefix: 'AB', blocks: ['C','D','E','F'], trim: 6, reserved: ['EE','EF']},\r\n\t\t'modi': {link: 'Modi_(Unicode_block)', prefix: 116, blocks: [0,1,2,3,4,5], trim: 6, reserved: ['45','46','47','48','49','4A','4B','4C','4D','4E','4F']},\r\n\t\t'mro': {link: 'Mro_(Unicode_block)', prefix: '16A', blocks: [4,5,6], reserved: ['5F','6A','6B','6C','6D']},\r\n\t\t'multani': {link: 'Multani_(Unicode_block)', prefix: 112, blocks: [8,9,'A'], trim: 6, reserved: ['87','89','8E','9E']},\r\n\t\t'myanmar': {link: 'Myanmar_(Unicode_block)', prefix: 10, blocks: [0,1,2,3,4,5,6,7,8,9]},\r\n\t\t'myanmar extended-a': {link: 'Myanmar_Extended-A', prefix: 'AA', blocks: [6,7]},\r\n\t\t'myanmar extended-b': {link: 'Myanmar_Extended-B', prefix: 'A9', blocks: ['E','F'], trim: 1},\r\n\t\t'nag mundari': {link: 'Nag_Mundari_(Unicode_block)', prefix: '1E4', blocks: ['D','E','F'], trim: 6},\r\n\t\t'new tai lue': {link: 'New_Tai_Lue_(Unicode_block)', prefix: 19, blocks: [8,9,'A','B','C','D'],\r\n\t\t\treserved: ['AC','AD','AE','AF','CA','CB','CC','CD','CE','CF','DB','DC','DD']\r\n\t\t},\r\n\t\t'newa': {link: 'Newa_(Unicode_block)', prefix: 114, blocks: [0,1,2,3,4,5,6,7], trim: 30, reserved: ['5C']},\r\n\t\t'ol chiki': {link: 'Ol_Chiki_(Unicode_block)', prefix: '1C', blocks: [5,6,7]},\r\n\t\t'oriya': {link: 'Oriya_(Unicode_block)', prefix: '0B', blocks: [0,1,2,3,4,5,6,7], trim: 8, enlarge: 14,\r\n\t\t\treserved: ['00','04','0D','0E','11','12','29','31','34','3A','3B','45','46','49','4A','4E','4F','50','51',\r\n\t\t\t'52','53','54','58','59','5A','5B','5E','64','65'],\r\n\t\t},\r\n\t\t'pau cin hau': {link: 'Pau_Cin_Hau_(Unicode_block)', prefix: '11A', blocks: ['C','D','E','F'], trim: 7},\r\n\t\t'phags-pa': {link: 'Phags-pa_(Unicode_block)', prefix: 'A8', blocks: [4,5,6,7], trim:-8},\r\n\t\t'saurashtra': {link: 'Saurashtra_(Unicode_block)', prefix: 'A8', blocks: [8,9,'A','B','C','D'], trim: 6,\r\n\t\t\treserved: ['C6','C7','C8','C9','CA','CB','CC','CD']\r\n\t\t},\r\n\t\t'sinhala': {link: 'Sinhala_(Unicode_block)', prefix: '0D', blocks: [8,9,'A','B','C','D','E','F'], trim: 11, enlarge: 16,\r\n\t\t\treserved: ['80','84','97','98','99','B2','BC','BE','BF','C7','C8','C9','CB','CC','CD','CE','D5','D7','E0','E1','E2','E3','E4','E5','F0','F1']\r\n\t\t},\r\n\t\t'sora sompeng': {link: 'Sora_Sompeng_(Unicode_block)', prefix: 110, blocks: ['D','E','F'], trim: 6, enlarge: 16,\r\n\t\t\treserved: ['E9','EA','EB','EC','ED','EE','EF']\r\n\t\t},\r\n\t\t'sundanese': {link: 'Sundanese_(Unicode_block)', prefix: '1B', blocks: [8,9,'A','B'], ignored: ['AB']}, // ignored: sign virama\r\n\t\t'sundanese supplement': {link: 'Sundanese_Supplement', prefix: '1C', blocks: ['C'], trim: 8},\r\n\t\t'syloti nagri': {link: 'Syloti_Nagri_(Unicode_block)', prefix: 'A8', blocks: [0,1,2], trim: 3, },\r\n\t\t'tagalog': {link: 'Tagalog_(Unicode_block)', prefix: 17, blocks: [0,1,], reserved: ['16','17','18','19','1A','1B','1C','1D','1E']},\r\n\t\t'tagbanwa': {link: 'Tagbanwa_(Unicode_block)', prefix: 17, blocks: [6,7], trim: 12, reserved: ['6D','71']},\r\n\t\t'tai le': {link: 'Tai_Le_(Unicode_block)', prefix: 19, blocks: [5,6,7], trim: 11, reserved: ['6E','6F'], enlarge: 14},\r\n\t\t'tai tham': {link: 'Tai_Tham_(Unicode_block)', prefix: '1A', blocks: [2,3,4,5,6,7,8,9,'A'], trim: 2,\r\n\t\t\treserved: ['5F','7D','7E','8A','8B','8C','8D','8E','8F','9A','9B','9C','9D','9E','9F']\r\n\t\t},\r\n\t\t'tai viet': {link: 'Tai_Viet_(Unicode_block)', prefix: 'AA', blocks: [8,9,'A','B','C','D'],\r\n\t\t\treserved: ['C3','C4','C5','C6','C7','C8','C9','CA','CB','CC','CD','CE','CF','D0','D1','D2','D3','D4','D5','D6','D7','D8','D9','DA']\r\n\t\t},\r\n\t\t'takri': {link: 'Takri_(Unicode_block)', prefix: 116, blocks: [8,9,'A','B','C'], trim: 6, reserved: ['BA','BB','BC','BD','BE','BF']},\r\n\t\t'tamil': {link: 'Tamil_(Unicode_block)', prefix: '0B', blocks: [8,9,'A','B','C','D','E','F'], trim: 5,\r\n\t\t\treserved: ['80','81','84','8B','8C','8D','91','96','97','98','9B','9D','A0','A1','A2','A5','A6','A7','AB','AC','AD','BA','BB','BC','BD',\r\n\t\t\t'C3','C4','C5','C9','CE','CF','D1','D2','D3','D4','D5','D6','D8','D9','DA','DB','DC','DD','DE','DF','E0','E1','E2','E3','E4','E5']\r\n\t\t},\r\n\t\t'tamil supplement': {link: 'Tamil_Supplement', prefix: '11F', blocks: ['C','D','E','F'],\r\n\t\t\treserved: ['F2','F3','F4','F5','F6','F7','F8','F9','FA','FB','FC','FD','FE']\r\n\t\t},\r\n\t\t'telugu': {link: 'Telugu_(Unicode_block)', prefix: '0C', blocks: [0,1,2,3,4,5,6,7], enlarge: 16,\r\n\t\t\treserved: ['0D','11','29','3A','3B','45','49','4E','4F','50','51','52','53','54','57','5B','5E','5F','64','65','70','71','72','73','74','75','76']\r\n\t\t},\r\n\t\t'thai': {link: 'Thai_(Unicode_block)', prefix: '0E', blocks: [0,1,2,3,4,5,6,7], trim: 36, enlarge: 14,\r\n\t\t\treserved: ['00','3B','3C','3D','3E']\r\n\t\t},\r\n\t\t'tibetan': {link: 'Tibetan_(Unicode_block)', prefix: '0F', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], trim: 37, enlarge: 18,\r\n\t\t\treserved: ['48','6D','6E','6F','70','98','BD','CD'], ignored: ['0C'] // ignored: mark delimiter tsheg bstar\r\n\t\t},\r\n\t\t'tirhuta': {link: 'Tirhuta_(Unicode_block)', prefix: 114, blocks: [8,9,'A','B','C','D'], trim: 6,\r\n\t\t\treserved: ['C8','C9','CA','CB','CC','CD','CE','CF']\r\n\t\t},\r\n\t\t'warang citi': {link: 'Warang_Citi_(Unicode_block)', prefix: 118, blocks: ['A','B','C','D','E','F'],\r\n\t\t\treserved: ['F3','F4','F5','F6','F7','F8','F9','FA','FB','FC','FD','FE']\r\n\t\t},\r\n\t},\r\n\t// CYRILLIC\r\n\t'cyrillic': {\r\n\t\t'cyrillic': {link: 'Cyrillic_(Unicode_block)', prefix: '04', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']},\r\n\t\t'cyrillic extended-a': {link: 'Cyrillic_Extended-A', prefix: '2D', blocks: ['E','F'], enlarge: 22, spacer: true},\r\n\t\t'cyrillic extended-b': {link: 'Cyrillic_Extended-B', prefix: 'A6', blocks: [4,5,6,7,8,9]},\r\n\t\t'cyrillic extended-c': {link: 'Cyrillic_Extended-C', prefix: '1C', blocks: [8], trim: 5},\r\n\t\t'cyrillic supplement': {link: 'Cyrillic_Supplement', prefix: '05', blocks: [0,1,2]},\r\n\t\t'glagolitic': {link: 'Glagolitic_(Unicode_block)', prefix: '2C', blocks: [0,1,2,3,4,5], enlarge: 14},\r\n\t},\r\n\t// EAST ASIAN\r\n\t'east asian': {\r\n\t\t'bopomofo': {link: 'Bopomofo_(Unicode_block)', prefix: 31, blocks: [0,1,2], reserved: ['00','01','02','03','04']},\r\n\t\t'bopomofo extended': {link: 'Bopomofo_Extended', prefix: 31, blocks: ['A','B'], enlarge: 14 },\r\n\t\t'cjk compatibility': {link: 'CJK_Compatibility', prefix: 33,\r\n\t\t\t//* partial: no changes since 2003, only 7 changes since 1993\r\n\t\t\tblocks: [0,1,2,3], isPartial: true\r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\t\t\t//*/\r\n\t\t},\r\n\t\t'cjk compatibility forms': {link: 'CJK_Compatibility_Forms', prefix: 'FE', blocks: [3,4]},\r\n\t\t'cjk compatibility ideographs': {link: 'CJK_Compatibility_Ideographs', prefix: 'F', reserved: ['A6E','A6F'],\r\n\t\t\t// partial: 5 changes out of 472 since 2005: all 5 changes are in A2 + A6\r\n\t\t\tblocks: ['A2','A3','A4','A5','A6'], isPartial: true\r\n\t\t\t/* full\r\n\t\t\tblocks: [90,91,92,93,94,95,96,97,98,99,'9A','9B','9C','9D','9E','9F','A0','A1','A2','A3','A4','A5','A6','A7','A8','A9','AA','AB','AC','AD','AE','AF'],\r\n\t\t\ttrim: 38,\r\n\t\t\t//*/\r\n\t\t},\r\n\t\t'cjk compatibility ideographs supplement': {link: 'CJK_Compatibility_Ideographs_Supplement', prefix: '2F',\r\n\t\t\t//* partial: all v3.1 2001\r\n\t\t\tblocks: [82,83,84,85], partial: true\r\n\t\t\t//*/\r\n\t\t\t/* full\r\n\t\t\tblocks: [80,81,82,83,84,85,86,87,88,89,'8A','8B','8C','8D','8E','8F',90,91,92,93,94,95,96,97,98,99,'9A','9B','9C','9D','9E','9F','A0','A1'], trim: 2\r\n\t\t\t//*/\r\n\t\t},\r\n\t\t'cjk strokes': {link: 'CJK_Strokes_(Unicode_block)', prefix: 31, blocks: ['C','D','E'],\r\n\t\t\treserved: ['E6','E7','E8','E9','EA','EB','EC','ED','EE'], ignored: ['EF'] // ignored character subtraction\r\n\t\t},\r\n\t\t'cjk symbols and punctuation': {link: 'CJK_Symbols_and_Punctuation', prefix: 30, blocks: [0,1,2,3], ignored: ['00']}, // ignored: ID SP\r\n\t\t'cjk radicals supplement': {link: 'CJK_Radicals_Supplement', prefix: '2E', blocks: [8,9,'A','B','C','D','E','F'], trim: 12, reserved: ['9A']},\r\n\t\t'cjk unified ideographs': {link: 'CJK_Unified_Ideographs_(Unicode_block)', prefix: '9F',\r\n\t\t\t// partial: (from 20k+) includes all 90 changes since the v1.0.1 in 1992\r\n\t\t\tblocks: ['A','B','C','D','E','F'], partial: true\r\n\t\t},\r\n\t\t'cjk unified ideographs extension-a': {link: 'CJK_Unified_Ideographs_Extension_A', prefix: '4D',\r\n\t\t\t// partial: (from 6.5k+) v3 1999 | v13 2020 = +10 | blocks include all 10 changes since the first version\r\n\t\t\tblocks: [9,'A','B','C'], partial: true\r\n\t\t},\r\n\t\t'cjk unified ideographs extension-b': {link: 'CJK_Unified_Ideographs_Extension_B', prefix: '2A6',\r\n\t\t\t// partial: (from 42k+) v3.1 2001 | v13+14 2020-21 = +9 | block A6D includes all 9 changes since the first version\r\n\t\t\tblocks: ['A','B','C','D'], partial: true\r\n\t\t},\r\n\t\t'enclosed cjk letters and months': {link: 'Enclosed_CJK_Letters_and_Months', prefix: '32',\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], reserved: ['1F']\r\n\t\t},\r\n\t\t'hangul compatibility jamo': {link: 'Hangul_Compatibility_Jamo', prefix: 31, blocks: [3,4,5,6,7,8], reserved: ['30'], ignored: ['64']}, // ignored: HF\r\n\t\t'hangul jamo': {link: 'Hangul_Jamo_(Unicode_block)', prefix: 11, enlarge: 14,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], ignored: ['5F','60'] // ignored: HCF hangul choseong filler, HJF hangul jungseong filler\r\n\t\t},\r\n\t\t'hangul syllables': {link: 'Hangul_Syllables', prefix: 'AC', blocks: [0,1,2,3], partial: true}, // partial: (from 11k+) AFAICT no changes since intial v2 1996\r\n\t\t'hiragana': {link: 'Hiragana_(Unicode_block)', prefix: 30, blocks: [4,5,6,7,8,9], reserved: ['40','97','98']},\r\n\t\t'ideographic description characters': {link: 'Ideographic_Description_Characters_(Unicode_block)', prefix: '2F', blocks: ['F']},\r\n\t\t'kanbun': {link: 'Kanbun_(Unicode_block)', prefix: 31, blocks: [9], enlarge: 16},\r\n\t\t'kangxi radicals': {link: 'Kangxi_Radicals_(Unicode_block)', prefix: '2F', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D'], trim: 10},\r\n\t\t'katakana': {link: 'Katakana_(Unicode_block)', prefix: 30, blocks: ['A','B','C','D','E','F']},\r\n\t\t'katakana phonetic extensions': {link: 'Katakana_Phonetic_Extensions', prefix: 31, blocks: ['F'], enlarge: 16},\r\n\t\t'lisu': {link: 'Lisu_(Unicode_block)', prefix: 'A4', blocks: ['D','E','F']},\r\n\t\t'vertical forms': {link: 'Vertical_Forms', prefix: 'FE', blocks: [1], trim: 6, enlarge: 16},\r\n\t\t'yi radicals': {link: 'Yi_Radicals', prefix: 'A4', blocks: [9,'A','B','C'], trim: 9},\r\n\t\t'yi syllables': {link: 'Yi_Syllables', prefix: 'A0', blocks: [0,1,2,3], partial: true, enlarge: 16}, // partial: (from 1k+) no changes since initial v3 1999\r\n\t},\r\n\t// GEORGIAN\r\n\t'georgian': {\r\n\t\t'georgian': {link: 'Georgian_(Unicode_block)', prefix: 10, blocks: ['A','B','C','D','E','F'], reserved: ['C6','C8','C9','CA','CB','CC','CE','CF']},\r\n\t\t'georgian extended': {link: 'Georgian_Extended', prefix: '1C', blocks: [9,'A','B'], reserved: ['BB','BC']},\r\n\t\t'georgian supplement': {link: 'Georgian_Supplement', prefix: '2D', blocks: [0,1,2], trim: 2, reserved: ['26','28','29','2A','2B','2C']},\r\n\t},\r\n\t// GREEK\r\n\t'greek': {\r\n\t\t'greek and coptic': {link: 'Greek_and_Coptic', prefix: '03', blocks: [7,8,9,'A','B','C','D','E','F'],\r\n\t\t\treserved: ['78','79','80','81','82','83','8B','8D','A2']\r\n\t\t},\r\n\t\t'greek extended': {link: 'Greek_Extended', prefix: '1F', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], trim: 1,\r\n\t\t\treserved: ['16','17','1E','1F','46','47','4E','4F','58','5A','5C','5E','7E','7F','B5','C5','D4','D5','DC','F0','F1','F5'],\r\n\t\t},\r\n\t},\r\n\t// LATIN\r\n\t'latin': {\r\n\t\t'basic latin': {link: 'Basic_Latin_(Unicode_block)', prefix: '00', blocks: [0,1,2,3,4,5,6,7],\r\n\t\t\tignored: [\r\n\t\t\t\t'00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F',\r\n\t\t\t\t'10','11','12','13','14','15','16','17','18','19','1A','1B','1C','1D','1E','1F', // first 2 rows\r\n\t\t\t\t'20','7F', // ignored: SP, DEL\r\n\t\t\t]\r\n\t\t},\r\n\t\t'latin-1 supplement': {link: 'Latin-1_Supplement', prefix: '00', blocks: [8,9,'A','B','C','D','E','F'], \r\n\t\t\tignored: [\r\n\t\t\t\t'80','81','82','83','84','85','86','87','88','89','8A','8B','8C','8D','8E','8F',\r\n\t\t\t\t'90','91','92','93','94','95','96','97','98','99','9A','9B','9C','9D','9E','9F', // first 2 rows\r\n\t\t\t\t'A0','AD', // ignored: NBSP, SHY\r\n\t\t\t]\r\n\t\t},\r\n\t\t'latin extended-a': {link: 'Latin_Extended-A', prefix: '01', blocks: [0,1,2,3,4,5,6,7]},\r\n\t\t'latin extended-b': {link: 'Latin_Extended-B', prefix: 0, blocks: [18,19,'1A','1B','1C','1D','1E','1F',20,21,22,23,24]},\r\n\t\t'latin extended-c': {link: 'Latin_Extended-C', prefix: '2C', blocks: [6,7]},\r\n\t\t'latin extended-d': {link: 'Latin_Extended-D', prefix: 'A7', blocks: [2,3,4,5,6,7,8,9,'A','B','C','D','E','F'],\r\n\t\t\treserved: ['DD','DE','DF','E0','E1','E2','E3','E4','E5','E6','E7','E8','E9','EA','EB','EC','ED','EE','EF','F0'] \r\n\t\t},\r\n\t\t'latin extended-e': {link: 'Latin_Extended-E', prefix: 'AB', blocks: [3,4,5,6], trim: 4},\r\n\t\t'latin extended-f': {link: 'Latin_Extended-F', prefix: 107, blocks: [8,9,'A','B'], trim: 5, reserved: ['86','B1']},\r\n\t\t'latin extended-g': {link: 'Latin_Extended-G', prefix: '1DF', blocks: [0,1,2], trim: 5 , reserved: ['1F','20','21','22','23','24']},\r\n\t\t'latin extended additional': {link: 'Latin_Extended_Additional', prefix: '1E', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']},\r\n\t},\r\n\t// PHONETIC\r\n\t'phonetic': {\r\n\t\t'ipa extensions': {link: 'IPA_Extensions', prefix: '02', blocks: [5,6,7,8,9,'A'], enlarge: 14},\r\n\t\t'phonetic extensions': {link: 'Phonetic_Extensions', prefix: '1D', blocks: [0,1,2,3,4,5,6,7], enlarge: 16},\r\n\t\t'phonetic extensions supplement': {link: 'Phonetic_Extensions_Supplement', prefix: '1D', blocks: [8,9,'A','B'], enlarge: 16},\r\n\t\t'spacing modifier letters': {link: 'Spacing_Modifier_Letters', prefix: '02', blocks: ['B','C','D','E','F'], enlarge: 18},\r\n\t},\r\n\t// SEMITIC\r\n\t'semitic': {\r\n\t\t'arabic': {link: 'Arabic_(Unicode_block)', prefix: '06', enlarge: 16,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], ignored: ['1C'] // ignored ALM\r\n\t\t},\r\n\t\t'arabic extended-a': {link: 'Arabic_Extended-A', prefix: '08', blocks: ['A','B','C','D','E','F'], enlarge: 16,\r\n\t\t\tignored: ['E2'] // ignored ARABIC DISPUTED END OF AYAH\r\n\t\t},\r\n\t\t'arabic extended-b': {link: 'Arabic_Extended-B', prefix: '08', blocks: [7,8,9], enlarge: 16,\r\n\t\t\treserved: ['92','93','94','95','96'], ignored: ['90','91']\r\n\t\t},\r\n\t\t'arabic extended-c': {link: 'Arabic_Extended-C', prefix: '10E', blocks: ['C','D','E','F'], enlarge: 16,\r\n\t\t\treserved: ['C0','C1','C8','C9','CA','CB','CC','CD','CE','CF','D9','DA','DB','DC','DD','DE','DF',\r\n\t\t\t'E0','E1','E2','E3','E4','E5','E6','E7','E8','E9','EA','EB','EC','ED','EE','EF', // all this row\r\n\t\t\t'F0','F1','F2','F3','F4','F5','F6','F7','F8','F9',\r\n\t\t\t]\r\n\t\t},\r\n\t\t'arabic presentation forms-a': {link: 'Arabic_Presentation_Forms-A', prefix: 'F', enlarge: 16,\r\n\t\t\t// partial: (from 650+) 45 changes since v6 2010: +20 in 2021, +25 in 2025 | blocks includes all 45 changes since 2010\r\n\t\t\t// selective: blocks couldn't be contiguous to get the 45 changes\r\n\t\t\tblocks: ['B5','B6','BC','BD','D4','D9','DC','DF'], partial: true, selective: true\r\n\t\t},\r\n\t\t'arabic supplement': {link: 'Arabic_Supplement', prefix: '07', blocks: [5,6,7], enlarge: 16},\r\n\t\t'hebrew': {link: 'Hebrew_(Unicode_block)', prefix: '05', blocks: [9,'A','B','C','D','E','F'], trim: 11, enlarge: 18,\r\n\t\t\treserved: ['90','C8','C9','CA','CB','CC','CD','CE','CF','EB','EC','ED','EE']\r\n\t\t},\r\n\t\t'mandaic': {link: 'Mandaic_(Unicode_block)', prefix: '08', blocks: [4,5], trim: 1, reserved: ['5C','5D']},\r\n\t\t'samaritan': {link: 'Samaritan_(Unicode_block)', prefix: '08', blocks: [0,1,2,3], trim: 1, reserved: ['2E','2F']},\r\n\t\t'syriac': {link: 'Syriac_(Unicode_block)', prefix: '07', blocks: [0,1,2,3,4], reserved: ['0E','4B','4C'], enlarge: 16},\r\n\t},\r\n\t// SYMBOLS\r\n\t'symbols': {\r\n\t\t'alphabetic presentation forms': {link: 'Alphabetic_Presentation_Forms', prefix: 'FB', blocks: [0,1,2,3,4], enlarge: 14,\r\n\t\t\treserved: ['07','08','09','0A','0B','0C','0D','0E','0F','10','11','12','18','19','1A','1B','1C','37','3D','3F','42','45']\r\n\t\t},\r\n\t\t'arrows': {link: 'Arrows_(Unicode_block)', prefix: 21, blocks: [9,'A','B','C','D','E','F'], enlarge: 14},\r\n\t\t'block elements': {link: 'Block_Elements', prefix: 25, blocks: [8,9]},\r\n\t\t'box drawing': {link: 'Box_Drawing', prefix: 25, blocks: [0,1,2,3,4,5,6,7]},\r\n\t\t'braille patterns': {link: 'Braille_Patterns', prefix: 28, blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']},\r\n\t\t'currency symbols': {link: 'Currency_Symbols_(Unicode_block)', prefix: 20, blocks: ['A','B','C'], trim: 14},\r\n\t\t'dingbat': {link: 'Dingbats_(Unicode_block)', prefix: 27, blocks: [0,1,2,3,4,5,6,7,8,9,'A','B']},\r\n\t\t'enclosed alphanumerics': {link: 'Enclosed_Alphanumerics', prefix: 24, blocks: [6,7,8,9,'A','B','C','D','E','F']},\r\n\t\t'general punctuation': {link: 'General_Punctuation', prefix: 20, blocks: [0,1,2,3,4,5,6], reserved: ['65'], enlarge: 16,\r\n\t\t\tignored: [\r\n\t\t\t\t'00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F',\r\n\t\t\t\t'06','61','62','63','64','66','67','68','69','6A','6B','6C','6D','6E','6F', // '65' is reserved\r\n\t\t\t\t'11', '28','29', '2A','2B','2C','2D','2E','2F','5F', // NB, L SEP, P SEP, LRE, RLE, PDF, LRO, RLO, NNB SP, MM SP\r\n\t\t\t],\r\n\t\t},\r\n\t\t'geometric shapes': {link: 'Geometric_Shapes_(Unicode_block)', prefix: 25, blocks: ['A','B','C','D','E','F']},\r\n\t\t'letterlike symbols': {link: 'Letterlike_Symbols', prefix: 21, blocks: [0,1,2,3,4]},\r\n\t\t'mathematical alphanumeric symbols': {link: 'Mathematical_Alphanumeric_Symbols', prefix: '1D4',\r\n\t\t\tblocks: [1,2,3], partial: true // partial: (from ~1k) 5 changes since the initial v3.1 in 2001 | no changes since v5 in 2006\r\n\t\t},\r\n\t\t'mathematical operators': {link: 'Mathematical_Operators_(Unicode_block)', prefix: 22,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\t\t},\r\n\t\t'miscellaneous mathematical symbols-a': {link: 'Miscellaneous_Mathematical_Symbols-A', prefix: 27, blocks: ['C','D','E']},\r\n\t\t'miscellaneous mathematical symbols-b': {link: 'Miscellaneous_Mathematical_Symbols-B', prefix: 29,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\t\t},\r\n\t\t'miscellaneous symbols': {link: 'Miscellaneous_Symbols', prefix: 26,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\t\t},\r\n\t\t'miscellaneous technical': {link: 'Miscellaneous_Technical', prefix: 23,\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], reserved: ['29','2A'], // 2 deprecated 5.2\r\n\t\t},\r\n\t\t'number forms': {link: 'Number_Forms', prefix: 21, blocks: [5,6,7,8], trim: 4},\r\n\t\t'optical character recognition': {link: 'Optical_Character_Recognition_(Unicode_block)',\r\n\t\t\tprefix: 24, blocks: [4,5], trim: 21, enlarge: 18\r\n\t\t},\r\n\t\t'superscripts and subscripts': {link: 'Superscripts_and_Subscripts_(Unicode_block)', prefix: 20,\r\n\t\t\tblocks: [7,8,9], trim: 3, reserved: ['72','73','8F'], enlarge: 18\r\n\t\t},\r\n\t\t'supplemental arrows-a': {link: 'Supplemental_Arrows-A', prefix: 27, blocks: ['F'], enlarge: 14},\r\n\t\t'supplemental arrows-b': {link: 'Supplemental_Arrows-B', prefix: 29, blocks: [0,1,2,3,4,5,6,7], enlarge: 14},\r\n\t\t'supplemental arrows-c': {link: 'Supplemental_Arrows-C', prefix: '1F8', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'], trim: 39,\r\n\t\t\treserved: ['0C','0D','0E','0F','48','49','4A','4B','4C','4D','4E','4F','5A','5B','5C','5D','5E','5F','88','89','8A','8B','8C','8D',\r\n\t\t\t'8E','8F','AE','AF','BC','BD','BE','BF','C2','C3','C4','C5','C6','C7','C8','C9','CA','CB','CC','CD','CE','CF'],\r\n\t\t},\r\n\t\t'supplemental mathematical operators': {link: 'Supplemental_Mathematical_Operators', prefix: '2A',\r\n\t\t\tblocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\t\t},\r\n\t},\r\n\t// output is sorted by keys\r\n\t\t// things that don't fit anywhere\r\n\t'misc': {\r\n\t\t'armenian': {link: 'Armenian_(Unicode_block)', prefix: '05', blocks: [3,4,5,6,7,8], reserved: ['30','57','58','8B','8C']},\r\n\r\n\t\t'combining diacritical marks': {link: 'Combining_Diacritical_Marks', spacer: true, enlarge: 20,\r\n\t\t\tprefix: '03', blocks: [0,1,2,3,4,5,6], ignored: ['4F'] // ignored: CGJ\r\n\t\t},\r\n\t\t'combining diacritical marks for symbols': {link: 'Combining_Diacritical_Marks_for_Symbols', spacer: true, enlarge: 14,\r\n\t\t\tprefix: 20, blocks: ['D','E','F'], trim: 15\r\n\t\t},\r\n\t\t'combining diacritical marks supplement': {link: 'Combining_Diacritical_Marks_Supplement', spacer: true, enlarge: 20,\r\n\t\t\tprefix: '1D', blocks: ['C','D','E','F']\r\n\t\t},\r\n\t\t'control pictures': {link: 'Control_Pictures', prefix: 24, blocks: [0,1,2,3], trim: 22, enlarge: 16},\r\n\t\t'halfwidth and fullwidth forms': {link: 'Halfwidth_and_Fullwidth_Forms_(Unicode_block)',\r\n\t\t\tprefix: 'FF', blocks: [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E'], trim: 1,\r\n\t\t\treserved: ['00','BF','C0','C1','C8','C9','D0','D1','D8','D9','DD','DE','DF','E7'],\r\n\t\t\tignored: ['A0'] // ignored: HW HF\r\n\t\t},\r\n\t\t'mongolian': {link: 'Mongolian_(Unicode_block)', prefix: 18, blocks: [0,1,2,3,4,5,6,7,8,9,'A'], trim: 5, enlarge: 18,\r\n\t\t\treserved: ['1A','1B','1C','1D','1E','1F','79','7A','7B','7C','7D','7E','7F'],\r\n\t\t\tignored: ['0B','0C','0D','0E','0F'], // ignored FVS1, FVS2, FVS3, MVS, FVS4\r\n\t\t},\r\n\t\t'thanaa': {link: 'Thaana_(Unicode_block)', prefix: '07', blocks: [8,9,'A','B'], trim: 14, enlarge: 16},\r\n\t},\r\n}\r\n\r\nlet oDisplay = {}, aSpacer = []\r\nlet ZWNJ = String.fromCodePoint('0x200C') +' '\r\nlet maxChars = 260, lastUsed, lastName, maxLanguages, maxScripts\r\nconst dTarget = dom.dMeasure\r\nconst sTarget = dom.sMeasure\r\n\r\nfunction strip(value) {return value.replace(/ /g,'')} // strip all spaces\r\nfunction shortcutKey(evt) {\r\n\tevt = evt || window.event\r\n\tif ('ArrowLeft' == evt.key) {\r\n\t\tcycle('back')\r\n\t} else if ('ArrowRight' == evt.key) {\r\n\t\tcycle('forward')\r\n\t} else if ('ArrowUp' == evt.key || 'ArrowDown' == evt.key) {\r\n\t\t// cycle between types\r\n\t\tlet type = lastUsed == 'scripts' ? 'languages' : 'scripts'\r\n\t\trun(type)\r\n\t}\r\n}\r\n\r\nfunction cycle(dir, type) {\r\n\t// on android it's a PITA to use comboboxes\r\n\t// and we can add shortcut keys for desktop\r\n\tif (undefined == type) {type = lastUsed}\r\n\tlet target = document.getElementById(type)\r\n\tif ('back' == dir) {\r\n\t\tlet index = target.selectedIndex\r\n\t\tlet lastIndex = ('script' == type ? maxScripts : maxLanguages) - 1\r\n\t\tlet newIndex = index == 0 ? lastIndex : index - 1\r\n\t\ttarget.selectedIndex = newIndex\r\n\t} else {\r\n\t\ttarget.selectedIndex++\r\n\t\tif (target.value == '') {target.selectedIndex++} // end of list, return to top\r\n\t}\r\n\trun(type)\r\n}\r\n\r\nfunction compare(lang, str) {\r\n\tsTarget.innerHTML = str\r\n\tlet aLang = ['', lang]\r\n\tlet aStyles = ['monospace','sans-serif','serif']\r\n\r\n\tlet data = {}\r\n\taStyles.forEach(function(s){\r\n\t\tdata[s] = []\r\n\t\tdTarget.style.setProperty('font-family', s)\r\n\t\taLang.forEach(function(l){\r\n\t\t\tdTarget.setAttribute('lang', l)\r\n\t\t\t// div height, span width\r\n\t\t\tlet h = dTarget.getBoundingClientRect().height\r\n\t\t\tlet w = sTarget.getBoundingClientRect().width\r\n\t\t\tdata[s].push(w +' x '+ h)\r\n\t\t})\r\n\t})\r\n\treturn data\r\n}\r\n\r\nfunction run_once() {\r\n\t// do once\r\n\t// populate languages\r\n\tlet aLang = [], aFallback = []\r\n\tfor (const k of Object.keys(oSentence).sort()) {\r\n\t\taLang.push('<option value = \"'+ k +'\"> '+ k +'</option>')\r\n\t\taFallback.push(oSentence[k])\r\n\t}\r\n\tmaxLanguages = aLang.length\r\n\tdom.languages.innerHTML = aLang.join('')\r\n\r\n\t// display sentences to help somewhat with async font fasllback\r\n\t// since we measure languages\r\n\tlet sentences = aFallback.join(' '+ ZWNJ +'<br>')\r\n\tdom.fallback.innerHTML = '<p class=\"mono s4\">'+ sentences +'</p>'\r\n\t\t+ '<p class=\"sans s8\">'+ sentences +'</p>'\r\n\t\t+ '<p class=\"serif s12\">'+ sentences +'</p>'\r\n\r\n\t// get display string per script\r\n\tlet oMaxChars = {}\r\n\tlet aSuffix = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']\r\n\toDisplay = {}\r\n\taSpacer = []\r\n\tlet aOptions = []\r\n\tfor (const group of Object.keys(oBlocks).sort()) {\r\n\t\tlet anchor = strip(group)\r\n\t\taOptions.push('<optgroup label = \"'+ anchor.toUpperCase() +'\">')\r\n\t\tfor (const script of Object.keys(oBlocks[group]).sort()) {\r\n\t\t\tlet name = script.toLowerCase()\r\n\t\t\taOptions.push('<option value = \"'+ name +'\"> '+ name +'</option>')\r\n\t\t\t// set vars\r\n\t\t\tlet data = oBlocks[group][script]\r\n\t\t\tlet prefix = data.prefix,\r\n\t\t\t\trange = data.blocks,\r\n\t\t\t\tremove = data['trim'],\r\n\t\t\t\treserved = data.reserved,\r\n\t\t\t\tignored = data.ignored,\r\n\t\t\t\tspacer = data.spacer\r\n\t\t\tif (spacer) {aSpacer.push(name)}\r\n\t\t\t// fixup vars\r\n\t\t\tif (reserved == undefined) {reserved = []}\r\n\t\t\tif (ignored == undefined) {ignored = []}\r\n\t\t\tif (remove == undefined) {remove = 0} else (remove = (Math.abs(remove)) * -1) // ensure negative\r\n\t\t\t// loop\r\n\t\t\tlet aChars = []\r\n\t\t\tlet tmpCodes = [], tmpChars = [], tmpReserved = []\r\n\t\t\tfor (let i = 0; i < range.length; i++) {\r\n\t\t\t\tfor (let j = 0; j < aSuffix.length; j++) {\r\n\t\t\t\t\tlet string = \"0x\"+ prefix + range[i] + aSuffix[j]\r\n\t\t\t\t\tlet match = \"\"+ range[i] + aSuffix[j]\r\n\t\t\t\t\tif (!ignored.includes(match) && !reserved.includes(match)) {\r\n\t\t\t\t\t\taChars.push(String.fromCodePoint(string))\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// trim results\r\n\t\t\tif (remove < 0) {aChars = aChars.slice(0, remove)}\r\n\t\t\t// limit size for display\r\n\t\t\tif (aChars.length > maxChars) {\r\n\t\t\t\toMaxChars[name] = { \r\n\t\t\t\t\t'count': aChars.length,\r\n\t\t\t\t\t'discarded': aChars.slice(maxChars - aChars.length),\r\n\t\t\t\t\t'kept': aChars.slice(0, maxChars)\r\n\t\t\t\t}\r\n\t\t\t\taChars = aChars.slice(0, maxChars)\r\n\t\t\t}\r\n\t\t\toDisplay[name] = aChars\r\n\t\t}\r\n\t}\r\n\tmaxScripts = Object.keys(oDisplay).length\r\n\tif (Object.keys(oMaxChars).length) {\r\n\t\tconsole.log('scripts limited to first ' + maxChars +' chars\\n', oMaxChars)\r\n\t}\r\n\t// populate scripts options\r\n\tdom.scripts.innerHTML = aOptions.join('')\r\n\t// display language\r\n\ttry {dom.lang.innerHTML = navigator.language} catch(e){console.log(e)}\r\n\t// add listener\r\n\tdocument.addEventListener('keydown', shortcutKey)\r\n\t// run first option\r\n\t\t// use scripts to allow more time for font fallback for languages\r\n\trun('scripts')\r\n}\r\n\r\nfunction run(type) {\r\n\tlastUsed = type\r\n\tlet name = document.getElementById(type).value\r\n\tif (lastName == name) {return}\r\n\tlastName = name\r\n\r\n\tconst lang = name.split(' ')[0]\r\n\tconst nolang = '<div class=\"faint\">lang=\"\"</div><div class=\"indent\">'\r\n\r\n\tlet display = '', str\r\n\tif ('scripts' == type) {\r\n\t\tdom.cmono.innerHTML = ''\r\n\t\tdom.csans.innerHTML = ''\r\n\t\tdom.cseri.innerHTML = ''\r\n\r\n\t\t// script\r\n\t\tif (oDisplay[name] !== undefined) {\r\n\t\t\tlet spacer = aSpacer.includes(name) ? ' ' : ''\r\n\t\t\tdisplay = '<span class=\"chars\">'+ spacer + oDisplay[name].join(ZWNJ + spacer) + '</span>'\r\n\t\t}\r\n\t} else {\r\n\t\t// language\r\n\t\tstr = oSentence[name]\r\n\t\tdisplay = nolang + str +'</div>'\r\n\t\t\t+ '<br><div class=\"faint\" lang=\\\"'+ lang +'\\\">'+ 'lang=\"'+ lang +'\"</div>'\r\n\t\t\t+ '<div class=\"indent\" lang=\"' + lang +'\">' + str +'</div>'\r\n\t}\r\n\tdom.divInfo.innerHTML = name\r\n\tdom.divType.innerHTML = type\r\n\tdom.divMono.innerHTML = display\r\n\tdom.divSans.innerHTML = display\r\n\tdom.divSerif.innerHTML = display\r\n\r\n\tif ('languages' == type) {\r\n\t\tlet data = compare(lang, str)\r\n\t\tfor (const k of Object.keys(data)) {\r\n\t\t\tlet isMatch = data[k][0] == data[k][1]\r\n\t\t\tlet key = 'c' + k.slice(0,4)\r\n\t\t\tlet target = dom[key]\r\n\t\t\tlet datastr = isMatch ? data[k][0] : data[k].join(' | ')\r\n\t\t\ttarget.innerHTML = (isMatch ? s99 : s3) +'['+ datastr +']'+ sc\r\n\t\t}\r\n\t}\r\n\r\n}\r\n\r\nrun_once()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/functionprops.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>function properties</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 780px}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n<span translate=\"no\">\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#misc\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb18\">\r\n\t\t<thead><tr><th>\r\n\t\t\t<div class=\"nav-title\">function properties\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"totalCount\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td class=\"intro\">\r\n\t\t\t<span class=\"no_color\"></span>\r\n\t\t\t\t<span class=\"btn18 btnfirst\" onClick=\"run()\">[ run ]</span> &nbsp; &nbsp;\r\n\t\t\t\t<select name=\"items\" id=\"optList\" onChange=\"run()\"></select> &nbsp; &nbsp;\r\n\t\t\t\t<input type=\"checkbox\" id=\"optSort\"> <span class=\"no_color\">sort\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td><hr></td></tr>\r\n\t\t<tr><td></td></tr>\r\n\t\t<tr><td style=\"text-align: left; color: var(--test0);\" class=\"mono spaces\" id=\"details\"></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\nlet oData = {}\r\nlet aEverything = []\r\nlet maxPad = 0\r\n\r\nlet stuff = {\r\nleftovers: [\r\n// events\r\n'AnimationEvent','AnimationPlaybackEvent','BeforeUnloadEvent','BlobEvent','ClipboardEvent',\r\n'CloseEvent','CompositionEvent','ContentVisibilityAutoStateChangeEvent','CustomEvent',\r\n'DragEvent','ErrorEvent','FocusEvent','FormDataEvent','HashChangeEvent','InputEvent',\r\n'KeyboardEvent','MessageEvent','MutationEvent','PageTransitionEvent','PopStateEvent',\r\n'PopupBlockedEvent','ProgressEvent','PromiseRejectionEvent','ScrollAreaEvent',\r\n'SecurityPolicyViolationEvent','SubmitEvent','TaskPriorityChangeEvent','TimeEvent',\r\n'ToggleEvent','TrackEvent','TransitionEvent','UIEvent','WheelEvent',\r\n\r\n'Array','ArrayBuffer','DataView',\r\n'BigInt','Boolean','Number','String',\r\n'AbortController','AbortSignal',\r\n'AbstractRange',\r\n'Animation','AnimationEffect','AnimationTimeline',\r\n'Attr',\r\n'AuthenticatorAssertionResponse','AuthenticatorAttestationResponse','AuthenticatorResponse',\r\n'Blob',\r\n'BroadcastChannel',\r\n'ByteLengthQueuingStrategy', // streams\r\n'CDATASection',\r\n'CaretPosition',\r\n'CharacterData',\r\n'Clipboard','ClipboardItem',\r\n'Comment',\r\n'CompressionStream',\r\n'CountQueuingStrategy', // streams\r\n'Credential','CredentialsContainer',\r\n'Crypto','CryptoKey',\r\n'CustomElementRegistry',\r\n'DOMException',\r\n'DOMImplementation',\r\n'DOMMatrix', 'DOMMatrixReadOnly',\r\n'DOMParser',\r\n'DOMPoint','DOMPointReadOnly',\r\n'DOMQuad','DOMRect','DOMRectList','DOMRectReadOnly',\r\n'DOMRequest',\r\n'DOMStringList',\r\n'DOMStringMap',\r\n'DOMTokenList',\r\n'DataTransfer','DataTransferItem','DataTransferItemList',\r\n'Date',\r\n'DecompressionStream',\r\n'Directory',\r\n'Document',\r\n'DocumentFragment',\r\n'DocumentTimeline',\r\n'DocumentType',\r\n'Event',\r\n'EventCounts',\r\n'EventSource',\r\n'EventTarget',\r\n'File',\r\n'FileList',\r\n'FileReader',\r\n'FinalizationRegistry',\r\n'FormData',\r\n'Headers',\r\n'Highlight','HighlightRegistry',\r\n'History',\r\n'IdentityCredential',\r\n'IdleDeadline',\r\n'Image','ImageBitmap','ImageBitmapRenderingContext','ImageData',\r\n'IntersectionObserver','IntersectionObserverEntry',\r\n'KeyframeEffect',\r\n'LargestContentfulPaint',\r\n'Location', // the url\r\n'Lock',\r\n'LockManager',\r\n'Map',\r\n'MessageChannel','MessagePort',\r\n'MutationObserver','MutationRecord',\r\n'NamedNodeMap',\r\n'NavigationPreloadManager',\r\n'Node',\r\n'NodeIterator',\r\n'NodeList',\r\n'Object',\r\n'PaintRequest','PaintRequestList',\r\n'PermissionStatus','Permissions',\r\n'ProcessingInstruction',\r\n'Promise',\r\n'PublicKeyCredential',\r\n'PushManager','PushSubscription','PushSubscriptionOptions',\r\n'RadioNodeList',\r\n'Range',\r\n'ReadableByteStreamController','ReadableStream','ReadableStreamBYOBReader','ReadableStreamBYOBRequest','ReadableStreamDefaultController','ReadableStreamDefaultReader',\r\n'RegExp',\r\n'Request',\r\n'ResizeObserver','ResizeObserverEntry','ResizeObserverSize',\r\n'Response',\r\n'Scheduler',\r\n'Screen','ScreenOrientation',\r\n'Selection',\r\n'Set',\r\n'ShadowRoot',\r\n'SharedWorker',\r\n'StaticRange',\r\n'SubtleCrypto',\r\n'Symbol',\r\n'TaskController',\r\n'TaskSignal',\r\n'Text',\r\n'TextDecoder','TextDecoderStream','TextEncoder','TextEncoderStream',\r\n'TimeRanges',\r\n'TransformStream',\r\n'TransformStreamDefaultController',\r\n'TreeWalker',\r\n'URL',\r\n'URLSearchParams',\r\n'UserActivation',\r\n'ValidityState',\r\n'VisualViewport',\r\n'WakeLock','WakeLockSentinel',\r\n'WeakMap','WeakRef','WeakSet',\r\n'WebSocket',\r\n'WebTransport','WebTransportBidirectionalStream','WebTransportDatagramDuplexStream','WebTransportReceiveStream','WebTransportSendStream',\r\n'Worklet',\r\n'WritableStream','WritableStreamDefaultController','WritableStreamDefaultWriter',\r\n'XMLDocument',\r\n'XMLHttpRequest',\r\n'XMLHttpRequestEventTarget',\r\n'XMLHttpRequestUpload',\r\n'XMLSerializer',\r\n'XPathEvaluator',\r\n'XPathExpression',\r\n'XPathResult',\r\n'XSLTProcessor',\r\n/*\r\n\r\n'BigInt64Array','BigUint64Array','Float32Array','Float64Array','Int16Array','Int32Array','Int8Array','Uint16Array','Uint32Array','Uint8Array','Uint8ClampedArray',\r\n\t// ^ all = BYTES_PER_ELEMENT, constructor\r\n'BarProp', // obsolete\r\n'SVGMatrix','WebKitCSSMatrix' // aliases of DOMMatrix\r\n'Window', // = constructor\r\n*/\r\n]}\r\n//console.log(stuff.leftovers.length)\r\n\r\nlet oList = {\r\n/*\r\n\t\"TEMPLATE\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\ttitle: 'main',\r\n\t\t\t\tdesc: '',\r\n\t\t\t\tprefix: '',\r\n\t\t\t\titems: [\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n*/\r\n\r\n\t\"_testing\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\ttitle: 'devices',\r\n\t\t\t\titems: [\r\n'DeviceMotionEvent','DeviceOrientationEvent',\r\n'Gamepad','GamepadAxisMoveEvent','GamepadButton','GamepadButtonEvent','GamepadEvent','GamepadHapticActuator','GamepadPose',\r\n'MediaDeviceInfo','MediaDevices',\r\n'MimeType','MimeTypeArray',\r\n'MouseEvent','MouseScrollEvent',\r\n'Plugin','PluginArray',\r\n'PointerEvent',\r\n'SpeechSynthesis','SpeechSynthesisErrorEvent','SpeechSynthesisEvent','SpeechSynthesisUtterance','SpeechSynthesisVoice',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t2: {\r\n\t\t\t\ttitle: 'fonts',\r\n\t\t\t\titems: [\r\n'FontFace','FontFaceSet','FontFaceSetLoadEvent','TextMetrics',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t3: {\r\n\t\t\t\ttitle: \"region\",\r\n\t\t\t\titems: [\r\n'Geolocation','GeolocationCoordinates','GeolocationPosition','GeolocationPositionError',]\r\n\t\t\t},\r\n\t\t\t4: {\r\n\t\t\t\ttitle: 'errors',\r\n\t\t\t\titems: [\r\n'AggregateError','Error','EvalError','GPUError','InternalError','RangeError',\r\n'ReferenceError','SyntaxError','TypeError','URIError','WebTransportError',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"All\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\"a\": {items: ['AbortController','AbortSignal','AbstractRange','AggregateError','AnalyserNode','Animation','AnimationEffect','AnimationEvent','AnimationPlaybackEvent','AnimationTimeline','Array','ArrayBuffer','Attr','Audio','AudioBuffer','AudioBufferSourceNode','AudioContext','AudioDestinationNode','AudioListener','AudioNode','AudioParam','AudioParamMap','AudioProcessingEvent','AudioScheduledSourceNode','AudioWorklet','AudioWorkletNode','AuthenticatorAssertionResponse','AuthenticatorAttestationResponse','AuthenticatorResponse',]},\r\n\"b\": {items: ['BarProp','BaseAudioContext','BeforeUnloadEvent','BigInt','BigInt64Array','BigUint64Array','BiquadFilterNode','Blob','BlobEvent','Boolean','BroadcastChannel','ByteLengthQueuingStrategy',]},\r\n\"c\": {items: ['CDATASection','CSS2Properties','CSSAnimation','CSSConditionRule','CSSContainerRule','CSSCounterStyleRule','CSSFontFaceRule','CSSFontFeatureValuesRule','CSSFontPaletteValuesRule','CSSGroupingRule','CSSImportRule','CSSKeyframeRule','CSSKeyframesRule','CSSLayerBlockRule','CSSLayerStatementRule','CSSMediaRule','CSSMozDocumentRule','CSSNamespaceRule','CSSPageRule','CSSPropertyRule','CSSRule','CSSRuleList','CSSStyleDeclaration','CSSStyleRule','CSSStyleSheet','CSSSupportsRule','CSSTransition','Cache','CacheStorage','CanvasCaptureMediaStream','CanvasGradient','CanvasPattern','CanvasRenderingContext2D','CaretPosition','ChannelMergerNode','ChannelSplitterNode','CharacterData','Clipboard','ClipboardEvent','ClipboardItem','CloseEvent','Comment','CompositionEvent','CompressionStream','ConstantSourceNode','ContentVisibilityAutoStateChangeEvent','ConvolverNode','CountQueuingStrategy','Credential','CredentialsContainer','Crypto','CryptoKey','CustomElementRegistry','CustomEvent',]},\r\n\"d\": {items: ['DOMException','DOMImplementation','DOMMatrix','DOMMatrixReadOnly','DOMParser','DOMPoint','DOMPointReadOnly','DOMQuad','DOMRect','DOMRectList','DOMRectReadOnly','DOMRequest','DOMStringList','DOMStringMap','DOMTokenList','DataTransfer','DataTransferItem','DataTransferItemList','DataView','Date','DecompressionStream','DelayNode','DeviceMotionEvent','DeviceOrientationEvent','Directory','Document','DocumentFragment','DocumentTimeline','DocumentType','DragEvent','DynamicsCompressorNode',]},\r\n\"e\": {items: ['Element','ElementInternals','EncodedVideoChunk','Error','ErrorEvent','EvalError','Event','EventCounts','EventSource','EventTarget',]},\r\n\"f\": {items: ['File','FileList','FileReader','FileSystem','FileSystemDirectoryEntry','FileSystemDirectoryHandle','FileSystemDirectoryReader','FileSystemEntry','FileSystemFileEntry','FileSystemFileHandle','FileSystemHandle','FileSystemWritableFileStream','FinalizationRegistry','Float32Array','Float64Array','FocusEvent','FontFace','FontFaceSet','FontFaceSetLoadEvent','FormData','FormDataEvent','Function',]},\r\n\"g\": {items: ['GPU','GPUAdapter','GPUAdapterInfo','GPUBindGroup','GPUBindGroupLayout','GPUBuffer','GPUCanvasContext','GPUCommandBuffer','GPUCommandEncoder','GPUCompilationInfo','GPUCompilationMessage','GPUComputePassEncoder','GPUComputePipeline','GPUDevice','GPUDeviceLostInfo','GPUError','GPUInternalError','GPUOutOfMemoryError','GPUPipelineLayout','GPUQuerySet','GPUQueue','GPURenderBundle','GPURenderBundleEncoder','GPURenderPassEncoder','GPURenderPipeline','GPUSampler','GPUShaderModule','GPUSupportedFeatures','GPUSupportedLimits','GPUTexture','GPUTextureView','GPUUncapturedErrorEvent','GPUValidationError','GainNode','Gamepad','GamepadAxisMoveEvent','GamepadButton','GamepadButtonEvent','GamepadEvent','GamepadHapticActuator','GamepadPose','Geolocation','GeolocationCoordinates','GeolocationPosition','GeolocationPositionError',]},\r\n\"h\": {items: ['HTMLAllCollection','HTMLAnchorElement','HTMLAreaElement','HTMLAudioElement','HTMLBRElement','HTMLBaseElement','HTMLBodyElement','HTMLButtonElement','HTMLCanvasElement','HTMLCollection','HTMLDListElement','HTMLDataElement','HTMLDataListElement','HTMLDetailsElement','HTMLDialogElement','HTMLDirectoryElement','HTMLDivElement','HTMLDocument','HTMLElement','HTMLEmbedElement','HTMLFieldSetElement','HTMLFontElement','HTMLFormControlsCollection','HTMLFormElement','HTMLFrameElement','HTMLFrameSetElement','HTMLHRElement','HTMLHeadElement','HTMLHeadingElement','HTMLHtmlElement','HTMLIFrameElement','HTMLImageElement','HTMLInputElement','HTMLLIElement','HTMLLabelElement','HTMLLegendElement','HTMLLinkElement','HTMLMapElement','HTMLMarqueeElement','HTMLMediaElement','HTMLMenuElement','HTMLMetaElement','HTMLMeterElement','HTMLModElement','HTMLOListElement','HTMLObjectElement','HTMLOptGroupElement','HTMLOptionElement','HTMLOptionsCollection','HTMLOutputElement','HTMLParagraphElement','HTMLParamElement','HTMLPictureElement','HTMLPreElement','HTMLProgressElement','HTMLQuoteElement','HTMLScriptElement','HTMLSelectElement','HTMLSlotElement','HTMLSourceElement','HTMLSpanElement','HTMLStyleElement','HTMLTableCaptionElement','HTMLTableCellElement','HTMLTableColElement','HTMLTableElement','HTMLTableRowElement','HTMLTableSectionElement','HTMLTemplateElement','HTMLTextAreaElement','HTMLTimeElement','HTMLTitleElement','HTMLTrackElement','HTMLUListElement','HTMLUnknownElement','HTMLVideoElement','HashChangeEvent','Headers','Highlight','HighlightRegistry','History',]},\r\n\"i\": {items: ['IDBCursor','IDBCursorWithValue','IDBDatabase','IDBFactory','IDBIndex','IDBKeyRange','IDBObjectStore','IDBOpenDBRequest','IDBRequest','IDBTransaction','IDBVersionChangeEvent','IIRFilterNode','IdentityCredential','IdleDeadline','Image','ImageBitmap','ImageBitmapRenderingContext','ImageData','InputEvent','Int16Array','Int32Array','Int8Array','InternalError','IntersectionObserver','IntersectionObserverEntry',]},\r\n\"k\": {items: ['KeyboardEvent','KeyframeEffect',]},\r\n\"l\": {items: ['LargestContentfulPaint','Location','Lock','LockManager',]},\r\n\"m\": {items: ['MIDIAccess','MIDIConnectionEvent','MIDIInput','MIDIInputMap','MIDIMessageEvent','MIDIOutput','MIDIOutputMap','MIDIPort','Map','MathMLElement','MediaCapabilities','MediaCapabilitiesInfo','MediaDeviceInfo','MediaDevices','MediaElementAudioSourceNode','MediaEncryptedEvent','MediaError','MediaKeyError','MediaKeyMessageEvent','MediaKeySession','MediaKeyStatusMap','MediaKeySystemAccess','MediaKeys','MediaList','MediaMetadata','MediaQueryList','MediaQueryListEvent','MediaRecorder','MediaRecorderErrorEvent','MediaSession','MediaSource','MediaStream','MediaStreamAudioDestinationNode','MediaStreamAudioSourceNode','MediaStreamEvent','MediaStreamTrack','MediaStreamTrackAudioSourceNode','MediaStreamTrackEvent','MessageChannel','MessageEvent','MessagePort','MimeType','MimeTypeArray','MouseEvent','MouseScrollEvent','MutationEvent','MutationObserver','MutationRecord',]},\r\n\"n\": {items: ['NamedNodeMap','NavigationPreloadManager','Navigator','Node','NodeIterator','NodeList','Notification','Number',]},\r\n\"o\": {items: ['Object','OfflineAudioCompletionEvent','OfflineAudioContext','OffscreenCanvas','OffscreenCanvasRenderingContext2D','Option','OscillatorNode',]},\r\n\"p\": {items: ['PageTransitionEvent','PaintRequest','PaintRequestList','PannerNode','Path2D','Performance','PerformanceEntry','PerformanceEventTiming','PerformanceMark','PerformanceMeasure','PerformanceNavigation','PerformanceNavigationTiming','PerformanceObserver','PerformanceObserverEntryList','PerformancePaintTiming','PerformanceResourceTiming','PerformanceServerTiming','PerformanceTiming','PeriodicWave','PermissionStatus','Permissions','Plugin','PluginArray','PointerEvent','PopStateEvent','PopupBlockedEvent','ProcessingInstruction','ProgressEvent','Promise','PromiseRejectionEvent','PublicKeyCredential','PushManager','PushSubscription','PushSubscriptionOptions',]},\r\n\"r\": {items: ['RTCCertificate','RTCDTMFSender','RTCDTMFToneChangeEvent','RTCDataChannel','RTCDataChannelEvent','RTCDtlsTransport','RTCEncodedAudioFrame','RTCEncodedVideoFrame','RTCIceCandidate','RTCPeerConnection','RTCPeerConnectionIceEvent','RTCRtpReceiver','RTCRtpScriptTransform','RTCRtpSender','RTCRtpTransceiver','RTCSctpTransport','RTCSessionDescription','RTCStatsReport','RTCTrackEvent','RadioNodeList','Range','RangeError','ReadableByteStreamController','ReadableStream','ReadableStreamBYOBReader','ReadableStreamBYOBRequest','ReadableStreamDefaultController','ReadableStreamDefaultReader','ReferenceError','RegExp','Request','ResizeObserver','ResizeObserverEntry','ResizeObserverSize','Response',]},\r\n\"s\": {items: ['SVGAElement','SVGAngle','SVGAnimateElement','SVGAnimateMotionElement','SVGAnimateTransformElement','SVGAnimatedAngle','SVGAnimatedBoolean','SVGAnimatedEnumeration','SVGAnimatedInteger','SVGAnimatedLength','SVGAnimatedLengthList','SVGAnimatedNumber','SVGAnimatedNumberList','SVGAnimatedPreserveAspectRatio','SVGAnimatedRect','SVGAnimatedString','SVGAnimatedTransformList','SVGAnimationElement','SVGCircleElement','SVGClipPathElement','SVGComponentTransferFunctionElement','SVGDefsElement','SVGDescElement','SVGElement','SVGEllipseElement','SVGFEBlendElement','SVGFEColorMatrixElement','SVGFEComponentTransferElement','SVGFECompositeElement','SVGFEConvolveMatrixElement','SVGFEDiffuseLightingElement','SVGFEDisplacementMapElement','SVGFEDistantLightElement','SVGFEDropShadowElement','SVGFEFloodElement','SVGFEFuncAElement','SVGFEFuncBElement','SVGFEFuncGElement','SVGFEFuncRElement','SVGFEGaussianBlurElement','SVGFEImageElement','SVGFEMergeElement','SVGFEMergeNodeElement','SVGFEMorphologyElement','SVGFEOffsetElement','SVGFEPointLightElement','SVGFESpecularLightingElement','SVGFESpotLightElement','SVGFETileElement','SVGFETurbulenceElement','SVGFilterElement','SVGForeignObjectElement','SVGGElement','SVGGeometryElement','SVGGradientElement','SVGGraphicsElement','SVGImageElement','SVGLength','SVGLengthList','SVGLineElement','SVGLinearGradientElement','SVGMPathElement','SVGMarkerElement','SVGMaskElement','SVGMatrix','SVGMetadataElement','SVGNumber','SVGNumberList','SVGPathElement','SVGPatternElement','SVGPoint','SVGPointList','SVGPolygonElement','SVGPolylineElement','SVGPreserveAspectRatio','SVGRadialGradientElement','SVGRect','SVGRectElement','SVGSVGElement','SVGScriptElement','SVGSetElement','SVGStopElement','SVGStringList','SVGStyleElement','SVGSwitchElement','SVGSymbolElement','SVGTSpanElement','SVGTextContentElement','SVGTextElement','SVGTextPathElement','SVGTextPositioningElement','SVGTitleElement','SVGTransform','SVGTransformList','SVGUseElement','SVGViewElement','Scheduler','Screen','ScreenOrientation','ScriptProcessorNode','ScrollAreaEvent','SecurityPolicyViolationEvent','Selection','ServiceWorker','ServiceWorkerContainer','ServiceWorkerRegistration','Set','ShadowRoot','SharedWorker','SourceBuffer','SourceBufferList','SpeechSynthesis','SpeechSynthesisErrorEvent','SpeechSynthesisEvent','SpeechSynthesisUtterance','SpeechSynthesisVoice','StaticRange','StereoPannerNode','Storage','StorageEvent','StorageManager','String','StyleSheet','StyleSheetList','SubmitEvent','SubtleCrypto','Symbol','SyntaxError',]},\r\n\"t\": {items: ['TaskController','TaskPriorityChangeEvent','TaskSignal','Text','TextDecoder','TextDecoderStream','TextEncoder','TextEncoderStream','TextMetrics','TextTrack','TextTrackCue','TextTrackCueList','TextTrackList','TimeEvent','TimeRanges','ToggleEvent','TrackEvent','TransformStream','TransformStreamDefaultController','TransitionEvent','TreeWalker','TypeError',]},\r\n\"u\": {items: ['UIEvent','URIError','URL','URLSearchParams','Uint16Array','Uint32Array','Uint8Array','Uint8ClampedArray','UserActivation',]},\r\n\"v\": {items: ['VTTCue','VTTRegion','ValidityState','VideoColorSpace','VideoDecoder','VideoFrame','VideoPlaybackQuality','VisualViewport',]},\r\n\"w\": {items: ['WakeLock','WakeLockSentinel','WaveShaperNode','WeakMap','WeakRef','WeakSet','WebGL2RenderingContext','WebGLActiveInfo','WebGLBuffer','WebGLContextEvent','WebGLFramebuffer','WebGLProgram','WebGLQuery','WebGLRenderbuffer','WebGLRenderingContext','WebGLSampler','WebGLShader','WebGLShaderPrecisionFormat','WebGLSync','WebGLTexture','WebGLTransformFeedback','WebGLUniformLocation','WebGLVertexArrayObject','WebKitCSSMatrix','WebSocket','WebTransport','WebTransportBidirectionalStream','WebTransportDatagramDuplexStream','WebTransportError','WebTransportReceiveStream','WebTransportSendStream','WheelEvent','Window','Worker','Worklet','WritableStream','WritableStreamDefaultController','WritableStreamDefaultWriter',]},\r\n\"x\": {items: ['XMLDocument','XMLHttpRequest','XMLHttpRequestEventTarget','XMLHttpRequestUpload','XMLSerializer','XPathEvaluator','XPathExpression','XPathResult','XSLTProcessor',]},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Audio\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'Audio',\r\n\t\t\t\tignore: [\r\n'Audio','AnalyserNode','BaseAudioContext','BiquadFilterNode','ChannelMergerNode','ChannelSplitterNode',\r\n'ConvolverNode','ConstantSourceNode','DelayNode','DynamicsCompressorNode','GainNode','IIRFilterNode',\r\n'MediaElementAudioSourceNode','OfflineAudioContext','OfflineAudioCompletionEvent','OscillatorNode',\r\n'PannerNode','PeriodicWave','ScriptProcessorNode','StereoPannerNode','WaveShaperNode',\r\n\t\t\t\t],\r\n\t\t\t\titems: [\r\n'Buffer','BufferSourceNode','Context','DestinationNode','Listener','Node',\r\n'Param','ParamMap','ProcessingEvent','ScheduledSourceNode','Worklet','WorkletNode',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Canvas\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'Canvas',\r\n\t\t\t\tignore: ['OffscreenCanvas','OffscreenCanvasRenderingContext2D','Path2D',],\r\n\t\t\t\titems: ['CaptureMediaStream','Gradient','Pattern','RenderingContext2D',],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"CSS\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'CSS',\r\n\t\t\t\tsuffix: 'Rule',\r\n\t\t\t\tignore: [\r\n'CSSAnimation','CSSRule','CSSRuleList','CSSStyleDeclaration','CSSStyleSheet','CSSTransition','StyleSheet','StyleSheetList',\r\n\t\t\t\t],\r\n\t\t\t\titems: [\r\n'Condition','Container','CounterStyle','FontFace','FontFeatureValues','FontPaletteValues','Grouping','Import','Keyframe',\r\n'Keyframes','LayerBlock','LayerStatement','Media','MozDocument','Namespace','Page','Property','Style','Supports',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Element\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t4: {\r\n\t\t\t\t// changes since FF104 (but stable since FF111 - that is not in current ESR115 cycle)\r\n\t\t\t\t// last checked 122\r\n\t\t\t\tdesc: \"checkin'\",\r\n\t\t\t\titems: ['ElementInternals',],\r\n\t\t\t},\r\n\t\t\t5: {\r\n\t\t\t\t// changes since FF115 (at least on windows) or exhibit extension fuckery\r\n\t\t\t\t// future = beta/nightly/experiment = will eventually land\r\n\t\t\t\ttitle: 'main',\r\n\t\t\t\tdesc: \"changes since FF115 | show extension tampering\",\r\n\t\t\t\tprefix: 'HTML',\r\n\t\t\t\tsuffix: 'Element',\r\n\t\t\t\tignore: [\r\n\t\t\t\t\t'Element',\r\n\t\t\t\t\t\t// future: setHTMLUnsafe, getBoxQuads, convertQuadFromNode, convertRectFromNode, convertPointFromNode\r\n\t\t\t\t\t\t// 119: role, 41 x aria*\r\n\t\t\t\t\t'HTMLElement',\r\n\t\t\t\t\t\t// future: showPopover, hidePopover, togglePopover, popover, onbeforetoggle, -ondragexit\r\n\t\t\t\t\t\t// 117: oncancel\r\n\t\t\t\t\t'MathMLElement',\r\n\t\t\t\t\t\t// 117: onauxclick, -oncancel, -onauxclick,\r\n\t\t\t\t],\r\n\t\t\t\titems: [\r\n\t\t\t\t\t'Button',\r\n\t\t\t\t\t\t// future: popoverTargetElement, popoverTargetAction\r\n\t\t\t\t\t'Dialog', // new 98\r\n\t\t\t\t\t\t// 139: requestClose (1960556)\r\n\t\t\t\t\t'Details',\r\n\t\t\t\t\t\t// 130n: name (1856460: dom.details_group.enabled)\r\n\t\t\t\t\t'Frame',\r\n\t\t\t\t\t\t// ext fuckery\r\n\t\t\t\t\t'IFrame',\r\n\t\t\t\t\t\t// 121: loading\r\n\t\t\t\t\t'Image',\r\n\t\t\t\t\t\t// 126n: 1882548 fetchPriority\r\n\t\t\t\t\t'Input',\r\n\t\t\t\t\t\t// 116: dirName\r\n\t\t\t\t\t'Link',\r\n\t\t\t\t\t\t// 126n: 1882548 fetchPriority\r\n\t\t\t\t\t'Marquee',\r\n\t\t\t\t\t\t// added 65\r\n\t\t\t\t\t\t// 126: 1689705 removed -onbounce, -onfinish, -onstart\r\n\t\t\t\t\t'Media',\r\n\t\t\t\t\t\t// future: allowedToPlay\r\n\t\t\t\t\t\t// 116: setSinkId, sinkId\r\n\t\t\t\t\t'Object',\r\n\t\t\t\t\t\t// ext fuckery\r\n\t\t\t\t\t'Select',\r\n\t\t\t\t\t\t// 122b: showPicker\r\n\t\t\t\t\t'Script',\r\n\t\t\t\t\t\t// 126n: 1882548 fetchPriority\r\n\t\t\t\t\t'Template',\r\n\t\t\t\t\t\t// future: shadowRootMode, shadowRootDelegatesFocus\r\n\t\t\t\t\t'TextArea',\r\n\t\t\t\t\t\t// 116: dirName\r\n\t\t\t\t\t'Video',\r\n\t\t\t\t\t\t// 122b: disablePictureInPicture\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t6: {\r\n\t\t\t\t// changes since FF104 (but stable since FF111 - that is not in current ESR115 cycle)\r\n\t\t\t\t// last checked 122\r\n\t\t\t\ttitle: 'recent',\r\n\t\t\t\tdesc: \"changes since FF104 (stable since FF111)\",\r\n\t\t\t\tprefix: 'HTML',\r\n\t\t\t\tsuffix: 'Element',\r\n\t\t\t\titems: [\r\n\t\t\t\t\t'Canvas', // last change 105 = cf7b9afc\r\n\t\t\t\t\t'Form', // last change 111 = bff84eef\r\n\t\t\t\t\t'Meta', // last change 106 = d32c1bc0\r\n\t\t\t\t\t'Source', // last change 108 = e74ef3df\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t7: {\r\n\t\t\t\t// stable since FF92\r\n\t\t\t\ttitle: 'stable92',\r\n\t\t\t\tdesc: 'AFAICT FF92+',\r\n\t\t\t\tprefix: 'HTML',\r\n\t\t\t\tsuffix: 'Element',\r\n\t\t\t\titems: [\r\n\t\t\t\t\t'Body',\r\n\t\t\t\t\t'FrameSet',\r\n\t\t\t\t\t'Menu', // changed 85\r\n\t\t\t\t\t'Meter',\r\n\t\t\t\t\t'Output',\r\n\t\t\t\t\t'Param', // changed 85\r\n\t\t\t\t\t'Progress',\r\n\t\t\t\t\t'Slot', // new in 63, changed 66 92\r\n\t\t\t\t\t'Style',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t8: {\r\n\t\t\t\t// stable since FF70\r\n\t\t\t\ttitle: 'stable70',\r\n\t\t\t\tdesc: 'AFAICT FF70+',\r\n\t\t\t\tignore: [\r\n\t\t\t\t\t'HTMLAllCollection', // changed 64\r\n\t\t\t\t\t'HTMLDocument' // changed 57, 58, 60, 61, 68, 69, 70\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t9: {\r\n\t\t\t\t// stable since FF52\r\n\t\t\t\ttitle: 'stable52',\r\n\t\t\t\tdesc: 'AFAICT FF52+ | since items added',\r\n\t\t\t\tprefix: 'HTML',\r\n\t\t\t\tsuffix: 'Element',\r\n\t\t\t\tignore: ['HTMLCollection','HTMLFormControlsCollection','HTMLOptionsCollection'], // 'Option', // = same as HTMLOptionElement\r\n\t\t\t\titems: [\r\n\t\t\t\t\t'Anchor','Area','Audio','BR','Base','DList','Data','DataList',\r\n\t\t\t\t\t'Directory','Div','Embed','FieldSet','Font','HR','Head','Heading',\r\n\t\t\t\t\t'Html','Label','LI','Legend','Map',\r\n\t\t\t\t\t'Mod','OList','OptGroup','Option',\r\n\t\t\t\t\t'Paragraph','Picture','Pre','Quote','Span',\r\n\t\t\t\t\t'Table','TableCaption','TableCell','TableCol','TableRow',\r\n\t\t\t\t\t'TableSection','Time','Title','Track','UList','Unknown',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"GPU\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'GPU',\r\n\t\t\t\tignore: ['GPU'],\r\n\t\t\t\titems: [\r\n'Adapter','AdapterInfo','BindGroup','BindGroupLayout','Buffer','CanvasContext','CommandBuffer',\r\n'CommandEncoder','CompilationInfo','CompilationMessage','ComputePassEncoder','ComputePipeline',\r\n'Device','DeviceLostInfo','InternalError','OutOfMemoryError','PipelineLayout','QuerySet',\r\n'Queue','RenderBundle','RenderBundleEncoder','RenderPassEncoder','RenderPipeline','Sampler',\r\n'ShaderModule','SupportedFeatures','SupportedLimits','Texture','TextureView','UncapturedErrorEvent',\r\n'ValidationError',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Media\": {\r\n\t\t// webcodecs\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\ttitle: 'media',\r\n\t\t\t\tprefix: 'Media',\r\n\t\t\t\tignore: ['SourceBuffer','SourceBufferList',],\r\n\t\t\t\titems: [\r\n'Capabilities','CapabilitiesInfo','EncryptedEvent',\r\n'Error','KeyError','KeyMessageEvent','KeySession','KeyStatusMap','KeySystemAccess','Keys','List',\r\n'Metadata','QueryList','QueryListEvent','Recorder','RecorderErrorEvent','Session','Source',\r\n'Stream','StreamAudioDestinationNode','StreamAudioSourceNode','StreamEvent','StreamTrack',\r\n'StreamTrackAudioSourceNode','StreamTrackEvent',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t2: {\r\n\t\t\t\ttitle: 'midi',\r\n\t\t\t\tprefix: 'MIDI',\r\n\t\t\t\titems: [\r\n'Access','ConnectionEvent','Input','InputMap','MessageEvent','Output','OutputMap','Port',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t5: {\r\n\t\t\t\ttitle: 'video',\r\n\t\t\t\tprefix: 'Video',\r\n\t\t\t\tignore: [\r\n'EncodedVideoChunk','TextTrack','TextTrackCue','TextTrackCueList','TextTrackList','VTTCue','VTTRegion',\r\n\t\t\t\t],\r\n\t\t\t\titems: [\r\n'ColorSpace','Decoder','Frame','PlaybackQuality',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Performance\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'Performance',\r\n\t\t\t\tignore: ['Performance'],\r\n\t\t\t\titems: [\r\n'Entry','EventTiming','Mark','Measure','Navigation','NavigationTiming','Observer',\r\n'ObserverEntryList','PaintTiming','ResourceTiming','ServerTiming','Timing',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"Storage\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t4: {\r\n\t\t\t\ttitle: 'filesystem',\r\n\t\t\t\tprefix: 'FileSystem',\r\n\t\t\t\tignore: ['FileSystem',],\r\n\t\t\t\titems: [\r\n'DirectoryEntry','DirectoryHandle','DirectoryReader','Entry',\r\n'FileEntry','FileHandle','Handle','WritableFileStream',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t5: {\r\n\t\t\t\ttitle: 'idb',\r\n\t\t\t\tprefix: 'IDB',\r\n\t\t\t\titems: [\r\n'Cursor','CursorWithValue','Database','Factory','Index','KeyRange','ObjectStore',\r\n'OpenDBRequest','Request','Transaction','VersionChangeEvent',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t8: {\r\n\t\t\t\ttitle: 'storage',\r\n\t\t\t\tignore: [\r\n'Cache','CacheStorage',\r\n'Storage','StorageEvent','StorageManager',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t\t9: {\r\n\t\t\t\ttitle: 'workers',\r\n\t\t\t\tignore: [\r\n'Notification','ServiceWorker','ServiceWorkerContainer','ServiceWorkerRegistration','Worker',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"SVG\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'SVG',\r\n\t\t\t\titems: [\r\n'AElement','Angle','AnimateElement','AnimateMotionElement','AnimateTransformElement','AnimatedAngle',\r\n'AnimatedBoolean','AnimatedEnumeration','AnimatedInteger','AnimatedLength','AnimatedLengthList',\r\n'AnimatedNumber','AnimatedNumberList','AnimatedPreserveAspectRatio','AnimatedRect','AnimatedString',\r\n'AnimatedTransformList','AnimationElement','CircleElement','ClipPathElement','ComponentTransferFunctionElement',\r\n'DefsElement','DescElement','Element','EllipseElement','FEBlendElement','FEColorMatrixElement',\r\n'FEComponentTransferElement','FECompositeElement','FEConvolveMatrixElement','FEDiffuseLightingElement',\r\n'FEDisplacementMapElement','FEDistantLightElement','FEDropShadowElement','FEFloodElement','FEFuncAElement',\r\n'FEFuncBElement','FEFuncGElement','FEFuncRElement','FEGaussianBlurElement','FEImageElement','FEMergeElement',\r\n'FEMergeNodeElement','FEMorphologyElement','FEOffsetElement','FEPointLightElement','FESpecularLightingElement',\r\n'FESpotLightElement','FETileElement','FETurbulenceElement','FilterElement','ForeignObjectElement',\r\n'GElement','GeometryElement','GradientElement','GraphicsElement','ImageElement','Length','LengthList',\r\n'LineElement','LinearGradientElement','MPathElement','MarkerElement','MaskElement','MetadataElement',\r\n'Number','NumberList','PathElement','PatternElement','Point','PointList','PolygonElement','PolylineElement',\r\n'PreserveAspectRatio','RadialGradientElement','Rect','RectElement','SVGElement','ScriptElement','SetElement',\r\n'StopElement','StringList','StyleElement','SwitchElement','SymbolElement','TSpanElement','TextContentElement',\r\n'TextElement','TextPathElement','TextPositioningElement','TitleElement','Transform','TransformList',\r\n'UseElement','ViewElement',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"WebGL\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'WebGL',\r\n\t\t\t\titems: [\r\n'2RenderingContext','ActiveInfo','Buffer','ContextEvent','Framebuffer','Program',\r\n'Query','Renderbuffer','RenderingContext','Sampler','Shader','ShaderPrecisionFormat',\r\n'Sync','Texture','TransformFeedback','UniformLocation','VertexArrayObject',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n\t\"WebRTC\": {\r\n\t\tpad: 0,\r\n\t\tdata: {\r\n\t\t\t1: {\r\n\t\t\t\tprefix: 'RTC',\r\n\t\t\t\titems: [\r\n'Certificate','DTMFSender','DTMFToneChangeEvent','DataChannel','DataChannelEvent','DtlsTransport',\r\n'EncodedAudioFrame','EncodedVideoFrame','IceCandidate','PeerConnection','PeerConnectionIceEvent',\r\n'RtpReceiver','RtpScriptTransform','RtpSender','RtpTransceiver','SctpTransport','SessionDescription',\r\n'StatsReport','TrackEvent',\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t},\r\n\t},\r\n\r\n}\r\n\r\nfunction build_list() {\r\n\tlet optItems = []\r\n\tfor (const n of Object.keys(oList)) {\r\n\t\tlet count = 0\r\n\t\toptItems.push(n)\r\n\t\tlet kdata = oList[n].data\r\n\t\tlet max = 0\r\n\t\tfor (const k of Object.keys(kdata)) {\r\n\t\t\tlet expected = 0\r\n\t\t\t// include ignore items\r\n\t\t\tlet tmpItems = []\r\n\t\t\t// add prefix/suffix to items\r\n\t\t\tif (kdata[k].items !== undefined) {\r\n\t\t\t\texpected = expected + kdata[k].items.length\r\n\t\t\t\tlet prefix = (kdata[k].prefix !== undefined) ? kdata[k].prefix : \"\"\r\n\t\t\t\tlet suffix = (kdata[k].suffix !== undefined) ? kdata[k].suffix : \"\"\r\n\t\t\t\t// add prefix/suffix to items\r\n\t\t\t\tkdata[k].items.forEach(function(item) {\r\n\t\t\t\t\ttmpItems.push(prefix + item + suffix)\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\t// _then_ add ignore items\r\n\t\t\tif (kdata[k].ignore !== undefined) {\r\n\t\t\t\texpected = expected + kdata[k].ignore.length\r\n\t\t\t\tkdata[k].ignore.forEach(function(item) {\r\n\t\t\t\t\ttmpItems.push(item)\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\t// get padding\r\n\t\t\ttmpItems.forEach(function(i) {\r\n\t\t\t\tif (i.length > max) {max = i.length}\r\n\t\t\t})\r\n\t\t\t// check for dupes\r\n\t\t\tlet oldCount = tmpItems.length\r\n\t\t\ttmpItems = tmpItems.filter(function(item, position) {return tmpItems.indexOf(item) === position})\r\n\t\t\tlet newCount = tmpItems.length\r\n\t\t\tif (newCount !== oldCount) {\r\n\t\t\t\tconsole.error(n, k, \"has duplicates\")\r\n\t\t\t}\r\n\t\t\t// check expected numbers\r\n\t\t\tif (newCount !== expected) {\r\n\t\t\t\tconsole.error(n, k, \"expected\", expected, \"got\", newCount)\r\n\t\t\t}\r\n\t\t\t// replace items with new sorted list\r\n\t\t\tkdata[k].items = tmpItems.sort()\r\n\t\t\tcount = count + newCount\r\n\t\t}\r\n\t\toList[n].pad = max + 4\r\n\t\toList[n][\"count\"] = count\r\n\t\tif (n.length > maxPad) {maxPad = n.length}\r\n\t}\r\n\t// populate combobox\r\n\toptItems.sort()\r\n\tlet optList = dom.optList\r\n\toptItems.forEach(function(opt) {\r\n\t\tvar el = document.createElement(\"option\")\r\n\t\tel.textContent = opt\r\n\t\tel.value = opt\r\n\t\toptList.appendChild(el)\r\n\t})\r\n\t// set a default\r\n\tif (optItems.includes(\"Element\")) {\r\n\t\toptList.value = \"Element\"\r\n\t} else {\r\n\t\toptList.value = optItems[0]\r\n\t}\r\n}\r\n\r\nfunction find(prop) {\r\n\tlet aFound = []\r\n\tfor (const k of Object.keys(oData)) {\r\n\t\tif (oData[k].includes(prop)) {\r\n\t\t\taFound.push(k)\r\n\t\t}\r\n\t}\r\n\tif (aFound.length) {\r\n\t\treturn aFound\r\n\t}\r\n\treturn \"not found\"\r\n}\r\n\r\nfunction run() {\r\n\t// https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API\r\n\r\n\tlet t0 = performance.now()\r\n\tlet optSort = dom.optSort.checked\r\n\tlet optList = dom.optList.value\r\n\tlet tmpList = oList[optList]\r\n\tlet tmpObj = tmpList.data\r\n\tlet padlen = tmpList.pad\r\n\tlet tmpData ={}, tmpPost = {}, aDisplay = [], tmpDisplay = []\r\n\r\n\ttry {\r\n\t\tfor (const k of Object.keys(tmpObj).sort()) {\r\n\t\t\tlet groupdata = tmpObj[k]\r\n\t\t\tif (groupdata.items !== undefined) {\r\n\t\t\t\tlet name, group\r\n\t\t\t\tif (optList.toLowerCase() == \"all\") {\r\n\t\t\t\t\tname = k\r\n\t\t\t\t\tgroup = k\r\n\t\t\t\t} else {\r\n\t\t\t\t\tname = (groupdata.title !== undefined ? groupdata.title.toLowerCase() : \"\")\r\n\t\t\t\t\tgroup = k + name\r\n\t\t\t\t}\r\n\t\t\t\ttmpData[group] = {}\r\n\t\t\t\ttmpDisplay = []\r\n\t\t\t\tgroupdata.items.forEach(function(item) {\r\n\t\t\t\t\tif (window[item] !== undefined) {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tlet array = Object.getOwnPropertyNames(window[item].prototype)\r\n\r\n\t\t\t\t\t\t\t// post constructor\r\n\t\t\t\t\t\t\tlet strPost = \"\", aPost = [], tmpArray = [], strData\r\n\t\t\t\t\t\t\tlet isPost = (array[array.length -1] !== \"constructor\")\r\n\t\t\t\t\t\t\tif (isPost) {\r\n\t\t\t\t\t\t\t\taPost = array.slice(array.indexOf(\"constructor\")+1)\r\n\t\t\t\t\t\t\t\tstrPost = \" \"+ sb + \"(\"+ aPost.length +\")\"+ sc\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t// now sort array\r\n\t\t\t\t\t\t\tif (optSort) {array.sort()}\r\n\t\t\t\t\t\t\tlet hash = mini(array)\r\n\t\t\t\t\t\t\ttmpData[group][item] = array\r\n\r\n\t\t\t\t\t\t\t// count + color each post item individually, record post data\r\n\t\t\t\t\t\t\tif (isPost) {\r\n\t\t\t\t\t\t\t\tarray.forEach(function(n) {\r\n\t\t\t\t\t\t\t\t\ttmpArray.push(n)\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\taPost.forEach(function(item) {\r\n\t\t\t\t\t\t\t\t\ttmpArray[tmpArray.indexOf(item)] = sb + item + sc\r\n\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\ttmpPost[item] = aPost\r\n\t\t\t\t\t\t\t\tstrData = tmpArray.join(\", \")\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tstrData = array.join(\", \")\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\ttmpDisplay.push(\r\n\t\t\t\t\t\t\t\ts18 + item.padStart(padlen) + sc + \": \"+ hash + s18 +\" (\"+ tmpData[group][item].length +\")\"+ sc + (isPost ? strPost : \"\") +\"<br>\"+\r\n\t\t\t\t\t\t\t\t\"<span class='toghidden\" + k +\" hidden faint'>[<br>\" + \"<span class='indent'>\" + strData +\"</span>\" + \"<br>]</span>\"\r\n\t\t\t\t\t\t\t)\r\n\r\n\t\t\t\t\t\t} catch(e) {\r\n\t\t\t\t\t\t\tconsole.log(item, e+\"\")\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tconsole.log(item, \"is undefined\")\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\t// calculate group hashes + notate\r\n\t\t\t\tlet hash = mini(tmpData[group]), notation = \"\"\r\n\t\t\t\tif (isFF && !optSort) {\r\n\t\t\t\t\tif (optList === \"Element\") {\r\n\t\t\t\t\t\tif (k == \"6\" && isVer > 110) { notation = (hash == \"b0cb1af2\") ? green_tick : ' '+ zNEW\r\n\t\t\t\t\t\t} else if (k == \"7\" && isVer > 91) { notation = (hash == \"642e531a\") ? green_tick : ' '+ zNEW\r\n\t\t\t\t\t\t} else if (k == \"8\" && isVer > 69) { notation = hash == \"eb335cf8\" ? green_tick : ' '+ zNEW\r\n\t\t\t\t\t\t} else if (k == \"9\") { notation = hash == \"1be9c8dc\" ? green_tick : ' '+ zNEW\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// display: add group header + details\r\n\t\t\t\tlet strDesc = (groupdata.desc !== undefined && groupdata.desc.length) ? \" <span class='faint'>... \"+ groupdata.desc +\"</span>\" : \"\"\r\n\t\t\t\taDisplay.push(\r\n\t\t\t\t\t\"<br><hr>\"\r\n\t\t\t\t\t+ \"<span id='labelhidden\"+ k +\"' class='btnfirst btn0' onClick=\\\"togglerows('hidden\"+ k +\"','expand')\\\">[ expand ]</span> \"\r\n\t\t\t\t\t+ s13 + hash + sc + notation +\" \"+ name.toUpperCase() + strDesc +\"<br><br>\"\r\n\t\t\t\t)\r\n\t\t\t\taDisplay.push(tmpDisplay.join(\"\"))\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// post constructor items\r\n\t\tlet oPost = {}, postCount = 0, strPost = \"\"\r\n\t\tfor (const k of Object.keys(tmpPost).sort()) {\r\n\t\t\toPost[k] = tmpPost[k]\r\n\t\t\tpostCount += oPost[k].length\r\n\t\t}\r\n\t\t// finish perf here so I can optimize\r\n\t\tdom.perf = Math.round(performance.now() - t0) +\" ms\"\r\n\r\n\t\t// collect everything for testing purposes\r\n\t\tlet newObj = {}\r\n\t\taEverything = []\r\n\t\tfor (const k of Object.keys(tmpData)) {\r\n\t\t\tfor (const j of Object.keys(tmpData[k])) {\r\n\t\t\t\tnewObj[j] = tmpData[k][j]\r\n\t\t\t\taEverything = aEverything.concat(tmpData[k][j])\r\n\t\t\t}\r\n\t\t}\r\n\t\taEverything = aEverything.filter(function(item, position) {return aEverything.indexOf(item) === position}) // deduped\r\n\t\taEverything.sort() // order is artifical due to htmlList so lets remove that here\r\n\t\t//console.log(\"['\"+ aEverything.join(\"','\") +\"']\") // 447\r\n\r\n\t\t// oData = sorted without subsections\r\n\t\toData = {}\r\n\t\tfor (const k of Object.keys(newObj).sort()) {\r\n\t\t\toData[k] = newObj[k]\r\n\t\t}\r\n\r\n\t\tif (postCount > 0) {\r\n\t\t\tlet posttoggle = \"post\"\r\n\t\t\tstrPost = \"<br>\"\r\n\t\t\t\t+ \"<span id='labelhidden\"+ posttoggle +\"' class='btnfirst btn0' onClick=\\\"togglerows('hidden\"+ posttoggle +\"','expand')\\\">[ expand ]</span> \"\r\n\t\t\t\t+ s13 + mini(oPost) + sc + sb +\" [\" + postCount +\"] \"+ sc + \"items after constructor\"\r\n\t\t\t\t+ \"<span class='toghidden\" + posttoggle +\" hidden faint'>[<br>\" + \"<span class='indent'>\" + json_highlight(oPost) +\"</span>\" + \"<br>]</span>\"\r\n\t\t\t\t+\"<br>\"\r\n\t\t}\r\n\r\n\t\t// display\r\n\t\tlet strAll = s18 + (optList.toUpperCase()).padStart(maxPad) + sc +\": \"+ s13 + mini(oData) + sc\r\n\t\t\t+ \" <span class='btn18 btnc' onclick='console.log(oData)'>[console]</span> \"\r\n\t\t\t+ (optSort ? \"sorted\" : \"unsorted\") +\" | unique values: \"\r\n\t\t\t+ s13 + mini(aEverything) + sc\r\n\t\t\t+ \" <span class='btn18 btnc' onclick='console.log(aEverything)'>[\" + aEverything.length +\"]</span>\"\r\n\r\n\t\tdom.details.innerHTML = strAll +\"<br>\"+ strPost + aDisplay.join(\"\")\r\n\t\tdom.totalCount = tmpList.count\r\n\r\n\t} catch(e) {\r\n\t\tdom.details = e+\"\"\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tPromise.all([\r\n\t\tget_isVer()\r\n\t]).then(function(){\r\n\t\tbuild_list()\r\n\t\trun()\r\n\t})\r\n})\r\n\r\n</script>\r\n\r\n</span>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/math.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>math</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 780px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#misc\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb18\">\r\n\t\t<col width=\"1%\"><col width=\"9%\"><col width=\"90%\">\r\n\t\t<thead><tr><th colspan=\"3\">math: version 2.5</th></tr></thead>\r\n\t\t<tr><td colspan=\"3\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Hashes all results, and compares those results to a control set, to build a list\r\n\t\t\t\tof tests that provides entropy. The (ongoing) results are <a class=\"blue\" href=\"mathdata.html\">here</a>.</span>\r\n\t\t</td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\"></td>\r\n\t\t\t<td>\r\n\t\t\t\t<span id=\"bfirefox\" class=\"btn18 btnfirst\" onClick=\"outputMath(`firefox`)\">[ firefox ]</span>\r\n\t\t\t\t<span id=\"bchrome\" class=\"btn18 btn\" onClick=\"outputMath(`chrome`)\">[ chrome ]</span>\r\n\t\t\t\t<span id=\"bsafari\" class=\"btn18 btn\" onClick=\"outputMath(`safari`)\">[ safari ]</span>\r\n\t\t\t</td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\"><span id=\"perfE\" class=\"mono\"></span> \r\n\t\t\t<span class=\"btn0 btn\" onClick=\"copyclip(`engine`)\">[ copy ]</span> ENGINE</td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">engine</td><td class=\"mono spaces\" id=\"engine\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\"><span id=\"perf\" class=\"c mono\"></span> \r\n\t\t\t<span class=\"btn0 btn\" onClick=\"copyclip(`report`)\">[ copy ]</span> SHORT REPORT</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"c mono spaces\" id=\"report\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\">DIFFS ANALYSIS</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"s18\">vs control</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"mono\">denoted in <span class=\"sb\">red</span></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">function</td><td class=\"c mono spaces\" id=\"diff1\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">polyfill</td><td class=\"c mono spaces\" id=\"diff2\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">test no's</td><td class=\"c mono spaces\" id=\"numbers\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"s18\">polyfill vs function</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td><td class=\"mono\">denoted with <span class=\"bad\"> [match]</span></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">diffs</td><td class=\"c mono spaces\" id=\"diff0\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\"><span class=\"btn0 btn\" onClick=\"copyclip(`mathdata`)\">[ copy ]</span> DATA</td></tr>\r\n\t\t<tr><td></td><td colspan=\"2\" class=\"c mono spaces\" id=\"mathdata\"></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar fns = [], // what tests to run\r\n\tn = 0.123,\r\n\tbigN = 5.860847362277284e+38\r\n\r\n// control sets\r\nvar controlname = \"\"\r\nvar setFF = [\r\n\t'1.4474840516030247','0.7853981633974483','709.889355822726','Infinity','1.811526272460853','1.8115262724608532','0.881373587019543','0.8813735870195432','0.12331227519187199','691.4686750787736','691.4686750787736','1.8622957433108482','1.885022066017804','1.1071487177940904','1.2626272556789115','0.5493061443340548','0.5493061443340548','5e-311','1.0038848218538872','4.641588833612779','4.641588833612778','1.4645918875615231','1.4645918875615231','-0.37419577499634155','-0.7854805190645291','0.7914463018528902','-0.767224894221913','-0.7415825695514536','0.5403023058681397','0.7086865671674246','-0.7482651726250321','0.9924450321351935','-1','-0.10868049424995659','-0.8913089376870335','-0.7108118501064332','-0.5369116957490239','-0.40677759702517235','-0.7017203400855445','0.43628480636189976','-0.6982689820462376','-0.6982689820462376','-0.6534063185820198','0.45375574259827833','0.6459044007438142','1.5430806348152437','1.5430806348152437','11.591953275521519','11.591953275521519','9.199870313877774e+307','Infinity','1.046919966902314e+308','Infinity','1.718281828459045','1.718281828459045','22.140692632779267','22.140692632779267','1.1308844209474893','23.140692632779267','9.539392014169456','9.539392014169458','8.288489826731114e+38','8.288489826731116e+38','100.14767208675258','100.14767208675259','101.7610227859332','101.76102278593319','100.00960859865252','100.0096085986525','100.01040630344927','100.01040630344929','100.00999950005','100.00999950004999','100.00249996875078','100.0024999687508','100.00377216279418','100.00377216279416','-2.0955709236097197','1.1447298858494002','0.11600367575630613','0.11600367575630613','1.4210804127942926','1.4210804127942926','-0.9100948885606021','-0.9100948885606022','0.49714987269413385','0.49714987269413385','0.4342944819032518','0.4342944819032518','1.965773398945507','1.965773398945507','-0.1591745389548616','-0.1591745389548616','0.8822181462033635','0.8822181462033635','0.15917453895486158','0.15917453895486158','1.7926429945344482','1.792642994534448','-0.36221568869946325','-0.3622156886994632','0.48288235131479357','0.4828823513147936','-0.15051499783199057','-0.15051499783199057','0.15051499783199063','0.1505149978319906','0.15051499783199063','0.1505149978319906','-0.9273497301314576','-0.6188863822787813','-0.6112387023768895','0.6413781736901984','0.6708616046081811','0.8414709848078965','-0.7055234578073583','0.66339975236386','0.994076732536068','1.2246467991473532e-16','-0.7181630308570678','-0.765996413898051','0.9989410140273757','0.10135692924965614','-0.37463575478582023','-0.9892668187780497','1.1752011936438014','1.1752011936438014','11.548739357257748','11.548739357257748','7.544137102816975','7.544137102816975','0.75','0.75','1.9978980091062795','1.9978980091062797','9.199870313877774e+307','Infinity','0.44807597941469024','0.4480759794146903','0.7675231451261164','0.7675231451261164','1.935066822174357','1.9350668221743568','1.046919966902314e+308','Infinity','0.3507135583350036','1.7724538509055159','2.478247463217681','0.7879079967710036','-0.7723059681318761','-0.8359715365344825','-0.904635076595654','1.5574077246549023','-0.9955366596368418','-0.8865837628611647','0.5086861259107568','-1.2246467991473532e-16','0.686676154645243','1.618281713571588','-3.353712870537601','-1.922295546179998','-1.922295546179998','2.5824856130712437','0.12238344189440875','0.12238344189440872','0.99627207622075','0.9962720762207501','1.0220893335845176e+91','1.9275814160560185e-50','3.720075976020851e-44','8269017203802410','6.003867926738811e-37','1.2093335584550061e-16','1.665592934758592e+36','1125899906842611.5','8.881784197001154e-16'\r\n]\r\nvar setChrome = [\r\n\t'1.4474840516030247','0.7853981633974483','709.889355822726','Infinity','1.811526272460853','1.8115262724608532','0.881373587019543','0.8813735870195432','0.12331227519187199','691.4686750787736','691.4686750787736','1.8622957433108482','1.885022066017804','1.1071487177940904','1.2626272556789115','0.5493061443340548','0.5493061443340548','5e-311','1.0038848218538872','4.641588833612779','4.641588833612778','1.4645918875615231','1.4645918875615234','-0.37419577499634155','-0.7854805190645291','0.7914463018528903','-0.767224894221913','-0.7415825695514535','0.5403023058681398','0.7086865671674247','-0.7482651726250322','0.9924450321351935','-1','-0.10868049424995659','-0.8913089376870335','-0.7108118501064331','-0.536911695749024','-0.4067775970251724','-0.7017203400855446','0.4362848063618998','-0.6982689820462377','-0.6982689820462377','-0.6534063185820198','0.4537557425982784','0.6459044007438142','1.5430806348152437','1.5430806348152437','11.591953275521519','11.591953275521519','9.199870313877772e+307','Infinity','1.0469199669023138e+308','Infinity','1.718281828459045','1.718281828459045','22.140692632779267','22.140692632779267','1.1308844209474893','23.140692632779267','9.539392014169456','9.539392014169458','8.288489826731116e+38','8.288489826731116e+38','100.14767208675259','100.14767208675259','101.76102278593319','101.76102278593319','100.0096085986525','100.0096085986525','100.01040630344929','100.01040630344929','100.00999950004999','100.00999950004999','100.0024999687508','100.0024999687508','100.00377216279416','100.00377216279416','-2.0955709236097197','1.1447298858494002','0.11600367575630613','0.11600367575630613','1.4210804127942926','1.4210804127942926','-0.9100948885606021','-0.9100948885606022','0.4971498726941338','0.49714987269413385','0.4342944819032518','0.4342944819032518','1.9657733989455068','1.965773398945507','-0.1591745389548616','-0.1591745389548616','0.8822181462033634','0.8822181462033635','0.15917453895486158','0.15917453895486158','1.792642994534448','1.792642994534448','-0.36221568869946325','-0.3622156886994632','0.4828823513147936','0.4828823513147936','-0.15051499783199057','-0.15051499783199057','0.1505149978319906','0.1505149978319906','0.1505149978319906','0.1505149978319906','-0.9273497301314576','-0.6188863822787813','-0.6112387023768895','0.6413781736901984','0.6708616046081811','0.8414709848078965','-0.7055234578073583','0.66339975236386','0.994076732536068','1.2246467991473532e-16','-0.7181630308570677','-0.7659964138980511','0.9989410140273756','0.10135692924965616','-0.3746357547858202','-0.9892668187780498','1.1752011936438014','1.1752011936438014','11.548739357257748','11.548739357257748','7.544137102816975','7.544137102816975','0.75','0.75','1.9978980091062795','1.9978980091062797','9.199870313877772e+307','Infinity','0.44807597941469024','0.4480759794146903','0.7675231451261164','0.7675231451261164','1.935066822174357','1.9350668221743568','1.0469199669023138e+308','Infinity','0.3507135583350036','1.7724538509055159','2.478247463217681','0.7879079967710036','-0.7723059681318761','-0.8359715365344825','-0.904635076595654','1.5574077246549023','-0.9955366596368418','-0.8865837628611647','0.5086861259107568','-1.2246467991473532e-16','0.6866761546452431','1.6182817135715877','-3.3537128705376014','-1.9222955461799982','-1.9222955461799982','2.5824856130712432','0.12238344189440875','0.12238344189440872','0.99627207622075','0.9962720762207501','1.022089333584519e+91','1.9275814160560204e-50','3.7200759760208555e-44','8269017203802394','6.003867926738829e-37','1.20933355845501e-16','1.6655929347585958e+36','1125899906842616.2','8.881784197001191e-16'\r\n]\r\nvar setSafari = [\r\n\t'1.4474840516030245','0.7853981633974483','709.889355822726','Infinity','1.811526272460853','1.8115262724608532','0.8813735870195432','0.8813735870195432','0.12331227519187199','691.4686750787736','691.4686750787736','1.8622957433108482','1.885022066017804','1.1071487177940906','1.2626272556789115','0.5493061443340549','0.5493061443340549','5e-311','1.0038848218538872','4.641588833612779','4.641588833612778','1.4645918875615234','1.4645918875615231','-0.3741957749963415','-0.7854805190645292','0.7914463018528903','-0.7672248942219131','-0.7415825695514535','0.5403023058681398','0.7086865671674247','-0.7482651726250322','0.9924450321351935','-1','-0.10868049424995659','-0.8913089376870335','-0.7108118501064331','-0.536911695749024','-0.40677759702517235','-0.7017203400855446','0.4362848063618998','-0.6982689820462377','-0.6982689820462377','-0.6534063185820198','0.4537557425982784','0.6459044007438142','1.5430806348152437','1.5430806348152437','11.591953275521519','11.591953275521519','9.199870313877772e+307','Infinity','1.0469199669023138e+308','Infinity','1.7182818284590453','1.718281828459045','22.140692632779267','22.140692632779267','1.1308844209474893','23.140692632779267','9.539392014169456','9.539392014169458','8.288489826731116e+38','8.288489826731116e+38','100.14767208675259','100.14767208675259','101.76102278593319','101.76102278593319','100.0096085986525','100.0096085986525','100.01040630344929','100.01040630344929','100.00999950004999','100.00999950004999','100.0024999687508','100.0024999687508','100.00377216279416','100.00377216279416','-2.0955709236097197','1.1447298858494002','0.11600367575630613','0.11600367575630613','1.4210804127942926','1.4210804127942926','-0.9100948885606021','-0.9100948885606022','0.49714987269413385','0.49714987269413385','0.4342944819032518','0.4342944819032518','1.965773398945507','1.965773398945507','-0.1591745389548616','-0.1591745389548616','0.8822181462033635','0.8822181462033635','0.15917453895486158','0.15917453895486158','1.7926429945344482','1.792642994534448','-0.36221568869946325','-0.3622156886994632','0.48288235131479357','0.4828823513147936','-0.15051499783199057','-0.15051499783199057','0.15051499783199063','0.1505149978319906','0.15051499783199063','0.1505149978319906','-0.9273497301314577','-0.6188863822787812','-0.6112387023768895','0.6413781736901984','0.6708616046081811','0.8414709848078965','-0.7055234578073583','0.66339975236386','0.994076732536068','1.2246467991473532e-16','-0.7181630308570677','-0.765996413898051','0.9989410140273756','0.10135692924965616','-0.37463575478582023','-0.9892668187780498','1.1752011936438014','1.1752011936438014','11.548739357257746','11.548739357257748','7.544137102816975','7.544137102816975','0.75','0.75','1.9978980091062795','1.9978980091062797','9.199870313877772e+307','Infinity','0.44807597941469024','0.4480759794146903','0.7675231451261164','0.7675231451261164','1.9350668221743568','1.9350668221743568','1.0469199669023138e+308','Infinity','0.3507135583350036','1.7724538509055159','2.4782474632176816','0.7879079967710035','-0.772305968131876','-0.8359715365344824','-0.9046350765956541','1.557407724654902','-0.9955366596368417','-0.8865837628611646','0.5086861259107567','-1.2246467991473532e-16','0.686676154645243','1.6182817135715875','-3.353712870537602','-1.922295546179998','-1.922295546179998','2.5824856130712437','0.12238344189440876','0.12238344189440872','0.99627207622075','0.9962720762207501','1.022089333584519e+91','1.9275814160560206e-50','3.7200759760208555e-44','8269017203802394','6.003867926738829e-37','1.20933355845501e-16','1.6655929347585955e+36','1125899906842616.2','8.881784197001191e-16'\r\n]\r\n\r\nfunction get_engine() {\r\n\tlet et0 = performance.now()\r\n\tfunction cbrt(x) {\r\n\t\ttry {\r\n\t\t\tlet y = Math.pow(Math.abs(x), 1 / 3)\r\n\t\t\treturn x < 0 ? -y : y\r\n\t\t} catch(e) {\r\n\t\t\treturn \"error\"\r\n\t\t}\r\n\t}\r\n\tlet res = [], engine = zNEW\r\n\tfor(let i=0; i < 6; i++) {\r\n\t\ttry {\r\n\t\t\tlet fnResult = \"unknown\"\r\n\t\t\tif (i == 0) {\r\n\t\t\t\tfnResult = cbrt(Math.PI) // polyfill\r\n\t\t\t} else if (i == 1) {\r\n\t\t\t\tfnResult = Math.log10(7*Math.LOG10E)\r\n\t\t\t} else if (i == 2) {\r\n\t\t\t\tfnResult = Math.log10(2*Math.SQRT1_2)\r\n\t\t\t} else if (i == 3) {\r\n\t\t\t\tfnResult = Math.acos(0.123)\r\n\t\t\t} else if (i == 4) {\r\n\t\t\t\tfnResult = Math.acosh(Math.SQRT2)\r\n\t\t\t} else if (i == 5) {\r\n\t\t\t\tfnResult = Math.atan(2)\r\n\t\t\t}\r\n\t\t\tres.push(fnResult)\r\n\t\t} catch(e) {\r\n\t\t\tres.push(\"error\")\r\n\t\t}\r\n\t}\r\n\tlet hash = sha1(res.join())\r\n\tif (hash == \"ede9ca53efbb1902cc213a0beb692fe1e58f9d7a\") {engine = \"blink\"\r\n\t} else if (hash == \"05513f36d87dd78af60ab448736fd0898d36b7a9\") {engine = \"webkit\"\r\n\t} else if (hash == \"38172d9426d77af71baa402940bad1336d3091d0\") {engine = \"edgeHTML\"\r\n\t} else if (hash == \"36f067c652c8cfd9072580fca1f177f07da7ecf0\") {engine = \"trident\" // also presto if we don't use `let`\r\n\t} else if (hash == \"225f4a612fdca4065043a4becff76a87ab324a74\") {engine = \"gecko [a]\" // abraham\r\n\t} else if (hash == \"cb89002a8d6fabf859f679fd318dffda1b4ae0ea\") {engine = \"gecko [b]\" // me\r\n\t}\r\n\tif (engine.substring(0,5) == \"gecko\") {\r\n\t\t// palemoon/basilisk: fails 53, passes 54\r\n\t\tif (\"function\" !== typeof CSSMozDocumentRule && URL.prototype.hasOwnProperty(\"toJSON\")) {\r\n\t\t\tengine += \"<br><br>but wait.. I did some extra tests<br>\"\r\n\t\t\t\t+ s12.trim() +\"real engine:\" + sc + s18 +\"goanna\"+ sc\r\n\t\t}\r\n\t}\r\n\tdom.perfE.innerHTML = Math.round(performance.now() - et0) +\" ms\"\r\n\tdom.engine.innerHTML = s12.trim() + hash + sc +\"<br> &middot; \"+ res.join(\"<br> &middot; \")\r\n\t\t+\"<br>\"+ s12.trim() +\"engine: \"+ sc + engine\r\n}\r\n\r\nfunction build_controlset(engine) {\r\n\tlet c = []\r\n\tif (engine == \"firefox\") {c = setFF\r\n\t} else if (engine == \"chrome\") {c = setChrome\r\n\t} else if (engine == \"safari\") {c = setSafari}\r\n\tfns = [\r\n\t\t// [item, function, value, description, control, poly1 control, poly2 control]\r\n\t\t[0,'acos', [n], n, c[0]],\r\n\t\t[1,'acos', [Math.SQRT1_2], \"Math.SQRT1_2\", c[1]],\r\n\r\n\t\t[0,'acosh', [1e308], \"1e300\", c[2], c[3]],\r\n\t\t[1,'acosh', [Math.PI], \"Math.PI\", c[4], c[5]],\r\n\t\t[2,'acosh', [Math.SQRT2], \"Math.SQRT2\", c[6], c[7]],\r\n\r\n\t\t[0,'asin', [n], n, c[8]],\r\n\r\n\t\t[0,'asinh', [1e300], \"1e300\", c[9], c[10]],\r\n\t\t[1,'asinh', [Math.PI], \"Math.PI\", c[11], c[12]],\r\n\r\n\t\t[0,'atan', [2], \"2\", c[13]],\r\n\t\t[1,'atan', [Math.PI], \"Math.PI\", c[14]],\r\n\r\n\t\t[0,'atanh', [0.5], \"0.5\", c[15], c[16]],\r\n\r\n\t\t[0,'atan2', [1e-310, 2], \"1e-310, 2\", c[17]],\r\n\t\t[1,'atan2', [Math.PI, 2], \"Math.PI, 2\", c[18]],\r\n\r\n\t\t[0,'cbrt', [100], \"100\", c[19], c[20]],\r\n\t\t[1,'cbrt', [Math.PI], \"Math.PI\", c[21], c[22]],\r\n\r\n\t\t// original TZP cos\r\n\t\t[0,'cos', [1e251], \"1e251\", c[23]],\r\n\t\t[1,'cos', [1e140], \"1e140\", c[24]],\r\n\t\t[2,'cos', [1e12], \"1e12\", c[25]],\r\n\t\t[3,'cos', [1e130], \"1e130\", c[26]],\r\n\t\t[4,'cos', [1e272], \"1e272\", c[27]],\r\n\t\t[5,'cos', [1e0], \"1e0\", c[28]],\r\n\t\t[6,'cos', [1e284], \"1e284\", c[29]],\r\n\t\t[7,'cos', [1e75], \"1e75\", c[30]],\r\n\r\n\t\t[8,'cos', [n], n, c[31]],\r\n\t\t[9,'cos', [Math.PI], \"Math.PI\", c[32]],\r\n\t\t[10,'cos', [bigN], \"bigN\", c[33]], // creep says unique in Tor\r\n\t\t[11,'cos', [-1e308], '-1e308', c[34]],\r\n\t\t[12,'cos', [13*Math.E], '13*Math.E', c[35]],\r\n\t\t[13,'cos', [57*Math.E], '57*Math.E', c[36]],\r\n\t\t[14,'cos', [21*Math.LN2], '21*Math.LN2', c[37]],\r\n\t\t[15,'cos', [51*Math.LN2], '51*Math.LN2', c[38]],\r\n\t\t[16,'cos', [21*Math.LOG2E], '21*Math.LOG2E', c[39]],\r\n\t\t[17,'cos', [25*Math.SQRT2], '25*Math.SQRT2', c[40]],\r\n\t\t[18,'cos', [50*Math.SQRT1_2], '50*Math.SQRT1_2', c[41]],\r\n\t\t[19,'cos', [21*Math.SQRT1_2], '21*Math.SQRT1_2', c[42]],\r\n\t\t[20,'cos', [17*Math.LOG10E], '17*Math.LOG10E', c[43]],\r\n\t\t[21,'cos', [2*Math.LOG10E], '2*Math.LOG10E', c[44]],\r\n\r\n\t\t[0,'cosh', [1], \"1\", c[45], c[46]],\r\n\t\t[1,'cosh', [Math.PI], \"Math.PI\", c[47], c[48]],\r\n\t\t[2,'cosh', [492*Math.LOG2E], '492*Math.LOG2E', c[49], c[50]],\r\n\t\t[3,'cosh', [502*Math.SQRT2], '502*Math.SQRT2', c[51], c[52]],\r\n\r\n\t\t[0,'expm1', [1], \"1\", c[53], c[54]],\r\n\t\t[1,'expm1', [Math.PI], \"Math.PI\", c[55], c[56]],\r\n\r\n\t\t[0,'exp', [n], n, c[57]],\r\n\t\t[1,'exp', [Math.PI], \"Math.PI\", c[58]],\r\n\r\n\t\t[0,'hypot', [1,2,3,4,5,6], \"1,2,3,4,5,6\", c[59], c[60]],\r\n\t\t[1,'hypot', [bigN, bigN], \"bigN, bigN\", c[61], c[62]],\r\n\t\t[2,'hypot', [2*Math.E,-100], '2*Math.E,-100', c[63], c[64]],\r\n\t\t[3,'hypot', [6*Math.PI,-100], '6*Math.PI,-100', c[65], c[66]],\r\n\t\t[4,'hypot', [2*Math.LN2,-100], '2*Math.LN2,-100', c[67], c[68]],\r\n\t\t[5,'hypot', [Math.LOG2E,-100], 'Math.LOG2E,-100', c[69], c[70]],\r\n\t\t[6,'hypot', [Math.SQRT2,-100], 'Math.SQRT2,-100', c[71], c[72]],\r\n\t\t[7,'hypot', [Math.SQRT1_2,-100], 'Math.SQRT1_2,-100',c[73], c[74]],\r\n\t\t[8,'hypot', [2*Math.LOG10E,-100], '2*Math.LOG10E,-100', c[75], c[76]],\r\n\r\n\t\t[0,'log', [n], n, c[77]],\r\n\t\t[1,'log', [Math.PI], \"Math.PI\", c[78]],\r\n\r\n\t\t[0,'log1p', [n], n, c[79], c[80]],\r\n\t\t[1,'log1p', [Math.PI], \"Math.PI\", c[81], c[82]],\r\n\r\n\t\t[0,'log10', [n], n, c[83], c[84]],\r\n\t\t[1,'log10', [Math.PI], \"Math.PI\", c[85], c[86]],\r\n\t\t[2,'log10', [Math.E], \"Math.E\", c[87], c[88]],\r\n\t\t[3,'log10', [34*Math.E], '34*Math.E', c[89], c[90]],\r\n\t\t[4,'log10', [Math.LN2], \"Math.LN2\", c[91], c[92]],\r\n\t\t[5,'log10', [11*Math.LN2], '11*Math.LN2', c[93], c[94]],\r\n\t\t[6,'log10', [Math.LOG2E], \"Math.LOG2E\", c[95], c[96]],\r\n\t\t[7,'log10', [43*Math.LOG2E], '43*Math.LOG2E', c[97], c[98]],\r\n\t\t[8,'log10', [Math.LOG10E], \"Math.LOG10E\", c[99], c[100]],\r\n\t\t[9,'log10', [7*Math.LOG10E], '7*Math.LOG10E', c[101], c[102]],\r\n\t\t[10,'log10', [Math.SQRT1_2], \"Math.SQRT1_2\", c[103], c[104]],\r\n\t\t[11,'log10', [2*Math.SQRT1_2], '2*Math.SQRT1_2', c[105], c[106]],\r\n\t\t[12,'log10', [Math.SQRT2], \"Math.SQRT2\", c[107], c[108]],\r\n\r\n\t\t// original TZP value but with sin\r\n\t\t[0,'sin', [1e251], \"1e251\", c[109]],\r\n\t\t[1,'sin', [1e140], \"1e140\", c[110]],\r\n\t\t[2,'sin', [1e12], \"1e12\", c[111]],\r\n\t\t[3,'sin', [1e130], \"1e130\", c[112]],\r\n\t\t[4,'sin', [1e272], \"1e272\", c[113]],\r\n\t\t[5,'sin', [1e0], \"1e0\", c[114]],\r\n\t\t[6,'sin', [1e284], \"1e284\", c[115]],\r\n\t\t[7,'sin', [1e75], \"1e75\", c[116]],\r\n\r\n\t\t[8,'sin', [bigN], \"bigN\", c[117]], // creep says unique in Tor\r\n\t\t[9,'sin', [Math.PI], \"Math.PI\", c[118]], // creep says unique in Tor\r\n\t\t[10,'sin', [39*Math.E], \"39*Math.E\", c[119]],\r\n\t\t[11,'sin', [35*Math.LN2], \"35*Math.LN2\", c[120]],\r\n\t\t[12,'sin', [110*Math.LOG2E], \"110*Math.LOG2E\", c[121]],\r\n\t\t[13,'sin', [7*Math.LOG10E], \"7*Math.LOG10E\", c[122]],\r\n\t\t[14,'sin', [35*Math.SQRT1_2], \"35*Math.SQRT1_2\", c[123]],\r\n\t\t[15,'sin', [21*Math.SQRT2], \"21*Math.SQRT2\", c[124]],\r\n\r\n\t\t[0,'sinh', [1], \"1\", c[125], c[126]],\r\n\t\t[1,'sinh', [Math.PI], \"Math.PI\", c[127], c[128]],\r\n\t\t[2,'sinh', [Math.E], \"Math.E\", c[129], c[130]],\r\n\t\t[3,'sinh', [Math.LN2], \"Math.LN2\", c[131], c[132]],\r\n\t\t[4,'sinh', [Math.LOG2E], \"Math.LOG2E\", c[133], c[134]],\r\n\t\t[5,'sinh', [492*Math.LOG2E], '492*Math.LOG2E', c[135], c[136]],\r\n\t\t[6,'sinh', [Math.LOG10E], \"Math.LOG10E\", c[137], c[138]],\r\n\t\t[7,'sinh', [Math.SQRT1_2], \"Math.SQRT1_2\", c[139], c[140]],\r\n\t\t[8,'sinh', [Math.SQRT2], \"Math.SQRT2\", c[141], c[142]],\r\n\t\t[9,'sinh', [502*Math.SQRT2], '502*Math.SQRT2', c[143], c[144]],\r\n\r\n\t\t[0,'sqrt', [n], n, c[145]],\r\n\t\t[1,'sqrt', [Math.PI], \"Math.PI\", c[146]],\r\n\r\n\t\t// original TZP with tan\r\n\t\t[0,'tan', [1e251], \"1e251\", c[147]],\r\n\t\t[1,'tan', [1e140], \"1e140\", c[148]],\r\n\t\t[2,'tan', [1e12], \"1e12\", c[149]],\r\n\t\t[3,'tan', [1e130], \"1e130\", c[150]],\r\n\t\t[4,'tan', [1e272], \"1e272\", c[151]],\r\n\t\t[5,'tan', [1e0], \"1e0\", c[152]],\r\n\t\t[6,'tan', [1e284], \"1e284\", c[153]],\r\n\t\t[7,'tan', [1e75], \"1e75\", c[154]],\r\n\r\n\t\t[8,'tan', [-1e308], \"-1e308\", c[155]],\r\n\t\t[9,'tan', [Math.PI], \"Math.PI\", c[156]],\r\n\t\t[10,'tan', [6*Math.E], '6*Math.E', c[157]],\r\n\t\t[11,'tan', [6*Math.LN2], '6*Math.LN2', c[158]],\r\n\t\t[12,'tan', [10*Math.LOG2E], '10*Math.LOG2E', c[159]],\r\n\t\t[13,'tan', [17*Math.SQRT2], '17*Math.SQRT2', c[160]],\r\n\t\t[14,'tan', [34*Math.SQRT1_2], '34*Math.SQRT1_2', c[161]],\r\n\t\t[15,'tan', [10*Math.LOG10E], '10*Math.LOG10E', c[162]],\r\n\r\n\t\t[0,'tanh', [n], n, c[163], c[164]],\r\n\t\t[1,'tanh', [Math.PI], \"Math.PI\", c[165], c[166]],\r\n\r\n\t\t[0,'pow', [n,-100], n+\",-100\", c[167]],\r\n\t\t[1,'pow', [Math.PI,-100], \"Math.PI,-100\", c[168]],\r\n\t\t[2,'pow', [Math.E,-100], \"Math.E,-100\", c[169]],\r\n\t\t[3,'pow', [Math.LN2,-100], \"Math.LN2,-100\", c[170]],\r\n\t\t[4,'pow', [Math.LN10,-100], \"Math.LN10,-100\", c[171]],\r\n\t\t[5,'pow', [Math.LOG2E,-100], \"Math.LOG2E,-100\", c[172]],\r\n\t\t[6,'pow', [Math.LOG10E,-100], \"Math.LOG10E,-100\", c[173]],\r\n\t\t[7,'pow', [Math.SQRT1_2,-100], \"Math.SQRT1_2,-100\", c[174]],\r\n\t\t[8,'pow', [Math.SQRT2,-100], \"Math.SQRT2,-100\", c[175]],\r\n\t]\r\n}\r\n\r\n\t// POLYFILLS\r\n\tconst acosh = x => Math.log(x + Math.sqrt(x * x - 1))\r\n\tconst asinh = x => {\r\n\t\tconst absX = Math.abs(x)\r\n\t\tif (absX < Math.pow(2, -28)) {\r\n\t\t\treturn x\r\n\t\t}\r\n\t\tconst w = (\r\n\t\t\tabsX > Math.pow(2, 28) ? Math.log(absX) + Math.LN2 :\r\n\t\t\tabsX > 2 ? Math.log(2 * absX + 1 / Math.sqrt(x * x + 1)) :\r\n\t\t\tMath.log1p(absX + (x * x) / (1 + Math.sqrt(1 + (x * x))))\r\n\t\t)\r\n\t\treturn x > 0 ? w : -w\r\n\t}\r\n\tconst atanh = x => Math.log((1 + x) / (1 - x)) / 2\r\n\tfunction cbrt(x) {\r\n\t\tlet y = Math.pow(Math.abs(x), 1 / 3)\r\n\t\treturn x < 0 ? -y : y\r\n\t}\r\n\tconst cosh = x => (Math.exp(x) + Math.exp(-x)) / 2\r\n\tconst expm1 = x => Math.exp(x) - 1\r\n\tfunction hypot(array) {\r\n\t\tlet i, s = 0,\r\n\t\t\tmax = 0,\r\n\t\t\tisInfinity = false,\r\n\t\t\tlen = array.length\r\n\t\tfor (i = 0; i < len; ++i) {\r\n\t\t\tconst arg = Math.abs(+array[i])\r\n\t\t\tif (arg === Infinity) {\r\n\t\t\t\tisInfinity = true\r\n\t\t\t}\r\n\t\t\tif (arg > max) {\r\n\t\t\t\ts *= (max / arg) * (max / arg)\r\n\t\t\t\tmax = arg\r\n\t\t\t}\r\n\t\t\ts += arg === 0 && max === 0 ? 0 : (arg / max) * (arg / max)\r\n\t\t}\r\n\t\treturn isInfinity ? Infinity : (max === Infinity ? Infinity : max * Math.sqrt(s))\r\n\t}\r\n\tconst log1p = x => {\r\n\t\tconst nearX = (x + 1) - 1\r\n\t\treturn (\r\n\t\t\tx < -1 || x !== x ? NaN :\r\n\t\t\tx === 0 || x === Infinity ? x :\r\n\t\t\tnearX === 0 ? x :\r\n\t\t\tx * (Math.log(x + 1) / nearX)\r\n\t\t)\r\n\t}\r\n\tconst log2 = x => Math.log(x) * Math.LOG2E\r\n\tconst log10 = x => Math.log(x) * Math.LOG10E\r\n\tconst sinh = x => (Math.exp(x) - Math.exp(-x)) / 2\r\n\tconst tanh = x => {\r\n\t\tconst a = Math.exp(+x)\r\n\t\tconst b = Math.exp(-x)\r\n\t\treturn a == Infinity ? 1 : b == Infinity ? -1 : (a - b) / (a + b)\r\n\t}\r\n\r\nfunction get_results() {\r\n\tlet t0 = performance.now()\r\n\t// vars\r\n\tlet display = [],\r\n\t\tprevFn = \"start\",\r\n\t\tdemarc = false,\r\n\t\tstrDemarc = \"----------------------------------------------------\",\r\n\t\tsColor = s18,\r\n\t\tvPad = 19, // value tested\r\n\t\tfPad = vPad + 7, // function + value\r\n\t\trPad = 23, // result\r\n\t\tnPad = 5, // user number: 5 means k indents by 2, and k+\"p1\"/\"p2\" is covered in diffs\r\n\t\tbigNstr = \"* bigN = \" + bigN\r\n\r\n\t// BUILD TEST RESULTS\r\n\tlet\tmath1 = [],\t// functions\r\n\t\tmath2 = [], // polyfills\r\n\t\tnumbers = [], // numbers which make a difference\r\n\t\tnumberchk = [], // make sure no duplicates\r\n\t\tpolymatch = [],\r\n\t\tfnmatch = [], // do not match control\r\n\t\tdiffs = [] // fn + poly don't match\r\n\r\n\tlet generate = []\r\n\tfor(let i=0; i < fns.length; i++) {\r\n\t\tlet fn = fns[i]\r\n\t\tlet k = fn[0] // user defined number\r\n\t\tlet kpad = (k.toString()).padStart(nPad)\r\n\t\tlet fnResult = (Math[fn[1]](...fn[2])).toString()\r\n\t\tlet fnValue = fn[3].toString()\r\n\t\tlet fnControl = fn[4].toString()\r\n\t\tlet fnName = fn[1].toString()\r\n\t\tnumberchk.push(fnName+\"-\"+k.toString())\r\n\t\t// fn result\r\n\t\tlet isFnDiff = false\r\n\t\tif (fnResult !== fnControl) {\r\n\t\t\tisFnDiff = true\r\n\t\t\tfnmatch.push(k+\":\"+fnName+\"(\"+fn[3]+\"):\"+fnControl +\":\"+fnResult)\r\n\t\t\tnumbers.push(fnName +\":\"+ k.toString())\r\n\t\t}\r\n\t\tmath1.push(fnName+\"(\"+fnValue+\"):\"+fnResult)\r\n\t\tgenerate.push(\"'\"+fnResult+\"'\")\r\n\r\n\t\t// poly tests\r\n\t\tlet poly1Result = \"\"\r\n\t\tlet poly1Control = \"\"\r\n\t\tif (fn[5] !== undefined && fn[5] !== \"\") {\r\n\t\t\tpoly1Control = fn[5].toString()\r\n\t\t\tif (fnName == \"acosh\") {poly1Result = acosh(...fn[2])\r\n\t\t\t} else if (fnName == \"asinh\") {poly1Result = asinh(...fn[2])\r\n\t\t\t} else if (fnName == \"atanh\") {poly1Result = atanh(...fn[2])\r\n\t\t\t} else if (fnName == \"cbrt\")  {poly1Result = cbrt(...fn[2])\r\n\t\t\t} else if (fnName == \"cosh\" ) {poly1Result = cosh(...fn[2])\r\n\t\t\t} else if (fnName == \"expm1\") {poly1Result = expm1(...fn[2])\r\n\t\t\t} else if (fnName == \"hypot\") {poly1Result = hypot([...fn[2]]) // pass array\r\n\t\t\t} else if (fnName == \"log1p\") {poly1Result = log1p(...fn[2])\r\n\t\t\t} else if (fnName == \"log2\" ) {poly1Result = log2(...fn[2])\r\n\t\t\t} else if (fnName == \"log10\") {poly1Result = log10(...fn[2])\r\n\t\t\t} else if (fnName == \"sinh\" ) {poly1Result = sinh(...fn[2])\r\n\t\t\t} else if (fnName == \"tanh\" ) {poly1Result = tanh(...fn[2])}\r\n\t\t\tpoly1Result = poly1Result.toString()\r\n\t\t\t//console.debug(k, fn[3], fnName, poly1Control, poly1Result)\r\n\t\t}\r\n\r\n\t\t// poly1 results\r\n\t\tlet isPolyDiff = false, isDiff = false\r\n\t\tif (poly1Result !== \"\") {\r\n\t\t\tif (poly1Result !== poly1Control) {\r\n\t\t\t\tisPolyDiff = true\r\n\t\t\t\tpolymatch.push(k+\"p1:\"+fnName+\"(\"+fn[3]+\"):\"+poly1Control +\":\"+poly1Result)\r\n\t\t\t\tnumbers.push(fnName +\":\"+ k.toString() +\"p1\")\r\n\t\t\t}\r\n\t\t\tmath2.push(fnName+\"(\"+fnValue+\")polyfill1:\"+poly1Result)\r\n\t\t\tgenerate.push(\"'\"+poly1Result+\"'\")\r\n\t\t\t// does not match fn\r\n\t\t\tif (poly1Result !== fnResult) {\r\n\t\t\t\tisDiff = true\r\n\t\t\t\tdiffs.push(k+\"p1:\"+fnName+\"(\"+fn[3]+\"):\"+fnResult +\":\"+poly1Result)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// color & pad\r\n\t\tif (isFnDiff) {\r\n\t\t\tfnResult = sb.trim() + fnResult.padStart(rPad) + sc\r\n\t\t} else {\r\n\t\t\tfnResult = fnResult.padStart(rPad)\r\n\t\t}\r\n\t\tif (isPolyDiff) {\r\n\t\t\tpoly1Result = sb.trim() + poly1Result.padStart(rPad) + sc\r\n\t\t} else {\r\n\t\t\tpoly1Result = poly1Result.padStart(rPad)\r\n\t\t}\r\n\t\tif (isDiff) {\r\n\t\t\tpoly1Result += sb +\" [match]\"+ sc\r\n\t\t}\r\n\t\tif (poly1Result !== \"\") {\r\n\t\t\tfnResult += \", \" + poly1Result\r\n\t\t}\r\n\r\n\t\t// demarcate each function\r\n\t\tif (prevFn !== fnName) {demarc = true} else {demarc = false}\r\n\t\tif (demarc == true) {\r\n\t\t\tdisplay.push(sColor.trim() + strDemarc +\"<br>\"+ fnName.toUpperCase() + sc)\r\n\t\t}\r\n\t\tdisplay.push(kpad +\": \"+ fnValue.padEnd(vPad) +\": \"+ fnResult)\r\n\t\t// remember last function\r\n\t\tprevFn = fn[1]\r\n\t}\r\n\t// generate control lists\r\n\t//console.log(\"CONTROL LIST:\\n \" + generate.join(\",\"))\r\n\r\n\t// NUMBER+NAME DUPE CHECK\r\n\tnumberchk = numberchk.filter(function(item, position) {return numberchk.indexOf(item) === position})\r\n\tif (fns.length !== numberchk.length) {\r\n\t\tconsole.debug(\"fns array has duplicate user defined test ids\")\r\n\t}\r\n\r\n\t// NUMBERS\r\n\tlet numstr = \"none\", mathPrev = \"\", mathNext = \"\", tmp_nums = []\r\n\tif (numbers.length > 0) {\r\n\t\tlet numdisplay = []\r\n\t\tfor(let i=0; i < numbers.length; i++) {\r\n\t\t\tlet part0 = numbers[i].split(\":\")[0]\r\n\t\t\tlet part1 = numbers[i].split(\":\")[1]\r\n\t\t\t\tpart1 = part1.replace(/\\p1/g, s12.trim() +\"p1\"+ sc)\r\n\t\t\t// build number string\r\n\t\t\ttmp_nums.push(part1)\r\n\t\t\tif (i < numbers.length - 1) {\r\n\t\t\t\tmathNext = numbers[(i+1)].split(\":\")[0]\r\n\t\t\t} else {\r\n\t\t\t\tmathNext = \"end\"\r\n\t\t\t}\r\n\t\t\t// next math function is diff: write data\r\n\t\t\tif (mathNext !== part0) {\r\n\t\t\t\tnumdisplay.push(s12.trim() + part0.padStart(5) + sc +\": \"+ tmp_nums.join(\", \"))\r\n\t\t\t\ttmp_nums = [] // reset\r\n\t\t\t}\r\n\t\t\tmathPrev = part0\r\n\t\t}\r\n\t\tnumstr = numdisplay.join(\"<br>\")\r\n\t}\r\n\tdom.numbers.innerHTML = numstr\r\n\r\n\t// DIFFS FUNCTION\r\n\tfunction output_diffs(array, type) {\r\n\t\tlet output = []\r\n\t\tif (array.length > 0) {\r\n\t\t\tfor(let i=0; i < array.length; i++) {\r\n\t\t\t\tlet part0 = array[i].split(\":\")[0]\r\n\t\t\t\t\tpart0 = (part0.toString()).padStart(nPad)\r\n\t\t\t\tlet part1 = array[i].split(\":\")[1]\r\n\t\t\t\t\tpart1 = (part1.toString()).padEnd(fPad)\r\n\t\t\t\tlet part2 = array[i].split(\":\")[2]\r\n\t\t\t\t\tpart2 = (part2.toString()).padStart(rPad)\r\n\t\t\t\tlet part3 = array[i].split(\":\")[3]\r\n\t\t\t\toutput.push(s12.trim() + part1 + sc +\" s/be \"+ part2 +\"  got \"+ sb + part3 + sc)\r\n\t\t\t}\r\n\t\t\tdocument.getElementById(\"diff\" + type).innerHTML = output.join(\"<br>\")\r\n\t\t} else {\r\n\t\t\tdocument.getElementById(\"diff\" + type).innerHTML = \"none\"\r\n\t\t}\r\n\t}\r\n\r\n\t// HASHES\r\n\tlet hash1 = sha1(math1.join())\r\n\tlet hash2 = sha1(math2.join())\r\n\tlet math3 = math1.concat(math2)\r\n\tlet hash0 = sha1(math3.join())\r\n\t//console.debug(math3.join(\"\\n\"))\r\n\tlet maincode = get_code(hash0)\r\n\tif (numstr == \"none\") {\r\n\t\tnumstr = s12.trim() +\"  nos: \"+ sc + numstr\r\n\t}\r\n\r\n\t// SHORT REPORT\r\n\tlet report = s12.trim() +\"control: \"+ controlname\r\n\t\t+\"<br>\"+ strDemarc + sc\r\n\t\t+\"<br>\"+ s12.trim() +\" hash: \"+ sc + hash0 + maincode\r\n\t\t+\"<br>\"+ s12.trim() +\" func: \"+ sc + hash1\r\n\t\t+\"<br>\"+ s12.trim() +\" poly: \"+ sc + hash2\r\n\t\t+\"<br>\"+ numstr\r\n\tdom.report.innerHTML = report\r\n\t// LONG REPORT\r\n\toutput_diffs(diffs, 0)\r\n\toutput_diffs(fnmatch, 1)\r\n\toutput_diffs(polymatch, 2)\r\n\t// DATA\r\n\tdom.mathdata.innerHTML = report\r\n\t\t+\"<br><br>\"+ bigNstr.padStart(52)\r\n\t\t+\"<br>\"+ display.join(\"<br>\")\r\n\t// PERF\r\n\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\r\n}\r\n\r\nfunction get_code(hash) {\r\n\tlet r = \"\"\r\n\t// FF\r\n\t\t// windows\r\n\tif      (hash == \"97c37c8c3b1de92333bf1993a7350cf785b2715d\") {r=\"F1\"} // Win7 64bit: FF68-82 64bit [CONTROL]\r\n\telse if (hash == \"d9e8b78b68a5d1086af27ceec9315f0797ccec7c\") {r=\"F2\"} // Win7 64bit: FF68-82 32bit\r\n\telse if (hash == \"06f9ca0df07d4836116143158b670c9d3a417cd4\") {r=\"F3\"} // Win7 64bit: TB68-78 64bit\r\n\telse if (hash == \"dd277bcf4a12289404b0aab1ae43be060be5a12a\") {r=\"F4\"} // Win7 64bit: TB68-78 32bit\r\n\r\n\t\t// pending windows machine no 2 tests (which I know provide more entropy)\r\n\t\t// rampaa\r\n\t//else if (hash == \"b0130c424be28aac4ed155c679c9baf951349b75\") {r=\"something\"} // Win10 64bit: FF80 64bit\r\n\t// hash: b0130c424be28aac4ed155c679c9baf951349b75 [NEW]\r\n\t// func: e8dd64a68e81d54fbaab58bd3b48cbe2179f6587\r\n\t// poly: 0cde3749d8f342c858af1eef6b48f2e2be985440\r\n\t//  cos: 5, 7, 14, 16, 17, 18\r\n\t//  sin: 14 \r\n\t//  tan: 12\r\n\r\n\t\t// android\r\n\telse if (hash == \"571b9a318869bd09dab3fd83fd88883a66188011\") {r=\"F20\"} // android 9 aarch64 | browserstacks: android 10, 11\r\n\telse if (hash == \"56c05c3f03fb55c3c046ad1b68d05d2392fe0c54\") {r=\"F21\"} // browserstacks: android 6,7,7.1,8,9\r\n\telse if (hash == \"da130b6da16b5ddffc42ebf56259c4746d01a3b6\") {r=\"F22\"} // browserstacks: android 9\r\n\telse if (hash == \"d246b2f0103139eb4407a7dfe4dda2af0bcca4e6\") {r=\"F23\"} // browserstacks: android 10\r\n\telse if (hash == \"a624571a642dbcb468f6d2d8203d0659d351bdc1\") {r=\"F24\"} // browserstacks: android 5\r\n\telse if (hash == \"02e1298182c58da58956df1342b525bd5aecfd7e\") {r=\"F25\"} // browserstacks: android 4.4\r\n\telse if (hash == \"66b3de14094fec47652f8ad203e6d8e6c9ee418f\") {r=\"F26\"} // android 8, browserstacks 8.1, chromeOS-32bit android 9\r\n\t\t// linux\r\n\telse if (hash == \"e6c7b364678e8ecec909088314318acb8430ee47\") {r=\"F30\"}\r\n\telse if (hash == \"05ea8b9bed51553711e3bbaa552449d8ea40c4be\") {r=\"F31\"}\r\n\t\t// mac\r\n\telse if (hash == \"84ad7bbb52342b6c224c0fe45977184bc943bb92\") {r=\"F50\"}\r\n\t\t// obsolete linux\r\n\telse if (hash == \"2047b6e1afc4c6bbe55d061e33fa8026c7afd648\") {r=\"F80\"} // mint32: FF60-67\r\n\telse if (hash == \"ac4f01ae0d298a6ef48bb20aa594a77fb14b0968\") {r=\"F81\"} // mint32: TB60\r\n\t\t// obsolete windows\r\n\telse if (hash == \"3ada7d492e4fa7a7a6acea446fa24ec3ff370556\") {r=\"F96\"} // Win7 64bit: FF60-67 64bit [obsolete versions]\r\n\telse if (hash == \"a8b5e74f336489c1df701a95650ab091ff6de436\") {r=\"F97\"} // Win7 64bit: FF60-67 32bit [obsolete versions]\r\n\telse if (hash == \"8d09688f127d62709bbb5d47f7fc8c959ae151a8\") {r=\"F98\"} // Win7 64bit: TB60 64bit [obsolete versions]\r\n\telse if (hash == \"4796b1d067dd047768dcd2e9ecd740c0ef353d31\") {r=\"F99\"} // Win7 64bit: TB60 32bit [obsolete versions]\r\n\r\n\t// CHROME\r\n\telse if (hash == \"42550bb913168ddbb8be40f970adc51344017cad\") { r=\"C1\"}\r\n\t// SAFARI\r\n\telse if (hash == \"4423be6d9bec5550ad38ea9dde7ef2daf0609e0f\") { r=\"S1\"}\r\n\t// iOS\r\n\telse if (hash == \"2e9162520d6f90c28e24d0f2e538f79f503f8a66\") { r=\"iOS1\"}\r\n\t// EDGEHTML\r\n\telse if (hash == \"a53608a3ac4caf531014dcd4cfaac425db405398\") { r=\"E99\"}\r\n\r\n\t// NEW\r\n\tif (r == \"\") {r = \" \"+ zNEW} else {r = s12+\"[\"+ r +\"]\"+sc}\r\n\treturn r\r\n}\r\n\r\nfunction outputMath(control) {\r\n\t// clear\r\n\tlet items = document.getElementsByClassName(\"c mono\")\r\n\tfor (let i=0; i < items.length; i++) {items[i].textContent = \"\"}\r\n\t// control\r\n\tsetBtn(control)\r\n\tif (control == \"firefox\") {controlname = s8 +\"Firefox\"+ sc +\" 78 64bit on Windows 64bit\"\r\n\t} else if (control == \"chrome\") {controlname = s8 +\"Chrome\"+ sc +\" 85 64bit on Windows 64bit\"\r\n\t} else if (control == \"safari\") {controlname = s8 +\"Safari\"+ sc +\" 13.0.5 on macOS Catalina [VM 64bit on Windows 10 64bit]\"}\r\n\t// build control & run\r\n\tbuild_controlset(control)\r\n\t// delay so user can see things being reset\r\n\tsetTimeout(function(){\r\n\t\tget_results()\r\n\t}, 170)\r\n}\r\n\r\nfunction setBtn(control) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn18\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ control)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn18\")\r\n}\r\n\r\ns8 = s8.trim()\r\nget_engine()\r\noutputMath(\"firefox\")\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/mathdata.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>math data</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 780px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#misc\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb18\">\r\n\t\t<col width=\"7%\"><col width=\"93%\">\r\n\t\t<thead><tr><th colspan=\"2\">math data</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A record of which tests for each unique hash [or short code] differ\r\n\t\t\tfrom the Firefox control in <a href=\"math.html\" class=\"blue\">this</a> test</span>\r\n\t\t</td></tr>\r\n\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\">[FF68+] UNIQUE</td></tr>\r\n\t\t<tr><td><span class=\"s12\" id=\"countFF\">FUNC</span></td><td id=\"firefox\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer-->\r\n\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\">[ALL] UNIQUE</td></tr>\r\n\t\t<tr><td><span class=\"s12\" id=\"countAll\">FUNC</span></td><td id=\"numbers\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer-->\r\n\r\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\">RESULTS</td></tr>\r\n\t\t<tr><td><span class=\"s12\">CODE</span></td><td colspan=\"2\">RESULTS</td></tr>\r\n\t\t<tr><td colspan=\"2\"></td></tr> <!-- spacer-->\r\n\t\t<!-- insert rows here for results -->\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar dataset = [\r\n\t// keep it clean and \", \". I trim for number crunching but not the result strings\r\n\r\n// windows\r\n\t['group', 'firefox 68+'],\r\n\t['header', 'windows'],\r\n\t['F1', \"97c37c8c3b1de92333bf1993a7350cf785b2715d\",\r\n\t\t\"nos: none, i'm the control\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: FF68-82 64bit\",\r\n\t],\r\n\t['F2', \"d9e8b78b68a5d1086af27ceec9315f0797ccec7c\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 11, 12, 14\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 6, 7, 8, 10, 15\",\r\n\t\t\"tests: Win7/Win10-vm 64bit**: FF68-82 32bit | Win7-vm 32bit: FF68-82 32bit\",\r\n\t],\r\n\t['F3', \"06f9ca0df07d4836116143158b670c9d3a417cd4\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 14\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: TB68-78 64bit\",\r\n\t],\r\n\t['F4', \"dd277bcf4a12289404b0aab1ae43be060be5a12a\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 14\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 6, 7, 8, 10, 15\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: TB68-78 32bit | Win7-vm 32bit: TB68-78 32bit\",\r\n\t],\r\n// android\r\n\t['header', 'android'],\r\n\t['F20', \"571b9a318869bd09dab3fd83fd88883a66188011\",\r\n\t\t\"cos: 5, 6, 13, 14, 15, 16, 17, 18, 20\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 9 aarch64 FF80 | android 10, 11 (browserstacks)\",\r\n\t],\r\n\t['F21', \"56c05c3f03fb55c3c046ad1b68d05d2392fe0c54\",\r\n\t\t\"cbrt: 1p1\",\r\n\t\t\"cos: 5, 6, 13, 14, 15 ,16, 17, 18, 20\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 6, 7, 7.1, 8, 9 (browserstacks)\",\r\n\t],\r\n\t['F22', \"da130b6da16b5ddffc42ebf56259c4746d01a3b6\",\r\n\t\t\"cos: 5, 6, 13, 15 ,16, 17, 18, 20\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 10, 11, 13, 14, 15\",\r\n\t\t\"tests: android 9 (browserstacks)\",\r\n\t],\r\n\t['F23', \"d246b2f0103139eb4407a7dfe4dda2af0bcca4e6\",\r\n\t\t\"cos: 5, 6, 13, 14, 15 ,16, 17, 18, 20\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 10 (browserstacks)\",\r\n\t],\r\n\t['F24', \"a624571a642dbcb468f6d2d8203d0659d351bdc1\",\r\n\t\t\"cbrt: 1p1\",\r\n\t\t\"cos: 5, 6, 7, 13, 14, 15 ,16, 17, 18, 20\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 5 (browserstacks)\",\r\n\t],\r\n\t['F25', \"02e1298182c58da58956df1342b525bd5aecfd7e\",\r\n\t\t\"cos: 5, 6, 7, 13, 14, 15 ,16, 17, 18, 20\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 4.4 (browserstacks)\",\r\n\t],\r\n\t['F26', \"66b3de14094fec47652f8ad203e6d8e6c9ee418f\",\r\n\t\t\"cbrt: 1p1\",\r\n\t\t\"cos: 5, 6, 13, 14, 15, 16, 17, 18, 20\",\r\n\t\t\"sin: 13, 14\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"tests: android 8 | android 9 chromeOS-32bit | android 8.1 (browserstacks)\",\r\n\t],\r\n// linux\r\n\t['header', 'linux'],\r\n\t['F30', \"e6c7b364678e8ecec909088314318acb8430ee47\",\r\n\t\t\"cos: 3, 4, 5, 6, 7, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"sin: 11, 12, 14\",\r\n\t\t\"tan: 1, 10, 15\",\r\n\t\t\"tests: Linux 64bit: Debian (Buster) Fedora (v32), Manjaro (20.1), MX Linux: all FF80+\",\r\n\t\t\"tests: Tails 4.11: TB78\",\r\n\t],\r\n\t['F31', \"05ea8b9bed51553711e3bbaa552449d8ea40c4be\",\r\n\t\t\"cos: 4, 5, 6, 7, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"sin: 11, 12, 14\",\r\n\t\t\"tan: 1, 10, 15\",\r\n\t\t\"tests: Mint 64bit FF78 | Mint-32bit FF68-82, TB68-78\",\r\n\t],\r\n// mac\r\n\t['header', 'mac'],\r\n\t['F50', \"84ad7bbb52342b6c224c0fe45977184bc943bb92\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 15, 16, 17, 18, 20\",\r\n\t\t\"sin: 0, 1, 10, 12, 13, 15\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12\",\r\n\t\t\"tests: macOS Catalina: FF80, TB68\",\r\n\t],\r\n\r\n// linux FF67 or lower: obsolete\r\n\t['group', 'firefox 67-'],\r\n\t['header', 'linux 67-'],\r\n\t['F80', \"2047b6e1afc4c6bbe55d061e33fa8026c7afd648\",\r\n\t\t\"cos: 4, 5, 6, 7, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 11, 12, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 1, 10, 15\",\r\n\t\t\"tests: Mint 32bit : FF60-67\",\r\n\t],\r\n\t['F81', \"ac4f01ae0d298a6ef48bb20aa594a77fb14b0968\",\r\n\t\t\"asinh: 0\",\r\n\t\t\"atan: 1\",\r\n\t\t\"atanh: 0, 0p1\",\r\n\t\t\"cos: 4, 5, 6, 7, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"expm1: 0\",\r\n\t\t\"sin: 11, 12, 14\",\r\n\t\t\"sinh: 1, 8\",\r\n\t\t\"tan: 1, 10, 15\",\r\n\t\t\"tanh: 0\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: Mint 32bit : TB60\",\r\n\t],\r\n// windows FF67 or lower: obsolete\r\n\t['header', 'windows 67-'],\r\n\t['F96', \"3ada7d492e4fa7a7a6acea446fa24ec3ff370556\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n \t\t\"sinh: 0p1\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: FF60-67 64bit\",\r\n\t],\r\n\t['F97', \"a8b5e74f336489c1df701a95650ab091ff6de436\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 11, 12, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 6, 7, 8, 10, 15\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: FF60-67 32bit | Win7-vm 32bit: FF60-67 32bit\",\r\n\t],\r\n\t['F98', \"8d09688f127d62709bbb5d47f7fc8c959ae151a8\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"cosh: 0, 0p1\",\r\n\t\t\"expm1: 0p1\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 14\",\r\n\t\t\"sinh: 0p1\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: TB60 64bit\",\r\n\t],\r\n\t['F99', \"4796b1d067dd047768dcd2e9ecd740c0ef353d31\",\r\n\t\t\"asinh: 0\",\r\n\t\t\"atan: 1\",\r\n\t\t\"atanh: 0, 0p1\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21\",\r\n\t\t\"expm1: 0\",\r\n\t\t\"sin: 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 14\",\r\n\t\t\"sinh: 1, 8\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 6, 7, 8, 10, 15\",\r\n\t\t\"tanh: 0\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: Win7/Win10-vm 64bit: TB60 32bit | Win7-vm 32bit: TB60 32bit\",\r\n\t],\r\n\r\n// non FF\r\n\t['group', 'non-firefox'],\r\n\t['header', 'chrome / chromium'],\r\n\t['C1', \"42550bb913168ddbb8be40f970adc51344017cad\",\r\n\t\t\"cbrt: 1p1\",\r\n\t\t\"cos: 2, 4, 5, 6, 7, 12, 13, 14, 15, 16, 17, 18, 20\",\r\n\t\t\"cosh: 2, 3\",\r\n\t\t\"hypot: 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"log10: 1, 3, 5, 7, 9, 11, 12\",\r\n\t\t\"sin: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"sinh: 5, 9\",\r\n\t\t\"tan: 10, 11, 12, 13, 14, 15\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: chrome: everywhere (windows, android, mac...) & everything (edge, opera, brave...)\",\r\n\t],\r\n\t['header', 'safari'],\r\n\t['S1', \"4423be6d9bec5550ad38ea9dde7ef2daf0609e0f\",\r\n\t\t\"acos: 0\",\r\n\t\t\"acosh: 2\",\r\n\t\t\"atan: 0\",\r\n\t\t\"atanh: 0, 0p1\",\r\n\t\t\"cbrt: 1\",\r\n\t\t\"cos: 0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 15, 16, 17, 18, 20\",\r\n\t\t\"cosh: 2, 3\",\r\n\t\t\"expm1: 0\",\r\n\t\t\"hypot: 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"sin: 0, 1, 10, 12, 13, 15\",\r\n\t\t\"sinh: 1, 5, 8, 9\",\r\n\t\t\"tan: 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12\",\r\n\t\t\"tanh: 0\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: macOS Catalina Safari\",\r\n\t],\r\n\t['header', 'iOS'],\r\n\t['iOS1', \"2e9162520d6f90c28e24d0f2e538f79f503f8a66\",\r\n\t\t\"acos: 0\",\r\n\t\t\"acosh: 2\",\r\n\t\t\"atan: 0\",\r\n\t\t\"atanh: 0, 0p1\",\r\n\t\t\"cbrt: 1\",\r\n\t\t\"cos: 1, 2, 4, 5, 6, 12, 13, 14, 15, 16, 17, 18, 20, 21\",\r\n\t\t\"cosh: 2, 3\",\r\n\t\t\"expm1: 0\",\r\n\t\t\"hypot: 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"sin: 11, 13, 14, 15\",\r\n\t\t\"sinh: 1, 5, 8, 9\",\r\n\t\t\"tan: 2, 3, 4, 5, 6, 7, 8, 11, 12\",\r\n\t\t\"tanh: 0\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: webkit: Safari, Firefox, Chrome\",\r\n\t],\r\n\t['header', 'EdgeHTML'],\r\n\t['E99', \"a53608a3ac4caf531014dcd4cfaac425db405398\",\r\n\t\t\"acosh: 1, 1p1\",\r\n\t\t\"atanh: 0, 0p1\",\r\n\t\t\"atan2: 1\",\r\n\t\t\"cbrt: 1\",\r\n\t\t\"cos: 0, 5, 7, 10, 11, 13, 16, 17, 18, 21\",\r\n\t\t\"cosh: 1, 1p1, 2, 3\",\r\n\t\t\"hypot: 1p1, 5p1, 6p1\",\r\n\t\t\"log: 1\",\r\n\t\t\"log10: 8, 9\",\r\n\t\t\"sin: 14\",\r\n\t\t\"sinh: 0, 0p1, 1, 4, 4p1, 5, 8p1, 9\",\r\n\t\t\"sqrt: 1\",\r\n\t\t\"tanh: 0\",\r\n\t\t\"pow: 0, 1, 2, 3, 4, 5, 6, 7, 8\",\r\n\t\t\"tests: Win10-vm 64bit EdgeHTML\",\r\n\t],\r\n]\r\n\r\n\r\nfunction outputData() {\r\n\tvar numbers = [],\r\n\t\tfirefox = [], // firefox only numbers\r\n\t\tobsolete = [], // FF67- results\r\n\t\tfirefoxall = [], // out of interest: FFonly vs FFall\r\n\t\ttable = dom.tb18\r\n\r\n\t// use consistent colors across similar tests\r\n\tvar sColor1 = s3, // subheader\r\n\t\tsColor2 = s12, // hash, nos\r\n\t\tsColor3 = s14, // tests\r\n\t\tsColor4 = sg // TB\r\n\r\n\tdataset.forEach(function(data) {\r\n\t\tlet code = data[0],\r\n\t\t\tstr = data[1]\r\n\t\tif (code == \"header\") {\r\n\t\t\tstr = sColor1 + str.toLowerCase() + sc\r\n\t\t\tlet row = table.insertRow(-1)\r\n\t\t\tlet a = row.insertCell(0)\r\n\t\t\tlet b = row.insertCell(1)\r\n\t\t\tb.setAttribute(\"class\", \"mono spaces\")\r\n\t\t\tb.innerHTML = str\r\n\t\t} else if (code == \"group\") {\r\n\t\t\tstr = \"<u>\" + str.toUpperCase() +\"</u>\"\r\n\t\t\tlet row = table.insertRow(-1)\r\n\t\t\tlet a = row.insertCell(0)\r\n\t\t\ta.setAttribute(\"class\", \"mono spaces intro\")\r\n\t\t\ta.colSpan = \"2\"\r\n\t\t\ta.innerHTML = str\r\n\t\t} else {\r\n\t\t\tlet lines = [sColor2 +\" hash: \"+ sc + data[1]]\r\n\t\t\tfor (let i=2; i < data.length; i++) {\r\n\t\t\t\tlet fnc = data[i].split(\":\")[0]\r\n\t\t\t\tlet fncPad = s12.trim() + fnc.padStart(5) +\":\"+ sc\r\n\t\t\t\tstr = data[i].split(\":\")[1]\r\n\r\n\t\t\t\tif (fnc == \"tests\") {\r\n\t\t\t\t\t// configs tested\r\n\t\t\t\t\tstr = data[i]\r\n\t\t\t\t\t// color up TB: boring list of replaces & lots of duplication :)\r\n\t\t\t\t\tstr = str.replace(/TB68-78 64bit/g, sColor4 +\"TB68-78 64bit\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB68-78 32bit/g, sColor4 +\"TB68-78 32bit\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB60 64bit/g, sColor4 +\"TB60 64bit\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB60 32bit/g, sColor4 +\"TB60 32bit\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB68-78/g, sColor4 +\"TB68-78\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB78/g, sColor4 +\"TB78\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB68/g, sColor4 +\"TB68\"+ sc)\r\n\t\t\t\t\tstr = str.replace(/TB60/g, sColor4 +\"TB60\"+ sc)\r\n\t\t\t\t\tlines.push(sColor3 + str + sc)\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// results\r\n\t\t\t\t\tlet strpretty = str.replace(/p1/g, sColor2 +\"p1\"+ sc)\r\n\t\t\t\t\tlines.push(fncPad + strpretty)\r\n\t\t\t\t\t// numbers: ignore F1 notation line (\"nos\")\r\n\t\t\t\t\tif (fnc !== \"nos\") {\r\n\t\t\t\t\t\tlet tmp = str.split(\",\")\r\n\t\t\t\t\t\ttmp.forEach(function(num) {\r\n\t\t\t\t\t\t\tnum = num.trim()\r\n\t\t\t\t\t\t\t// all\r\n\t\t\t\t\t\t\tnumbers.push(fnc +\" \"+ num)\r\n\t\t\t\t\t\t\t// FF\r\n\t\t\t\t\t\t\tif (code.substring(0,1) == \"F\") {\r\n\t\t\t\t\t\t\t\tlet second = code.substring(1,2)\r\n\t\t\t\t\t\t\t\tif (second == \"8\" || second == \"9\") {\r\n\t\t\t\t\t\t\t\t\t// obsolete FF\r\n\t\t\t\t\t\t\t\t\tobsolete.push(fnc +\" \"+ num)\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t// FF68+\r\n\t\t\t\t\t\t\t\t\tfirefox.push(fnc +\" \"+ num)\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tfirefoxall.push(fnc +\" \"+ num)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// add table row\r\n\t\t\tlet row = table.insertRow(-1)\r\n\t\t\tlet a = row.insertCell(0)\r\n\t\t\tlet b = row.insertCell(1)\r\n\t\t\ta.innerHTML = code\r\n\t\t\tb.setAttribute(\"class\", \"mono spaces\")\r\n\t\t\tb.innerHTML = lines.join(\"<br>\")\r\n\t\t}\r\n\t})\r\n\r\n\t// test number: sort them, de-dupe them, output them\r\n\tnumbers.sort()\r\n\tfirefox.sort()\r\n\tobsolete.sort()\r\n\tfirefoxall.sort()\r\n\tnumbers = numbers.filter(function(item, position) {return numbers.indexOf(item) === position})\r\n\tfirefox = firefox.filter(function(item, position) {return firefox.indexOf(item) === position})\r\n\tobsolete = obsolete.filter(function(item, position) {return obsolete.indexOf(item) === position})\r\n\tfirefoxall = firefoxall.filter(function(item, position) {return firefoxall.indexOf(item) === position})\r\n\t//console.debug(numbers.length, firefox.length, obsolete.length, firefoxall.length)\r\n\t// 110 55 61 72\r\n\t// ToDo: maybe abraham wants all numbers excluding FF67-\r\n\r\n\tlet strpretty = numbers.join(\", \")\r\n\tstrpretty = strpretty.replace(/p1/g, sColor2 +\"p1\"+ sc)\r\n\tdom.numbers.innerHTML = strpretty\r\n\tdom.countAll = numbers.length\r\n\r\n\tstrpretty = firefox.join(\", \")\r\n\tstrpretty = strpretty.replace(/p1/g, sColor2 +\"p1\"+ sc)\r\n\tdom.firefox.innerHTML = strpretty\r\n\tdom.countFF = firefox.length\r\n\r\n\t// ToDo: number crunch a number set to bare minimum\r\n\r\n}\r\noutputData()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/mathspoof.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>math spoof detection</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#misc\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb18\">\r\n\t\t<col width=\"1%\"><col width=\"8%\"><col width=\"92%\">\r\n\t\t<thead><tr><th colspan=\"3\">\r\n\t\t\t<div class=\"nav-title\">math spoof fingerprinting\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\r\n\t\t<tr><td colspan=\"3\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">blah blah</span>\r\n\t\t</td></tr>\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\"><div class=\"btn-left\"><span class=\"btn18 btn\" onClick=\"outputMath(170)\">[ re-run ]</span></div>ANALYSIS</td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!--spacer-->\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">run 1</td><td class=\"c mono spaces\" id=\"run1hash\"></td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"padr\">run 2</td><td class=\"c mono spaces\" id=\"run2hash\"></td></tr>\r\n\t\t<tr><td colspan=\"3\"></td></tr> <!--spacer-->\r\n\r\n\t\t<tr><td colspan=\"3\"><hr></td></tr>\r\n\t\t<tr><td colspan=\"3\">[RUN 1] DATA</td></tr>\r\n\t\t<tr><td></td><td colspan=\"2\" class=\"c mono spaces\" id=\"output\"></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\n/* NOTES: cydec targets:\r\nMath.SQRT1_2\r\nMath.PI\r\nMath.SQRT2\r\nMath.cosh(1)\r\nMath.expm1(1)\r\nMath.E\r\nMath.LN2\r\nMath.LOG2E\r\nMath.LOG10E\r\n1e-310\r\n1e0\r\n*/\r\n\r\nvar fnType = true // toggle between runs\r\n\r\nfunction run(runtype) {\r\n\t/* https://en.wikipedia.org/wiki/Trigonometric_functions#cos */\r\n\r\n\tif (runtype == 1) {fnType = !fnType}\r\n\r\n\treturn new Promise(resolve => {\r\n\t\t// rebuild it on each run\r\n\t\tlet c = 1 // reference counter\r\n\t\tlet fns = [\r\n\t\t\t// [counter, function1, value1, function2, value2, engine, toFixed]\r\n\t\t\t[\"separator\",\"hypot/sqrt: known targeted values\"],\r\n\t\t\t[c++, 'hypot', [Math.PI, Math.PI], 'sqrt', [(Math.PI*Math.PI) + (Math.PI*Math.PI)]],\r\n\t\t\t[c++, 'hypot', [Math.cosh(1), Math.cosh(1)], 'sqrt', [(Math.cosh(1)*Math.cosh(1)) + (Math.cosh(1)*Math.cosh(1))]],\r\n\t\t\t[c++, 'hypot', [Math.expm1(1), Math.expm1(1)], 'sqrt', [(Math.expm1(1)*Math.expm1(1)) + (Math.expm1(1)*Math.expm1(1))]],\r\n\t\t\t[c++, 'hypot', [Math.LOG2E, Math.LOG2E], 'sqrt', [(Math.LOG2E*Math.LOG2E) + (Math.LOG2E*Math.LOG2E)]],\r\n\t\t\t[c++, 'hypot', [Math.LOG10E, Math.LOG10E], 'sqrt', [(Math.LOG10E*Math.LOG10E) + (Math.LOG10E*Math.LOG10E)]],\r\n\t\t\t[c++, 'hypot', [1e0, 1e0], 'sqrt', [(1e0*1e0) + (1e0*1e0)]],\r\n\t\t\t/* in chrome (not sure about Safari) we need to fix up the decimals places\r\n\t\t\t\t1.0000000000000002  1\r\n\t\t\t\t2.0000000000000004  2\r\n\t\t\t\t3.844231028159117   3.8442310281591165\r\n\t\t\t\t0.9802581434685472  0.9802581434685471\r\n\t\t\t*/\r\n\t\t\t[c++, 'hypot', [Math.SQRT1_2, Math.SQRT1_2], 'sqrt', [(Math.SQRT1_2*Math.SQRT1_2) + (Math.SQRT1_2*Math.SQRT1_2)], \"nonFF\", 15],\r\n\t\t\t[c++, 'hypot', [Math.SQRT2, Math.SQRT2], 'sqrt', [(Math.SQRT2*Math.SQRT2) + (Math.SQRT2*Math.SQRT2)], \"nonFF\", 15],\r\n\t\t\t[c++, 'hypot', [Math.E, Math.E], 'sqrt', [(Math.E*Math.E) + (Math.E*Math.E)], \"nonFF\", 14],\r\n\t\t\t[c++, 'hypot', [Math.LN2, Math.LN2], 'sqrt', [(Math.LN2*Math.LN2) + (Math.LN2*Math.LN2)], \"nonFF\", 15],\r\n\r\n\t\t\t[\"separator\", \"hypot/sqrt\"],\r\n\t\t\t[c++, 'hypot', [9, 3], 'sqrt', [(9*9) + (3*3)]],\r\n\t\t\t[c++, 'hypot', [5, 7], 'sqrt', [(5*5) + (7*7)]],\r\n\t\t\t[c++, 'hypot', [95, -23], 'sqrt', [(95*95) + (-23 * -23)]],\r\n\t\t\t[c++, 'hypot', [1e-3, 1e-3, 1e-3], 'none', 0.0017320508075688772],\r\n\t\t\t[c++, 'hypot', [1e300, 1e300], 'none', 1.4142135623730952e+300],\r\n\t\t\t[c++, 'hypot', [1e100, 1e200, 1e300], 'none', 1e300],\r\n\t\t\t[c++, 'hypot', [1e3, 1e-3], 'none', 1000.0000000005],\r\n\t\t\t[c++, 'hypot', [1e-300, 1e300], 'none', 1e300],\r\n\t\t\t[c++, 'hypot', [1e3, 1e-3, 1e3, 1e-3], 'none', 1414.2135623738021555],\r\n\t\t\t[c++, 'hypot', [1e1, 1e2, 1e3], 'sqrt', [1e2 + 1e4 + 1e6]],\r\n\t\t\t[c++, 'hypot', [1e1, 1e2, 1e3, 1e4], 'sqrt', [1e2 + 1e4 + 1e6 + 1e8]],\r\n\r\n\t\t\t// cos/sin\r\n\t\t\t[\"separator\", \"cos/sin\"],\r\n\t\t\t[c++, 'cos', [5-2], 'none', [(Math.cos(5) * Math.cos(2)) + (Math.sin(5) * Math.sin(2))] ],\r\n\t\t\t[c++, 'cos', [1-8], 'none', [(Math.cos(1) * Math.cos(8)) + (Math.sin(1) * Math.sin(8))] ],\r\n\t\t\t[c++, 'cos', [8-(-1)], 'none', [(Math.cos(8) * Math.cos(-1)) + (Math.sin(8) * Math.sin(-1))] ],\r\n\r\n\t\t\t// constants\r\n\t\t\t[\"separator\", \"constants\"],\r\n\t\t\t[\"indent\", \"pythagoras SQRT2\"],\r\n\t\t\t[c++, 'none', [\"2.000000000000000\"], 'none', [Math.SQRT2 * Math.SQRT2], \"all\", 15],\r\n\t\t\t[\"indent\", \"Theodorus SQRT 3\"],\r\n\t\t\t[c++, 'none', [3], 'none', [Math.sqrt(3) * Math.sqrt(3)], \"all\", 15],\r\n\r\n\r\n\t\t\t// law of cosines\r\n\t\t\t//[\"separator\", \"law of cosines\"],\r\n\t\t\t//x = 3; y = 3\r\n\t\t\t//let temp = Math.sqrt((x * x) + (y * y) - ( (2 * x * y) * Math.cos(Math.PI/3) ))\r\n\t\t\t// limit to 15 decimal places: values is usually 2.9999999999999996\r\n\t\t\t//temp = temp.toFixed(15)\r\n\t\t\t//build( temp, 2.999999999999999 )\r\n\r\n\t\t]\r\n\r\n\t\t// expected: 26 item: f50756e5747a502327cebeed8b11d8b7fc6a2aa3\r\n\r\n\t\tlet output = []\r\n\t\tlet data = []\r\n\t\tlet diffs = []\r\n\t\tlet detail = []\r\n\t\t\r\n\t\tfor(let i=0; i < fns.length; i++) {\r\n\t\t\tlet fn = fns[i]\r\n\t\t\tif (fn[0] == \"separator\") {\r\n\t\t\t\t// add separator\r\n\t\t\t\toutput.push(\"<br>\"+ s18 +\" \".repeat(5) + fn[1].toLowerCase() + sc +\"<br>\")\r\n\t\t\t} else if (fn[0] == \"indent\") {\r\n\t\t\t\toutput.push(s12 +\"     \"+ fn[1].toLowerCase() + sc)\r\n\t\t\t} else {\r\n\t\t\t\tlet r1, r2\r\n\t\t\t\ttry {\r\n\t\t\t\t\tif (fn[1] == \"none\") {\r\n\t\t\t\t\t\t// just use the value\r\n\t\t\t\t\t\tif (Array.isArray(fn[2])) {\r\n\t\t\t\t\t\t\t// in case I left it in an array on it's own\r\n\t\t\t\t\t\t\tlet array = fn[2]\r\n\t\t\t\t\t\t\tr1 = array[0]\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tr1 = fn[2]\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (fnType) {\r\n\t\t\t\t\t\t\tr1 = newFn(Math[fn[1]](...fn[2])) //.toString()\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tr1 = (Math[fn[1]](...fn[2])) //.toString()\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tr1 = zErr\r\n\t\t\t\t}\r\n\t\t\t\ttry {\r\n\t\t\t\t\tif (fn[3] == \"none\") {\r\n\t\t\t\t\t\t// just use the value\r\n\t\t\t\t\t\tif (Array.isArray(fn[4])) {\r\n\t\t\t\t\t\t\t// in case I left it in an array on it's own\r\n\t\t\t\t\t\t\tlet array = fn[4]\r\n\t\t\t\t\t\t\tr2 = array[0]\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tr2 = fn[4]\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (fnType) {\r\n\t\t\t\t\t\t\tr2 = newFn(Math[fn[3]](...fn[4])) //.toString()\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tr2 = (Math[fn[3]](...fn[4])) //.toString()\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch(e) {\r\n\t\t\t\t\tr2 = zErr\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// fixed decimal places\r\n\t\t\t\tif (fn[5] !== undefined) {\r\n\t\t\t\t\tlet isFixed = false\r\n\t\t\t\t\tlet fixedType = fn[5]\r\n\t\t\t\t\tif (fixedType == \"nonFF\") {if (!isFF) {isFixed = true}}\r\n\t\t\t\t\tif (fixedType == \"all\") {isFixed = true}\r\n\t\t\t\t\tif (isFixed) {\r\n\t\t\t\t\t\tlet fix = fn[6]\r\n\t\t\t\t\t\ttry {r1 = r1.toFixed(fix)} catch(e) {}\r\n\t\t\t\t\t\ttry {r2 = r2.toFixed(fix)} catch(e) {}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// make results the same type\r\n\t\t\t\tif (typeof r1 === \"string\") {\r\n\t\t\t\t\tr2 = r2.toString()\r\n\t\t\t\t}\r\n\t\t\t\t// store pure data\r\n\t\t\t\tdata.push([r1,r2])\r\n\r\n\t\t\t\t// compare\r\n\t\t\t\tlet notation = \"\"\r\n\t\t\t\tif (r1 !== zErr && r2 !== zErr) {\r\n\t\t\t\t\tnotation = \" \"+ (r1 == r2 ? green_tick : red_cross)\r\n\t\t\t\t\tif (r1 !== r2) {\r\n\t\t\t\t\t\tdetail.push([fn[0],r1,r2])\r\n\t\t\t\t\t\tdiffs.push(fn[0])\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// pad\r\n\t\t\t\tr1 = r1.toString().padEnd(24)\r\n\t\t\t\tr2 = r2.toString().padEnd(24)\r\n\t\t\t\t// push\r\n\t\t\t\tlet ref = fn[0].toString()\r\n\t\t\t\tref = s12 + ref.padStart(3) + sc +\": \"\r\n\t\t\t\toutput.push(ref + r1 +\"   \"+ r2 + notation)\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t\t// log detailed diffs\r\n\t\tif (detail.length) {\r\n\t\t\tconsole.log(\"RUN \"+ runtype +\" DIFFS: \"\r\n\t\t\t\t+ detail.length +\" item\"+ (detail.length > 1 ? \"s\" : \"\") +\"\\n\", detail)\r\n\t\t}\r\n\r\n\t\tif (runtype == 1) {\r\n\t\t\tdom.output.innerHTML = output.join(\"<br>\")\r\n\t\t}\r\n\t\treturn resolve([data,diffs])\r\n\t})\r\n}\r\n\r\nfunction outputMath(delay) {\r\n\t// clear\r\n\tlet items = document.getElementsByClassName(\"c\")\r\n\tfor (let i=0; i < items.length; i++) {items[i].textContent = \"\"}\r\n\t// delay so user can see things being reset\r\n\tsetTimeout(function(){\r\n\t\tlet t0 = performance.now()\r\n\t\tPromise.all([\r\n\t\t\trun(1),\r\n\t\t\trun(2),\r\n\t\t]).then(function(results){\r\n\t\t\t// run1\r\n\t\t\tlet run1 = results[0]\r\n\t\t\tlet run1hash = mini(run1[0].join())\r\n\t\t\tlet run1diff = run1[1]\r\n\t\t\tlet run1count = run1diff.length\r\n\t\t\trun1count = (run1count == 0 ? sg: sb) +\"[\"+ run1count +\"]\"+ sc\r\n\t\t\t\t+ (fnType ? s13 +\"[newFn]\"+ sc : \"\")\r\n\t\t\tdom.run1hash.innerHTML = run1hash + run1count\r\n\t\t\t// run 2\r\n\t\t\tlet run2 = results[1]\r\n\t\t\tlet run2hash = mini(run2[0].join())\r\n\t\t\tlet run2diff = run2[1]\r\n\t\t\tlet run2count = run2diff.length\r\n\t\t\trun2count = (run2count == 0 ? sg: sb) +\"[\"+ run2count +\"]\"+ sc\r\n\t\t\t// match hashes\r\n\t\t\tlet hashmatch = (run1hash == run2hash ? sg : sb) +\"[match]\"+ sc\r\n\t\t\tdom.run2hash.innerHTML = run2hash + run2count + hashmatch\r\n\t\t\t// perf\r\n\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t})\r\n\t}, delay)\r\n}\r\n\r\noutputMath(1)\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/newwin.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=400\">\n\t<title>i'm a new window</title>\n\t<style>\n\tbody {background-color: #161b22; color: #b3b3b3;}\n\th2 {font-size: 24px}\n\t.s1 {background-color: #161b22; color: #dc8c8c;}\n\t.hidden {display: none;}\n\t</style>\n</head>\n\n<body>\n\t<center>\n\t<br>\n\t<p>feel free to <span class=\"s1\">close</span> me<p>\n\t<p><span class=\"s1\">refer</span> back to the<br>results on the main test</p>\n\t</center>\n\t<br>\n</body>\n</html>\n"
  },
  {
    "path": "tests/newwinsim.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<title>newwin sim</title>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\ttable {width: 780px;}\r\n\t</style>\r\n</head>\r\n<body>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#screen\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb1\">\r\n\t\t<col width=\"50%\"><col width=\"50%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">new window simulation</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A \"what if\" simulation of all possible new window sizes.\r\n\t\t\tThis is a worst case scenario, and not all variations would be likely,\r\n\t\t\te.g. rediculous scaling or having massive taskbar sizes on low-res displays (I have tried to weed a few of these out).\r\n\t\t\t<code>p</code> means portrait e.g. <code>pXGA</code> is XGA in portrait mode.</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn1 btnfirst\" onClick=\"run('win7')\">[ win7 ]</span>\r\n\t\t\t<span class=\"btn1 btn\" onClick=\"run('win11')\">[ win11 ]</span>\r\n\t\t\t<!--<span class=\"btn1 btn\" onClick=\"run('mac')\">[ mac ]</span>-->\r\n\t\t\t\t| <span class=\"no_color\">lock at default: </span>\r\n\t\t\t<input type=\"checkbox\" id=\"optCompact\"> <span class=\"no_color\">compact</span>\r\n\t\t\t<input type=\"checkbox\" id=\"optMenubar\"> <span class=\"no_color\">menubar</span>\r\n\t\t\t<input type=\"checkbox\" id=\"optTitlebar\"> <span class=\"no_color\">titlebar</span>\r\n\t\t\t<br><br><hr>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td colspan=\"2\" class=\"mono spaces\" style=\"text-align: left\"><span class=\"no_color\" id=\"results\"></span></td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\n// actual resolutions reported (i.e not necessarily native)\r\n\t// https://firefoxgraphics.github.io/telemetry/#view=monitors\r\n\t// https://data.firefox.com/dashboard/hardware\r\n\t// https://gs.statcounter.com/screen-resolution-stats/desktop/worldwide\r\nconst resCommon = [\r\n\t// anything smaller than these would have no change with expanding max width from 1000\r\n                               //  statc,    FF,  dashboard\r\n\t[3840, 2160, \"4K UHS-1\"],    //          1.4%\r\n\t[2880, 1800, \"Retina 15in\"], //                 2.1%\r\n\t[2560, 1440, \"WQHD\"],        //   2.5%,  2.2%\r\n\t[1920, 1200, \"WUXGA\"],       //   0.8%,  1.7%\r\n\t[1920, 1080, \"FHD\"],         //  23.0%, 49.6%\r\n\t[1680, 1050, \"WSXGA+\"],      //   1.4%,  2.2%\r\n\t[1600,  900, \"HD+\"],         //   3.4%,  6.3%\r\n\t[1536,  864],                //  10.8%, \r\n\t[1440,  900, \"WXSGA\"],       //   6.0%,  2.6%\r\n\t[1366,  768, \"WXGA-HD\"],     //  16.9%, 21.5%\r\n\t[1360,  768],                //   1.0%,  1.3%\r\n\t[1280, 1024, \"SXGA\"],        //   1.8%,  3.2%\r\n\t[1280,  800, \"WXGA-max\"],    //   1.6%,         1.7%\r\n\t[1280,  720, \"WXGA-min\"],    //   6.2%\r\n\t[1024,  768, \"XGA\"],         //   1.6%   2.0%\r\n\r\n\t/* ignore for now\r\n\t\t// none of these change if we up max width from 1000 to 1400\r\n\t\t// and none are likely to system scale bigger\r\n\t\t// p = portrait\r\n\t[ 768, 1024, \"pXGA\"],\t       //   1.8%         // portrait 1.33\r\n\t[ 810, 1080],                //   1.7%         // portrait 1.33\r\n\t[1024, 1366],                //   0.7%         // portrait 1.33\r\n\t[ 820, 1180],                //   0.6%         // portrait 1.43\r\n\t[ 834, 1194],                //   0.5%         // portrait 1.43\r\n\t//*/\r\n                               //  82.3%, 94.0%\r\n\t// other                     //  15.9%,  6.1%\r\n\t/* ignored\r\n\t[ 360,  800], // 1.0%\r\n\t[ 800,  600], // 0.9%\r\n\t//*/\r\n\r\n]\r\n\r\n// these ones we apply scaling to generate more possible\r\n\t// screen resolutions which we scale then merge with common\r\n\t// https://en.wikipedia.org/wiki/List_of_common_resolutions\r\n  // https://en.wikipedia.org/wiki/Computer_display_standard#Standards\r\n  // https://en.wikipedia.org/wiki/Graphics_display_resolution#Extended_Graphics_Array_(XGA_and_derivatives)\r\nconst resNative = [\r\n\t/* ignore\r\n\t[ 1024,  600], // SVGA 10\" netbooks\r\n\t[ 1024,  800], // Sun-1\r\n\t[ 1080, 1200], // HTC Vive\r\n\t[ 1120,  832], // NeXt\r\n\t[ 1152,  768], // Apple PowerBook G4\r\n\t[ 1152,  864], // XGA+ (apple)\r\n\t[ 1152,  900], // Sun-2, Sun-3, Sun-4\r\n\t[ 1280,  854], // Apple PowerBook G4\r\n\t[ 1440,  960], // Apple PowerBook G4\r\n\t[ 1440, 1080], // HDV\r\n\t[ 1600,  768], // Sony VAIO P Series (2009-2010)\r\n\t[ 1600, 1024], // SGI\r\n\t[ 1600, 1200], // UXGA: laptops e.g. Lenovo Thankpad T60 (pre 2007)\r\n\t[ 1600, 1280], // Sun-3\r\n\t[ 2560, 1700], // chromebooks\r\n\t[ 2560, 1920], // max CRT .. WTF? CRT's in this day and age ... good grief\r\n\t[ 8192, 8192], // 8K Fulldome = theatres\r\n\t//*/\r\n\t/* ignore DCI - video format: digital cinema spec\r\n\t[ 2048, 1080], // DCI 2K\r\n\t[ 4096, 2160], // DCI 4K\r\n\t[ 8192, 4320], // DCI 8K\r\n\t[16384, 8640], // DCI 16K\r\n\t//*/\r\n\r\n\t//* no documentation except \"supported in some GPUs, monitors, and games\"\r\n\t\t// don't scale these\r\n\t[ 1152,  720, \"1152_720\"],\r\n\t[ 1776, 1000, \"1776_1000\"],\r\n\t[ 1792, 1344, \"1792_1344\"],\r\n\t[ 1800, 1440, \"1800_1440\"],\r\n\t[ 1856, 1392, \"1856_1392\"],\r\n\t[ 2304, 1728, \"2304_1728\"],\r\n\t[ 2048, 1280, \"2048_1280\"],\r\n\t[ 2576, 1450, \"2576_1450\"],\r\n\t[ 2880,  900, \"2880_900\"], // Alienware\r\n\t//*/\r\n\r\n\t//* temp from resCommon\r\n\t\t// all the rest of resCommon are below\r\n\t\t// these maybe == scaled higher res\r\n\t[ 1360,  768, \"1360_768\"],\r\n\t[ 1536,  864, \"1536_864\"],\r\n\t//*/\r\n\r\n\t//* temp portrait from resCommon\r\n\t[ 768, 1024, \"pXGA\"],       //   1.8%   // portrait 1.33\r\n\t[ 810, 1080, \"p1080_810\"],  //   1.7%   // portrait 1.33\r\n\t[1024, 1366, \"pWXGA-HD\"],   //   0.7%   // portrait 1.33\r\n\t[ 820, 1180, \"p1180_820\"],  //   0.6%   // portrait 1.43\r\n\t[ 834, 1194, \"p1194_834\"],  //   0.5%   // portrait 1.43\r\n\t//*/\r\n\r\n\t// standards\r\n\t[ 1024,  768, \"XGA\"],\r\n\t[ 1280,  720, \"WXGA-min\"],\r\n\t[ 1280,  768, \"WXGA\"], // WXGA-average\r\n\t[ 1280,  800, \"WXGA-max\"],\r\n\t[ 1280,  960, \"SXGA−\"],\r\n\t[ 1280, 1024, \"SXGA\"],\r\n\t[ 1366,  768, \"WXGA-HD\"],\r\n\t[ 1440,  900, \"WXSGA\"], // also WXGA+\r\n\t[ 1400, 1050, \"SXGA+\"],\r\n\t[ 1600,  900, \"HD+\"],\r\n\t[ 1680, 1050, \"WSXGA+\"],\r\n\t[ 1920, 1080, \"FHD\"],\r\n\t[ 1920, 1200, \"WUXGA\"],\r\n\t[ 1920, 1280, \"FHD Surface 3\"],\r\n\t[ 1920, 1440, \"TXGA\"],\r\n\t[ 2048, 1152, \"QWXGA\"], // 2K\r\n\t[ 2160, 1440, \"Surface Pro 3\"],\r\n\t[ 2304, 1440, \"Retina\"],\r\n\t[ 2256, 1504, \"Surface\"],\r\n\t[ 2560, 1080, \"UW FHD\"],\r\n\t[ 2560, 1440, \"WQHD\"],\r\n\t[ 2560, 1600, \"WQXGA\"],\r\n\t[ 2560, 2048, \"SQXGA\"],\r\n\t[ 2736, 1824, \"Surface 4\"], // Pro\r\n\t[ 2800, 2100, \"QSXGA+\"],\r\n\t[ 2880, 1620, \"Thinkpad W541\"],\r\n\t[ 2880, 1800, \"Retina 15in\"], // Apple 15\" MacBook Pro\r\n\t[ 2880, 1920, \"Surface Pro X\"], // Pro\r\n\t[ 3000, 2000, \"3K\"], //MS Surface Book, Huawei MateBook X Pro\r\n\t[ 3072, 1920, \"Retina 16in\"], // Apple 16\" MacBook Pro\r\n\t[ 3200, 1800, \"WQXGA+\"],\r\n\t[ 3200, 2048, \"WQSXGA\"],\r\n\t[ 3200, 2400, \"QWUXGA\"],\r\n\t[ 3240, 2160, \"Surface Book 2\"], // 15inch\r\n\t[ 3440, 1440, \"UWQHD\"],\r\n\t[ 3840, 1600, \"UW4K\"],\r\n\t[ 3840, 2160, \"4K UHS-1\"],\r\n\t[ 3840, 2400, \"WQUXGA\"],\r\n\t[ 4096, 2304, \"4K Retina\"],\r\n\t[ 4096, 3072, \"HXGA\"],\r\n\t[ 4480, 2520, \"4.5K Retina\"],\r\n\t[ 4500, 3000, \"Surface Studio\"],\r\n\t[ 5120, 1440, \"DQHD\"],\r\n\t[ 5120, 2880, \"5K\"],\r\n\t[ 5120, 3200, \"WHXGA\"],\r\n\t[ 5120, 4096, \"HSXGA\"],\r\n\t[ 6016, 3384, \"6K Retina\"],\r\n\t[ 6400, 4096, \"WHSXGA\"],\r\n\t[ 6400, 4800, \"HUXGA\"],\r\n//\t[ 6480, 3240, \"?\"],\r\n\t[ 7680, 4320, \"8K UDH-2\"],\r\n\t[ 7680, 4800, \"WHUXGA\"],\r\n//\t[ 8192, 4608, \"?\"],\r\n\t[10240, 4320, \"UW10K\"],\r\n\t[15360, 8640, \"16K\"],\r\n]\r\n\r\nconst oResData = {\r\n\t\"windows\": {\r\n\t\t// min/max: nothing at compact [1], everything at touch\r\n\t\t// bookmarks toolbar, menubar, titlebar\r\n\t\t\"chrome\": {\r\n\t\t\t// devPixels = -1, system scaling 100\r\n\t\t\t\"min\": [12,85],\r\n\t\t\t\"max\": [16,188],\r\n\t\t},\r\n\t\t\"dockerheight\": [\r\n\t\t\t// on top/bottom\r\n\t\t\t0, // min: autohide\r\n\t\t\t30, // win 7 single height\r\n\t\t\t48, // win 11 default: can vary by 1 with various scaling\r\n\t\t\t62, // max: win 7 double height\r\n\t\t],\r\n\t\t\"dockerwidth\": [\r\n\t\t\t// on side\r\n\t\t\t0, // min: autohide\r\n\t\t\t62, // default\r\n\t\t\t130, // max: user stretched it a little to read app titles\r\n\t\t],\r\n\t\t\"scaling\": [100, 125, 150, 175, 200, 225, 250, 300, 350], // from win7-11: custom scaling 100-500 not recommended\r\n\t}\r\n}\r\n\r\n\r\n// VARS\r\nlet aVariations = []\r\nlet oData = {}\r\nlet oRaw = {}\r\nconst oTests = {\r\n\t// width, height\r\n\t\"ORIGINAL\": [1000, 1000],\r\n\t\"NEW\": [1400,  900],\r\n\t\"bigger\": [1400, 1000],\r\n\t\"embiggened\": [1600,  900],\r\n\t\"cromulent\": [1600, 1000],\r\n\t\"X\": [\"variable up to 1400\", 900]\r\n}\r\nlet oBuckets = {}\r\nlet resUsed = []\r\nlet isDimensions = false // show available inner dimensions or use the short basename\r\n\r\nfunction calcAB(w, h, origin, basename) {\r\n\tlet res = []\r\n\tfor (const k of Object.keys(oTests)) {\r\n\t\tlet maxW = oTests[k][0], maxH = oTests[k][1], finalWidth, finalHeight\r\n\r\n\t\t// height 100's\r\n\t\tif (maxH < h) {\r\n\t\t\tfinalHeight = maxH\r\n\t\t} else {\r\n\t\t\tfinalHeight = Math.floor(h/100) * 100\r\n\t\t}\r\n\t\t// width 200's\r\n\t\t// variable steps\r\n\t\tif (maxW == \"variable up to 1400\") {\r\n\t\t\tif (finalHeight == 900) {maxW = 1400\r\n\t\t\t} else if (finalHeight === 800) {maxW = 1200\r\n\t\t\t} else {maxW = 1000}\r\n\t\t}\r\n\t\tif (maxW < w) {\r\n\t\t\tfinalWidth = maxW\r\n\t\t} else {\r\n\t\t\tfinalWidth = Math.floor(w/200) * 200\r\n\t\t}\r\n\t\tres.push([finalWidth, finalHeight, k, origin])\r\n\t\t// add to buckets\r\n\t\tlet inner = finalWidth +\" x \"+ finalHeight\r\n\t\tif (oBuckets[k][inner] === undefined) {\r\n\t\t\toBuckets[k][inner] = [origin]\r\n\t\t} else {\r\n\t\t\toBuckets[k][inner].push(origin)\r\n\t\t}\r\n\t\t// ToDo: track all the sizes returned per baseName\r\n\t\tif (oData[k] == undefined) {oData[k] = {}}\r\n\t\tif (oData[k][basename] == undefined) {oData[k][basename] = []}\r\n\t\toData[k][basename].push(inner)\r\n\t}\r\n\treturn res\r\n}\r\n\r\n\r\nfunction calc(runtype) {\r\n\r\n\tlet ignoreWin7DoubleSide = false\r\n\tlet ignoreCompact = dom.optCompact.checked,\r\n\t\tignoreTitlebar = dom.optTitlebar.checked,\r\n\t\tignoreMenubar = dom.optMenubar.checked\r\n\r\n\t// build w/h variables\r\n\tlet aTemp = [], taskbarheights = [], taskbarwidths = [], menubarheights = [], titlebardata = [], toolbardata = []\r\n\tlet varCountNoDedupe = 0\r\n\tif (runtype == \"win7\") {\r\n\t\ttaskbarheights = [62, 30, 0]\r\n\t\ttaskbarwidths = [130, 62] // ignore 0, we already add this calc under height\r\n\t\tif (ignoreWin7DoubleSide) {\r\n\t\t\ttaskbarwidths = [62]\r\n\t\t}\r\n\t\tmenubarheights = [15] // same on all densities\r\n\t\ttitlebardata = [[4, 27]] // [w, h] same on all densities\r\n\t\ttoolbardata = [\r\n\t\t\t[12, 85, 28], // compact: w, h, toolbarheight\r\n\t\t\t[12, 98, 28], // normal\r\n\t\t\t[12, 107, 34], // touch\r\n\t\t]\r\n\t\tif (ignoreCompact) {\r\n\t\t\ttoolbardata = [\r\n\t\t\t\t[12, 98, 28], // normal\r\n\t\t\t\t[12, 107, 34], // touch\r\n\t\t\t]\r\n\t\t}\r\n\t} else if (runtype == \"win11\") {\r\n\t\ttaskbarheights = [48, 0]\r\n\t\ttaskbarwidths = []\r\n\t\tmenubarheights = [28] // same on all densities\r\n\t\ttitlebardata = [[4, 33]] // [w, h] same on all densities\r\n\t\ttoolbardata = [\r\n\t\t\t[12, 78, 28], // compact: w, h, toolbarheight\r\n\t\t\t[12, 91, 28], // normal\r\n\t\t\t[12, 100, 34], // touch\r\n\t\t]\r\n\t\tif (ignoreCompact) {\r\n\t\t\ttoolbardata = [\r\n\t\t\t\t[12, 91, 28], // normal\r\n\t\t\t\t[12, 100, 34], // touch\r\n\t\t\t]\r\n\t\t}\r\n\t}\r\n\ttry {\r\n\t\t// taskbar\r\n\t\ttaskbarheights.forEach(function(h) {\r\n\t\t\taTemp.push([0, -h])\r\n\t\t})\r\n\t\ttaskbarwidths.forEach(function(w) {\r\n\t\t\taTemp.push([-w, 0])\r\n\t\t})\r\n\t\t//console.log(aTemp)\r\n\r\n\t\t// menubar\r\n\t\tif (!ignoreMenubar) {\r\n\t\t\taTemp.forEach(function(item) {\r\n\t\t\t\tmenubarheights.forEach(function(h) {\r\n\t\t\t\t\taTemp.push([item[0], item[1] - h]) // only one: same per density in windows\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\t//console.log(aTemp)\r\n\t\t}\r\n\r\n\t\t// titlebar\r\n\t\tif (!ignoreTitlebar) {\r\n\t\t\taTemp.forEach(function(item) {\r\n\t\t\t\ttitlebardata.forEach(function(pair) {\r\n\t\t\t\t aTemp.push([item[0] - pair[0], item[1] - pair[1]]) // only one: same per density in windows\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\t//console.log(aTemp)\r\n\t\t}\r\n\r\n\t\t// chrome w/ + w/o toolbar at each density\r\n\t\tfor (let i = 0; i < aTemp.length; i++) {\r\n\t\t\tlet w0 = aTemp[i][0],\r\n\t\t\t\th0 = aTemp[i][1]\r\n\t\t\ttoolbardata.forEach(function(array) {\r\n\t\t\t\tlet wChrome = array[0], hChrome = array[1], tChrome = array[2]\r\n\t\t\t\t// no toolbar\r\n\t\t\t\taVariations.push([w0 - wChrome, h0 - hChrome])\r\n\t\t\t\t// toolbar\r\n\t\t\t\taVariations.push([w0 - wChrome, h0 - (hChrome + tChrome)])\r\n\t\t\t})\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t}\r\n\r\n\t// dedupe: e.g. 120 in win7 but only 96 are unique\r\n\tlet aUnique = []\r\n\taVariations.forEach(function(array) {\r\n\t\taUnique.push(array.join(\"x\"))\r\n\t})\r\n\taUnique.sort()\r\n\tvarCountNoDedupe = aUnique.length\r\n\taUnique = aUnique.filter(function(item, position) {return aUnique.indexOf(item) === position})\r\n\taVariations = []\r\n\taUnique.forEach(function(item) {\r\n\t\tlet w = item.split(\"x\")[0], h = item.split(\"x\")[1]\r\n\t\taVariations.push([w * 1, h * 1])\r\n\t})\r\n\t//console.log(aVariations.join(\"\\n\"))\r\n\r\n\t// win7 variations that include double height taskbar\r\n\t// these are unique so we don't need to check width\r\n\tlet aIgnoreHeights = [\r\n\t\t-147, -160, -162, -169, -175, -184, -188, -190, -203, -218, // @ -12 width\r\n\t\t-174, -187, -189, -196, -202, -211, -215, -217, -230, -245, // @ -16 width\r\n\t]\r\n\tlet aIgnoreWidths = [-142, -146] // win7 variations that include expanded taskbar on side\r\n\r\n\tlet nameCheck = []\r\n\tresUsed.forEach(function(item) {\r\n\t\ttry {\r\n\t\t\tlet baseWidth = item[0],\r\n\t\t\t\tbaseHeight = item[1]\r\n\t\t\tlet skipHeight = baseHeight < 800,\r\n\t\t\t\tskipWidth = baseWidth < 1000\r\n\r\n\t\t\tlet baseName = item[2] !== undefined ? item[2] : baseWidth +\" x \"+ baseHeight\r\n\t\t\tlet originName = item[2] !== undefined ? item[2] +\" \" : \"\"\r\n\t\t\tnameCheck.push(baseName)\r\n\t\t\toRaw[\"resBase\"][baseName] = []\r\n\t\t\tlet AB = []\r\n\t\t\taVariations.forEach(function(item) {\r\n\t\t\t\tlet go = true\r\n\t\t\t\tif (runtype == \"win7\") {\r\n\t\t\t\t\tif (skipHeight) {\r\n\t\t\t\t\t\tif (aIgnoreWidths.includes(item[0])) {go = false}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (go && skipHeight) {\r\n\t\t\t\t\t\tif (aIgnoreHeights.includes(item[1])) {go = false}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (go) {\r\n\t\t\t\t\tlet w = item[0], h = item[1]\r\n\t\t\t\t\tlet availableWidth = baseWidth + w,\r\n\t\t\t\t\t\tavailableHeight = baseHeight + h\r\n\t\t\t\t\tlet nameAvailable = availableWidth +\" x \"+ availableHeight\r\n\t\t\t\t\toRaw[\"resAvailable\"].push(nameAvailable)\r\n\t\t\t\t\tlet nameAvailableInner = (isDimensions ? originName + availableWidth +\" x \"+ availableHeight : baseName)\r\n\t\t\t\t\toRaw[\"innerAvailable\"].push(nameAvailableInner)\r\n\t\t\t\t\tAB = calcAB(availableWidth, availableHeight, nameAvailableInner, baseName)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t} catch(e) {\r\n\t\t\tconsole.log(e)\r\n\t\t}\r\n\t})\r\n\tnameCheck.sort()\r\n\t//console.log(nameCheck)\r\n\r\n\tlet intInner = oRaw[\"innerAvailable\"].length\r\n\tlet display = [s3 + runtype.toUpperCase() + sc +\" | \"\r\n\t\t+ s3 + resUsed.length + sc + \" base res | \"\r\n\t\t+ s3 + aVariations.length + sc + \" unique from \"+ s3 + varCountNoDedupe + sc + \" chrome/os variations | \"\r\n\t\t+ s3 + intInner + sc + \" tests per scenario<br>\"\r\n\t]\r\n\tdisplay.push(s3 +\"compact mode: \"+ sc + (ignoreCompact ? \"disabled\" : \"allowed\")\r\n\t\t+ s3 +\" | menubar: \"+ sc + (ignoreMenubar ? \"default off\" : \"allowed\")\r\n\t\t+ s3 +\" | titlebar: \"+ sc + (ignoreTitlebar ? \"default off\" : \"allowed\")\r\n\t)\r\n\r\n\tfor (const k of Object.keys(oTests)) {\r\n\t\tlet data = oBuckets[k]\r\n\t\tlet bucketcount = Object.keys(data).length\r\n\t\tlet testname = oTests[k][0] +\" x \" + (oTests[k][1]+\"\").padStart(4, \" \")\r\n\t\tlet detail = []\r\n\t\tfor (const j of Object.keys(oBuckets[k]).sort()) {\r\n\t\t\tlet array = oBuckets[k][j].sort()\r\n\t\t\tlet count = array.length\r\n\t\t\tif (!isDimensions) {\r\n\t\t\t\t// lets dedupe the basenames in each bucket with a count if > 1\r\n\t\t\t\tlet tmpObj = {}, newArray = []\r\n\t\t\t\tarray.forEach(function(base) {\r\n\t\t\t\t\tif (tmpObj[base] == undefined) {tmpObj[base] = 1} else {tmpObj[base] = tmpObj[base] + 1}\r\n\t\t\t\t})\r\n\t\t\t\tfor (const m of Object.keys(tmpObj)) {\r\n\t\t\t\t\tlet note = (tmpObj[m] > 1) ? s99 +\" [\"+ tmpObj[m] +\"]\"+ sc : \"\"\r\n\t\t\t\t\tnewArray.push(m + note)\r\n\t\t\t\t}\r\n\t\t\t\tarray = newArray\r\n\t\t\t}\r\n\t\t\tdetail.push(s3 + j + sc + \" [\" + count +\"] \"\r\n\t\t\t\t+ \"<ul>\" + array.join(\", \") +\"</ul>\")\r\n\t\t}\r\n\r\n\r\n\t\tdisplay.push(\"<br>\"+ s1 + k +\": \"+ sc + testname + s1 +\" [\" + (bucketcount+\"\").padStart(2, \" \") +\" buckets]\" + sc\r\n\t\t\t+\"<br><details><summary>details</summary>\" + detail.join(\"<br>\") + \"</details>\"\r\n\t\t)\r\n\t\tconsole.log(k +\": \"+ testname +\" [\" + bucketcount +\" buckets]\\n\", data)\r\n\t\t//if (k == \"B\" || k == \"E\") {\r\n\t\t//\tconsole.log(k +\": \"+ testname +\" [\" + bucketcount +\" buckets]\\n\")\r\n\t\t//}\r\n\t}\r\n\tdom.results.innerHTML = display.join(\"<br>\")\r\n\r\n\t// cleanup buckets per res base: oData\r\n\tfor (const k of Object.keys(oData)) {\r\n\t\tfor (const j of Object.keys(oData[k])) {\r\n\t\t\tlet tmpArray = oData[k][j]\r\n\t\t\ttmpArray = tmpArray.filter(function(item, position) {return tmpArray.indexOf(item) === position})\r\n\t\t\ttmpArray.sort()\r\n\t\t\toData[k][j] = tmpArray\r\n\t\t}\r\n\t}\r\n\t//console.log(oData)\r\n\t//console.log(oRaw)\r\n}\r\n\r\nfunction run(runtype) {\r\n\t// reset display\r\n\tdom.results.innerHTML = \"\"\r\n\t// reset vars\r\n\taVariations = []\r\n\toData = {}\r\n\tfor (const k of Object.keys(oTests)) {\r\n\t\toBuckets[k] = {}\r\n\t}\r\n\toRaw = {\r\n\t\t\"resBase\": {},\r\n\t\t\"resAvailable\": [], \r\n\t\t\"innerAvailable\": [], \r\n\t}\r\n\t// ToDo select res array to use\r\n\t//resUsed = resCommon\r\n\tresUsed = resNative\r\n\r\n\r\n\tsetTimeout(function() {\r\n\t\tcalc(runtype)\r\n\t}, 170) // delay so user can see it's working\r\n}\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfcompact.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>nf: compactdisplay</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat : compactdisplay\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy in compactDisplay and useGrouping</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisBigIntSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet\theader = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat\r\n\t// Note: ignore ChainNumberFormat (FF54) ? not sure how to use it TBH\r\n\tlet grouping = [\"true\", \"false\"]\r\n\tlet styles = [\"short\", \"long\"]\r\n\tlet items = [\r\n\t\t0/0, 0, 1/10,\r\n\t\t200, // hundred\r\n\t\t1000, // thousand\r\n\t\t2000000, // million\r\n\t\t-1100000000, // billion\r\n\t\t6600000000000, // trillion\r\n\t\t7000000000000000, // force a group\r\n\t\tInfinity,\r\n\t\t/*\r\n\t\t\t// there seems to be no change in plurality (million vs millions), but\r\n\t\t\t// using certain leading digits affects entropy: test from time to time\r\n\t\t100, 200, 300, 400, 500, 600, 700, 800, 900, // hundred(s)\r\n\t\t1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, // thousand(s)\r\n\t\t1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000,// million(s)\r\n\t\t// billion(s)\r\n\t\t1000000000, 2000000000, 3000000000, 4000000000, 5000000000,\r\n\t\t6000000000, 7000000000, 8000000000, 9000000000,\r\n\t\t// trillion(s)\r\n\t\t1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000,\r\n\t\t6000000000000, 7000000000000, 8000000000000, 9000000000000,\r\n\t\t// force a group\r\n\t\t1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000,\r\n\t\t6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000,\r\n\t\t//*/\r\n\t]\r\n\tif (isBigIntSupported) {\r\n\t\titems.push( BigInt(\"987354000000000000\") ) // add a bigint\r\n\t}\r\n\tlet tests = {\r\n\t\t\"false\": {\r\n\t\t\t\"long\": items,\r\n\t\t\t\"short\": items,\r\n\t\t},\r\n\t\t\"true\": {\r\n\t\t\t\"long\": items,\r\n\t\t\t\"short\": items,\r\n\t\t},\r\n\t}\r\n\r\n\tif (method == \"min\") {\r\n\t\ttests = { // FF78+\r\n\t\t\t\"true\": {\r\n\t\t\t\t\"long\": [0/0, 1000, 2e6, 6.6e12, 7e15], // force group on trillions\r\n\t\t\t\t\"short\": [\r\n\t\t\t\t\t-1100000000, // only needed for FF133 or lower\r\n\t\t\t\t\t-1000, // FF134+\r\n\t\t\t\t],\r\n\t\t\t}\r\n\t\t}\r\n\t\t// FF77 or lower: add 1/10\r\n\t\tif (isFF && !window.Document.prototype.hasOwnProperty(\"replaceChildren\")) {\r\n\t\t\ttests[\"true\"][\"long\"] = [0/0, 1/10, 1000, 2e6, 6.6e12, 7e15]\r\n\t\t}\r\n\t\tif (isBigIntSupported) {\r\n\t\t\ttests[\"true\"][\"long\"].push( BigInt(\"987354000000000000\") ) // we need the bigint\r\n\t\t}\r\n\t}\r\n\r\n\ttry {\r\n\t\t//test: each useGrouping x each compactDisplay\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oStyles = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(g){\r\n\t\t\t\toStyles[g] = {}\r\n\t\t\t\tObject.keys(tests[g]).sort().forEach(function(s){\r\n\t\t\t\t\toStyles[g][s] = []\r\n\t\t\t\t\t// set formatter once per code\r\n\t\t\t\t\tlet gValue = g == \"true\" ? true : false\r\n\t\t\t\t\tlet formatter = new Intl.NumberFormat(code, {notation: \"compact\", compactDisplay: s, useGrouping: gValue })\r\n\t\t\t\t\ttests[g][s].forEach(function(t){\r\n\t\t\t\t\t\toStyles[g][s].push(formatter.format(t))\t\t\t\t\t\t\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(tests).sort().forEach(function(g){\r\n\t\t\t\t\toTempData[hash][g] = {}\r\n\t\t\t\t\tObject.keys(tests[g]).sort().forEach(function(s){\r\n\t\t\t\t\t\toTempData[hash][g][s] = oStyles[g][s].join(\" | \")\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tlet strFL = \"\", strFS = \"\", strFalse = \"\"\r\n\t\t\tif (oData[k][\"false\"] !== undefined) {\r\n\t\t\t\tif (oData[k][\"false\"][\"long\"] !== undefined) {strFL = \"<li>\"+ s16 +\"L: \"+ sc + oData[k][\"false\"][\"long\"] +\"</li>\"}\r\n\t\t\t\tif (oData[k][\"false\"][\"short\"] !== undefined) {strFS = \"<li>\"+ s16 +\"S: \"+ sc + oData[k][\"false\"][\"short\"] +\"</li>\"}\r\n\t\t\t\tstrFalse = s14 +\"false\"+ sc + strFS + strFL\r\n\t\t\t}\r\n\t\t\tlet strTL = \"\", strTS = \"\", strTrue = \"\"\r\n\t\t\tif (oData[k][\"true\"] !== undefined) {\r\n\t\t\t\tif (oData[k][\"true\"][\"long\"] !== undefined) {strTL = \"<li>\"+ s16 +\"L: \"+ sc + oData[k][\"true\"][\"long\"] +\"</li>\"}\r\n\t\t\t\tif (oData[k][\"true\"][\"short\"] !== undefined) {strTS = \"<li>\"+ s16 +\"S: \"+ sc + oData[k][\"true\"][\"short\"] +\"</li>\"}\r\n\t\t\t\tstrTrue = s14 +\"true\"+ sc + strTS + strTL\r\n\t\t\t}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ strFalse + strTrue\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n \t\t\t\tif (resultsHash == \"c4044acb\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"c00615d3\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"bc3aadde\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"b8fa8ded\") { // FF151+: 179\r\n\t\t\t\t} else if (localesHash == \"3220ea11\") { // FF147-150: 178\r\n\t\t\t\t} else if (localesHash == \"6141ce3e\") { // FF140-146: 170\r\n\t\t\t\t} else if (isFF) {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\t//reset\r\n\tsetBtn(method)\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\t// delay so users see change and allow paint\r\n\tsetTimeout(function() {\r\n\t\trun_main(method)\r\n\t}, 1)\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n'ar-bh,arabic (bahrain)',\r\n'ar-dz,arabic (algeria)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'de-at,german (austria)',\r\n'de-ch,german (switzerland)',\r\n'en-at,english (austria)',\r\n'en-ch,english (switzerland)',\r\n'en-gb,english (united kingdom)',\r\n'en-fi,english (finland)',\r\n'en-in,english (india)',\r\n'es-cr,spanish (costa rica)',\r\n'es-mx,spanish (mexico)',\r\n'es-py,spanish (paraguay)',\r\n'es-us,spanish (united states)',\r\n'ff-adlm,fulah (adlam)',\r\n'fr-ca,french (canada)',\r\n'fr-ch,french (switzerland)',\r\n'fr-lu,french (luxembourg)',\r\n'it-ch,italian (switzerland)',\r\n'kk-cn,kazakh (china)',\r\n'kok-latn,konkani (latin)',\r\n'kxv-telu,kuvi (telugu)',\r\n'ms-bn,malay (brunei)',\r\n'pt-pt,portuguese (portugal)',\r\n'se-fi,northern sami (finland)',\r\n'sr-latn,serbian (latin)',\r\n'sw-cd,swahili (congo kinshasa)',\r\n'sw-ke,swahili (kenya)',\r\n'ta-my,tamil (malaysia)',\r\n'ur-in,urdu (india)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-hans,cantonese (simplified)',\r\n\t// blink\r\n'az-cyrl,azerbaijani (cyrillic)',\r\n'pa-arab,punjabi (arabic)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt('9999999999999999')\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\tsetBtn('all')\r\n\tsetTimeout(function() {\r\n\t\trun_main('all')\r\n\t}, 100)\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfcurrency.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>nf: currencydisplay</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 680px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\"><col>\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat: currencydisplay\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy in currencyDisplay\r\n\t\t\t\tand currencySign. The <code>TINY</code> test is a minimalist hardcoded test built for speed.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<span id=\"btiny\" class=\"btn4 btn\" onClick=\"run('tiny')\">[ TINY ]</span>\r\n\t\t\t\t<span id=\"allcurrency\"><input type=\"checkbox\" id=\"optCurrency\"> +cur</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span class=\"spaces\" id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar oCurrencies = {},\r\n\tlist = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tisBigIntSupported = false,\r\n\tlocalesHashAll = '' // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"currencies\") {\r\n\t\tconsole.log(name +\": \" + sDetail[name].length +\"\\n\"+ sDetail[name].join(\", \"))\r\n\t} else if (name == \"allcurrencies\") {\r\n\t\tconsole.log(\"all supported currencies\" +\": \" + sDetail[name].length +\"\\n\"+ sDetail[name].join(\", \"))\r\n\t} else if (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[name].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction set_currency_lists() {\r\n\r\n\tlet supported = []\r\n\ttry {\r\n\t\tsupported = Intl.supportedValuesOf(\"currency\")\r\n\t} catch(e) {}\r\n\tsupported.sort()\r\n\toCurrencies[\"supported\"] = supported\r\n\r\n\t// expanded: for additonal locale tests\r\n\tlet expanded = [\r\n\t\t'AED','ANG','AOA','ARS','AUD','AWG','AZN','BAM','BBD','BIF','BMD','BOB',\r\n\t\t'BRL','BSD','BWP','BYN','BZD','CAD','CDF','CNY','COP','CUP','DJF','DKK',\r\n\t\t'DZD','ERN','ETB','FJD','FKP','FRF','GBP','GEL','GTQ','GHS','GIP','GMD',\r\n\t\t'GNF','GYD','HNL','HTG','JMD','KES','KGS','KMF','KYD','KZT','LKR','LRD',\r\n\t\t'LSL','LUF','MDL','MGA','MKD','MOP','MRU','MUR','MWK','MYR','MZN','NAD',\r\n\t\t'NGN','NIO','NOK','NZD','PAB','PGK','PHP','PKR','PLN','RWF','SBD','SCR',\r\n\t\t'SDG','SGD','SHP','SLE','SOS','SRD','SSP','STN','SYP','SZL','TND','TOP',\r\n\t\t'TTD','TZS','XCD','UGX','USD','VUV','WST','XAF','XXX','ZAR','ZMW',\r\n\t\t// blink\r\n\t\t'PEN','PYG',\r\n\t]\r\n\tif (supported.length) {expanded = expanded.filter(x => supported.includes(x))}\r\n\texpanded = expanded.filter(function(item, position) {return expanded.indexOf(item) === position})\r\n\texpanded.sort()\r\n\toCurrencies[\"expanded\"] = expanded\r\n\r\n\t// ignore: don't seem to add any more buckets\r\n\tlet ignore = [\r\n\t\t'ADP','AFA','AFN','ALK','ALL','AMD','AOK','AON','AOR','ARA','ARL','ARM',\r\n\t\t'ARP','ATS','AZM','BAD','BAN','BDT','BEC','BEF','BEL','BGL','BGM','BGN',\r\n\t\t'BGO','BHD','BND','BOL','BOP','BOV','BRB','BRC','BRE','BRN','BRR','BRZ',\r\n\t\t'BTN','BUK','BYB','BYR','CHE','CHF','CHW','CLE','CLF','CLP','CNH','CNX',\r\n\t\t'COU','CRC','CSD','CSK','CUC','CVE','CYP','CZK','DDM','DEM','DOP','ECS',\r\n\t\t'ECV','EEK','EGP','ESA','ESB','ESP','EUR','FIM','GEK','GHC','GNS','GQE',\r\n\t\t'GRD','GWE','GWP','HKD','HRD','HRK','HUF','IDR','IEP','ILP','ILR','ILS',\r\n\t\t'INR','IQD','IRR','ISJ','ISK','ITL','JOD','JPY','KHR','KPW','KRH','KRO',\r\n\t\t'KRW','KWD','LAK','LBP','LTL','LTT','LUC','LUL','LVL','LVR','LYD','MAD',\r\n\t\t'MAF','MCF','MDC','MGF','MKN','MLF','MMK','MNT','MRO','MTL','MTP','MVP',\r\n\t\t'MVR','MXN','MXP','MXV','MZE','MZM','NIC','NLG','NPR','OMR','PEI','PES',\r\n\t\t'PLZ','PTE','QAR','RHD','ROL','RON','RSD','RUB','RUR','SAR','SDD','SDP',\r\n\t\t'SEK','SIT','SKK','SLL','SRG','STD','SUR','SVC','THB','TJR','TJS','TMM',\r\n\t\t'TMT','TPE','TRL','TRY','TWD','UAH','UAK','UGS','USN','USS','UYI','UYP',\r\n\t\t'UYU','UYW','UZS','VEB','VED','VEF','VES','VND','VNN','XAG','XAU','XBA',\r\n\t\t'XBB','XBC','XBD','XDR','XEU','XFO','XFU','XOF','XPD','XPF','XPT','XRE',\r\n\t\t'XSU','XTS','XUA','YDD','YER','YUD','YUM','YUN','YUR','ZAL','ZMK','ZRN',\r\n\t\t'ZRZ','ZWD','ZWL','ZWR',\r\n\t]\r\n\tlet all = expanded.concat(ignore) // so at least we have soomething if supported = empty\r\n\tall = all.concat(supported) // in case anything new turns up\r\n\tall = all.filter(function(item, position) {return all.indexOf(item) === position})\r\n\tif (supported.length) {all = all.filter(x => supported.includes(x))}\r\n\tall.sort()\r\n\toCurrencies[\"all\"] = all\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\tlet optCurrency = dom.optCurrency.checked\r\n\tlet intTests = 0\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\r\n\r\n\tlet tests = {}, tmptests = []\r\n\tlet items = {\r\n\t\t// these do not add anything\r\n\t\t\t// \"code\", \"narrowSymbol\", or the values 0/0, 1000, 1000000\r\n\t\t\"name\": [-1],\r\n\t\t\"symbol\": [1000],\r\n\t}\r\n\tlet curAll = {\"accounting\": [-1000], \"name\": [-1], \"symbol\": [1000]},\r\n\t\tcurA = {\"accounting\": [-1000]},\r\n\t\tcurAN = {\"accounting\": [-1000], \"name\": [-1]},\r\n\t\tcurAS = {\"accounting\": [-1000], \"symbol\": [1000]},\r\n\t\tcurN = {\"name\": [-1]},\r\n\t\tcurNS = {\"name\": [-1], \"symbol\": [1000]},\r\n\t\tcurS = {\"symbol\": [1000]}\r\n\r\n\tif (method == \"all\") {\r\n\t\tlet currencies = []\r\n\t\tcurrencies = oCurrencies[\"expanded\"]\r\n\t\tif (optCurrency) {currencies = oCurrencies[\"all\"]}\r\n\t\tcurrencies.forEach(function(c){\r\n\t\t\t/*\r\n\t\t\tcurAll : 426\r\n\t\t\t\t\t\t\t ---\r\n\t\t\tcurA   : 341\r\n\t\t\tcurAN  : 426 <-\r\n\t\t\tcurAS  : 344\r\n\t\t\tcurN   : 259\r\n\t\t\tcurNS  : 426 <-\r\n\t\t\tcurS   : 325\r\n\t\t\t*/\r\n\t\t\ttests[c] = curNS //curAll\r\n\t\t})\r\n\t\tintTests = currencies.length * Object.keys(curNS).length\r\n\r\n\t\t// style then currency isn't any faster\r\n\t\t// 2.3s = old / 2.2 new\r\n\t} else if (method == \"tiny\") {\r\n\t\ttests = {\r\n\t\t\t\"USD\": curAN, // 264\r\n\t\t\t\"XXX\": curN, // 275 (+11)\r\n\t\t\t'ETB': curN, // 281 (+6)\r\n\t\t\t\"GBP\": curS, // 286 (+5)\r\n\t\t\t\"KES\": curS, // 291 (+5)\r\n\t\t}\r\n\t} else if (method == \"min\") {\r\n\t\t// looking for 426\r\n\t\ttests = {\r\n\t\t\t// from tiny\r\n\t\t\t\"USD\": curAN, // 264\r\n\t\t\t\"XXX\": curNS, // 275 (+11)\t// added S\r\n\t\t\t'ETB': curNS, // 281 (+6)\t\t// added S\r\n\t\t\t\"GBP\": curNS, // 286 (+5)\t\t// added N\r\n\t\t\t\"KES\": curS, // 291 (+5)\r\n\t\t\t// everything else now with NS = 426 max\r\n\t\t\t// 294 so far\r\n\t\t\t'GHS': curNS, 'MOP': curNS, // +4s = 302 \r\n\t\t\t// +3s\r\n\t\t\t'ANG': curNS, 'BYN': curNS, 'DJF': curNS, 'ERN': curNS,\r\n\t\t\t'MRU': curNS, 'GNF': curNS, 'LUF': curNS, 'PKR': curNS,\r\n\t\t\t'RWF': curNS, // 329\r\n\t\t\t// +2s\r\n\t\t\t'AUD': curNS, 'BIF': curNS, 'BWP': curNS, 'BZD': curNS,\r\n\t\t\t'DKK': curNS, 'GMD': curNS, 'KMF': curNS, 'LRD': curNS,\r\n\t\t\t'MDL': curNS, 'MGA': curNS, 'MUR': curNS, 'MZN': curNS,\r\n\t\t\t'NAD': curNS,\t'NGN': curNS, 'SCR': curNS, 'SGD': curNS,\r\n\t\t\t'SLE': curNS, 'SZL': curNS, 'TZS': curNS, 'UGX': curNS,\r\n\t\t\t'VUV': curNS, 'ZAR': curNS, // 373\r\n\t\t\t// +1s name\r\n\t\t\t'CAD': curN, 'XAF': curN, // 375\r\n\t\t\t// +1s symbol\r\n\t\t\t'AOA': curS, 'ARS': curS, 'AWG': curS, 'AZN': curS, 'BAM': curS,\r\n\t\t\t'BBD': curS, 'BMD': curS, 'BRL': curS, 'BSD': curS, 'CDF': curS,\r\n\t\t\t'CNY': curS, 'DZD': curS, 'FJD': curS, 'FKP': curS, 'FRF': curS,\r\n\t\t\t'GEL': curS, 'GTQ': curS, 'GIP': curS, 'GYD': curS, 'HNL': curS,\r\n\t\t\t'HTG': curS, 'JMD': curS, 'KGS': curS, 'KYD': curS, 'KZT': curS,\r\n\t\t\t'LKR': curS, 'LSL': curS, 'MKD': curS, 'MWK': curS, 'MYR': curS,\r\n\t\t\t'NIO': curS, 'NOK': curS, 'NZD': curS, 'PAB': curS, 'PGK': curS,\r\n\t\t\t'PHP': curS, 'PLN': curS, 'SBD': curS, 'SDG': curS, 'SHP': curS,\r\n\t\t\t'SOS': curS, 'SRD': curS, 'SSP': curS, 'STN': curS, 'SYP': curS,\r\n\t\t\t'TND': curS, 'TOP': curS, 'TTD': curS, 'XCD': curS, 'WST': curS,\r\n\t\t\t'ZMW': curS, // +51 = 426\r\n\r\n\t\t\t// blink\r\n\t\t\t'PEN': curS, // just for blink\r\n\t\t}\r\n\t}\r\n\tif ('all' !== method) {\r\n\t\tfor (const k of Object.keys(tests)) {intTests += Object.keys(tests[k]).length}\r\n\t}\r\n\r\n\t// remove unsupported if min: the others have aleady been checked\r\n\tif (method == 'min') {\r\n\t\tlet supported = oCurrencies[\"supported\"]\r\n\t\tif (supported.length) {\r\n\t\t\tfor (const k of Object.keys(tests)) {\r\n\t\t\t\tif (!supported.includes(k)) {delete tests[k]}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tlet oStyles = {}\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\toStyles = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(c){ // for each currency: sort for consistency\r\n\t\t\t\toStyles[c] = {}\r\n\t\t\t\tObject.keys(tests[c]).forEach(function(cd){ // for each currencyDisplay\r\n\t\t\t\t\toStyles[c][cd] = []\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tlet option = {style: \"currency\", currency: c, currencyDisplay: cd}\r\n\t\t\t\t\t\tif (cd == \"accounting\") {option = {style: \"currency\", currency: c, currencySign: cd}} // accounting is currencySign\r\n\t\t\t\t\t\tlet formatter = Intl.NumberFormat(code, option)\r\n\t\t\t\t\t\ttests[c][cd].forEach(function(n){ // for each number\r\n\t\t\t\t\t\t\toStyles[c][cd].push(formatter.format(n))\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} catch (e) {} // ignore invalid\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oStyles).forEach(function(c){ // each currency\r\n\t\t\t\t\toTempData[hash][c] = {}\r\n\t\t\t\t\tObject.keys(oStyles[c]).forEach(function(cd){ // for each currencyDisplay\r\n\t\t\t\t\t\tif (oStyles[c][cd].length) {\r\n\t\t\t\t\t\t\toTempData[hash][c][cd] = oStyles[c][cd].join(\" | \")\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const c of Object.keys(oTempData[h]).sort()) { // for each currency\r\n\t\t\t\tif (c == \"locales\") {\r\n\t\t\t\t\toData[h][c] = oTempData[h][c].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][c]).length) {\r\n\t\t\t\t\t\toData[h][c] = oTempData[h][c]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const h of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[h][\"locales\"])\r\n\t\t\tlet localeCount = oData[h][\"locales\"].split(\",\").length\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const c of Object.keys(oData[h])) { // for each currency\r\n\t\t\t\tif (c !== \"locales\") {\r\n\t\t\t\t\tstr += \"<li>\"+ s12 + c + \":\"+ sc\r\n\t\t\t\t\tObject.keys(oData[h][c]).forEach(function(cd){ // for each currencyDisplay\r\n\t\t\t\t\t\tlet option = ('all' == method) ? cd.slice(0,1).toUpperCase() : cd\r\n\t\t\t\t\t\tstr += ' '+ s16 + option +\": \"+ sc + oData[h][c][cd]\r\n\t\t\t\t\t})\r\n\t\t\t\t\tstr += \"</li>\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// wrap into details for long lists\r\n\t\t\tif (Object.keys(tests).length > 15) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + h + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[h][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"currencies\"] = Object.keys(tests).sort()\r\n\t\tlet curStr = Object.keys(tests).length, curBtn = \"\"\r\n\t\tif (curStr < 6) {\r\n\t\t\tlet aCur = []\r\n\t\t\tfor (const k of Object.keys(tests)) {aCur.push(k)}\r\n\t\t\tcurBtn = aCur.join(\", \") + s4 +\" [\"+ curStr +\"]\"+ sc\r\n\t\t} else {\r\n\t\t\tcurBtn = \"<span class='btn4 btnc' onClick='log_console(`currencies`)'>[\" + curStr + \"]</span>\"\r\n\t\t\tsDetail[\"results\"] = oData\r\n\t\t}\r\n\t\tif (oCurrencies[\"supported\"].length) {\r\n\t\t\tif (!optCurrency || optCurrency && method == \"min\") {\r\n\t\t\t\tsDetail[\"allcurrencies\"] = oCurrencies[\"all\"]\r\n\t\t\t\tcurBtn += \" from \"+ \"<span class='btn4 btnc' onClick='log_console(`allcurrencies`)'>[\" + oCurrencies[\"supported\"].length + \"]</span>\"\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\t//console.log(localesHash)\r\n\t\t//console.log(localeGroups)\r\n\t\t//console.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\" && !optCurrency) {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"975e37a5\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"8927863e\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"105215e4\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"f3b23b8f\") { // FF147+: 430\r\n\t\t\t\t} else if (localesHash == \"e4c818f6\") { // FF140-146: 426\r\n\t\t\t\t} else if (isFF) {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"] \" + sc + spacer\r\n\t\t\t+ s12 +\"currencies: \"+ sc + curBtn +' ['+  intTests + ' tests]'+spacer\r\n\t\t\t+ s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = ''\r\n\t\tlet status = ''\r\n\t\tif ('tiny' !== method) {\r\n\t\t\tstatus = 'calculating ...'+ ('all' == method ? ' takes a few seconds': '')\r\n\t\t}\r\n\t\tdom.results = status\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\tdom.optCurrency.checked = false\r\n\tif (!isFile) {\r\n\t\tdom.allcurrency.style.display = \"none\"\r\n\t}\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat\r\n\t// note: numberformat supported since at least FF29\r\n\r\n\t// FF78+ : currencyDisplay ? but it works all the way back to FF52! ?!^$%?\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF78+\r\n\t\tlet test = new Intl.NumberFormat(\"en\", {style: \"currency\", currency: \"USD\", currencyDisplay: \"symbol\"}).format(5)\r\n\t\ttest += \", \"+ new Intl.NumberFormat(\"en\", {style: \"currency\", currency: \"USD\", currencyDisplay: \"name\"}).format(5)\r\n\t\t// FF52: USD5.00, $5.00, 5.00 US dollars\r\n\t\t// FF111: USD 5.00, $5.00, 5.00 US dollars\r\n\t\t// console.log(test)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt(\"9999999999999999\")\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"af-na,afrikaans (namibia)\",\r\n\t\t\"ar-ae,arabic (united arabic emirates)\",\r\n\t\t\"ar-bh,arabic (bahrain)\",\r\n\t\t\"ar-dj,arabic (djibouti)\",\r\n\t\t\"ar-dz,arabic (algeria)\",\r\n\t\t\"ar-er,arabic (eritrea)\",\r\n\t\t\"ar-km,arabic (cosmoros)\",\r\n\t\t\"ar-lb,arabic (lebanon)\",\r\n\t\t\"ar-so,arabic (somalia)\",\r\n\t\t\"ar-ss,arabic (south sudan)\",\r\n\t\t\"az-cyrl,azerbaijani (cyrillic)\",\r\n\t\t\"bn-in,bengali (india)\",\r\n\t\t\"bo-in,tibetan (india)\",\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"ca-fr,catalan (france)\",\r\n\t\t\"de-at,german (austria)\",\r\n\t\t\"de-ch,german (switzerland)\",\r\n\t\t\"de-li,german (liechtenstein)\",\r\n\t\t\"de-lu,german (luxembourg)\",\r\n\t\t\"en-150,english (europe)\",\r\n\t\t\"en-ag,english (antigua & barbuda)\",\r\n\t\t\"en-at,english (austria)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-bb,english (barbados)\",\r\n\t\t\"en-bi,english (burundi)\",\r\n\t\t\"en-bm,english (bermuda)\",\r\n\t\t\"en-bs,english (bahamas)\",\r\n\t\t\"en-bw,english (botswana)\",\r\n\t\t\"en-bz,english (belize)\",\r\n\t\t\"en-ca,english (canada)\",\r\n\t\t\"en-cc,english (cocos islands)\",\r\n\t\t\"en-ch,english (switzerland)\",\r\n\t\t\"en-dk,english (denmark)\",\r\n\t\t\"en-er,english (eritrea)\",\r\n\t\t\"en-fi,english (finland)\",\r\n\t\t\"en-fj,english (fiji)\",\r\n\t\t\"en-fk,english (falkland islands)\",\r\n\t\t\"en-gb,english (united kingdom)\",\r\n\t\t\"en-gg,english (guernsey)\",\r\n\t\t\"en-gh,english (ghana)\",\r\n\t\t\"en-gi,english (gibraltar)\",\r\n\t\t\"en-gm,english (gambia)\",\r\n\t\t\"en-gy,english (guyana)\",\r\n\t\t\"en-in,english (india)\",\r\n\t\t\"en-jm,english (jamaica)\",\r\n\t\t\"en-ke,english (kenya)\",\r\n\t\t\"en-ky,english (cayman islands)\",\r\n\t\t\"en-lr,english (liberia)\",\r\n\t\t\"en-ls,english (lesotho)\",\r\n\t\t\"en-mg,english (madagascar)\",\r\n\t\t\"en-mo,english (macau)\",\r\n\t\t\"en-mt,english (malta)\",\r\n\t\t\"en-mu,english (mauritius)\",\r\n\t\t\"en-mw,english (malawai)\",\r\n\t\t\"en-my,english (malaysia)\",\r\n\t\t\"en-na,english (namibia)\",\r\n\t\t\"en-ng,english (nigeria)\",\r\n\t\t\"en-nz,english (new zealand)\",\r\n\t\t\"en-pg,english (papua new guinea)\",\r\n\t\t\"en-pk,english (pakistan)\",\r\n\t\t\"en-rw,english (rwanda)\",\r\n\t\t\"en-sb,english (solomon islands)\",\r\n\t\t\"en-sc,english (seychelles)\",\r\n\t\t\"en-se,english (sweden)\",\r\n\t\t\"en-sg,english (singapore)\",\r\n\t\t\"en-sh,english (saint helena)\",\r\n\t\t\"en-sl,english (sierra leone)\",\r\n\t\t\"en-ss,english (south sudan)\",\r\n\t\t\"en-sx,english (sint maarten)\",\r\n\t\t\"en-sz,english (swaziland)\",\r\n\t\t\"en-to,english (tonga)\",\r\n\t\t\"en-tt,english (trinidad & tobago)\",\r\n\t\t\"en-tz,english (tanzania)\",\r\n\t\t\"en-ug,english (uganda)\",\r\n\t\t\"en-vu,english (vanuatu)\",\r\n\t\t\"en-ws,english (samoa)\",\r\n\t\t\"en-za,english (south africa)\",\r\n\t\t\"en-zm,english (zambia)\",\r\n\t\t\"es-419,spanish (latin america and the caribbean)\",\r\n\t\t\"es-ar,spanish (argentina)\",\r\n\t\t\"es-bo,spanish (bolivia)\",\r\n\t\t\"es-br,spanish (brazil)\",\r\n\t\t\"es-bz,spanish (belize)\",\r\n\t\t\"es-cl,spanish (chile)\",\r\n\t\t\"es-co,spanish (colombia)\",\r\n\t\t\"es-cr,spanish (costa rica)\",\r\n\t\t\"es-cu,spanish (cuba)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-ec,spanish (ecuador)\",\r\n\t\t\"es-gq,spanish (equatorial guinea)\",\r\n\t\t\"es-gt,spanish (guatemala)\",\r\n\t\t\"es-hn,spanish (honduras)\",\r\n\t\t\"es-mx,spanish (mexico)\",\r\n\t\t\"es-ni,spanish (nicaragua)\",\r\n\t\t\"es-pa,spanish (panama)\",\r\n\t\t\"es-pe,spanish (peru)\",\r\n\t\t\"es-ph,spanish (philippines)\",\r\n\t\t\"es-py,spanish (paraguay)\",\r\n\t\t\"es-sv,spanish (el salvador)\",\r\n\t\t\"es-us,spanish (united states)\",\r\n\t\t\"es-uy,spanish (uruguay)\",\r\n\t\t\"es-ve,spanish (venezuela)\",\r\n\t\t\"fa-af,persian (afghanistan)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"ff-adlm-bf,fulah (adlam burkina faso)\",\r\n\t\t\"ff-adlm-gh,fulah (adlam ghana)\",\r\n\t\t\"ff-adlm-gm,fulah (adlam gambia)\",\r\n\t\t\"ff-adlm-lr,fulah (adlamd liberia)\",\r\n\t\t\"ff-adlm-mr,fulah (adlam mauritania)\",\r\n\t\t\"ff-adlm-ng,fulah (adlam nigeria)\",\r\n\t\t\"ff-adlm-sl,fulah (adlam sierra leone)\",\r\n\t\t\"ff-gn,fulah (guinea)\",\r\n\t\t\"ff-mr,fulah (mauritania)\",\r\n\t\t\"fo-dk,faroese (denmark)\",\r\n\t\t\"fr-bi,french (burundi)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t\"fr-cd,french (congo kinshasa)\",\r\n\t\t\"fr-ch,french (switzerland)\",\r\n\t\t\"fr-dj,french (djibouti)\",\r\n\t\t\"fr-dz,french (algeria)\",\r\n\t\t\"fr-gn,french (guinea)\",\r\n\t\t\"fr-ht,french (haiti)\",\r\n\t\t\"fr-km,french (comoros)\",\r\n\t\t\"fr-lu,french (luxembourg)\",\r\n\t\t\"fr-ma,french (morocco)\",\r\n\t\t\"fr-mg,french (madagascar)\",\r\n\t\t\"fr-mr,french (mauritania)\",\r\n\t\t\"fr-mu,french (mauritius)\",\r\n\t\t\"fr-rw,french (rwanda)\",\r\n\t\t\"fr-sc,french (seychelles)\",\r\n\t\t\"fr-sy,french (syria)\",\r\n\t\t\"fr-tn,french (tunisia)\",\r\n\t\t\"fr-vu,french (vanuatu)\",\r\n\t\t\"ha-gh,hausa (ghana)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t\"hr-ba,croatian (bosnia & herzegovina)\",\r\n\t\t\"it-ch,italian (switzerland)\",\r\n\t\t\"kea-cv,kabuverdianu (cape verde)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"ks-deva,kashmiri (devanagari)\",\r\n\t\t\"kxv-telu,kuvi (telugu)\",\r\n\t\t\"ln-ao,lingala (angola)\",\r\n\t\t\"mas-tz,masia (tanzania)\",\r\n\t\t\"ms-bn,malay (brunei)\",\r\n\t\t\"ms-id,malay (indonesia)\",\r\n\t\t\"ms-sg,malay (singapore)\",\r\n\t\t\"nl-aw,dutch (aruba)\",\r\n\t\t\"nl-bq,dutch (caribbean netherlands)\",\r\n\t\t\"nl-cw,dutch (curaçao)\",\r\n\t\t\"nl-sr,dutch (suriname)\",\r\n\t\t\"om-ke,oromo (kenya)\",\r\n\t\t\"os-ru,ossetian (russia)\",\r\n\t\t\"pa-pk,punjabi (pakistan)\",\r\n\t\t\"ps-pk,pashto (pakistan)\",\r\n\t\t\"pt-ao,portuguese (angola)\",\r\n\t\t\"pt-ch,portuguese (switzerland)\",\r\n\t\t\"pt-cv,portuguese (cape verde)\",\r\n\t\t\"pt-lu,portuguese (luxembourg)\",\r\n\t\t\"pt-mo,portuguese (macau)\",\r\n\t\t\"pt-mz,portuguese (mazambique)\",\r\n\t\t\"pt-pt,portuguese (portugal)\",\r\n\t\t\"pt-st,portuguese (são tomé & príncipe)\",\r\n\t\t\"qu-bo,quechua (bolivia)\",\r\n\t\t\"qu-ec,quechua (ecuador)\",\r\n\t\t\"ro-md,romanian (moldova)\",\r\n\t\t\"ru-by,russian (belarus)\",\r\n\t\t\"ru-kg,russian (kyrgyzstan)\",\r\n\t\t\"ru-kz,russian (kazakhstan)\",\r\n\t\t\"ru-md,russian (moldova)\",\r\n\t\t\"ru-ua,russian (ukraine)\",\r\n\t\t\"sd-deva,sindhi (devanagari)\",\r\n\t\t\"se-se,northern sami\",\r\n\t\t\"shi-latn,tachelhit (latin)\",\r\n\t\t\"so-dj,somali (djibouti)\",\r\n\t\t\"so-et,somali (ethiopia)\",\r\n\t\t\"so-ke,somali (kenya)\",\r\n\t\t\"sq-mk,albanian (macedonia)\",\r\n\t\t\"sr-cyrl-ba,serbian (cyrillic bosnia & herzegovina)\",\r\n\t\t\"sr-latn,serbian (latin)\",\r\n\t\t\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\r\n\t\t'st-ls,southern sotho',\r\n\t\t\"sw-cd,swahili (congo kinshasa)\",\r\n\t\t\"sw-ke,swahili (kenya)\",\r\n\t\t\"sw-ug,swahili (uganda)\",\r\n\t\t\"ta-lk,tamil (sri lanka)\",\r\n\t\t\"ta-my,tamil (malaysia)\",\r\n\t\t\"ta-sg,tamil (singapore)\",\r\n\t\t\"teo-ke,teso (kenya)\",\r\n\t\t\"ti-er,tigrinya (eritrea)\",\r\n\t\t'tn-bw,tswana (botswana)',\r\n\t\t\"tr-tr,turkish (turkey)\",\r\n\t\t\"ur-in,urdu (india)\",\r\n\t\t\"uz-af,uzbek (afghanistan)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"vai-latn,vai (latin)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t\"zh-hans-hk,chinese (simplified hong kong)\",\r\n\t\t\"zh-hans-mo,chinese (simplified macau)\",\r\n\t\t\"zh-hant-mo,chinese (traditional macau)\",\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en','de','sv']\r\n\r\n\tlegend()\r\n\tset_currency_lists()\r\n\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tdom.results = \"calculating ... takes a few seconds\"\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfformattoparts.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>nf: number formattoparts</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 580px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat: formattoparts\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">testing values, not entropy</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<hr><br>\r\n\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toTestData = {},\r\n\tisSupported = false,\r\n\tisBigIntSupported = false\r\n\r\nlet aNumsys = [\r\n'undefined', // 58\r\n// the rest vary 51-53 except for arab/arabext and 15 and 17\r\n'adlm','ahom','arab','arabext','bali','beng','bhks','brah','cakm','cham','deva','diak','fullwide','gara','gong',\r\n'gonm','gujr','gukh','guru','hanidec','hmng','hmnp','java','kali','kawi','khmr','knda','krai','lana','lanatham',\r\n'laoo','latn','lepc','limb','mathbold','mathdbl','mathmono','mathsanb','mathsans','mlym','modi','mong','mroo',\r\n'mtei','mymr','mymrepka','mymrpao','mymrshan','mymrtlng','nagm','newa','nkoo','olck','onao','orya','osma','outlined',\r\n'rohg','saur','segment','shrd','sind','sinh','sora','sund','sunu','takr','talu','tamldec','telu','thai','tibt',\r\n'tirh','tnsa','vaii','wara','wcho',\r\n]\r\naNumsys.sort()\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction testitems() {\r\n\t//loop each numsys on it's own\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\toTestData = {}\r\n\tsetTimeout(function() {\r\n\t\ttry {\r\n\t\t\taNumsys.forEach(function(item){\r\n\t\t\t\t// allow testing arrays of scripts\r\n\t\t\t\tlet testarray = 'object' == typeof item ? item : [item]\r\n\t\t\t\trun_main(item, true)\r\n\t\t\t})\r\n\t\t} catch(e) {\r\n\t\t\tconsole.log(e)\r\n\t\t}\r\n\t}, 5)\r\n}\r\n\r\nfunction run_main(numsys = undefined, isLoop = false) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\tif (!isLoop) {\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t}\r\n\r\n\tif (numsys == 'undefined') {numsys = undefined}\r\n\tlet nBig\r\n\tif (isBigIntSupported) {\r\n\t\tnBig = BigInt(\"987354000000000000\")\r\n\t}\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options\r\n\t// ^ options\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/formatToParts#return_value\r\n\t// ^ return values\r\n\t\t//\"exponentMinusSign\" returns minusSign as type\r\n\tlet tests = {\r\n\t\t// these need no options\r\n\t\t\"decimal\": [1.2],\r\n\t\t\"group\": [1000, 99999],\r\n\t\t\"infinity\": [Infinity],\r\n\t\t\"minusSign\": [-5],\r\n\t\t\"nan\": [\"a\"],\r\n\r\n\t\t// these items require options which means another constructor\r\n\t\t//\"percentSign\": [.2], // {style: \"percent\"}\r\n\t\t//\"exponentSeparator\": [1], //{style: 'scientitic'}\r\n\r\n\t\t/* already captured elsewhere\r\n\t\t\t- units + currency: both are large and lots of constructors\r\n\t\t\t- literals - extra constructors\r\n\t\t\t- percentSign + exponentSeparator complicates TZP tests due to changein constuctor\r\n\t\t*/\r\n\t}\r\n\r\n\tfunction get_value(type, parts) {\r\n\t\ttry {\r\n\t\t\tlet str = \"none\"\r\n\t\t\tfor (let i = 0 ; i < parts.length; i++) {\r\n\t\t\t\tif (parts[i][\"type\"] === type) {\r\n\t\t\t\t\tstr = parts[i][\"value\"]\r\n\t\t\t\t\tstr += str.length == 1 ? \" (\"+ str.charCodeAt(0) +\")\" : \"\"\r\n\t\t\t\t\treturn str // no need to keep checking\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn str\r\n\t\t} catch(e) {\r\n\t\t\treturn \"error\"\r\n\t\t}\r\n\t}\r\n\t\r\n\ttry {\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet formatter = new Intl.NumberFormat(code, {numberingSystem: numsys})\r\n\t\t\tlet oStyles = {}\r\n\t\t\tObject.keys(tests).forEach(function(t){ // each type - DO NOT SORT\r\n\t\t\t\t// we need to ensure we have the right options\r\n\t\t\t\t// we can do this by grouping by style, then by the test name\r\n\t\t\t\t// but for now we don't know how much we want to add, so for now\r\n\t\t\t\t// lets just add items after default and change the formatter as required\r\n\t\t\t\tif ('percentSign' == t) {\r\n\t\t\t\t\tformatter = new Intl.NumberFormat(code, {style: 'percent', numberingSystem: numsys})\r\n\t\t\t\t} else if ('exponentSeparator' == t) {\r\n\t\t\t\t\tformatter = new Intl.NumberFormat(code, {notation: 'scientific', numberingSystem: numsys})\r\n\t\t\t\t}\r\n\t\t\t\toStyles[t] = []\r\n\t\t\t\ttests[t].forEach(function(n){ // each number\r\n\t\t\t\t\tlet value = get_value(t, formatter.formatToParts(n))\r\n\t\t\t\t\tif (list.length == 1) {\r\n\t\t\t\t\t\tconsole.log(t,formatter.formatToParts(n))\r\n\t\t\t\t\t}\r\n\t\t\t\t\toStyles[t].push(value)\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(tests).forEach(function(s){\r\n\t\t\t\t\toTempData[hash][s] = oStyles[s].join(\" | \")\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = \"aww snap, something went wrong\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\t\t\tif (!isLoop) {\r\n\t\t\t\tlet str = \"\"\r\n\t\t\t\tfor (const p of Object.keys(oData[k])) {\r\n\t\t\t\t\tif (p !== \"locales\") {\r\n\t\t\t\t\t\tstr += \"<li>\"+ s16 + p +\": \"+ sc + oData[k][p] +\"</li>\"\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (isLoop) {\r\n\t\t\tlet testCount = localeGroups.length\r\n\t\t\t// record it\r\n\t\t\tif (undefined == oTestData[testCount]) {oTestData[testCount] = []}\r\n\t\t\toTestData[testCount].push(numsys)\r\n\t\t\tdom.results.innerHTML = dom.results.innerHTML + '<br>'\r\n\t\t\t\t+ '\\''+ numsys +' ' + testCount\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// display\r\n\t\tlet display = s4 + localeGroups.length + sc +\" from \"+ s4 + aLocales.length + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tbuildnav()\r\n\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF58+\r\n\t\tlet test = new Intl.NumberFormat(\"en\").formatToParts(1.1)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt(\"9999999999999999\")\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\r\n\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"ar-dj,arabic (djibouti)\",\r\n\t\t\"ar-dz,arabic (algeria)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"it-ch,italian (switzerland)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"ru-ua,russian (ukraine)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"yue-cn,cantonese (china)\",\r\n\t\t\t\t// blink\r\n\t\t'pa-pk,punjabi (pakistan)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = ['en']\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main()\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfnotation.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>nf: notation</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 780px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat: notation\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy:\r\n\t\t\texcluding compact, currency and unit as they are covered elsewhere</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tisBigIntSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\tlet tests = {}\r\n\tlet nBig\r\n\tif (isBigIntSupported) {\r\n\t\tnBig = BigInt(\"987354000000000000\")\r\n\t}\r\n\tif (method == \"min\") {\r\n\t\ttests = {\r\n\t\t\t\"scientific\": {\r\n\t\t\t\t\"decimal\": [987654], // swap with bigint\r\n\t\t\t},\r\n\t\t\t\"standard\": {\r\n\t\t\t\t\"decimal\": [0/0, -1000, 987654],\r\n\t\t\t\t\"percent\": [1000],\r\n\t\t\t},\r\n\t\t}\r\n\t\tif (isBigIntSupported) {\r\n\t\t\ttests.scientific.decimal = [nBig] // replace: use at least 1 bigint (either works for entropy)\r\n\t\t}\r\n\t} else {\r\n\t\t// standard (default), scientific, engineering\r\n\t\t// style: decimal (default), currency, percent, unit\r\n\t\ttests = {\r\n\t\t\t\"engineering\": {\r\n\t\t\t\t\"decimal\": [-1, 0, 0/0, 1/10, 1/1000, 987654],\r\n\t\t\t},\r\n\t\t\t\"scientific\": {\r\n\t\t\t\t\"decimal\": [-1, 0, 0/0, 1/10, 1/1000, 987654],\r\n\t\t\t},\r\n\t\t\t\"standard\": {\r\n\t\t\t\t\"decimal\": [-1, 0, 0/0, 1/10, -1000, 987654],\r\n\t\t\t\t\"percent\": [1/100, 1000, 987654321, Infinity],\r\n\t\t\t},\r\n\t\t}\r\n\t\tif (isBigIntSupported) {\r\n\t\t\ttests.engineering.decimal.push(nBig)\r\n\t\t\ttests.scientific.decimal.push(nBig)\r\n\t\t\ttests.standard.decimal.push(nBig)\r\n\t\t}\r\n\t}\r\n\r\n\tlet oStyles = {}\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\toStyles = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(not){ // for each notation\r\n\t\t\t\toStyles[not] = {}\r\n\t\t\t\tObject.keys(tests[not]).forEach(function(s){ // for each style\r\n\t\t\t\t\toStyles[not][s] = []\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tlet formatter = Intl.NumberFormat(code, {notation: not, style: s})\r\n\t\t\t\t\t\ttests[not][s].forEach(function(n){ // for each number\r\n\t\t\t\t\t\t\toStyles[not][s].push(formatter.format(n))\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} catch (e) {console.log(e.name, e.message)} // ignore invalid\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oStyles).forEach(function(not){ // each notation\r\n\t\t\t\t\toTempData[hash][not] = {}\r\n\t\t\t\t\tObject.keys(oStyles[not]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\tif (oStyles[not][s].length) {\r\n\t\t\t\t\t\t\toTempData[hash][not][s] = oStyles[not][s].join(\" | \")\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const not of Object.keys(oTempData[h]).sort()) { // for each notation\r\n\t\t\t\tif (not == \"locales\") {\r\n\t\t\t\t\toData[h][not] = oTempData[h][not].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][not]).length) {\r\n\t\t\t\t\t\toData[h][not] = oTempData[h][not]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const not of Object.keys(oData[k])) { // for each notation\r\n\t\t\t\tif (not !== \"locales\") {\r\n\t\t\t\t\tstr += \"<li>\"+ s12 + not + sc +\"</li>\"\r\n\t\t\t\t\tObject.keys(oData[k][not]).forEach(function(s){ // for each style\r\n\t\t\t\t\t\tstr += s16 + s +\": \"+ sc + oData[k][not][s] +\"</br>\"\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// wrap into details for long lists\r\n\t\t\tif (Object.keys(tests).length > 15) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"a5e43330\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"c3ccc494\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"cf242641\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"247165ee\") { // FF151+: 83\r\n\t\t\t\t} else if (localesHash == \"37083b3a\") { // FF147-150: 83\r\n\t\t\t\t} else if (localesHash == \"6fe4f315\") { // FF140-146: 80\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\r\n\r\n\t// standard (default), scientific, engineering\r\n\t// style: decimal (default), currency, percent, unit\r\n\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF78+\r\n\t\tlet test = new Intl.NumberFormat(\"en-US\", {notation: \"scientific\"}).format(987654321)\r\n\t\tif (test.length == 7) {\r\n\t\t\tisSupported = true\r\n\t\t} else {\r\n\t\t\tdom.results.innerHTML = s4 + \"notation:\" + sc +\" not supported\"\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt(\"9999999999999999\")\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"ar-dj,arabic (djibouti)\",\r\n\t\t\"ar-dz,arabic (algeria)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-se,english (sweden)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"it-ch,italian (switzerland)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"ru-ua,russian (ukraine)\",\r\n\t\t\"ur-in,urdu (india)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t// blink\r\n\t\t\"fr-ch,french (switzerland)\",\r\n\t\t'pa-arab,punjabi (arabic)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfsign.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>nf: signdisplay</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat: signdisplay\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tisBigIntSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\r\n\r\n\tlet items = [-1, -0, 0/0, 0, 1]\r\n\tlet tests = {\r\n\t\t\"always\": items,\r\n\t\t\"auto\": items,\r\n\t\t\"exceptZero\": items,\r\n\t\t//\"negative\":items, // RangeError: invalid value \"negative\" for option signDisplay\r\n\t\t\"never\": items,\r\n\t}\r\n\tif (method == \"min\") {\r\n\t\ttests = {\r\n\t\t\t\"always\": [-1, 0/0]\r\n\t\t}\r\n\t}\r\n\r\n\ttry {\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oStyles = {}\r\n\t\t\tObject.keys(tests).forEach(function(s){\r\n\t\t\t\toStyles[s] = []\r\n\t\t\t\tlet formatter = new Intl.NumberFormat(code, {signDisplay: s})\r\n\t\t\t\ttests[s].forEach(function(t){\r\n\t\t\t\t\toStyles[s].push(formatter.format(t))\t\t\t\t\t\t\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(tests).forEach(function(s){\r\n\t\t\t\t\toTempData[hash][s] = oStyles[s].join(\" | \")\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const p of Object.keys(oData[k])) {\r\n\t\t\t\tif (p !== \"locales\") {\r\n\t\t\t\t\tstr += \"<li>\"+ s16 + p +\": \"+ sc + oData[k][p] +\"</li>\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"4491496a\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"2a5a0fce\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"492461bc\") { // FF147+: 43\r\n\t\t\t\t} else if (localesHash == \"68ebd8fb\") { // FF140-146: 40\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat\r\n\t// note: numberformat supported since at least FF29\r\n\r\n\t// FF78+ : signDisplay\r\n\t// FF93+ : negative\r\n\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested: FF78+\r\n\t\tlet test = new Intl.NumberFormat(\"en\", {signDisplay: \"always\"}).format(5)\r\n\t\ttest += \"\"\r\n\t\tif (test.length > 1) {\r\n\t\t\tisSupported = true\r\n\t\t} else {\r\n\t\t\tdom.results.innerHTML = s4 + \"signDisplay:\" + sc +\" not supported\"\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt(\"9999999999999999\")\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"ar-er,arabic (eritrea)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n'kk-cn,kazakh (china)',\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t// blink\r\n\t\t'pa-pk,punjabi (pakistan)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/nfunit.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>nf: unitdisplay</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">numberformat: unitdisplay\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btnfirst\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t&nbsp; KEY: <span class=\"s16\">L</span> long <span class=\"s16\">N</span> narrow <span class=\"s16\">S</span> short\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\toUnits = {},\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toTestData = {},\r\n\tisSupported = false,\r\n\tisBigIntSupported = false,\r\n\tlocalesHashAll = \"\", // to compare min to\r\n\tstrWarning = \" don't panic, it's working<br><br> ... running 'ALL' takes a few seconds\"\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction set_units() {\r\n\tif (!isSupported) {return}\r\n\r\n\t// https://github.com/unicode-org/cldr/blob/main/common/validity/unit.xml\\\r\n\t// https://unicode.org/reports/tr35/tr35-general.html#63-example-units\r\n\r\n\t// all\r\n\tlet units = [\r\n\t\t// https://searchfox.org/mozilla-central/source/js/src/builtin/intl/SanctionedSimpleUnitIdentifiersGenerated.js\r\n\t\t// FF: simple units\r\n\t\t\"acre\",\"bit\",\"byte\",\"celsius\",\"centimeter\",\r\n\t\t\"day\",\"degree\",\"fahrenheit\",\"fluid-ounce\",\"foot\",\r\n\t\t\"gallon\",\"gigabit\",\"gigabyte\",\"gram\",\"hectare\",\r\n\t\t\"hour\",\"inch\",\"kilobit\",\"kilobyte\",\"kilogram\",\r\n\t\t\"kilometer\",\"liter\",\"megabit\",\"megabyte\",\"meter\",\r\n\t\t\"microsecond\",\"mile\",\"mile-scandinavian\",\"milliliter\",\"millimeter\",\r\n\t\t\"millisecond\",\"minute\",\"month\",\"nanosecond\",\"ounce\",\r\n\t\t\"percent\",\"petabyte\",\"pound\",\"second\",\"stone\",\r\n\t\t\"terabit\",\"terabyte\",\"week\",\"yard\",\"year\",\r\n\t\t// combined\r\n\t\t\t// we already get the simple units in single/plural\r\n\t\t\t// so we only need one of these to get \"per\" (in case it holds entropy)\r\n\t\t\t// not true for all\r\n\t\t\"liter-per-kilometer\",\r\n\t\t\"kilometer-per-hour\", // this covers it\r\n\t\t\"meter-per-second\",\r\n\t\t\"mile-per-gallon\",\r\n\t\t\"mile-per-hour\",\r\n\r\n\t\t//* other\r\n\t\t// acceleration\r\n\t\t\"g-force\",\"meter-per-square-second\",\r\n\t\t// angle\r\n\t\t\"revolution\",\"radian\",\"arc-minute\",\"arc-second\",\r\n\t\t// area\r\n\t\t\"square-kilometer\",\"square-inch\",\"dunam\",\r\n\t\t// concentration\r\n\t\t\"karat\",\"milligram-per-deciliter\",\"millimole-per-liter\",\"permillion\",\r\n\t\t\"permille\",\"permyriad\",\"mole\",\"liter-per-100-kilometer\",\r\n\t\t\"mile-per-gallon-imperial\",\"petabyte\",\r\n\t\t// duration\r\n\t\t\"century\",\"day-person\",\"month-person\",\"week-person\",\"year-person\",\r\n\t\t// electric\r\n\t\t\"ampere\",\"milliampere\",\"ohm\",\"volt\",\r\n\t\t// energy\r\n\t\t\"kilocalorie\",\"calorie\",\"foodcalorie\",\"kilojoule\",\"joule\",\"kilowatt-hour\",\"electronvolt\",\r\n\t\t\"british-thermal-unit\",\r\n\t\t// force\r\n\t\t\"pound-force\",\"newton\",\r\n\t\t// frequency\r\n\t\t\"gigahertz\",\"megahertz\",\"kilohertz\",\"hertz\",\r\n\t\t// length\r\n\t\t\"parsec\",\"light-year\",\"astronomical-unit\",\"furlong\",\"fathom\",\r\n\t\t\"nautical-mile\",\"point\",\"solar-radius\",\"lux\",\"solar-luminosity\",\r\n\t\t// mass\r\n\t\t\"metric-ton\",\"ounce-troy\",\"carat\",\"dalton\",\"earth-mass\",\"solar-mass\",\r\n\t\t// power\r\n\t\t\"gigawatt\",\"milliwatt\",\"horsepower\",\r\n\t\t// pressure\r\n\t\t\"hectopascal\",\"millimeter-ofhg\",\"pound-force-per-square-inch\",\"inch-ofhg\",\r\n\t\t\"millibar\",\"atmosphere\",\"kilopascal\",\"megapascal\",\r\n\t\t// speed\r\n\t\t\"knot\",\r\n\t\t// temperature\r\n\t\t\"generic\",\"kelvin\",\r\n\t\t// torque\r\n\t\t\"pound-force-foot\",\"newton-meter\",\r\n\t\t// volume\r\n\t\t\"cubic-kilometer\",\"cubic-inch\",\"megaliter\",\"pint\",\"cup\",\r\n\t\t\"fluid-ounce-imperial\",\"tablespoon\",\"teaspoon\",\"barrel\",\r\n\t\t//*/\r\n\t]\r\n\tunits.sort()\r\n\tunits = units.filter(function(item, position) {return units.indexOf(item) === position})\r\n\toUnits[\"all\"] = units\r\n\r\n\t// supported: ignore invalid unit identifiers\r\n\tlet supported = []\r\n\tunits.forEach(function(u) {\r\n\t\ttry {\r\n\t\t\tlet formatter = Intl.NumberFormat(\"en\", {style: \"unit\", unit: u})\r\n\t\t\tsupported.push(u)\r\n\t\t} catch(e) {}\r\n\t})\r\n\toUnits[\"supported\"] = supported\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.NumberFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\toTestData = {}\r\n\r\n\tlet tests = {}\r\n\tlet units = oUnits[\"supported\"]\r\n\tlet nBig\r\n\tif (isBigIntSupported) {\r\n\t\tnBig = BigInt(\"987354000000000000\")\r\n\t}\r\n\r\n\tif (method == \"min\") {\r\n\t\tlet optN = {\"narrow\": [1]},\r\n\t\t\toptL = {\"long\": [1]},\r\n\t\t\tboth = {\"long\": [1], \"narrow\": [1],},\r\n\t\t\teverything = {\"long\": [1], \"narrow\": [1], \"short\": [987654]}\r\n\r\n\t\ttests = {\r\n\t\t\t// FF115+\r\n\t\t\t\"fahrenheit\": both,\r\n\t\t\t\"foot\": optL,\r\n\t\t\t\"hectare\": {\"long\": [1], \"short\": [987654]},\r\n\t\t\t\"kilometer-per-hour\": optN,\r\n\t\t\t\"millimeter\": optN,\r\n\t\t\t\"month\": both,\r\n\t\t\t\"nanosecond\": optN,\r\n\t\t\t\"percent\": everything,\r\n\t\t\t\"second\": {\"long\": [1], \"narrow\": [1], \"short\": [987654]},\r\n\t\t\t\"terabyte\": optL,\r\n\t\t\t// FF121+ ICU 74\r\n\t\t\t\"byte\": optN,\r\n\t\t}\r\n\r\n\t\t// FF109 and lower needs some help: swap month long for short\r\n\t\tif (isFF && \"object\" !== typeof ondeviceorientationabsolute) {\r\n\t\t\ttests[\"month\"] = {\"narrow\": [1], \"short\": [987654]}\r\n\t\t\ttests[\"day\"] = optL\r\n\t\t\ttests[\"gallon\"] = {\"short\": [987654]}\r\n\t\t}\r\n\t\tif (!isFF) {\r\n\t\t\t// chrome\r\n\t\t\ttests[\"gallon\"] = {\"short\": [987654]}\r\n\t\t\t// non-gecko: make month = all three to cover bases\r\n\t\t\ttests[\"month\"] = {\"long\": [1], \"narrow\": [1], \"short\": [987654]}\r\n\t\t}\r\n\t} else {\r\n\t\t// unitdisplay\r\n\t\tlet unitDisplays = [\"long\",\"narrow\",\"short\"] // 184\r\n\t\t/*\r\n\t\t// short adds 1 region (splits es-bo + es-py)\r\n\t\tunitDisplays = [\"long\"] // 169\r\n\t\tunitDisplays = [\"narrow\"] // 176\r\n\t\tunitDisplays = [\"short\"] // 172\r\n\t\tunitDisplays = [\"long\",\"narrow\"] // 183\r\n\t\tunitDisplays = [\"long\",\"short\"] // 179\r\n\t\tunitDisplays = [\"narrow\",\"short\"] // 180\r\n\t\t//*/\r\n\r\n\t\tunits.forEach(function(u) {\r\n\t\t\ttry {\r\n\t\t\t\tlet formatter = Intl.NumberFormat(\"en\", {style: \"unit\", unit: u}) // ignore invalid unit identifiers\r\n\t\t\t\ttests[u] = {}\r\n\t\t\t\tunitDisplays.forEach(function(ud) {\r\n\t\t\t\t\t// we only need one test from each but we need both values\r\n\t\t\t\t\tif (ud == \"short\") {\r\n\t\t\t\t\t\ttests[u][ud] = [987654]\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttests[u][ud] = [1]\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// bigint doesn't add anything\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tif (isBigIntSupported) {\r\n\t\t\t\t\t\ttests[u][ud].push(nBig)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t*/\r\n\t\t\t\t})\r\n\t\t\t} catch(e) {}\r\n\t\t})\r\n\t}\r\n\r\n\tfunction get_value(type, parts) {\r\n\t\ttry {\r\n\t\t\tlet str = \"none\"\r\n\t\t\tfor (let i = 0 ; i < parts.length; i++) {\r\n\t\t\t\tif (parts[i][\"type\"] === type) {\r\n\t\t\t\t\tstr = parts[i][\"value\"]\r\n\t\t\t\t\tstr += str.length == 1 ? \" (\"+ str.charCodeAt(0) +\")\" : \"\"\r\n\t\t\t\t\tstr = str.charCodeAt(0)\r\n\t\t\t\t\treturn str // no need to keep checking\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn str\r\n\t\t} catch(e) {\r\n\t\t\treturn \"error\"\r\n\t\t}\r\n\t}\r\n\r\n\r\n\tlet oStyles = {}\r\n\ttry {\r\n\t\taLocales.forEach(function(code) { // for each locale\r\n\t\t\toStyles = {}\r\n\t\t\tObject.keys(tests).sort().forEach(function(u){ // for each unit\r\n\t\t\t\toStyles[u] = {}\r\n\t\t\t\tObject.keys(tests[u]).sort().forEach(function(ud){ // for each unitdisplay\r\n\t\t\t\t\toStyles[u][ud] = []\r\n\t\t\t\t\tlet isFound = false\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tlet formatter = Intl.NumberFormat(code, {style: \"unit\", unit: u, unitDisplay: ud})\r\n\t\t\t\t\t\ttests[u][ud].forEach(function(n){ // for each number\r\n\t\t\t\t\t\t\toStyles[u][ud].push(formatter.format(n))\r\n\t\t\t\t\t\t\t/* temp: checking for a test that returns a literal most/all of the time\r\n\t\t\t\t\t\t\tlet testvalue = get_value('literal', formatter.formatToParts(n))\r\n\t\t\t\t\t\t\tif ('number' == typeof testvalue) {\r\n\t\t\t\t\t\t\t\tif (undefined == oTestData[u+'_'+ud]) {oTestData[u+'_'+ud] = {}}\r\n\t\t\t\t\t\t\t\tif (undefined == oTestData[u+'_'+ud][testvalue]) {\r\n\t\t\t\t\t\t\t\t\toTestData[u+'_'+ud][testvalue] = [code]; isFound = true\r\n\t\t\t\t\t\t\t\t} else if (!isFound) {\r\n\t\t\t\t\t\t\t\t\t// don't duplicate\r\n\t\t\t\t\t\t\t\t\toTestData[u+'_'+ud][testvalue].push(code); isFound = true\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t//*/\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} catch (e) {} // ignore invalid\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\tObject.keys(oStyles).forEach(function(u){ // each unit\r\n\t\t\t\t\toTempData[hash][u] = {}\r\n\t\t\t\t\tObject.keys(oStyles[u]).forEach(function(ud){ // for each unitdisplay\r\n\t\t\t\t\t\tif (oStyles[u][ud].length) {\r\n\t\t\t\t\t\t\toTempData[hash][u][ud] = oStyles[u][ud].join(\" | \")\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order in new object\r\n\t\tfor (const h of Object.keys(oTempData).sort()) { // for each hash\r\n\t\t\toData[h] = {}\r\n\t\t\tfor (const u of Object.keys(oTempData[h]).sort()) { // for each unit\r\n\t\t\t\tif (u == \"locales\") {\r\n\t\t\t\t\toData[h][u] = oTempData[h][u].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (Object.keys(oTempData[h][u]).length) {\r\n\t\t\t\t\t\toData[h][u] = oTempData[h][u]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const h of Object.keys(oData)) { // for each hash\r\n\t\t\tlocaleGroups.push(oData[h][\"locales\"])\r\n\t\t\tlet localeCount = oData[h][\"locales\"].split(\",\").length\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const u of Object.keys(oData[h])) { // for each unit\r\n\t\t\t\tif (u !== \"locales\") {\r\n\t\t\t\t\tstr += \"<li>\"+ s12 + u +\": \"+ sc\r\n\t\t\t\t\tlet items = []\r\n\t\t\t\t\tObject.keys(oData[h][u]).forEach(function(ud){ // for each unitdisplay\r\n\t\t\t\t\t\tlet abbrev = ud.slice(0,1).toUpperCase()\r\n\t\t\t\t\t\titems.push(s16 + abbrev +\": \"+ sc + oData[h][u][ud])\r\n\t\t\t\t\t})\r\n\t\t\t\t\tstr += items.join(\" \")\r\n\t\t\t\t}\r\n\t\t\t\tstr += \"</li>\"\r\n\t\t\t}\r\n\t\t\t// wrap into details for long lists\r\n\t\t\tif (Object.keys(tests).length > 15) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + h + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[h][\"locales\"] +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"5549ad9b\") { // FF151+\r\n\t\t\t\t} else if (resultsHash == \"46a2bf50\") { // FF147-150\r\n\t\t\t\t} else if (resultsHash == \"f6967327\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"e4adc310\") { // FF147+ 209\r\n\t\t\t\t} else if (localesHash == \"85300889\") { // FF140-146 206\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tlet status = \"calculating ...\"\r\n\t\tif (method == \"all\") {status += strWarning}\r\n\t\tdom.results.innerHTML = status\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\r\n\r\n\t// FF78+ : unit, unitDisplay\r\n\ttry {\r\n\t\t// pointless if we can't use the feature being tested\r\n\t\tlet test = new Intl.NumberFormat(undefined, {style: \"unit\", unit: \"mile-per-hour\", unitDisplay: \"long\"}).format(5)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// check bigint support: FF68+\r\n\ttry {\r\n\t\tlet y = BigInt(\"9999999999999999\")\r\n\t\tisBigIntSupported = true\r\n\t} catch(e) {}\r\n\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"ar-bh,arabic (bahrain)\",\r\n\t\t\"ar-dz,arabic (algeria)\",\r\n\t\t\"ar-sa,arabic (saudi arabia)\",\r\n\t\t\"bs-cyrl,bosnian (cyrillic)\",\r\n\t\t\"de-at,german (austria)\",\r\n\t\t\"de-ch,german (switzerland)\",\r\n\t\t\"de-li,german (liechtenstein)\",\r\n\t\t\"en-ag,english (antigua & barbuda)\",\r\n\t\t\"en-at,english (austria)\",\r\n\t\t\"en-au,english (australia)\",\r\n\t\t\"en-be,english (belgium)\",\r\n\t\t\"en-bs,english (bahamas)\",\r\n\t\t\"en-ca,english (canada)\",\r\n\t\t\"en-ch,english (switzerland)\",\r\n\t\t\"en-fi,english (finland)\",\r\n\t\t\"en-in,english (india)\",\r\n\t\t\"en-za,english (south africa)\",\r\n\t\t\"es-ar,spanish (argentina)\",\r\n\t\t\"es-bo,spanish (bolivia)\",\r\n\t\t\"es-br,spanish (brazil)\",\r\n\t\t\"es-co,spanish (colombia)\",\r\n\t\t\"es-cr,spanish (costa rica)\",\r\n\t\t\"es-do,spanish (dominican republic)\",\r\n\t\t\"es-mx,spanish (mexico)\",\r\n\t\t\"es-pr,spanish (puerto rico)\",\r\n\t\t\"es-py,spanish (paraguay)\",\r\n\t\t\"es-us,spanish (united states)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"fr-ca,french (canada)\",\r\n\t\t\"fr-ch,french (switzerland)\",\r\n\t\t\"fr-ht,french (haiti)\",\r\n\t\t\"fr-lu,french (luxembourg)\",\r\n\t\t\"hi-latn,hindi (latin)\",\r\n\t\t\"it-ch,italian (switzerland)\",\r\n\t\t'kk-cn,kazakh (china)',\r\n\t\t\"kok-latn,konkani (latin)\",\r\n\t\t\"ms-bn,malay (brunei)\",\r\n\t\t\"ps-pk,pashto (pakistan)\",\r\n\t\t\"pt-ao,portuguese (angola)\",\r\n\t\t\"qu-bo,quechua (bolivia)\",\r\n\t\t\"ro-md,romanian (moldova)\",\r\n\t\t\"sr-cyrl-ba,serbian (cyrillic bosnia & herzegovina)\",\r\n\t\t\"sr-cyrl-me,serbian (cyrillic montenegro)\",\r\n\t\t\"sr-latn-ba,serbian (latin bosnia & herzegovina)\",\r\n\t\t\"sv-fi,swedish (finland)\",\r\n\t\t\"sw-cd,swahili (congo kinshasa)\",\r\n\t\t\"sw-ke,swahili (kenya)\",\r\n\t\t\"ta-my,tamil (malaysia)\",\r\n\t\t\"ur-in,urdu (india)\",\r\n\t\t\"uz-cyrl-uz,uzbek (cyrillic uzbekistan)\",\r\n\t\t\"yo-bj,yoruba (benin)\",\r\n\t\t\"yue-hans,cantonese (simplified)\",\r\n\t\t// blink\r\n\t\t'pa-pk,punjabi (pakistan)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tset_units()\r\n\t\tsetBtn(\"all\")\r\n\t\tdom.results.innerHTML = \"calculating ...\"+ strWarning\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 50)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/os.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>os</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\t.oscenter {\r\n\t\t\ttext-align: center;\r\n\t\t}\r\n\t\t.cssDocFont {font-family: \"Arial Black\";}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"offscreen\">\r\n\t\t<div class=\"cssDocFont\" id=\"divDocFont\"></div>\r\n\t</div>\r\n\t<div class=\"hidden\">\r\n\t\t<input type=\"radio\" id=\"wgtradio\">\r\n\t</div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#feature\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb3\">\r\n\t\t<thead><tr><th>\r\n\t\t\t<div class=\"nav-title\">os\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td class=\"intro\">\r\n\t\t\t<span class=\"no_color\">gecko (FF89+) only: testing os detection logic</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td><hr><br></td></tr>\r\n\t\t<tr><td style=\"text-align: left;\">\r\n\t\t\t<div>OS &nbsp;\r\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"os\"> &nbsp; </p><br>\r\n\t\t\t</div>\r\n\t\t\t<div>DEBUG\r\n\t\t\t\t<p class=\"c mono spaces no_color\" id=\"debug\"> &nbsp; </p>\r\n\t\t\t</div>\r\n\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet aDebug = [],\r\n\toResults = {},\r\n\tt0,\r\n\tpad = 25,\r\n\tisTimeOut = false,\r\n\tisThrowError = false,\r\n\tisThrowWidgetError = false,\r\n\tisThrowDocFontError = false,\r\n\tisThrowFontError = false,\r\n\tisOnlyGecko = false\r\n\r\nlet notNormal = sb +\" not normal\"+ sc +\" [now you <b><u>really</u></b> stand out]\",\r\n\tgoodResult = sg +\"result: \"+ sc,\r\n\tbadResult = sb +\"result: \"+ sc\r\n\r\nlet oMap = {\r\n\t1: goodResult +\"you are not gecko\",\r\n\t2: goodResult +\"update your browser\" +\"<br><br><div class='indent faint'>this PoC requires FF89+ to be fully effective</div>\",\r\n\t\"01\": \"desktop\",\r\n\t\"02\": \"chrome\",\r\n\t\"03\": \"widget\",\r\n\t\"04\": \"fonts\",\r\n\t\"05\": \"currentTime\",\r\n}\r\n\r\nfunction finish(type) {\r\n\tdom.perf = Math.round((performance.now() - t0)) + \" ms\"\r\n\tdom.debug.innerHTML = aDebug.join(\"<br>\")\r\n\tif (!isFF) (type = 1)\r\n\tif (type !== undefined && type < 3) {\r\n\t\tdom.os.innerHTML = oMap[type]\r\n\t\treturn\r\n\t} else {\r\n\t\tlet display = []\r\n\t\tfor (const k of Object.keys(oResults)) {\r\n\t\t\tlet name = oMap[k]\r\n\t\t\tdisplay.push(s13 + (name+\": \").padStart(15) +sc + oResults[k])\r\n\t\t}\r\n\t\tdom.os.innerHTML = display.join(\"<br>\")\r\n\t}\r\n}\r\n\r\nconst check_css = (isNew) => new Promise(resolve => {\r\n\tsetTimeout(() => resolve(\"timed out\"), 100)\r\n\r\n\t// FF89-123\r\n\t\t// 1280128: FF51+ win/mac | 1701257: FF89+ linux, therefore undefined = android\r\n\t// FF121+: 1855861\r\n\t// FF124+: 1874232\r\n\t\t// new: chrome://browser/content/extension-popup-panel.css\r\n\t\t// fallback: chrome://browser/content/extension.css\r\n\t\t// both these are desktop only\r\n\r\n\tlet newCounter = 0, tmpOS\r\n\tfunction exit() {\r\n\t\treturn resolve((isNew ? newCounter : tmpOS))\r\n\t}\r\n\tconst get_event = (css, item) => new Promise(resolve => {\r\n\t\tcss.onload = function() {\r\n\t\t\tif (isNew) {\r\n\t\t\t\t//desktop vs android\r\n\t\t\t\tnewCounter++\r\n\t\t\t} else {\r\n\t\t\t\ttmpOS = item == \"win\" ? \"windows\" : item\r\n\t\t\t}\r\n\t\t\tcount++\r\n\t\t\tdocument.head.removeChild(css)\r\n\t\t\taDebug.push(s3 + (item +\": \").padStart(pad) + sc +\"detected\")\r\n\t\t\tif (count == maxCount) {exit()}\r\n\t\t\treturn resolve()\r\n\t\t}\r\n\t\tcss.onerror = function() {\r\n\t\t\tcount++\r\n\t\t\tdocument.head.removeChild(css)\r\n\t\t\taDebug.push(s3 + (item +\": \").padStart(pad) + sc +\"not detected\")\r\n\t\t\tif (count == maxCount) {exit()}\r\n\t\t\treturn resolve()\r\n\t\t}\r\n\t})\r\n\r\n\tlet count = 0, maxCount\r\n\tlet path = \"chrome://browser/content/extension-\", suffix = \"-panel.css\"\r\n\tlet list = [\"win\", \"mac\",\"linux\"]\r\n\tif (isNew) {\r\n\t\tlist = ['extension']\r\n\t\tif (isFF && isVer > 123) {\r\n\t\t\tlist = ['extension-popup-panel','extension']\r\n\t\t}\r\n\t\tpath = \"chrome://browser/content/\", suffix = \".css\"\r\n\t\tif (isFF) {\r\n\t\t\tlet string = (isVer > 123 ? \"124+ \" : \"89+ \") + \"desktop: \"\r\n\t\t\taDebug.push(s13 + string.padStart(pad) + sc + path +\"*.css\")\r\n\t\t} else {\r\n\t\t\taDebug.push(s13 + \"desktop: \".padStart(pad) + sc + path +\"*.css\")\r\n\t\t}\r\n\t} else {\r\n\t\taDebug.push(s13 + \"89-123 chrome://: \".padStart(pad) + sc +\"browser/content/extension-*\"+ suffix)\r\n\t}\r\n\tif (!isTimeOut) {\r\n\t\ttry {\r\n\t\t\tif (isThrowError) {hortonhearsawho++}\r\n\t\t\tmaxCount = list.length\r\n\t\t\tlist.forEach(function(item) {\r\n\t\t\t\tlet css = document.createElement(\"link\")\r\n\t\t\t\tcss.type = \"text/css\"\r\n\t\t\t\tcss.rel = \"stylesheet\"\r\n\t\t\t\tcss.href = path + item + suffix\r\n\t\t\t\tdocument.head.appendChild(css)\r\n\t\t\t\tget_event(css, item)\r\n\t\t\t})\r\n\t\t} catch(e) {\r\n\t\t\taDebug.push(sb + (\"error: \").padStart(pad) + sc)\r\n\t\t\taDebug.push(\"<div class='oscenter faint'>\"+ e +\"</div>\")\r\n\t\t\treturn resolve(\"error\")\r\n\t\t}\r\n\t}\r\n})\r\n\r\nfunction check_docfonts() {\r\n\t// FF124+ desktop: dig deeper\r\n\t// the easiest way is to check for '-apple-system', 'MS Shell Dlg','MS Shell Dlg \\\\32'\r\n\t\t// we could check a widget fonts but that fails in TB windows, may change\r\n\t\t// and getComputedStyle can report the wrong font, so detect the actual fonts\r\n\r\n\tlet key = \"04\" // this is for fonts test if we fail\r\n\ttry {\r\n\t\tif (isThrowDocFontError) {i_am_groot++}\r\n\t\t// test doc fonts enabled\r\n\t\tlet fntTest = \"\\\"Arial Black\\\"\"\r\n\t\tlet font = getComputedStyle(dom.divDocFont).getPropertyValue(\"font-family\")\r\n\t\tlet fntEnabled = (font == fntTest ? true : false)\r\n\t\tif (!fntEnabled) {\r\n\t\t\tif (font.slice(0,11) == \"Arial Black\") {fntEnabled = true} // ext may strip quotes marks\r\n\t\t}\r\n\t\tif (fntEnabled) {\r\n\t\t\taDebug.push(s13 + \"document fonts: \".padStart(pad) + sc +\"enabled\")\r\n\t\t\ttry_fonts()\r\n\t\t} else {\r\n\t\t\taDebug.push(s13 + \"document fonts: \".padStart(pad) + sc +\"disabled\")\r\n\t\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc + zNA +\"<br>\")\r\n\t\t\toResults[key] = zNA\r\n\t\t\ttry_somethingelse()\r\n\t\t}\r\n\t} catch(e) {\r\n\t\taDebug.push(s13 + \"document fonts: \".padStart(pad) + sc)\r\n\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc + zNA)\r\n\t\taDebug.push(sb + \"error: \".padStart(pad) + sc)\r\n\t\taDebug.push(\"<div class='oscenter faint'>\"+ e +\"</div>\")\r\n\t\toResults[key] = zNA\r\n\t\ttry_somethingelse()\r\n\t}\r\n}\r\n\r\nfunction check_widgetfonts() {\r\n\tlet key = \"03\"\r\n\ttry {\r\n\t\tif (isThrowWidgetError) {green_eggs_and_ham++}\r\n\t\tlet aIgnore = [\r\n\t\t'cursive','emoji','fangsong','fantasy','math','monospace','none','sans-serif',\r\n\t\t'serif','system-ui','ui-monospace','ui-rounded','ui-serif','undefined', undefined, '']\r\n\t\tlet font = getComputedStyle(dom.wgtradio).getPropertyValue(\"font-family\")\r\n\t\t//font = \"Roboto\"\r\n\t\t//font = \"\" // godamnit pierov\r\n\t\tlet fontDisplay = font == \"\" ? \"empty string\" : font\r\n\t\taDebug.push(s13 + \"widget font: \".padStart(pad) + sc + fontDisplay)\r\n\t\tlet display, value\r\n\t\tif (isFF) {\r\n\t\t\tif (aIgnore.includes(font)) {\r\n\t\t\t\tvalue = zNA\r\n\t\t\t\tdisplay = sb + (\"result: \").padStart(pad) + sc + \"too generic\"\r\n\t\t\t} else {\r\n\t\t\t\tlet systemfont\r\n\t\t\t\tif (font.slice(0,12) == \"MS Shell Dlg\") {systemfont = \"windows\"\r\n\t\t\t\t} else if (font == \"-apple-system\") {systemfont = \"mac\"}\r\n\t\t\t\tif (systemfont !== undefined) {\r\n\t\t\t\t\tvalue = systemfont\r\n\t\t\t\t\tdisplay = sg + (\"result: \").padStart(pad) + sc + value\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalue = font\r\n\t\t\t\t\tdisplay = s13 + (\"result: \").padStart(pad) + sc + \"maybe useful\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tdisplay = s3 + (\"result: \").padStart(pad) + sc + zNA\r\n\t\t}\r\n\t\tif (display !== undefined) {aDebug.push(display +\"<br>\")}\r\n\t\tif (isFF) {oResults[key] = value}\r\n\t\tcheck_docfonts()\r\n\t} catch(e) {\r\n\t\taDebug.push(s13 + \"widget font: \".padStart(pad) + sc)\r\n\t\taDebug.push(sb + \"error: \".padStart(pad) + sc)\r\n\t\taDebug.push(\"<div class='oscenter faint'>\"+ e +\"</div>\")\r\n\t\toResults[key] = zNA\r\n\t\tcheck_docfonts()\r\n\t}\r\n}\r\n\r\nfunction run() {\r\n\tt0 = performance.now()\r\n\tdom.os.innerHTML = \"\"\r\n\tdom.debug.innerHTML = \"\"\r\n\taDebug = []\r\n\toResults = {}\r\n\tlet notation = \"\", isVerOpen, isVerFull\r\n\r\n\tlet resFF = isOnlyGecko ? isFF : (isFF ? true : zNA)\r\n\taDebug.push(s13 + \"gecko: \".padStart(pad) + sc + resFF)\r\n\r\n\tif (isFF) {\r\n\t\tisVerOpen = (isVer == isVerMax +\"\")\r\n\t\tisVerFull = isVer + (isVerOpen ? \"+\" : \"\")\r\n\t\taDebug.push(s13 + \"version: \".padStart(pad) + sc + isVer + (isVerOpen ? \"+\" : (isVer == 52 ? \" or lower\" : \"\")) +\"<br>\")\r\n\t} else {\r\n\t\taDebug.push(s13 + \"version: \".padStart(pad) + sc + zNA +\"<br>\")\r\n\t}\r\n\r\n\tfunction analyze(isNew, result) {\r\n\t\tlet key = isNew ? \"01\" : \"02\"\r\n\t\tlet value = result, display\r\n\t\tif (result == \"timed out\") {\r\n\t\t\tdisplay = sb + (\"timed out: \").padStart(pad) + sc\r\n\t\t\tvalue = zNA\r\n\t\t} else if (result == \"error\") {\r\n\t\t\tvalue = zNA\r\n\t\t} else {\r\n\t\t\tif (isFF && isNew) {\r\n\t\t\t\t// 6 results: 0,1,2 for > 123 and < 124\r\n\t\t\t\tif (result == 0) {\r\n\t\t\t\t\tvalue = \"android\"\r\n\t\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value\r\n\t\t\t\t} else if (isVer > 123 && result == 1) {\r\n\t\t\t\t\tvalue = \"desktop\"\r\n\t\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value + sb +\" [but only 1 of 2]\"+ sc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalue = \"desktop\"\r\n\t\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value\r\n\t\t\t\t}\r\n\t\t\t} else if (isFF && !isNew) {\r\n\t\t\t\tif (result == undefined) {\r\n\t\t\t\t\tif (isVer > 123) {\r\n\t\t\t\t\t\tvalue = zNA\r\n\t\t\t\t\t\tdisplay = s13 + \"result: \".padStart(pad) + sc + value\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tvalue = \"android\" // FF123 or lower nothing found: assume android\r\n\t\t\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// if not undefined, we can only have windows/mac/linux\r\n\t\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + result\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tdisplay = s13 + \"result: \".padStart(pad) + sc + zNA\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (display !== undefined) {aDebug.push(display +\"<br>\")}\r\n\t\tif (isFF) {oResults[key] = value}\r\n\t}\r\n\r\n\tif (isOnlyGecko && !isFF) {\r\n\t\tfinish(1)\r\n\t} else if (isOnlyGecko && !isFF || isFF && isVer < 89) {\r\n\t\tfinish(2)\r\n\t} else {\r\n\t\tPromise.all([\r\n\t\t\tcheck_css(true)\r\n\t\t]).then(function(res){\r\n\t\t\tanalyze(true, res[0])\r\n\t\t\tPromise.all([\r\n\t\t\t\tcheck_css(false)\r\n\t\t\t]).then(function(res){\r\n\t\t\t\tanalyze(false, res[0])\r\n\t\t\t\tcheck_widgetfonts()\r\n\t\t\t})\r\n\t\t})\r\n\t}\r\n}\r\n\r\nfunction try_somethingelse() {\r\n\t// is there anything that is platform specific that can't be flipped with a pref\r\n\r\n\t// FF34+ 848954\r\n\t// linux doesn't have a currentTime property in HTMLMediaElement\r\n\t// https://searchfox.org/mozilla-central/source/dom/html/HTMLMediaElement.h#37\r\n\t// hmm ok , not what I thought it was\r\n\t/*\r\n\tlet key = \"05\"\r\n\taDebug.push(s13 + \"HTMLMediaElement: \".padStart(pad) + sc + \"currentTime\")\r\n\ttry {\r\n\t\tlet value = HTMLMediaElement.prototype.hasOwnProperty(\"currentTime\") ? \"not linux\" : \"linux\" // 0.008ms\r\n\t\taDebug.push(sg + \"result: \".padStart(pad) + sc + value +\"<br>\")\r\n\t\toResults[key] = value\r\n\t} catch(e) {\r\n\t\taDebug.push(sb + \"error: \".padStart(pad) + sc)\r\n\t\taDebug.push(\"<div class='oscenter faint'>\"+ e +\"</div>\")\r\n\t\toResults[key] = zNA\r\n\t}\r\n\t*/\r\n\r\n\taDebug.push(s13 + \"something else: \".padStart(pad) + sc +\"pending\")\r\n\r\n\tfinish()\r\n}\r\n\r\n\r\nsetTimeout(function() {\r\n\tPromise.all([\r\n\t\tget_globals()\r\n\t]).then(function(){\r\n\t\tPromise.all([\r\n\t\t\tget_isVer()\r\n\t\t]).then(function(){\r\n\t\t\trun()\r\n\t\t})\r\n\t})\r\n}, 25)\r\n\r\nfunction try_fonts() {\r\n\tlet key = \"04\"\r\n\tlet fntFake = \"--00\"+ rnd_string()\r\n\tlet fntSize = \"512px\"\r\n\tlet fntString = \"Mōá?-\"+ String.fromCodePoint('0xFFFF')\r\n\tlet fntTest = [\r\n\t\t'Dancing Script','Roboto',\r\n\t\t'-apple-system',\r\n\t\t'MS Shell Dlg \\\\32',\r\n\t]\r\n\tfntTest.push(fntFake)\r\n\tfntTest.sort()\r\n\tlet fntControl = ['monospace, Consolas, Menlo, \"Courier New\\\"','sans-serif, Arial','serif, \"Times New Roman\\\"']\r\n\tlet fntGeneric = fntControl\r\n\r\n\ttry {\r\n\t\tif (isThrowFontError) {let_them_eat_cake++}\r\n\t\tconst doc = document // or iframe.contentWindow.document\r\n\t\tconst id = `font-fingerprint`\r\n\t\tconst div = doc.createElement('div')\r\n\t\tdiv.setAttribute('id', id)\r\n\t\tdoc.body.appendChild(div)\r\n\t\tset_element(id, fntSize, fntString)\r\n\r\n\t\tconst span = doc.getElementById(`${id}-detector`)\r\n\t\tconst originPixelsToNumber = pixels => 2*pixels.replace('px', '')\r\n\t\tconst detectedFonts = new Set()\r\n\t\tconst style = getComputedStyle(span)\r\n\t\tlet getDimensions = (span, style) => {\r\n\t\t\tconst transform = style.transformOrigin.split(' ')\r\n\t\t\tconst dimensions = {\r\n\t\t\t\theight: originPixelsToNumber(transform[1]),\r\n\t\t\t\twidth: originPixelsToNumber(transform[0]),\r\n\t\t\t}\r\n\t\t\treturn dimensions\r\n\t\t}\r\n\r\n\t\t// base sizes\r\n\t\tlet base = fntGeneric.reduce((acc, font) => {\r\n\t\t\tspan.style.font = \"\"\r\n\t\t\tspan.style.setProperty('--font', font)\r\n\t\t\tconst dimensions = getDimensions(span, style)\r\n\t\t\tacc[font.split(\",\")[0]] = dimensions // use only first name, i.e w/o fallback\r\n\t\t\treturn acc\r\n\t\t}, {})\r\n\t\tspan.style.font = \"\" // reset\r\n\r\n\t\t// test validity\r\n\t\tlet baseStyle = \"monospace\"\r\n\t\tlet wValue = base[baseStyle].width,\r\n\t\t\thValue = base[baseStyle].height\r\n\t\tlet wType = typeFn(wValue)\r\n\t\tlet hType = typeFn(hValue)\r\n\t\tif (\"number\" !== wType || \"number\" !== hType) {\r\n\t\t\ttry {document.getElementById(\"font-fingerprint\").remove()} catch(e) {}\r\n\t\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc)\r\n\t\t\taDebug.push(sb + \"type error: \".padStart(pad) + sc + wType +\" x \"+ hType +\"<br>\")\r\n\t\t\toResults[key] = zNA\r\n\t\t\ttry_somethingelse()\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tlet isDetected = false\r\n\t\tfntTest.forEach(font => {\r\n\t\t\tisDetected = false // have we found it\r\n\t\t\tfntControl.forEach(basefont => {\r\n\t\t\t\tif (isDetected) {return}\r\n\t\t\t\tconst family = \"'\"+ font +\"', \"+ basefont\t\r\n\t\t\t\tspan.style.setProperty('--font', family)\r\n\t\t\t\tconst style = getComputedStyle(span)\r\n\t\t\t\tconst dimensions = getDimensions(span, style)\r\n\t\t\t\tbasefont = basefont.split(\",\")[0] // switch to short generic name\r\n\t\t\t\tif (dimensions.width != base[basefont].width || dimensions.height != base[basefont].height) {\r\n\t\t\t\t\tdetectedFonts.add(font)\r\n\t\t\t\t\tisDetected = true // we're only testing one method\r\n\t\t\t\t}\r\n\t\t\t\treturn\r\n\t\t\t})\r\n\t\t})\r\n\r\n\t\tlet aFonts = [...detectedFonts]\r\n\t\t// testing\r\n\t\t//aFonts = ['Dancing Script','Roboto','-apple-system','MS Shell Dlg \\\\32']\r\n\t\t//aFonts.push(fntFake)\r\n\t\t//aFonts = ['MS Shell Dlg \\\\32']\r\n\t\t//aFonts = ['-apple-system']\r\n\t\t//aFonts = ['-apple-system','MS Shell Dlg \\\\32']\r\n\t\t//aFonts = ['Dancing Script','Roboto']\r\n\t\t//aFonts = ['Roboto']\r\n\t\t//aFonts = ['Dancing Script']\r\n\t\t//aFonts = []\r\n\r\n\t\taFonts.sort()\r\n\t\tlet len = aFonts.length, display, value\r\n\r\n\t\tif (len == 1) {\r\n\t\t\tlet fnt0 = aFonts[0]\r\n\t\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc + fnt0)\r\n\t\t\tif (fnt0 == \"MS Shell Dlg \\\\32\") { value = \"windows\"\r\n\t\t\t} else if (fnt0 == \"-apple-system\") {value = \"mac\"}\r\n\t\t\tif (value !== undefined) {\r\n\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (len == 2) {\r\n\t\t\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc + aFonts.join(\", \"))\r\n\t\t\t} else {\r\n\t\t\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc + (aFonts.length ? len : \"none\") + \" detected\")\r\n\t\t\t\taFonts.forEach(function(f) {\r\n\t\t\t\t\taDebug.push(\"\".padStart(pad) + f)\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (aFonts.join() == \"Dancing Script,Roboto\" || aFonts.join() == \"Dancing Script\" || aFonts.join() == \"Roboto\") {\r\n\t\t\tif (oResults[\"01\"] == \"android\" && oResults[\"03\"] == \"Roboto\") {\r\n\t\t\t\tvalue = \"android\"\r\n\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value + sg +\" [see desktop + widget test]\"+ sc\r\n\t\t\t} else if (oResults[\"01\"] !== \"desktop\" && oResults[\"03\"] == \"Roboto\" && aFonts.includes('Dancing Script')) {\r\n\t\t\t\tvalue = \"android\"\r\n\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + value + sg +\" [Dancing Script + widget test]\"+ sc\r\n\t\t\t} else {\r\n\t\t\t\tvalue = aFonts.join()\r\n\t\t\t\tdisplay = s13 + \"result: \".padStart(pad) + sc + \"maybe android\"+ sc\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (len == 0) {\r\n\t\t\tif (oResults[\"01\"] == \"desktop\" && oResults[\"03\"] !== \"windows\" && oResults[\"03\"] !== \"mac\") {\r\n\t\t\t\tvalue = \"linux\"\r\n\t\t\t\tdisplay = sg + \"result: \".padStart(pad) + sc + \"linux\"+ sc +\" [see desktop + widget test]\"+ sc\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (aFonts.includes(fntFake)) {\r\n\t\t\t// also covers all fonts returned\r\n\t\t\tvalue = zNA\r\n\t\t\tdisplay = sb + \"result: \".padStart(pad) + sc + \"fake font detected\"\r\n\t\t} else if ( aFonts.includes(\"-apple-system\") && aFonts.includes('MS Shell Dlg \\\\32') ) {\r\n\t\t\tvalue = zNA\r\n\t\t\tdisplay = sb + \"result: \".padStart(pad) + sc + \"windows and mac detected\"\r\n\t\t}\r\n\r\n\t\tif (display == undefined) {\r\n\t\t\tvalue = (len == 0 ? \"none\" : aFonts.join(\", \"))\r\n\t\t\tdisplay = s13 + \"result: \".padStart(pad) + sc + \"pending\"+ sc\r\n\t\t}\r\n\t\tif (display !== undefined) {\r\n\t\t\taDebug.push(display +\"<br>\")\r\n\t\t\toResults[key] = value\r\n\t\t\ttry_somethingelse()\r\n\t\t\treturn\r\n\t\t}\r\n\t} catch(e) {\r\n\t\ttry {document.getElementById(\"font-fingerprint\").remove()} catch(e) {}\r\n\t\taDebug.push(s13 + \"fonts: \".padStart(pad) + sc)\r\n\t\taDebug.push(sb + \"error: \".padStart(pad) + sc)\r\n\t\taDebug.push(\"<div class='oscenter faint'>\"+ e +\"</div>\")\r\n\t\toResults[key] = zNA\r\n\t\ttry_somethingelse()\r\n\t}\r\n}\r\n\r\nfunction set_element(id, fntSize, fntString) {\r\n\tdocument.getElementById(id).innerHTML = `\r\n\t\t<style>\r\n\t\t#${id}-detector {\r\n\t\t\t--font: '';\r\n\t\t\tposition: absolute !important;\r\n\t\t\tleft: -9999px!important;\r\n\t\t\tfont-size: ` + fntSize + ` !important;\r\n\t\t\tfont-style: normal !important;\r\n\t\t\tfont-weight: normal !important;\r\n\t\t\tletter-spacing: normal !important;\r\n\t\t\tline-break: auto !important;\r\n\t\t\tline-height: normal !important;\r\n\t\t\ttext-transform: none !important;\r\n\t\t\ttext-align: left !important;\r\n\t\t\ttext-decoration: none !important;\r\n\t\t\ttext-shadow: none !important;\r\n\t\t\twhite-space: normal !important;\r\n\t\t\tword-break: normal !important;\r\n\t\t\tword-spacing: normal !important;\r\n\t\t\t/* in order to test scrollWidth, clientWidth, etc. */\r\n\t\t\tpadding: 0 !important;\r\n\t\t\tmargin: 0 !important;\r\n\t\t\t/* in order to test inlineSize and blockSize */\r\n\t\t\twriting-mode: horizontal-tb !important;\r\n\t\t\t/* for transform and perspective */\r\n\t\t\ttransform-origin: unset !important;\r\n\t\t\tperspective-origin: unset !important;\r\n\t\t}\r\n\t\t#${id}-detector::after {\r\n\t\t\tfont-family: var(--font);\r\n\t\t\tcontent: '` + fntString + `';\r\n\t\t}\r\n\t\t</style>\r\n\t\t<span id=\"${id}-detector\"></span>`\r\n}\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/pointerevent.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>pointer & touch events</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\ttable {width: 580px;}\r\n\t\t.reset {\r\n\t\t\tfloat: left;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\theight: 50px;\r\n\t\t\twidth: 110px;\r\n\t\t\tcolor: var(--test0);\r\n\t\t\tborder: 2px solid var(--test7);\r\n\t\t\tcursor: pointer;\r\n\t\t\tmargin-bottom: 20px;\r\n\t\t}\r\n\t\t.pointer {\r\n\t\t\tfloat: left;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\theight: 210px;\r\n\t\t\twidth: 110px;\r\n\t\t\tcolor: var(--test0);\r\n\t\t\tborder: 2px solid var(--test7);\r\n\t\t\tcursor: pointer;\r\n\t\t\tmargin-bottom: 20px;\r\n\t\t}\r\n\t\t.flex-item {\r\n\t\t\ttext-align: center;\r\n\t\t\tmargin: 10px;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#devices\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb7\">\r\n\t\t<col width=\"23%\"><col width=\"77%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">pointer & touch events</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\"></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td>\r\n\t\t\t\t<div class=\"reset\" id=\"reset\"><div class=\"flex=item\" id=\"resettext\">1: START</div></div>\r\n\t\t\t\t<div class=\"pointer\" id=\"target\">\r\n\t\t\t\t\t<div class=\"flex-item\"><br>2: CLICK<br><br>context click<br>(if you can)<br><br>otherwise<br><br>keep stabbing<br>and moving</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"reset\"><div class=\"flex=item\">3: FINISH</div></div>\r\n\t\t\t</td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<div class=\"s6\">OVERALL HASH <span class=\"no_color\" id=\"hash\"></span></div><br></div>\r\n\t\t\t\t<div class=\"s4\"><u>POINTER</u>\r\n\t\t\t\t\t<span class='no_color spaces' id='pointerevents'></span>\r\n\t\t\t\t\t<span class=\"no_color\" id=\"pointerhash\"></span>\r\n\t\t\t\t</div><br>\r\n\t\t\t\t<div class=\"no_color spaces\" id=\"results\"></div>\r\n\t\t\t\t<div class=\"s4\"><u>TOUCH</u>\r\n\t\t\t\t\t<span class='no_color spaces' id='touchevents'></span>\r\n\t\t\t\t\t<span class=\"no_color\" id=\"touchhash\"></span>\r\n\t\t\t\t</div><br>\r\n\t\t\t\t<div class=\"no_color spaces\" id=\"touch\"></div><hr><br>\r\n\t\t\t\t<div class=\"s6 spaces\">  maxTouchPoints <span class=\"no_color\" id=\"maxTouchPoints\"></span></div></div><br>\r\n\t\t\t\t<div class=\"s6\">touch properties <span class=\"no_color spaces\" id=\"touch_properties\"></span></div><br>\r\n\t\t\t\t<div class=\"s4\"><u>EVENTS</u></div><br><div class=\"no_color\" id=\"events\"></div><br>\r\n\t\t\t\t<div class=\"s4\"><u>POINTERRAWUPDATE</u>  <span class=\"no_color spaces\" id='raw'></span></div>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1363508\r\n\r\nlet padlen = 18\r\nlet oTemp = {'pointer':{},'touch':{}}\r\nlet oData = {} // sorted oTemp for display and oFP\r\nlet oFP = {\r\n\t'maxTouchPoints': '',\r\n\t'pointer': {},\r\n\t'pointerrawupdate': '',\r\n\t'touch': {},\r\n\t'touch_properties': '',\r\n}\r\nlet isStart = true, hasKeys = false\r\nlet setEvents = new Set()\r\nlet setRaw = new Set()\r\n\r\n// sorted list: these are all numbers\r\nlet aList = [\r\n\t'clientX','clientY',\r\n\t'force', // float 0.0 (no pressure) - 1.0 (max pressure)\r\n\t'radiusX','radiusY',\r\n\t'rotationAngle',\r\n\t'screenX',\r\n\t'screenY', // RFP = matches clientX/Y\r\n]\r\n// not sorted: so we group display items\r\nlet oList = {\r\n\tpointerId: \"number\", //\r\n\tisPrimary: \"boolean\", // RFP true\r\n\tpressure: \"number\", // RFP: 0 if not active, 0.5 if active\r\n\tmozPressure: \"number\", // RFP should always return 0.5 now\r\n\tpointerType: \"string\", // RFP mouse\r\n\tmozInputSource: \"number\", // RFP should be 1 now\r\n\ttangentialPressure: \"number\", // RFP 0\r\n\ttiltX: \"number\", // RFP 0\r\n\ttiltY: \"number\", // RFP 0\r\n\ttwist: \"number\", // RFP 0\r\n\twidth: \"number\", // RFP 1\r\n\theight: \"number\", // RFP 1\r\n\taltitudeAngle: \"number\",\r\n\tazimuthAngle: \"number\",\r\n}\r\n// events\r\nlet oDataEvents = {\r\n\t'pointer': ['down','enter','leave','move','over','out','up'],\r\n\t'touch': ['cancel','end','move','start'],\r\n}\r\nlet oNonDataEvents = {\r\n\t'mouse': ['down','enter','leave','move','out','over','up'],\r\n\t'pointer': ['cancel'],\r\n\t//'touch': ['cancel'],\r\n}\r\n\r\nfunction finish() {\r\n\t// check all events have been recorded\r\n\t\t// pointer\r\n\tlet expected = oDataEvents.pointer.length\r\n\tlet count = Object.keys(oTemp.pointer).length\r\n\tif (count !== expected) {return}\r\n\t\t// touch\r\n\texpected = oDataEvents.touch.length\r\n\tcount = Object.keys(oTemp.touch).length\r\n\tif (hasKeys && count !== expected) {return}\r\n\r\n\t// add pointerrawupdate\r\n\tlet aRaw = Array.from(setRaw)\r\n\toFP.pointerrawupdate = aRaw.join(', ')\r\n\r\n\t// sort & filter object for consistent hashes\r\n\tfor (const k of Object.keys(oTemp).sort()) {\r\n\t\toData[k] = {}\r\n\t\tfor (const n of Object.keys(oTemp[k]).sort()) {oData[k][n] = oTemp[k][n]}\r\n\t}\r\n\t// grab hashes\r\n\tlet hash = mini(oFP.pointer)\r\n\tdom.pointerhash.innerHTML = hash\r\n\r\n\thash = mini(oFP.touch)\r\n\tif (0 == count) {hash = 'none'; oFP.touch = 'none'}\r\n\tdom.touchhash.innerHTML = hash\r\n\r\n\thash = mini(oFP)\r\n\tdom.hash.innerHTML = hash + '<span class=\"spaces\"><br><br>' + json_stringify(oFP) +\"</span>\"\r\n\tconsole.log('fingerprint\\n', oFP)\r\n\tconsole.log('data\\n', oData)\r\n\r\n}\r\n\r\nfunction runtouch(event, type) {\r\n\t// return if we already captured it\r\n\tlet input = 'touch'\r\n\tif (undefined !== oTemp[input][type]) {return}\r\n\taddEvent(type)\r\n\toTemp[input][type] = {}\r\n\r\n\ttry {\r\n\t\tlet touch = event.touches[0]\r\n\t\t// touchcancel + touchend don't have touch data\r\n\t\tif (undefined == touch) {\r\n\t\t\ttouch = event.changedTouches[0]\r\n\t\t}\r\n\t\taList.forEach(function(k){\r\n\t\t\tlet value\r\n\t\t\ttry {\r\n\t\t\t\tvalue = touch[k]\r\n\t\t\t\tif ('number' !== typeof value) {\r\n\t\t\t\t\tvalue = 'err'\r\n\t\t\t\t} else if (Number.isNaN(value)) {\r\n\t\t\t\t\tvalue = 'NaN'\r\n\t\t\t\t}\r\n\t\t\t\toTemp[input][type][k] = value\r\n\t\t\t} catch(e) {\r\n\t\t\t\toTemp[input][type][k] = e.name\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t// display when we have all events\r\n\t\tlet expected = oDataEvents[input].length\r\n\t\tlet count = Object.keys(oTemp[input]).length\r\n\t\tif (count == expected) {\r\n\t\t\tlet oDisplay = [], oTmpFP = {}\r\n\t\t\tlet aStable = [0, 0.5, 1]\r\n\r\n\t\t\taList.forEach(function(k){\r\n\t\t\t\t// if all tests are the same value just display one value\r\n\t\t\t\tlet aSet = new Set(), aArray = []\r\n\t\t\t\tfor (const p of Object.keys(oTemp[input]).sort()) { // sort so array is in order when needed\r\n\t\t\t\t\tlet x = oTemp[input][p][k]\r\n\t\t\t\t\t// force is variable\r\n\t\t\t\t\tif ('force' == k) {if ('number' == typeof x && !aStable.includes(x)) {x = 'float'}}\r\n\t\t\t\t\taSet.add(x)\r\n\t\t\t\t\taArray.push(x)\r\n\t\t\t\t}\r\n\t\t\t\tlet fp = aArray\r\n\t\t\t\tif (1 == aSet.size) {aArray = Array.from(aSet); fp = aArray[0]}\r\n\t\t\t\tlet str = aArray.join(' | ')\r\n\t\t\t\toDisplay.push(s6 + k.padStart(padlen) +\": \"+ sc + str)\r\n\t\t\t\toTmpFP[k] = fp\r\n\t\t\t})\r\n\t\t\t// modify the FP by replacing screen/clientX/Y with booleans for valid matches\r\n\t\t\tlet aCoords = ['X','Y']\r\n\t\t\taCoords.forEach(function(k){\r\n\t\t\t\tlet isMatch = false, isAValid = true, isBValid = true\r\n\t\t\t\tlet A = oTmpFP['client' +k], B = oTmpFP['screen'+ k]\r\n\t\t\t\tlet typeA = typeof A, typeB = typeof B\r\n\t\t\t\tif ('object' == typeA) {\r\n\t\t\t\t\tA.forEach(function(m){if ('number' !== typeof m) {isAValid = false}})\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif ('number' !== typeA) {isAValid = false}\r\n\t\t\t\t}\r\n\t\t\t\tif ('object' == typeB) {\r\n\t\t\t\t\tB.forEach(function(m){if ('number' !== typeof m) {isBValid = false}})\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif ('number' !== typeB) {isAValid = false}\r\n\t\t\t\t}\r\n\t\t\t\tif (isAValid && isBValid) {isMatch = mini(A) === mini(B)}\r\n\t\t\t\t// remove\r\n\t\t\t\tdelete oTmpFP['client' +k]\r\n\t\t\t\tdelete oTmpFP['screen' +k]\r\n\t\t\t\t// add\r\n\t\t\t\toTmpFP['client_screen_'+ k +'_match'] = isMatch\r\n\t\t\t})\r\n\t\t\t// sort FP data into overall FP\r\n\t\t\tfor (const k of Object.keys(oTmpFP).sort()) {oFP[input][k] = oTmpFP[k]}\r\n\t\t\tdom.touch.innerHTML = oDisplay.join(\"<br>\") + '<br><br>'\r\n\t\t\tfinish()\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdom.touch.innerHTML = e+'<br><br>'\r\n\t}\r\n}\r\n\r\nfunction run(event, type) {\r\n\t// return if we already captured it\r\n\tlet input = 'pointer'\r\n\tif (undefined !== oTemp[input][type]) {return}\r\n\taddEvent(type)\r\n\toTemp[input][type] = {}\r\n\r\n\t// isPrimary: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary\r\n\t\t// pen/touch can be true or false: as discovered in tests\r\n\tlet oEvent = {}\r\n\r\n\tfor (const k of Object.keys(oList)) {\r\n\t\tlet value\r\n\t\ttry {\r\n\t\t\tvalue = event[k]\r\n\t\t\tlet\texpected = oList[k]\r\n\t\t\tif (typeof value !== expected) {\r\n\t\t\t\tvalue = 'err'\r\n\t\t\t} else if (\"number\" == expected && Number.isNaN(value)) {\r\n\t\t\t\tvalue = 'NaN'\r\n\t\t\t}\r\n\t\t} catch(e) {\r\n\t\t\tvalue = e.name\r\n\t\t}\r\n\t\toEvent[k] = value\r\n\t}\r\n\t// sort\r\n\tfor (const k of Object.keys(oEvent).sort()) {oTemp[input][type][k] = oEvent[k]}\r\n\r\n\t// display when we have all events\r\n\tlet expected = oDataEvents[input].length\r\n\tlet count = Object.keys(oTemp[input]).length\r\n\tif (count == expected) {\r\n\t\tlet oDisplay = [], oTmpFP = {}\r\n\t\tlet aStable = [0, 0.5, 1]\r\n\r\n\t\tlet lines = [\"pressure\", \"pointerType\", \"tangentialPressure\", \"altitudeAngle\"]\r\n\t\tlet divider = \"<span class='faint'>\" + \"----------\".padStart(padlen) +\"--------\"+ sc\r\n\t\tfor (const k of Object.keys(oList)) {\r\n\t\t\tif (lines.includes(k)) {oDisplay.push(divider)}\r\n\r\n\t\t\t// if all tests are the same value just display one value\r\n\t\t\tlet aSet = new Set(), aArray = []\r\n\t\t\tfor (const p of Object.keys(oTemp[input]).sort()) { // sort so array is in order when needed\r\n\t\t\t\tlet x = oTemp[input][p][k]\r\n\t\t\t\t// active pen pressure is variable between 0-1\r\n\t\t\t\tif ('pressure' == k || 'mozPressure' == k) {\r\n\t\t\t\t\tif ('number' == typeof x && !aStable.includes(x)) {x = 'float'}\r\n\t\t\t\t}\r\n\t\t\t\taSet.add(x)\r\n\t\t\t\taArray.push(x)\r\n\t\t\t}\r\n\t\t\tlet fp = aArray\r\n\t\t\tif (1 == aSet.size) {aArray = Array.from(aSet); fp = aArray[0]}\r\n\t\t\tlet str = aArray.join(' | ')\r\n\t\t\t// tweak the fp width/height for less entropy/stability (e.g touch)\r\n\t\t\t\t// but still show real values in display str\r\n\t\t\tif ('width' == k || 'height' == k) {\r\n\t\t\t\tif ('object' == typeof fp) {fp = 'mixed'}\r\n\t\t\t}\r\n\r\n\t\t\toDisplay.push(s6 + k.padStart(padlen) +\": \"+ sc + str)\r\n\t\t\toTmpFP[k] = fp\r\n\t\t}\r\n\t\t// sort FP data into overall FP\r\n\t\tfor (const k of Object.keys(oTmpFP).sort()) {oFP[input][k] = oTmpFP[k]}\r\n\t\tdom.results.innerHTML = oDisplay.join(\"<br>\") + '<br><br>'\r\n\t\tfinish()\r\n\t}\r\n}\r\n\r\nfunction addEvent(type) {\r\n\tsetEvents.add(type)\r\n\tlet aEvents = Array.from(setEvents)\r\n\tdom.events.innerHTML = aEvents.join(', ')\r\n}\r\n\r\nfunction reset() {\r\n\toTemp = {'pointer':{},'touch':{}}, oData = {}\r\n\toFP.pointer = {}\r\n\toFP.pointerrawupdate = ''\r\n\toFP.touch = {}\r\n\r\n\t// force mouse out of target area\r\n\tif (isStart) {\r\n\t\tstart()\r\n\t\tdom.resettext.innerHTML = '1: RESET'\r\n\t\tisStart = false\r\n\t} else {\r\n\t\tdom.results = ''\r\n\t\tdom.touch = ''\r\n\t\tdom.events = ''\r\n\t\tdom.raw = ''\r\n\t\tdom.hash = ''\r\n\t\tdom.touchhash = ''\r\n\t\tdom.pointerhash = ''\r\n\t\tsetEvents.clear()\r\n\t\tsetRaw.clear()\r\n\t}\r\n}\r\n\r\nfunction start() {\r\n\tlet target = dom.target\r\n\t// record the event happening\r\n\tfor (const k of Object.keys(oNonDataEvents)) {\r\n\t\tlet list = oNonDataEvents[k]\r\n\t\tlist.forEach(function(type){\r\n\t\t\ttarget.addEventListener(k + type, (event) => {addEvent(k + type)})\r\n\t\t})\r\n\t}\r\n\t// record data on these events\r\n\tfor (const k of Object.keys(oDataEvents)) {\r\n\t\tlet list = oDataEvents[k]\r\n\t\tdom[k +'events'].innerHTML = ' [' + list.join('|') +'] '\r\n\t\tlist.forEach(function(type){\r\n\t\t\tlet str = 'auxclick' == type ? type : k + type\r\n\t\t\tif ('pointer' == k) {\r\n\t\t\t\ttarget.addEventListener(str, (event) => {run(event, str)})\r\n\t\t\t} else if ('touch' == k) {\r\n\t\t\t\ttarget.addEventListener(str, (event) => {runtouch(event, str)})\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\t// add an auxclick\r\n\t// tested, this doesn't seem to reveal/leak anything than all the other pointer events\r\n\t//target.addEventListener('auxclick', (event) => {run(event, 'auxclick')})\r\n\r\n\t// pointerrawupdate\r\n\taddEventListener(\"pointerrawupdate\", (event) => {\r\n\t\tlet data = 'undefined'\r\n\t\tif (event.getCoalescedEvents && event.getCoalescedEvents().length > 1) {\r\n\t\t\tconsole.log(\"Coalesced events:\", event.getCoalescedEvents());\r\n\t\t} else {\r\n\t\t\tdata = event.persistentDeviceId +' '+ event.pointerType\r\n\t\t}\r\n\t\tsetRaw.add(data)\r\n\t\tlet aRaw = Array.from(setRaw)\r\n\t\tdom.raw.innerHTML = aRaw.join(', ')\r\n\t})\r\n}\r\n\r\nfunction run_once() {\r\n\tlet t0 = performance.now()\r\n\tlet hash, display = '', data = {'element': [], 'window': []}, counter = 0\r\n\r\n\t// maxtouchpoints\r\n\ttry {hash = navigator.maxTouchPoints} catch(e){hash = zErr}\r\n\toFP.maxTouchPoints = hash\r\n\tdom.maxTouchPoints.innerHTML = hash\r\n\r\n\t// keys\r\n\ttry {\r\n\t\tlet parser = new DOMParser\r\n\t\tlet doc = parser.parseFromString('<div>', \"text/html\")\r\n\t\tlet htmlElement = doc.body.firstChild\r\n\t\tfor (const key in htmlElement) {\r\n\t\t\tcounter++\r\n\t\t\tif (key.includes('Touch') || key.includes('touch')) {data['element'].push(key +', '+ counter)}\r\n\t\t}\r\n\t\tif (data['element'].length) {\r\n\t\t\thasKeys = true // when we have element keys we have touch events\r\n\t\t} else {\r\n\t\t\tdata['element'] = 'none'\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdata['element'] = e.name\r\n\t}\r\n\r\n\t// properties\r\n\tlet id = 'iframe-window-version'\r\n\tcounter = 0\r\n\ttry {\r\n\t\t// create & append\r\n\t\tlet el = document.createElement('iframe')\r\n\t\tel.setAttribute('id', id)\r\n\t\tel.setAttribute('style', 'display: none')\r\n\t\tdocument.body.appendChild(el)\r\n\t\t// get props\r\n\t\tlet iframe = dom[id]\r\n\t\tlet contentWindow = iframe.contentWindow\r\n\t\tlet props = Object.getOwnPropertyNames(contentWindow)\r\n\t\tprops.forEach(function(item){\r\n\t\t\tcounter++\r\n\t\t\tif (item.includes('Touch') || item.includes('touch')) {data['window'].push(item + ', ' + counter)}\r\n\t\t})\r\n\t\t//let test = props.filter(x => x.includes('ouch'))\r\n\t\t//console.log(test)\r\n\t\tif (data['window'].length == 0) {\r\n\t\t\tdata['window'] = 'none'\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tdata['window'] = e.name\r\n\t}\r\n\tremoveElementFn(id)\r\n\r\n\t//console.log(performance.now() - t0)\r\n\r\n\t// hash\r\n\tif (data['element'].length && 'object' == typeof data['element']) {data.element.sort()}\r\n\tif (data['window'].length && 'object' == typeof data['window']) {data['window'].sort()}\r\n\thash = mini(data)\r\n\tif ('a9139fa7' == hash) {data = 'none'; hash = 'none'} else {display = '<br><br>' + json_stringify(data)}\r\n\toFP.touch_properties = data\r\n\tdom.touch_properties.innerHTML = hash + display\r\n\r\n\r\n}\r\n\r\nlet targetreset = dom.reset\r\ntargetreset.addEventListener(\"pointerdown\", (event) => {reset()})\r\nrun_once()\r\n\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/pointertouchevents.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <title>Pointer+Touch event dump</title>\n  </head>\n  <body>\n    <button>Clear</button>\n    <label><input type=\"checkbox\" id=\"mouse\"> Mouse events</label>\n    <label><input type=\"checkbox\" id=\"move\"> Move events</label>\n    <pre></pre>\n    <script>\nconst pre = document.querySelector(\"pre\");\nconst mouse = document.getElementById(\"mouse\");\nconst move = document.getElementById(\"move\");\n\ndocument.querySelector(\"button\").addEventListener(\"click\", () => {\n  pre.textContent = \"\";\n});\n\nfunction log(t) {\n  pre.textContent = t + \"\\n\" + pre.textContent;\n}\n\nfunction dumpPointer(e) {\n  if (e.pointerType == \"mouse\" && !mouse.checked) {\n    return;\n  }\n  if (e.type == \"pointermove\" && !move.checked) {\n    return;\n  }\n  log(`${e.type} ${e.pointerType} ${e.pointerId} (pressure=${e.pressure}; client=(${e.clientX}, ${e.clientY}) primary=${e.isPrimary})`);\n}\n\nfunction dumpTouch(e) {\n  if (e.type == \"touchmove\" && !move.checked) {\n    return;\n  }\n  const touches = Array.from(e.touches ?? [], t => \"\" + t.identifier).join(\" \");\n  log(`${e.type} [${touches}]`);\n}\n\nfor (const e of [\"cancel\", \"down\", \"enter\", \"leave\", \"move\", \"out\", \"over\", \"up\"]) {\n  document.addEventListener(`pointer${e}`, dumpPointer);\n}\n\nfor (const e of [\"cancel\", \"end\", \"move\", \"start\"]) {\n  document.addEventListener(`touch${e}`, dumpTouch);\n}\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/pr.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>pr: select</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">pluralrules: select\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"perf1st\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm the minimum set of numbers to return maximum entropy in Intl.PluralRules.\r\n\t\t\tThe first test checks all numbers from 0 to 102 inclusive. A second test checks only those numbers the first instance\r\n\t\t\twe saw them, per option (cardinal, ordinal). An empty custom test will instead run the minimal example</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span class=\"btn4 btnfirst\" onClick=\"run()\">[ run ]</span>\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"clearcustom()\">[ clear input ]</span>\r\n\t\t\t\t<br><br>\r\n\t\t\t\t<textarea rows=\"2\" placeholder=\"\" style=\"width: 98%; resize: vertical\" id=\"custom\"></textarea>\r\n\t\t\t\t<br><br><hr>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"numbers2\"></span>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"results2\"></span>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"numbers1\"></span>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"results1\"></span>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"numbers0\"></span>\r\n\t\t\t\t<br><span class=\"spaces\" id=\"results0\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = []\r\n\r\n\r\nlet aFirstCardinal = [],\r\n\taFirstOrdinal = [],\r\n\tmain_buckets = [],\r\n\tuseSupportedOnly = true,\r\n\tperf = \"\",\r\n\tisSupported = false\r\n\r\nfunction clearcustom() {\r\n\tdom.custom.value = \"\"\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.PluralRules.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_test(type) {\r\n\t// vars\r\n\tlet t0 = performance.now()\r\n\tlet aNosCardinal = [], // numbers used\r\n\t\taNosOrdinal = []\r\n\r\n\tlet test_all = [], // result hash + locale code + result\r\n\t\tblinkStr = \"\",\r\n\t\tstr = \"\"\r\n\r\n\tlet\telement = document.getElementById(\"results\" + type)\r\n\tlet\telperf = document.getElementById(\"perf\" + type)\r\n\tlet aTemp = []\r\n\r\n\t// main test\r\n\tif (type == \"0\") {\r\n\t\taTemp = []\r\n\t\tfor (let i=0; i < 101; i++) {aTemp.push(i)}\r\n\t\taNosCardinal = aTemp\r\n\t\taNosOrdinal = aTemp\r\n\t\tmain_buckets = []\r\n\t}\r\n\t// first changes\r\n\tif (type == \"1\") {\r\n\t\taNosCardinal = aFirstCardinal\r\n\t\taNosOrdinal = aFirstOrdinal\r\n\t\t// FF121: 1859752 ICU 74 : lij\r\n\t\t\t// to keep lij unique from it,sc in ordinal (bug?)\r\n\t\tif (!isFF || \"lij\" === Intl.Collator.supportedLocalesOf(\"lij\").join()) {\r\n\t\t\tif (!aNosOrdinal.includes(81)) {\r\n\t\t\t\taNosOrdinal.push(81) // chromium \r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// custom\r\n\tif (type == \"2\") {\r\n\t\tlet value = dom.custom.value\r\n\t\tlet go = false\r\n\t\tvalue = value.trim()\r\n\r\n\t\tif (value == \"\") {\r\n\t\t\t// our minimal example\r\n\t\t\taNosCardinal = [0, 1, 2, 3, 7, 21, 100] // redundant: 4, 6, 11, 20\r\n\t\t\taNosOrdinal = [1, 2, 3, 4, 5, 6, 8, 10, 81] // redundant: 0, 6, 7, 9, 21 || FF147+ we need 6\r\n\t\t\t// display\r\n\t\t\tdom.custom.value = \"[\" + aNosCardinal.join(\", \") +\"]\"\r\n\t\t\t\t+ \"\\n[\" + aNosOrdinal.join(\", \") +\"]\"\r\n\t\t} else {\r\n\t\t\taNosCardinal = []\r\n\t\t\taNosOrdinal = []\r\n\t\t\t// cardinal\r\n\t\t\tlet start1 = value.indexOf(\"[\"),\r\n\t\t\t\tstart2 = value.indexOf(\"]\")\r\n\t\t\tlet str1 = value.slice(start1+1, start2)\r\n\t\t\taTemp = []\r\n\t\t\taTemp = str1.split(\",\")\r\n\t\t\taTemp.forEach(function(item) {\r\n\t\t\t\titem = item.trim()\r\n\t\t\t\tif (item !== \"\") {\r\n\t\t\t\t\titem = item * 1\r\n\t\t\t\t\tif (Number.isInteger(item)) {\r\n\t\t\t\t\t\taNosCardinal.push(item)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// ordinal\r\n\t\t\tvalue = value.slice(start2+1, value.length)\r\n\t\t\tstart1 = value.indexOf(\"[\"),\r\n\t\t\t\tstart2 = value.indexOf(\"]\")\r\n\t\t\tlet str2 = value.slice(start1+1, start2)\r\n\t\t\taTemp = []\r\n\t\t\taTemp = str2.split(\",\")\r\n\t\t\taTemp.forEach(function(item) {\r\n\t\t\t\titem = item.trim()\r\n\t\t\t\tif (item !== \"\") {\r\n\t\t\t\t\titem = item * 1\r\n\t\t\t\t\tif (Number.isInteger(item)) {\r\n\t\t\t\t\t\taNosOrdinal.push(item)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\tif (aNosCardinal.length || aNosOrdinal.length) {go = true}\r\n\t\t}\r\n\r\n\t\tif (go) {\r\n\t\t\t// dedupe\r\n\t\t\taNosCardinal = aNosCardinal.filter(function(item, position) {return aNosCardinal.indexOf(item) === position})\r\n\t\t\taNosOrdinal = aNosOrdinal.filter(function(item, position) {return aNosOrdinal.indexOf(item) === position})\r\n\t\t\t// sort\r\n\t\t\taNosCardinal.sort((a,b) => a-b)\r\n\t\t\taNosOrdinal.sort((a,b) => a-b)\r\n\r\n\t\t}\r\n\t}\r\n\t// always sort the arrays\r\n\taNosCardinal.sort((a,b) => a-b)\r\n\taNosOrdinal.sort((a,b) => a-b)\r\n\r\n\tif (aNosCardinal.length == 0 && aNosOrdinal == 0) {\r\n\t\tlet header = \"CUSTOM TEST\"\r\n\t\tif (type == \"1\") {header = \"<hr><br>FIRST CHANGES ONLY\"}\r\n\t\tdocument.getElementById(\"numbers\" + type).innerHTML = s4 + header + sc\r\n\t\telement.innerHTML = \"<br>there are no numbers to check<br>\"\r\n\t\treturn\r\n\t}\r\n\r\n\tfor (let j=0; j < aLocales.length; j++) {\r\n\t\t// reset cardinal\r\n\t\tlet prevC = \"\", currentC = \"\", tmp_resultC = []\r\n\t\tlet c0 = true, c1 = true, c2 = true, c3 = true, c4 = true, c5 = true\r\n\t\t// reset ordinal\r\n\t\tlet prevO = \"\", currentO = \"\", tmp_resultO = []\r\n\t\tlet o0 = true, o1 = true, o2 = true, o3 = true, o4 = true, o5 = true\r\n\r\n\t\tlet code = aLocales[j]\r\n\t\tlet intlPRcardinal = new Intl.PluralRules(code, {type:\"cardinal\"})\r\n\t\tlet intlPRordinal = new Intl.PluralRules(code, {type:\"ordinal\"})\r\n\r\n\t\t// cardinal\r\n\t\tfor (let k=0; k < aNosCardinal.length; k++) {\r\n\t\t\tlet n = aNosCardinal[k]\r\n\t\t\ttry {\r\n\t\t\t\tcurrentC = intlPRcardinal.select(n)\r\n\t\t\t\tif (type == \"0\") {\r\n\t\t\t\t\t// catch first change only: not EVERY change\r\n\t\t\t\t\tif (c0 && currentC == \"zero\") {c0 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t\tif (c1 && currentC == \"one\") {c1 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t\tif (c2 && currentC == \"two\") {c2 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t\tif (c3 && currentC == \"few\") {c3 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t\tif (c4 && currentC == \"many\") {c4 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t\tif (c5 && currentC == \"other\") {c5 = false; aFirstCardinal.push(n)}\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {\r\n\t\t\t\tcurrentC = \"error\"\r\n\t\t\t}\r\n\t\t\t// record all changes\r\n\t\t\tif (prevC !== currentC) {\r\n\t\t\t\ttmp_resultC.push(n +\": \"+ currentC)\r\n\t\t\t}\r\n\t\t\tprevC = currentC\r\n\t\t}\r\n\t\t// ordinal\r\n\t\tfor (let k=0; k < aNosOrdinal.length; k++) {\r\n\t\t\tlet n = aNosOrdinal[k]\r\n\t\t\ttry {\r\n\t\t\t\tcurrentO = intlPRordinal.select(n)\r\n\t\t\t\tif (type == \"0\") {\r\n\t\t\t\t\t// catch first change only: not EVERY change\r\n\t\t\t\t\tif (o0 && currentO == \"zero\") {o0 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t\tif (o1 && currentO == \"one\") {o1 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t\tif (o2 && currentO == \"two\") {o2 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t\tif (o3 && currentO == \"few\") {o3 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t\tif (o4 && currentO == \"many\") {o4 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t\tif (o5 && currentO == \"other\") {o5 = false; aFirstOrdinal.push(n)}\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {\r\n\t\t\t\tcurrentO = \"error\"\r\n\t\t\t}\r\n\t\t\t// record all changes\r\n\t\t\tif (prevO !== currentO) {\r\n\t\t\t\ttmp_resultO.push(n +\": \"+ currentO)\r\n\t\t\t}\r\n\t\t\tprevO = currentO\r\n\t\t}\r\n\t\t// array: hash-combined + code + resultC + resultO\r\n\t\tlet hashC = mini(tmp_resultC)\r\n\t\tlet hashO = mini(tmp_resultO)\r\n\t\tlet hashCombined = mini(hashC + hashO)\r\n\t\tlet strC = tmp_resultC.join(\", \")\r\n\t\tlet strO = tmp_resultO.join(\", \")\r\n\t\ttest_all.push(hashCombined +\"~\"+ code +\"~\"+ strC +\"~\"+ strO)\r\n\t}\r\n\r\n\t// clean up first changes\r\n\tif (type == \"0\") {\r\n\t\taFirstCardinal = aFirstCardinal.filter(function(item, position) {return aFirstCardinal.indexOf(item) === position})\r\n\t\taFirstCardinal.sort((a,b) => a-b)\r\n\t\taFirstOrdinal = aFirstOrdinal.filter(function(item, position) {return aFirstOrdinal.indexOf(item) === position})\r\n\t\taFirstOrdinal.sort((a,b) => a-b)\r\n\t}\r\n\r\n\tif (type == \"1\" || type == \"2\") {\r\n\t\tlet typename = (type == \"1\" ? \"first changes only\" : \"custom test\")\r\n\t\tlet target = document.getElementById(\"numbers\"+ type)\r\n\t\ttarget.innerHTML = (type == \"1\" ? \"<hr><br>\" : \"\")\r\n\t\t\t+ s4 + typename.toUpperCase() + sc\r\n\t\t\t+ \"<br><br>\"+ s4 +\"[\"+ aNosCardinal.length +\"]\" + sc\r\n\t\t\t+ s12 + \" cardinal: \"+ sc +\"[\"+ aNosCardinal.join(\", \")\r\n\t\t\t+ \"] <br>\"+ s4 +\"[\"+ aNosOrdinal.length +\"]\" + sc\r\n\t\t\t+ s12 + \"  ordinal: \"+ sc +\"[\"+ aNosOrdinal.join(\", \") +\"]\"\r\n\t\t\t+ blinkStr\r\n\t}\r\n\r\n\t// perf\r\n\tif (type == \"1\") {\r\n\t\tdom.perf1st.innerHTML = '1st: '+ Math.round(performance.now()-t0) +\" ms\"\r\n\t} else {\r\n\t\tdom.perf.innerHTML = Math.round(performance.now()-t0) +\" ms\"\r\n\t}\r\n\r\n\t// sort array & loop: get hash + code + result buckets, and code_total\r\n\ttest_all.sort()\r\n\tlet bucket_hash = [], bucket_code = [], bucket_resC = [], bucket_resO = []\r\n\tlet tmp_code = [], nextHash = \"\", code_total = 0\r\n\tfor (let i=0; i < test_all.length; i++) {\r\n\t\tlet part1 = test_all[i].split(\"~\")[0],\r\n\t\t\tpart2 = test_all[i].split(\"~\")[1],\r\n\t\t\tpart3 = test_all[i].split(\"~\")[2],\r\n\t\t\tpart4 = test_all[i].split(\"~\")[3]\r\n\t\t// build code string\r\n\t\ttmp_code.push(part2)\r\n\t\t// grab next item\r\n\t\tif (i < test_all.length - 1) {\r\n\t\t\tnextHash = test_all[(i+1)].split(\"~\")[0]\r\n\t\t} else {\r\n\t\t\tnextHash = \"end\"\r\n\t\t}\r\n\t\t// next hash is diff: write data\r\n\t\tif (nextHash !== part1) {\r\n\t\t\tbucket_hash.push(part1 + s4 +\" [\"+ tmp_code.length +\"]\"+ sc)\r\n\t\t\tbucket_resC.push(part3)\r\n\t\t\tbucket_resO.push(part4)\r\n\t\t\tbucket_code.push(tmp_code.join(\", \"))\r\n\t\t\tcode_total += tmp_code.length\r\n\t\t\ttmp_code = [] // reset tmp_code\r\n\t\t}\r\n\t}\r\n\r\n\t// main\r\n\tif (type == \"0\") {\r\n\t\t// build pretty BEFORE sorting\r\n\t\tlet pretty = []\r\n\t\tfor (let i=0; i < bucket_hash.length; i++) {\r\n\t\t\tlet part1 = s4 +\"hash: \"+ sc + bucket_hash[i]\r\n\t\t\tlet part2 = \"<ul class='main'><li>\" + s12 +\"cardinal: \"+ sc + bucket_resC[i] +\"</li>\"\r\n\t\t\tlet part3 = \"<li>\" + s12 +\"ordinal: \"+ sc + bucket_resO[i] +\"</li>\"\r\n\t\t\tlet part4 = \"<li>\" + s12 +\"locale: \"+ sc + bucket_code[i] +\"</li></ul>\"\r\n\t\t\tpretty.push(part1 + part2 + part3 + part4)\r\n\t\t}\r\n\t\t// remember main details\r\n\t\tmain_buckets = bucket_code\r\n\t\tmain_buckets.sort()\r\n\t\t// output\r\n\t\tstr = code_total + (code_total == aLocales.length ? sg : sb) +\" [match]\"+ sc\r\n\t\tstr = \"<hr><br>\"+ s4 +\"ALL NUMBERS [\"+ aNosCardinal.length +\"]\"+ sc +\"<br>\"\r\n\t\t\t+\"<ul class='main'><li>\"+ s12 +\"unique hashes: \"+ sc + s16 + main_buckets.length +sc + \"</li>\"\r\n\t\t\t+\"<li>\"+ s12 +\" locales hash: \"+ sc + mini(main_buckets) +\"</li>\"\r\n\t\t\t+\"<li>\"+ s12 +\" locale check: \"+ sc + str +\"</li></ul>\"\r\n\t\tdom.numbers0.innerHTML = str\r\n\t\telement.innerHTML = pretty.join(\"\")\r\n\t}\r\n\r\n\t// first changes\r\n\tif (type == \"1\" || type == \"2\") {\r\n\t\t// set vars to compare to main\r\n\t\tbucket_code.sort()\r\n\t\tlet check_count = bucket_code.length\r\n\t\tlet check_hash = mini(bucket_code)\r\n\t\tlet main_hash = mini(main_buckets)\r\n\t\t// append results\r\n\t\tlet matchbad = sb +\" [match]\"+ sc, matchgood = sg +\" [match]\"+ sc\r\n\t\tstr = \"<ul class='main'><li>\" + s12 +\"unique hashes:\"+ sc\r\n\t\tstr += \" \"+ s16 + check_count + sc + (check_count == main_buckets.length ? matchgood : matchbad)\r\n\t\tstr += \"</li><li>\"+ s12 +\" locales hash: \"+ sc + check_hash + (check_hash == main_hash ? matchgood : matchbad)\r\n\t\tstr += \"</li></ul>\"\r\n\t\telement.innerHTML = str\r\n\t\tif (check_hash !== main_hash) {\r\n\t\t\tlet testName = (type == \"1\" ? \"FIRST CHANGES ONLY\": \"CUSTOM TEST\" )\r\n\t\t\tconsole.debug(\"MISMATCH\\n\" + \"101 TEST buckets\\n\", main_buckets, testName +\" buckets\\n\", bucket_code)\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction run() {\r\n\tif (isSupported) {\r\n\t\tperf = \"\"\r\n\t\tdom.perf = \"\"\r\n\t\tdom.numbers2 = \"\"\r\n\t\tdom.numbers1 = \"\"\r\n\t\tdom.numbers0 = \"\"\r\n\t\tdom.results2 = \"\"\r\n\t\tdom.results1 = \"\"\r\n\t\tdom.results0 = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_test(\"0\") // main test\r\n\t\t\trun_test(\"1\") // 1st changes only\r\n\t\t\trun_test(\"2\") // custom\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\ndom.custom.placeholder = \"two arrays [cardinal] then [ordinal] e.g.\"\r\n\t+ \"\\n[0, 1, , ,  7  , 21, 100  ]\"\r\n \t+ \"\\n[1,2,  3  , 5, 8, 45 ,non integers ignored ]\"\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tbuildnav()\r\n\t// support\r\n\ttry {\r\n\t\tlet test = new Intl.PluralRules(undefined)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.numbers2.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = []\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tsetTimeout(function() {\r\n\t\t\trun()\r\n\t\t}, 1)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/prrange.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>pr: selectrange</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">pluralrules: selectrange\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy. Both tests are not \"equal\": ALL\r\n\t\t\tcalculates every single combination of words (using all numbers) which creates an exact number of\r\n\t\t\thashes in <code>selectRange</code>. Languages that share selectRanges can differ in <code>select</code>\r\n\t\t\t(see previous test) and MIN uses minimal numbers, which means languages that share selectRange can now\r\n\t\t\tend up with different word combos, creating more differences. The key is for MIN to match ALL, not exceed it.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t\t<span id=\"maxsettings\">\r\n\t\t\t\t\t<input type=\"checkbox\" name=\"maximum\" style=\"margin: 0; height: 12px\">\r\n\t\t\t\t\t<span class=\"no_color\">test 0-100 (slow... 3-5 secs)</span>\r\n\t\t\t\t</span>\r\n\t\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toLocaleChanges = {},\r\n\toChanges = {},\r\n\taAll = [],\r\n\tisMax = false,\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction generatePairs(max){\r\n\taAll = []\r\n\tfor (let i=0; i <= max; i++) {\r\n\t\tfor (let j=0; j <= max; j++) {\r\n\t\t\taAll.push([i, j])\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.PluralRules.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/selectRange\r\n\t/* using generated pairs _should_ catch all combos of \"words\"\r\n\t\t\tNOTE: blink is very slow and may prompt to kill the script and can hang: tested 100 cardinal but will set at 21 for PoC\r\n\t\t100 = 30 unique hashes\r\n\t\t 21 = 30\r\n\t\thttps://github.com/unicode-org/cldr/blob/main/common/supplemental/pluralRanges.xml\r\n\t*/\r\n\r\n\tlet testC = aAll, testO = aAll\r\n\tif (method == 'min') {\r\n\t\t//test = [[0,1],[0,2],[1,1],[2,2]] // 20 - but not the same locale groupings\r\n\t\t// 50 + 33 items from oChanges (deduped all number pairs per pr option)\r\n\t\ttestC = [\r\n\t\t\t[0,0],[0,1],[0,2],[0,3],[0,4],      [0,6],[0,7],[0,11],[0,20],\r\n\t\t\t[1,0],[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,11],[1,20],\r\n\t\t\t[2,0],[2,1],[2,2],[2,3],[2,4],[2,5],[2,6],[2,7],[2,11],\r\n\t\t\t[3,0],[3,1],[3,2],[3,3],      [3,5],[3,6],[3,7],[3,11],\r\n\t\t\t      [4,1],[4,2],[4,3],[4,4],[4,5],[4,6],[7,7],[7,11],\r\n\t\t\t[11,0],[11,3],[11,11],\r\n\t\t\t[20,0],[20,1],[20,20],\r\n\t\t]\r\n\t\ttestO = [\r\n\t\t\t[0,0],[0,1],[0,2],[0,3],[0,5],[0,6],\r\n\t\t\t[1,1],[1,2],[1,3],[1,5],[1,6],[1,7],[1,11],[1,21],\r\n\t\t\t[2,2],[2,3],[2,4],[2,5],[2,7],\r\n\t\t\t[3,3],[3,4],[3,5],[3,7],\r\n\t\t\t[9,1],[9,3],[9,6],[9,9],\r\n\t\t\t[10,1],[10,2],[10,3],[10,5],[10,7],\r\n\t\t\t[11,0],\r\n\t\t]\r\n\t\t// let's play\r\n\t\t// ok, what am I missing: I can get more than 30 if I exclude some of the 50/33\r\n\t\ttestC = [[0,0],[1,1],[2,1],[2,4],]\r\n\t\ttestO = [[0,0],[0,1],[0,6],[1,1],[1,3],[1,5],[3,3],]\r\n\t}\r\n\r\n\tlet oData = {}, oTempData = {}, oChanges = {} // reset\r\n\toLocaleChanges = {'C': {}, 'O': {}}\r\n\ttry {\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oTmp = {'C': {}, 'O': {}}, prevC = '', prevO = ''\r\n\t\t\t// cardinal\r\n\t\t\tlet formatterC = new Intl.PluralRules(code, {type:'cardinal'})\r\n\t\t\toLocaleChanges['C'][code] = []\r\n\t\t\ttestC.forEach(function(t){\r\n\t\t\t\tlet rC = formatterC.selectRange(t[0], t[1])\r\n\t\t\t\tif (rC !== prevC) {\r\n\t\t\t\t\tlet keyC = formatterC.select(t[0]) +'-'+ formatterC.select(t[1])\r\n\t\t\t\t\tif (oTmp['C'][keyC] == undefined) {\r\n\t\t\t\t\t\toTmp['C'][keyC] = rC\r\n\t\t\t\t\t\toLocaleChanges['C'][code].push([t[0], t[1]]) // changes\r\n\t\t\t\t\t}\r\n\t\t\t\t\tprevC = rC\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// ordinal\r\n\t\t\tlet formatterO = new Intl.PluralRules(code, {type:'ordinal'})\r\n\t\t\toLocaleChanges['O'][code] = []\r\n\t\t\ttestO.forEach(function(t){\r\n\t\t\t\tlet rO = formatterO.selectRange(t[0], t[1])\r\n\t\t\t\tif (rO !== prevO) {\r\n\t\t\t\t\tlet keyO = formatterO.select(t[0]) +'-'+ formatterO.select(t[1])\r\n\t\t\t\t\t// rules are the same between cardinal and ordinal\r\n\t\t\t\t\t// we test ordinal because it gioves us more \"words\", e.g. \"two\" in hebrew\r\n\t\t\t\t\tif (oTmp['O'][keyO] == undefined) {\r\n\t\t\t\t\t\toTmp['O'][keyO] = rO\r\n\t\t\t\t\t\toLocaleChanges['O'][code].push([t[0], t[1]]) // changes\r\n\t\t\t\t\t}\r\n\t\t\t\t\tprevO = rO\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// sort oTmp by keys\r\n\t\t\tlet newObj = {}\r\n\t\t\tfor (const k of Object.keys(oTmp).sort()) {\r\n\t\t\t\tnewObj[k] = {}\r\n\t\t\t\tfor (const j of Object.keys(oTmp[k]).sort()) {\r\n\t\t\t\t\tnewObj[k][j] = oTmp[k][j]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// hash + add\r\n\t\t\tlet hash = mini(newObj)\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {'data': newObj, 'locales': [code]}\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash].locales.push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +': '+ sc + ' try again'\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == 'locales') {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(', ')\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t//console.log(oData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const hash of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[hash]['locales'])\r\n\t\t\tlet localeCount = oData[hash]['locales'].split(',').length\r\n\r\n\t\t\tlet str = ''\r\n\t\t\tfor (const p of Object.keys(oData[hash])) {\r\n\t\t\t\tif (p !== 'locales') {\r\n\t\t\t\t\tfor (const type of Object.keys(oData[hash][p])) {\r\n\t\t\t\t\t\t// type: C(ardinal) or O(rdinal)\r\n\t\t\t\t\t\tlet aTmp = []\r\n\t\t\t\t\t\tfor (const key of Object.keys(oData[hash][p][type])) {\r\n\t\t\t\t\t\t\taTmp.push(s99 + key + ': '+ sc + oData[hash][p][type][key])\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tstr += '<li>'+ s14 + (type == 'C' ? 'cardinal' : 'ordinal') + sc +'<br>' + aTmp.join('<br>') +'</li>'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + hash + sc + s4 + ' ['+ localeCount +']'+ sc\r\n\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t+ '<li>'+ s12 +'L: '+ sc + oData[hash]['locales'] +'</li></ul>'\r\n\t\t\t)\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\" & !isMax) {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"fd71342c\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"deeeb2ad\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"ce9ce45f\") { // FF147+: 30\r\n\t\t\t\t} else if (localesHash == \"d3b22832\") { // FF140-146: 30\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet countC = testC.length, countO = testO.length\r\n\t\tlet testCount = 'all' == method ? countC*2 : (countC + countO) +': '+ testC.length +' cardinal + '+ testO.length +' ordinal'\r\n\t\tlet testInfo = '', testRange = ''\r\n\t\tif (isFF && 'all' == method) {\r\n\t\t\ttestRange = 'nos: 0-'+ (isMax ? '100' : '21') +' | '\r\n\t\t}\r\n\t\ttestInfo = ' [' + testRange + 'tests: '+ testCount + ']'\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc + testInfo\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\r\n\t\t// unique changes\r\n\t\t//console.log(oLocaleChanges)\r\n\t\tfor (const k of Object.keys(oLocaleChanges)) { // cardinal/ordinal\r\n\t\t\tlet tmpChanges = [], strChanges = ''\r\n\t\t\toChanges[k] = []\r\n\t\t\tfor (const l of Object.keys(oLocaleChanges[k])) { // each locale\r\n\t\t\t\tfor (const a of Object.keys(oLocaleChanges[k][l])) { // each array\r\n\t\t\t\t\ttmpChanges.push(oLocaleChanges[k][l][a].join('-'))\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// dedupe\r\n\t\t\ttmpChanges = tmpChanges.filter(function(item, position) {return tmpChanges.indexOf(item) === position})\r\n\t\t\ttmpChanges.forEach(function(c) {\r\n\t\t\t\tlet parts = c.split('-')\r\n\t\t\t\toChanges[k].push([parts[0]*1, parts[1]*1])\r\n\t\t\t\tstrChanges += '['+ parts[0] +','+ parts[1]+'],'\r\n\t\t\t})\r\n\t\t\toChanges[k +'_string'] = strChanges\r\n\t\t}\r\n\t\t//console.log(oChanges)\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\tif ('all' == method) {\r\n\t\t\tisMax = dom.maximum.checked\r\n\t\t\tlet max = isMax ? 100 : 21\r\n\t\t\tgeneratePairs(max)\r\n\t\t}\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\tdom.maximum.checked = false\r\n\tif (!isFF) {\r\n\t\tdom.maxsettings.style.display = 'none'\r\n\t}\r\n\r\n\ttry {\r\n\t\tlet test = new Intl.PluralRules(undefined)\r\n\t\tisSupported = true\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t}\r\n\tlet aListExtra = []\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t// test\r\n\t//list = ['de,german','en,english',]\r\n\t//list = ['uk,ukrainian',]\r\n\r\n\tlegend()\r\n\tif (isSupported) {\r\n\t\tgeneratePairs(21) // default\r\n\t\tsetBtn(\"all\")\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(\"all\")\r\n\t\t}, 100)\r\n\t}\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/readerview.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=400\">\r\n\t<title>reader view</title>\r\n\t<style>\r\n\t\tbody {\r\n\t\t\tbackground-color: #fae5df;\r\n\t\t\twidth: 95%; min-width: 380px; max-width: 680px;\r\n\t\t}\r\n\t\th1 {color: #5f2c3e; font-weight: bold;}\r\n\t\tp {color: #ed5841;}\r\n\t\t.center {\r\n\t\t\ttext-align: center;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<article>\r\n\t\t<h1 id=\"test\">Lorem Ipsum</h1>\r\n\t\t<p class=\"center\"><a class=\"return\" href=\"../index.html#other\">return to TZP index</a></p>\r\n\t\t<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Massa tincidunt nunc pulvinar sapien et. Ultricies tristique nulla aliquet enim. Ac tortor dignissim convallis aenean et tortor at risus viverra. Tellus at urna condimentum mattis pellentesque. Lorem ipsum dolor sit amet consectetur adipiscing elit. Nunc mi ipsum faucibus vitae. Nibh cras pulvinar mattis nunc sed blandit libero volutpat sed. Arcu non sodales neque sodales ut etiam sit. Porta lorem mollis aliquam ut porttitor leo a. Egestas integer eget aliquet nibh. Morbi tincidunt ornare massa eget egestas purus viverra accumsan. A cras semper auctor neque. Venenatis lectus magna fringilla urna porttitor rhoncus dolor purus non. Sapien et ligula ullamcorper malesuada proin libero nunc.</p>\r\n\t\t<p>Augue eget arcu dictum varius duis at consectetur. Pellentesque dignissim enim sit amet venenatis. Magna etiam tempor orci eu lobortis. Gravida neque convallis a cras semper auctor neque. Quis risus sed vulputate odio. Faucibus et molestie ac feugiat sed lectus vestibulum. Feugiat scelerisque varius morbi enim nunc faucibus a. Netus et malesuada fames ac turpis egestas integer eget. Pellentesque id nibh tortor id aliquet lectus proin nibh nisl. Adipiscing commodo elit at imperdiet dui accumsan sit. Nulla pharetra diam sit amet nisl suscipit adipiscing bibendum est. Dignissim cras tincidunt lobortis feugiat vivamus at augue eget. Tristique nulla aliquet enim tortor at auctor urna nunc. Integer enim neque volutpat ac. Viverra suspendisse potenti nullam ac. Volutpat odio facilisis mauris sit amet.</p>\r\n\t\t<p>Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Pellentesque elit ullamcorper dignissim cras. Nulla facilisi cras fermentum odio eu feugiat pretium. Tortor posuere ac ut consequat semper viverra nam. Ridiculus mus mauris vitae ultricies leo integer malesuada nunc. Convallis a cras semper auctor neque. Eu facilisis sed odio morbi quis. Orci dapibus ultrices in iaculis nunc sed augue. Neque sodales ut etiam sit amet nisl purus in mollis. Interdum consectetur libero id faucibus nisl tincidunt. Vitae auctor eu augue ut lectus. Tristique risus nec feugiat in. Vitae congue eu consequat ac felis. Fermentum leo vel orci porta non pulvinar. Tempus iaculis urna id volutpat lacus laoreet non curabitur. Quam adipiscing vitae proin sagittis nisl rhoncus mattis.</p>\r\n\t</article>\r\n</body>\r\n</html>"
  },
  {
    "path": "tests/recursion.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=400\">\n\t<title>recursion</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<style>\n\t\ttable {width: 97%; min-width: 380px; max-width: 480px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#devices\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb7\">\n\t\t<col width=\"40%\"><col width=\"60%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">recursion | stack length\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t\t<div class=\"nav-down\"><span class=\"c perf\" id=\"type\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr>\n\t\t\t<td class=\"padr\"><span class=\"btnfirst btn\" onClick=\"run('worker')\">[ run ]</span> WORKER</td>\n\t\t\t<td><span class='mono faint'>level: <span id=\"worker1\"></span><br>stack: <span id=\"worker2\"></span></span></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"padr\"><span class=\"btnfirst btn\" onClick=\"run('doc')\">[ run ]</span> DOCUMENT</td>\n\t\t\t<td><span class='mono faint'>level: <span id=\"doc1\"></span><br>stack: <span id=\"doc2\"></span></span></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"padr\"><span class=\"btnfirst btn\" onClick=\"run('iframe')\">[ run ]</span> IFRAME</td>\n\t\t\t<td id=\"iframehere\" class=\"mono faint\"></td>\n\t\t</tr>\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nfunction perf(t0) {\n\tdocument.getElementById(\"perf\").innerHTML = Math.round(performance.now() - t0) +\" ms\"\n}\n\nconst get_isRecursion_doc = () => new Promise(resolve => {\n\tlet t0 = performance.now()\n\ttry {\n\t\tlet level = 0\n\t\tfunction recurse() {level++; recurse()}\n\t\ttry {recurse()} catch(e) {}\n\t\tlevel = 0\n\t\ttry {\n\t\t\trecurse()\n\t\t} catch(e) {\n\t\t\t// 2nd test is more accurate/stable\n\t\t\tdocument.getElementById(\"doc1\").innerHTML = level\n\t\t\tdocument.getElementById(\"doc2\").innerHTML = e.stack.toString().length\n\t\t\tperf(t0)\n\t\t\treturn resolve()\n\t\t}\n\t} catch(e) {\n\t\tconsole.error(e)\n\t\tdocument.getElementById(\"doc1\").innerHTML = e.name\n\t\treturn resolve()\n\t}\n})\n\nfunction get_isRecursion_iframe() {\n\tlet t0 = performance.now()\n\tlet iframe\n\tlet id = \"targetiframe\"\n\ttry {\n\t\t// create & append\n\t\tlet el = document.createElement(\"iframe\")\n\t\tel.setAttribute(\"id\", id)\n\t\tel.width = \"200\"\n\t\tel.setAttribute(\"style\", \"border: none\")\n\t\tconst node = document.getElementById(\"iframehere\")\n\t\tnode.appendChild(el)\n\t\t// add iframe\n\t\tiframe = document.getElementById(id)\n\t\tiframe.addEventListener(\"error\", event => {\n\t\t\tdocument.getElementById(\"iframehere\").innerHTML = event.type\n\t\t})\n\t\tiframe.onload = function() {\n\t\t\t// slow AF\n\t\t}\n\t\tiframe.src = \"recursion_iframe.html\"\n\t} catch(e) {\n\t\ttry {iframe.parentNode.removeChild(iframe)} catch(err) {}\n\t\tdocument.getElementById(\"iframehere\").innerHTML = e.name\n\t}\n}\n\nconst get_isRecursion_worker = () => new Promise(resolve => {\n\tlet t0 = performance.now()\n\tconst METRIC = \"isRecursion\"\n\ttry {\n\t\tlet worker = new Worker(\"recursion_worker.js\")\n\t\tworker.addEventListener(\"message\", function(msg) {\n\t\t\tfor (const k of Object.keys(msg.data)) {\n\t\t\t\tdocument.getElementById(k).innerHTML = msg.data[k]\n\t\t\t}\n\t\t\tperf(t0)\n\t\t\tworker.terminate\n\t\t}, false)\n\t\tworker.postMessage(\"\")\n\t} catch(e) {\n\t\tconsole.error(e)\n\t\tdocument.getElementById(\"worker1\").innerHTML = e.name\n\t}\n})\n\nfunction run(type) {\n\n\tdocument.getElementById(\"type\").innerHTML = (type == \"doc\" ? \"document\" : type)\n\tdocument.getElementById(\"perf\").innerHTML = \"\"\n\tif (type == \"iframe\") {\n\t\t// remove iframe\n\t\tlet iframe = document.getElementById(\"targetiframe\")\n\t\ttry {iframe.parentNode.removeChild(iframe)} catch(err) {}\n\t} else {\n\t\tfor (let i=1; i < 3; i++) {\n\t\t\ttry {\n\t\t\t\tdocument.getElementById(type+i).innerHTML = \"\"\n\t\t\t} catch(e) {}\n\t\t}\n\t}\n\t// delay\n\tsetTimeout(function() {\n\t\tif (type == \"doc\") {\n\t\t\tget_isRecursion_doc()\n\t\t} else if (type == \"worker\") {\n\t\t\tget_isRecursion_worker()\n\t\t} else if (type == \"iframe\") {\n\t\t\tget_isRecursion_iframe()\n\t\t}\n\t}, 100)\n\n}\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/recursion_iframe.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"recursion\" content=\"width=300\">\n\t<style>\n\t\t.s14 {color: #b29ddc;}\n\t\t.s18 {color: #dc9db2;}\n\t\t.faint {color: #808080;}\n\t\t.mono {font-family: monospace, \"Courier New\"; font-size: 12px;}\n\t\t.spaces {white-space: pre-wrap;}\n\t\t.top {\n\t\t\tposition: fixed;\n\t\t\ttop: 0px;\n\t\t\tleft: 0px;\n\t\t}\n\t</style>\n</head>\n\n<body>\n<div class=\"top mono\">\n\t<span class='indent faint'>level: <span id=\"doc1\"></span><br>stack: <span id=\"doc2\"></span></span>\n</div>\n\n</body>\n<script>\n'use strict';\n\nconst iframe_function = () => new Promise(resolve => {\n\ttry {\n\t\tlet level = 0\n\t\tfunction recurse() {level++; recurse()}\n\t\ttry {recurse()} catch(e) {}\n\t\tlevel = 0\n\t\ttry {\n\t\t\trecurse()\n\t\t} catch(e) {\n\t\t\tdocument.getElementById(\"doc1\").innerHTML = level\n\t\t\tdocument.getElementById(\"doc2\").innerHTML = e.stack.toString().length\n\t\t\treturn resolve()\n\t\t}\n\t} catch(e) {\n\t\tconsole.error(e)\n\t\tdocument.getElementById(\"doc1\").innerHTML = e.name\n\t\treturn resolve()\n\t}\n})\niframe_function()\n\n</script>\n</html>\n"
  },
  {
    "path": "tests/recursion_worker.js",
    "content": "'use strict';\n\naddEventListener(\"message\", function(e) {\n\tlet data = {\n\t}\n\ttry {\n\t\tlet level = 0\n\t\tfunction recurse() {level++; recurse()}\n\t\ttry {recurse()} catch(e) {}\n\t\tlevel = 0\n\t\ttry {\n\t\t\trecurse()\n\t\t} catch(e) {\n\t\t\tdata[\"worker1\"] = level\n\t\t\tdata[\"worker2\"] = e.stack.toString().length\n\t\t\tself.postMessage(data)\n\t\t}\n\t} catch(e) {\n\t\tconsole.error(e)\n\t\tdata[\"worker1\"] = e.name\n\t\tself.postMessage(data)\n\t}\n}, false)\n"
  },
  {
    "path": "tests/resolvedoptions.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>resolvedoptions</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"250px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">resolvedoptions\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Max entropy in <code>resolvedOptions()</code> properties across Intl constuctors</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\toData = {},\r\n\toTests = {},\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet test = Intl.DateTimeFormat.supportedLocalesOf([code])\r\n\t\t\tif (test.length) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet\theader = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\toData = {}\r\n\tlet oTempData = {}\r\n\tlet spacer = '<br><br>'\r\n\r\n\tfunction get_metrics(code) {\r\n\t\tlet oTest = {}\r\n\t\tfor (const k of Object.keys(oTests)) {\r\n\t\t\toTest[k] = {}\r\n\t\t\tlet metrics = oTests[k]\r\n\t\t\ttry {\r\n\t\t\t\t// set constructor\r\n\t\t\t\tlet constructor\r\n\t\t\t\tif ('collator' == k) {constructor = Intl.Collator(code).resolvedOptions()\r\n\t\t\t\t} else if ('datetimeformat' == k) {constructor = Intl.DateTimeFormat(code).resolvedOptions()\r\n\t\t\t\t} else if ('durationformat' == k) {constructor = new Intl.DurationFormat(code).resolvedOptions()\r\n\t\t\t\t} else if ('listformat' == k) {constructor = new Intl.ListFormat(code).resolvedOptions()\r\n\t\t\t\t} else if ('numberformat' == k) {constructor = new Intl.NumberFormat(code).resolvedOptions()\r\n\t\t\t\t} else if ('pluralrules' == k) {constructor = new Intl.PluralRules(code).resolvedOptions()\r\n\t\t\t\t} else if ('relativetimeformat' == k) {constructor = new Intl.RelativeTimeFormat(code).resolvedOptions()\r\n\t\t\t\t} else if ('segmenter' == k) {constructor = new Intl.Segmenter(code).resolvedOptions()\r\n\t\t\t\t}\r\n\t\t\t\t// get values\r\n\t\t\t\tmetrics.forEach(function(m) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tlet value\r\n\t\t\t\t\t\tif ('hourcycle' == m) {\r\n\t\t\t\t\t\t\tvalue = Intl.DateTimeFormat(code, {hour: \"numeric\"}).resolvedOptions().hourCycle\r\n\t\t\t\t\t\t} else if ('pluralCategories' == m) {\r\n\t\t\t\t\t\t\tvalue = constructor[m].join(', ')\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tvalue = constructor[m]\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\toTest[k][m] = value\r\n\t\t\t\t\t} catch(e) {\r\n\t\t\t\t\t\toTest[k][m] = zErr\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t} catch(e) {\r\n\t\t\t\toTest[k] = zErr\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn oTest\r\n\t}\r\n\r\n\tlet oMin = {\r\n\t\t'collator': ['caseFirst'],\r\n\t\t'datetimeformat': ['calendar','day','hourcycle','month','numberingSystem'],\r\n\t\t'pluralrules': ['pluralCategories'],\r\n\t}\r\n\tlet oMax = {\r\n\t\t'collator': ['caseFirst','collation','ignorePunctuation','numeric','sensitivity','usage'],\r\n\t\t\"datetimeformat\": ['calendar','day','hourcycle','month','numberingSystem','year'],\r\n\t\t/* NOTE: displaynames: https://tc39.es/ecma402/#intl-displaynames-objects\r\n\t\t\t- four types: currency, language, region, script\r\n\t\t\t\t-\te.g. new Intl.DisplayNames(code, {type: 'language'}).resolvedOptions()\r\n\t\t\t- second options parameter\r\n\t\t\t\t- fallback: default is code\r\n\t\t\t\t- style: default is long\r\n\t\t\t\t- languageDisplay (for use with type 'language'): default is dialect\r\n\t\t\t- so nothing is gained here as all codes are indentical\r\n\t\t\t- but we can use this API to get results per locale using 'of' e.g.\r\n\t\t\t\t> let l = new Intl.DisplayNames(code, {type: 'language'}).resolvedOptions()\r\n\t\t\t\t> s.of('en-US') = \"American English\" // code: en-US\r\n\t\t\t\t> s.of('en-US') = \"anglais américain\" // code: fr\r\n\t\t\t\t> and we can of course pass undefined as the code\r\n\t\t*/\r\n\t\t'durationformat': [ // creates 10 buckets on it's own\r\n\t\t\t'days','daysDisplay','hours','hoursDisplay','microseconds','microsecondsDisplay',\r\n\t\t\t'milliseconds','millisecondsDisplay','minutes','minutesDisplay','months','monthsDisplay',\r\n\t\t\t'nanoseconds','nanosecondsDisplay','numberingSystem','seconds','secondsDisplay',\r\n\t\t\t'style','weeks','weeksDisplay','years','yearsDisplay'\r\n\t\t],\r\n\t\t/* listformat: https://tc39.es/ecma402/#listformat-objects\r\n\t\t\t\t- options are defaults\r\n\t\t\t\t- style = long, type = conjunction\r\n\t\t\t\t- i.e user must set them\r\n\t\t*/\r\n\t\t'numberformat': [\r\n\t\t\t'maximumFractionDigits','minimumFractionDigits','minimumIntegerDigits',\r\n\t\t\t'roundingIncrement','roundingMode','roundingPriority','trailingZeroDisplay',\r\n\t\t\t'notation','numberingSystem','signDisplay','style','useGrouping',\r\n\t\t],\r\n\t\t'pluralrules': [\r\n\t\t\t'maximumFractionDigits','minimumFractionDigits','minimumIntegerDigits','pluralCategories',\r\n\t\t\t'roundingIncrement','roundingMode','roundingPriority','trailingZeroDisplay','type'\r\n\t\t],\r\n\t\t'relativetimeformat': ['numberingSystem','numeric','style'],\r\n\t\t'segmenter': ['granularity'],\r\n\t}\r\n\r\n\ttry {\r\n\t\toTests = method == 'all' ? oMax : oMin\r\n\r\n\t\tlet aHashes = []\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oTest = get_metrics(code)\r\n\r\n\t\t\tlet hash = mini(oTest)\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\taHashes.push(hash)\r\n\t\t\t\toTempData[hash] = {\r\n\t\t\t\t\t\"data\": {},\r\n\t\t\t\t\t\"locales\": []\r\n\t\t\t\t}\r\n\t\t\t\tfor (const k of Object.keys(oTest).sort()) {\r\n\t\t\t\t\toTempData[hash][\"data\"][k] = oTest[k]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\toTempData[hash].locales.push(code)\r\n\t\t})\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = oTempData[n]\r\n\t\t}\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t//console.log(oData)\r\n\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet localeCount = oData[k].locales.length\r\n\t\t\tlet str = \"\"\r\n\t\t\tfor (const j of Object.keys(oData[k].data).sort()) {\r\n\t\t\t\tstr += s14 + j + sc // +\": \"+ oData[k].data[j] +\"</li>\"\r\n\t\t\t\tfor (const m of Object.keys(oData[k].data[j]).sort()) {\r\n\t\t\t\t\tstr += \"<li>\"+ s99 + m + sc +\": \"+ oData[k].data[j][m] +\"</li>\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// wrap into details for long lists\r\n\t\t\tif ('all' == method) {str = \"<details><summary>details</summary>\"+ str +\"</details>\"}\r\n\t\t\tdisplaylist.push(\r\n\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc +\"<br>\"\r\n\t\t\t\t\t+ \"<ul class='main'>\"+ str\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k].locales.join(\", \") +\"</li></ul>\"\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"b84cb93a\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"0af83683\") { // FF140-14\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"44faf04f\") { // FF147+: 59\r\n\t\t\t\t} else if (localesHash == \"37fee972\") { // FF140-146: 59\r\n\t\t\t\t} else {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\r\n\t\t// display\r\n\t\tlet display = s4 + Object.keys(oData).length + sc +\" from \"+ s4 + aLocales.length + sc\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\t//reset\r\n\tsetBtn(method)\r\n\tdom.perf = \"\"\r\n\tdom.results = \"\"\r\n\t// delay so users see change and allow paint\r\n\tsetTimeout(function() {\r\n\t\trun_main(method)\r\n\t}, 1)\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tbuildnav()\r\n\t// add additional locales to core locales for this test\r\n\tlet aListExtra = [\r\n\t\t\"ar-ae,arabic (united arabic emirates)\",\r\n\t\t\"ar-il,arabic (israel)\",\r\n\t\t\"ar-sa,arabic (saudi arabia)\",\r\n\t\t\"ckb-ir,central kurdish (iran)\",\r\n\t\t\"en-nz,english (new zealand)\",\r\n\t\t\"es-pr,spanish (puerto rico)\",\r\n\t\t\"es-us,spanish (united states)\",\r\n\t\t\"ff-adlm,fulah (adlam)\",\r\n\t\t\"ff-adlm-gh,fulah (adlam ghana)\",\r\n\t\t\"fr-dz,french (algeria)\",\r\n\t\t\"lrc-iq,northern luri (iraq)\",\r\n\t\t\"ps-pk,pashto (pakistan)\",\r\n\t\t// blink\r\n\t\t'ar-bh,arabic (bahrain)',\r\n\t\t'ar-ma,arabic (morocco)',\r\n\t\t'pa-arab,punjabi (arabic)',\r\n\t\t'ur-in,urdu (india)',\r\n\t\t'uz-af,uzbek (afghanistan)',\r\n\t\t'yo-bj,yoruba (benin)',\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t//list = [\"en,english\"]\r\n\tlegend()\r\n\tsetBtn(\"all\")\r\n\tsetTimeout(function() {\r\n\t\trun_main(\"all\")\r\n\t}, 100)\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/rtf.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>relativetimeformat</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 680px;}\r\n\t\tul.main {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"offscreen\">\r\n\t\t<div id=\"test95a\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-1</div>\r\n\t\t<div id=\"test95b\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-12020-1</div>\r\n\t</div>\r\n\t<div class=\"hidden\">\r\n\t\t<div><input type=\"time\" min=\"14:00:00\" max=\"12:00:00\" value=\"15:00:00\" id=\"test76\"></div>\r\n\t</div>\r\n\r\n\t<table>\r\n\t\t<col width=\"25%\"><col width=\"50%\"><col width=\"25%\">\r\n\t\t<tr><td></td><td colpsan=\"3\"><h2>TorZillaPrint</h2></td><td></td></tr>\r\n\t\t<tr>\r\n\t\t\t<td id=\"navprev\" style=\"text-align: left;\"></td>\r\n\t\t\t<td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td>\r\n\t\t\t<td id=\"navnext\" style=\"text-align: right;\"></td>\r\n\t\t</tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"200px\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">relativetimeformat\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof to confirm minimum tests for maximum entropy:\r\n\t\t\tfrom <code>2</code> numeric options (auto, always), <code>3</code> styles (narrow, short, long),\r\n\t\t\t<code>8</code> time\tunits (second, minute, hour, day, week, month, year, quarter) and\r\n\t\t\t<code>5</code> amounts (-3, -1, 0, 1, 3).</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"bnarrow\" class=\"btn4 btnfirst\" onClick=\"run('narrow')\">[ N ]</span>\r\n\t\t\t\t<span id=\"bshort\" class=\"btn4 btn\" onClick=\"run('short')\">[ S ]</span>\r\n\t\t\t\t<span id=\"blong\" class=\"btn4 btn\" onClick=\"run('long')\">[ L ]</span>\r\n\t\t\t\t<span id=\"ball\" class=\"btn4 btn\" onClick=\"run('all')\">[ ALL ]</span>\r\n\t\t\t\t<span id=\"bmin\" class=\"btn4 btn\" onClick=\"run('min')\">[ MIN ]</span>\r\n\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = gLocales,\r\n\taLegend = [],\r\n\taLocales = [],\r\n\tisSupported = false,\r\n\tlocalesHashAll = \"\" // to compare min to\r\n\r\nfunction log_console(name) {\r\n\tlet hash = mini(sDetail[name])\r\n\tif (name == \"locales\") {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\"+ sDetail[\"locales\"].join(\"\\n\"))\r\n\t} else {\r\n\t\tconsole.log(name +\": \" + hash +\"\\n\", sDetail[name])\r\n\t}\r\n}\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tif (aLegend.length == 0) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet str = list[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tlet go = true\r\n\t\t\tif (isSupported) {\r\n\t\t\t\tgo = Intl.RelativeTimeFormat.supportedLocalesOf([code]).length > 0\r\n\t\t\t}\r\n\t\t\tif (go) {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t)\r\n\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// output\r\n\tlet header = s4 +\"LEGEND [\"+ aLegend.length +\"]\"+ sc +\"<br><br>\"\r\n\tdom.legend.innerHTML = header + aLegend.join(\"<br>\")\r\n}\r\n\r\nfunction get_dayperiod(date, code, option) {\r\n\t// always use h12\r\n\treturn new Intl.DateTimeFormat(code, {hourCycle: \"h12\", dayPeriod: option}).format(date)\r\n}\r\n\r\nfunction run_main(method) {\r\n\tlet t0 = performance.now()\r\n\tlet oData = {}, oTempData = {}\r\n\tlet spacer = \"<br><br>\"\r\n\r\n\ttry {\r\n\t\tlet\tstyles = ['narrow','short','long']\r\n\t\tlet times = [-3,-1,0,1,3]\r\n\t\tlet units = [\"second\",\"minute\",\"hour\",\"day\",\"week\",\"month\",\"quarter\",\"year\"]\r\n\t\tlet minList = []\r\n\t\tlet oOptions = {}\r\n\r\n\t\t// select what to test\r\n\t\tif (method == \"min\") {\r\n\t\t\tstyles = [\"narrow\",\"short\",\"long\"]\r\n\r\n\t\t\t// TZP: en-US\r\n\t\t\t// A: now, in 1s, in 1 second, in 3s, in 1d, in 3d, this qtr., in 0y\r\n\t\t\t// B: today, tomorrow, next wk., next yr.\r\n\t\t\t/*\r\n\t\t\t\tALWAYS\r\n\t\t\t\tN: in 1d | in 0y\r\n\t\t\t\tAUTO\r\n\t\t\t\tN: now | in 1s | in 3s | today | tomorrow | in 3d | next wk. | this qtr. | next yr.\r\n\t\t\t\tL: in 1 second\r\n\t\t\t*/\r\n\r\n\t\t\t// FF65-67 & FF96-110+\r\n\t\t\tminList = [\r\n\t\t\t\t\"autonarrow0second\",  // A: now\r\n\t\t\t\t\"autonarrow1second\",  // A: in 1s\r\n\t\t\t\t\"autolong1second\",    // A: in 1 second\r\n\t\t\t\t\"autonarrow3second\",  // A: in 3s\r\n\t\t\t\t\"autonarrow0day\",     // B: today \r\n\t\t\t\t\"autonarrow1day\",     // B: tomorrow\r\n\t\t\t\t\"alwaysnarrow1day\",   // A: in 1d\r\n\t\t\t\t\"autonarrow3day\",     // A: in 3d\r\n\t\t\t\t\"autonarrow1week\",    // B: next wk.\r\n\t\t\t\t\"autonarrow0quarter\", // A: this qtr.\r\n\t\t\t\t\"alwaysnarrow0year\",  // A: in 0y\r\n\t\t\t\t\"autonarrow1year\",    // B: next yr.\r\n\t\t\t]\r\n\t\t\t// older versions need some help\r\n\t\t\tif (isVer > 67 && isVer < 91) {\r\n\t\t\t\tminList.push(\"autolong1week\") // FF68-90\r\n\t\t\t}\r\n\t\t\tif (isVer > 71 && isVer < 96) {\r\n\t\t\t\tminList.push(\"autoshort-1week\") // FF72-95\r\n\t\t\t}\r\n\r\n/* troubleshooting list\r\n\"autonarrow-3second\",\r\n\"autonarrow-1second\",\r\n\"autonarrow0second\",\r\n\"autonarrow1second\",\r\n\"autonarrow3second\",\r\n\r\n\"autonarrow-3minute\",\r\n\"autonarrow-1minute\",\r\n\"autonarrow0minute\",\r\n\"autonarrow1minute\",\r\n\"autonarrow3minute\",\r\n\r\n\"autonarrow-3hour\",\r\n\"autonarrow-1hour\",\r\n\"autonarrow0hour\",\r\n\"autonarrow1hour\",\r\n\"autonarrow3hour\",\r\n\r\n\"autonarrow-3day\",\r\n\"autonarrow-1day\",\r\n\"autonarrow0day\",\r\n\"autonarrow1day\",\r\n\"autonarrow3day\",\r\n\r\n\"autonarrow-3week\",\r\n\"autonarrow-1week\",\r\n\"autonarrow0week\",\r\n\"autonarrow1week\",\r\n\"autonarrow3week\",\r\n\r\n\"autonarrow-3month\",\r\n\"autonarrow-1month\",\r\n\"autonarrow0month\",\r\n\"autonarrow1month\",\r\n\"autonarrow3month\",\r\n\r\n\"autonarrow-3year\",\r\n\"autonarrow-1year\",\r\n\"autonarrow0year\",\r\n\"autonarrow1year\",\r\n\"autonarrow3year\",\r\n\r\n\"autonarrow-3quarter\",\r\n\"autonarrow-1quarter\",\r\n\"autonarrow0quarter\",\r\n\"autonarrow1quarter\",\r\n\"autonarrow3quarter\",\r\n*/\r\n\r\n\r\n\t\t\tsDetail[\"minlist\"] = minList\r\n\t\t} else {\r\n\t\t\tif (method == \"narrow\") {styles = [\"narrow\"]}\r\n\t\t\tif (method == \"short\") {styles = [\"short\"]}\r\n\t\t\tif (method == \"long\") {styles = [\"long\"]}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\tunits.forEach(function(u){\r\n\t\t\t\t\toOptions[s + u] = true\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t}\r\n\r\n\t\t//test: 3 styles x 5 times x 8 units = 120 results\r\n\t\taLocales.forEach(function(code) {\r\n\t\t\tlet oStyles = {\r\n\t\t\t\t\"narrowalways\": [], \"narrowauto\": [],\r\n\t\t\t\t\"shortalways\": [], \"shortauto\": [],\r\n\t\t\t\t\"longalways\": [], \"longauto\": []\r\n\t\t\t}\r\n\t\t\tstyles.forEach(function(s){\r\n\t\t\t\tlet IntlRTF = new Intl.RelativeTimeFormat(code, {style: s, numeric: \"auto\"})\r\n\t\t\t\tlet IntlRTFn = new Intl.RelativeTimeFormat(code, {style: s, numeric: \"always\"})\r\n\t\t\t\tunits.forEach(function(u){\r\n\t\t\t\t\ttimes.forEach(function(t){\r\n\t\t\t\t\t\tif (method == \"min\") {\r\n\t\t\t\t\t\t\tlet concat = s + t + u\r\n\t\t\t\t\t\t\tif (minList.includes(\"always\"+concat)) {\r\n\t\t\t\t\t\t\t\toStyles[s +\"always\"].push( IntlRTFn.format(t, u) )\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tif (minList.includes(\"auto\"+concat)) {\r\n\t\t\t\t\t\t\t\toStyles[s +\"auto\"].push( IntlRTF.format(t, u) )\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tlet concat = s+t+u\r\n\t\t\t\t\t\t\t//if (code == \"en\") {console.log(concat)}\r\n\t\t\t\t\t\t\toStyles[s +\"always\"].push( IntlRTFn.format(t, u) )\r\n\t\t\t\t\t\t\toStyles[s +\"auto\"].push( IntlRTF.format(t, u) )\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t\tlet hash = mini(oStyles) +\" \" // make numbers sort like strings\r\n\t\t\tif (oTempData[hash] == undefined) {\r\n\t\t\t\toTempData[hash] = {}\r\n\t\t\t\toTempData[hash][\"locales\"] = [code]\r\n\t\t\t\toTempData[hash][\"longalways\"] = oStyles[\"longalways\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"longauto\"] = oStyles[\"longauto\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"shortalways\"] = oStyles[\"shortalways\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"shortauto\"] = oStyles[\"shortauto\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"narrowalways\"] = oStyles[\"narrowalways\"].join(\" | \")\r\n\t\t\t\toTempData[hash][\"narrowauto\"] = oStyles[\"narrowauto\"].join(\" | \")\r\n\t\t\t} else {\r\n\t\t\t\toTempData[hash][\"locales\"].push(code)\r\n\t\t\t}\r\n\t\t})\r\n\t\t// handle empty\r\n\t\tif (Object.keys(oTempData).length == 0) {\r\n\t\t\tdom.results.innerHTML = s4 + method.toUpperCase() +\": \" + sc + \" try again\"\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// order object\r\n\t\tfor (const n of Object.keys(oTempData).sort()) {\r\n\t\t\toData[n] = {}\r\n\t\t\tfor (const p of Object.keys(oTempData[n]).sort()) {\r\n\t\t\t\tif (p == \"locales\") {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p].join(\", \")\r\n\t\t\t\t} else {\r\n\t\t\t\t\toData[n][p] = oTempData[n][p]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tlet localeGroups = [], displaylist = []\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlocaleGroups.push(oData[k][\"locales\"])\r\n\t\t\tlet alwaysN = oData[k][\"narrowalways\"],\r\n\t\t\t\talwaysS = oData[k][\"shortalways\"],\r\n\t\t\t\talwaysL = oData[k][\"longalways\"]\r\n\t\t\tlet autoN = oData[k][\"narrowauto\"],\r\n\t\t\t\tautoS = oData[k][\"shortauto\"],\r\n\t\t\t\tautoL = oData[k][\"longauto\"]\r\n\t\t\tlet localeCount = oData[k][\"locales\"].split(\",\").length\r\n\r\n\t\t\tif (alwaysN.length) {alwaysN = \"<li>\"+ s16 +\"N: \"+ sc + alwaysN +\"</li>\"}\r\n\t\t\tif (alwaysS.length) {alwaysS = \"<li>\"+ s16 +\"S: \"+ sc + alwaysS +\"</li>\"}\r\n\t\t\tif (alwaysL.length) {alwaysL = \"<li>\"+ s16 +\"L: \"+ sc + alwaysL +\"</li>\"}\r\n\r\n\t\t\tif (autoN.length) {autoN = \"<li>\"+ s16 +\"N: \"+ sc + autoN +\"</li>\"}\r\n\t\t\tif (autoS.length) {autoS = \"<li>\"+ s16 +\"S: \"+ sc + autoS +\"</li>\"}\r\n\t\t\tif (autoL.length) {autoL = \"<li>\"+ s16 +\"L: \"+ sc + autoL +\"</li>\"}\r\n\r\n\t\t\tlet strAlways = \"\", strAuto = \"\"\r\n\t\t\tif (alwaysN.length + alwaysS.length + alwaysL.length > 0) {\r\n\t\t\t\tstrAlways = s12 +\"ALWAYS\"+ sc + alwaysN + alwaysS + alwaysL\r\n\t\t\t}\r\n\t\t\tif (autoN.length + autoS.length + autoL.length > 0) {\r\n\t\t\t\tstrAuto = s12 + \"AUTO\"+ sc + autoN + autoS + autoL\r\n\t\t\t}\r\n\t\t\tif (method == \"all\") {\r\n\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\" + \"<details><summary>details</summary>\"+ strAlways + strAuto\r\n\t\t\t\t\t+ \"<li>\"+ \"</details>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\r\n\t\t\t} else {\r\n\t\t\t\tdisplaylist.push(\r\n\t\t\t\t\ts12 + k + sc + s4 + \" [\"+ localeCount +\"]\"+ sc\r\n\t\t\t\t\t+ \"<ul class='main'>\" + strAlways + strAuto\r\n\t\t\t\t\t+ \"<li>\"+ s12 +\"L: \"+ sc + oData[k][\"locales\"] +\"</li></ul>\"\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// hashes + btns\r\n\t\tsDetail[\"results\"] = oData\r\n\t\tlet resultsBtn = \"<span class='btn4 btnc' onClick='log_console(`results`)'>[details]</span>\"\r\n\t\tlet resultsHash = mini(oData)\r\n\t\tlocaleGroups.sort()\r\n\t\tsDetail[\"locales\"] = localeGroups\r\n\t\tlet localesBtn = \"<span class='btn4 btnc' onClick='log_console(`locales`)'>[details]</span>\"\r\n\t\tlet localesHash = mini(localeGroups)\r\n\t\tlet minBtn = \"\"\r\n\t\tif (method == \"min\") {\r\n\t\t\tminBtn = \"<span class='btn12 btnc' onClick='log_console(`minlist`)'>[\"\r\n\t\t\t\t+ minList.length + \" tests]</span>\"\r\n\t\t}\r\n\r\n\t\t/*\r\n\t\tconsole.log(localesHash)\r\n\t\tconsole.log(localeGroups)\r\n\t\tconsole.log(resultsHash)\r\n\t\tconsole.log(oData)\r\n\t\tconsole.log(oTempData)\r\n\t\t//*/\r\n\r\n\t\t// notations\r\n\t\tlet localesMatch = \"\"\r\n\t\tif (method == \"all\") {\r\n\t\t\tlocalesHashAll = localesHash\r\n\t\t\t// notate new if 128+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\t// ignore if non-supported used, which return same as undefined = user's resolved options\r\n\t\t\t\t// results\r\n\t\t\t\tif (resultsHash == \"afdf39f2\") { // FF147+\r\n\t\t\t\t} else if (resultsHash == \"cba6cd42\") { // FF140-146\r\n\t\t\t\t} else {resultsHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t\t// locales\r\n\t\t\t\tif (localesHash == \"e2f93418\") { // FF147+: 256\r\n\t\t\t\t} else if (localesHash == \"0e9d33f9\") { // FF140-14: 250\r\n\t\t\t\t} else if (isFF) {localesHash += ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (method == \"min\") {\r\n\t\t\tlocalesMatch = localesHash == localesHashAll ? green_tick : red_cross\r\n\t\t}\r\n\t\t// display\r\n\t\tlet display = s4 + method.toUpperCase() +\": \"\r\n\t\t\t+\" [\"+ localeGroups.length + sc +\" from \"+ s4 + aLocales.length +\"]\" + sc + \" \" + minBtn\r\n\t\t\t+ spacer + s16 +\"results: \"+ sc + resultsHash +\" \" + resultsBtn +\"<br>\"\r\n\t\t\t+ s12 +\"locales: \"+ sc + localesHash +\" \"+ localesBtn + localesMatch + spacer\r\n\t\tdom.results.innerHTML = display + \"<br>\" + displaylist.join(\"<br>\")\r\n\t\t// perf\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch(e) {\r\n\t\tdom.results.innerHTML = s4 + e.name +\": \"+ sc + e.message\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\tif (isSupported) {\r\n\t\t//reset\r\n\t\tsetBtn(method)\r\n\t\tdom.perf = \"\"\r\n\t\tdom.results = \"\"\r\n\t\t// delay so users see change and allow paint\r\n\t\tsetTimeout(function() {\r\n\t\t\trun_main(method)\r\n\t\t}, 1)\r\n\t}\r\n}\r\n\r\nfunction setBtn(method) {\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tPromise.all([\r\n\t\tget_is95(),\r\n\t\tget_isVer(),\r\n\t\tbuildnav()\r\n\t]).then(function(){\r\n\t\ttry {\r\n\t\t\tlet test = new Intl.RelativeTimeFormat(undefined, {style: \"short\", numeric: \"auto\"})\r\n\t\t\tisSupported = true\r\n\t\t} catch(e) {\r\n\t\t\tdom.results.innerHTML = s4 + e.name +\":\" + sc +\" \"+ e.message\r\n\t\t}\r\n\t\t// add additional locales to core locales for this test\r\n\t\tlet aListExtra = [\r\n'ar-ae,arabic (united arabic emirates)',\r\n'ar-dj,arabic (djibouti)',\r\n'bs-cyrl,bosnian (cyrillic)',\r\n'en-at,english (austria)',\r\n'en-au,english (australia)',\r\n'en-ca,english (canada)',\r\n'en-sg,english (singapore)',\r\n'es-ar,spanish (argentina)',\r\n'es-co,spanish (colombia)',\r\n'es-mx,spanish (mexico)',\r\n'es-py,spanish (paraguay)',\r\n'es-us,spanish (united states)',\r\n'ff-adlm,fulah (adlam)',\r\n'fr-ca,french (canada)',\r\n'hi-latn,hindi (latin)',\r\n'kk-cn,kazakh (china)',\r\n'kok-latn,konkani (latin)',\r\n'ks-deva,kashmiri (devanagari)',\r\n'kxv-telu,kuvi (telugu)',\r\n'pa-pk,punjabi (pakistan)',\r\n'ps-pk,pashto (pakistan)',\r\n'pt-gw,portuguese (guinea-bissau)',\r\n'pt-pt,portuguese (portugal)',\r\n'sd-deva,sindhi (devanagari)',\r\n'se-fi,northern sami (finland)',\r\n'shi-latn,tachelhit (latin)',\r\n'sr-ba,serbian (bosnia & herzegovina)',\r\n'sr-latn,serbian (latin)',\r\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\r\n'ur-in,urdu (india)',\r\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\r\n'vai-latn,vai (latin)',\r\n'yo-bj,yoruba (benin)',\r\n'yue-hans,cantonese (simplified)',\r\n\t\t]\r\n\t\tlist = list.concat(aListExtra)\r\n\t\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\t\t//list = ['en']\r\n\t\tlegend()\r\n\t\tif (isSupported) {\r\n\t\t\tsetBtn('all')\r\n\t\t\tsetTimeout(function() {\r\n\t\t\t\trun_main('all')\r\n\t\t\t}, 100)\r\n\t\t}\r\n\t})\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/sanitizing.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=700\">\n\t<title>sanitizing</title>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n  <script src=\"testglobals.js\"></script>\n  <script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 680px;}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#storage\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb6\">\n\t\t<col width=\"37%\"><col width=\"63%\">\n\t\t<thead><tr><th colspan=\"2\">sanitizing</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"no_color\">Checks for specific existing persistent\n\t\t\tlocal web storage entries. If no such data is found, it creates it. If at least one is found,\n\t\t\tit will propagate to any others that do not exist.</span>\n\t\t<br><br><hr>\n\n\t\t</td></tr>\n\t\t<tr><td class=\"padr\"><div>\n\t\t\t\t<div class=\"btn-left\"><span class=\"btn6 btn\" onClick=\"run_sanitize_check()\">[ re-run ]</span></div>\n\t\t\t\t<div>[navigator] cookieEnabled</div></div></td><td class=\"c\" id=\"ckieE\"></td>\n\t\t</tr>\n\t\t<tr><td class=\"padr\">[session] JS 1st party cookie</td><td class=\"faint\" id=\"ckieS\"></td></tr>\n\t\t<tr><td class=\"padr\">[persistent] JS 1st party cookie</td><td class=\"c\" id=\"ckieP\"></td></tr>\n\t\t<tr><td class=\"padr\">localStorage</td><td class=\"c\" id=\"ls\"></td></tr>\n\t\t<tr><td class=\"padr\">indexedDB</td><td class=\"c\" id=\"idb\"></td></tr>\n\t\t<tr><td class=\"padr\">service worker cache</td><td class=\"faint\" id=\"swc\"></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"center\">------</td></tr>\n\t\t<tr><td class=\"padr\">debugging</td><td class=\"c\" id=\"debug\"></td></tr>\n\t</table>\n\t<br>\n<script>\n'use strict';\n\nvar ckie = false, // are these storage mechanisms actually working\n\tls = false, \n\tidb = false,\n\tcookieStored = \"\", // remember any existing values\n\tlsStored = \"\",\n\tidbStored = \"\",\n\tdebug = \"\" // keep track of what is happening\n\n// value to use: new if nothing found\n// otherwise we re-use the existing one, cuz ZOMBIE!!\nvar rndStrX = \"\"\n\nfunction lookup_cookie(name) {\n\tname = name +\"=\"\n\tlet decodedCookie = decodeURIComponent(document.cookie)\n\tlet ca = decodedCookie.split(';')\n\tfor(let i = 0 ; i < ca.length; i++) {\n\t\tlet c = ca[i]\n\t\twhile (c.charAt(0) == ' ') {\n\t\t\tc = c.substring(1)\n\t\t}\n\t\tif (c.indexOf(name) == 0) {\n\t\t\treturn c.substring(name.length, c.length)\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunction check_cookie(name) {\n\tif (ckie == true) {\n\t\t// we already checked that cookies work with no errors\n\t\tcookieStored = lookup_cookie(name)\n\t\tif (cookieStored != \"\") {\n\t\t\t// we found something\n\t\t\tdebug += \"<br>zombie \"+ name +\" cookie found\"\n\t\t\tdom.ckieP.innerHTML = \"<span class='bad'>zombie \"+ name\n\t\t\t\t+\" cookie found:</span> value <span class='bad'>\"+ cookieStored +\"</span>\"\n\t\t} else {\n\t\t\t// nothing found\n\t\t\tdebug += \"<br>zombie \"+ name +\" cookie: nothing found\"\n\t\t}\n\t}\n}\n\nfunction test_cookie() {\n\t// cookie support\n\tif (navigator.cookieEnabled == true) {\n\t\tdebug = \"cookies: enabled\"\n\t\tdom.ckieE = \"enabled\"\n\t\t// random\n\t\tlet rndStrC = rnd_string(\"ptest_\")\n\t\tlet rndStrD = rnd_string(\"\")\n\t\tlet d = new Date()\n\t\td.setTime(d.getTime() + 86400000) // 1 day\n\t\tlet expires = \"expires=\"+ d.toUTCString()\n\t\t// set cookie\n\t\tdocument.cookie = rndStrC +\"=\"+ rndStrD +\";\"+ expires\n\t\t// look it up\n\t\tlet pcookievalue = lookup_cookie(rndStrC)\n\t\tif (pcookievalue != \"\") {\n\t\t\tif (pcookievalue == rndStrD) {\n\t\t\t\t// value matches\n\t\t\t\tdebug += \"<br>cookies: test successful\"\n\t\t\t\tckie = true\n\t\t\t} else {\n\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\tdebug += \"<br>cookies: test failed: values do not match\"\n\t\t\t}\n\t\t} else {\n\t\t\tdebug += \"<br>cookies: test failed\"\n\t\t}\n\t} else {\n\t\tdebug = \"cookies: disabled\"\n\t\tdom.ckieE = \"disabled\"\n\t}\n}\n\nfunction set_zombie_cookie(name, value) {\n\t\t// set cookie\n\t\tlet d = new Date()\n\t\td.setTime(d.getTime() + 86400000) // 1 day\n\t\tlet expires = \"expires=\"+ d.toUTCString()\n\t\tdocument.cookie = name +\"=\"+ value +\";\"+ expires\n\t\t// look it up\n\t\tlet pcookievalue = lookup_cookie(name)\n\t\tif (pcookievalue != \"\") {\n\t\t\tif (pcookievalue == value) {\n\t\t\t\t// value matches\n\t\t\t\tdebug += \"<br>zombie \"+ name +\" cookie: successfully set\"\n\t\t\t\tdom.ckieP.innerHTML = \"<span class='good'>nothing found:</span> setting new zombie \"+ name\n\t\t\t\t\t+\" cookie: value <span class='good'>\"+ value +\"</span>\"\n\t\t\t} else {\n\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\tdebug += \"<br>zombie \"+ name +\" cookie: test failed: values do not match\"\n\t\t\t}\n\t\t} else {\n\t\t\tdebug += \"<br>zombie \"+ name +\" cookie: test failed\"\n\t\t}\n}\n\nfunction check_storage(key) {\n\tif (ls == true) {\n\t\t// we already checked that localStorage works with no errors\n\t\tlsStored = localStorage.getItem(key)\n\t\tdebug += \"<br>zombie \"+ key\n\t\tif (lsStored == null) {\n\t\t\t// nothing found\n\t\t\tdebug += \" key: nothing found\"\n\t\t} else {\n\t\t\t// we found something\n\t\t\tdebug += \" key found\"\n\t\t\tdom.ls.innerHTML = \"<span class='bad'>zombie \"+ key +\" key found:</span> value \"\n\t\t\t\t+\"<span class='bad'>\"+ lsStored +\"</span>\"\n\t\t}\n\t}\n}\n\nfunction test_storage() {\n\tdebug = debug +\"<br>---\"\n\ttry {\n\t\tif (typeof(localStorage) != \"undefined\") {\n\t\t\tdebug += \"<br>localStorage: enabled\"\n\t\t\t// localStorage test\n\t\t\ttry {\n\t\t\t\tlet rndStrE = rnd_string(\"test_\")\n\t\t\t\tlet rndStrF = rnd_string(\"\")\n\t\t\t\tlocalStorage.setItem(rndStrE, rndStrF)\n\t\t\t\tlet lsvalue = localStorage.getItem(rndStrE)\n\t\t\t\tif (lsvalue == null) {\n\t\t\t\t\tdebug += \"<br>localStorage: test failed\"\n\t\t\t\t} else {\n\t\t\t\t\tif (lsvalue == rndStrF) {\n\t\t\t\t\t\t// values match\n\t\t\t\t\t\tls = true\n\t\t\t\t\t\tdebug += \"<br>localStorage: test successful\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\t\t\tdebug += \"<br>localStorage: test failed: values do not match\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tdebug += \"<br>localStorage: test failed: \"+ e.name\n\t\t\t}\n\t\t}\telse {\n\t\t\tdebug += \"<br>localStorage: disabled: undefined\"\n\t\t}\n\t} catch(e) {\n\t\tdebug += \"<br>localStorage: disabled: \"+ e.name\n\t}\n}\n\nfunction set_zombie_storage(key, value) {\n\ttry {\n\t\tlocalStorage.setItem(key, value)\n\t\tlet lsvalue = localStorage.getItem(key)\n\t\tif (lsvalue == null) {\n\t\t\tdebug += \"<br>zombie \"+ key +\" key: failed\"\n\t\t} else {\n\t\t\tif (lsvalue == value) {\n\t\t\t\t// values match\n\t\t\t\tdebug += \"<br>zombie \"+ key +\" key: successfully set\"\n\t\t\t\tdom.ls.innerHTML = \"<span class='good'>nothing found:</span> setting new zombie \"+ key\n\t\t\t\t\t+\" key: value <span class='good'>\"+ value +\"</span>\"\n\t\t\t} else {\n\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\tdebug += \"<br>zombie \"+ key +\" key: failed: values do not match\"\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\tdebug += \"<br>zombie \"+ key +\" key: failed: \"+ e.name\n\t}\n}\n\nfunction check_idb(name, object, id) {\n\t//   name: TZP\n\t// object: zombie\n\t//     id: 1\n\tif (idb == true) {\n\t\t// we already checked that idb works with no errors\n\t\tlet check = indexedDB.open(name)\n\t\t// create objectStore\n\t\tcheck.onupgradeneeded = function(event){\n\t\t\tlet db = event.target.result\n\t\t\tlet store = db.createObjectStore(object, {keyPath: \"id\"})\n\t\t}\n\t\tcheck.onsuccess = function(event) {\n\t\t\tlet db = event.target.result\n\t\t\t// start transaction\n\t\t\tlet transaction = db.transaction(object, \"readwrite\")\n\t\t\tlet store = transaction.objectStore(object)\n\t\t\tlet request = store.get(id)\n\t\t\t// query the data\n\t\t\tlet getStr = store.get(id)\n\t\t\tgetStr.onsuccess = function() {\n\t\t\t\ttry {\n\t\t\t\t\tidbStored = getStr.result.value\n\t\t\t\t\t// we found something\n\t\t\t\t\tdebug += \"<br>zombie \"+ name +\" store found\"\n\t\t\t\t\tdom.idb.innerHTML = \"<span class='bad'>zombie \"+ name +\" store found:</span> value \"\n\t\t\t\t\t\t+\"<span class='bad'>\"+ idbStored +\"<span>\"\n\t\t\t\t} catch (e) {\n\t\t\t\t\t// the value doesn't exist?\n\t\t\t\t\tdebug += \"<br>zombie \"+ name +\" store: nothing found\"\n\t\t\t\t}\n\t\t\t}\n\t\t\t// close transaction\n\t\t\tdb.oncomplete = function() {object.close()}\n\t\t}\n\t}\n}\n\nfunction test_idb() {\n\tdebug = debug +\"<br>---\"\n\t// idb support\n\ttry {\n\t\tif (!window.indexedDB) {\n\t\t\tdebug += \"<br>idb: disabled\"\n\t\t} else {\n\t\t\tdebug += \"<br>idb: enabled\"\n\t\t\t// idb test\n\t\t\ttry {\n\t\t\t\tlet dbIDB = indexedDB.open(\"_testPBMode\")\n\t\t\t\tdbIDB.onerror = function() {\n\t\t\t\t\t// current pb mode\n\t\t\t\t\tdebug += \"<br>idb: test failed: onerror\"\n\t\t\t\t}\n\t\t\t\tdbIDB.onsuccess = function() {\n\t\t\t\t\tidb = true\n\t\t\t\t\tlet rndStrI = rnd_string(\"test_\")\n\t\t\t\t\t// normal mode\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet openIDB = indexedDB.open(rndStrI)\n\t\t\t\t\t\t// create objectStore\n\t\t\t\t\t\topenIDB.onupgradeneeded = function(event){\n\t\t\t\t\t\t\tlet dbObject = event.target.result\n\t\t\t\t\t\t\tlet dbStore = dbObject.createObjectStore(\"testIDB\", {keyPath: \"id\"})\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// test\n\t\t\t\t\t\topenIDB.onsuccess = function(event) {\n\t\t\t\t\t\t\tlet dbObject = event.target.result\n\t\t\t\t\t\t\t// start transaction\n\t\t\t\t\t\t\tlet dbTx = dbObject.transaction(\"testIDB\", \"readwrite\")\n\t\t\t\t\t\t\tlet dbStore = dbTx.objectStore(\"testIDB\")\n\t\t\t\t\t\t\t// add some data\n\t\t\t\t\t\t\tlet rndIndex = rnd_number()\n\t\t\t\t\t\t\tlet rndValue = rnd_string(\"\")\n\t\t\t\t\t\t\tdbStore.put({id: rndIndex, value: rndValue})\n\t\t\t\t\t\t\t// query the data\n\t\t\t\t\t\t\tlet getStr = dbStore.get(rndIndex)\n\t\t\t\t\t\t\tgetStr.onsuccess = function() {\n\t\t\t\t\t\t\t\tif (getStr.result.value == rndValue) {\n\t\t\t\t\t\t\t\t\t// values match\n\t\t\t\t\t\t\t\t\tdebug += \"<br>idb: test successful\"\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\t\t\t\t\t\tdebug += \"<br>idb: test failed: values didn't match\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// close transaction\n\t\t\t\t\t\t\tdbTx.oncomplete = function() {dbObject.close()}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tdebug += \"<br>idb: test failed: \"+ e.name\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\t// blocking cookies or something\n\t\t\t\tdebug += \"<br>idb: failed: .open: \"+ e.name\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\tdebug += \"<br>idb: disabled: \"+ e.name\n\t}\n}\n\nfunction set_zombie_idb(name, object, zid, zvalue) {\n\n\tlet openIDB = indexedDB.open(name)\n\t// create objectStore\n\topenIDB.onupgradeneeded = function(event){\n\t\tlet dbObject = event.target.result\n\t\tlet dbStore = dbObject.createObjectStore(object, {keyPath: \"id\"})\n\t}\n\t// test\n\topenIDB.onsuccess = function(event) {\n\t\tlet dbObject = event.target.result\n\t\t// start transaction\n\t\tlet dbTx = dbObject.transaction(object, \"readwrite\")\n\t\tlet dbStore = dbTx.objectStore(object)\n\t\t// add the data\n\t\tdbStore.put({id: zid, value: zvalue})\n\t\t// query the data\n\t\tlet getStr = dbStore.get(zid)\n\t\tgetStr.onsuccess = function() {\n\t\t\tif (getStr.result.value == zvalue) {\n\t\t\t\t// values match\n\t\t\t\tdebug += \"<br>zombie \"+ name +\" store: successfully set\"\n\t\t\t\tdom.idb.innerHTML = \"<span class='good'>nothing found:</span> setting new zombie \"+ name\n\t\t\t\t\t+\" store: value <span class='good'>\"+ zvalue +\"</span>\"\n\t\t\t} else {\n\t\t\t\t// value doesn't match: this should never happen?\n\t\t\t\tdebug += \"<br>zombie \"+ name +\" store: failed: values do not match\"\n\t\t\t}\n\t\t}\n\t\t// close transaction\n\t\tdbTx.oncomplete = function() {dbObject.close()}\n\t}\n}\n\nfunction populate_data() {\n\tdebug = debug +\"<br>---\"\n\t// lsStored is a pita: can be a null: convert that\n\tif (lsStored == null) {\n\t\tlsStored = \"\"\n\t}\n\n\t// unique string for kicks\n\trndStrX = rnd_string(\"\")\n\t// any zombies: use that instead\n\t// if multiple: they should always be the same: maybe I can check that in the future\n\tif (cookieStored !== \"\") {rndStrX = cookieStored}\n\tif (lsStored !== \"\") {rndStrX = lsStored}\n\tif (idbStored !== \"\") {rndStrX = idbStored}\n\n\t// repopulate\n\tif (ckie == true) {\n\t\tif (cookieStored == \"\") {\n\t\t\tset_zombie_cookie(\"TZP\", rndStrX)\n\t\t}\n\t}\n\tif (ls == true) {\n\t\tif (lsStored == \"\") {\n\t\t\tset_zombie_storage(\"TZP\", rndStrX)\n\t\t}\n\t}\n\tif (idb == true) {\n\t\tif (idbStored == \"\") {\n\t\t\tset_zombie_idb(\"TZP\", \"zombie\", \"1\", rndStrX)\n\t\t}\n\t}\n}\n\nfunction run_sanitize_check() {\n\t// clear\n\tlet items = document.getElementsByClassName(\"c\")\n\tfor(let i=0; i < items.length; i++) {items[i].innerHTML = \"&nbsp\"}\n\t// not coded yet\n\titems = document.getElementsByClassName(\"faint\")\n\tfor (let i=0; i < items.length; i++) {items[i].textContent = \"not coded yet\"}\n\n\t// use a delay so user can see things are cleared\n\tsetTimeout(function() {\n\t\t// reset\n\t\tckie = false, ls = false, idb = false, cookieStored = \"\", lsStored = \"\", idbStored = \"\", debug = \"\"\n\t\t// tests\n\t\ttest_cookie()\n\t\tcheck_cookie(\"TZP\")\n\t\ttest_storage()\n\t\tcheck_storage(\"TZP\")\n\t\ttest_idb()\n\t\tsetTimeout(function() {check_idb(\"TZP\", \"zombie\", \"1\")}, 200)\n\t\t// wait for idb tests to finish\n\t\tsetTimeout(function() {\n\t\t\t// ToDo: use promises, put results into an array, output after promise.all, output perf\n\t\t\tif (ckie == false) {dom.ckieP = zNA}\n\t\t\tif (ls == false) {dom.ls = zNA}\n\t\t\tif (idb == false) {dom.idb = zNA}\n\t\t\tpopulate_data()\n\t\t\t// we have to wait for idb again, godamnit\n\t\t\tsetTimeout(function() {\n\t\t\t\tdom.debug.innerHTML = debug\n\t\t\t}, 700)\n\t\t}, 700)\n\t}, 170)\n}\n\nrun_sanitize_check()\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/screeniframe.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=400\">\n\t<title>screen iframe</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 380px;}\n\t\t#tb1 td {padding-right: 10px;}\n\t\t#vwh {\n\t\t\theight: 100vh;\n\t\t\twidth: 100vw;\n\t\t\tposition: fixed;\n\t\t\tleft: 0;\n\t\t\tborder: 0px;\n\t\t\tz-index: -6000;\n\t\t}\n\t</style>\n</head>\n\n<body>\n\t<div id=\"vwh\"></div>\n\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb1\">\n\t\t<col width=\"30%\"><col width=\"70%\">\n\t\t<thead><tr><th colspan=\"2\">\n\t\t\t<div class=\"nav-title\">screen iframe\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\n\t\t<tr><td></td><td class=\"s1\">top level document</td></tr>\n\t\t<tr><td>screen</td><td class=\"c mono spaces\" id=\"doc0\"></td></tr>\n\t\t<tr><td>available screen</td><td class=\"c mono spaces\" id=\"doc1\"></td></tr>\n\t\t<tr><td>outer</td><td class=\"c mono spaces\" id=\"doc2\"></td></tr>\n\t\t<tr><td>inner</td><td class=\"c mono spaces\" id=\"doc3\"></td></tr>\n\t\t<tr><td>viewport units</td><td class=\"c mono spaces\" id=\"vunits\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td></td><td class=\"s1\">nested iframe | 100vw 100vh</td></tr>\n\t\t<tr><td>screen</td><td class=\"c mono spaces\" id=\"nest0\"></td></tr>\n\t\t<tr><td>available screen</td><td class=\"c mono spaces\" id=\"nest1\"></td></tr>\n\t\t<tr><td>outer</td><td class=\"c mono spaces\" id=\"nest2\"></td></tr>\n\t\t<tr><td>inner</td><td class=\"c mono spaces\" id=\"nest3\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td></td><td class=\"s1\">nested iframe</td></tr>\n\t\t<tr><td>screen</td><td class=\"c mono spaces\" id=\"nest4\"></td></tr>\n\t\t<tr><td>available screen</td><td class=\"c mono spaces\" id=\"nest5\"></td></tr>\n\t\t<tr><td>outer</td><td class=\"c mono spaces\" id=\"nest6\"></td></tr>\n\t\t<tr><td>inner</td><td class=\"c mono spaces\" id=\"nest7\"></td></tr>\n\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nfunction run() {\n\t//let t0 = performance.now()\n\n\ttry {\n\t\tlet vtarget = dom.vwh.getBoundingClientRect()\n\t\tdom.vunits.innerHTML = vtarget.width +' x '+ vtarget.height\n\n\t} catch(e) {\n\t\tdom.wunits = 'error'\n\t}\n\n\n\tconst getNestediFrameWindow = () => {\n\t\tconst numberOfIframes = window.length\n\t\tconst div = document.createElement('div')\n\t\t//div.setAttribute('style', 'display:none') // this line will cause inner to return zero\n\t\tdocument.body.appendChild(div)\n\t\tconst id = 'parent-nest'\n\t\tconst ghost = `\n\t\t\tstyle=\"\n\t\t\t\theight: 100vh;\n\t\t\t\twidth: 100vw;\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: -10000px;\n\t\t\t\tvisibility: hidden;\n\t\t\t\"\n\t\t\t`\n\t\tdiv.innerHTML = `\n\t<div ${ghost} id=\"${id}\">\n\t\t<iframe ${ghost}></iframe>\n\t</div>\n\t`\n\t\tconst el = document.getElementById(id)\n\t\treturn {\n\t\t\tparent: !!el,\n\t\t\tiframeWindow: window[numberOfIframes],\n\t\t\tremove: () => el.parentNode.removeChild(el)\n\t\t}\n\t}\n\n\tconst getNestediFrameWindow2 = () => {\n\t\tconst numberOfIframes = window.length\n\t\tconst div = document.createElement('div')\n\t\t//div.setAttribute('style', 'display:none') // this line will cause inner to return zero\n\t\tdocument.body.appendChild(div)\n\t\tconst id = 'parent-nest2'\n\t\tconst ghost = `\n\t\t\tstyle=\"\n\t\t\t\t/*\n\t\t\t\theight: 100vh;\n\t\t\t\twidth: 100vw;\n\t\t\t\t*/\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: -10000px;\n\t\t\t\tvisibility: hidden;\n\t\t\t\"\n\t\t\t`\n\t\tdiv.innerHTML = `\n\t<div ${ghost} id=\"${id}\">\n\t\t<iframe ${ghost}></iframe>\n\t</div>\n\t`\n\t\tconst el = document.getElementById(id)\n\t\treturn {\n\t\t\tparent: !!el,\n\t\t\tiframeWindow: window[numberOfIframes],\n\t\t\tremove: () => el.parentNode.removeChild(el)\n\t\t}\n\t}\n\n\tdom.doc0 =  screen.width +\" x \"+ screen.height\n\tdom.doc1 =  screen.availWidth +\" x \"+ screen.availHeight\n\tdom.doc2 =  window.outerWidth +\" x \"+ window.outerHeight\n\tdom.doc3 =  window.innerWidth +\" x \"+ window.innerHeight\n\n\tconst nest = getNestediFrameWindow()\n\tlet target = nest.iframeWindow.screen\n\tdom.nest0 =  target.width +\" x \"+ target.height\n\tdom.nest1 =  target.availWidth +\" x \"+ target.availHeight\n\ttarget = nest.iframeWindow.window\n\tdom.nest2 =  target.outerWidth +\" x \"+ target.outerHeight\n\tdom.nest3 =  target.innerWidth +\" x \"+ target.innerHeight\n\tif (nest.parent) {nest.remove()}\n\n\tconst nest2 = getNestediFrameWindow2()\n\ttarget = nest2.iframeWindow.screen\n\tdom.nest4 =  target.width +\" x \"+ target.height\n\tdom.nest5 =  target.availWidth +\" x \"+ target.availHeight\n\ttarget = nest2.iframeWindow.window\n\tdom.nest6 =  target.outerWidth +\" x \"+ target.outerHeight\n\tdom.nest7 =  target.innerWidth +\" x \"+ target.innerHeight\n\tif (nest2.parent) {nest2.remove()}\n\n}\n\nrun()\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/screenorientation.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"width=400\">\n\t<title>screen orientation</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\n\t<script src=\"testglobals.js\"></script>\n\t<script src=\"testgeneric.js\"></script>\n\t<style>\n\t\ttable {width: 380px;}\n\t\t#tb1 td {padding-right: 10px;}\n\t\t@media (-moz-device-orientation:portrait){#cssMoz:after{content:\"portrait\";}}\n\t\t@media (-moz-device-orientation:landscape){#cssMoz:after{content:\"landscape\";}}\n\t\t@media (device-aspect-ratio:1/1){#cssDAR:after{content:\"square\";}}\n\t\t@media (min-device-aspect-ratio:10000/9999){#cssDAR:after{content:\"landscape\";}}\n\t\t@media (max-device-aspect-ratio:9999/10000){#cssDAR:after{content:\"portrait\";}}\n\t</style>\n</head>\n\n<body>\n\t<table>\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#screen\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<table id=\"tb1\">\n\t\t<col width=\"50%\"><col width=\"50%\">\n\t\t<thead><tr><th colspan=\"2\"><div class=\"nav-title\">screen orientation</div></th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"no_color\">Testing screen values and actions:\n\t\t\t<code>screen.orientation</code> change and <code>window</code> resize listeners</span></td></tr>\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\n\n\t\t<tr><td>\n\t\t\t<div class=\"btn-left\"><span class=\"btn1 btn\" onClick=\"run('manual')\">[ re-run ]</span>\n\t\t\t</div>last updated</td><td class='mono' id=\"last\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\n\n\t\t<tr><td>-moz-device-orientation</td><td class='mono' id=\"-moz-device-orientation\"></td></tr>\n\t\t<tr><td>[pseudo] -moz-device-orientation</td><td class='mono' id=\"-moz-device-orientation_css\"></td></tr>\n\t\t<tr><td>device-aspect-ratio</td><td class='mono' id=\"device-aspect-ratio\"></td></tr>\n\t\t<tr><td>[pseudo] device-aspect-ratio</td><td class='mono' id=\"device-aspect-ratio_css\"></td></tr>\n\t\t<tr><td>mozOrientation</td><td class='mono' id=\"mozOrientation\"></td></tr>\n\t\t<tr><td>orientation.angle</td><td class='mono' id=\"orientation.angle\"></td></tr>\n\t\t<tr><td>orientation.type</td><td class='mono' id=\"orientation.type\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td><span class=\"no_color\">hash<span></td><td class='mono' id=\"hash\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\n\t\t<tr><td>[css] -moz-device-orientation</td><td class='mono' id=\"cssMoz\"></td></tr>\n\t\t<tr><td>[css] device-aspect-ratio</td><td class='mono' id=\"cssDAR\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><hr></td></tr>\n\t\t<tr><td>[inner] window size</td><td class='mono' id=\"size\"></td></tr>\n\t\t<tr><td>events</td><td class='mono' id=\"events\"></td></tr>\n\n\t</table>\n\t<br>\n\n<script>\n'use strict';\n\nlet isLoaded = false\nlet option = {day: '2-digit', month: '2-digit', year: 'numeric', hour12: false, hour: '2-digit', minute: 'numeric', second: 'numeric'}\nlet aEvents = []\n\nfunction run(trigger) {\n\n\tlet list = [\n\t\t'-moz-device-orientation', 'device-aspect-ratio', 'mozOrientation', 'orientation.angle', 'orientation.type'\n\t]\n\tlet oData = {}\n\tlet l = 'landscape', p = 'portrait', q = '(orientation: ', s = 'square', a = 'aspect-ratio'\n\n\tlist.forEach(function(item) {\n\t\tlet value, cssID\n\t\ttry {\n\t\t\tif ('-moz-device-orientation' == item) {\n\t\t\t\tcssID = '#cssMoz'\n\t\t\t\tif (window.matchMedia('(-moz-device-orientation:'+ l +')').matches) value = l\n\t\t\t\tif (window.matchMedia('(-moz-device-orientation:'+ p +')').matches) value = p\n\t\t\t} else if ('device-aspect-ratio' == item) {\n\t\t\t\tcssID = '#cssDAR'\n\t\t\t\tif (window.matchMedia('(device-'+ a +':1/1)').matches) value = s\n\t\t\t\tif (window.matchMedia('(min-device-'+ a +':10000/9999)').matches) value = l\n\t\t\t\tif (window.matchMedia('(max-device-'+ a +':9999/10000)').matches) value = p\n\t\t\t} else if ('mozOrientation' == item) {\n\t\t\t\tvalue = screen.mozOrientation\n\t\t\t} else if ('orientation.angle' == item) {\n\t\t\t\tvalue = screen.orientation.angle\n\t\t\t} else {\n\t\t\t\tvalue = screen.orientation.type\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tvalue = e +''\n\t\t}\n\t\toData[item] = value\n\t\tif (cssID !== undefined) {\n\t\t\tlet cssvalue\n\t\t\ttry {\n\t\t\t\tlet target = window.getComputedStyle(document.querySelector(cssID), ':after')\n\t\t\t\tcssvalue = target.getPropertyValue('content')\n\t\t\t\tcssvalue = cssvalue.replace(/['\"]+/g, '')\n\t\t\t} catch(e) {\n\t\t\t\tcssvalue = e+''\n\t\t\t}\n\t\t\toData[item +'_css'] = cssvalue\n\t\t}\n\t})\n\n\tfor (const k of Object.keys(oData)) {dom[k].innerHTML = oData[k]}\n\tlet hash = mini(oData)\n\tlet notation = hash == 'a1de035c' ? sg +' [RFP desktop]' + sc : ''\n\tdom.hash.innerHTML = s1 + hash + sc + notation\n\tdom.size.innerHTML = window.innerWidth +' x '+ window.innerHeight\n\tlet last = (new Date()).toLocaleDateString(\"en\", option)\n\tlast = last.split(', ')[1]\n\tdom.last.innerHTML = last\n\n\t\taEvents.push(trigger + \": \" + last)\n\n\ttry {\n\t\tif (aEvents.length) {\n\t\t\t//limit to four events\n\t\t\tif (aEvents.length > 5) {aEvents = aEvents.slice(-5)}\n\t\t\tdom.events.innerHTML = aEvents.join(\"<br>\")\n\t\t}\n\t} catch(e) {}\n\n}\n\nrun('loaded')\nsetTimeout(function(){\n\tscreen.orientation.addEventListener('change', function(){run('change')})\n\twindow.addEventListener('resize', function(){run('resize')})\n}, 100)\n\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/scroll.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=500\">\r\n\t<title>scrolling</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\ttable {width: 480px;}\r\n\t\t#scrolltarget {\r\n\t\t\tposition: fixed;\r\n\t\t\ttop: 0px;\r\n\t\t\tleft: 0px;\r\n\t\t\theight: 1px;\r\n\t\t\twidth: 1px;\r\n\t\t\toverflow: scroll;\r\n\t\t\tz-index: -999;\r\n\t\t}\r\n\t\t#scrollinner {\r\n\t\t\theight: 3px;\r\n\t\t\twidth: 50px;\r\n\t\t}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<!--<div id=\"scrolltarget\"><div id=\"scrollinner\"></div></div>-->\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#devices\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb7\">\r\n\t\t<col width=\"30%\"><col width=\"70%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">scrolling\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">minimalist (and faster!) smooth scrolling test using an element based on\r\n\t\t\t\t<a class=\"blue\" target =\"_blank\" href=\"https://dlrobertson.com/examples/scrollinto-view-scrollend.html\">Dan Robertson's</a> test.\r\n\t\t\t\t<br><br>prefs<ul>\r\n\t\t\t\t\t<li><code>RFP</code></li>\r\n\t\t\t\t\t<li><code>general.smoothScroll</code></li>\r\n\t\t\t\t\t<li><code>ui.prefersReducedMotion</code> (0 = off, 1 = on)</li>\r\n\t\t\t\t</ul>\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\" class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t<span class=\"btn7 btnfirst\" onClick=\"run(130)\">[ run ]</span> |\r\n\t\t\t\t<span>select length\r\n\t\t\t\t<input type=\"radio\" id=\"len\" name=\"len\" value=\"4\"><label>4</label>\r\n\t\t\t\t<input type=\"radio\" id=\"len\" name=\"len\" value=\"10\"><label>10</label>\r\n\t\t\t\t<input type=\"radio\" id=\"len\" name=\"len\" value=\"50\" checked><label>50</label>\r\n\t\t\t\t<input type=\"radio\" id=\"len\" name=\"len\" value=\"100\"><label>100</label>\r\n\t\t\t\t<input type=\"radio\" id=\"len\" name=\"len\" value=\"600\"><label>600</label>\r\n\t\t\t</span>\r\n\t\t\t<br><br><hr>\r\n\t\t</td></tr>\r\n\t\t<tr><td>smooth scroll &nbsp;</td><td><span class=\"c mono spaces\" id=\"result\"></span></td></tr>\r\n\t\t<tr><td>scroll events &nbsp;</td><td><span class=\"c mono spaces\" id=\"count\"></span></td></tr>\r\n\t\t<tr><td></td><td><span class=\"c mono spaces\" id=\"detail\"></span></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\n// https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/42070\r\n// https://dlrobertson.com/examples/scrollinto-view-scrollend.html\r\n\t// note: drop document scroll: issues with movement, innerwindow size etc\r\n\r\n// ToDo: what does it return on android\r\n\r\nconst get_scroll = () => new Promise(resolve => {\r\n\tlet t0 = performance.now()\r\n\tlet eScrollCount = 0, eEndCount = 0, eEvents = []\r\n\r\n\tfunction exit() {\r\n\t\tdom.detail.innerHTML = eEvents.join(\"<br>\")\r\n\t\treturn resolve(eScrollCount)\r\n\t}\r\n\r\n\tfunction onTargetScroll(e) {\r\n\t\teScrollCount++\r\n\t\tdom.count = eScrollCount + (eScrollCount > 2 ? \" (smooth scroll enabled)\": \"\")\r\n\r\n\t\tlet x = Math.abs(dom.scrollinner.getBoundingClientRect().x)\r\n\t\teEvents.push((eScrollCount+'').padStart(2,' ')\r\n\t\t\t+\" : \"+ (Math.round(performance.now() - t0) +\" ms\").padStart(6,' ')\r\n\t\t\t+\" : \"+ x\r\n\t\t)\r\n\r\n\t\t//dom.detail.innerHTML = eEvents.join(\"<br>\")\r\n\t\te.stopPropagation()\r\n\t\tif (eScrollCount > 2) {exit()}\r\n\t}\r\n\tfunction onTargetScrollend(e) {\r\n\t\teEndCount++\r\n\t\teEvents.push(\"scrollend \"+ eEndCount +\": \"+ Math.round(performance.now() - t0) +\" ms\")\r\n\t\tdom.detail.innerHTML = eEvents.join(\"<br>\")\r\n\t\texit() // we only ever expect 1 scrollend\r\n\t}\r\n\r\n\ttry {\r\n\t\t// recreate element: this means it's always at 0,0 and no need to run setup\r\n\t\ttry {document.getElementById(\"scrolltarget\").remove()} catch(e) {}\r\n\t\tconst doc = document\r\n\t\tconst id = 'scrolltarget'\r\n\t\tconst div = doc.createElement('div')\r\n\t\tdiv.setAttribute('id', id)\r\n\t\tdoc.body.appendChild(div)\r\n\t\tdoc.getElementById(id).innerHTML = \"<div id='scrollinner'></div>\"\r\n\t\t// set element width\r\n\t\tlet len = ((document.querySelector('input[name=\"len\"]:checked').value) * 1) // + 1\r\n\r\n\t\t// don't use len == 3 as this would be 2 scroll events which can periodically also happen without smooth scroll\r\n\t\t\t// and if we don't use 3, then we don't need to reset the posiiton (1,1) because (0,0) is good\r\n\t\tdom.scrollinner.style.width = len +\"px\"\r\n\t\t// reset scroll position\r\n\t\t\t// without this sometimes we can end up with 1 event (with smooth scroll = false)\r\n\t\t\t// we should promise this before we run the test\r\n\t\tscrolltarget.scrollTo(0, 0)\r\n\r\n\t\tsetTimeout(function() {\r\n\t\t\teScrollCount = 0\r\n\t\t\teEndCount = 0\r\n\t\t\teEvents = []\r\n\t\t\t// add listeners\r\n\t\t\tscrolltarget.addEventListener(\"scroll\", onTargetScroll)\r\n\t\t\tscrolltarget.addEventListener(\"scrollend\", onTargetScrollend)\t\r\n\t\t\tscrollinner.scrollIntoView({inline: \"end\", block: \"end\", behavior: \"smooth\"});\r\n\t\t}, 20) // ensure we only get 1 scrollend\r\n\r\n\t} catch(e) {\r\n\t\treturn resolve(e+\"\")\r\n\t}\r\n})\r\n\r\nfunction run() {\r\n\tdom.result = \"\"\r\n\tdom.count = \"\"\r\n\tdom.detail = \"\"\r\n\tlet t0 = performance.now()\r\n\tPromise.all([\r\n\t\tget_scroll()\r\n\t]).then(function(results){\r\n\t\tlet smoothScroll = results[0]\r\n\t\tif (\"number\" === typeof smoothScroll) {\r\n\t\t\tsmoothScroll = (results[0] > 2 ? \"enabled\" : \"disabled\")\r\n\t\t}\r\n\t\tdom.result = smoothScroll + \" [\" + (Math.round(performance.now() - t0) +\" ms]\")\r\n\t})\r\n}\r\nrun()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/supportedlocales.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=800\">\r\n\t<title>supportedlocalesof</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 780px;}\r\n\t\t.btn, .btnfirst {padding-right: 0px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"32%\"><col width=\"68%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">supportedlocalesof\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Return supported locales, including mappings and duplicates,\r\n\t\t\tfor each given Intl constructor. The list is a base list and supplemented in TZP entropy\r\n\t\t\tPoCs, and is not a complete list of all locales.</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span id=\"bC\" class=\"btn4 btnfirst\" onClick=\"run('C')\">[C]</span>\r\n\t\t\t\t<span id=\"bDTF\" class=\"btn4 btn\" onClick=\"run('DTF')\">[DTF]</span>\r\n\t\t\t\t<span id=\"bDN\" class=\"btn4 btn\" onClick=\"run('DN')\">[DN]</span>\r\n\t\t\t\t<span id=\"bDF\" class=\"btn4 btn\" onClick=\"run('DF')\">[DF]</span>\r\n\t\t\t\t<span id=\"bLF\" class=\"btn4 btn\" onClick=\"run('LF')\">[LF]</span>\r\n\t\t\t\t<span id=\"bNF\" class=\"btn4 btn\" onClick=\"run('NF')\">[NF]</span>\r\n\t\t\t\t<span id=\"bPR\" class=\"btn4 btn\" onClick=\"run('PR')\">[PR]</span>\r\n\t\t\t\t<span id=\"bRTF\" class=\"btn4 btn\" onClick=\"run('RTF')\">[RTF]</span>\r\n\t\t\t\t<span id=\"bS\" class=\"btn4 btn\" onClick=\"run('S')\">[S]</span>\r\n\t\t\t\t<span id=\"bALL\" class=\"btn4 btn\" onClick=\"run('ALL')\">[ALL]</span>\r\n\t\t\t\t<input type=\"checkbox\" id=\"optExpanded\" onChange=\"run()\"> expand\r\n\t\t\t\t<br><br><hr><br>\r\n\t\t\t\t<span id =\"results\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar list = [],\r\n\tlistExpanded = [],\r\n\taLegend = [],\r\n\taLegendExpanded = [],\r\n\tlastMethod,\r\n\taLocales = [],\r\n\taLocalesExpanded = [],\r\n\taSummary = [],\r\n\taSupported = [],\r\n\taNotSupported = []\r\n\r\nfunction legend() {\r\n\t// build once\r\n\tlet isExpanded = dom.optExpanded.checked\r\n\tif (!isExpanded && aLegend.length == 0 || isExpanded && aLegendExpanded.length == 0) {\r\n\t\tlet listUsed = isExpanded ? listExpanded : list\r\n\t\tlistUsed.sort()\r\n\t\tfor (let i = 0 ; i < listUsed.length; i++) {\r\n\t\t\tlet str = listUsed[i].toLowerCase()\r\n\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\tif (isExpanded) {\r\n\t\t\t\taLocalesExpanded.push(code)\r\n\t\t\t} else {\r\n\t\t\t\taLocales.push(code)\r\n\t\t\t}\r\n\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t)\r\n\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\tif (isSplit) {\r\n\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t} else {\r\n\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (isExpanded) {\r\n\t\t\t\taLegendExpanded.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t} else {\r\n\t\t\t\taLegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tlet legendUsed = isExpanded ? aLegendExpanded : aLegend\r\n\t// output\r\n\tdom.legend.innerHTML = s4 +\"LEGEND [\"+ legendUsed.length +\"]\"+ sc +\"<br><br>\"+ legendUsed.join(\"<br>\")\r\n}\r\n\r\nfunction run_main(method, isLoopy) {\r\n\tlet t0 = performance.now()\r\n\tlet legend = [], data = [], map = [], nocase = [], nocasemap = [], all = []\r\n\tlet spacer = \"<br><br>\", hashLookup = \"\", lookupSummary = \"\"\r\n\tlet display = []\r\n\tlet isExpanded = dom.optExpanded.checked\r\n\taSupported = []\r\n\taNotSupported = []\r\n\r\n\tfunction getPretty(method) {\r\n\t\tlet pretty = \"\"\r\n\t\tif (method == \"C\") {pretty = \"collator\"\r\n\t\t} else if (method == \"DTF\") {pretty = \"datetimeformat\"\r\n\t\t} else if (method == \"DN\") {pretty = \"displaynames\"\r\n\t\t} else if (method == \"DF\") {pretty = \"durationformat\"\r\n\t\t} else if (method == \"LF\") {pretty = \"listformat\"\r\n\t\t} else if (method == \"NF\") {pretty = \"numberformat\"\r\n\t\t} else if (method == \"PR\") {pretty = \"pluralrules\"\r\n\t\t} else if (method == \"RTF\") {pretty = \"relativetimeformat\"\r\n\t\t} else if (method == \"S\") {pretty = \"segmenter\"\r\n\t\t}\r\n\t\treturn pretty\r\n\t}\r\n\r\n\ttry {\r\n\t\t// hash\r\n\t\tfunction getNotation(hash, method) {\r\n\t\t\tif (!isFF || isExpanded) return \"\"\r\n\t\t\t// only hash notate if 140+\r\n\t\t\tlet notation = \"\"\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\tif (method == \"C\") {\r\n\t\t\t\t\tif (hash == \"cdcd1420\") {notation = \" [FF150+]\" // 120 : 1937541: dropped dz, sa, wae\r\n\t\t\t\t\t} else if (hash == \"50c5b9d7\") {notation = \" [FF147-159]\" // 123 - added 'ba' (bashkir)\r\n\t\t\t\t\t} else if (hash == \"6eb2ef9e\") {notation = \" [FF140-146]\" // 122\r\n\t\t\t\t\t} else { notation = \" \"+ zNEW\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (hash == \"0376e707\") {notation = \" [FF147+]\" // 245\r\n\t\t\t\t\t} else if (hash == \"66c554ac\") {notation = \" [FF140-14]\" // 245\r\n\t\t\t\t\t} else { notation = \" \"+ zNEW\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (notation !== zNEW) notation = s14 + notation + sc\r\n\t\t\t}\r\n\t\t\treturn notation\r\n\t\t}\r\n\t\t// get data\r\n\t\tlet listUsed = isExpanded ? listExpanded : list\r\n\t\tlet aLocalesUsed = isExpanded ? aLocalesExpanded : aLocales\r\n\t\tfunction getSupported(type, method) {\r\n\t\t\tdata = []\r\n\t\t\tmap = []\r\n\t\t\tall = []\r\n\t\t\tfor (let i = 0 ; i < listUsed.length; i++) {\r\n\t\t\t\t// split: code, name\r\n\t\t\t\tlet code = listUsed[i].split(\",\")[0]\r\n\t\t\t\tlet name = listUsed[i].split(\",\")[1]\r\n\t\t\t\tcode = code.toLowerCase()\r\n\r\n\t\t\t\t// test\r\n\t\t\t\tlet test = \"\"\r\n\t\t\t\tif (method == \"C\") {\r\n\t\t\t\t\ttest = Intl.Collator.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"DTF\") {\r\n\t\t\t\t\ttest = Intl.DateTimeFormat.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"DN\") {\r\n\t\t\t\t\ttest = Intl.DisplayNames.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"DF\") {\r\n\t\t\t\t\ttest = Intl.DurationFormat.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"LF\") {\r\n\t\t\t\t\ttest = Intl.ListFormat.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"NF\") {\r\n\t\t\t\t\ttest = Intl.NumberFormat.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"PR\") {\r\n\t\t\t\t\ttest = Intl.PluralRules.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"RTF\") {\r\n\t\t\t\t\ttest = Intl.RelativeTimeFormat.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t} else if (method == \"S\") {\r\n\t\t\t\t\ttest = Intl.Segmenter.supportedLocalesOf([code], {localeMatcher: type})\r\n\t\t\t\t}\r\n\t\t\t\tif (test.length) {\r\n\t\t\t\t\tlet found = test[0].toLowerCase()\r\n\t\t\t\t\tall.push(found +\":\"+ code)\r\n\t\t\t\t\tif (type == \"lookup\") {\r\n\t\t\t\t\t\tnocase.push(found.toLowerCase())\r\n\t\t\t\t\t\tif (code.toLowerCase() !== found.toLowerCase()) {\r\n\t\t\t\t\t\t\tnocasemap.push(code)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (code !== found) {\r\n\t\t\t\t\t\tmap.push(code +\" -> \"+ found)\r\n\t\t\t\t\t\tdata.push(code +\" -> \"+ found)\r\n\t\t\t\t\t\tif ('lookup' == type) {aSupported.push(found.toLowerCase())}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tdata.push(found)\r\n\t\t\t\t\t\tif ('lookup' == type) {aSupported.push(found.toLowerCase())}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// don't sort or remove any dupes\r\n\t\t\tlet hash = mini(data.join())\r\n\r\n\t\t\t// dupes\r\n\t\t\tall.sort()\r\n\t\t\tlet dupes = [], dupesclean = [], tmpCodes = [], tmpCount = 0\r\n\t\t\tlet nextItem = \"\"\r\n\t\t\tfor (let i = 0 ; i < all.length; i++) {\r\n\t\t\t\tlet a = all[i].split(\":\")[0] // found locale\r\n\t\t\t\tlet b = all[i].split(\":\")[1] // tested code\r\n\t\t\t\ttmpCodes.push(b)\r\n\t\t\t\tif (i < all.length - 1) {\r\n\t\t\t\t\tnextItem = all[(i+1)].split(\":\")[0]\r\n\t\t\t\t} else {\r\n\t\t\t\t\tnextItem = \"end\"\r\n\t\t\t\t}\r\n\t\t\t\tif (nextItem !== a) {\r\n\t\t\t\t\tif (tmpCodes.length > 1) {\r\n\t\t\t\t\t\tdupes.push(s14 + a + sc + s4 +\" [\"+ tmpCodes.length +\"] \"+ sc + tmpCodes.join(\", \"))\r\n\t\t\t\t\t\tdupesclean.push(a)\r\n\t\t\t\t\t}\r\n\t\t\t\t\ttmpCodes = []\r\n\t\t\t\t\ttmpCount = 0\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// not in legend\r\n\t\t\tlet notlist = [], notlistclean = [], map2 = []\r\n\t\t\tfor (let i = 0 ; i < map.length; i++) {\r\n\t\t\t\tlet x = map[i].split(\" -> \")[0] // tested\r\n\t\t\t\tlet y = map[i].split(\" -> \")[1] // found\r\n\t\t\t\tif (!aLocalesUsed.includes(y.toLowerCase())) { // case insenstive\r\n\t\t\t\t\tnotlist.push(s16 + y + sc +\" [\"+ x +\"]\")\r\n\t\t\t\t\tnotlistclean.push(y)\r\n\t\t\t\t}\r\n\t\t\t\tmap2.push(s12 + x + sc + \" -> \"+ y)\r\n\t\t\t\tif ('lookup' == type) {aSupported.push(x.toLowerCase())}\r\n\t\t\t}\r\n\t\t\t// color up results\r\n\t\t\tif (!isLoopy) {\r\n\t\t\t\tfor (let i = 0 ; i < data.length; i++) {\r\n\t\t\t\t\tlet r = data[i]\r\n\t\t\t\t\tif (map.includes(r)) {\r\n\t\t\t\t\t\tlet part1 = r.split(\" -> \")[0],\r\n\t\t\t\t\t\t\tpart2 = r.split(\" -> \")[1]\r\n\t\t\t\t\t\tif (dupesclean.includes(part2)) {\r\n\t\t\t\t\t\t\tdata[i] = s12 + part1 +\" -> \"+ sc + s14 + part2 + sc\r\n\t\t\t\t\t\t} else if (notlistclean.includes(part2)) {\r\n\t\t\t\t\t\t\tdata[i] = s12 + part1 +\" -> \"+ sc + s16 + part2 + sc\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tdata[i] = s12 + r + sc\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (dupesclean.includes(r)) {\r\n\t\t\t\t\t\t\tdata[i] = s14 + r + sc\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// output\r\n\t\t\tlet str = \"\"\r\n\t\t\tif (type == \"lookup\") {hashLookup = hash}\r\n\r\n\t\t\tif (isLoopy) {\r\n\t\t\t\t// build summary\r\n\t\t\t\tif (type == \"lookup\") {\r\n\t\t\t\t\tlookupSummary = \"<ul><li>\" + hashLookup + s4 +\" [\"+ data.length +\"]\"+ sc + getNotation(hash, method) +\"</li>\"\r\n\t\t\t\t\tif (map2.length) {lookupSummary += \"<li>\"+ s12 +\"MAPPED: \"+ sc + map2.join(\", \") +\"</li>\"}\r\n\t\t\t\t\tif (dupes.length) {lookupSummary += \"<li>\"+ s14 +\"DUPES: \"+ sc + dupes.join(\", \") +\"</li>\"}\r\n\t\t\t\t\tif (notlist.length) {lookupSummary += \"<li>\"+ s16 +\"NOT LISTED: \"+ sc + notlist.join(\", \") +\"</li>\"}\t\t\t\t\t\r\n\t\t\t\t\tlookupSummary += \"</ul>\"\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstr = s4 + getPretty(method).toUpperCase() + sc\r\n\t\t\t\t\t\t+ (hash == hashLookup ? \"\" : sb +\" [localeMatcher mismatch]\"+ sc)\r\n\t\t\t\t\tstr += \"<br>\" + lookupSummary\r\n\t\t\t\t\taSummary.push(str)\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tstr = s4 + type.toUpperCase() + sc\r\n\t\t\t\tlet strNotSupported = ''\r\n\t\t\t\tif (type == \"lookup\") {\r\n\t\t\t\t\t\r\n\t\t\t\t\taNotSupported = aLocalesUsed.filter(x => !aSupported.includes(x))\r\n\t\t\t\t\tif (aNotSupported.length) {\r\n\t\t\t\t\t\tstrNotSupported = spacer + s16 + 'NOT SUPPORTED '+ sc + mini(aNotSupported) + ' ['+ aNotSupported.length + ']'\r\n\t\t\t\t\t\t\t+ spacer + \"<span class='faint'>\" + aNotSupported.join(\", \") +\"</span>\"\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tstr = s4 + getPretty(method).toUpperCase() + sc + spacer + str + spacer\r\n\t\t\t\t\tstr += sg + hash + sc + s4 +\" [\"+ data.length +\"]\"+ sc + getNotation(hash, method)\r\n\t\t\t\t\t// nocase used to color legend items\r\n\t\t\t\t\tnocase.sort()\r\n\t\t\t\t\tnocase = nocase.filter(function(item, position) {return nocase.indexOf(item) === position})\r\n\t\t\t\t\tnocasemap.sort()\r\n\t\t\t\t\tnocasemap = nocasemap.filter(function(item, position) {return nocasemap.indexOf(item) === position})\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstr = spacer + \"<hr>\" + \"<br>\" + str + spacer\r\n\t\t\t\t\tstr += hash + s4 +\" [\"+ data.length +\"]\"+ sc + (hash == hashLookup ? sg : sb) +\" [match]\"+ sc\r\n\t\t\t\t}\r\n\t\t\t\tstr += spacer + \"<span class='faint'>\" + data.join(\", \") +\"</span>\"\r\n\t\t\t\tif (map2.length) {str += spacer + s12 +\"MAPPED\"+ sc + spacer + map2.join(\"<br>\")}\r\n\t\t\t\tif (dupes.length) {str += spacer + s14 +\"DUPES\"+ sc + spacer + dupes.join(\"<br>\")}\r\n\t\t\t\tif (notlist.length) {str += spacer + s16 +\"NOT LISTED\"+ sc + spacer + notlist.join(\"<br>\")}\r\n\t\t\t\tstr += strNotSupported\r\n\t\t\t\tdisplay.push(str)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tgetSupported(\"lookup\", method)\r\n\t\tgetSupported(\"best fit\", method)\r\n\r\n\t\tif (!isLoopy) {\r\n\t\t\tdom.results.innerHTML = display.join(\"<br>\")\r\n\t\t\t// build + color legend\r\n\t\t\tfor (let i = 0 ; i < listUsed.length; i++) {\r\n\t\t\t\t// split: code, name\r\n\t\t\t\tlet str = listUsed[i].toLowerCase()\r\n\t\t\t\tlet code = str.split(\",\")[0].trim()\r\n\t\t\t\tlet name = (undefined !== str.split(\",\")[1]) ? str.split(\",\")[1].trim() : ''\r\n\t\t\t\tif (nocase.includes(code.toLowerCase())) {\r\n\t\t\t\t\tlet isSplit = (name.includes(\"(\") && (name.length + code.length) > 32)\r\n\t\t\t\t\tif (name.includes(\"(\")) {\r\n\t\t\t\t\t\tlet name0 = name.split(\"(\")[0].trim()\r\n\t\t\t\t\t\tlet name1 = name.substring(\r\n\t\t\t\t\t\t\tname.indexOf(\"(\") + 1, \r\n\t\t\t\t\t\t\tname.lastIndexOf(\")\")\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\tname1 = s99 +\"(\"+ name1 + \")\"+ sc\r\n\t\t\t\t\t\tif (isSplit) {\r\n\t\t\t\t\t\t\tname = name0 +\"<br>\"+ \" \".repeat(4) + name1\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tname = name0 +\" \"+ name1\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tlegend.push(sg + code.padStart(7) + sc +\": \"+ name)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (nocasemap.includes(code)) {\r\n\t\t\t\t\t\tlegend.push(s12 + code.padStart(7) +\": \"+ name + sc)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tlegend.push(code.padStart(7) +\": \"+ name)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// display legend\r\n\t\t\tdom.legend.innerHTML = s4 +\"LEGEND [\"+ legend.length +\"]\"+ sc +\"<br><br>\"+ legend.join(\"<br>\")\r\n\t\t\t// perf\r\n\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\t}\r\n\t} catch(e) {\r\n\t\t// catch unsupported\r\n\t\tlet msg = e.message\r\n\t\tif (isFF) {msg = msg.replace(\"can't access property \\\"supportedLocalesOf\\\", \", \"\")} // trim *error_fix\r\n\t\tif (isLoopy) {\r\n\t\t\tlet x = s4 + getPretty(method).toUpperCase() + sc\r\n\t\t\t\t+ \"<ul><li>\"+ e.name +\": \"+ msg +\"</li></ul>\"\r\n\t\t\taSummary.push(x)\r\n\t\t} else {\r\n\t\t\tdom.results.innerHTML = \"<br>\"+ s4 + e.name +\": \"+ sc + msg\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction run(method) {\r\n\t//reset\r\n\tlegend()\r\n\tdom.results = \"\"\r\n\tdom.perf = \"\"\r\n\tlet delay = 250\r\n\tif (undefined == method) {method = lastMethod; delay = 0} else {setBtn(method)}\r\n\tlastMethod = method\r\n\t// delay so users see change and allow paint\r\n\tsetTimeout(function() {\r\n\t\tif (method == \"ALL\") {\r\n\t\t\trun_all()\r\n\t\t} else {\r\n\t\t\trun_main(method, false)\r\n\t\t}\r\n\t}, delay)\r\n}\r\n\r\nfunction run_all() {\r\n\tlet t0 = performance.now()\r\n\taSummary = []\r\n\trun_main(\"C\", true)\r\n\trun_main(\"DTF\", true)\r\n\trun_main(\"DN\", true)\r\n\trun_main(\"DF\", true)\r\n\trun_main(\"LF\", true)\r\n\trun_main(\"NF\", true)\r\n\trun_main(\"PR\", true)\r\n\trun_main(\"RTF\", true)\r\n\trun_main(\"S\", true)\r\n\tdom.results.innerHTML = aSummary.join(\"<br>\")\r\n\tlet t1 = performance.now()\r\n\tdom.perf.innerHTML = Math.round(t1-t0) +\"ms\"\r\n}\r\n\r\nfunction setBtn(method) {\r\n\tif (undefined == method) {return}\r\n\t// reset btns\r\n\tlet items = document.getElementsByClassName(\"btn8\")\r\n\tfor (let i=0; i < items.length; i++) {\r\n\t\titems[i].classList.add(\"btn4\")\r\n\t\titems[i].classList.remove(\"btn8\")\r\n\t}\r\n\t// set btn\r\n\tlet el = document.getElementById(\"b\"+ method)\r\n\tel.classList.add(\"btn8\")\r\n\tel.classList.remove(\"btn4\")\r\n}\r\n\r\nlegend()\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\tlist = gLocales\r\n\tlet aListExtra = [\r\n/*\r\n'cnr,montenegrin', // maps to sr-me\r\n'gom,goan', // maps to kok\r\n'prp,parsi', // maps to gu\r\n'prs,dari', // maps to fa-af\r\n'sh,serbo-croatian', // maps to sr-latn\r\n'swc,congo', // maps to sw-cd\r\n'tl,tagalog', // maps to fil\r\n'tw,twi', // maps to ak except in collator\r\n*/\r\n\t]\r\n\tlist = list.concat(aListExtra)\r\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\r\n\r\n\tlistExpanded = gLocales = gLocales.concat(gLocalesExpand)\r\n\tlistExpanded = listExpanded.filter(function(item, position) {return listExpanded.indexOf(item) === position})\r\n\trun(\"ALL\")\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/supportedvalues.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=400\">\r\n\t<title>supportedvaluesof</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 380px; max-width: 480px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"40%\"><col width=\"60%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">supportedvaluesof\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">Return supported values for each parameter</span>\r\n\t\t</td></tr>\r\n\t\t<tr><td colspan=\"2\"><hr><br></td></tr>\r\n\t\t<tr><td class=\"padr\">hash</td><td class=\"mono spaces\" id=\"hashAll\"></td></tr>\r\n\t\t<tr><td class=\"padr\">calendar</td><td class=\"mono spaces\" id=\"calendar\"></td></tr>\r\n\t\t<tr><td class=\"padr\">collation</td><td class=\"mono spaces\" id=\"collation\"></td></tr>\r\n\t\t<tr><td class=\"padr\">currency</td><td class=\"mono spaces\" id=\"currency\"></td></tr>\r\n\t\t<tr><td class=\"padr\">numberingSystem</td><td class=\"mono spaces\" id=\"numberingSystem\"></td></tr>\r\n\t\t<tr><td class=\"padr\">timeZone</td><td class=\"mono spaces\" id=\"timeZone\"></td></tr>\r\n\t\t<tr><td class=\"padr\">unit</td><td class=\"mono spaces\" id=\"unit\"></td></tr>\r\n\t\t<tr><td colspan=\"2\"><br><hr></td></tr>\r\n\t\t<tr><td colspan=\"2\" style=\"text-align: left; color: var(--test0);\" class=\"mono spaces\" id=\"details\">click counts to list their results here</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet oData = {}\r\n\r\nfunction display(item) {\r\n\tlet delim = (item == \"timeZone\" ? \"<br>\" : \", \")\r\n\tdom.details.innerHTML = s4 + item + sc +\"<br><span class='faint'>[<br><span class='indent'>\" + oData[item].join(delim) + \"</span><br>]</span>\"\r\n}\r\n\r\nfunction run() {\r\n\tlet t0 = performance.now()\r\n\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf\r\n\ttry {\r\n\t\tlet res = []\r\n\t\tlet list = [\"calendar\",\"collation\",\"currency\",\"numberingSystem\",\"timeZone\",\"unit\"]\r\n\t\tlist.forEach(function(item) {\r\n\t\t\tlet array = Intl.supportedValuesOf(item)\r\n\t\t\tlet el = document.getElementById(item)\r\n\t\t\tlet hash = mini(array)\r\n\t\t\tel.innerHTML = hash + \" <span class='btn4 btnc' onclick='display(`\" + item + \"`)'>[\" + array.length +\"]</span>\"\r\n\t\t\tres.push(item +\": \"+ hash)\r\n\t\t\toData[item] = array\r\n\t\t})\r\n\t\t// overall hash\r\n\t\tlet hash = mini(res), code = \"\"\r\n\r\n\t\tif (isFF) {\r\n\t\t\t// notate new if 140+\r\n\t\t\tif (isVer > 139) {\r\n\t\t\t\tif (hash == \"1a35c685\") {code = s14 +\" [FF150+]\"+ sc // collation: -searchjl\r\n\t\t\t\t} else if (hash == \"3c1a144a\") {code = s14 +\" [FF147-149]\"+ sc\r\n\t\t\t\t\t/* calendar: -islamic -islamic-rgsa\r\n\t\t\t\t\t\tnumberingSystem: +tols\r\n\t\t\t\t\t*/\r\n\t\t\t\t} else if (hash == \"db8d9901\") {code = s14 +\" [FF140-146]\"+ sc\r\n\t\t\t\t\t/* timezone: +America/Coyhaique */\r\n\t\t\t\t} else {code = ' '+ zNEW\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tdom.hashAll.innerHTML = hash + code\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t} catch (e) {\r\n\t\tdom.details = \"\"\r\n\t\tdom.detailslabel = e.name\r\n\t\tdom.details = e.message\r\n\t}\r\n}\r\n\r\nPromise.all([\r\n\tget_globals()\r\n]).then(function(){\r\n\tget_isVer()\r\n\trun()\r\n})\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/testgeneric.js",
    "content": "'use strict';\ndom = getUniqueElements();\n\n/*** GENERIC ***/\n\nconst newFn = x => typeof x != 'string' ? x : new Function(x)()\nfunction nowFn() {\n\ttry {return performance.now()\n\t} catch(e) {return}\n}\nfunction rnd_string() {return Math.random().toString(36).substring(2, 15)}\nfunction rnd_number() {return Math.floor((Math.random() * (99999-10000))+10000)}\nfunction count_decimals(value) {if(Math.floor(value) === value) return 0;return value.toString().split(\".\")[1].length || 0}\nfunction removeElementFn(id) {try {dom[id].remove()} catch(e) {}}\n\nfunction cleanFn(item, skipArray = false) {\n\t// strings, tidy undefined, empty strings\n\tif (typeof item === \"number\" || typeof item === \"bigint\") { return item\n\t} else if (item == zU) {item = zUQ\n\t} else if (item == \"true\" || item == \"false\" || item == \"null\") {item = \"\\\"\" + item + \"\\\"\"\n\t} else if (!skipArray && Array.isArray(item)) {\n\t\titem = !item.length ? \"empty array\" : \"array\"\n\t} else if (item === undefined || item === true || item === false || item === null) {item += \"\"\n\t} else if (!skipArray && item == \"\") {item = \"empty string\"\n\t} else if (typeof item === \"string\") {\n\t\tif (!Number.isNaN(item*1)) {item = \"\\\"\" + item + \"\\\"\"}\n\t}\n\treturn item\n}\n\nfunction typeFn(item, isSimple = false) {\n\t// return a more detailed result\n\tlet type = typeof item\n\tif (\"number\" === type) {\n\t\tif (Number.isNaN(item)) {type = \"NaN\"} else if (Infinity === item) {type = 'Infinity'}\n\t} else if (\"string\" === type) {\n\t\tif (!isSimple) {\n\t\t\tif (\"\" === item) {type = \"empty string\"} else if (\"\" === item.trim()) {type = \"whitespace\"}\n\t\t}\n\t} else if (\"object\" === type) {\n\t\tif (null === item) {type = \"null\"\n\t\t} else if (Array.isArray(item)) {\n\t\t\ttype = \"array\"\n\t\t\tif (!isSimple) {type = !item.length ? \"empty array\" : \"array\"}\n\t\t} else {\n\t\t\tif (!isSimple) {\n\t\t\t\ttry {if (0 === Object.keys(item).length) {type = \"empty object\"}} catch(e) {}\n\t\t\t}\n\t\t}\n\t}\n\t// do nothing: undefined, bigint, boolean, function\n\treturn type +''\n}\n\nfunction getUniqueElements() {\n\tconst dom = document.getElementsByTagName('*')\n\treturn new Proxy(dom, {\n\t\tget: function(obj, prop) {return obj[prop]},\n\t\tset: function(obj, prop, val) {obj[prop].textContent = `${val}`; return true}\n\t})\n}\n\nfunction buildButton(colorCode, arrayName, displayText, functionName, btnType) {\n\tif (functionName == undefined) {functionName = \"showDetail\"}\n\tif (btnType == undefined) {btnType = \"btnc\"}\n\tlet part1 = arrayName.split(\",\")[0]\n\tlet part2 = arrayName.split(\",\")[1] // pass a second parameter (boolean) e.g. domrectspoof non-FF tests\n\tpart2 = part2 == undefined ? \"\" : part2.trim()\n\tif (part2 == \"true\" || part2 == \"false\") {part2 = \", \"+ part2} else {part2 = \"\"}\n\treturn \" <span class='btn\"+ colorCode +\" \"+ btnType +\"' onClick='\"\n\t\t+ functionName +\"(`\"+ part1 +\"`\"+ part2 + \")'>[\"+ displayText +\"]</span>\"\n}\n\n/*** JSON ***/\n\nfunction json_highlight(json, maxWidth = 65) {\n\tif (typeof json != 'string') {\n\t\tjson = json_stringify(json, {maxLength: maxWidth});\n\t}\n\tjson = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\treturn json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {\n\t\tvar cls = 'number';\n\t\tif (/^\"/.test(match)) {\n\t\t\tif (/:$/.test(match)) {\n\t\t\t\tcls = 'key';\n\t\t\t} else {\n\t\t\t\tcls = 'string';\n\t\t\t\t// color undefined (aka \"typeof undefined\")\n\t\t\t\t//if (match == \"\\\"typeof undefined\\\"\") {cls = 'null';}\n\t\t\t}\n\t\t} else if (/true|false/.test(match)) {\n\t\t\tcls = 'boolean';\n\t\t} else if (/null/.test(match)) {\n\t\t\tcls = 'null';\n\t\t}\n\t\treturn '<span class=\"'+ cls +'\">'+ match +'</span>';\n\t})\n}\n\nfunction json_stringify(passedObj, options = {maxLength: 65}) {\n\t/* https://github.com/lydell/json-stringify-pretty-compact */\n\tconst stringOrChar = /(\"(?:[^\\\\\"]|\\\\.)*\")|[:,]/g;\n\tconst indent = JSON.stringify(\n\t\t[1],\n\t\tundefined,\n\t\toptions.indent === undefined ? 2 : options.indent\n\t).slice(2, -3);\n\tconst maxLength =\n\t\tindent === \"\"\n\t\t\t? Infinity\n\t\t\t: options.maxLength === undefined\n\t\t\t? 65 // was 80\n\t\t\t: options.maxLength;\n\tlet { replacer } = options;\n\n\treturn (function _stringify(obj, currentIndent, reserved) {\n\t\tif (obj && typeof obj.toJSON === \"function\") {\n\t\t\tobj = obj.toJSON();\n\t\t}\n\n\t\t// display undefined under an alias so we always have the right number of values\n\t\t// this is just a display, it does not alter the fingerprint data\n\t\t//if (obj === undefined) {obj = \"typeof undefined\"}\n\n\t\tconst string = JSON.stringify(obj, replacer);\n\t\tif (string === undefined) {\n\t\t\treturn string;\n\t\t}\n\t\tconst length = maxLength - currentIndent.length - reserved;\n\t\tif (string.length <= length) {\n\t\t\tconst prettified = string.replace(\n\t\t\t\tstringOrChar,\n\t\t\t\t(match, stringLiteral) => {\n\t\t\t\t\treturn stringLiteral || `${match} `;\n\t\t\t\t}\n\t\t\t);\n\t\t\tif (prettified.length <= length) {\n\t\t\t\treturn prettified;\n\t\t\t}\n\t\t}\n\t\tif (replacer != null) {\n\t\t\tobj = JSON.parse(string);\n\t\t\treplacer = undefined;\n\t\t}\n\t\tif (typeof obj === \"object\" && obj !== null) {\n\t\t\tconst nextIndent = currentIndent + indent;\n\t\t\tconst items = [];\n\t\t\tlet index = 0;\n\t\t\tlet start;\n\t\t\tlet end;\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tstart = \"[\";\n\t\t\t\tend = \"]\";\n\t\t\t\tconst { length } = obj;\n\t\t\t\tfor (; index < length; index++) {\n\t\t\t\t\titems.push(\n\t\t\t\t\t\t_stringify(obj[index], nextIndent, index === length - 1 ? 0 : 1) ||\n\t\t\t\t\t\t\"null\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstart = \"{\";\n\t\t\t\tend = \"}\";\n\t\t\t\tconst keys = Object.keys(obj);\n\t\t\t\tconst { length } = keys;\n\t\t\t\tfor (; index < length; index++) {\n\t\t\t\t\tconst key = keys[index];\n\t\t\t\t\tconst keyPart = `${JSON.stringify(key)}: `;\n\t\t\t\t\tconst value = _stringify(\n\t\t\t\t\t\tobj[key],\n\t\t\t\t\t\tnextIndent,\n\t\t\t\t\t\tkeyPart.length + (index === length - 1 ? 0 : 1)\n\t\t\t\t\t);\n\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\titems.push(keyPart + value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (items.length > 0) {\n\t\t\t\treturn [start, indent + items.join(`,\\n${nextIndent}`), end].join(\n\t\t\t\t`\\n${currentIndent}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\treturn string;\n\t})(passedObj, \"\", 0);\n}\n\n/*** HASH ***/\n\nfunction mini(str) {\n\t// https://stackoverflow.com/a/22429679\n\tconst json = `${JSON.stringify(str)}`\n\tlet i, len, hash = 0x811c9dc5\n\tfor (i = 0, len = json.length; i < len; i++) {\n\t\thash = Math.imul(31, hash) + json.charCodeAt(i) | 0\n\t}\n\treturn ('0000000' + (hash >>> 0).toString(16)).slice(-8)\n}\n\nfunction sha1(str1){\n\tfor (var blockstart=0,\n\t\ti = 0,\n\t\tW = [],\n\t\tH = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0],\n\t\tA, B, C, D, F, G,\n\t\tword_array = [],\n\t\ttemp2,\n\t\ts = unescape(encodeURI(str1)),\n\t\tstr_len = s.length;\n\t\ti<=str_len;){\n\t\tword_array[i>>2] |= (s.charCodeAt(i)||128)<<(8*(3-i++%4));\n\t}\n\tword_array[temp2 = ((str_len+8)>>6<<4)+15] = str_len<<3;\n\tfor (; blockstart <= temp2; blockstart += 16) {\n\t\tA = H,i=0;\n\t\tfor (; i < 80;\n\t\t\tA = [[\n\t\t\t\t(G = ((s=A[0])<<5|s>>>27) + A[4] + (W[i] = (i<16) ? ~~word_array[blockstart + i] : G<<1|G>>>31) + 1518500249) + ((B=A[1]) & (C=A[2]) | ~B & (D=A[3])),\n\t\t\t\tF = G + (B ^ C ^ D) + 341275144,\n\t\t\t\tG + (B & C | B & D | C & D) + 882459459,\n\t\t\t\tF + 1535694389\n\t\t\t][0|i++/20] | 0, s, B<<30|B>>>2, C, D]\n\t\t) {\n\t\t\tG = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];\n\t\t}\n\t\tfor(i=5;i;) H[--i] = H[i] + A[i] | 0;\n\t}\n\tfor(str1='';i<40;)str1 += (H[i>>3] >> (7-i++%8)*4 & 15).toString(16);\n\treturn str1\n}\n\n/*** GLOBAL VARS ***/\n\nconst get_globals = () => new Promise(resolve => {\n\t// immutables: do once but promise from each test page if used\n\tlet tstart = nowFn()\n\t// we use > not >= which means 50% or more to break an engine check\n\t\t// e.g always use odd\n\t\t\t// 9 all same: to get under/over 4.5 you would need to lie about 5/9 = 56%\n\t\t// e.g. even\n\t\t\t// 8 true: to get 4-or-lower you would need to lie about 4/8\n\t\t\t// 8 false: to get over 4 you would need to lie about 5/8\n\tlet oEngines = {\n\t\t\"blink\": [\n\t\t\t\"number\" === typeof TEMPORARY,\n\t\t\t\"number\" === typeof PERSISTENT,\n\t\t\t\"object\" === typeof onappinstalled,\n\t\t\t\"object\" === typeof onbeforeinstallprompt,\n\t\t\t//\"object\" === typeof onpointerrawupdate,\n\t\t\t//\"object\" === typeof onsearch,\n\t\t\t//\"boolean\" === typeof originAgentCluster,\n\t\t\t//\"object\" === typeof trustedTypes,\n\t\t\t\"function\" === typeof webkitResolveLocalFileSystemURL,\n\t\t],\n\t\t\"webkit\": [\n\t\t\t\"object\" === typeof browser,\n\t\t\t//\"function\" === typeof getMatchedCSSRules,\n\t\t\t\"object\" === typeof safari,\n\t\t\t//\"function\" === typeof showModalDialog,\n\t\t\t\"function\" === typeof webkitConvertPointFromNodeToPage,\n\t\t\t\"function\" === typeof webkitCancelRequestAnimationFrame,\n\t\t\t\"object\" === typeof webkitIndexedDB,\n\t\t],\n\t\t\"gecko\": [\n\t\t\t\"function\" === typeof dump,\n\t\t\t\"boolean\" === typeof fullScreen,\n\t\t\t\"number\" === typeof mozInnerScreenX,\n\t\t\t\"function\" === typeof scrollByLines,\n\t\t\t\"number\" === typeof scrollMaxY,\n\t\t\t\"function\" === typeof setResizable,\n\t\t\t//\"function\" === typeof sizeToContent,  // removed nightly FF117+ 1832733 / 1600400\n\t\t\t\"function\" === typeof updateCommands,\n\t\t],\n\t\t\"edgeHTML\": [\n\t\t\t\"function\" === typeof clearImmediate,\n\t\t\t\"function\" === typeof msWriteProfilerMark,\n\t\t\t\"object\" === typeof oncompassneedscalibration,\n\t\t\t\"object\" === typeof onmsgesturechange,\n\t\t\t\"object\" === typeof onmsinertiastart,\n\t\t\t\"object\" === typeof onreadystatechange,\n\t\t\t//\"object\" === typeof onvrdisplayfocus,\n\t\t\t\"function\" === typeof setImmediate,\n\t\t]\n\t}\n\n\t// array engine matches, so subsequent results doesn't override prev\n\tlet aEngine = []\n\tfor (const engine of Object.keys(oEngines).sort()) {\n\t\tlet sumE = oEngines[engine].reduce((prev, current) => prev + current, 0)\n\t\tif (sumE > (oEngines[engine].length/2)) {aEngine.push(engine)}\n\t}\n\tif (aEngine.length == 1) {isEngine = aEngine[0]} // valid one result\n\t// perf\n\tlet tend = nowFn()\n\t// re-tidy vars\n\tif (isEngine == \"gecko\") {\n\t\tisFF = true\n\t\t// check for PM28+ : fails 53\n\t\tif (\"function\" !== typeof CSSMozDocumentRule) {\n\t\t\tisEngine = \"goanna\"\n\t\t}\n\t}\n\t// build a pretty display\n\tlet displayAll = []\n\tfor (const engine of Object.keys(oEngines).sort()) {\n\t\tlet displayE = []\n\t\toEngines[engine].forEach(function(check) {\n\t\t\tdisplayE.push(check ? green_tick : red_cross)\n\t\t})\n\t\tdisplayAll.push(displayE.join(\"\"))\n\t}\n\tisEnginePretty = Math.round(tend-tstart) +\" ms |\" + displayAll.join(\" |\")\n\t\t\t+ \" | \" + (isEngine == \"\" ? \"UNKNOWN\" : isEngine.toUpperCase())\n\n\t// isFF: gecko 10 more tests\n\ttstart = nowFn()\n\ttry {\n\t\tlet list = [\n\t\t\t[DataTransfer, \"DataTransfer\", \"mozSourceNode\"],\n\t\t\t[Document, \"Document\", \"mozFullScreen\"],\n\t\t\t[HTMLCanvasElement, \"HTMLCanvasElement\", \"mozPrintCallback\"],\n\t\t\t[HTMLElement, \"HTMLElement\", \"onmozfullscreenerror\"],\n\t\t\t//[HTMLInputElement, \"HTMLInputElement\", \"mozIsTextField\"], // removed FF142\n\t\t\t[HTMLVideoElement, \"HTMLVideoElement\", \"mozDecodedFrames\"],\n\t\t\t[IDBIndex, \"IDBIndex\", \"mozGetAllKeys\"],\n\t\t\t[IDBObjectStore, \"IDBObjectStore\", \"mozGetAll\"],\n\t\t\t[Screen, \"Screen\", \"mozOrientation\"],\n\t\t\t[SVGElement, \"SVGElement\", \"onmozfullscreenchange\"] \n\t\t]\n\t\tlet obj, aNo = []\n\t\tlist.forEach(function(array) {\n\t\t\tobj = array[0]\n\t\t\tif (\"function\" === typeof obj\n\t\t\t\t&& (\"object\" === typeof Object.getOwnPropertyDescriptor(obj.prototype, array[2]))\n\t\t\t) {\n\t\t\t} else {\n\t\t\t\taNo.push(array[1])\n\t\t\t}\n\t\t})\n\t\tlet tend = nowFn()\n\t\tlet found = (list.length - aNo.length)\n\t\tif (found > 5) {\n\t\t\tisFF = true\n\t\t\tisEngine = \"gecko\"\n\t\t\t// check for PM28+ : fails 53\n\t\t\tif (\"function\" !== typeof CSSMozDocumentRule) {\n\t\t\t\tisEngine = \"goanna\"\n\t\t\t}\n\t\t}\n\t\t// build a pretty display\n\t\tlet display = []\n\t\tlist.forEach(function(array) {\n\t\t\tlet check = aNo.includes(array[1]) ? red_cross : green_tick\n\t\t\tdisplay.push(check)\n\t\t})\n\t\tisFFvalid = true\n\t\tlet strYou = \" | are you \" + (isEngine == \"goanna\" ? \"goanna\" : \"gecko\") + \"? \"\n\t\t\t+ (isFF ? sg.trim() + \"YES\" : sb.trim() + \"NO\") + sc\n\n\t\tisFFpretty = Math.round(tend-tstart) +\" ms |\"+ display.join(\"\") + strYou\n\t} catch(e) {\n\t\tisFFvalid = false\n\t\tisFFpretty = sb.trim() + e.name +\": \"+ sc + e.message\n\t}\n\n\treturn resolve()\n})\n\nfunction get_is95() {\n\t// requires a dom element\n\treturn new Promise(resolve => {\n\t\tif (!isFF) {\n\t\t\treturn resolve()\n\t\t}\n\t\t// pre-compute slow 95 test\n\t\tif (\"function\" === typeof self.structuredClone && \"function\" !== typeof crypto.randomUUID) {\n\t\t\t// ^ do if 94+ but not 95+ fast path\n\t\t\ttry {\n\t\t\t\tif (\"sc\" !== Intl.PluralRules.supportedLocalesOf(\"sc\").join()) {\n\t\t\t\t\t// but not if 96+\n\t\t\t\t\tlet ratio = dom.test95a.offsetWidth/dom.test95b.offsetWidth\n\t\t\t\t\tis95 = (ratio > 0.4 && ratio < 0.6)\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tconsole.debug(e.name, e.message)\n\t\t\t}\n\t\t}\n\t\treturn resolve()\n\t})\n}\n\nconst get_isVer = () => new Promise(resolve => {\n\t// NOTE: requires dom for 95 and 76, and a promised is95\n\tif (!isFF) {return resolve()}\n\tfunction output(verNo) {\n\t\tisVer = verNo\n\t\treturn resolve()\n\t}\n\t// avoid false returns with forks\n\tif (\"function\" !== typeof CSSMozDocumentRule) {\n\t\t// palemoon/basilisk: fails 53\n\t\toutput(52)\n\t\treturn\n\t} else if (\"function\" === typeof AbortSignal && \"undefined\" !== typeof HTMLAppletElement) {\n\t\t// waterfox classic: 57 pass, 56 fail\n\t\t// but waterfox (v78) also fails 56, so test that as well\n\t\tif (!window.Document.prototype.hasOwnProperty(\"replaceChildren\")) {\n\t\t\toutput(52)\n\t\t\treturn\n\t\t}\n\t}\n\toutput(cascade())\n\n\tfunction cascade() {\n\t\tisVerMax = 152\n\n\t\t// old-timey check: avoid false postives\n\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty('letterSpacing')) {\n\t\t\ttry {if (SVGTextPathElement.prototype.hasOwnProperty('side')) return 152} catch(e) {} // 2034371\n\t\t\tif (CSSContainerRule.prototype.hasOwnProperty('conditions')) return 151 // 2022827\n\t\t\tif ('object' == typeof visualViewport.onscrollend) return 150 // 1801658\n\t\t\ttry {Temporal.PlainDate.from({calendar:'gregory', monthCode:'M12', month:13, year:2019, day:1})} catch(e) {if ('RangeError' == e.name) return 149} // 2009792\n\t\t\t// 148: fast-path: pref dom.location.ancestorOrigins.enabled (default true)\n\t\t\ttry {if (undefined !== location.ancestorOrigins) return 148} catch(e) {} // 1085214\n\t\t\ttry {let test148 = new Temporal.Duration(0).total({unit:'years', relativeTo:'-271821-04-19'}); return 148} catch(e) {} // 2004851\n\t\t\ttry {if (Intl.supportedValuesOf('numberingSystem').includes('tols')) return 147} catch(e) {} // 2000225 ?\n\t\t\ttry {throw new DOMException('a', 'b')} catch(e) {if (0 !== e.columnNumber) return 146} // 1997216\n\t\t\tif (undefined !== (new ToggleEvent('toggle', null)).source) return 145 // 1968987\n\t\t\tif (undefined == window.CSS2Properties) return 144 // 144: 1919582\n\t\t\t// 143: fast-path: pref: layout.css.moz-appearance.webidl.enabled: default false 143+\n\t\t\tif (!CSS2Properties.prototype.hasOwnProperty('-moz-appearance')) return 143 // 1977489\n\t\t\t// 142\n\t\t\ttry {\n\t\t\t\tlet segmenter = new Intl.Segmenter('en', {granularity:'word'})\n\t\t\t\tlet test142 = Array.from(segmenter.segment('a:b')).map(({ segment }) => segment)\n\t\t\t\tif (3 == test142.length) return 142 // 1960300\n\t\t\t} catch(e) {}\n\t\t\t// 141: fast-path: requires temporal default enabled FF139+ javascript.options.experimental.temporal\n\t\t\ttry {if (undefined == Temporal.PlainDate.from('2029-12-31[u-ca=gregory]').weekOfYear) return 141} catch(e) {} // 1950162\n\t\t\t// 141: fast-path: dom.intersection_observer.scroll_margin.enabled (default true)\n\t\t\ttry {if (window[\"IntersectionObserver\"].prototype.hasOwnProperty('scrollMargin')) return 141} catch(e) {} // 1860030\n\t\t\t// 140: fast-path: pref: dom.event.pointer.rawupdate.enabled : default true 140+\n\t\t\ttry {if (\"object\" === typeof onpointerrawupdate) return 140} catch(e) {} // 1550462\n\t\t\t// 140: if < 141 there is only one paint entry \"PerformancePaintTiming\"\n\t\t\ttry {if (undefined !== performance.getEntriesByType(\"paint\")[0].presentationTime) return 140} catch(e) {} // 1963464\n\t\t\t// 139\n\t\t\ttry {if (HTMLDialogElement.prototype.hasOwnProperty('requestClose')) return 139} catch(e) {} // 1960556\n\t\t\t// 138: fast-path: requires webrtc e.g. media.peerconnection.enabled | --disable-webrtc\n\t\t\ttry {if (RTCCertificate.prototype.hasOwnProperty('getFingerprints')) return 138} catch(e) {} // 1525241\n\t\t\t// 138: fast-path: dom.origin_agent_cluster.enabled\n\t\t\tif ('boolean' == typeof originAgentCluster) return 138 // 1665474\n\t\t\t// 138: must be FF134 or higher\n\t\t\ttry {\n\t\t\t\tif (HTMLScriptElement.prototype.hasOwnProperty('textContent')) { // FF135+\n\t\t\t\t\tlet test138 = Intl.NumberFormat('yo-bj', {style: 'unit', unit: 'year', unitDisplay: 'narrow'}).format(1)\n\t\t\t\t\tif ('606d1046' == mini(test138)) return 138 // 1954425\n\t\t\t\t}\n\t\t\t} catch(e) {}\n\t\t\t// 137 fast-path: javascript.options.experimental.math_sumprecise\n\t\t\tif ('function' == typeof Math.sumPrecise) return 137 // 1943120\n\t\t\ttry {\n\t\t\t\t// fastpath: FF132+: javascript.options.experimental.regexp_modifiers\n\t\t\t\tif ((new RegExp(\"(?i:[A-Z]{4})\")).test('abcd')) return 136 // 1939533\n\t\t\t} catch(e) {}\n\t\t\tif (HTMLScriptElement.prototype.hasOwnProperty('textContent')) return 135 // 1905706\n\t\t\ttry {\n\t\t\t\tif ('lij' == Intl.PluralRules.supportedLocalesOf('lij').join()) return 134 // 1927706\n\t\t\t} catch(e) {}\n\t\t\ttry {\n\t\t\t\tlet parser = (new DOMParser).parseFromString(\"<select><option name=''></option></select>\", 'text/html')\n\t\t\t\tif (null === parser.body.firstChild.namedItem('')) return 133 // 1837773\n\t\t\t} catch(e) {}\n\t\t\ttry {\n\t\t\t\tconst re = new RegExp('(?:)', 'gv');\n\t\t\t\tlet test132 = RegExp.prototype[Symbol.matchAll].call(re, '𠮷')\n\t\t\t\tfor (let i=0; i < 3; i++) {if (true == test132.next().done) return 132} // 1899413\n\t\t\t} catch(e) {}\n\t\t\ttry {\n\t\t\t\tlet test131 = new Intl.DateTimeFormat('zh', {calendar: 'chinese', dateStyle: 'medium'}).format(new Date(2033, 9, 1))\n\t\t\t\tif ('2033' == test131.slice(0,4)) return 131 // 1900196\n\t\t\t} catch(e) {}\n\t\t\ttry {new RegExp('[\\\\00]','u')} catch(e) {if (e+'' == 'SyntaxError: invalid decimal escape in regular expression') return 130} // 1907236\n\t\t\tif (CSS2Properties.prototype.hasOwnProperty('WebkitFontFeatureSettings')) return 129 // 1595620\n\t\t\ttry {let test128 = (new Blob()).bytes(); return 128} catch(e) {} // 1896509\n\t\t\ttry {if ((new Date('15Jan0024')).getYear() > 0) return 127} catch(e) {} // 1894248\n\t\t\tif ('function' === typeof URL.parse) {return 126}\n\t\t\ttry {if ('Invalid Date' == new Date('Sep 26 Thurs 1995 10:00')) return 125} catch(e) {} // 1872793\n\t\t\tlet el = document.documentElement\n\t\t\tif (!CSS2Properties.prototype.hasOwnProperty('MozUserFocus')) {\n\t\t\t\ttry {\n\t\t\t\t\tel.style.zIndex = 'calc(1 / max(-0, 0))'\n\t\t\t\t\tlet test = getComputedStyle(el).zIndex\n\t\t\t\t\tel.style.zIndex = 'auto'\n\t\t\t\t\tif (test > 0) {return 124} // 1867569\n\t\t\t\t} catch(e) {}\n\t\t\t\treturn 123 // 1871745\n\t\t\t}\n\t\t\tif ('function' === typeof Promise.withResolvers) {\n\t\t\t\ttry {\n\t\t\t\t\tel.style.zIndex = 'calc(1 / abs(-0))'\n\t\t\t\t\tlet test = getComputedStyle(el).zIndex\n\t\t\t\t\tel.style.zIndex = 'auto'\n\t\t\t\t\tif (test > 0) {return 122} // 1867558\n\t\t\t\t} catch(e) {}\n\t\t\t\treturn 121 // 1845586\n\t\t\t}\n\t\t\tif (window.hasOwnProperty('UserActivation')) return 120 // 1791079\n\t\t\ttry {location.href = 'http://a>b/'} catch(e) {if (e.name === 'SyntaxError') return 119} // 1817591\n\t\t\tif (CSS2Properties.prototype.hasOwnProperty('fontSynthesisPosition')) return 118 // 1849010\n\t\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty('fontStretch')) return 117 // 1842467\n\t\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty('textRendering')) return 116 // 1839614\n\t\t\treturn 115 // 1778909\n\t\t}\n\t\t// 114 or lower\n\t\tif (\"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"WebkitTextSecurity\")) return 114 // 1826629\n\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty(\"reset\")) return 113 // 1709347\n\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty(\"roundRect\")) return 112 // 1756175\n\t\tif (HTMLElement.prototype.hasOwnProperty(\"translate\")) return 111 // 1418449\n\t\tif (\"object\" === typeof ondeviceorientationabsolute) return 110 // 1689631\n\t\tif (CSSKeyframesRule.prototype.hasOwnProperty(\"length\")) return 109 // 1789776\n\t\tif (\"undefined\" === typeof onloadend) return 108 // 1574487\n\t\tif (!SVGSVGElement.prototype.hasOwnProperty(\"useCurrentView\")) return 107 // 1174097\n\t\tif (Element.prototype.hasOwnProperty(\"checkVisibility\")) return 106 // 1777293\n\t\ttry {structuredClone((() => {}))} catch(e) {if (e.message.length == 36) return 105} // 830716\n\t\tif (undefined !== window.SVGStyleElement && SVGStyleElement.prototype.hasOwnProperty(\"disabled\")) return 104 // 1712623\n\t\tif (undefined === new ErrorEvent(\"error\").error) return 103 // 1772494\n\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty(\"direction\")) {\n\t\t\tif (Array(1).includes()) return 102 // 1767541: regression FF99\n\t\t\treturn 101 // 1728999\n\t\t}\n\t\tif (\"function\" === typeof AbortSignal && \"function\" === typeof AbortSignal.timeout) return 100 // 1753309\n\t\ttry {newFn(\"class A { #x; h(o) { return !#x in o; }}\")} catch(e) {if (e.message.length == 72) return 99} // 1711715 + 1756204\n\t\tif (HTMLElement.prototype.hasOwnProperty(\"outerText\")) return 98 // 1709790\n\t\tif (\"function\" === typeof AbortSignal && \"function\" === typeof AbortSignal.prototype.throwIfAborted) return 97 // 1745372\n\t\tif (\"undefined\" === typeof Object.toSource\n\t\t\t&& \"sc\" === Intl.PluralRules.supportedLocalesOf(\"sc\").join()) return 96 // 1738422\n\t\t\t// ^ legacy perf: toSource (74+): FF68- very slow\n\t\tif (\"function\" === typeof crypto.randomUUID) return 95 // 1723674: fast path pref\n\t\tif (is95) return 95 // 1674204\n\t\t\t// ^ pre-computed\n\t\tif (\"function\" === typeof self.structuredClone) return 94 // 1722576\n\t\tif (\"function\" === typeof self.reportError) return 93 // 1722448\n\t\tif (\"function\" === typeof Object.hasOwn) return 92 // 1721149\n\t\tif (\"object\" === typeof window.clientInformation) return 91 // 1717072 fast path pref\n\t\ttry {if (\"sa\" === Intl.Collator.supportedLocalesOf(\"sa\").join()) return 91} catch(e) {} // 1714933\n\t\tif (\"function\" === typeof Array.prototype.at) return 90 // 1681371\n\t\tif (\"function\" === typeof CountQueuingStrategy\n\t\t\t&& ! new CountQueuingStrategy({highWaterMark: 1}).hasOwnProperty(\"highWaterMark\")) return 89 // 1684316\n\t\t\t// ^ legacy check FF64- CountQueuingStrategy\n\t\tif (\":\" === document.createElement(\"a\").protocol) return 88 // 1497557\n\t\tif (undefined === console.length) return 87 // 1688335\n\t\tif (\"function\" === typeof Intl.DisplayNames) return 86 // 1654116\n\t\ttry {Object.getOwnPropertyDescriptor(RegExp.prototype, \"global\").get.call(\"/a\")\n\t\t\t} catch(e) {if (e.message.length == 66) {return 85}} // 1675240\n\t\t\t// ^ replace ?\n\t\tif (\"function\" === typeof PerformancePaintTiming) return 84 // 1518999\n\t\tif (\"undefined\" === typeof Object.toSource\n\t\t\t&& !window.HTMLIFrameElement.prototype.hasOwnProperty(\"allowPaymentRequest\")) return 83 // 1665252\n\t\ttry {if (1595289600000 === Date.parse('21 Jul 20 00:00:00 GMT')) {return 82}} catch(e) {} // 1655947\n\t\t\t// ^ ext fuckery: cydec\n\t\tif (new File([\"x\"], \"a/b\").name == \"a/b\") return 81 // 1650607\n\t\tif (\"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"appearance\")) return 80 // 1620467\n\t\tif (\"function\" === typeof Promise.any) return 79 // 1599769 shipped\n\t\tif (window.Document.prototype.hasOwnProperty(\"replaceChildren\")) return 78 // 1626015\n\t\tif (undefined !== window.IDBCursor && window.IDBCursor.prototype.hasOwnProperty(\"request\")) return 77 // 1536540\n\t\tif (\"undefined\" === typeof Object.toSource\n\t\t\t&& !test76.validity.rangeOverflow) return 76 // 1608010\n\t\tif (\"function\" === typeof Intl.Locale) return 75 // 1613713\n\t\tif (\"undefined\" === typeof Object.toSource) return 74 // 1565170\n\t\tif (!VideoPlaybackQuality.prototype.hasOwnProperty(\"corruptedVideoFrames\")) return 73 // 1602163\n\t\tif (\"boolean\" === typeof self.crossOriginIsolated) return 72 // 1591892\n\t\tif (\"function\" === typeof Promise.allSettled) return 71 // 1549176\n\t\tif (\"function\" === typeof Intl.RelativeTimeFormat\n\t\t\t&& \"function\" === typeof Intl.RelativeTimeFormat.prototype.formatToParts) return 70 // 1473229\n\t\t\t// ^ legacy check: FF64- Intl.RelativeTimeFormat\n\t\t\t// ^ extension fuckery: formatToParts\n\t\ttry {newFn(\"let t = 1_050\"); return 70} catch(e) {} // 1435818\n\t\tif (\"function\" === typeof Blob.prototype.text) return 69 // 1557121\n\t\tif (!HTMLObjectElement.prototype.hasOwnProperty(\"typeMustMatch\")) return 68 // 1548773\n\t\tif (\"function\" === typeof String.prototype.matchAll) return 67 // 1531830\n\t\tif (\"function\" === typeof HTMLSlotElement\n\t\t\t&& \"function\" === typeof HTMLSlotElement.prototype.assignedElements) return 66 // 1425685\n\t\t\t// ^ legacy check: FF60- HTMLSlotElement\n\t\tif (1 === DataView.length) return 65 // 1334813\n\t\tif (\"number\" === typeof window.screenTop) return 64 // 1498860\n\t\tif (\"desc\" === Symbol('desc').description) return 63 // 1472170\n\t\tif (\"function\" === typeof console.timeLog) return 62 // 1458466\n\t\tif (\"object\" === typeof CSS) return 61 // 1455805\n\t\tif (undefined !== window.Animation && \"function\" === typeof Animation.prototype.updatePlaybackRate) return 60 // 1436659\n\t\tif (!HTMLMediaElement.prototype.hasOwnProperty(\"mozAutoplayEnabled\")) return 59 // 1336400\n\t\tif (\"function\" === typeof Intl.PluralRules) return 58 // 1403318\n\t\tif (\"function\" === typeof AbortSignal) return 57 // 1378342\n\t\tif (\"undefined\" === typeof HTMLAppletElement) return 56 // 1279218\n\t\tif (\"undefined\" === typeof console.timeline) return 55 // 1351795\n\t\tif (URL.prototype.hasOwnProperty(\"toJSON\")) return 54 // 1337702\n\t\tif (\"function\" === typeof CSSMozDocumentRule) return 53\n\t\treturn 52\n\t}\n})\n\n/** GENERAL CLICK FUNCTIONS **/\n\nfunction buildnav() {\n\t// add prev/next nav links in all the intl tests so it's easy to loop thru them\n\tlet aTests = [ // filenames: in order as per listed, not necerssarily alphabetical\n\t\t'collation',\n\t\t'dtfcomponents','dtfdatetimestyle','dtfdayperiod','dtflistformat','dtfrelated','dtftimezonename',\n\t\t'dncalendar','dncurrency','dndatetime','dnlanguage','dnregion','dnscript',\n\t\t'duration',\n\t\t'nfcompact','nfcurrency','nfformattoparts','nfnotation','nfsign','nfunit',\n\t\t'pr','prrange','rtf','resolvedoptions','segmenter',\n\t]\n\tlet oTests = { // just hardcode the prettyy names\n\t\t'dncalendar': 'dn: calendar',\n\t\t'dncurrency': 'dn: currency',\n\t\t'dndatetime': 'dn: datetimefield',\n\t\t'dnlanguage': 'dn: language',\n\t\t'dnregion': 'dn: region',\n\t\t'dnscript': 'dn: script',\n\t\t'dtfcomponents': 'dtf: components',\n\t\t'dtfdatetimestyle': 'dtf: date-&-timestyle',\n\t\t'dtfdayperiod': 'dtf: dayperiod',\n\t\t'dtflistformat': 'dtf: listformat',\n\t\t'dtfrelated': 'dtf: relatedyear',\n\t\t'dtftimezonename': 'dtf: timezonename',\n\t\t'duration': 'durationformat',\n\t\t'nfcompact': 'nf: compact',\n\t\t'nfcurrency': 'nf: currency',\n\t\t'nfformattoparts': 'nf: ftp',\n\t\t'nfnotation': 'nf: notation',\n\t\t'nfsign': 'nf: sign',\n\t\t'nfunit': 'nf: unit',\n\t\t'pr': 'pr: select',\n\t\t'prrange': 'pr: selectrange',\n\t\t'rtf': 'relativetimeformat',\n\t}\n\t// if not file skip segementer\n\tif (!isFile) {aTests = aTests.filter(x => !['segmenter'].includes(x)) }\n\n\t// get filename\n\tlet loc = location +''\n\tlet file = loc.slice(loc.lastIndexOf('/') + 1, loc.length - 5)\n\t// index\n\tlet current = aTests.indexOf(file), previous, next, max = (aTests.length - 1)\n\tif (current == 0) {previous = max} else {previous = current - 1}\n\tif (current == max) {next = 0} else {next = current + 1}\n\t// keys/filenames\n\tprevious = aTests[previous]\n\tnext = aTests[next]\n\t// pretty names\n\tlet pName = undefined !== oTests[previous] ? oTests[previous] : previous\n\tlet nName = undefined !== oTests[next] ? oTests[next] : next\n\n\ttry {\n\t\t// handle undefined: i.e I forgot to add it to a/oTests\n\t\tif (undefined !== pName) {\n\t\t\tdom.navprev.innerHTML = '<a class=\"return\" href=\"'+ previous +'.html\">◀ ' + pName +'</a>'\n\t\t}\n\t\tif (undefined !== nName) {\n\t\t\tdom.navnext.innerHTML = '<a class=\"return\" href=\"'+ next +'.html\">'+ nName +' ▶</a>'\n\t\t}\n\t} catch(e) {}\n\n}\n\nfunction copyclip(element) {\n\t// fallback: e.g FF62-\n\tfunction copyExec() {\n\t\tif (document.selection) {\n\t\t\tlet range = document.body.createTextRange()\n\t\t\trange.moveToElementText(document.getElementById(element))\n\t\t\trange.select().createTextRange()\n\t\t\tdocument.execCommand(\"copy\")\n\t\t} else if (window.getSelection) {\n\t\t\tlet range = document.createRange()\n\t\t\trange.selectNode(document.getElementById(element))\n\t\t\twindow.getSelection().addRange(range)\n\t\t\tdocument.execCommand(\"copy\")\n\t\t}\n\t}\n\t// clipboard API\n\tif (\"clipboard\" in navigator) {\n\t\ttry {\n\t\t\tlet content = document.getElementById(element).innerHTML\n\t\t\t// remove spans, change linebreaks\n\t\t\tlet regex = /<br\\s*[\\/]?>/gi\n\t\t\tcontent = content.replace(regex, \"\\r\\n\")\n\t\t\tcontent = content.replace(/<\\/?span[^>]*>/g,\"\")\n\t\t\t// get it\n\t\t\tnavigator.clipboard.writeText(content).then(function() {\n\t\t\t\t// clipboard successfully set\n\t\t\t}, function() {\n\t\t\t\t// clipboard write failed\n\t\t\t\tcopyExec()\n\t\t\t})\n\t\t} catch(e) {\n\t\t\tcopyExec()\n\t\t}\n\t} else {\n\t\tcopyExec()\n\t}\n}\n\nfunction hide_overlays() {\n\tdom.modaloverlay.style.display = \"none\"\n\tdom.overlay.style.display = \"none\"\n}\n\nfunction show_overlay() {\n\t// this just displays it, test PoCs wil need to populate it beforehabd\n\tdom.modaloverlay.style.display = \"block\"\n\tdom.overlay.style.display = \"block\"\n}\n\nfunction showDetail(name) {\n\tif (name == \"all\") {\n\t\tconsole.log(\"ALL\", sDetail)\n\t} else {\n\t\tlet data = sDetail[name]\n\t\tlet hash = mini(data)\n\t\t// split+tidy name\n\t\tname = name.replace(/\\_/g, \" \")\n\t\tlet n = name.indexOf(\" \"),\n\t\t\tsection = name.substring(0,n).toUpperCase(),\n\t\t\tmetric = name.substring(n,name.length).trim()\n\t\tconsole.log(section +\": \"+ metric +\": \"+ hash, data)\n\t}\n}\n\nfunction showhide(id, style) {\n\tlet items = document.getElementsByClassName(\"tog\"+ id)\n\tfor (let i=0; i < items.length; i++) {items[i].style.display = style}\n}\n\nfunction togglerows(id, word) {\n\tlet items = document.getElementsByClassName(\"tog\"+ id)\n\tlet\tstyle = items[0].style.display == \"table-row\" ? \"none\" : \"table-row\"\n\tfor (let i=0; i < items.length; i++) {items[i].style.display = style}\n\tif (word == \"btn\") {\n\t\tword = \"[ \"+ (style == \"none\" ? \"show\" : \"hide\") +\" ]\"\n\t} else if (word == \"expand\") {\n\t\tword = \"[ \"+ (style == \"none\" ? \"expand\" : \"collapse\") +\" ]\"\n\t} else {\n\t\tword = (style == \"none\" ? \"&#9660; show \" : \"&#9650; hide \") + (word == \"\" || word == undefined ? \"details\" : word)\n\t}\n\ttry {document.getElementById(\"label\"+ id).innerHTML = word} catch(e) {}\n}\n\nif (location.protocol == \"file:\") {isFile = true}\n\n"
  },
  {
    "path": "tests/testglobals.js",
    "content": "'use strict';\n\nvar dom;\nlet sDetail = {}\n\n// css\nlet s0 = \"<span class='\",\n\tsb = s0+\"bad'>\",\n\tsg = s0+\"good'>\",\n\tsf = s0+\"faint'>\",\n\tsn = s0+\"neutral'>\",\n\tsnc = s0+\"no_color'>\",\n\ts1 = s0+\"s1'>\",\n\ts2 = s0+\"s2'>\",\n\ts3 = s0+\"s3'>\",\n\ts4 = s0+\"s4'>\",\n\ts5 = s0+\"s5'>\",\n\ts6 = s0+\"s6'>\",\n\ts7 = s0+\"s7'>\",\n\ts8 = s0+\"s8'>\",\n\ts9 = s0+\"s9'>\",\n\ts10 = s0+\"s10'>\",\n\ts11 = s0+\"s11'>\",\n\ts12 = s0+\"s12'>\",\n\ts13 = s0+\"s13'>\",\n\ts14 = s0+\"s14'>\",\n\ts15 = s0+\"s15'>\",\n\ts16 = s0+\"s16'>\",\n\ts17 = s0+\"s17'>\",\n\ts18 = s0+\"s18'>\",\n\ts99 = s0+\"s99'>\",\n\tsc = \"</span>\",\n// test icons\n\tgreen_tick = \"<span style='font-size: 10px;'><b>\" + s9.trim() +\" \\u2713\"+ sc + \"</b></span>\",\n\tred_cross = \"<span style='font-size: 10px;'><b>\" + sb.trim() +\" \\u2715\"+ sc + \"</b></span>\",\n\tyellow_block = \"<span style='font-size: 10px;'><b>\" + s4.trim() +\" \\u2715\"+ sc + \"</b></span>\",\n\twhite_na =  \"<span style='font-size: 10px;'><b>\" + snc.trim() +\" \\u2715\"+ sc + \"</b></span>\",\n// common results\n\tzErr = 'error',\n\tzNA = 'n/a',\n\tzU = 'undefined',\n\tzUQ = \"\\\"undefined\\\"\",\n\tzNEW = sb+'[NEW]'+sc,\n// other\n\tis95 = false,\n\tisEngine = '',\n\tisEnginePretty = '', // results string with perf\n\tisFF = false,\n\tisFFpretty = '', // results string with perf\n\tisFFvalid = false, // no errors\n\tisFile = false,\n\tisVer = '',\n\tisVerMax = ''\n\n// timezones\nlet gTimezones = [\n// no regional prefix\n'CET','CST6CDT','Cuba','EET','EST','EST5EDT','Egypt','Eire','Factory','GB','GB-Eire','GMT','GMT+0','GMT-0','GMT0',\n'Greenwich','HST','Hongkong','Iceland','Iran','Israel','Jamaica','Japan','Kwajalein','Libya','MET','MST','MST7MDT',\n'NZ','NZ-CHAT','Navajo','PRC','PST8PDT','Poland','Portugal','ROC','ROK','Singapore','Turkey','UCT','UTC','Universal',\n'W-SU','WET','Zulu',\n\n// THE REST\n'Africa/Abidjan','Africa/Accra','Africa/Addis_Ababa','Africa/Algiers','Africa/Asmara','Africa/Asmera','Africa/Bamako',\n'Africa/Bangui','Africa/Banjul','Africa/Bissau','Africa/Blantyre','Africa/Brazzaville','Africa/Bujumbura','Africa/Cairo',\n'Africa/Casablanca','Africa/Ceuta','Africa/Conakry','Africa/Dakar','Africa/Dar_es_Salaam','Africa/Djibouti','Africa/Douala',\n'Africa/El_Aaiun','Africa/Freetown','Africa/Gaborone','Africa/Harare','Africa/Johannesburg','Africa/Juba','Africa/Kampala',\n'Africa/Khartoum','Africa/Kigali','Africa/Kinshasa','Africa/Lagos','Africa/Libreville','Africa/Lome','Africa/Luanda',\n'Africa/Lubumbashi','Africa/Lusaka','Africa/Malabo','Africa/Maputo','Africa/Maseru','Africa/Mbabane','Africa/Mogadishu',\n'Africa/Monrovia','Africa/Nairobi','Africa/Ndjamena','Africa/Niamey','Africa/Nouakchott','Africa/Ouagadougou',\n'Africa/Porto-Novo','Africa/Sao_Tome','Africa/Timbuktu','Africa/Tripoli','Africa/Tunis','Africa/Windhoek',\n\n'America/Adak','America/Anchorage','America/Anguilla','America/Antigua','America/Araguaina','America/Argentina/Buenos_Aires',\n'America/Argentina/Catamarca','America/Argentina/ComodRivadavia','America/Argentina/Cordoba','America/Argentina/Jujuy',\n'America/Argentina/La_Rioja','America/Argentina/Mendoza','America/Argentina/Rio_Gallegos','America/Argentina/Salta',\n'America/Argentina/San_Juan','America/Argentina/San_Luis','America/Argentina/Tucuman','America/Argentina/Ushuaia',\n'America/Aruba','America/Asuncion','America/Atikokan','America/Atka','America/Bahia','America/Bahia_Banderas',\n'America/Barbados','America/Belem','America/Belize','America/Blanc-Sablon','America/Boa_Vista','America/Bogota',\n'America/Boise','America/Buenos_Aires','America/Cambridge_Bay','America/Campo_Grande','America/Cancun','America/Caracas',\n'America/Catamarca','America/Cayenne','America/Cayman','America/Chicago','America/Chihuahua','America/Ciudad_Juarez',\n'America/Coral_Harbour','America/Cordoba','America/Costa_Rica','America/Creston','America/Cuiaba','America/Curacao',\n'America/Danmarkshavn','America/Dawson','America/Dawson_Creek','America/Denver','America/Detroit','America/Dominica',\n'America/Edmonton','America/Eirunepe','America/El_Salvador','America/Ensenada','America/Fort_Nelson','America/Fort_Wayne',\n'America/Fortaleza','America/Glace_Bay','America/Godthab','America/Goose_Bay','America/Grand_Turk','America/Grenada',\n'America/Guadeloupe','America/Guatemala','America/Guayaquil','America/Guyana','America/Halifax','America/Havana',\n'America/Hermosillo','America/Indiana/Indianapolis','America/Indiana/Knox','America/Indiana/Marengo',\n'America/Indiana/Petersburg','America/Indiana/Tell_City','America/Indiana/Vevay','America/Indiana/Vincennes',\n'America/Indiana/Winamac','America/Indianapolis','America/Inuvik','America/Iqaluit','America/Jamaica','America/Jujuy',\n'America/Juneau','America/Kentucky/Louisville','America/Kentucky/Monticello','America/Knox_IN','America/Kralendijk',\n'America/La_Paz','America/Lima','America/Los_Angeles','America/Louisville','America/Lower_Princes','America/Maceio',\n'America/Managua','America/Manaus','America/Marigot','America/Martinique','America/Matamoros','America/Mazatlan',\n'America/Mendoza','America/Menominee','America/Merida','America/Metlakatla','America/Mexico_City','America/Miquelon',\n'America/Moncton','America/Monterrey','America/Montevideo','America/Montreal','America/Montserrat','America/Nassau',\n'America/New_York','America/Nipigon','America/Nome','America/Noronha','America/North_Dakota/Beulah',\n'America/North_Dakota/Center','America/North_Dakota/New_Salem','America/Nuuk','America/Ojinaga','America/Panama',\n'America/Pangnirtung','America/Paramaribo','America/Phoenix','America/Port-au-Prince','America/Port_of_Spain',\n'America/Porto_Acre','America/Porto_Velho','America/Puerto_Rico','America/Punta_Arenas','America/Rainy_River',\n'America/Rankin_Inlet','America/Recife','America/Regina','America/Resolute','America/Rio_Branco','America/Rosario',\n'America/Santa_Isabel','America/Santarem','America/Santiago','America/Santo_Domingo','America/Sao_Paulo',\n'America/Scoresbysund','America/Shiprock','America/Sitka','America/St_Barthelemy','America/St_Johns','America/St_Kitts',\n'America/St_Lucia','America/St_Thomas','America/St_Vincent','America/Swift_Current','America/Tegucigalpa',\n'America/Thule','America/Thunder_Bay','America/Tijuana','America/Toronto','America/Tortola','America/Vancouver',\n'America/Virgin','America/Whitehorse','America/Winnipeg','America/Yakutat','America/Yellowknife',\n'America/Coyhaique', // tzdata2025b\n\n'Antarctica/Casey','Antarctica/Davis','Antarctica/DumontDUrville','Antarctica/Macquarie','Antarctica/Mawson',\n'Antarctica/McMurdo','Antarctica/Palmer','Antarctica/Rothera','Antarctica/South_Pole','Antarctica/Syowa',\n'Antarctica/Troll','Antarctica/Vostok','Arctic/Longyearbyen',\n\n'Asia/Aden','Asia/Almaty','Asia/Amman','Asia/Anadyr','Asia/Aqtau','Asia/Aqtobe','Asia/Ashgabat','Asia/Ashkhabad',\n'Asia/Atyrau','Asia/Baghdad','Asia/Bahrain','Asia/Baku','Asia/Bangkok','Asia/Barnaul','Asia/Beirut','Asia/Bishkek',\n'Asia/Brunei','Asia/Calcutta','Asia/Chita','Asia/Choibalsan','Asia/Chongqing','Asia/Chungking','Asia/Colombo',\n'Asia/Dacca','Asia/Damascus','Asia/Dhaka','Asia/Dili','Asia/Dubai','Asia/Dushanbe','Asia/Famagusta','Asia/Gaza',\n'Asia/Harbin','Asia/Hebron','Asia/Ho_Chi_Minh','Asia/Hong_Kong','Asia/Hovd','Asia/Irkutsk','Asia/Istanbul',\n'Asia/Jakarta','Asia/Jayapura','Asia/Jerusalem','Asia/Kabul','Asia/Kamchatka','Asia/Karachi','Asia/Kashgar',\n'Asia/Kathmandu','Asia/Katmandu','Asia/Khandyga','Asia/Kolkata','Asia/Krasnoyarsk','Asia/Kuala_Lumpur','Asia/Kuching',\n'Asia/Kuwait','Asia/Macao','Asia/Macau','Asia/Magadan','Asia/Makassar','Asia/Manila','Asia/Muscat','Asia/Nicosia',\n'Asia/Novokuznetsk','Asia/Novosibirsk','Asia/Omsk','Asia/Oral','Asia/Phnom_Penh','Asia/Pontianak','Asia/Pyongyang',\n'Asia/Qatar','Asia/Qostanay','Asia/Qyzylorda','Asia/Rangoon','Asia/Riyadh','Asia/Saigon','Asia/Sakhalin',\n'Asia/Samarkand','Asia/Seoul','Asia/Shanghai','Asia/Singapore','Asia/Srednekolymsk','Asia/Taipei','Asia/Tashkent',\n'Asia/Tbilisi','Asia/Tehran','Asia/Tel_Aviv','Asia/Thimbu','Asia/Thimphu','Asia/Tokyo','Asia/Tomsk','Asia/Ujung_Pandang',\n'Asia/Ulaanbaatar','Asia/Ulan_Bator','Asia/Urumqi','Asia/Ust-Nera','Asia/Vientiane','Asia/Vladivostok','Asia/Yakutsk',\n'Asia/Yangon','Asia/Yekaterinburg','Asia/Yerevan',\n\n'Atlantic/Azores','Atlantic/Bermuda','Atlantic/Canary','Atlantic/Cape_Verde','Atlantic/Faeroe','Atlantic/Faroe',\n'Atlantic/Jan_Mayen','Atlantic/Madeira','Atlantic/Reykjavik','Atlantic/South_Georgia','Atlantic/St_Helena',\n'Atlantic/Stanley',\n\n'Australia/ACT','Australia/Adelaide','Australia/Brisbane','Australia/Broken_Hill','Australia/Canberra','Australia/Currie',\n'Australia/Darwin','Australia/Eucla','Australia/Hobart','Australia/LHI','Australia/Lindeman','Australia/Lord_Howe',\n'Australia/Melbourne','Australia/NSW','Australia/North','Australia/Perth','Australia/Queensland','Australia/South',\n'Australia/Sydney','Australia/Tasmania','Australia/Victoria','Australia/West','Australia/Yancowinna',\n\n'Brazil/Acre','Brazil/DeNoronha','Brazil/East','Brazil/West',\n\n'Canada/Atlantic','Canada/Central','Canada/Eastern','Canada/Mountain','Canada/Newfoundland','Canada/Pacific',\n'Canada/Saskatchewan','Canada/Yukon',\n\n'Chile/Continental','Chile/EasterIsland',\n\n'Etc/GMT','Etc/GMT+0','Etc/GMT+1','Etc/GMT+10','Etc/GMT+11','Etc/GMT+12','Etc/GMT+2','Etc/GMT+3','Etc/GMT+4',\n'Etc/GMT+5','Etc/GMT+6','Etc/GMT+7','Etc/GMT+8','Etc/GMT+9','Etc/GMT-0','Etc/GMT-1','Etc/GMT-10','Etc/GMT-11',\n'Etc/GMT-12','Etc/GMT-13','Etc/GMT-14','Etc/GMT-2','Etc/GMT-3','Etc/GMT-4','Etc/GMT-5','Etc/GMT-6','Etc/GMT-7',\n'Etc/GMT-8','Etc/GMT-9','Etc/GMT0','Etc/Greenwich','Etc/UCT','Etc/UTC','Etc/Universal','Etc/Zulu',\n\n'Europe/Amsterdam','Europe/Andorra','Europe/Astrakhan','Europe/Athens','Europe/Belfast','Europe/Belgrade','Europe/Berlin',\n'Europe/Bratislava','Europe/Brussels','Europe/Bucharest','Europe/Budapest','Europe/Busingen','Europe/Chisinau',\n'Europe/Copenhagen','Europe/Dublin','Europe/Gibraltar','Europe/Guernsey','Europe/Helsinki','Europe/Isle_of_Man',\n'Europe/Istanbul','Europe/Jersey','Europe/Kaliningrad','Europe/Kiev','Europe/Kirov','Europe/Kyiv','Europe/Lisbon',\n'Europe/Ljubljana','Europe/London','Europe/Luxembourg','Europe/Madrid','Europe/Malta','Europe/Mariehamn','Europe/Minsk',\n'Europe/Monaco','Europe/Moscow','Europe/Nicosia','Europe/Oslo','Europe/Paris','Europe/Podgorica','Europe/Prague','Europe/Riga',\n'Europe/Rome','Europe/Samara','Europe/San_Marino','Europe/Sarajevo','Europe/Saratov','Europe/Simferopol','Europe/Skopje',\n'Europe/Sofia','Europe/Stockholm','Europe/Tallinn','Europe/Tirane','Europe/Tiraspol','Europe/Ulyanovsk','Europe/Uzhgorod',\n'Europe/Vaduz','Europe/Vatican','Europe/Vienna','Europe/Vilnius','Europe/Volgograd','Europe/Warsaw','Europe/Zagreb',\n'Europe/Zaporozhye','Europe/Zurich',\n\n'Indian/Antananarivo','Indian/Chagos','Indian/Christmas','Indian/Cocos','Indian/Comoro','Indian/Kerguelen',\n'Indian/Mahe','Indian/Maldives','Indian/Mauritius','Indian/Mayotte','Indian/Reunion',\n\n'Mexico/BajaNorte','Mexico/BajaSur','Mexico/General',\n\n'Pacific/Apia','Pacific/Auckland','Pacific/Bougainville','Pacific/Chatham','Pacific/Chuuk','Pacific/Easter','Pacific/Efate',\n'Pacific/Enderbury','Pacific/Fakaofo','Pacific/Fiji','Pacific/Funafuti','Pacific/Galapagos','Pacific/Gambier',\n'Pacific/Guadalcanal','Pacific/Guam','Pacific/Honolulu','Pacific/Johnston','Pacific/Kanton','Pacific/Kiritimati',\n'Pacific/Kosrae','Pacific/Kwajalein','Pacific/Majuro','Pacific/Marquesas','Pacific/Midway','Pacific/Nauru',\n'Pacific/Niue','Pacific/Norfolk','Pacific/Noumea','Pacific/Pago_Pago','Pacific/Palau','Pacific/Pitcairn','Pacific/Pohnpei',\n'Pacific/Ponape','Pacific/Port_Moresby','Pacific/Rarotonga','Pacific/Saipan','Pacific/Samoa','Pacific/Tahiti',\n'Pacific/Tarawa','Pacific/Tongatapu','Pacific/Truk','Pacific/Wake','Pacific/Wallis','Pacific/Yap',\n\n'US/Alaska','US/Aleutian','US/Arizona','US/Central','US/East-Indiana','US/Eastern','US/Hawaii','US/Indiana-Starke',\n'US/Michigan','US/Mountain','US/Pacific','US/Samoa'\n]\n\n// language/locale tests\nvar gLocales = [\n\t// ISO_639-1 alpha-2 codes: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\n\t// ISO_639-2 alpha-3 codes: https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes\n\n// not supported in FF\n\t// but keep just in case: we check SupportedLocalesOf in each Intl constructor anyway\n'aa,afar',\n'ab,abkhazian',\n'ae,avestan',\n'an,aragonese',\n'av,avaric',\n'ay,aymara',\n'ba,bashkir',\n'bal,baluchi',\n'bcg,haryanvi',\n'bi,bislama',\n'bqi,luri bakhtiari', // 2020533\n'cak,kaqchikel',\n'ch,chamorro',\n'cho,choctaw',\n'co,corsican',\n'cr,cree',\n'cu,church slavic',\n'dv,divehi',\n'fj,fijian',\n'gn,guarani',\n'ho,hiri motu',\n'ht,haitian',\n'hz,herero',\n'ik,inupiaq',\n'io,ido',\n'iu,inuktitut',\n'kg,kongo',\n'kj,kuanyama',\n'kr,kanuri',\n'kv,komi',\n'la,latin',\n'li,limburgan',\n'ltg,latgalian',\n'meh,mixtec (southwestern tlaxiaco)',\n'mh,marshallese',\n'mix,mixtepec mixtec',\n'na,nauru',\n'ng,ndonga',\n'nr,south ndebele',\n'nv,navajo',\n'ny,chichewa',\n'oj,ojibwa',\n'pap,papiamento',\n'pi,pali',\n'sco,scots',\n'skr,saraiki',\n'sm,samoan',\n'ss,siswati',\n'trs,triqui',\n'ts,tsonga',\n'ty,tahitian',\n've,venda',\n'vo,volapük',\n'wa,walloon',\n\n// supported in FF140+\n'af,afrikaans',\n'agq,aghem',\n'ak,akan',\n'am,amharic',\n'ar,arabic',\n'as,assamese',\n'asa,asu',\n'ast,asturian',\n'az,azerbaijani',\n'bas,basaa',\n'be,belarusian',\n'bem,bemba',\n'bez,bena',\n'bg,bulgarian',\n'bgc,haryanvi',\n'bho,bhojpuri',\n'blo,anii',\n'bm,bambara',\n'bn,bengali',\n'bo,tibetan',\n'br,breton',\n'brx,bodo',\n'bs,bosnian',\n'ca,catalan',\n'ccp,chakma',\n'ce,chechen',\n'ceb,cebuano',\n'cgg,chiga',\n'chr,cherokee',\n'ckb,central kurdish',\n'cs,czech',\n'csw,swampy',\n'cv,chuvash',\n'cy,welsh',\n'da,danish',\n'dav,taita',\n'de,german',\n'dje,zarma',\n'doi,dogri',\n'dsb,lower sorbian',\n'dua,duala',\n'dyo,jola-fonyi',\n'dz,dzongkha',\n'ebu,embu',\n'ee,éwé',\n'el,greek',\n'en,english',\n'eo,esperanto',\n'es,spanish',\n'et,estonian',\n'eu,basque',\n'ewo,ewondo',\n'fa,persian',\n'ff,fulah',\n'fi,finnish',\n'fil,filipino',\n'fo,faroese',\n'fr,french',\n'fur,friulian',\n'fy,frisian',\n'ga,irish',\n'gaa,ga',\n'gd,scottish gaelic',\n'gl,galician',\n'gsw,swiss german',\n'gu,gujarati',\n'guz,gusii',\n'gv,manx',\n'ha,hausa',\n'haw,hawaiian',\n'he,hebrew',\n'hi,hindi',\n'hr,croatian',\n'hsb,upper sorbian',\n'hu,hungarian',\n'hy,armenian',\n'ia,interlingua',\n'id,indonesian',\n'ie,interlingue',\n'ig,igbo',\n'ii,sichuan yi',\n'is,icelandic',\n'it,italian',\n'ja,japanese',\n'jgo,ngomba',\n'jmc,machame',\n'jv,javanese',\n'ka,georgian',\n'kab,kabyle',\n'kam,kamba',\n'kde,makonde',\n'kea,kabuverdianu',\n'kgp,kaingang',\n'khq,koyra chiini',\n'ki,kikuyu',\n'kk,kazakh',\n'kkj,kako',\n'kl,greenlandic',\n'kln,kalenjin',\n'km,khmer',\n'kn,kannada',\n'ko,korean',\n'kok,konkani',\n'ks,kashmiri',\n'ksb,shambala',\n'ksf,bafia',\n'ksh,colognian',\n'ku,kurdish',\n'kw,cornish',\n'kxv,kuvi',\n'ky,kyrgyz',\n'lag,langi',\n'lb,luxembourgish',\n'lg,ganda',\n'lij,ligurian',\n'lkt,lakota',\n'lmo,lombard',\n'ln,lingala',\n'lo,lao',\n'lrc,northern luri',\n'lt,lithuanian',\n'lu,luba-katanga',\n'luo,luo',\n'luy,luyia',\n'lv,latvian',\n'mai,maithili',\n'mas,maasai',\n'mer,meru',\n'mfe,morisyen',\n'mg,malagasy',\n'mgh,makhuwa-meetto',\n'mgo,meta\\'',\n'mi,maori',\n'mk,macedonian',\n'ml,malayalam',\n'mn,mongolian',\n'mni,meitei',\n'mr,marathi',\n'ms,malay',\n'mt,maltese',\n'mua,mundang',\n'my,burmese',\n'mzn,mazanderani',\n'naq,nama',\n'nb,norwegian bokmål',\n'nd,north ndebele',\n'nds,low german',\n'ne,nepali',\n'nl,dutch',\n'nmg,kwasio',\n'nn,norwegian nynorsk',\n'nnh,ngiemboon',\n'no,norwegian',\n'nqo,n’ko',\n'nso,northern sotho',\n'nus,nuer',\n'nyn,nyankole',\n'oc,occitan',\n'om,oromo',\n'or,odia',\n'os,ossetian',\n'pa,punjabi',\n'pcm,nigerian',\n'pl,polish',\n'prg,prussian',\n'ps,pashto',\n'pt,portuguese',\n'qu,quechua',\n'raj,rajasthani',\n'rm,rhaeto-romanic',\n'rn,kirundi',\n'ro,romanian',\n'rof,rombo',\n'ru,russian',\n'rw,kinyarwanda',\n'rwk,rwa',\n'sa,sanskrit',\n'sah,yakut',\n'saq,samburu',\n'sat,santali',\n'sbp,sangu',\n'sc,sardinian',\n'sd,sindhi',\n'se,northern sami',\n'seh,sena',\n'ses,koyraboro senni',\n'sg,sango',\n'shi,tachelhit',\n'si,sinhala',\n'sk,slovak',\n'sl,slovenian',\n'smn,inari sámi',\n'sn,shona',\n'so,somali',\n'sq,albanian',\n'sr,serbian',\n'st,southern sotho',\n'su,sundanese',\n'sv,swedish',\n'sw,swahili',\n'syr,syriac',\n'szl,silesian',\n'ta,tamil',\n'te,telugu',\n'teo,teso',\n'tg,tajik',\n'th,thai',\n'ti,tigrinya',\n'tk,turkmen',\n'tn,tswana',\n'to,tongan',\n'tok,toki pona',\n'tr,turkish',\n'tt,tatar',\n'twq,tasawaq',\n'tzm,central atlas tamazight',\n'ug,uighur',\n'uk,ukrainian',\n'ur,urdu',\n'uz,uzbek',\n'vai,vai',\n'vec,venetian',\n'vi,vietnamese',\n'vmw,makhuwa',\n'vun,vunjo',\n'wae,walser',\n'wo,wolof',\n'xh,xhosa',\n'xnr,kangri',\n'xog,soga',\n'yav,yangben',\n'yi,yiddish',\n'yo,yoruba',\n'yrl,nhengatu',\n'yue,cantonese',\n'za,zhuang',\n'zgh,standard moroccan tamazight',\n'zh,chinese',\n'zh-cn,chinese (china)',\n'zh-hans,chinese (simplified)',\n'zh-hant,chinese (traditional)',\n'zh-hk,chinese (hong kong)',\n'zh-sg,chinese (singapore)',\n'zh-tw,chinese (taiwan)',\n'zu,zulu',\n]\n\nvar gLocalesOriginal = gLocales\n\n// ** local files only for dev tests ** //\nlet gLocalesLikely = [\n// the usual suspects\n//*\n'af-na,afrikaans (namibia)',\n'ar-ae,arabic (united arabic emirates)',\n'ar-bh,arabic (bahrain)',\n'ar-dj,arabic (djibouti)',\n'ar-dz,arabic (algeria)',\n'ar-eg,arabic (egypt)',\n'ar-eh,arabic (western sahara)',\n'ar-er,arabic (eritrea)',\n'ar-il,arabic (israel)',\n'ar-iq,arabic (iraq)',\n'ar-km,arabic (cosmoros)',\n'ar-lb,arabic (lebanon)',\n'ar-ly,arabic (libya)',\n'ar-ma,arabic (morocco)',\n'ar-mr,arabic (mauritania)',\n'ar-sa,arabic (saudi arabia)',\n'ar-so,arabic (somalia)',\n'ar-ss,arabic (south sudan)',\n'ar-tn,arabic (tunisia)',\n'az-cyrl,azerbaijani (cyrillic)',\n'bn-in,bengali (india)',\n'bo-in,tibetan (india)',\n'bs-cyrl,bosnian (cyrillic)',\n'ca-fr,catalan (france)',\n'ckb-ir,central kurdish (iran)',\n'de-at,german (austria)',\n'de-ch,german (switzerland)',\n'de-li,german (liechtenstein)',\n'de-lu,german (luxembourg)',\n'dyo-sn,jola-fonyl (senegal)',\n'ee-tg,éwé (togo)',\n'en-001,english',\n'en-150,english (europe)',\n'en-ae,english (united arab emirates)',\n'en-ag,english (antigua & barbuda)',\n'en-ai,english (anguilla)',\n'en-at,english (austria)',\n'en-au,english (australia)',\n'en-bb,english (barbados)',\n'en-be,english (belgium)',\n'en-bi,english (burundi)',\n'en-bm,english (bermuda)',\n'en-bs,english (bahamas)',\n'en-bw,english (botswana)',\n'en-bz,english (belize)',\n'en-ca,english (canada)',\n'en-cc,english (cocos islands)',\n'en-ch,english (switzerland)',\n'en-dk,english (denmark)',\n'en-er,english (eritrea)',\n'en-fi,english (finland)',\n'en-fj,english (fiji)',\n'en-fk,english (falkland islands)',\n'en-gb,english (united kingdom)',\n'en-gg,english (guernsey)',\n'en-gh,english (ghana)',\n'en-gi,english (gibraltar)',\n'en-gm,english (gambia)',\n'en-gu,english (guam)',\n'en-gy,english (guyana)',\n'en-hk,english (hong kong)',\n'en-ie,english (ireland)',\n'en-il,english (israel)',\n'en-in,english (india)',\n'en-jm,english (jamaica)',\n'en-ke,english (kenya)',\n'en-ky,english (cayman islands)',\n'en-lr,english (liberia)',\n'en-ls,english (lesotho)',\n'en-mg,english (madagascar)',\n'en-mh,english (marshall islands)',\n'en-mo,english (macau)',\n'en-mt,english (malta)',\n'en-mu,english (mauritius)',\n'en-mw,english (malawai)',\n'en-my,english (malaysia)',\n'en-na,english (namibia)',\n'en-ng,english (nigeria)',\n'en-nz,english (new zealand)',\n'en-pg,english (papua new guinea)',\n'en-pk,english (pakistan)',\n'en-pr,english (puerto rico)',\n'en-rw,english (rwanda)',\n'en-sb,english (solomon islands)',\n'en-sc,english (seychelles)',\n'en-se,english (sweden)',\n'en-sg,english (singapore)',\n'en-sh,english (saint helena)',\n'en-sl,english (sierra leone)',\n'en-ss,english (south sudan)',\n'en-sx,english (sint maarten)',\n'en-sz,english (swaziland)',\n'en-to,english (tonga)',\n'en-tt,english (trinidad & tobago)',\n'en-tz,english (tanzania)',\n'en-ug,english (uganda)',\n'en-vu,english (vanuatu)',\n'en-ws,english (samoa)',\n'en-za,english (south africa)',\n'en-zm,english (zambia)',\n'en-zw,english (zimbabwe)',\n'es-419,spanish (latin america and the caribbean)',\n'es-ar,spanish (argentina)',\n'es-bo,spanish (bolivia)',\n'es-br,spanish (brazil)',\n'es-bz,spanish (belize)',\n'es-cl,spanish (chile)',\n'es-co,spanish (colombia)',\n'es-cr,spanish (costa rica)',\n'es-cu,spanish (cuba)',\n'es-do,spanish (dominican republic)',\n'es-ec,spanish (ecuador)',\n'es-gq,spanish (equatorial guinea)',\n'es-gt,spanish (guatemala)',\n'es-hn,spanish (honduras)',\n'es-mx,spanish (mexico)',\n'es-ni,spanish (nicaragua)',\n'es-pa,spanish (panama)',\n'es-pe,spanish (peru)',\n'es-ph,spanish (philippines)',\n'es-pr,spanish (puerto rico)',\n'es-py,spanish (paraguay)',\n'es-sv,spanish (el salvador)',\n'es-us,spanish (united states)',\n'es-uy,spanish (uruguay)',\n'es-ve,spanish (venezuela)',\n'fa-af,persian (afghanistan)',\n'ff-adlm,fulah (adlam)',\n'ff-adlm-bf,fulah (adlam burkina faso)',\n'ff-adlm-gh,fulah (adlam ghana)',\n'ff-adlm-gm,fulah (adlam gambia)',\n'ff-adlm-lr,fulah (adlamd liberia)',\n'ff-adlm-mr,fulah (adlam mauritania)',\n'ff-adlm-ng,fulah (adlam nigeria)',\n'ff-adlm-sl,fulah (adlam sierra leone)',\n'ff-gh,fulah (ghana)',\n'ff-gn,fulah (guinea)',\n'ff-mr,fulah (mauritania)',\n'fo-dk,faroese (denmark)',\n'fr-be,french (belgium)',\n'fr-bi,french (burundi)',\n'fr-ca,french (canada)',\n'fr-cd,french (congo kinshasa)',\n'fr-ch,french (switzerland)',\n'fr-cm,french (cameroon)',\n'fr-dj,french (djibouti)',\n'fr-dz,french (algeria)',\n'fr-gf,french (french guiana)',\n'fr-gn,french (guinea)',\n'fr-gp,french (guadeloupe)',\n'fr-ht,french (haiti)',\n'fr-km,french (comoros)',\n'fr-lu,french (luxembourg)',\n'fr-ma,french (morocco)',\n'fr-ml,french (mali)',\n'fr-mg,french (madagascar)',\n'fr-mr,french (mauritania)',\n'fr-mu,french (mauritius)',\n'fr-rw,french (rwanda)',\n'fr-sc,french (seychelles)',\n'fr-sn,french (senegal)',\n'fr-sy,french (syria)',\n'fr-tn,french (tunisia)',\n'fr-vu,french (vanuatu)',\n'ha-gh,hausa (ghana)',\n'hi-latn,hindi (latin)',\n'hr-ba,croatian (bosnia & herzegovina)',\n'it-ch,italian (switzerland)',\n'kea-cv,kabuverdianu (cape verde)',\n'kk-cn,kazakh (china)',\n'ko-kp,korean (north korea)',\n'kok-in,konkani (india)',\n'kok-latn,konkani (latin)',\n'knn-knda,konkani (kannada)', // \n'knn-latn,konkani (latin)', // \n'ks-deva,kashmiri (devanagari)',\n'kxv-telu,kuvi (telugu)',\n'ln-ao,lingala (angola)',\n'lrc-iq,northern luri (iraq)',\n'mas-tz,masia (tanzania)',\n'ms-bn,malay (brunei)',\n'ms-id,malay (indonesia)',\n'ms-sg,malay (singapore)',\n'ne-in,nepali (india)',\n'nl-aw,dutch (aruba)',\n'nl-be,dutch (belgium)',\n'nl-bq,dutch (caribbean netherlands)',\n'nl-cw,dutch (curaçao)',\n'nl-sr,dutch (suriname)',\n'om-ke,oromo (kenya)',\n'os-ru,ossetian (russia)',\n'pa-arab,punjabi (arabic)',\n'pa-pk,punjabi (pakistan)',\n'ps-pk,pashto (pakistan)',\n'pt-ao,portuguese (angola)',\n'pt-ch,portuguese (switzerland)',\n'pt-cv,portuguese (cape verde)',\n'pt-gw,portuguese (guinea-bissau)',\n'pt-lu,portuguese (luxembourg)',\n'pt-mo,portuguese (macau)',\n'pt-mz,portuguese (mazambique)',\n'pt-pt,portuguese (portugal)',\n'pt-st,portuguese (são tomé & príncipe)',\n'qu-bo,quechua (bolivia)',\n'qu-ec,quechua (ecuador)',\n'ro-md,romanian (moldova)',\n'ru-by,russian (belarus)',\n'ru-kg,russian (kyrgyzstan)',\n'ru-kz,russian (kazakhstan)',\n'ru-md,russian (moldova)',\n'ru-ua,russian (ukraine)',\n'sd-deva,sindhi (devanagari)',\n'se-fi,northern sami (finland)',\n'se-se,northern sami',\n'shi-latn,tachelhit (latin)',\n'so-dj,somali (djibouti)',\n'so-et,somali (ethiopia)',\n'so-ke,somali (kenya)',\n'sq-mk,albanian (macedonia)',\n'sr-ba,serbian (bosnia & herzegovina)',\n'sr-cyrl-ba,serbian (cyrillic bosnia & herzegovina)',\n'sr-cyrl-me,serbian (cyrillic montenegro)',\n'sr-cyrl-xk,serbian (cyrillic kosovo)',\n'sr-latn,serbian (latin)',\n'sr-latn-ba,serbian (latin bosnia & herzegovina)',\n'sr-latn-me,serbian (latin montenegro)',\n'sr-latn-xk,serbian (latin kosovo)',\n'sr-me,serbian (montenegro)',\n'sr-xk,serbian (kosovo)',\n'st-ls,southern sotho',\n'sv-ax,swedish (åland islands)',\n'sv-fi,swedish (finland)',\n'sw-cd,swahili (congo kinshasa)',\n'sw-ke,swahili (kenya)',\n'sw-ug,swahili (uganda)',\n'ta-lk,tamil (sri lanka)',\n'ta-my,tamil (malaysia)',\n'ta-sg,tamil (singapore)',\n'teo-ke,teso (kenya)',\n'ti-er,tigrinya (eritrea)',\n'tn-bw,tswana (botswana)',\n'tr-cy,turkish (cyprus)',\n'tr-tr,turkish (turkey)',\n'ur-in,urdu (india)',\n'uz-af,uzbek (afghanistan)',\n'uz-arab,uzbek (arabic)',\n'uz-cyrl-uz,uzbek (cyrillic uzbekistan)',\n'vai-latn,vai (latin)',\n'yo-bj,yoruba (benin)',\n'yue-cn,cantonese (china)',\n'yue-hans,cantonese (simplified)',\n'zh-hans-hk,chinese (simplified hong kong)',\n'zh-hans-mo,chinese (simplified macau)',\n'zh-hant-mo,chinese (traditional macau)',\n'zh-my,chinese (malaysia)',\n//*/\n]\n\nlet gLocalesExpand = [\n// unlikely\n//*\n'af-arab,afrikaans (arabic)',\n'af-brai,afrikaans (braille)',\n'af-za,afrikaans (south africa)',\n'agq-cm,aghem (cameroon)',\n'ak-arab,akan (arabic)',\n'ak-gh,akan (ghana)',\n'ak-x-asante,akan',\n'am-arab,amharic (arabic)',\n'am-brai,amharic (braille)',\n'am-et,amharic (ethiopic)',\n'ar-001,arabic (world)',\n'ar-brai,arabic (braille)',\n'ar-jo,arabic (jordan)',\n'ar-kw,arabic (kuwait)',\n'ar-om,arabic (oman)',\n'ar-ps,arabic (palestinian territories)',\n'ar-qa,arabic (qatar)',\n'ar-sd,arabic (sudan)',\n'ar-sy,arabic (syria)',\n'ar-syrc,arabic (syraic)',\n'ar-td,arabic (chad)',\n'ar-ye,arabic (yemen)',\n'as-in,assamese (india)',\n'asa-tz,asu (tanzania)',\n'ast-es,asturian (spain)',\n'az-brai,azerbaijani (braille)',\n'az-cyrl-az,azerbaijani (cyrillic azerbaijan)',\n'az-iq,azerbaijani (iraq)',\n'az-ir,azerbaijani (iran)',\n'az-latn,azerbaijani (latin)',\n'az-ru,azerbaijani (russia)',\n'bas-cm,basaa (cameroon)',\n'be-arab,belarusian (arabic)',\n'be-brai,belarusian (braille)',\n'be-by,belarusian (belarus)',\n'be-latn,belarusian',\n'bem-brai,bemba (braille)',\n'bem-zm,bemba (zambia)',\n'bez-tz,bena (tanzania)',\n'bg-bg,bulgarian (bulgaria)',\n'bg-brai,bulgarian (braille)',\n'bg-cyrs,bulgarian',\n'bg-latn,bulgarian',\n'bho-kthi,bhojpuri (kaithi)',\n'bm-arab,bambara (arabic)',\n'bm-ml,bambara (mali)',\n'bm-nkoo,bambara',\n'bn-bd,bengali (bangladesh)',\n'bn-brai,bengali (braille)',\n'bn-newa,bengali (newar)',\n'bo-cn,tibetan (china)',\n'bo-latn,tibetan',\n'bo-marc,tibetan',\n'bo-phag,tibetan',\n'br-fr,breton (france)',\n'br-ogam,breton',\n'brx-beng,bodo (bengali)',\n'brx-in,bodo (india)',\n'brx-latn,bodo (latin)',\n'bs-arab,bosnian (arabic)',\n'bs-latn,bosnian (latin)',\n'ca-ad,catalan (andorra)',\n'ca-es,catalan (spain)',\n'ca-it,catalan (italy)',\n'ca-valencia,catalan (valencia)',\n'ccp-bd,chakma (bangladesh)',\n'ccp-beng,chakma (bengali)',\n'ccp-in,chakma (india)',\n'ccp-latn,chakma',\n'ce-arab,chechen (arabic)',\n'ce-latn,chechen',\n'ce-ru,chechen (russia)',\n'ceb-brai,cebuano (braille)',\n'cgg-ug,chiga (uganda)',\n'chr-latn,cherokee',\n'chr-us,cherokee (united states)',\n'ckb-iq,central kurdish (iraq)',\n'ckb-latn,central kurdish',\n'cs-brai,czech (braille)',\n'cs-cz,czech (czechia)',\n'cy-brai,welsh (braille)',\n'cy-gb,welsh (united kingdom)',\n'cy-ogam,welsh',\n'da-brai,danish (braille)',\n'da-dk,danish (denmark)',\n'da-gl,danish (greenland)',\n'da-runr,danish (runic)',\n'dav-ke,taita (kenya)',\n'de-be,german (belgium)',\n'de-brai,german (braille)',\n'de-de,german (germany)',\n'de-dupl,german (duployan)',\n'de-it,german (italy)',\n'de-latf,german',\n'de-runr,german (runic)',\n'dje-arab,zarma (arabic)',\n'dje-brai,zarma (braille)',\n'dje-ne,zarma (niger)',\n'doi-arab,dogri (arabic)',\n'doi-dogr,dogri',\n'doi-takr,dogri',\n'dsb-de,lower sorbian (germany)',\n'dua-cm,duala (cameroon)',\n'dyo-arab,jola-fonyi (arabic)',\n'dz-bt,dzongkha (bhutan)',\n'ebu-ke,embu (kenya)',\n'ee-brai,éwé (braille)',\n'ee-gh,éwé (ghana)',\n'el-brai,greek (braille)',\n'el-cy,greek (cyprus)',\n'el-cyrl,greek (cyrillic)',\n'el-gr,greek (greece)',\n'en-as,english (american samoa)',\n'en-brai,english (braille)',\n'en-ck,english (cook islands)',\n'en-cm,english (cameroon)',\n'en-cx,english (christmas island)',\n'en-cy,english (cyprus)',\n'en-de,english (germany)',\n'en-dg,english (diego garcia)',\n'en-dm,english (dominica)',\n'en-dsrt,english (deseret)',\n'en-dupl,english (duployan)',\n'en-es,english (spain)', // ICU77 CLDR47\n'en-fm,english (micronesia)',\n'en-gd,english (grenada)',\n'en-im,english (isle of man)',\n'en-io,english (british indian oceam territory)',\n'en-je,english (jersey)',\n'en-ki,english (kiribati)',\n'en-kn,english (saint kitts & nevis)',\n'en-lc,english (saint lucia)',\n'en-ma,english (morocco)',\n'en-mp,english (northern mariana islands)',\n'en-ms,english (montserrat)',\n'en-nf,english (norfolk island)',\n'en-nl,english (netherlands)',\n'en-nr,english (nauru)',\n'en-nu,english (niue)',\n'en-ph,english (philippines)',\n'en-pn,english (pitcarin islands)',\n'en-pw,english (palau)',\n'en-runr,english (runic)',\n'en-sd,english (sudan)',\n'en-shaw,english (shaw)',\n'en-si,english (solvenia)',\n'en-tc,english (turks & caicos islands)',\n'en-tk,english (tokelau)',\n'en-tv,english (tuvalu)',\n'en-um,english (us outlying islands)',\n'en-us,english (united states)',\n'en-vc,english (saint vincent & grenadines)',\n'en-vg,english (british virgin islands)',\n'en-vi,english (us virgin islands)',\n'es-brai,spanish (braille)',\n'es-dupl,spanish (duployan)',\n'es-ea,spanish (ceuta & melilla)',\n'es-es,spanish (spain)',\n'es-ic,spanish (canary islands)',\n'et-brai,estonian (braille)',\n'et-ee,estonian (estonia)',\n'eu-es,basque (spain)',\n'ewo-cm,ewondo (cameroon)',\n'fa-ir,persian (iran)',\n'ff-adlm-cm,fulah (adlam cameroon)',\n'ff-adlm-gw,fulah (adlam guinea-bissau)',\n'ff-adlm-ne,fulah (adlam niger)',\n'ff-adlm-sn,fulah (adlam senegal)',\n'ff-arab,fulah (arabic)',\n'ff-bf,fulah (burkina faso)',\n'ff-cm,fulah (cameroon)',\n'ff-gm,fulah (gambia)',\n'ff-gw,fulah (guinea-bissau)',\n'ff-lr,fulah (liberia)',\n'ff-ne,fulah (niger)',\n'ff-ng,fulah (nigeria)',\n'ff-sl,fulah (sierra leone)',\n'ff-sn,fulah (senegal)',\n'fi-brai,finnish (braille)',\n'fi-fi,finnish (finland)',\n'fil-ph,filipino (philippines)',\n'fil-brai,filipino (braille)',\n'fil-buhd,filipino',\n'fil-hano,filipino',\n'fil-tglg,filipino (tagalog)',\n'fo-fo,faroese (faroe islands)',\n'fo-runr,faroese (runic)',\n'fr-bf,french (burkina faso)',\n'fr-bj,french (benin)',\n'fr-bl,french (saint barthélemy)',\n'fr-brai,french (braille)',\n'fr-cf,french (central african republic)',\n'fr-cg,french (congo brazzaville)',\n'fr-ci,french (côte d’ivoire)',\n'fr-dupl,french (duployan)',\n'fr-fr,french (france)',\n'fr-ga,french (gabon)',\n'fr-gq,french (equatorial guinea)',\n'fr-mc,french (monaco)',\n'fr-mf,french (saint martin)',\n'fr-mq,french (martinique)',\n'fr-nc,french (new caledonia)',\n'fr-ne,french (niger)',\n'fr-pf,french (french polynesia)',\n'fr-pm,french (saint pierre & miquelon)',\n'fr-re,french (réunion)',\n'fr-td,french (chad)',\n'fr-tg,french (togo)',\n'fr-wf,french (wallis & futuna)',\n'fr-yt,french (mayotte)',\n'fur-it,friulian (italy)',\n'fy-nl,frisian (netherlands)',\n'ga-ie,irish (ireland)',\n'ga-latg,irish',\n'ga-ogam,irish',\n'gd-gb,scottish gaelic (united kingdom)',\n'gd-ogam,scottish gaelic',\n'gl-es,galician (spain)',\n'gsw-ch,swiss german (switzerland)',\n'gsw-fr,swiss german (france)',\n'gsw-li,swiss german (liechtenstein)',\n'gu-brai,gujarati (braille)',\n'gu-in,gujarati (india)',\n'gu-khoj,gujarati',\n'gv-im,manx (isle of man)',\n'gv-ogam,manx',\n'ha-arab,hausa (arabic)',\n'ha-brai,hausa (braille)',\n'ha-cm,hausa (cameroon)',\n'ha-latn-ne,hausa (latin niger)',\n'ha-ne,hausa (niger)',\n'ha-ng,hausa (nigeria)',\n'ha-sd,hausa (sudan)',\n'haw-us,hawaiian (united states)',\n'he-brai,hebrew (braille)',\n'he-il,hebrew (israel)',\n'hi-brai,hindi (braille)',\n'hi-in,hindi (india)',\n'hi-mahj,hindi',\n'hi-newa,hindi (newar)',\n'hr-brai,croatian (braille)',\n'hr-hr,croatian (croatia)',\n'hsb-de,upper sorbian (germany)',\n'hu-brai,hungarian (braille)',\n'hu-hu,hungarian (hungary',\n'hy-am,armenian (armenia)',\n'hy-brai,armenian (braille)',\n'id-arab,indonesian (arabic)',\n'id-brai,indonesian (braille)',\n'id-id,indonesian (indonesia)',\n'ig-ng,igbo (niger)',\n'ii-cn,sichuan yi (china)',\n'ii-latn,sichuan yi (latin)',\n'is-is,icelandic (iceland)',\n'is-runr,icelandic (runic)',\n'it-brai,italian (braille)',\n'it-it,italian (italy)',\n'it-sm,italian (san marino)',\n'it-va,italian (vatican city)',\n'ja-brai,japanese (braille)',\n'ja-jp,japanese (japan)',\n'ja-latn,japanese (latin)',\n'jmc-tz,machame (tanzania)',\n'jv-arab,javanese (arabic)',\n'jv-java,javanese',\n'ka-brai,georgian (braille)',\n'ka-ge,georgian (georgia)',\n'ka-geok,georgian',\n'kab-arab,kabyle (arabic)',\n'kab-dz,kabyle (algeria)',\n'kab-tfng,amazigh (tifinagh)',\n'kam-ke,kamba (kenya)',\n'kde-tz,makonde (tanzania)',\n'khq-ml,koyra chiini (mali)',\n'ki-ke,kikuyu (kenya)',\n'kk-af,kazakh (afghanistan)',\n'kk-brai,kazakh (braille)',\n'kk-ir,kazakh',\n'kk-kz,kazakh (kazakhstan)',\n'kk-latn,kazakh',\n'kk-mn,kazakh',\n'kkj-cm,kako (cameroon)',\n'kkj-td,kako',\n'kl-gl,greenlandic (greenland)',\n'kln-ke,kalenjin (kenya)',\n'km-kh,khmer (cambodia)',\n'kn-brai,kannada (braille)',\n'kn-in,kannada (india)',\n'kn-nand,kannada',\n'ko-brai,korean (braille)',\n'ko-kr,korean (south korea)',\n'ko-latn,korean (latin)',\n'kok-knda,konkani (kannada)',\n'ks-in,kashmiri (india)',\n'ks-latn,kashmiri (latin)',\n'ks-shrd,kashmiri',\n'ksb-tz,shambala (tanzania)',\n'ksf-cm,bafia (cameroon)',\n'ksh-de,colognian (germany)',\n'ku-arab,kurdish (arabic)',\n'ku-arab-tr,kurdish (arabic)',\n'ku-armn,kurdish',\n'ku-cyrl,kurdish (cyrillic)',\n'ku-cyrl-az,kurdish (azerbaijan)',\n'ku-lb,kurdish (lebanon)',\n'ku-yezi-ge,kurdish',\n'kw-gb,cornish (united kingdom)',\n'kw-ogam,cornish',\n'ky-cn,kyrgyz (china)',\n'ky-kg,kyrgyz (kyrgyzstan)',\n'ky-tr,kyrgyz',\n'lag-tz,langi (tanzania)',\n'lb-lu,luxembourgish (luxembourg)',\n'lg-arab,ganda (arabic)',\n'lg-ug,ganda (uganda)',\n'lkt-us,lakota (united states)',\n'ln-cd,lingala (congo kinshasa)',\n'ln-cf,lingala (central african republic)',\n'ln-cg,lingala (congo brazzaville)',\n'lo-la,lao (laos)',\n'lrc-ir,northern luri (iran)',\n'lt-latf,lithuanian',\n'lt-lt,lithuanian (lithuania)',\n'lu-cd,luba-katanga (congo kinshasa)',\n'luo-ke,luo (kenya)',\n'luy-ke,luyia (kenya)',\n'lv-brai,latvian (braille)',\n'lv-lv,latvian (latvia)',\n'mai-kthi,maithili (kaithi)',\n'mai-newa,maithili (newar)',\n'mai-tirh,maithili (tirhuta)',\n'mas-ke,masia (kenya)',\n'mer-ke,meru (kenya)',\n'mfe-mu,morisyen (mauritius)',\n'mg-arab,malagasy (arabic)',\n'mg-brai,malagasy (braille)',\n'mg-mg,malagasy (madagascar)',\n'mgh-mz,makhuwa-meetto (mozambique)',\n'mgo-cm,meta\\' (cameroon)',\n'mk-brai,macedonian (braille)',\n'mk-mk,macedonian (north macedonia)',\n'ml-arab,malayalam (arabic)',\n'ml-brai,malayalam (braille)',\n'ml-in,malayalam (india)',\n'mn-brai,mongolian (braille)',\n'mn-cn,mongolian (china)',\n'mn-mn,mongolian (mongolia)',\n'mn-phag,mongolian',\n'mn-phag-mn, halh',\n'mn-tibt,mongolian',\n'mni-brai,meitei (braille)',\n'mni-mtei,meitei',\n'mr-brai,marathi (braille)',\n'mr-in,marathi (india)',\n'mr-modi,marathi',\n'ms-arab,malay (arabic)',\n'ms-brai,malay (braille)',\n'ms-cc,malay',\n'ms-my,malay (malaysia)',\n'mt-arab,maltese (arabic)',\n'mt-brai,maltese (braille)',\n'mt-mt,maltese (malta)',\n'mua-cm,mundang (cameroon)',\n'my-brai,burmese (braille)',\n'my-mm,burmese (burma)',\n'mzn-ir,mazanderani (iran)',\n'naq-na,nama (namibia)',\n'nb-no,norwegian bokmål (norway)',\n'nb-sj,norwegian bokmål (svalbard & jan mayen)',\n'nd-zw,north ndebele (zimbabwe)',\n'ne-brai,nepali (braille)',\n'ne-newa,nepali (newar)',\n'ne-np,nepali (nepal)',\n'nl-brai,dutch (braille)',\n'nl-nl,dutch (netherlands)',\n'nl-sx,dutch (sint maarten)',\n'nmg-gq,kwasio',\n'nn-no,norwegian nynorsk (norway',\n'nnh-cm,ngiemboon (cameroon)',\n'no-runr,norwegian (runic)',\n'nus-sd,nuer (sudan)',\n'nus-ss,nuer (south sudan)',\n'nyn-ug,nyankole (uganda)',\n'om-arab,oromo (arabic)',\n'om-et,oromo (ethiopia)',\n'om-ethi,oromo (ethiopic)',\n'or-brai,odia (braille)',\n'or-in,odia (india)',\n'os-ge,ossetian (georgia)',\n'os-geor,ossetian',\n'os-latn,ossetian (latin)',\n'pa-guru,punjabi (gurmukhi)',\n'pa-in,punjabi (india)',\n'pa-khoj,punjabi',\n'pa-mahj,punjabi',\n'pl-brai,polish (braille)',\n'pl-pl,polish (poland)',\n'pt-br,portuguese (brazil)',\n'pt-brai,portuguese (braille)',\n'pt-gq,portuguese (equatorial guinea)',\n'pt-tl,portuguese (timor-leste)',\n'qu-pe,quechua (peru)',\n'raj-latn,rajasthani (latin)',\n'rm-ch,rhaeto-romanic (switzerland)',\n'rn-bi,kirundi (burundi)',\n'rn-brai,rundi (braille)',\n'ro-brai,romanian (braille)',\n'ro-cyrl,romanian (cyrillic)',\n'ro-cyrs,romanian',\n'ro-dupl,romanian (duployan)',\n'ro-ro,romanian (romania)',\n'rof-tz,rombo (tanzania)',\n'ru-brai,russian (braille)',\n'ru-ru,russian (russia)',\n'rw-brai,kinyarwanda (braille)',\n'rw-rw,kinyarwanda (rwanda)',\n'rwk-tz,rwa (tanzania)',\n'sa-bhks,sanskrit',\n'sa-gran,sanskrit',\n'sa-kawi,sanskrit',\n'sa-khar,sanskrit',\n'sa-mymr,sanskrit',\n'sa-nand,sanskrit',\n'sa-newa,sanskrit',\n'sa-shrd,sanskrit',\n'sa-sidd,sanskrit',\n'sa-sinh,sanskrit',\n'sa-tirh,sanskrit (tirhuta)',\n'sah-cyrs,yakut',\n'sah-latn,yakut (latin)',\n'sah-ru,yakut (russia)',\n'saq-ke,samburu (kenya)',\n'sat-beng,santali (bengali)',\n'sat-deva,santali (devanagari)',\n'sat-latn,santali (latin)',\n'sat-orya,santali',\n'sbp-tz,sangu (tanzania)',\n'sd-guru,sindhi',\n'sd-khoj,sindhi',\n'sd-khoj-pk,sindhi',\n'sd-sind,sindhi',\n'se-cyrl,northern sami (cyrillic)',\n'se-no,northern sami (norway)',\n'se-sw,northern sami (sweden)',\n'seh-mz,sena (mozambique)',\n'seh-x-podzu,sena',\n'ses-ml,koyraboro senni (mali)',\n'sg-cf,sango (central african republicj)',\n'shi-arab,tachelhit (arabic)',\n'shi-tfng,tachelhit (tifinagh)',\n'si-brai,sinhala (braille)',\n'si-lk,sinhala (sri lanka)',\n'sk-brai,slovak (braille)',\n'sk-sk,slovak (slovakia)',\n'sl-brai,slovenian (braille)',\n'sl-si,slovenian (slovenia)',\n'smn-fi,inari sámi (finland)',\n'sn-brai,shona (braille)',\n'sn-zw,shona (zimbabwe)',\n'so-arab,somali (arabic)',\n'so-osma,somali',\n'so-so,somali (somalia)',\n'sq-al,albanian (albania)',\n'sq-brai,albanian (braille)',\n'sq-elba,albanian',\n'sq-grek,albanian',\n'sq-vith,albanian',\n'sq-xk,albanian (kosovo)',\n'sr-brai,serbian (braille)',\n'sr-cyrl,serbian (cyrillic)',\n'sr-ro,serbian',\n'sr-ru,serbian',\n'sr-tr,serbian',\n'su-arab,sundanese (arabic)',\n'su-java,sundanese',\n'su-sund,sundanese',\n'sv-brai,swedish (braille)',\n'sv-runr,swedish (runic)',\n'sv-se,swedish (sweden)',\n'sw-arab,swahili (arabic)',\n'sw-arab-cd,swahili (arabic congo kinshasa)',\n'sw-brai,swahili (braille)',\n'sw-tz,swahili (tanzania)',\n'ta-arab,tamil (arabic)',\n'ta-brai,tamil (braille)',\n'ta-in,tamil (india)',\n'te-brai,telugu (braille)',\n'te-in,telugu (india)',\n'teo-ug,teso (uganda)',\n'tg-arab,tajik (arabic)',\n'tg-hebr,tajik',\n'tg-latn,tajik',\n'tg-tj,tajik (tajikistan)',\n'th-brai,thai (braille)',\n'th-th,thai (thailand)',\n'ti-arab,tigrinya (arabic)',\n'ti-et,tigrinya (ethiopia)',\n'tk-arab,turkmen (arabic)',\n'tk-arab-ir,turkmen (arabic iran)',\n'tk-cyrl,turkmen (cyrillic)',\n'to-to,tongan (tonga)',\n'tr-arab,turkish (arabic)',\n'tr-brai,turkish (braille)',\n'tr-cyrl,turkish (cyrillic)',\n'tr-grek,turkish',\n'tt-arab,tatar (arabic)',\n'tt-latn,tatar (latin)',\n'tt-ru,tatar (russia)',\n'twq-ne,tasawaq (niger)',\n'tzm-arab,central atlas tamazight (arabic)',\n'tzm-ma,central atlas tamazight (morocco)',\n'tzm-tfng,central atlas tamazight (tifinagh)',\n'ug-cn,uighur (china)',\n'ug-kz,uyghur',\n'ug-latn,uighur (latin)',\n'ug-mn,uighur',\n'uk-latn,ukrainian (latin)',\n'uk-ua,ukrainian (ukraine)',\n'ur-brai,urdu (braille)',\n'ur-deva,urdu (devanagari)',\n'ur-latn,urdu',\n'ur-pk,urdu (pakistan)',\n'uz-arab-af,uzbek (arabic afghanistan)',\n'uz-brai,uzbek (braille)',\n'uz-cn,uzbek (china)',\n'uz-cryl,uzbek (cyrillic)',\n'uz-latn,uzbek (latin)',\n'uz-latn-uz,uzbek (latin uzbekistan)',\n'uz-sogd,uzbek',\n'vai-latn-lr,vai (latin liberia)',\n'vai-vaii,vai (vai)',\n'vai-vaii-lr,vai (vai liberia)',\n'vi-brai,vietnamese (braille)',\n'vi-hani,vietnamese',\n'vi-vn,vietnamese (vietnam)',\n'vi-x-central,vietnamese',\n'vi-x-ncentral,vietnamese',\n'vi-x-southern,vietnamese',\n'vi-x-sub25,vietnamese',\n'vi-x-sub31,vietnamese',\n'vi-x-sub39,vietnamese',\n'vi-x-sub49,vietnamese',\n'vi-x-sub67,vietnamese',\n'vi-x-subdn,vietnamese',\n'vun-tz,vunjo (tanzania)',\n'wae-ch,walser (switzerland)',\n'wo-arab,wolof (arabic)',\n'wo-sn,wolof (senegal)',\n'xh-brai,xhosa (braille)',\n'xog-ug,soga (uganda)',\n'yav-cm,yangben (cameroon)',\n'yi-001,yiddish (world)',\n'yo-arab,yoruba (arabic)',\n'yo-brai,yoruba (braille)',\n'yo-ng,yoruba (nigeria)',\n'yue-brai,cantonese (braille)',\n'yue-hant,cantonese (traditional)',\n'yue-hant-hk,cantonese (traditional hong kong)',\n'yue-latn,yue (latin)',\n'zgh-ma,standard moroccan tamazight (morocco)',\n'zh-arab,chinese (arabic)',\n'zh-au,chinese',\n'zh-bn,chinese',\n'zh-bopo,chinese',\n'zh-brai,mandarin (braille)',\n'zh-gb,chinese (united kingdom)',\n'zh-gf,chinese',\n'zh-hanb,chinese',\n'zh-hans-cn,chinese (simplified china)',\n'zh-hans-sg,chinese (simplified singapore)',\n'zh-hant-hk,chinese (traditional hong kong)',\n'zh-hant-tw,chinese (traditional taiwan)',\n'zh-id,chinese (indonesia)',\n'zh-latn,chinese (latin)',\n'zh-mo,chinese (macau)',\n'zh-pa,chinese (panama)',\n'zh-pf,chinese',\n'zh-ph,chinese (philippines)',\n'zh-phag,chinese',\n'zh-sr,chinese',\n'zh-th,chinese',\n'zh-us,chinese',\n'zh-vn,chinese (vietnam)',\n'zu-brai,zulu (braille)',\n'zu-za,zulu (south africa)',\n//*/\n\n// gecko: supportedlocaleof, running maxmimum\n  // 779 + 1093: with these in\n\t// 777 + 1083: with these no included\n\t// get names and sort into place in expanded\n\t// they don't add entropy in locale POCs\n'gaa-arab',\n'kxv-latn',\n'nds-nl',\n'nds-runr',\n'nso-brai',\n'uz-cyrl',\n'vmw-arab',\n'xnr-takr',\n'za-hani',\n'za-hans',\n]\n\nlet gLocalesCanonical = [\n//*\n\t// redundant: see getCanonicalLocale\n'arb-eg,arabic standard (egypt)',   // ar-eg\n'arb-lb,arabic standard (lebanon)', // ar-lb\n'cnr,montenegrin',           // sr-me\n'cnr-latn,montenegrin',      // sr-latn-me\n'gom,goan',                  // kok\n'gom-knda',                  // kok-knda\n'gom-latn',                  // kok-latn\n'prp,parsi',                 // gu\n'prs,dari',                  // fa-af\n'sh,serbo-croatian',         // sr-latn\n'swc,congo',                 // sw-cd\n'swc-arab,congo (arabic)',   // sw-arab-cd\n'swh-arab,swahili (arabic)', // sw-arab\n'swh-brai,swahili (braille)',// sw-brai\n'tl,tagalog',                // fil\n'tl-brai,tagalog (braille)', // fil-brai\n'tl-buhd,tagalog',           // fil-buhd\n'tl-hano,tagalog',           // fil-hano\n'tl-tglg,tagalog (tagalog)', // fil-tglg\n'tw,twi',                    // ak\n'tw-x-asante,twi',           // ak-x-asante\n//*/\n]\n\nfunction check_canonicals() {\n\n\tlet list = gLocales\n\tlist = list.concat(gLocalesLikely)\n\tlist = list.concat(gLocalesExpand)\n\tlist = list.concat(gLocalesCanonical)\n\tlist = list.filter(function(item, position) {return list.indexOf(item) === position})\n\n\tlet aCanonical = []\n\tlist.forEach(function(item) {\n\t\tlet code = item.split(',')[0]\n\t\tlet test = Intl.getCanonicalLocales([code])\n\t\tif (test.join(',').toLowerCase() !== code.toLowerCase()) {\n\t\t\taCanonical.push(code +': ' + test.join(', '))\n\t\t}\n\t})\n\tif (aCanonical.length) {\n\t\tconsole.log('canonicals detected\\n-------\\n', aCanonical.join('\\n '))\n\t}\n}\n\nfunction expand_likely() {\n\tgLocales = gLocales.concat(gLocalesLikely)\n\tgLocales = gLocales.filter(function(item, position) {return gLocales.indexOf(item) === position})\n}\n\nfunction expand_maximum() {\n\tgLocales = gLocales.concat(gLocalesLikely)\n\tgLocales = gLocales.concat(gLocalesExpand)\n\tgLocales = gLocales.filter(function(item, position) {return gLocales.indexOf(item) === position})\n}\n\n// expand once in a while to see if entropy counts change\n//expand_likely()\n//expand_maximum() // also checks for canonical names\n"
  },
  {
    "path": "tests/testindex.css",
    "content": ":root{\n\t/* sat 73 */\n\t--test0: #b3b3b3;\n\t--test1: #dc9d9d;\n\t--test2: #dcb29d;\n\t--test3: #dcc79d;\n\t--test4: #dcdc9d;\n\t--test5: #c7dc9d;\n\t--test6: #b2dc9d;\n\t--test7: #9ddc9d;\n\t--test8: #9ddcb2;\n\t--test9: #9ddcc7;\n\t--test10: #9ddcdc;\n\t--test11: #9dc7dc;\n\t--test12: #9db2dc;\n\t--test13: #9d9ddc;\n\t--test14: #b29ddc;\n\t--test15: #c79ddc;\n\t--test16: #dc9ddc;\n\t--test17: #dc9dc7;\n\t--test18: #dc9db2;\n\t--test99: #808080;\n\t--testbad: #ff8787; /* sat 120 */\n\t/* sat 90 */\n\t--bg0: #161b22;\n\t--bg1: #f09b9b;\n\t--bg2: #f0b89b;\n\t--bg3: #f0d49b;\n\t--bg4: #f0f09b;\n\t--bg5: #d4f09b;\n\t--bg6: #b8f09b;\n\t--bg7: #9bf09b;\n\t--bg8: #9bf0b8;\n\t--bg9: #9bf0d4;\n\t--bg10: #9bf0f0;\n\t--bg11: #9bd4f0;\n\t--bg12: #9bb8f0;\n\t--bg13: #9b9bf0;\n\t--bg14: #b89bf0;\n\t--bg15: #d49bf0;\n\t--bg16: #f09bf0;\n\t--bg17: #f09bd4;\n\t--bg18: #f09bb8;\n\t--bg99: #808080;\n\n\t--jstring: #ff7de9;\n\t--jboolean: #86de74;\n\t--jnull: #939395;\n\t--jkey: #75bfff;\n}\n\n#modaloverlay {\n\tposition: fixed;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 990;\n\tdisplay: none;\n}\n#overlay {\n\tposition: fixed;\n\ttop: 50%;\n\tleft: 50%;\n\tright: 0;\n\tbottom: 0;\n\ttransform: translate(-50%, -50%);\n\tdisplay: none;\n\twidth: 700px;\n\tmin-width: 400px;\n\theight: 85%;\n\toverflow-y:scroll;\n\tborder: 2px solid var(--test0);\n\tbackground-color: var(--bg0);\n\tz-index: 1000;\n\toverscroll-behavior: contain;\n}\n#overlaybuttons {\n\tposition: fixed;\n\tright: 25px;\n\tz-index: 1010;\n}\n\n/* JSON */\n.string {color: var(--jstring);}\n.boolean, .number {color: var(--jboolean);}\n.null {color: var(--jnull);}\n.key {color: var(--jkey);}\n\nbody {background-color: #161b22; color: var(--test0);}\nh2 {color: white; font-size: 14px; text-align: center; margin-top: inherit;}\ncode {\n\tbackground: rgba(142, 142, 145, 0.25) !important;\n\tpadding: 2px 6px; /* top+bottom | left+ right */\n}\n\na {color: black; text-decoration: none;}\na.blue {color: var(--test12); text-decoration: none;}\na.return {color: var(--test12); text-decoration: none;  font-size: 14px; line-height: 1.2em}\n.no_color {color: var(--test0);}\n.good {color: var(--test7);}\n.bad {color: var(--testbad);}\n.blue {color: var(--test12);}\n.faint {color: var(--test99);}\n.hidden {display: none;}\n.small {font-size: 10px;}\n.offscreen {\n\tposition: absolute !important;\n\ttop: -2000% !important;\n\tleft: 0px !important;\n}\n.indent {\n\tdisplay:inline-block;\n\tmargin-left:20px;\n}\n\n.bold {font-weight: bold;}\n.mono {font-family: monospace, \"Courier New\"; font-size: 12px;}\n.strike {text-decoration: line-through;}\n.spaces {white-space: pre-wrap;}\n.perf {font-family: monospace, \"Courier New\"; font-size: 12px; white-space: pre-wrap;}\n\n.cursive {font-family: cursive;}\n.emoji {font-family: emoji;}\n.fangsong {font-family: fangsong;}\n.fantasy {font-family: fantasy;}\n.math {font-family: math;}\n.monospace {font-family: monospace;}\n.none {font-family: none;}\n.sans-serif {font-family: sans-serif;}\n.serif {font-family: serif;}\n.system-ui {font-family: system-ui;}\n.ui-monospace {font-family: ui-monospace;}\n.ui-rounded {font-family: ui-rounded;}\n.ui-sans-serif {font-family: ui-sans-serif;}\n.ui-serif {font-family: ui-serif;}\n\n.normalized {\n\tfont-family: none !important;\n\tfont-size: initial !important; /* testing */\n\tfont-style: normal !important;\n\tfont-variant: normal !important;\n\tfont-weight: normal !important;\n\tline-height: normal !important;\n\ttext-transform: none !important;\n\ttext-align: left !important;\n\ttext-decoration: none !important;\n\ttext-shadow: none !important;\n\twhite-space: normal !important;\n\tword-break: normal !important;\n\tword-spacing: normal !important;\n}\n\n/* run/re-run, click here */\n.btn {background-color: #161b22;\n\tdisplay: inline-block;\n\tfont-size: 12px;\n\tfont-family: monospace, \"Courier New\";\n\tfont-weight: bold;\n\tpadding-left: 6px;\n\tpadding-right: 6px;\n\tcursor: pointer;\n}\n.btnfirst {background-color: #161b22;\n\tdisplay: inline-block;\n\tfont-size: 12px;\n\tfont-family: monospace, \"Courier New\";\n\tfont-weight: bold;\n\tpadding-left: 0px;\n\tpadding-right: 6px;\n\tcursor: pointer;\n}\n.btnb {cursor: pointer;}\n/* item metrics/counts: dotted, no padding, normal */\n.btnc {\n\tfont-weight: normal;\n\ttext-decoration: underline;\n\ttext-decoration-style: dotted;\n\tcursor: pointer;\n}\n/* section metrics/counts: underline, padded, bold */\n.btns {\n\tfont-weight: bold;\n\ttext-decoration: underline;\n\tpadding-left: 8px;\n\tpadding-right: 8px;\n\tcursor: pointer;\n}\n.btn0 {border-color: var(--test0); color: var(--test0);}\n.btn1, .s1 {border-color: var(--test1); color: var(--test1);}\n.btn2, .s2 {border-color: var(--test2); color: var(--test2);}\n.btn3, .s3 {border-color: var(--test3); color: var(--test3);}\n.btn4, .s4 {border-color: var(--test4); color: var(--test4);}\n.btn5, .s5 {border-color: var(--test5); color: var(--test5);}\n.btn6, .s6 {border-color: var(--test6); color: var(--test6);}\n.btn7, .s7 {border-color: var(--test7); color: var(--test7);}\n.btn8, .s8 {border-color: var(--test8); color: var(--test8);}\n.btn9, .s9 {border-color: var(--test9); color: var(--test9);}\n.btn10, .s10 {border-color: var(--test10); color: var(--test10);}\n.btn11, .s11 {border-color: var(--test11); color: var(--test11);}\n.btn12, .s12 {border-color: var(--test12); color: var(--test12);}\n.btn13, .s13 {border-color: var(--test13); color: var(--test13);}\n.btn14, .s14 {border-color: var(--test14); color: var(--test14);}\n.btn15, .s15 {border-color: var(--test15); color: var(--test15);}\n.btn16, .s16 {border-color: var(--test16); color: var(--test16);}\n.btn17, .s17 {border-color: var(--test17); color: var(--test17);}\n.btn18, .s18 {border-color: var(--test18); color: var(--test18);}\n.s99 {color: var(--test99);}\n.btnbad, .sbad {border-color: var(--testbad); color: var(--testbad);}\n.btn-left {float: left; position: relative; left: -10px; top: 0px;}\n.btn-right {float: right; position: relative; right: 10px; top: 0px; text-align: right;}\n\n/* tooltips */\n.icon {font-size: 10px; font-weight: bold; color: var(--test0); cursor: default;}\n.ttip {position: relative; display: inline-block; border-bottom: 1px dotted black; color: white;}\n.ttip .ttxt {\n\tvisibility: hidden; width: 150px; background-color: var(--test0); color: black; text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip .ttxtb {\n\tvisibility: hidden; width: 210px; background-color: var(--test0); color: black; text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip .ttxtx {\n\tvisibility: hidden; width: 250px; background-color: var(--test0); color: black; text-align: center;\n\tborder-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; top: -12px; left: 130%;}\n.ttip:hover .ttxt, .ttip:hover .ttxtb, .ttip:hover .ttxtx {visibility: visible;}\n.ttip .ttxt::after, .ttip .ttxtb::after, .ttip .ttxtx::after {\n\tcontent: \" \"; position: absolute; top: 50%; right: 100%; margin-top: -5px; border-width: 5px;\n\tborder-style: solid; border-color: transparent var(--test0) transparent transparent;}\n\n/* table nav */\ndiv.nav-title {position: relative;}\ndiv.nav-down {position: absolute; right: 20px; top: 0px; width: 100px; text-align: right;}\ndiv.nav-up {position: absolute; left: 20px; top: 0px; width: 100px; text-align: left;}\n\n/* tables */\ntable {\n\tborder-collapse: collapse;\n\tmargin: 0 auto 10px auto;\n\tfont-size: 12px;\n}\ntbody:before {\n\tcontent: \"-\"; /* this line affects table tests in elements_other but we add an elements-fp rule */\n\tdisplay: block;\n\tline-height: 1em;\n\tcolor: transparent;\n}\ntd {padding-top: 2px; padding-bottom: 3px;}\nth {color: black; font-size: 14px; padding: 3px 0;}\n\ntable td:first-child { text-align: right; vertical-align: top;}\ntable td.blurb {text-align: center; line-height: 1.5em;}\ntable td.center {text-align: center;}\ntable td.intro {text-align: left; line-height: 1.5em; padding-bottom: 10px;}\ntable td.showhide {text-align: center; padding-top: 7px; padding-bottom: 7px;}\ntable td.padr {padding-right: 10px;}\ntable td.bottom {vertical-align: bottom;}\n\n#tb1 th {background-color: var(--bg1);}\n#tb2 th {background-color: var(--bg2);}\n#tb3 th {background-color: var(--bg3);}\n#tb4 th {background-color: var(--bg4);}\n#tb5 th {background-color: var(--bg5);}\n#tb6 th {background-color: var(--bg6);}\n#tb7 th {background-color: var(--bg7);}\n#tb8 th {background-color: var(--bg8);}\n#tb9 th {background-color: var(--bg9);}\n#tb10 th {background-color: var(--bg10);}\n#tb11 th {background-color: var(--bg11);}\n#tb12 th {background-color: var(--bg12);}\n#tb13 th {background-color: var(--bg13);}\n#tb14 th {background-color: var(--bg14);}\n#tb15 th {background-color: var(--bg15);}\n#tb16 th {background-color: var(--bg16);}\n#tb17 th {background-color: var(--bg17);}\n#tb18 th {background-color: var(--bg18);}\n#tb99 th {background-color: var(--bg99);}\n\n#tb1 td:first-child {color: var(--test1);}\n#tb2 td:first-child {color: var(--test2);}\n#tb3 td:first-child {color: var(--test3);}\n#tb4 td:first-child {color: var(--test4);}\n#tb5 td:first-child {color: var(--test5);}\n#tb6 td:first-child {color: var(--test6);}\n#tb7 td:first-child {color: var(--test7);}\n#tb8 td:first-child {color: var(--test8);}\n#tb9 td:first-child {color: var(--test9);}\n#tb10 td:first-child {color: var(--test10);}\n#tb11 td:first-child {color: var(--test11);}\n#tb12 td:first-child {color: var(--test12);}\n#tb13 td:first-child {color: var(--test13);}\n#tb14 td:first-child {color: var(--test14);}\n#tb15 td:first-child {color: var(--test15);}\n#tb16 td:first-child {color: var(--test16);}\n#tb17 td:first-child {color: var(--test17);}\n#tb18 td:first-child {color: var(--test18);}\n#tb99 td:first-child {color: var(--test99);}\n\n#tb1 hr {color: var(--test1);}\n#tb2 hr {color: var(--test2);}\n#tb3 hr {color: var(--test3);}\n#tb4 hr {color: var(--test4);}\n#tb5 hr {color: var(--test5);}\n#tb6 hr {color: var(--test6);}\n#tb7 hr {color: var(--test7);}\n#tb8 hr {color: var(--test8);}\n#tb9 hr {color: var(--test9);}\n#tb10 hr {color: var(--test10);}\n#tb11 hr {color: var(--test11);}\n#tb12 hr {color: var(--test12);}\n#tb13 hr {color: var(--test13);}\n#tb14 hr {color: var(--test14);}\n#tb15 hr {color: var(--test15);}\n#tb16 hr {color: var(--test16);}\n#tb17 hr {color: var(--test17);}\n#tb18 hr {color: var(--test18);}\n\n#element-fp {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tfont-family: none;\n\tfont-size: initial;\n\tfont-style: normal;\n\tfont-variant: normal;\n\tfont-weight: normal;\n\tline-height: normal;\n\ttext-transform: none;\n\ttext-align: left;\n\ttext-decoration: none;\n\ttext-shadow: none;\n\twhite-space: nowrap;\n\t/* domrect */\n\t/*transform: scale(1.1234567);*/\n\ttransform: skew(1.787542deg,3.263901deg);\n}\n#element-fp .unstyled {\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tappearance: none;\n}\n#element-fp tbody:before {\n\tcontent: none;\n\tline-height: normal;\n}\n#element-fp .revert {\n\tall: revert;\n}\n.skew {\n\ttransform: skew(1.787542deg,3.263901deg);\n}\n"
  },
  {
    "path": "tests/timezones.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=600\">\r\n\t<title>timezones</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 97%; min-width: 580px; max-width: 780px;}\r\n\t\tdiv.pad {pad-bottom: 10px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#region\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb4\">\r\n\t\t<col width=\"25%\"><col width=\"75%\">\r\n\t\t<thead><tr><th colspan=\"2\">\r\n\t\t\t<div class=\"nav-title\">timezones\r\n\t\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"2\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">A proof that calculates the maximum outcomes in timezone offsets between timezones.\r\n\t\t\t\tTesting with an empty input runs the example.\r\n\t\t\t\tReducing the number of years loses maximum outcomes. Adding more years gains nothing.\r\n\t\t\t\tYou can, however, swap some years for others.\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<tr>\r\n\t\t\t<td class=\"mono spaces\" id=\"legend\" style=\"text-align: left; vertical-align: top; color: #b3b3b3; font-size: 11px;\"></td>\r\n\t\t\t<td class=\"mono\" style=\"text-align: left; vertical-align: top;\">\r\n\t\t\t\t<span class=\"btn4 btnfirst\" onClick=\"run('run')\">[ run ]</span><span class=\"btn4 btn\" onClick=\"run('summary')\">[ summary ]</span>\r\n\t\t\t\t<select name=\"measure\" id=\"measure\">\r\n\t\t\t\t\t<option value=\"math\">date.parse</option>\r\n\t\t\t\t\t<option value=\"tzn\">timeZoneName</option>\r\n\t\t\t\t\t<option value=\"both\">both</option>\r\n\t\t\t\t</select>\r\n\t\t\t\t<span id=\"elementSupported\"><input type=\"checkbox\" id=\"useAll\"> all</span> |\r\n\t\t\t\t<span class=\"btn4 btn\" onClick=\"run('tzn')\">[ tzn ]</span>\r\n\t\t\t<br><br>\r\n\t\t\t\t<label style=\"display: flex;\">\r\n\t\t\t\t\t<input type=\"text\" style=\"flex: 1;\" placeholder=\"comma delimited year(s)\" name=\"inputYears\" id=\"inputYears\">\r\n\t\t\t\t\t<span class=\"btn4 btn\" onClick=\"reset()\"> [clear]</span>\r\n\t\t\t</label>\r\n\t\t\t<br>\r\n\t\t\t\t<span><hr></span>\r\n\t\t\t<br>\r\n\t\t\t\t<span class=\"spaces\" id=\"info\" style=\"line-height: 1.5em;\"></span>\r\n\t\t\t<br>\r\n\t\t\t\t<span class=\"spaces\" id=\"data\"></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar aLegend = [],\r\n\taLegendFull = [],\r\n\toLists = {},\r\n\taDates = [],\r\n\taDays = [\"January 15\",\"July 15\"],\r\n\t\t// to match TZP min test\r\n\t\t\t// to make sure we don't change years or months when a day or two ticks over\r\n\t\t\t// use the 15th - this makes get* and getUTC* PoCs possible\r\n\taYears = [],\r\n\taYearsTZP = [],\r\n\tuseSupported, \r\n\tisLog = false\r\n\r\nlet\toData = {},\r\n\toDataShort = {},\r\n\toBucketed = {},\r\n\toUndefined = {},\r\n\taBuckets = [],\r\n\taShortNames = [],\r\n\tloopData = []\r\n\r\n\r\nfunction build_dates(year) {\r\n\t// reset\r\n\taDates = []\r\n\tlet suffix = \"13:00:00 UTC\"\r\n\tif (year !== undefined && year !== \"\") {\r\n\t\t// single year\r\n\t\taYears = [year]\r\n\t} else {\r\n\t\t// aYears is set in run()\r\n\t\t// sort numerically & dedupe\r\n\t\taYears.sort((b,a) => b-a)\r\n\t\taYears = aYears.filter(function(item, position) {return aYears.indexOf(item) === position})\r\n\t}\r\n\t// build\r\n\tfor (let i = 0; i < aYears.length; i++) {\r\n\t\tfor (let j = 0; j < aDays.length; j++) {\r\n\t\t\taDates.push(aDays[j] +\", \"+ aYears[i] +\" \"+ suffix )\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction log(item) {\r\n\ttry {\r\n\t\titem = item.toUpperCase()\r\n\t\tif ('GROUPS' == item) {\r\n\t\t\tconsole.log(item, aBuckets.length, mini(aBuckets), '\\n', aBuckets)\r\n\t\t} else if ('RESULTS' == item) {\r\n\t\t\tconsole.log(item, mini(oBucketed) +'\\n', oBucketed)\r\n\t\t} else if ('FINGERPRINT' == item) {\r\n\t\t\tconsole.log(item, mini(oUndefined) +'\\n', oUndefined)\r\n\t\t} else if ('TIMEZONENAMES' == item) {\r\n\t\t\tconsole.log(item +' DATA', '\\n', oDataShort)\r\n\t\t\tconsole.log(item +' LIST', aShortNames.length, '\\n', aShortNames)\r\n\t\t}\r\n\t} catch(e) {\r\n\t\tconsole.log(e)\r\n\t}\r\n}\r\n\r\nfunction get_lengths() {\r\n\t// display string lengths for tzp\r\n\tlet max = 0\r\n\tlet oLengths = {}\r\n\tfor (const k of Object.keys(oData)) {\r\n\t\tlet display = []\r\n\t\tfor (const y of Object.keys(oData[k].data)) {\r\n\t\t\tdisplay.push(oData[k].data[y].join(', '))\r\n\t\t}\r\n\t\tlet str = display.join(' | ')\r\n\t\tlet len = str.length\r\n\t\tif (len > max) {\r\n\t\t\tmax = len\r\n\t\t\t//console.log(max, str)\r\n\t\t}\r\n\t\tif (undefined == oLengths[len]) {\r\n\t\t\toLengths[len] = 1\r\n\t\t} else {\r\n\t\t\toLengths[len] = oLengths[len] + 1\r\n\t\t}\r\n\r\n\t}\r\n\tconsole.log(max)\r\n\tconsole.log(oLengths)\r\n}\r\n\r\nfunction reset() {\r\n\tdom.inputYears.value = \"\"\r\n}\r\n\r\nfunction legend() {\r\n\t// pick our list\r\n\tlet proceed = false, list = []\r\n\tif (useSupported) {\r\n\t\tif (aLegend.length == 0) {proceed = true}\r\n\t\tlist = oLists.supported\r\n\t} else {\r\n\t\tif (aLegendFull.length == 0) {proceed = true}\r\n\t\tlist = oLists.full\r\n\t}\r\n\r\n\tlet prev = ''\r\n\t// do once\r\n\tif (proceed) {\r\n\t\tlist.sort()\r\n\t\tfor (let i = 0 ; i < list.length; i++) {\r\n\t\t\tlet parts = list[i].split('/')\r\n\t\t\tlet area = parts[0]\r\n\t\t\tlet location = parts.slice(1).join('/')\r\n\t\t\tif (area == prev) {\r\n\t\t\t\tif (useSupported) {aLegend.push(' '+ location)} else {aLegendFull.push(' '+ location)}\r\n\t\t\t} else {\r\n\t\t\t\tlet str = '<br>'+ s4+ (area == 'AA' ? 'MISC' : area.toUpperCase()) + sc +'<br><br> '+ location\r\n\t\t\t\tif (useSupported) {aLegend.push(str)} else {aLegendFull.push(str)}\r\n\t\t\t}\r\n\t\t\tprev = area\r\n\t\t}\r\n\t}\r\n\r\n\tlet aDisplay = useSupported ? aLegend : aLegendFull\r\n\tlet header = s4.trim() +\"TIMEZONES [\"+ list.length + (useSupported ? \" supported\" : \"\") +\"] \"+ sc +\"<br><br>\"\r\n\t\t+\"<a class='blue' target='blank' href='https://www.iana.org/time-zones'>IANA TimeZone Database</a><br>\"\r\n\tdom.legend.innerHTML = header + aDisplay.join(\"<br>\")\r\n\r\n}\r\n\r\nfunction run_test(type) {\r\n\r\n\tlet t0 = performance.now()\r\n\tlet method = dom.measure.value\r\n\tlet methodStr = 'math' == method ? 'date.parse' : ('tzn' == method ? 'timeZoneName' : 'date.parse + timeZoneName')\r\n\tlet yrStr = aYears.length > 10 ? aYears.slice(0,3).join(', ') +' ... and ' + (aYears.length - 3) +' more' : aYears.join(', ')\r\n\tlet isTZP = 'math' == method && (aYears.join(',') == aYearsTZP.join(','))\r\n\tlet padSize = 15\r\n\tfunction title(input, color) {\r\n\t\tif (color == undefined) {return input.padStart(padSize) +': '} else {return color + input.padStart(padSize) +': '+ sc}\r\n\t}\r\n\r\n\tfunction test() {\r\n\t\t// reset\r\n\t\toData = {}\r\n\t\toDataShort = {}\r\n\t\toBucketed = {}\r\n\t\taBuckets = []\r\n\t\taShortNames = []\r\n\r\n\t\t// vars\r\n\t\tlet tmpoption = {\r\n\t\t\tday: '2-digit', month: '2-digit', year: 'numeric',\r\n\t\t\thour12: false, hour: '2-digit', minute: 'numeric', second: 'numeric',\r\n\t\t\ttimeZoneName: 'short',\r\n\t\t}\r\n\t\tlet k = 60000,\r\n\t\t\ttzresults = {},\r\n\t\t\tuniqueTZs = 0,\r\n\t\t\tundefinedError,\r\n\t\t\taDisplay = [],\r\n\t\t\taInvalid = [],\r\n\t\t\taBucketSizes = []\r\n\r\n\t\tif ('run' == type) {\r\n\t\t\t// get undefined hash\r\n\t\t\t/* notes:\r\n\t\t\t\tbecause we are formatting (toLocaleString or DTF etc) to get the timezonename we need\r\n\t\t\t\tto ensure both use the same format (en) otherwise the string can mix e.g. 1/7 vs 7/1\r\n\t\t\t\tin TZP we don't format and if we do it will only be for a timeZoneName PoC\r\n\t\t\t*/\r\n\t\t\toUndefined = {}\r\n\t\t\ttmpoption.timeZone = undefined\r\n\t\t\ttry {\r\n\t\t\t\tfor (let i = 0 ; i < aYears.length; i++) {\r\n\t\t\t\t\tlet year = aYears[i], control, test\r\n\t\t\t\t\toUndefined[year] = []\r\n\t\t\t\t\tfor (let j = 0 ; j < aDays.length; j++) {\r\n\t\t\t\t\t\tlet datetime = aDays[j] +', '+ aYears[i] +' 13:00:00'\r\n\t\t\t\t\t\tlet control = new Date(datetime)\r\n\t\t\t\t\t\tlet test = control.toLocaleString('en', {timeZone: 'UTC'})\r\n\t\t\t\t\t\tlet formatter = new Intl.DateTimeFormat('en', tmpoption)\r\n\t\t\t\t\t\tcontrol = formatter.format(control).replace(',','')\r\n\t\t\t\t\t\tlet tznshort = control.split(' ')[2]\r\n\t\t\t\t\t\tcontrol = control.slice(0,19) // remove tzn\r\n\t\t\t\t\t\tlet diff = ((Date.parse(test) - Date.parse(control))/k)\r\n\t\t\t\t\t\tif ('math' == method || 'both' == method) {oUndefined[year].push(diff)}\r\n\t\t\t\t\t\tif ('tzn' == method || 'both' == method) {oUndefined[year].push(tznshort)}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// dedupe year data\r\n\t\t\t\t\toUndefined[year] = oUndefined[year].filter(function(item, position) {return oUndefined[year].indexOf(item) === position})\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {\r\n\t\t\t\tundefinedError = e+''\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// select list and loop each timezone\r\n\t\tlet array = useSupported ? oLists[\"supported\"] : oLists[\"full\"]\r\n\r\n\t\t// get data\r\n\t\tlet tmpData = {}\r\n\t\tfor (let i = 0 ; i < array.length; i++) {\r\n\t\t\tlet tz = array[i]\r\n\t\t\tlet tzclean = tz\r\n\t\t\t// strip bogus area\r\n\t\t\tif (tz.substring(0,3) == \"AA/\") { tzclean = tz.substring(3)}\r\n\t\t\tlet tzvalid = 'UTC' == tzclean ? 'GMT' : tzclean\r\n\t\t\ttmpoption.timeZone = tzvalid\r\n\r\n\t\t\ttzresults = {} // loop each date\r\n\t\t\ttry {\r\n\t\t\t\tfor (let i = 0 ; i < aYears.length; i++) {\r\n\t\t\t\t\tlet year = aYears[i]\r\n\t\t\t\t\ttzresults[year] = []\r\n\t\t\t\t\tfor (let j = 0 ; j < aDays.length; j++) {\r\n\t\t\t\t\t\tlet datetime = aDays[j] +\", \"+ aYears[i] +\" 13:00:00\"\r\n\t\t\t\t\t\tlet control = new Date(datetime)\r\n\t\t\t\t\t\tlet test = control.toLocaleString(\"en\", {timeZone: \"UTC\"})\r\n\t\t\t\t\t\tlet formatter = new Intl.DateTimeFormat('en', tmpoption)\r\n\t\t\t\t\t\tcontrol = formatter.format(control).replace(',','')\r\n\t\t\t\t\t\tlet tznshort = control.split(' ')[2]\r\n\t\t\t\t\t\tcontrol = control.slice(0,19)\r\n\t\t\t\t\t\tlet diff = ((Date.parse(test) - Date.parse(control))/k)\r\n\t\t\t\t\t\tif ('math' == method || 'both' == method) {tzresults[year].push(diff)}\r\n\t\t\t\t\t\tif ('tzn' == method || 'both' == method) {tzresults[year].push(tznshort)}\r\n\t\t\t\t\t\tif ('run' == type || 'tzn' == type) {\r\n\t\t\t\t\t\t\tif (undefined == oDataShort[tznshort]) {\r\n\t\t\t\t\t\t\t\toDataShort[tznshort] = [tzclean]\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\toDataShort[tznshort].push(tzclean)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t/* what timezones in have part hours\r\n\t\t\t\t\t\tif (2026 == aYears[i]) {\r\n\t\t\t\t\t\t\tlet partial = diff % 60\r\n\t\t\t\t\t\t\tif (partial !== 0) {console.log(aYears[i], tz, diff)}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t//*/\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// dedupe year results\r\n\t\t\t\t\ttzresults[year] = tzresults[year].filter(function(item, position) {return tzresults[year].indexOf(item) === position})\r\n\t\t\t\t\ttmpData[tzclean] = {\r\n\t\t\t\t\t\t'hash': mini(tzresults),\r\n\t\t\t\t\t\t'data': tzresults\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {\r\n\t\t\t\taInvalid.push(tzclean)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// dedupe short timezonenames collection\r\n\t\tif ('tzn' == type || 'run' == type) {\r\n\t\t\t// map names into an order\r\n\t\t\tlet tznShort = {\r\n\t\t\t\tAKST: 540, AKDT: 480,\r\n\t\t\t\tAST: 240, ADT: 180,\r\n\t\t\t\tCST: 360, CDT: 300,\r\n\t\t\t\tEST: 300, EDT: 240,\r\n\t\t\t\tHAST: 600, HADT: 540,\r\n\t\t\t\tHST: 600, HDT: 540,\r\n\t\t\t\tMST: 420, MDT: 360,\r\n\t\t\t\tPST: 480, PDT: 420,\r\n\t\t\t\tUTC: 0, GMT: 0, 'GMT+0': 0,\r\n\t\t\t}\r\n\t\t\tlet oOrder = {}\r\n\t\t\tlet oMinutes = {}\r\n\t\t\tfor (const k of Object.keys(oDataShort)) {\r\n\t\t\t\t// dedupe and sort\r\n\t\t\t\tlet parts\r\n\t\t\t\tlet tzones = oDataShort[k]\r\n\t\t\t\ttzones = tzones.filter(function(item, position) {return tzones.indexOf(item) === position})\r\n\t\t\t\ttzones.sort()\r\n\t\t\t\toDataShort[k] = tzones\r\n\t\t\t\t// calculate numerical order\r\n\t\t\t\tlet minutes = 'TBA'\r\n\t\t\t\tif (undefined !== tznShort[k]) {\r\n\t\t\t\t\tminutes = tznShort[k]\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// should be just left with GMTs\r\n\t\t\t\t\tlet value = k\r\n\t\t\t\t\tlet sign = value.includes('-') ? 1 : -1\r\n\t\t\t\t\tvalue = value.replace('GMT','')\r\n\t\t\t\t\tvalue = value.replace('-','')\r\n\t\t\t\t\tvalue = value.replace('+','')\r\n\t\t\t\t\tlet parts = value.split(':')\r\n\t\t\t\t\tminutes = parts[0] * 60\r\n\t\t\t\t\tif (undefined !== parts[1]) { minutes += parts[1] * 1} // minutes\r\n\t\t\t\t\tif (undefined !== parts[2]) { minutes += (parts[2] * 1)/60} // seconds\r\n\t\t\t\t\tminutes = minutes * sign\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\toMinutes[k] = minutes\r\n\t\t\t\t\tif (undefined == oOrder[minutes]) {oOrder[minutes] = {}}\r\n\t\t\t\t\tif (undefined == oOrder[minutes][k]) {oOrder[minutes][k] = oDataShort[k].sort()}\r\n\t\t\t}\r\n\r\n\t\t\t//console.log(oOrder)\r\n\t\t\t//console.log(oMinutes)\r\n\t\t\t// populate aShortNames\t\t\t\r\n\t\t\tfor (const m of Object.keys(oOrder).sort((a,b) => b-a)) {\r\n\t\t\t\tfor (const k of Object.keys(oOrder[m]).sort()) {aShortNames.push(k)}\r\n\t\t\t}\r\n\t\t\tif ('tzn' == type) {\r\n\t\t\t\taDisplay.push(s12 +'YEARS: '+ sc + yrStr +'<br>')\r\n\t\t\t\taDisplay.push(s12 +'TIMEZONENAMES: '+ sc + s4 +' ['+ aShortNames.length +']' + sc +'<br><br>'+ aShortNames.join(', ') +'<br>')\r\n\t\t\t\taDisplay.push(s12 +'DATA: '+ sc +'<br>')\r\n\t\t\t\taShortNames.forEach(function(k) {\r\n\t\t\t\t\taDisplay.push(s14 + k + sc +': '+ s4 +'['+ oDataShort[k].length +'] '+ sc + s7 + oMinutes[k] + sc)\r\n\t\t\t\t\taDisplay.push('<li>' + oDataShort[k].join(', ') +'</li>')\r\n\t\t\t\t})\r\n\t\t\t\tdom.info.innerHTML = aDisplay.join('<br>') +'<br>'\r\n\t\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +' ms'\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// structure/tidy our data\r\n\t\tfor (const k of Object.keys(tmpData).sort()) {\r\n\t\t\tlet tmphash = tmpData[k].hash\r\n\t\t\tif (undefined == oData[tmphash]) {\r\n\t\t\t\toData[tmphash] = {'data': tmpData[k]['data'],'timezones': [k]}\r\n\t\t\t} else {\r\n\t\t\t\toData[tmphash]['timezones'].push(k)\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (const k of Object.keys(oData)) {\r\n\t\t\tlet count = oData[k].timezones.length\r\n\t\t\toData[k]['count'] = count\r\n\t\t\taBuckets.push(oData[k]['timezones'].join(', '))\r\n\t\t\taBucketSizes.push(count)\r\n\t\t\tif (undefined == oBucketed[count]) {oBucketed[count] = {}} // group by count\r\n\t\t\toBucketed[count][k] = {'data': oData[k]['data'], 'timezones': oData[k]['timezones']}\r\n\t\t}\r\n\t\tif (undefined !== oBucketed['1']) {uniqueTZs = Object.keys(oBucketed['1']).length}\r\n\t\taBuckets.sort()\r\n\t\taInvalid = aInvalid.filter(function(item, position) {return aInvalid.indexOf(item) === position})\r\n\t\taInvalid.sort()\r\n\t\tlet buckethash = mini(aBuckets)\r\n\t\taBucketSizes.sort((b,a) => b-a)\r\n\t\tlet sumTZ = aBucketSizes.reduce(function(a, b){return a + b}, 0)\r\n\t\tif (isLog && 'run' == type) {\r\n\t\t\tconsole.log('tmpData\\n',tmpData)\r\n\t\t\tconsole.log('oData HASH/DATA', Object.keys(oData).length, mini(oData) +'\\n', oData)\r\n\t\t\tconsole.log('BUCKETSIZES', '[sum of sizes:', sumTZ, ']\\n', aBucketSizes)\r\n\t\t}\r\n\r\n\t\tif ('summary' == type) {\r\n\t\t\tloopData.push(s12 + loopYear + sc +' '+ buckethash + s4 +' ['+ aBucketSizes.length +'] '+ sc + sg +'['+ uniqueTZs +']'+ sc)\r\n\t\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +' ms'\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// display data\r\n\t\taDisplay = ['<hr>']\r\n\t\tlet counter = 0\r\n\t\tfor (const c of Object.keys(oBucketed).sort((a,b) => b-a)) { // count\r\n\t\t\tlet isOnes = '1' == c\r\n\t\t\tif (isOnes) {\r\n\t\t\t\tlet total = Object.keys(oData).length, next = counter + 1, remainder = total - counter\r\n\t\t\t\taDisplay.push('<hr>timezones in '+ s12 + 'hashes '+ next + '-' + total + sc\r\n\t\t\t\t\t+' are all unqiue'+ s4 +' ['+ remainder +']'+ sc +'<br>'\r\n\t\t\t\t)\r\n\t\t\t}\r\n\r\n\t\t\tfor (const h of Object.keys(oBucketed[c])) { // hash\r\n\t\t\t\tcounter++\r\n\t\t\t\tlet str = ''\r\n\t\t\t\tif (isOnes) {\r\n\t\t\t\t\tstr = s12 + h + sc + ' ' + oBucketed[c][h].timezones + '<ul>'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstr = s12 + (counter+'').padStart(3,' ') +': '+ h + sc + s4 +' ['+ c +']' + sc\r\n\t\t\t\t\tstr += '<ul><li>' + oBucketed[c][h].timezones.join(', ') +'</li>'\r\n\t\t\t\t}\r\n\t\t\t\tlet oTmp = oBucketed[c][h].data, dTmp = []\r\n\t\t\t\tfor (const y of Object.keys(oTmp)) {dTmp.push(s4 + y +sc +': ' + oTmp[y].join(', '))}\r\n\t\t\t\tstr += '<li>'+ dTmp.join('<br>') +'</li></ul>'\r\n\t\t\t\taDisplay.push(str)\r\n\t\t\t}\r\n\t\t}\r\n\t\tdom.data.innerHTML = aDisplay.join('<br>')\r\n\r\n\t\t// display info\r\n\t\taDisplay = []\r\n\t\tfunction splitgroup(strToSplit) {\r\n\t\t\tlet parts = strToSplit.split(\", \"); parts = parts.join('<br>' + ('').padStart(padSize + 2)); return parts\r\n\t\t}\r\n\t\taDisplay.push( title('YEARS', s14) + yrStr)\r\n\t\taDisplay.push( title('METHOD', s14) + methodStr)\r\n\t\taDisplay.push( title('TIMEZONES', s14) + (useSupported ? 'supported only' : 'all') +'<br>')\r\n\t\taDisplay.push(\r\n\t\t\t// color it up\r\n\t\t\ttitle('unique')\t+ sg + (aBucketSizes.length - uniqueTZs).toString().padStart(3) + sc +' groups (>1) '\r\n\t\t\t\t+ s4 + (array.length - aInvalid.length - uniqueTZs).toString().padStart(3) + sc + ' timezones<br>'\r\n\t\t\t\t+ title('') + sg + (uniqueTZs).toString().padStart(3) + sc +' groups (=1) '\r\n\t\t\t\t+ s4 + (uniqueTZs).toString().padStart(3) + sc + ' timezones<br>'\r\n\t\t\t\t+ title('total') + sg + (aBucketSizes.length).toString().padStart(3) + sc +' groups .... '\r\n\t\t\t\t+ s4 + (array.length - aInvalid.length).toString().padStart(3) + sc + ' timezones'\r\n\t\t)\r\n\t\taDisplay.push( title('[groups] hash') + '<span class=\"s12 btnc\" onClick=\"log(' + \"'groups'\"+ ')\">' + buckethash + '</span>')\r\n\t\taDisplay.push( title('[results] hash') + '<span class=\"s12 btnc\" onClick=\"log(' + \"'results'\"+ ')\">' + mini(oBucketed) + '</span>')\r\n\t\taDisplay.push( title('timezonenames') + '<span class=\"s12 btnc\" onClick=\"log(' + \"'timezonenames'\"+ ')\">' + 'details' + '</span>')\r\n\t\tlet fpstring = title((isTZP ? '[tzp]' : '[your]') + ' hash')\r\n\t\tif (undefined == undefinedError) {\r\n\t\t\taDisplay.push( fpstring\t+ '<span class=\"s16 btnc\" onClick=\"log('+ \"'fingerprint'\"+ ')\">' + mini(oUndefined) + '</span>')\r\n\t\t} else {\r\n\t\t\taDisplay.push( fpstring\t+ undefinedError)\r\n\t\t}\r\n\t\t// invalid timezones\r\n\t\tif (sumTZ !== array.length) {\r\n\t\t\taDisplay.push( title('alert', sb) +'total timezones '+ sb +'(' + sumTZ +')'+ sc +' !== legend count '+ sb + '(' + array.length +')'+ sc)\r\n\t\t}\r\n\t\tif (aInvalid.length) {\r\n\t\t\taDisplay.push( title(aInvalid.length +' invalid', sb) + aInvalid[0])\r\n\t\t\tif (aInvalid.length > 1) {aDisplay.push(title('') + splitgroup((aInvalid.slice(1)).join(', ')))}\r\n\t\t}\r\n\t\t// group info\r\n\t\taBucketSizes.sort((a,b) => b-a)\r\n\t\tlet aBucketSizesUnique = aBucketSizes.filter(function(item, position) {return aBucketSizes.indexOf(item) === position})\r\n\t\tlet notoneBS = aBucketSizes.filter(x => x != 1)\r\n\t\taDisplay.push(s4 +'<br>unique groups sizes'+ sc +'<br>'+ aBucketSizesUnique.join(', ') +'<br>')\r\n\t\taDisplay.push(s4 +'groups > 1 summary'+ sc +'<br>'+ notoneBS.join(', '))\r\n\r\n\t\tdom.info.innerHTML = aDisplay.join('<br>') +'<br>'\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +\" ms\"\r\n\t\treturn\r\n\t}\r\n\r\n\tlet loopYear = ''\r\n\tif ('summary' == type) {\r\n\t\t// reset loopData\r\n\t\tloopData = []\r\n\t\tloopData.push( title('METHOD', s14) + methodStr)\r\n\t\tloopData.push( title('TIMEZONES', s14) + (useSupported ? 'supported only' : 'all') +'<br>')\r\n\t\tloopData.push(s12 +'year'+ sc +' hash-of-timezone-groups' + s4 +' [groups] '+ sc + sg +'[unique timezones]'+ sc +'<br>')\r\n\r\n\t\t// loop\r\n\t\tlet loopYears = aYears\r\n\t\tfor (let i = 0; i < loopYears.length; i++) {\r\n\t\t\tloopYear = loopYears[i]\r\n\t\t\tbuild_dates(loopYears[i])\r\n\t\t\ttest()\r\n\t\t}\r\n\t\t// output loopData\r\n\t\tdom.info.innerHTML = loopData.join('<br>')\r\n\t\tdom.perf.innerHTML = Math.round(performance.now() - t0) +' ms'\r\n\t\treturn\r\n\t} else {\r\n\t\ttest()\r\n\t}\r\n\r\n}\r\n\r\nfunction run(type) {\r\n\tuseSupported = !dom.useAll.checked\r\n\r\n\tlet go = false\r\n\t// reset legend\r\n\tlegend()\r\n\tdom.info = ''\r\n\tdom.data = ''\r\n\tdom.perf = ''\r\n\taYears = []\r\n\r\n\tlet yearStr = dom.inputYears.value\r\n\tyearStr = yearStr.trim()\r\n\tif (yearStr.length) {\r\n\t\t// parse for valid years\r\n\t\tlet tmpArr = yearStr.split(',')\r\n\t\tfor (let i = 0 ; i < tmpArr.length; i++) {\r\n\t\t\tlet trimmed = tmpArr[i].trim()\r\n\t\t\tif (trimmed.length) {\r\n\t\t\t\t// only collect numbers\r\n\t\t\t\ttrimmed = parseInt(trimmed)\r\n\t\t\t\tif (Number.isInteger(trimmed)) {\r\n\t\t\t\t\t// put a range limit on it\r\n\t\t\t\t\tif (trimmed > -2000 && trimmed < 3000) {\r\n\t\t\t\t\t\taYears.push(trimmed)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t// do we have at least one year\r\n\t\tif (aYears.length) {\r\n\t\t\tgo = true\r\n\t\t} else {\r\n\t\t\tdom.info = 'please provide at least one year'\r\n\t\t}\r\n\t} else {\r\n\t\t// run examples\r\n\t\t// anything prior to 1879 is redundant\r\n\t\t\t// tested: every year from 0-2100\r\n\t\t\t// tested: negative years\r\n\t\t// use a year in the future to catch some changes from tzdata\r\n\t\taYears = aYearsTZP\r\n\t\t/*\r\n\t\told: using 2018\r\n\t\tunique:  49 groups (>1) 173 timezones\r\n\t\t\t\t\t: 296 groups (=1) 296 timezones\r\n\t\t total: 345 groups .... 469 timezones\r\n\t\t\tnew: using 2025\r\n\t\tunique:  48 groups (>1) 171 timezones <- less shared\r\n\t\t\t\t\t: 298 groups (=1) 298 timezones <- more unique\r\n\t\t total: 346 groups .... 469 timezones <- more hashes\r\n\t\t kaching! matches\r\n\t\t*/\r\n\r\n\t\t// test flips\r\n\t\t//aDays = [\"January 1\",\"April 1\",\"July 1\",\"October 1\"]  // do four days: no extra entropy\r\n\t\t//for (let i = 1879; i < 2030; i++) {aYears.push(i)}    // 1879+\r\n\t\t//for (let i = 1800; i < 1900; i++) {aYears.push(i)}    // add a century\r\n\t\t//for (let i = -1050; i < -350; i=i+7) {aYears.push(i)} // add negative years\r\n\t\t//for (let i = 2000; i < 2100; i++) {aYears.push(i)}    // add the future\r\n\t\tdom.inputYears.value = aYears.join(',')\r\n\t\tgo = true\r\n\t}\r\n\r\n\tif (go) {\r\n\t\tdom.info = 'calculating...'\r\n\t\t// rebuild aDates\r\n\t\tbuild_dates()\r\n\t\t// pause for repaint\r\n\t\tfunction test() {\r\n\t\t\tclearInterval(checking)\r\n\t\t\trun_test(type)\r\n\t\t}\r\n\t\tlet checking = setInterval(test, 10)\r\n\t}\r\n}\r\n\r\nfunction start() {\r\n\tget_globals()\r\n\tget_isVer()\r\n\t// chrome has more groups with full vs supported\r\n\t\t// but this creates a false sense of uniqueness as aliases pair up\r\n\t\t// e.g. supported = 280 | all = 229\r\n\t\t//dom.useAll.checked = isFF ? false : true\r\n\tdom.useAll.checked = false\r\n\r\n\taYearsTZP = [1879,1952,1976,2025]\r\n\tif (isVer < 112) {aYearsTZP = [1879,1921,1952,1976,2025]}\r\n\t/*\r\n\taYearsTZP = []\r\n\tfor (let i = 1879; i < 2030; i++) {\r\n\t\taYearsTZP.push(i)\r\n\t}\r\n\t*/\r\n\r\n\t// remove any sloppy duplicates\r\n\tgTimezones = gTimezones.filter(function(item, position) {return gTimezones.indexOf(item) === position})\r\n\t// add AA\r\n\tlet baselist = []\r\n\tgTimezones.forEach(function(item){\r\n\t\tif (!item.includes('/')) {item = 'AA/'+ item}\r\n\t\tbaselist.push(item)\r\n\t})\r\n\tbaselist.sort()\r\n\toLists['full'] = baselist\r\n\ttry {\r\n\t\tlet supported = Intl.supportedValuesOf('timeZone')\r\n\t\tlet newlist = []\r\n\t\tsupported.forEach(function(item){\r\n\t\t\tif (!item.includes('/')) { item = 'AA/'+ item}\r\n\t\t\tnewlist.push(item)\r\n\t\t})\r\n\t\tnewlist.sort()\r\n\t\toLists['supported'] = newlist\r\n\t} catch(e) {\r\n\t\tdom.useAll.checked = true\r\n\t\tdom.elementSupported.style.display = 'none'\r\n\t}\r\n\tlegend()\r\n}\r\nstart()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/versions.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=700\">\r\n\t<title>versions</title>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"testindex.css\">\r\n\t<script src=\"testglobals.js\"></script>\r\n\t<script src=\"testgeneric.js\"></script>\r\n\t<style>\r\n\t\ttable {width: 680px;}\r\n\t</style>\r\n\t<style id=\"test73\">\r\n\t\t.a{border-width: 1px; border-style: solid; border-color: black; border-image: linear-gradient(white,black);}\r\n\t\tli {margin-left: -20px;}\r\n\t</style>\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"hidden\">\r\n\t\t<div id=\"test73\"></div>\r\n\t\t<div><input type=\"time\" min=\"14:00:00\" max=\"12:00:00\" value=\"15:00:00\" id=\"test76\"></div>\r\n\t\t<output id=\"test90a\">a</output>\r\n\t\t<select id=\"test107\"></select>\r\n\t</div>\r\n\t<div class=\"offscreen\">\r\n\t\t<div id=\"test89\" style=\"width:1em\">a &#x3000; b</div>\r\n\t\t<div id=\"ctrl89\" style=\"width:1em\">a b</div>\r\n\t\t<span id=\"test81a\"></span><span id=\"test81b\"></span>\r\n\t\t<div id=\"test95a\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-1</div>\r\n\t\t<div id=\"test95b\" style=\"width: min-content; hyphens: auto; border: 1px solid red\">2020-12020-1</div>\r\n\t\t<div><audio id=\"tzpPreload\"></audio></div>\r\n\t</div>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#feature\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb3\">\r\n\t\t<col width=\"1%\"><col width=\"33%\"><col width=\"33%\"><col width=\"32%\"><col width=\"1%\">\r\n\t\t<thead><tr><th colspan=\"5\">\r\n\t\t\t<div class=\"nav-title\">gecko versions\r\n\t\t\t<div class=\"nav-up\"><span class=\"c perf\" id=\"perf\"></span></div>\r\n\t\t\t</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr><td colspan=\"5\" class=\"intro\">\r\n\t\t\t<span class=\"no_color\">\r\n\t\t\t\t<details class=\"no_color\"><summary>INFO</summary>\r\n\t\t\t\t\t<ul>\r\n\t\t\t\t\t\t<li><span class='s3'>RESULT: </span><b><span class='good'> &#x2713; </span></b> &nbsp; pass\r\n\t\t\t\t\t\t\t&nbsp; <b><span class='bad'> &#x2715; </span></b> &nbsp; fail\r\n\t\t\t\t\t\t\t&nbsp; <b><span class='s4'> &#x2715; </span></b> &nbsp; error\r\n\t\t\t\t\t\t\t&nbsp; <b> &#x2715; </b> &nbsp; n/a\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t<li><span class='s3'>NOTES:</span>\r\n\t\t\t\t\t\t\t<ul>\r\n\t\t\t\t\t\t\t\t<li><span class='mono spaces'>PREF: </span>Fast-path with no false positives. Depends on a pref. Ideally should have a fallback</li>\r\n\t\t\t\t\t\t\t\t<li><span class='mono spaces'>DOM : </span>Touches the DOM, which we have to await.\r\n\t\t\t\t\t\t\t\t\tCan be slow. Is susceptible to being manipulated</li>\r\n\t\t\t\t\t\t\t\t<li><span class='mono spaces'>EVAL: </span>Requires <code>eval()</code> to avoid <code>SyntaxError</code>\r\n\t\t\t\t\t\t\t\t\tin earlier versions. Not universal, e.g. <code>CSP</code>.\r\n\t\t\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t\t\t<li><span class='mono spaces'>DATE INTL TMPR: Anything to do with dates or Intl APIs or Temporal</span>\r\n\t\t\t\t\t\t\t\t\t<ul>\r\n\t\t\t\t\t\t\t\t\t\t<li>are often targeted by <code>extensions</code></li>\r\n\t\t\t\t\t\t\t\t\t\t<li>may be affected by builds e.g. <code>--with-system-icu</code></li>\r\n\t\t\t\t\t\t\t\t\t</ul>\r\n\t\t\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t\t</ul>\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t<li><span class='s3'>VERSION:</span>\r\n\t\t\t\t\t\t\t<ul><li><b>Not <u>guaranteed</u> to be 100% correct</b>. Ideally, we don't want to <b><u>rely</u></b> on any possibly\r\n\t\t\t\t\t\t\t\tproblematic tests (see NOTES above), but could use them as fast paths or fallbacks, as long as we don't get false positives.\r\n\t\t\t\t\t\t\t\tFor the 0.01% of users that have or cause an \"incorrect\" version detection, that only increases their entropy in a\r\n\t\t\t\t\t\t\t\tfull fingerprinting test\r\n\t\t\t\t\t\t\t</li></ul>\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t</ul>\r\n\t\t\t\t</details>\r\n\t\t\t</span>\r\n\t\t</td></tr>\r\n\t\t<!-- tzp -->\r\n\t\t<tr><td colspan=\"4\"><hr></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"4\"><span class=\"s3\">TZP</span><span id=\"perftzp\" class=\"c mono spaces\"></span></td></tr>\r\n\t\t<tr><td></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"tzp1\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"tzp2\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"tzp3\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td></td>\r\n\t\t</tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"alerttzp1\"></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"alerttzp2\"></td><td></td></tr>\r\n\t\t<!-- other-->\r\n\t\t<tr><td colspan=\"4\"><hr></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"4\"><span class=\"s3\">OTHER</span><span id=\"perfother\" class=\"c mono spaces\"></span></td></tr>\r\n\t\t<tr><td></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"other1\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"other2\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td class=\"c mono spaces\" id=\"other3\" style=\"vertical-align: top;\"></td>\r\n\t\t\t<td></td>\r\n\t\t</tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"alertother1\"></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"alertother2\"></td><td></td></tr>\r\n\t\t<!-- watch -->\r\n\t\t<!--<tr><td colspan=\"4\"><hr></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"4\"><span class=\"s3\">WATCH</span></td></tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"watchlist\"></td><td></td></tr>-->\r\n\r\n\t\t<!-- debug-->\r\n\t\t<tr><td colspan=\"4\"><hr></td><td></td></tr>\r\n\t\t<tr><td></td><td colspan=\"4\"><span class=\"s3\">DEBUG</span></td></tr>\r\n\t\t<tr><td></td><td colspan=\"3\" class=\"c mono spaces\" id=\"debug\"></td><td></td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nlet debug = []\r\n\r\nfunction outputVersion() {\r\n\r\n\tlet xS = green_tick,\r\n\t\txF = red_cross,\r\n\t\txB = yellow_block,\r\n\t\txNA = white_na,\r\n\t\txNC = sb +\"no change \"+ sc,\r\n\t\txOMG = sg +\"has landed\"+ sc,\r\n\t\tstrA = \"<a href='https://bugzilla.mozilla.org/\",\r\n\t\tstrB = \"' class='blue' target='_blank'>\",\r\n\t\tstrC = \"</a>\",\r\n\t\tt0 = performance.now()\r\n\r\n\tdebug.push(\"engine: \"+ isEnginePretty.trim() +\"<br>\")\r\n\tdebug.push(\" gecko: \"+ isFFpretty.trim() +\"<br>\")\r\n\r\n\tlet gTZP, gOther, gWatch // global timers\r\n\tlet runtype\r\n\r\n\tfunction output(type) {\r\n\t\t// vars\r\n\t\tlet results = [], expected, elAlert\r\n\t\t// data -> results in descending order\r\n\t\tif (type == \"tzp\") {\r\n\t\t\tconst names = Object.keys(oTZP).sort((a,b) => b-a)\r\n\t\t\tfor (const k of names) {results.push(oTZP[k])}\r\n\t\t\texpected = expectTZP\r\n\t\t\telAlert = dom.alerttzp2\r\n\t\t} else if (type == \"other\") {\r\n\t\t\tconst names = Object.keys(oOther).sort((a,b) => b-a)\r\n\t\t\tfor (const k of names) {results.push(oOther[k])}\r\n\t\t\texpected = expectOther\r\n\t\t\telAlert = dom.alertother2\r\n\t\t} else {\r\n\t\t\tconst names = Object.keys(oWatch).sort((a,b) => b-a)\r\n\t\t\tfor (const k of names) {results.push(oWatch[k])}\r\n\t\t\texpected = expectWatch\r\n\t\t\telAlert = dom.alertwatch2\r\n\t\t}\r\n\t\t// we duped on unique object names\r\n\t\tif (results.length !== expected) {\r\n\t\t\telAlert.innerHTML = sb.trim() + \"DISPLAY ALERT: expected \"+ expected +\" got \"+ results.length + sc + \"<br>\"\r\n\t\t}\r\n\t\t// build display data\r\n\t\tlet display = [], prevV = \"\", prevB = \"\", prefix = 0\r\n\t\tfor (let i=0; i < results.length; i++) {\r\n\t\t\tlet id = results[i].split(\"~\")[0].trim(),\r\n\t\t\t\tbug = results[i].split(\"~\")[1],\r\n\t\t\t\tresult = results[i].split(\"~\")[2],\r\n\t\t\t\tperf = results[i].split(\"~\")[3],\r\n\t\t\t\tnote = results[i].split(\"~\")[4]\r\n\t\t\t// round perf\r\n\t\t\tperf = Math.round(perf*1)\r\n\t\t\t// version\r\n\t\t\tif (Number.isInteger(id * 1)) {\r\n\t\t\t\tif (id !== prevV) {id = s3 + id.padStart(3) + sc} else {id = \"\".padStart(3)}\r\n\t\t\t}\r\n\t\t\t// note\r\n\t\t\tnote = ' '+ (note.substring(0,4)).padEnd(4)\r\n\t\t\t// bugzilla\r\n\t\t\tif (bug !== \"\") {\r\n\t\t\t\tbug = bug +\"\"\r\n\t\t\t\tprefix = 9 - bug.length\r\n\t\t\t\tbug = \"\".padStart(prefix) + strA + bug + strB + bug + strC + note\r\n\t\t\t} else {\r\n\t\t\t\tbug = \"\".padStart(9) + note\r\n\t\t\t}\r\n\t\t\t// perf\r\n\t\t\tperf = perf * 1\r\n\t\t\t// color up legit slow\r\n\t\t\tperf = (perf > 1 && perf < 50 ? sb + (perf + \"\").padStart(4) + sc : (perf + \"\").padStart(4)) + \" ms\"\r\n\t\t\tdisplay.push(id + bug + result + perf)\r\n\t\t\tprevV = results[i].split(\"~\")[0].trim()\r\n\t\t\tprevB = results[i].split(\"~\")[1]\r\n\t\t}\r\n\t\t// split into three columns: make sure we split with a version number\r\n\t\tlet cutpoint = Math.ceil(display.length/3)\r\n\t\tlet splitpoint = \"\"\r\n\t\tfor (let i = cutpoint; i < cutpoint + 5; i++) {\r\n\t\t\tlet test = display[i]\r\n\t\t\tif (splitpoint == \"\" & test.slice(0,1) !== \" \") {splitpoint = i}\r\n\t\t}\r\n\t\tlet display1 = display.slice(0, splitpoint)\r\n\t\t// split the rest in half\r\n\t\tdisplay = display.slice(splitpoint, display.length)\r\n\t\tcutpoint = Math.ceil(display.length/2)\r\n\t\tsplitpoint = \"\"\r\n\t\tfor (let i = cutpoint; i < cutpoint + 5; i++) {\r\n\t\t\tlet test = display[i]\r\n\t\t\tif (splitpoint == \"\" & test.slice(0,1) !== \" \") {splitpoint = i}\r\n\t\t}\r\n\t\tlet display2 = display.slice(0, splitpoint)\r\n\t\tlet display3 = display.slice(splitpoint, display.length)\r\n\t\t// output\r\n\t\tdocument.getElementById(type+\"1\").innerHTML = display1.join(\"<br>\")\r\n\t\tdocument.getElementById(type+\"2\").innerHTML = display2.join(\"<br>\")\r\n\t\tdocument.getElementById(type+\"3\").innerHTML = display3.join(\"<br>\")\r\n\t\t// debug\r\n\t\tif (debug.length) {dom.debug.innerHTML = debug.join(\"<br>\")}\r\n\t}\r\n\r\n\tlet countTZP = 0, expectTZP = 110, maxVer = 152, oTZP = {}, oResults = {}\r\n\tlet is144 = 'undefined' === typeof CSS2Properties\r\n\r\n\tfunction run_tzp() {\r\n\t\t// start global timer\r\n\t\tgTZP = performance.now()\r\n\t\t// record\r\n\t\tfunction rec(order, version, bug, result, perf, note = '') {\r\n\t\t\t//dom.perf.innerHTML = 'main: '+ order\r\n\t\t\toResults[order] = [version, (performance.now() - gTZP) +\" ms\", result]\r\n\t\t\t// do as little as possible to not affect perf\r\n\t\t\tif (result === true) {\r\n\t\t\t\tresult = xS\r\n\t\t\t\t//oResults[order] = version +\"~\"+ \r\n\t\t\t} else if (result === false || result === undefined || result == \"\") {\r\n\t\t\t\tresult = xF\r\n\t\t\t}\r\n\t\t\toTZP[order] = version +\"~\"+ bug +\"~\"+ result +\"~\"+ perf +\"~\"+ note\r\n\t\t\tcountTZP++\r\n\t\t\t// now do stuff after we have finished\r\n\t\t\tif (countTZP == expectTZP) {\r\n\t\t\t\tlet totalPerf = Math.round((performance.now() - gTZP))\r\n\t\t\t\tlet strSuccess = \"\"\r\n\t\t\t\tif (isFF) {\r\n\t\t\t\t\t//console.debug(oResults)\r\n\t\t\t\t\tlet results = []\r\n\t\t\t\t\tconst names = Object.keys(oResults).sort((a,b) => b-a)\r\n\t\t\t\t\tfor (const k of names) {results.push(oResults[k])}\r\n\r\n\t\t\t\t\t// loop: stop at first success: so this covers dual tests where one fails and one passes\r\n\t\t\t\t\t\t// e.g. [105, \"0 ms\", true]\r\n\t\t\t\t\tlet myVer = \"\", myPerf = \"\"\r\n\t\t\t\t\tfor (let i = 0; i < results.length; i++) {\r\n\t\t\t\t\t\tlet array = results[i]\r\n\t\t\t\t\t\tif (array[2]) {\r\n\t\t\t\t\t\t\tmyVer = array[0]\r\n\t\t\t\t\t\t\tmyPerf = array[1]\r\n\t\t\t\t\t\t\tbreak\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// cleanup false results\r\n\t\t\t\t\tlet is52 = false\r\n\t\t\t\t\tif (myVer == \"\") {is52 = true}\r\n\t\t\t\t\tif (\"function\" !== typeof CSSMozDocumentRule) {is52 = true} // Palemoon/Basilisk\r\n\t\t\t\t\t// waterfox classic: 57 pass, 56 fail\r\n\t\t\t\t\tif (\"function\" === typeof AbortSignal && \"undefined\" !== typeof HTMLAppletElement) {\r\n\t\t\t\t\t\t// but waterfox (v78) also fails 56, so test that as well\r\n\t\t\t\t\t\tif (!window.Document.prototype.hasOwnProperty(\"replaceChildren\")) {\r\n\t\t\t\t\t\t\tis52 = true\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (is52) {\r\n\t\t\t\t\t\tmyVer = \"52 or lower\"\r\n\t\t\t\t\t\tmyPerf = totalPerf +\" ms\"\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (myVer > (maxVer - 1)) {myVer += \"+\"}\r\n\t\t\t\t\tstrSuccess = \" \"+ s14 + \" [result: FF\"+ myVer +\" | \"+ myPerf +\"]\"+ sc\r\n\t\t\t\t}\r\n\t\t\t\tdom.perftzp.innerHTML = \"\".padStart(3) + expectTZP +\" tests | \"+ totalPerf +\" ms\" + strSuccess\r\n\t\t\t\toutput(\"tzp\")\r\n\t\t\t} else if (countTZP > expectTZP) {\r\n\t\t\t\tdom.alerttzp1.innerHTML = sb.trim() + \" RESULT ALERT: expected \"+ expectTZP +\" got \"+ countTZP + sc\r\n\t\t\t}\r\n\t\t}\r\n\r\nfunction testperf() {\r\n\tlet tStart = performance.now(), testversion\r\n\t// code here\r\n\r\n\tlet tEnd = performance.now()\r\n\tconsole.log(tEnd - tStart, 'ms', testversion)\r\n}\r\n\r\n\t\t// 152: 2034371\r\n\t\tlet t152 = performance.now(), ver152\r\n\t\ttry {\r\n\t\t\tif (SVGTextPathElement.prototype.hasOwnProperty('side')) {ver152 = true}\r\n\t\t} catch(e) {}\r\n\t\trec(152, 152, \"2034371\", ver152, performance.now()-t152) // 0.06 ms\r\n\r\n\t\t// 151: 2024291\r\n\t\tlet t151a = performance.now(), ver151a\r\n\t\ttry {\r\n\t\t\tTemporal.PlainMonthDay.from({calendar:'chinese', year: 1898, monthCode:'M01L', day: 29}, {overflow: 'reject'})\r\n\t\t} catch(e) {\r\n\t\t\tif ('RangeError: calendar field \"day\" is too large: 29' == e+'') ver151a = true\r\n\t\t}\r\n\t\trec(151.1, 151, \"2024291\", ver151a, performance.now()-t151a, 'TMPR') // 0.06 ms if no error | 0.35 ms is 151+\r\n\r\n\t\t// 151: 2022827\r\n\t\tlet t151 = performance.now(), ver151\r\n\t\ttry {\r\n\t\t\tif (CSSContainerRule.prototype.hasOwnProperty('conditions')) {ver151 = true}\r\n\t\t} catch(e) {}\r\n\t\trec(151, 151, \"2022827\", ver151, performance.now()-t151) // 0.005 ms\r\n\r\n\t\t// 150: 1801658\r\n\t\tlet t150 = performance.now(), ver150\r\n\t\ttry {\r\n\t\t\tver150 = 'object' == typeof visualViewport.onscrollend\r\n\t\t} catch(e) {}\r\n\t\trec(150, 150, \"1801658\", ver150, performance.now()-t150) // 0.005 \u001bms\r\n\r\n\t\t// 149: 2009792\r\n\t\tlet t149 = performance.now(), ver149\r\n\t\ttry {\r\n\t\t\tTemporal.PlainDate.from({calendar: 'gregory', monthCode: 'M12', month: 13, year: 2019, day: 1})\r\n\t\t} catch(e) {\r\n\t\t\tif ('RangeError' == e.name) {ver149 = true}\r\n\t\t}\r\n\t\trec(149, 149, \"2009792\", ver149, performance.now()-t149, 'TMPR') // 0.02 !temporal, 0.05 false, 0.07 true\r\n\r\n\t\t// 148: 1085214\r\n\t\t\t// fast-path: pref dom.location.ancestorOrigins.enabled (added + default true FF148+)\r\n\t\tlet t148a = performance.now(), ver148a\r\n\t\ttry {\r\n\t\t\tver148a = undefined !== location.ancestorOrigins\r\n\t\t} catch(e) {}\r\n\t\trec(148.1, 148, \"1085214\", ver148a, performance.now()-t148a, 'PREF') // 0.01ms\r\n\r\n\t\t// 148: 2004851\r\n\t\tlet t148 = performance.now(), ver148\r\n\t\ttry {\r\n\t\t\tlet test148 = new Temporal.Duration(0).total({unit:'years', relativeTo:'-271821-04-19'})\r\n\t\t\tver148 = true\r\n\t\t} catch(e) {}\r\n\t\trec(148, 148, \"2004851\", ver148, performance.now()-t148, 'TMPR') // 0.07ms fail | 0.04ms success\r\n\r\n\t\t// 147: 2000225 (not confirmed)\r\n\t\tlet t147 = performance.now(), ver147\r\n\t\ttry {ver147 = Intl.supportedValuesOf('numberingSystem').includes('tols')} catch(e) {}\r\n\t\trec(147, 147, \"2000225\", ver147, performance.now()-t147, 'INTL') // 0.02ms\r\n\r\n\t\t// 146: 1997216\r\n\t\tlet t146 = performance.now(), ver146\r\n\t\ttry {throw new DOMException('a', 'b')} catch(e) {ver146 = e.columnNumber !== 0}\r\n\t\trec(146, 146, \"1997216\", ver146, performance.now()-t146) // 0.02ms\r\n\r\n\t\t// 145: 1968987\r\n\t\tlet t145 = performance.now(), ver145\r\n\t\ttry {\r\n\t\t\tver145 = undefined !== (new ToggleEvent('toggle', null)).source\r\n\t\t} catch(e) {}\r\n\t\trec(145, 145, \"1968987\", ver145, performance.now()-t145) // 0.02ms\r\n\r\n\t\t// 144: 1919582\r\n\t\tlet t144 = performance.now()\r\n\t\tlet ver144 = undefined == window.CSS2Properties\r\n\t\trec(144, 144, \"1919582\", ver144, performance.now()-t144) // 0.005ms\r\n\r\n\t\t// 143: 1977489\r\n\t\t\t// fast-path: layout.css.moz-appearance.webidl.enabled (default false)\r\n\t\tlet t143 = performance.now(), ver143\r\n\t\ttry {\r\n\t\t\tver143 = (is144 || 'function' === typeof CSS2Properties && !CSS2Properties.prototype.hasOwnProperty('-moz-appearance'))\r\n\t\t} catch(e) {}\r\n\t\trec(143, 143, \"1977489\", ver143, performance.now()-t143, 'PREF') // \r\n\r\n\t\t// 142: 1960300\r\n\t\t\t// note: intl.icu4x.segmenter.enabled has no effect on this test\r\n\t\tlet t142 = performance.now(), ver142\r\n\t\ttry {\r\n\t\t\tlet segmenter = new Intl.Segmenter('en', {granularity:'word'})\r\n\t\t\tlet test142 = Array.from(segmenter.segment('a:b')).map(({ segment }) => segment)\r\n\t\t\tif (3 == test142.length) {ver142 = true}\r\n\t\t} catch(e) {}\r\n\t\trec(142, 142, \"1960300\", ver142, performance.now()-t142, 'INTL') // 0.15ms\r\n\r\n\t\t// 141: 1860030\r\n\t\t\t// fast-path: pref: dom.intersection_observer.scroll_margin.enabled (added + default true FF141+)\r\n\t\tlet t141a = performance.now(), ver141a\r\n\t\ttry {ver141a = window[\"IntersectionObserver\"].prototype.hasOwnProperty('scrollMargin')} catch(e) {}\r\n\t\trec(141.1, 141, \"1860030\", ver141a, performance.now()-t141a, \"PREF\") // 0.005ms\r\n\r\n\t\t// 141: 1950162: \r\n\t\t\t// fast-path: requires temporal (default enabled FF139+) javascript.options.experimental.temporal\r\n\t\tlet t141 = performance.now(), ver141\r\n\t\ttry {ver141 = undefined == Temporal.PlainDate.from('2029-12-31[u-ca=gregory]').weekOfYear} catch(e) {}\r\n\t\trec(141, 141, \"1950162\", ver141, performance.now()-t141, \"PREF\") // 0.06ms (0.02 if error)\r\n\r\n\t\t// 140: 1550462\r\n\t\t\t// fast-path: pref: dom.event.pointer.rawupdate.enabled  (added + default true FF140+)\r\n\t\tlet t140b = performance.now(), ver140b\r\n\t\ttry {ver140b = \"object\" === typeof onpointerrawupdate} catch(e) {}\r\n\t\trec(140.2, 140, \"1550462\", ver140b, performance.now()-t140b, \"PREF\") // 0.003ms\r\n\r\n\t\t// 140: 1963464\r\n\t\t\t// can't find any prefs that disable paint: added FF84+: https://developer.mozilla.org/en-US/docs/Web/API/PerformancePaintTiming\r\n\t\tlet t140a = performance.now(), ver140a\r\n\t\ttry {\r\n\t\t\tlet entries140 = performance.getEntriesByType(\"paint\")\r\n\t\t\tfor (let i=0; i < entries140.length; i++) {\r\n\t\t\t\tif ('[object PerformancePaintTiming]' === entries140[i] +'') {\r\n\t\t\t\t\tver140a = undefined !== entries140[i].presentationTime\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(140.1, 140, \"1963464\", ver140a, performance.now()-t140a) // 0.04ms\r\n\r\n\t\t// 140: 929890: 139 = \"\", 140+ = \"auto\" : also see 1969210\r\n\t\tlet t140 = performance.now(), ver140\r\n\t\ttry {ver140 = '' !== dom.tzpPreload.preload} catch(e) {}\r\n\t\trec(140, 140, \"929890\", ver140, performance.now()-t140, \"DOM\") // 0.04ms\r\n\r\n\t\t// 139: 1960556\r\n\t\tlet t139 = performance.now() , ver139\r\n\t\ttry {ver139 = HTMLDialogElement.prototype.hasOwnProperty('requestClose')} catch(e) {}\r\n\t\trec(139, 139, \"1960556\", ver139, performance.now()-t139) // 0.005ms\r\n\r\n\t\t// 138: 1525241\r\n\t\t\t// fast-path: requires webrtc\r\n\t\t\t// e.g. media.peerconnection.enabled | --disable-webrtc\r\n\t\tlet t138b = performance.now(), ver138b\r\n\t\ttry {\r\n\t\t\tver138b = RTCCertificate.prototype.hasOwnProperty('getFingerprints')\r\n\t\t} catch(e) {}\r\n\t\trec(138.2, 138, \"1525241\", ver138b, performance.now()-t138b, \"PREF\") // 0.004ms\r\n\r\n\t\t// 138: fast-path: dom.origin_agent_cluster.enabled\r\n\t\tlet t138a = performance.now()\r\n\t\tlet ver138a = 'boolean' == typeof originAgentCluster\r\n\t\trec(138.1, 138, \"1665474\", ver138a, performance.now()-t138a, \"PREF\") // 0.002ms\r\n\r\n\t\t// 138: 1954425: must be FF134 or higher\r\n\t\tlet t138 = performance.now(), ver138, test138\r\n\t\ttry {\r\n\t\t\tif (HTMLScriptElement.prototype.hasOwnProperty('textContent')) { // FF135+\r\n\t\t\t\ttest138 = Intl.NumberFormat('yo-bj', {style: 'unit', unit: 'year', unitDisplay: 'narrow'}).format(1)\r\n\t\t\t\tver138 = '606d1046' == mini(test138)\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(138, 138, \"1954425\", ver138, performance.now()-t138, \"INTL\") // 6ms cold, 0.3ms with TZP's NumberFormat warmup\r\n\r\n\t\t// 137: 1943120\r\n\t\t\t// javascript.options.experimental.math_sumprecise\r\n\t\t\t// fast-path 136 ifdef NIGHTLY_BUILD false | 137 true all releases\r\n\t\tlet t137 = performance.now(), ver137\r\n\t\ttry {\r\n\t\t\tver137 = 'function' == typeof Math.sumPrecise\r\n\t\t} catch(e) {}\r\n\t\trec(137, 137, \"1943120\", ver137, performance.now()-t137, \"PREF\") // 0.002ms\r\n\r\n\t\t// 136: 1939533\r\n\t\t\t// fast-path: FF132+ pref enabled javascript.options.experimental.regexp_modifiers\r\n\t\t\t// note: TB slider's `javascript.options.native_regexp` (requires restart) has no effect\r\n\t\tlet t136 = performance.now(), ver136\r\n\t\ttry {\r\n\t\t\tver136 = (new RegExp(\"(?i:[A-Z]{4})\")).test('abcd')\r\n\t\t} catch(e) {} // FF131 or lower: SyntaxError: invalid regexp group\r\n\t\trec(136, 136, \"1939533\", ver136, performance.now()-t136, \"PREF\") // 0.02ms\r\n\r\n\t\t// 135: 1905706\r\n\t\tlet t135 = performance.now()\r\n\t\tlet ver135 = HTMLScriptElement.prototype.hasOwnProperty('textContent')\r\n\t\trec(135, 135, \"1905706\", ver135, performance.now()-t135) // 0.004ms\r\n\r\n\t\t// 134: 1927706 : ie, lij, oc, st, szl, tn, za\r\n\t\t// packages that use --with-system-icu can cause issues\r\n\t\tlet t134 = performance.now(), ver134\r\n\t\ttry {ver134 = (\"lij\" == Intl.PluralRules.supportedLocalesOf(\"lij\").join())} catch(e) {}\r\n\t\trec(134, 134, \"1927706\", ver134, performance.now()-t134, \"INTL\") // 0.125ms\r\n\r\n\t\t// 133: 1837773\r\n\t\tlet t133 = performance.now(), ver133\r\n\t\ttry {\r\n\t\t\tlet parser = (new DOMParser).parseFromString(\"<select><option name=''></option></select>\", 'text/html')\r\n\t\t\tver133 = null === parser.body.firstChild.namedItem('')\r\n\t\t} catch(e) {}\r\n\t\trec(133, 133, \"1837773\", ver133, performance.now()-t133) // 0.2ms\r\n\r\n\t\t// 132: 1899413\r\n\t\tlet t132 = performance.now(), ver132 = false\r\n\t\ttry {\r\n\t\t\tconst re = new RegExp('(?:)', 'gv');\r\n\t\t\tlet test132 = RegExp.prototype[Symbol.matchAll].call(re, '𠮷')\r\n\t\t\tfor (let i=0; i < 3; i++) {ver132 = test132.next().done}\r\n\t\t} catch(e) {}\r\n\t\trec(132, 132, \"1899413\", ver132, performance.now()-t132) // 0.08ms | 0.03 if error\r\n\r\n\t\t// 131: 1900196\r\n\t\t// false posiitve in FF78 or lower\r\n\t\tlet t131 = performance.now(), ver131 = false\r\n\t\tif (window.hasOwnProperty(\"UserActivation\")) { // FF120+\r\n\t\t\ttry {\r\n\t\t\t\tlet test131 = new Intl.DateTimeFormat('zh', { calendar: 'chinese', dateStyle: 'medium'}).format(new Date(2033, 9, 1))\r\n\t\t\t\tver131 = '2033' == test131.slice(0,4)\r\n\t\t\t} catch(e) {}\r\n\t\t}\r\n\t\trec(131, 131, \"1900196\", ver131, performance.now()-t131, 'INTL') // slow AF\r\n\r\n\t\t// 130: 1907236\r\n\t\t// false posiitve in FF77 or lower\r\n\t\tlet t130 = performance.now(), ver130 = false\r\n\t\tif (window.hasOwnProperty(\"UserActivation\")) { // FF120+\r\n\t\t\ttry {\r\n\t\t\t\tnew RegExp('[\\\\00]','u')\r\n\t\t\t} catch(e) {\r\n\t\t\t\tif (e+'' == 'SyntaxError: invalid decimal escape in regular expression') {ver130 = true}\r\n\t\t\t}\r\n\t\t}\r\n\t\trec(130, 130, \"1907236\", ver130, performance.now()-t130) // 0.025ms\r\n\r\n\t\t// 129: 1595620\r\n\t\tlet t129 = performance.now()\r\n\t\tlet ver129 = (is144 || \"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"WebkitFontFeatureSettings\"))\r\n\t\trec(129, 129, \"1595620\", ver129, performance.now()-t129) // 0.6ms\r\n\r\n\t\t// 128: 1896509\r\n\t\tlet t128 = performance.now(), ver128 = false\r\n\t\ttry {\r\n\t\t\tlet test128 = (new Blob()).bytes()\r\n\t\t\tver128 = true\r\n\t\t} catch(e) {}\r\n\t\trec(128, 128, \"1896509\", ver128, performance.now()-t128) // 0.03ms\r\n\r\n\t\t// 127: 1894248\r\n\t\tlet t127 = performance.now(), ver127 = false\r\n\t\ttry {ver127 = (new Date('15Jan0024')).getYear() > 0} catch(e) {}\r\n\t\trec(127, 127, \"1894248\", ver127, performance.now()-t127, \"DATE\") // 0.07ms\r\n\r\n\t\t// 126: 1887611\r\n\t\tlet t126 = performance.now(), ver126 = false\r\n\t\ttry {ver126 = \"function\" === typeof URL.parse} catch(e) {}\r\n\t\trec(126, 126, \"1887611\", ver126, performance.now()-t126) // 0.25ms\r\n\r\n\t\t// 125: 1872793\r\n\t\tlet t125 = performance.now(), ver125 = false\r\n\t\ttry {\r\n\t\t\tver125 = \"Invalid Date\" == new Date(\"Sep 26 Thurs 1995 10:00\")\r\n\t\t} catch(e) {}\r\n\t\trec(125, 125, \"1872793\", ver125, performance.now()-t125, \"DATE\") // 0.05ms when invalid, 0.2+ when valid\r\n\r\n\t\t// 124: 1867569 (slow, like 122)\r\n\t\tlet t124 = performance.now(), ver124 = false\r\n\t\ttry {\r\n\t\t\tlet el124 = document.documentElement\r\n\t\t\tel124.style.zIndex = \"calc(1 / max(-0, 0))\" // default = auto\r\n\t\t\tlet test124 = getComputedStyle(el124).zIndex\r\n\t\t\tdebug.push(\"124 1867569 : \"+ test124)\r\n\t\t\tif (test124 > 0) {ver124 = true}\r\n\t\t\tel124.style.zIndex = \"auto\"\r\n\t\t} catch(e) {}\r\n\t\trec(124, 124, \"1867569\", ver124, performance.now()-t124) //\r\n\r\n\t\t// 123: 1871745\r\n\t\tlet t123 = performance.now()\r\n\t\tlet ver123 = (is144 || \"function\" === typeof CSS2Properties && !CSS2Properties.prototype.hasOwnProperty(\"MozUserFocus\"))\r\n\t\trec(123, 123, \"1871745\", ver123, performance.now()-t123)\r\n\r\n\t\t// 122: 1867558 (slow)\r\n\t\tlet t122 = performance.now(), ver122 = false\r\n\t\ttry {\r\n\t\t\tlet el122 = document.documentElement\r\n\t\t\tel122.style.zIndex = \"calc(1 / abs(-0))\" // default = auto\r\n\t\t\tlet test122 = getComputedStyle(el122).zIndex\r\n\t\t\tdebug.push(\"122 1867558 : \"+ test122)\r\n\t\t\tif (test122 > 0) {ver122 = true}\r\n\t\t\tel122.style.zIndex = \"auto\"\r\n\t\t} catch(e) {}\r\n\t\trec(122, 122, \"1867558\", ver122, performance.now()-t122) // 0.73ms\r\n\r\n\t\t// 121: 1845586\r\n\t\tlet t121 = performance.now()\r\n\t\tlet ver121 = (\"function\" === typeof Promise.withResolvers)\r\n\t\trec(121, 121, \"1845586\", ver121, performance.now()-t121) //\r\n\r\n\t\t// 120: 1791079\r\n\t\tlet t120 = performance.now()\r\n\t\tlet ver120 = (window.hasOwnProperty(\"UserActivation\"))\r\n\t\trec(120, 120, \"1791079\", ver120, performance.now()-t120) // 0.017ms\r\n\r\n\t\t// 119: 1817591\r\n\t\tlet t119 = performance.now()\r\n\t\ttry {\r\n\t\t\tlocation.href = \"http://a>b/\"\r\n\t\t} catch(e) {\r\n\t\t\trec(119, 119, \"1817591\", e.name === \"SyntaxError\", performance.now()-t119)\r\n\t\t}\r\n\r\n\t\t// 118: 1849010\r\n\t\tlet t118 = performance.now()\r\n\t\tlet ver118 = (is144 || \"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"fontSynthesisPosition\"))\r\n\t\trec(118, 118, \"1849010\", ver118, performance.now()-t118)\r\n\r\n\t\t// 117: 1842467\r\n\t\tlet t117 = performance.now()\r\n\t\tlet ver117 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"fontStretch\"))\r\n\t\trec(117, 117, \"1842467\", ver117, performance.now()-t117)\r\n\r\n\t\t// 116: 1839614\r\n\t\tlet t116 = performance.now()\r\n\t\tlet ver116 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"textRendering\"))\r\n\t\trec(116, 116, \"1839614\", ver116, performance.now()-t116)\r\n\r\n\t\t// 115: 1778909\r\n\t\tlet t115 = performance.now()\r\n\t\tlet ver115 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"letterSpacing\"))\r\n\t\trec(115, 115, \"1778909\", ver115, performance.now()-t115)\r\n\r\n\t\t// 114: 1826629 // slow ~2ms\r\n\t\tlet t114 = performance.now()\r\n\t\tlet ver114 = (is144 || \"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"WebkitTextSecurity\"))\r\n\t\trec(114, 114, \"1826629\", ver114, performance.now()-t114)\r\n\r\n\t\t// 113: 1709347\r\n\t\tlet t113 = performance.now()\r\n\t\tlet ver113 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"reset\"))\r\n\t\trec(113, 113, \"1709347\", ver113, performance.now()-t113)\r\n\r\n\t\t// 112: 1756175\r\n\t\tlet t112 = performance.now()\r\n\t\tlet ver112 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"roundRect\")) // 0.01ms\r\n\t\trec(112, 112, \"1756175\", ver112, performance.now()-t112)\r\n\r\n\t\t// 111: 1418449\r\n\t\tlet t111 = performance.now()\r\n\t\tlet ver111 = (HTMLElement.prototype.hasOwnProperty(\"translate\")) // 0.1ms\r\n\t\trec(111, 111, \"1418449\", ver111, performance.now()-t111) //\r\n\r\n\t\t// 110: 1689631\r\n\t\tlet t110 = performance.now()\r\n\t\tlet ver110 = (\"object\" === typeof ondeviceorientationabsolute)\r\n\t\trec(110, 110, \"1689631\", ver110, performance.now()-t110) // 0.01ms\r\n\r\n\t\t// 109: 1789776\r\n\t\tlet t109 = performance.now()\r\n\t\tlet ver109 = (CSSKeyframesRule.prototype.hasOwnProperty(\"length\")) // 0.01ms\r\n\t\trec(109, 109, \"1789776\", ver109, performance.now()-t109)\r\n\r\n\t\t// 108: 1574487\r\n\t\tlet t108 = performance.now()\r\n\t\tlet ver108 = (\"undefined\" === typeof onloadend)\r\n\t\trec(108, 108, \"1574487\", ver108, performance.now()-t108)\r\n\r\n\t\t// 107: 1174097\r\n\t\tlet t107 = performance.now()\r\n\t\tlet ver107 = (!SVGSVGElement.prototype.hasOwnProperty(\"useCurrentView\")) // 0.07ms\r\n\t\trec(107, 107, \"1174097\", ver107, performance.now()-t107)\r\n\r\n\t\t// 106: 1777293\r\n\t\tlet t106 = performance.now()\r\n\t\tlet ver106 = (Element.prototype.hasOwnProperty(\"checkVisibility\")) // 0.009 ms\r\n\t\trec(106, 106, \"1777293\", ver106, performance.now()-t106)\r\n\r\n\t\t// 105: 830716\r\n\t\t\t// 105: Function object could not be cloned. 36\r\n\t\t\t// 94-104: The object could not be cloned. 31\r\n\t\t\t// 93-: structuredClone is not defined. 30\r\n\t\tlet t105 = performance.now(), ver105\r\n\t\ttry {structuredClone((() => {}))} catch(e) {ver105 = (e.message.length == 36)}\r\n\t\trec(105, 105, \"830716\", ver105, performance.now()-t105) // 0.15ms\r\n\r\n\t\t// 104: 1712623\r\n\t\tlet t104 = performance.now(), ver104\r\n\t\tif (undefined !== window.SVGStyleElement) { // check window prop first e.g. servo\r\n\t\t\tver104 = (SVGStyleElement.prototype.hasOwnProperty(\"disabled\"))\r\n\t\t}\r\n\t\trec(104, 104, \"1712623\", ver104, performance.now()-t104)\r\n\r\n\t\t// 103: 1772494\r\n\t\tlet t103 = performance.now()\r\n\t\tlet ver103 = (undefined === new ErrorEvent(\"error\").error)\r\n\t\trec(103, 103, \"1772494\", ver103, performance.now()-t103)\r\n\r\n\t\t// 102: 1767541: regression FF99, hence check for v101\r\n\t\tlet t102 = performance.now()\r\n\t\tlet ver102 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"direction\") && Array(1).includes())\r\n\t\trec(102, 102, \"1767541\", ver102, performance.now()-t102)\r\n\r\n\t\t// 101: 1728999\r\n\t\tlet t101 = performance.now(), ver101 = (CanvasRenderingContext2D.prototype.hasOwnProperty(\"direction\"))\r\n\t\trec(101, 101, \"1728999\", ver101, performance.now()-t101)\r\n\r\n\t\t// 100: 1753309: legacy check: FF56- AbortSignal\r\n\t\tlet t100 = performance.now(), ver100 = (\"function\" === typeof AbortSignal\r\n\t\t\t&& \"function\" === typeof AbortSignal.timeout)\r\n\t\trec(100, 100, \"1753309\", ver100, performance.now()-t100)\r\n\r\n\t\t// 99: 1711715 + 1756204\r\n\t\t\t// note: FF90+ javascript.options.experimental.private_fields : default true\r\n\t\tlet t99 = performance.now(), ver99\r\n\t\ttry {\r\n\t\t\tnewFn(\"class A { #x; h(o) { return !#x in o; }}\")\r\n\t\t} catch(e) {\r\n\t\t\t// 99+ : invalid use of private name in unary expression without object reference (72) (pref has no effect)\r\n\t\t\t// 98- : private names aren't valid in this context (pref = true)\r\n\t\t\t// 98- : private fields are not currently supported (pref = false / no-pref)\r\n\t\t\t//debug.push(\" 99 1711715 : \"+ e.name +\" : \"+ e.message)\r\n\t\t\tver99 = (e.message.length == 72)\r\n\t\t}\r\n\t\trec(99, 99, \"1711715\", ver99, performance.now()-t99, \"EVAL\")\r\n\r\n\t\t// 98: 1709790\r\n\t\tlet t98 = performance.now(), ver98 = (HTMLElement.prototype.hasOwnProperty(\"outerText\"))\r\n\t\trec(98, 98, \"1709790\", ver98, performance.now()-t98)\r\n\r\n\t\t// 97: 1745372: legacy check: FF56- AbortSignal\r\n\t\tlet t97 = performance.now(), ver97 = (\"function\" === typeof AbortSignal\r\n\t\t\t&& \"function\" === typeof AbortSignal.prototype.throwIfAborted)\r\n\t\trec(97, 97, \"1745372\", ver97, performance.now()-t97)\r\n\r\n\t\t// 96: 1738422: legacy check + perf: toSource (74+): FF68- very slow: FF57- Intl.PluralRules\r\n\t\tlet t96 = performance.now(), ver96\r\n\t\ttry {ver96 = (\"undefined\" === typeof Object.toSource\r\n\t\t\t&& \"sc\" == Intl.PluralRules.supportedLocalesOf(\"sc\").join())} catch(e) {ver96 = xB}\r\n\t\trec(96, 96, \"1738422\", ver96, performance.now()-t96, \"INTL\")\r\n\r\n\t\t// 95: 1723674: fast path: dom.crypto.randomUUID.enabled = true (default)\r\n\t\tlet t95p = performance.now(), ver95p = (\"function\" === typeof crypto.randomUUID)\r\n\t\trec(95.1, 95, \"1723674\", ver95p, performance.now()-t95p, \"PREF\")\r\n\r\n\t\t// 95: 1674204 : slow\r\n\t\t\t// note: en-US, windows\r\n\t\t\t// FF60-65 wraps identical           : 93/93 = 1 (offscreen: if not offscreen uses page width)\r\n\t\t\t// FF66-94 wraps 2020-1 vs 2020-12   : 45/53 = 0.8490566037735849\r\n\t\t\t// FF95+   wraps 2020-1 x twice that : 47/93 = approx 0.5\r\n\t\tlet t95 = performance.now(), ver95\r\n\t\ttry {\r\n\t\t\tlet w95 = dom.test95a.offsetWidth, h95 = dom.test95b.offsetWidth\r\n\t\t\tlet test95 = w95/h95\r\n\t\t\tdebug.push(\" 95 1674204 : \"+ w95 +\"/\"+ h95 +\" = \" +test95)\r\n\t\t\tver95 = (test95 > 0.4 && test95 < 0.6)\r\n\t\t} catch(e) {\r\n\t\t\tver95 = xB\r\n\t\t}\r\n\t\trec(95, 95, \"1674204\", ver95, performance.now()-t95, \"DOM\")\r\n\r\n\t\t// 94: 1722576\r\n\t\tlet t94 = performance.now(), ver94 = (\"function\" === typeof self.structuredClone)\r\n\t\trec(94, 94, \"1722576\", ver94, performance.now()-t94)\r\n\r\n\t\t// 93: 1722448\r\n\t\tlet t93 = performance.now(), ver93 = (\"function\" === typeof self.reportError)\r\n\t\trec(93, 93, \"1722448\", ver93, performance.now()-t93)\r\n\r\n\t\t// 92: 1721149\r\n\t\tlet t92 = performance.now(), ver92 = (\"function\" === typeof Object.hasOwn)\r\n\t\trec(92, 92, \"1721149\", ver92, performance.now()-t92)\r\n\r\n\t\t// 91: 1717072: fast path: dom.window.clientinformation.enabled = true (default)\r\n\t\tlet t91p = performance.now(), ver91p = (\"object\" === typeof window.clientInformation)\r\n\t\trec(91.1, 91, \"1717072\", ver91p, performance.now()-t91p, \"PREF\")\r\n\r\n\t\t// 91: 1714933: legacy perf: toSource (74+): FF70- very slow\r\n\t\tlet t91 = performance.now(), ver91\r\n\t\ttry {ver91 = (\"undefined\" === typeof Object.toSource\r\n\t\t\t&& \"sa\" == Intl.Collator.supportedLocalesOf(\"sa\").join())} catch(e) {ver91 = xB}\r\n\t\trec(91, 91, \"1714933\", ver91, performance.now()-t91, \"INTL\")\r\n\r\n\t\t// 90: 1681371 shipped\r\n\t\tlet t90 = performance.now(), ver90 = (\"function\" == typeof Array.prototype.at)\r\n\t\trec(90, 90, \"1681371\", ver90, performance.now()-t90)\r\n\r\n\t\t// 89: 1684316: legacy check: FF64- CountQueuingStrategy\r\n\t\tlet t89 = performance.now(), ver89 = (\"function\" === typeof CountQueuingStrategy\r\n\t\t\t&& ! new CountQueuingStrategy({highWaterMark: 1}).hasOwnProperty(\"highWaterMark\"))\r\n\t\trec(89, 89, \"1684316\", ver89, performance.now()-t89)\r\n\r\n\t\t// 88: 1497557\r\n\t\tlet t88 = performance.now(), ver88 = (\":\" === document.createElement(\"a\").protocol)\r\n\t\trec(88,88, \"1497557\", ver88, performance.now()-t88)\r\n\r\n\t\t// 87: 1688335\r\n\t\tlet t87 = performance.now(), ver87 = (undefined === console.length)\r\n\t\trec(87, 87, \"1688335\", ver87, performance.now()-t87)\r\n\r\n\t\t// 86: 1654116 shipped\r\n\t\tlet t86 = performance.now(), ver86 = (\"function\" === typeof Intl.DisplayNames)\r\n\t\trec(86, 86, \"1654116\", ver86, performance.now()-t86)\r\n\r\n\t\t// 85: 1675240\r\n\t\t\t// 85+: RegExp.prototype.global getter called on non-RegExp object: string\r\n\t\t\t// 84-: get global method called on incompatible string\r\n\t\tlet t85 = performance.now(), ver85\r\n\t\ttry {\r\n\t\t\tObject.getOwnPropertyDescriptor(RegExp.prototype, \"global\").get.call(\"/a\")\r\n\t\t} catch(e) {\r\n\t\t\tver85 = (e.message.length == 66)\r\n\t\t}\r\n\t\trec(85, 85, \"1675240\", ver85, performance.now()-t85)\r\n\r\n\t\t// 84: 1518999\r\n\t\tlet t84 = performance.now(), ver84 = (\"function\" === typeof PerformancePaintTiming)\r\n\t\trec(84, 84, \"1518999\", ver84, performance.now()-t84)\r\n\r\n\t\t// 83: 1665252: legacy check: toSource (74+): FF55- false positive\r\n\t\tlet t83 = performance.now(), ver83 = (\"undefined\" === typeof Object.toSource\r\n\t\t\t&& !window.HTMLIFrameElement.prototype.hasOwnProperty(\"allowPaymentRequest\"))\r\n\t\trec(83, 83, \"1665252\", ver83, performance.now()-t83)\r\n\r\n\t\t// 82: 1655947\r\n\t\t\t// ext fuckery: cydec\r\n\t\tlet t82 = performance.now(), ver82\r\n\t\ttry {ver82 = (1595289600000 === Date.parse(\"21 Jul 20 00:00:00 GMT\"))} catch(e) {ver82 = xB}\r\n\t\trec(82, 82, \"1655947\", ver82, performance.now()-t82, \"DATE\")\r\n\r\n\t\t// 81: 1650607: legacy check: toSource (74+): 52 false positive\r\n\t\tlet t81 = performance.now(), ver81 = (\"undefined\" === typeof Object.toSource\r\n\t\t\t&& new File([\"x\"], \"a/b\").name == \"a/b\")\r\n\t\trec(81, 81, \"1650607\", ver81, performance.now()-t81)\r\n\r\n\t\t// 80: 1620467\r\n\t\t\t// blink check: CSS2Properties\r\n\t\tlet t80 = performance.now()\r\n\t\tlet ver80 = (is144 || \"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"appearance\"))\r\n\t\trec(80, 80, \"1620467\", ver80, performance.now()-t80)\r\n\r\n\t\t// 79: 1599769 shipped\r\n\t\tlet t79 = performance.now(), ver79 = (\"function\" === typeof Promise.any)\r\n\t\trec(79, 79, \"1599769\", ver79, performance.now()-t79)\r\n\r\n\t\t// 78: 1626015\r\n\t\tlet t78 = performance.now(), ver78 = (window.Document.prototype.hasOwnProperty(\"replaceChildren\"))\r\n\t\trec(78, 78, \"1626015\", ver78, performance.now()-t78)\r\n\r\n\t\t// 77: 1536540\r\n\t\tlet t77 = performance.now(), ver77\r\n\t\tif (undefined !== window.IDBCursor) { // checked for window proeprty for e.g. servo\r\n\t\t\tver77 = (window.IDBCursor.prototype.hasOwnProperty(\"request\"))\r\n\t\t}\r\n\t\trec(77, 77, \"1536540\", ver77, performance.now()-t77)\r\n\r\n\t\t// 76: 1608010: legacy check toSource (74+) FF56- false positive\r\n\t\tlet t76 = performance.now(), ver76 = (\"undefined\" === typeof Object.toSource && !test76.validity.rangeOverflow)\r\n\t\trec(76, 76, \"1608010\", ver76, performance.now()-t76, \"DOM\")\r\n\r\n\t\t// 75: 1613713\r\n\t\tlet t75 = performance.now(), ver75 = (\"function\" === typeof Intl.Locale)\r\n\t\trec(75, 75, \"1613713\", ver75, performance.now()-t75)\r\n\r\n\t\t// 74 1565170\r\n\t\tlet t74 = performance.now(), ver74 = (\"undefined\" === typeof Object.toSource)\r\n\t\trec(74, 74, \"1565170\", ver74, performance.now()-t74)\r\n\r\n\t\t// 73: 1602163\r\n\t\tlet t73 = performance.now(), ver73 = (\"function\" === typeof VideoPlaybackQuality\r\n\t\t\t&& !VideoPlaybackQuality.prototype.hasOwnProperty(\"corruptedVideoFrames\"))\r\n\t\trec(73, 73, \"1602163\", ver73, performance.now()-t73)\r\n\r\n\t\t// 72: 1591892\r\n\t\tlet t72 = performance.now(), ver72 = (\"boolean\" === typeof self.crossOriginIsolated)\r\n\t\trec(72, 72, \"1591892\", ver72, performance.now()-t72)\r\n\r\n\t\t// 71: 1549176\r\n\t\tlet t71 = performance.now(), ver71 = (\"function\" === typeof Promise.allSettled)\r\n\t\trec(71, 71, \"1549176\", ver71, performance.now()-t71)\r\n\r\n\t\t// 70: 1473229: legacy check: FF64- Intl.RelativeTimeFormat\r\n\t\t\t// ext fuckery: chameleon\r\n\t\tlet t70e = performance.now(), ver70e = (\"function\" === typeof Intl.RelativeTimeFormat\r\n\t\t\t&& \"function\" === typeof Intl.RelativeTimeFormat.prototype.formatToParts)\r\n\t\trec(70.1, 70, \"1473229\", ver70e, performance.now()-t70e, \"INTL\")\r\n\r\n\t\t// 70: 1435818: fallback\r\n\t\tlet t70 = performance.now(), ver70\r\n\t\ttry {newFn(\"let t = 1_050\"); ver70 = true} catch(e) {}\r\n\t\trec(70, 70, \"1435818\", ver70, performance.now()-t70, \"EVAL\")\r\n\r\n\t\t// 69: 1557121\r\n\t\tlet t69 = performance.now(), ver69 = (\"function\" === typeof Blob.prototype.text)\r\n\t\trec(69, 69, \"1557121\", ver69, performance.now()-t69)\r\n\r\n\t\t// 68: 1548773\r\n\t\tlet t68 = performance.now(), ver68 = (!HTMLObjectElement.prototype.hasOwnProperty(\"typeMustMatch\"))\r\n\t\trec(68, 68, \"1548773\", ver68, performance.now()-t68)\r\n\r\n\t\t// 67: 1531830\r\n\t\tlet t67 = performance.now(), ver67 = (\"function\" === typeof String.prototype.matchAll)\r\n\t\trec(67, 67, \"1531830\", ver67, performance.now()-t67)\r\n\r\n\t\t// 66: 1425685: legacy check: FF60- HTMLSlotElement\r\n\t\tlet t66 = performance.now(), ver66 = (\"function\" === typeof HTMLSlotElement\r\n\t\t\t&& \"function\" === typeof HTMLSlotElement.prototype.assignedElements)\r\n\t\trec(66, 66, \"1425685\", ver66, performance.now()-t66)\r\n\r\n\t\t// 65: 1334813\r\n\t\tlet t65 = performance.now(), ver65 = (1 === DataView.length)\r\n\t\trec(65, 65, \"1334813\", ver65, performance.now()-t65)\r\n\r\n\t\t// 64: 1498860\r\n\t\tlet t64 = performance.now(), ver64 = (\"number\" === typeof window.screenTop)\r\n\t\trec(64, 64, \"1498860\", ver64, performance.now()-t64)\r\n\r\n\t\t// 63: 1472170\r\n\t\tlet t63 = performance.now(), ver63 = (\"desc\" === Symbol('desc').description)\r\n\t\trec(63, 63, \"1472170\", ver63, performance.now()-t63)\r\n\r\n\t\t// 62: 1458466\r\n\t\tlet t62 = performance.now(), ver62 = (\"function\" === typeof console.timeLog)\r\n\t\trec(62, 62, \"1458466\", ver62, performance.now()-t62)\r\n\r\n\t\t// 61: 1455805\r\n\t\tlet t61 = performance.now(), ver61 = (\"object\" === typeof CSS)\r\n\t\trec(61, 61, \"1455805\", ver61, performance.now()-t61)\r\n\r\n\t\t// 60: 1436659\r\n\t\tlet t60 = performance.now(), ver60\r\n\t\tif (undefined !== window.Animation) { // check for window property e.g. servo\r\n\t\t\tver60 = (\"function\" === typeof Animation.prototype.updatePlaybackRate)\r\n\t\t}\r\n\t\trec(60, 60, \"1436659\", ver60, performance.now()-t60)\r\n\r\n\t\t// 59: 1336400\r\n\t\tlet t59 = performance.now(), ver59 = (!HTMLMediaElement.prototype.hasOwnProperty(\"mozAutoplayEnabled\"))\r\n\t\trec(59, 59, \"1336400\", ver59, performance.now()-t59)\r\n\r\n\t\t// 58: 1403318\r\n\t\tlet t58 = performance.now(), ver58 = (\"function\" === typeof Intl.PluralRules)\r\n\t\trec(58, 58, \"1403318\", ver58, performance.now()-t58)\r\n\r\n\t\t// 57: 1378342\r\n\t\tlet t57 = performance.now(), ver57 = (\"function\" === typeof AbortSignal)\r\n\t\trec(57, 57, \"1378342\", ver57, performance.now()-t57)\r\n\r\n\t\t// 56: 1279218\r\n\t\tlet t56 = performance.now(), ver56 = (\"function\" !== typeof HTMLAppletElement)\r\n\t\trec(56, 56, \"1279218\", ver56, performance.now()-t56)\r\n\r\n\t\t// 55: 1351795\r\n\t\tlet t55 = performance.now(), ver55 = (\"undefined\" === typeof console.timeline)\r\n\t\trec(55, 55, \"1351795\", ver55, performance.now()-t55)\r\n\r\n\t\t// 54: 1337702\r\n\t\tlet t54 = performance.now(), ver54 = (URL.prototype.hasOwnProperty(\"toJSON\"))\r\n\t\trec(54, 54, \"1337702\", ver54, performance.now()-t54)\r\n\r\n\t\t// 53: \r\n\t\tlet t53 = performance.now(), ver53 = (\"function\" === typeof CSSMozDocumentRule)\r\n\t\trec(53, 53, \"\", ver53, performance.now()-t53)\r\n\r\n\t}\r\n\trun_tzp()\r\n\r\n\tlet countOther = 0, expectOther = 70, oOther = {}\r\n\tfunction run_other() {\r\n\t\t// start global timer\r\n\t\tgOther = performance.now()\r\n\t\t// record\r\n\t\tfunction rec(order, version, bug, result, perf, note = \"\") {\r\n\t\t\tif (result === true) {result = xS\r\n\t\t\t} else if (result === false || result === undefined) {result = xF}\r\n\t\t\toOther[order] = version +\"~\"+ bug +\"~\"+ result +\"~\"+ perf +\"~\"+ note\r\n\t\t\tcountOther++\r\n\t\t\tif (countOther == expectOther) {\r\n\t\t\t\tdom.perfother = \"\".padStart(3) + expectOther +\" tests | \"+ Math.round((performance.now() - gOther)) +\" ms\"\r\n\t\t\t\toutput(\"other\")\r\n\t\t\t} else if (countOther > expectOther) {\r\n\t\t\t\tdom.alertother1.innerHTML = sb.trim() + \" RESULT ALERT: expected \"+ expectOther +\" got \"+ countOther + sc\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 150: 1561441: fast-path: requires webRTC\r\n\t\tlet t150a = performance.now(), ver150a\r\n\t\ttry {\r\n\t\t\tver150a = 'function' == typeof window.RTCPeerConnectionIceErrorEvent\r\n\t\t} catch(e) {}\r\n\t\trec(150.1, 150, \"1561441\", ver150a, performance.now()-t150a, 'RTC') // 0.004ms\r\n\r\n\t\t// 150: 2018544\r\n\t\t\t// false positives if FF134-146 so check for 149 first\r\n\t\tlet t150 = performance.now(), ver150\r\n\t\ttry {\r\n\t\t\tTemporal.PlainDate.from({calendar:'gregory', monthCode:'M12', month:13, year:2019, day:1})\r\n\t\t} catch(e) {\r\n\t\t\tif ('RangeError' == e.name) { // FF149+\r\n\t\t\t\tver150 = 'Anno Domini 1970-01-01' == new Intl.DateTimeFormat('en-u-ca-iso8601', {era: 'long'}).format(0)\r\n\t\t\t}\r\n\t\t}\r\n\t\trec(150, 150, \"2018544\", ver150, performance.now()-t150, 'TMPR') // \u001b~1+ ms\r\n\r\n\t\t// 149: 1999917 = SLOW\r\n\t\tlet t149 = performance.now(), ver149\r\n\t\ttry {\r\n\t\t\tver149 = '/' !== new Intl.DateTimeFormat(\"en-u-ca-chinese\", {timeZone:\"UTC\"}).format(new Date(\"+100000-01-01T00:00:00.000Z\"))\r\n\t\t} catch(e) {}\r\n\t\trec(149, 149, \"1999917\", ver149, performance.now()-t149, 'DATE') // 6.5ms\r\n\r\n\t\t// 147: 1666613: can't be used with base browser for when they backport it to ESR140\r\n\t\tlet t147 = performance.now(), ver147\r\n\t\ttry {\r\n\t\t\tlet test147 = ((new DOMParser()).parseFromString('INVALID', 'text/xml')).firstChild.attributes[0].nodeValue\r\n\t\t\tver147 = true\r\n\t\t} catch(e) {}\r\n\t\trec(147, 147, \"1666613\", ver147, performance.now()-t147) // 0.17ms\r\n\r\n\t\t// 140: 1965802 SLOW\r\n\t\tlet t140 = performance.now(), ver140\r\n\t\ttry {\r\n\t\t\tdocument.body.style.zIndex = 'calc(1 + 0.01)'\r\n\t\t\tver140 = 'calc(1.01)' == document.body.style.zIndex\r\n\t\t\tdocument.body.style.zIndex = ''\r\n\t\t} catch(e) {}\r\n\t\trec(140, 140, \"1965802\", ver140, performance.now()-t140) // 0.6ms\r\n\r\n\t\t// 139: 1960049\r\n\t\tlet t139 = performance.now(), ver139\r\n\t\ttry {\r\n\t\t\tver139 = Intl.supportedValuesOf(\"timeZone\").includes(\"America/Coyhaique\")\r\n\t\t} catch(e) {}\r\n\t\trec(139, 139, \"1960049\", ver139, performance.now()-t139, \"INTL\") // 0.3ms with warmup | 2ms cold\r\n\r\n\t\t// 136: 1945535: backported to 136 and ESR128\r\n\t\tlet t136 = performance.now(), ver136\r\n\t\ttry {\r\n\t\t\t// backported to ESR128, so check for 129+ first\r\n\t\t\tif (is144 || \"function\" === typeof CSS2Properties && CSS2Properties.prototype.hasOwnProperty(\"WebkitFontFeatureSettings\")) {\r\n\t\t\t\tlet test136 = new Date('July 2, 2025 0:00:00 UTC').toLocaleString('en', {timeZone: 'America/Asuncion'})\r\n\t\t\t\tver136 = '7/1/2025, 9:00:00 PM' == test136 // was -4, now -3\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(136, 136, \"1945535\", ver136, performance.now()-t136, 'DATE') // 0.25ms\r\n\r\n\t\t// 135: 1935198: pref `layout.css.moz-user-input.enabled` added as false\r\n\t\t\t// fast-path: if property missing you can't be any lower than 135: \"MozUserInput\",\"-moz-user-input\"\r\n\t\tlet t135c = performance.now()\r\n\t\tlet ver135c = (is144 || \"function\" === typeof CSS2Properties && !CSS2Properties.prototype.hasOwnProperty(\"MozUserInput\"))\r\n\t\trec(135.3, 135, \"1935198\", ver135c, performance.now()-t135c, 'PREF') // 0.002ms\r\n\r\n\t\t// 135: 1930464\r\n\t\tlet t135b = performance.now(), ver135b\r\n\t\ttry {\r\n\t\t\t// returns 2 in FF52 but lets avoid any possible old-timey regressions/changes\r\n\t\t\t// tested FF115-135\r\n\t\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty('letterSpacing')) { // FF115+\r\n\t\t\t\tlet test135b = new Intl.NumberFormat('en-US', {style:'currency', currency:'USD', notation:'scientific'})\r\n\t\t\t\tver135b = 0 == test135b.resolvedOptions().minimumFractionDigits\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(135.2, 135, \"1930464\", ver135b, performance.now()-t135b, 'INTL') // 0.2ms\r\n\r\n\t\t// 135: 1775215\r\n\t\tlet t135a = performance.now(), ver135a\r\n\t\ttry {\r\n\t\t\tnewFn('var else')\r\n\t\t} catch(e) {\r\n\t\t\t// SyntaxError: missing variable name, got keyword 'else'\r\n\t\t\tver135a = (e+'').includes('else')\r\n\t\t}\r\n\t\trec(135.1, 135, \"1775215\", ver135a, performance.now()-t135a, \"EVAL\") // 0.05ms\r\n\r\n\t\t// 135: 1930466\r\n\t\t\t// this broke in FF142+, and even though we could wrap it in another test\r\n\t\t\t// and it doesn't affect any logic since we cascade down, let's just remove it\r\n\t\t/*\r\n\t\tlet t135 = performance.now(), ver135 = false\r\n\t\ttry {\r\n\t\t\tlet test135 = new Intl.DateTimeFormat('en', {timeZone: 'Factory'}).format(new Date())\r\n\t\t} catch(e) {\r\n\t\t\tif ('RangeError: invalid time zone in DateTimeFormat(): Factory' == e+'') ver135 = true\r\n\t\t}\r\n\t\trec(135, 135, \"1930466\", ver135, performance.now()-t135) // very slow if not an error, slow if an eror\r\n\t\t//*/\r\n\r\n\t\t// 134: 1927706 (see 1931044)\r\n\t\t// packages that use --with-system-icu can cause issues\r\n\t\tlet t134 = performance.now(), ver134\r\n\t\ttry {\r\n\t\t\t// avoid old timey false positives e.g FF78-109\r\n\t\t\tif (CanvasRenderingContext2D.prototype.hasOwnProperty('letterSpacing')) { // FF115+\r\n\t\t\t\tver134 = '$1.00' == (1).toLocaleString('en-CA', {style: 'currency', currencyDisplay: 'narrowSymbol', currency: 'USD'})\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(134, 134, \"1927706\", ver134, performance.now()-t134, \"INTL\") // 0.2ms\r\n\r\n\t\t// 133: 1922503\r\n\t\tlet t133 = performance.now(), ver133\r\n\t\ttry {\r\n\t\t\tlet date133 = new Date('jan 15 2000')\r\n\t\t\tver133 = date133.toLocaleString(\"en\", {timeZone: \"Asia/Choibalsan\"}) == date133.toLocaleString(\"en\", {timeZone: \"Asia/Ulaanbaatar\"})\r\n\t\t} catch(e) {}\r\n\t\trec(133, 133, \"1922503\", ver133, performance.now()-t133) // 0.3ms\r\n\r\n\t\t// 130: 1907369\r\n\t\tlet t130 = performance.now(), ver130 = false\r\n\t\ttry {\r\n\t\t\teval('class X { constructor() {} constructor() {} }') // eww, eval\r\n\t\t} catch(e) {\r\n\t\t\tif (e+'' == 'SyntaxError: a class cannot have more than one constructor definition') {ver130 = true}\r\n\t\t}\r\n\t\trec(130, 130, \"1907369\", ver130, performance.now()-t130, \"EVAL\") // 0.09ms\r\n\r\n\t\t// 129: 1796785\r\n\t\tlet t129 = performance.now(), ver129 = false\r\n\t\ttry {\r\n\t\t\tver129 = (\"function\" === typeof PerformanceResourceTiming && PerformanceResourceTiming.prototype.hasOwnProperty(\"responseStatus\"))\r\n\t\t} catch(e) {}\r\n\t\trec(129, 129, \"1796785\", ver129, performance.now()-t129) // 0.04ms\r\n\r\n\t\t// 128: 1887817\r\n\t\t\t// FF123-27: Error: Permission denied to access property \"lastModified\"\r\n\t\t\t// ^ if console closed: NS_ERROR_UNEXPECTED:\r\n\t\t\t// ^ we need to check a property otherwise no error is thrown if the console is open\r\n\t\t\t// FF52-122: TypeError: Document.parseHTMLUnsafe is not a function\r\n\t\t// relies on dom.webcomponents.shadowdom.declarative.enabled = true (flipped true in FF123)\r\n\t\tlet t128 = performance.now(), ver128 = false\r\n\t\ttry {\r\n\t\t\tlet test128 = Document.parseHTMLUnsafe('<p></p>').lastModified\r\n\t\t\tver128 = true\r\n\t\t} catch(e) {}\r\n\t\trec(128, 128, \"1887817\", ver128, performance.now()-t128) // 0.08ms\r\n\r\n\t\t// 122: 1862910\r\n\t\tlet t122 = performance.now()\r\n\t\tlet\tver122 = false\r\n\t\tif (\"function\" === typeof Promise.withResolvers) { // 121 or lower is slow, so only check if 121+\r\n\t\t\tver122 = !isNaN(Date.parse(\"Mercredi 8 Septembre 2021\")) ? true : false\r\n\t\t}\r\n\t\trec(122, 122, \"1862910\", ver122, performance.now()-t122, \"DATE\") // 122: 0.1 | 121 = 1.1\r\n\r\n\t\t// 120b: 1557650\r\n\t\tlet t120b = performance.now()\r\n\t\tlet ver120b = (new Date(\"19999-11-11\").toString() !== \"Invalid Date\")\r\n\t\trec(120.2, 120, \"1557650\", ver120b, performance.now()-t120b, \"DATE\") // 0.22ms (on new rig)\r\n\r\n\t\t// 120a: 449921\r\n\t\tlet t120a = performance.now()\r\n\t\tlet\tver120a = (new Date(\"1JAN2008\").toString() !== \"Invalid Date\")\r\n\t\trec(120.1, 120, \"449921\", ver120a, performance.now()-t120a, \"DATE\") // 0.22ms (on new rig)\r\n\r\n\t\t// 120: 1852422\r\n\t\tlet t120 = performance.now()\r\n\t\tlet ver120 = (new Date(\"01/01/2001 10:00Z\").toString() !== \"Invalid Date\")\r\n\t\trec(120, 120, \"1852422\", ver120, performance.now()-t120, \"DATE\") // 0.22ms (on new rig)\r\n\r\n\t\t// 116: 1769088\r\n\t\tlet t116 = performance.now()\r\n\t\tlet ver116 = (isNaN(Date.parse(\"-000000-01-01T00:00:00.000Z\")))\r\n\t\trec(116, 116, \"1769088\", ver116, performance.now()-t116, \"DATE\") // 0.015ms\r\n\r\n\t\t// 107: 344060\r\n\t\tlet t107 = performance.now(), ver107\r\n\t\t// false positives FF52-57\r\n\t\tif (Element.prototype.hasOwnProperty(\"checkVisibility\")) {\r\n\t\t\ttry {\r\n\t\t\t\tdom.test107.options.length = -1\r\n\t\t\t\tver107 = true\r\n\t\t\t} catch(e) {} // NotSupportedError Operation is not supported\r\n\t\t}\r\n\t\trec(107, 107, \"344060\", ver107, performance.now()-t107, \"DOM\")\r\n\r\n\t\t// 101a: 1764050\r\n\t\tlet t101a = performance.now(), ver101a\r\n\t\ttry {\r\n\t\t\tver101a = Intl.supportedValuesOf(\"currency\").includes(\"SLE\")\r\n\t\t} catch(e) {}\r\n\t\trec(101.1, 101, \"1764050\", ver101a, performance.now()-t101a, \"INTL\")\r\n\r\n\t\t// 101: 1752808\r\n\t\tlet t101 = performance.now(), ver101\r\n\t\ttry {\r\n\t\t\t// avoid old timey false positives under FF93\r\n\t\t\tif (\"function\" === typeof self.structuredClone) { // FF94+\r\n\t\t\t\tlet test101 = new Intl.NumberFormat(\"en\", {style:\"currency\", currency:\"USD\", maximumFractionDigits:2, notation:\"compact\"}).format(1773500)\r\n\t\t\t\tver101 = (test101 == \"$1.77M\")\r\n\t\t\t\tdebug.push(\"101 1752808 : \"+ test101)\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t\trec(101, 101, \"1752808\", ver101, performance.now()-t101, 'INTL')\r\n\t\t// 99: 1720353\r\n\t\t\t// ext fuckery possible/likely due to mimeTypes/plugins\r\n\t\tlet t99 = performance.now(), ver99 = (\"pdfViewerEnabled\" in navigator)\r\n\t\trec(99, 99, \"1720353\", ver99, performance.now()-t99, \"PREF\")\r\n\r\n\t\t// 96: 1738422\r\n\t\t\t// ext fuckery: chameleon, cydec\r\n\t\tlet t96 = performance.now(), ver96\r\n\t\ttry {\r\n\t\t\t//let date96 = \r\n\t\t\tlet test96 = new Date(Date.UTC(2012,12-1,6,12,0,0)).toLocaleString(\"zh-Hans-CN\", {timeZone: \"Asia/Shanghai\", timeZoneName: \"long\"})\r\n\t\t\tver96 = (test96 == \"2012/12/6 中国标准时间 20:00:00\")\r\n\t\t} catch(e) {\r\n\t\t\tdebug.push(\" 96 1738422 : \"+ e.name +\": \"+ e.message)\r\n\t\t\tver96 = xB\r\n\t\t}\r\n\t\trec(96,96, \"1738422\", ver96, performance.now()-t96, \"DATE\")\r\n\r\n\t\t// 94: 1729239\r\n\t\tlet t94 = performance.now(), ver94 = (\"function\" === typeof HTMLScriptElement.supports)\r\n\t\trec(94, 94, \"1729239\", ver94, performance.now()-t94)\r\n\r\n\t\t// 93b: 1670033\r\n\t\tlet t93b = performance.now(), ver93b = (\"function\" === typeof Intl.supportedValuesOf)\r\n\t\trec(93.1, 93, \"1670033\", ver93b, performance.now()-t93b)\r\n\r\n\t\t// 93a: 1328672\r\n\t\tlet t93a = performance.now(), ver93a\r\n\t\ttry {\r\n\t\t\tver93a = (!isNaN(new Date(\"1997-03-08 11:19:10-07\").getTime()))\r\n\t\t} catch(e) {\r\n\t\t\tver93a = xB\r\n\t\t}\r\n\t\trec(93, 93, \"1328672\", ver93a, performance.now()-t93a)\r\n\r\n\t\t// 91b: 1284868\r\n\t\t\t// ext fuckery: cydec\r\n\t\tlet t91b = performance.now(), ver91b\r\n\t\ttry {\r\n\t\t\tlet test91b = new Intl.DateTimeFormat('en-US', {second: \"2-digit\", hour12: false})\r\n\t\t\tver91b = (\"07\" == test91b.format(new Date('2016/06/06 15:05:07')))\r\n\t\t} catch(e) {\r\n\t\t\tver91b = xB\r\n\t\t}\r\n\t\trec(91.1, 91, \"1284868\", ver91b, performance.now()-t91b, \"DATE\")\r\n\r\n\t\t// 91a: 1710429: very slow cold ~15-20ms, slow 4-5ms\r\n\t\tlet t91a = performance.now(), ver91a\r\n\t\ttry {\r\n\t\t\tlet t91 = Intl.DateTimeFormat(undefined, {timeZoneName: \"longGeneric\"}).format(new Date(\"January 30, 2019 13:00:00\"))\r\n\t\t\tver91a = true\r\n\t\t} catch(e) {}\r\n\t\trec(91, 91, \"1710429\", ver91a, performance.now()-t91a)\r\n\r\n\t\t// 90b: 1520434\r\n\t\t\t// 90+: start offset of Int32Array should be a multiple of 4\r\n\t\t\t// 89-: attempting to construct out-of-bounds TypedArray on ArrayBuffer\r\n\t\tlet t90b = performance.now(), ver90b\r\n\t\ttry {let tst90b = new Int32Array(new ArrayBuffer(4096), 7)} catch(e) {ver90b = (e.message.length == 52)}\r\n\t\trec(90.1, 90, \"1520434\", ver90b, performance.now()-t90b)\r\n\r\n\t\t// 90a: 1537689\r\n\t\tlet t90a = performance.now(), ver90a\r\n\t\ttry {ver90a = (dom.test90a.defaultValue == \"a\")} catch(e) {}\r\n\t\trec(90, 90, \"1537689\", ver90a, performance.now()-t90a, \"DOM\")\r\n\r\n\t\t// 89: 1703213\r\n\t\tlet t89 = performance.now(), ver89\r\n\t\ttry {\r\n\t\t\tver89 = ((dom.ctrl89.offsetHeight/dom.test89.offsetHeight) > 0.85)\r\n\t\t} catch(e) {\r\n\t\t\tver89 = xB\r\n\t\t}\r\n\t\trec(89, 89, \"1703213\", ver89, performance.now()-t89, \"DOM\")\r\n\r\n\t\t// 88: 1670124\r\n\t\t\t// FF88+: the escapes \\8 and \\9 can't be used...\r\n\t\t\t// FF87-: no error\r\n\t\tlet t88 = performance.now(), ver88\r\n\t\ttry {newFn('\"use strict\" \\n\"\\\\8\"')} catch(e) {ver88 = true}\r\n\t\trec(88, 88, \"1670124\", ver88, performance.now()-t88, \"EVAL\")\r\n\r\n\t\t// 87b: 944846\r\n\t\tlet t87b = performance.now(), ver87b\r\n\t\ttry {ver87b = ((12345).toExponential(3) == 1.235e+4)} catch(e) {}\r\n\t\trec(87.1, 87, \"944846\", ver87b, performance.now()-t87b)\r\n\r\n\t\t// 87a: 1676708\r\n\t\tlet t87a = performance.now(), ver87a\r\n\t\ttry {\r\n\t\t\tlet test87a = new Date(\"Wed Nov 11 2020 19:18:50 GMT+0010 (Central European Standard Time)\").toUTCString()\r\n\t\t\tver87a = (test87a !== \"Wed, 11 Nov 2020 09:18:50 GMT\")\r\n\t\t} catch(e) {}\r\n\t\trec(87, 87, \"1676708\", ver87a, performance.now()-t87a)\r\n\r\n\t\t// 86: 1685482\r\n\t\t\t// 86+: an expression X in 'for (X of Y)' must not start with 'async of' (64)\r\n\t\t\t// 85-: expected '=>' on the same line after an argument list, got '[' (62)\r\n\t\tlet t86 = performance.now(), ver86\r\n\t\ttry {newFn('for (async of [])')} catch(e) {ver86 = (e.message.length == 64)}\r\n\t\trec(86,86, \"1685482\", ver86, performance.now()-t86, \"EVAL\" )\r\n\r\n\t\t//85 possibles? : 1445482 1670062 1635561 \r\n\r\n\t\t// 84: 1673440\r\n\t\t\t// 84+: illegal character U+0040\r\n\t\t\t// 83-: illegal character\r\n\t\tlet t84 = performance.now(), ver84\r\n\t\ttry {newFn(\"var x = @\")} catch(e) {ver84 = (e.message.length == 24)}\r\n\t\trec(84, 84, \"1673440\", ver84, performance.now()-t84, \"EVAL\")\r\n\r\n\t\t// 83: 1667094\r\n\t\tlet t83 = performance.now(), ver83\r\n\t\ttry {\r\n\t\t\tlet obj = {exec() {return function(){}}}; let t = RegExp.prototype.test.call(obj,'')\r\n\t\t\tver83 = true\r\n\t\t} catch(e) {}\r\n\t\trec(83.2, 83, \"1667094\", ver83, performance.now()-t83)\r\n\r\n\t\t// 83a: 1665746: legacy check: toSource (74+): 52 false postive\r\n\t\tlet t83a = performance.now(), ver83a\r\n\t\ttry {\r\n\t\t\tlet a83 = new Set([1,2,3]); let b83 = new Set([2,3,4]); let c83 = new Set(...a83, ...b83);\r\n\t\t} catch(e) {\r\n\t\t\tver83a = (\"undefined\" === typeof Object.toSource && e.message == \"1 is not iterable\")\r\n\t\t\t//debug.push(\" 83 1665746 : \" + e.name +\": \"+ e.message)\r\n\t\t}\r\n\t\trec(83.1, 83, \"1665746\", ver83a, performance.now()-t83a)\r\n\r\n\t\t// 81a: 1657437: legacy check: toSource (74+): 52 false positive\r\n\t\t\t// this is unstable in Tor Browser: change from offscreen: use existing visible spans\r\n\t\tlet t81a = performance.now(), ver81a = xF\r\n\t\ttry {\r\n\t\t\tdom.test81a.innerHTML = \"AB\"\r\n\t\t\tdom.test81b.innerHTML = \"A &#013;B\"\r\n\t\t\tlet w81a = dom.test81a.offsetWidth\r\n\t\t\tlet w81b = dom.test81b.offsetWidth\r\n\t\t\tdom.test81a.textContent = \"\"\r\n\t\t\tdom.test81b.textContent = \"\"\r\n\t\t\t//console.debug(w81a, w81b)\r\n\t\t\tver81a = (\"undefined\" === typeof Object.toSource && w81a < w81b)\r\n\t\t} catch(e) {\r\n\t\t\tver81a = xB\r\n\t\t}\r\n\t\trec(81, 81, \"1657437\", ver81a, performance.now()-t81a, \"DOM\")\r\n\r\n\t\t// 80: 1651732: legacy check: toSource (74+): 52 false positive\r\n\t\tlet t80 = performance.now(), ver80\r\n\t\ttry {\r\n\t\t\tlet obj80 = {[Symbol.toPrimitive]: () => Symbol()}\r\n\t\t\tlet proxy80 = (new Proxy({},{get: (obj80, prop, proxy80) => prop}))\r\n\t\t\ttry {\r\n\t\t\t\tfor (let i = 0; i < 11; i++) {if (typeof proxy80[obj80] == 'symbol') {}}\r\n\t\t\t\tver80 = (\"undefined\" === typeof Object.toSource)\r\n\t\t\t} catch(e) {}\r\n\t\t\trec(80.1, 80, \"1651732\", ver80, performance.now()-t80)\r\n\t\t} catch(e) {\r\n\t\t\trec(80.1,80, \"1651732\", xB, performance.now()-t80)\r\n\t\t}\r\n\r\n\t\t// 79p: 1639246 shipped: javascript.options.weakrefs = true (default)\r\n\t\tlet t79p = performance.now(), ver79p = (\"function\" === typeof WeakRef)\r\n\t\trec(79.4, 79, \"1639246\", ver79p, performance.now()-t79p, \"PREF\")\r\n\r\n\t\t// 79a: 1639591 shipped (FF77 added 1629106)\r\n\t\t\t// 79+: SyntaxError invalid assignment left-hand side (33)\r\n\t\t\t// 78-: SyntaxError expected expression, got '?' (28)\r\n\t\tlet t79a = performance.now(), ver79a\r\n\t\ttry {newFn(\"let z = (3 ??= 3 * 3)\")} catch(e) {ver79a = (e.message.length == 33)}\r\n\t\trec(79.3, 79, \"1639591\", ver79a, performance.now()-t79a, \"EVAL\")\r\n\r\n\t\t// 79b: 1557718\r\n\t\t\t// ext fuckery: cydec\r\n\t\tlet t79b = performance.now(), ver79b\r\n\t\ttry {\r\n\t\t\tver79b = (new Intl.DateTimeFormat(\"en\", {timeStyle: \"short\"}).format(new Date()).includes(':'))\r\n\t\t} catch(e) {ver79b = xB}\r\n\t\trec(79.2, 79, \"1557718\", ver79b, performance.now()-t79b, \"INTL\")\r\n\r\n\t\t// 79: 1413504\r\n\t\tlet t79c = performance.now(), ver79c\r\n\t\ttry {\r\n\t\t\tver79c = (\"1\" !== new Intl.NumberFormat(\"en\", {numberingSystem: \"gong\"}).format(1))\r\n\t\t} catch(e) {ver79c = xB}\r\n\t\trec(79.1, 79, \"1413504\", ver79c, performance.now()-t79c, \"INTL\")\r\n\r\n\t\t// 79: 1644878: legacy check: toSource (74+): 52 false positive\r\n\t\t\t// 79+: entries method called on incompatible boolean\r\n\t\t\t// 78-: std_Map_iterator method called on incompatible boolean (54)\r\n\t\tlet t79 = performance.now(), ver79\r\n\t\ttry {Map.prototype.entries.call(true)} catch(e) {\r\n\t\t\tver79 = (\"undefined\" === typeof Object.toSource && e.message.length == 45)\r\n\t\t}\r\n\t\trec(79, 79, \"1644878\", ver79, performance.now()-t79)\r\n\r\n\t\t// 78a: 1589095\r\n\t\tlet t78a = performance.now(), ver78a\r\n\t\ttry {\r\n\t\t\tlet test78a = new Intl.ListFormat(undefined,{style: 'long', type: 'unit'}).format(['a','b','c'])\r\n\t\t\tver78a = true\r\n\t\t} catch(e) {}\r\n\t\trec(78.2, 78, \"1589095\", ver78a, performance.now()-t78a)\r\n\r\n\t\t// 78b: 1634135\r\n\t\tlet t78b = performance.now(), ver78b\r\n\t\ttry {let regex78b = new RegExp('b'); ver78b = (regex78b.dotAll == false)} catch(e) {ver78b = xB}\r\n\t\trec(78.1, 78, \"1634135\", ver78b, performance.now()-t78b)\r\n\r\n\t\t// 78c: 1633836\r\n\t\tlet t78c = performance.now(), ver78c\r\n\t\ttry {let test78c = new Intl.NumberFormat(undefined, {style:\"unit\", unit:\"percent\"}).format(1/2); ver78c = true} catch(e) {}\r\n\t\trec(78, 78, \"1633836\", ver78c, performance.now()-t78c)\r\n\r\n\t\t// 77: 1627285\r\n\t\tlet t77 = performance.now(), ver77 = (isNaN(new DOMRect(0, 0, NaN, NaN).top))\r\n\t\trec(77.1, 77, \"1627285\", ver77, performance.now()-t77)\r\n\r\n\t\t// 77a: 1608168\r\n\t\tlet t77a = performance.now(), ver77a\r\n\t\ttry {(\"aa\").replaceAll(\"a\",\"b\"); ver77a = true} catch(e) {}\r\n\t\trec(77, 77, \"1608168\", ver77a, performance.now()-t77a)\r\n\r\n\t\t// 75: 1615600\r\n\t\t\t// 75+: 2.5 can't be converted to BigInt because it isn't an integer (60)\r\n\t\t\t// 74-: BigInt is not defined (21)\r\n\t\tlet t75 = performance.now(), ver75\r\n\t\ttry {let test75 = BigInt(2.5)} catch(e) {ver75 = (e.message.length == 60)}\r\n\t\trec(75, 75, \"1615600\", ver75, performance.now()-t75)\r\n\r\n\t\t// 74: 1605835\r\n\t\tlet t74 = performance.now(), ver74\r\n\t\ttry {newFn(\"let t = ({ 1n: 1 })\"); ver74 = true} catch(e) {}\r\n\t\trec(74, 74, \"1605835\", ver74, performance.now()-t74, \"EVAL\")\r\n\r\n\t\t// 73: 1605803\r\n\t\tlet t73 = performance.now(), ver73\r\n\t\ttry {ver73 = (getComputedStyle(dom.test73).content == \"normal\")} catch(e) {}\r\n\t\trec(73, 73, \"1605803\", ver73, performance.now()-t73, \"DOM\")\r\n\r\n\t\t// 72a: 1441976\r\n\t\tlet t72a = performance.now(), ver72a\r\n\t\ttry {if (BatteryManager in window) {}} catch(e) {ver72a = true}\r\n\t\trec(72.2, 72, \"1441976\", ver72a, performance.now()-t72a)\r\n\r\n\t\t// 72b: 1566141\r\n\t\tlet t72b = performance.now(), ver72b\r\n\t\ttry {let test72 = (newFn(\"null ?? 'foo'\")); ver72b = true} catch(e) {}\r\n\t\trec(72.1, 72, \"1566141\", ver72b, performance.now()-t72b, \"EVAL\")\r\n\r\n\t\t// 72c: 1589072\r\n\t\t\t// 72+: underscore can appear only between digits, not after the last digit in a number (79)\r\n\t\t\t// 71-: identifier starts immediately after numeric literal (51)\r\n\t\tlet t72c = performance.now(), ver72c\r\n\t\ttry {newFn('let a = 100_00_;')} catch(e) {ver72c = (e.message.length == 79)}\r\n\t\trec(72, 72, \"1589072\", ver72c, performance.now()-t72c, \"EVAL\")\r\n\r\n\t\t// 71: 1575980 [charAt[17]]\r\n\t\t\t// FF71+: StaticRange requires at least 1 argument, but only 0 were passed\r\n\t\t\t// ^ error_fix: StaticRange constructor: At least 1 argument required, but only 0 passed\r\n\t\t\t// FF70-: Illegal constructor\r\n\t\t\t// FF68-: StaticRange is not defined\r\n\t\tlet t71 = performance.now(), ver71\r\n\t\ttry {\r\n\t\t\tlet test71 = new StaticRange()\r\n\t\t} catch(e) {\r\n\t\t\tver71 = (\"function\" === typeof StaticRange && (e.message).charAt(0) == \"S\")\r\n\t\t\t// ^ legacy check: 68- StaticRange\r\n\t\t}\r\n\t\trec(71, 71, \"1575980\", ver71, performance.now()-t71)\r\n\r\n\t\t// 69: 1558387\r\n\t\tlet t69 = performance.now(), ver69 = (\"undefined\" === typeof window.DOMError)\r\n\t\trec(69, 69, \"1558387\", ver69, performance.now()-t69)\r\n\r\n\t\t// 68: 1550949\r\n\t\tlet t68 = performance.now(), ver68\r\n\t\ttry {ver68 = isNaN(Date.parse(\"T00:00:00Z\"))} catch(e) {ver68 = xB}\r\n\t\trec(68, 68, \"1550949\", ver68, performance.now()-t68, \"DATE\")\r\n\r\n\t\t// 66: 1514664\r\n\t\tlet t66 = performance.now(), ver66 = (\"function\" === typeof TextEncoder.prototype.encodeInto)\r\n\t\trec(66, 66, \"1514664\", ver66, performance.now()-t66)\r\n\r\n\t\t// 65b: 1499026\r\n\t\tlet t65b = performance.now(), ver65b\r\n\t\ttry {ver65b = (\"ia\" == Intl.DateTimeFormat.supportedLocalesOf(\"ia\").join())} catch(e) {ver65b = xB}\r\n\t\trec(65.1, 65, \"1499026\", ver65b, performance.now()-t65b, \"INTL\")\r\n\r\n\t\t// 65a: 1504334\r\n\t\tlet t65a = performance.now(), ver65a = (\"function\" === typeof Intl.RelativeTimeFormat)\r\n\t\trec(65, 65, \"1504334\", ver65a, performance.now()-t65a)\r\n\t\t\r\n\t\t// 61: 1434007\r\n\t\tlet t61 = performance.now(), ver61\r\n\t\ttry {let test61a = (' a').trimStart(); ver61 = true} catch(e) {}\r\n\t\trec(61, 61, \"1434007\", ver61, performance.now()-t61)\r\n\r\n\t\t// 59: 1405993\r\n\t\tlet t59 = performance.now(), ver59\r\n\t\ttry {ver59 = (\"tt\" == Intl.DateTimeFormat.supportedLocalesOf(\"tt\").join())} catch(e) {ver59 = xB}\r\n\t\trec(59, 59, \"1405993\", ver59, performance.now()-t59, \"INTL\")\r\n\r\n\t\t// 55: 1354974: isFF check: this test hangs blink\r\n\t\tif (isFF) {\r\n\t\t\tlet t55 = performance.now(), ver55\r\n\t\t\ttry {\r\n\t\t\t\tlet maxIndex = Math.pow(2, 31)\r\n\t\t\t\tlet list55 = []\r\n\t\t\t\tlist55[maxIndex - 1] = 'a'\r\n\t\t\t\tlist55[maxIndex - 0] = 'b'\r\n\t\t\t\tif (list55[maxIndex] !== 'b') {\r\n\t\t\t\t} else if (list55.slice()[maxIndex] !== 'b') {\r\n\t\t\t\t} else if (list55.slice(maxIndex - 1).length !== 2) {\r\n\t\t\t\t} else {\r\n\t\t\t\t\tver55 = true\r\n\t\t\t\t}\r\n\t\t\t} catch(e) {}\r\n\t\t\trec(55, 55, \"1354974\", ver55, performance.now()-t55)\r\n\t\t} else {\r\n\t\t\trec(55, 55, \"1354974\", xNA, 0)\r\n\t\t}\r\n\r\n\t\t// 54: 1050755\r\n\t\tlet t54 = performance.now(), ver54\r\n\t\ttry {\r\n\t\t\tlet test54 = [Date.UTC(), Date.UTC(1)]\r\n\t\t\tver54 = (isNaN(test54[1]) == false)\r\n\t\t} catch(e) {}\r\n\t\trec(54, 54, \"1050755\", ver54, performance.now()-t54)\r\n\r\n\t\t// 53: 1317307\r\n\t\tlet t53 = performance.now(), ver53\r\n\t\ttry {Object.defineProperty([], \"length\", {get(){}})} catch(e) {if (e.name == \"TypeError\") {ver53 = true}}\r\n\t\trec(53, 53, \"1317307\", ver53, performance.now()-t53)\r\n\r\n\t\t// 52: 837961\r\n\t\tlet t52 = performance.now(), ver52\r\n\t\ttry {let test52 = new Intl.DateTimeFormat(undefined, {timeZone: \"Europe/Warsaw\"}); ver52 = true} catch(e) {}\r\n\t\trec(52, 52, \"837961\", ver52, performance.now()-t52)\r\n\t}\r\n\trun_other()\r\n\r\n\tfunction run_watch() {\r\n\t\t// watch\r\n\t\t// 1274354: META\r\n\r\n\t\t// 1439800\r\n\t\t\t// ext fuckery: cydec\r\n\t\tlet t9 = performance.now(), ver9 = xNC\r\n\t\tlet test9 = new Date(\"11-Nov-11\")\r\n\t\tif (test9.toString() !== \"Invalid Date\") {ver9 = xOMG}\r\n\t\trec(9,\"Invalid Date\", \"1439800\", ver9, performance.now()-t9, \"DATE\")\r\n\r\n\t\t// 1515318: 63 and lower = NaN, 64+ = -2011\r\n\t\tlet t7 = performance.now(), ver7 = xNC\r\n\t\tlet test7 = new Date(\"31-Mar-2011\").getFullYear()\r\n\t\tif (test7 !== -2011 && !isNaN(test7)) {ver7 = xOMG + (isFF ? \" [\" + test7 +\"]\" : \"\")}\r\n\t\trec(7,\"-2011\", \"1515318\", ver7, performance.now()-t7)\r\n\r\n\t\t// 1599375\r\n\t\tlet t6 = performance.now(), ver6 = xNC\r\n\t\tlet test6 = Date.parse(\"2019-11-26 07:39:58.286157072 +0000 UTC\")\r\n\t\tif (!isNaN(test6)) {ver6 = xOMG}\r\n\t\trec(6,\"NaN\", \"1599375\", ver6, performance.now()-t6, \"DATE\")\r\n\r\n\t\t// 1742592 related\r\n\t\tlet t4 = performance.now(), ver4 = xNC\r\n\t\ttry {\r\n\t\t\tlet lf4 = new Intl.ListFormat(\"en\")\r\n\t\t\tlet test4 = \"~\"+ lf4.format([\"\", \"B\"]) +\"~\"\r\n\t\t\tif (test4 !== \"~ and B~\") {ver4 = xOMG}\r\n\t\t} catch(e) {\r\n\t\t\tver4 = \" \"+ zNA + \" error \"\r\n\t\t}\r\n\t\trec(4,\"\\\" and B\\\"\", \"1742592\", ver4, performance.now()-t4, \"DATE\")\r\n\t}\r\n\r\n}\r\n\r\nfunction run() {\r\n\ttry {let warmup = Intl.supportedValuesOf(\"timeZone\").includes(\"America/Coyhaique\")} catch(e) {}\r\n\tsetTimeout(function() {\r\n\t\tPromise.all([\r\n\t\t\tget_globals()\r\n\t\t]).then(function(){\r\n\t\t\toutputVersion()\r\n\t\t})\r\n\t}, 50)\t\r\n}\r\nrun()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tests/windownamea.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n\t<meta name=\"viewport\" content=\"width=400\">\r\n\t<title>window.name [a]</title>\r\n\t<!-- custom -->\r\n\t<style>\r\n\t\t:root{\r\n\t\t\t--test0: #b3b3b3;\r\n\t\t\t--test7: #9ddc9d;\r\n\t\t\t--test12: #9db2dc;\r\n\t\t\t--test99: #808080;\r\n\t\t\t--bg99: #808080;\r\n\t\t}\r\n\t\tbody {background-color: #161b22; color: var(--test0);}\r\n\t\th2 {color: white; font-size: 14px; text-align: center; margin-top: inherit;}\r\n\t\ta.blue {color: var(--test12); text-decoration: none;}\r\n\t\ta.return {color: var(--test12); text-decoration: none;  font-size: 14px; line-height: 1.2em}\r\n\t\t.no_color {color: #b3b3b3;}\r\n\t\t.mono {font-family: monospace, \"Courier New\";}\r\n\t\t.good {color: var(--test7);}\r\n\t\t.bad {color: #ff6363;}\r\n\r\n\t\tdiv.nav-title {position: relative;}\r\n\t\ttable {\r\n\t\t\twidth: 97%; min-width: 380px; max-width: 480px;\r\n\t\t\tborder-collapse: collapse; margin: 0 auto 10px auto; font-size: 12px;\r\n\t\t}\r\n\t\ttbody:before {content: \"-\"; display: block; line-height: 1em; color: transparent;}\r\n\t\ttd {padding-bottom: 3px; padding-top: 3px; padding-left: 10px;}\r\n\t\tth {color: black; font-weight: bold; font-size: 16px; padding: 3px 0;}\r\n\t\ttable td:first-child { text-align: right; vertical-align: top;}\r\n\t\ttable td.blurb {text-align: center; line-height: 1.5em;}\r\n\t\ttable td.center {text-align: center;}\r\n\t\ttable td.intro {text-align: left; line-height: 1.5em; padding-bottom: 10px;}\r\n\t\t#tb99 th {background-color: var(--bg99);}\r\n\t\t#tb99 td:first-child {color: var(--test99);}\r\n\r\n\t\t.btn {background-color: #161b22;\r\n\t\t\tdisplay: inline-block;\r\n\t\t\tfont-size: 12px;\r\n\t\t\tfont-family: monospace, \"Courier New\";\r\n\t\t\tfont-weight: bold;\r\n\t\t\tpadding-left: 6px;\r\n\t\t\tpadding-right: 6px;\r\n\t\t\tcursor: pointer;\r\n\t\t}\r\n\t\t.btn0 {border-color: var(--test0); color: var(--test0);}\r\n\t\t.btn-left {float: left; position: relative; left: -10px; top: 0px;}\r\n\t</style>\r\n</head>\r\n<body>\r\n\r\n\t<table>\r\n\t<tr><td><h2>TorZillaPrint</h2></td></tr>\r\n\t<tr><td class=\"blurb\"><a class=\"return\" href=\"../index.html#other\">return to TZP index</a></td></tr>\r\n\t</table>\r\n\r\n\t<table id=\"tb99\">\r\n\t\t<col width=\"40%\"><col width=\"60%\">\r\n\t\t<thead><tr><th colspan=\"2\"><div class=\"nav-title\">window.name [a]</div>\r\n\t\t</th></tr></thead>\r\n\t\t<tr>\r\n\t\t\t<td><div class=\"btn-left\"><span class=\"btn0 btn\" onClick=\"run()\">[ re-run ]</span></div>current</td>\r\n\t\t\t<td class=\"mono\" id=\"current\"></td></tr>\r\n\t\t<tr><td>setting new</td><td class=\"mono\" id=\"setnew\"></td></tr>\r\n\t\t<tr><td>check new</td><td class=\"mono\" id=\"check\"></td></tr>\r\n\t\t<tr><td>next</td>\r\n\t\t\t<td><span class=\"no_color mono\">load\r\n\t\t\t<a class=\"blue\" href=\"https://thorin-oakenpants.github.io/testing/windownameb.html\">window.name [b]</a></span>\r\n\t\t\t</td></tr>\r\n\t</table>\r\n\t<br>\r\n\r\n<script>\r\n'use strict';\r\n\r\nvar s0 = \" <span class='\",\r\n\tsb = s0 +\"bad'>\",\r\n\tsg = s0 +\"good'>\",\r\n\tsc = \"</span>\"\r\n\r\nfunction rnd_string(prefix) {\r\n\treturn (prefix == undefined ? \"\" : prefix) + Math.random().toString(36).substring(2, 15)\r\n}\r\n\r\nfunction run() {\r\n\tlet str = \"\",\r\n\t\tcurrent = document.getElementById(\"current\"),\r\n\t\tsetnew = document.getElementById(\"setnew\"),\r\n\t\tcheck = document.getElementById(\"check\")\r\n\t// clear\r\n\tcurrent.innerHTML = \"&nbsp\"\r\n\tsetnew.innerHTML = \"&nbsp\"\r\n\tcheck.innerHTML = \"&nbsp\"\r\n\ttry {\r\n\t\tstr = window.name\r\n\t\tif (str == undefined) {str = \"undefined\"}\r\n\t\telse if (str == \"undefined\") {str = \"\\\"undefined\\\"\"}\r\n\t\telse if (str == \"\") {str = \"nothing found\"}\r\n\t\tcurrent.innerHTML = str\r\n\t} catch(e) {\r\n\t\tcurrent.innerHTML = e.name\r\n\t}\r\n\t// create a random string\r\n\tstr = rnd_string()\r\n\tvar control = str\r\n\ttry {\r\n\t\twindow.name = str\r\n\t} catch(e) {\r\n\t\tstr = e.name\r\n\t}\r\n\tsetnew.innerHTML = str\r\n\t// read it back: set a little timer\r\n\tsetTimeout(function() {\r\n\t\tlet chk = \"\"\r\n\t\ttry {\r\n\t\t\tchk = window.name\r\n\t\t\tif (chk == undefined) {chk = \"undefined\"}\r\n\t\t\telse if (chk == \"undefined\") {chk = \"\\\"undefined\\\"\"}\r\n\t\t\telse if (chk == \"\") {chk = \"nothing found\"}\r\n\t\t} catch(e) {\r\n\t\t\tchk = e.name\r\n\t\t}\r\n\t\tcheck.innerHTML = chk + (chk == control ?  sg +\"[success]\": sb +\"[failure]\") + sc\r\n\t}, 150)\t\r\n}\r\n\r\nrun()\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "tzp.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"viewport\" content=\"max-width=800, width=500\">\n\t<title>TZP</title>\n\t<link rel=\"preload\" href=\"xml/xmlunstyled.xml\" as=\"script\">\n\t<link rel=\"preload\" href=\"xml/xslterror.xml\" as=\"script\">\n\t<link rel=\"preload\" href=\"images/InvalidImage.png\" as=\"image\">\n\t<link rel=\"preload\" href=\"images/ScaledImage.png\" as=\"image\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"css/index.css\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"css/media.css\">\n\t<link rel=\"stylesheet\" href=\"chrome://global/locale/intl.css\"> <!-- required for the parsererror dir leak -->\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"css/window_size.css\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"css/screen_size.css\">\n\t<link rel=\"icon\" type=\"image/png\" href=\"chrome://branding/content/icon32.png\">\n\t<script src=\"js/globals.js\"></script>\n\t<script src=\"js/generic.js\"></script>\n\t<!--<style></style>\n\t\timportant to not have any styleSheet here as when we calculate expected stylesheets\n\t\tfor later comparison, these haven't yet been enumerated into document.styleSheets\n\t-->\n</head>\n\n<body>\n\t<div id=\"tzpRect\"></div>\n\t<div id=\"tzpLV\"></div>\n\t<div id=\"tzpSV\"></div>\n\t<!-- calc must be outside our tzpBody -->\n  <div id=\"tzpCalc\" class=\"offscreen\"><div id=\"tzpCalcTarget\"></div><div class=\"tzpCalcContainer\"></div></div>\n\n\t<!-- keep tzpBody first: contains scrollbar but MUST come after tzpLV and tzpSV\n\t\tin order to be able to be able to select and drag non-overlays -->\n\t<div id=\"tzpBody\">\n\t<span translate=\"no\">\n\n\t<!--offscreen-->\n\t<div class=\"offscreen\">\n\t\t<div id=\"tzpFS\" width=0 height=0></div>\n\t\t<iframe id=\"tzpInvalidImage\" width=\"100\" height=\"30\"></iframe>\n\t\t<iframe id=\"tzpScaledImage\" width=\"100\" height=\"30\"></iframe>\n\t\t<iframe id=\"tzpXMLunstyled\" width=\"20\" height=\"30\"></iframe>\n\t\t<iframe id=\"tzpXSLT\" width=\"40\" height=\"30\"></iframe>\n\t\t<iframe id=\"tzpIframe\" width=\"0\" height=\"0\"></iframe>\n\t\t<audio id=\"tzpAudio\"></audio><video id=\"tzpVideo\" width=\"20px\"></video>\n\t\t<div id=\"tzpDiv\"></div>\n\t\t<div id=\"tzpDocFont\" style=\"font-family: 'test font name'\"></div>\n\t\t<div id=\"tzpSVG\"></div>\n\t\t<div id=\"tzpScroll\"><div></div></div>\n\t\t<span id=\"tzpScript\"></span>\n\t\t<svg id=\"tzpSwitch\" width=\"0px\" height=\"0px\" viewBox=\"0 0 0 0\"></svg>\n\t\t<div id='tzpGraphite' style=\"font-family: 'graphite';\"><span>+</span><span>-</span></div>\n\t\t<div id='tzpDPI' style='height: 1in; width: 1in;'></div>\n\t\t<img id=\"tzpBrand\" src=\"chrome://branding/content/about-wordmark.svg\">\n\t\t<img id=\"tzpAbout\" src=\"about:logo\">\n\t\t<!--for async fallback-->\n\t\t<div>\n\t\t\t<span> &#x007F; </span><span> &#x0218; </span><span> &#x058F; </span><span> &#x05C6; </span>\n\t\t\t<span> &#x061C; </span><span> &#x0700; </span><span> &#x08E4; </span><span> &#x097F; </span>\n\t\t\t<span> &#x09B3; </span><span> &#x0B82; </span><span> &#x0D02; </span><span> &#x10A0; </span>\n\t\t\t<span> &#x115A; </span><span> &#x17DD; </span><span> &#x1950; </span><span> &#x1C50; </span>\n\t\t\t<span> &#x1CDA; </span><span> &#x1D790; </span><span> &#x1E9E; </span><span> &#x20B0; </span>\n\t\t\t<span> &#x20B8; </span><span> &#x20B9; </span><span> &#x20BA; </span><span> &#x20BD; </span>\n\t\t\t<span> &#x20E3; </span><span> &#x21E4; </span><span> &#x23AE; </span><span> &#x2425; </span>\n\t\t\t<span> &#x2581; </span><span> &#x2619; </span><span> &#x2B06; </span><span> &#x2C7B; </span>\n\t\t\t<span> &#x302E; </span><span> &#x3095; </span><span> &#x532D; </span><span> &#x6E2F; </span>\n\t\t\t<span> &#xA73D; </span><span> &#xA830; </span><span> &#xF003; </span><span> &#xF810; </span>\n\t\t\t<span> &#xFBEE; </span><span> &#xFFF9; </span><span> &#xFFFD; </span><span> &#xFFFF; </span>\n\t\t</div>\n\t\t<div id=\"tzpFontMax\" class=\"normalized\"></div>\n\t\t<div id='tzpDirection'></div>\n\t</div>\n\t<!--hidden-->\n\t<div class=\"hidden\">\n\t\t<div><img id=\"tzpBrandHidden\" src=\"chrome://branding/content/about-wordmark.svg\"></div>\n\t\t<!--<div><img id=\"tzpBrandHidden\" src=\"images/about-wordmark.svg\"></div>-->\n\t\t<canvas id=\"tzpCanvasGet\"></canvas>\n\t\t<canvas id=\"tzpCanvasGetSolid\"></canvas>\n\t\t<canvas id=\"tzpCanvasPath\"></canvas>\n\t\t<canvas id=\"tzpCanvasTo\"></canvas>\n\t\t<canvas id=\"tzpCanvasToSolid\"></canvas>\n\t\t<div class=\"normalized\"><canvas id=\"tzpTextmetrics\"></canvas></div>\n\t\t<span id=\"tzpColor\"></span>\n\t\t<div id=\"tzpDPR\" style=\"border: 0.1px solid;\">foo</div>\n\t\t<div id=\"tzpWidget\" class=\"normalized\">\n\t\t\t<input type=\"button\" id=\"tzpbutton\">\n\t\t\t<input type=\"checkbox\">\n\t\t\t<input type=\"color\">\n\t\t\t<input type=\"date\">\n\t\t\t<input type=\"datetime-local\">\n\t\t\t<input type=\"email\">\n\t\t\t<input type=\"file\">\n\t\t\t<input type=\"hidden\">\n\t\t\t<input type=\"image\">\n\t\t\t<input type=\"month\" id=\"tzpmonth\">\n\t\t\t<input type=\"number\">\n\t\t\t<input type=\"password\">\n\t\t\t<input type=\"radio\">\n\t\t\t<input type=\"range\">\n\t\t\t<input type=\"reset\">\n\t\t\t<input type=\"search\" id=\"tzpsearch\">\n\t\t\t<select id=\"tzpselect\"></select>\n\t\t\t<input type=\"submit\">\n\t\t\t<input type=\"tel\">\n\t\t\t<input type=\"text\">\n\t\t\t<textarea id=\"tzptextarea\"></textarea>\n\t\t\t<input type=\"time\">\n\t\t\t<input type=\"url\">\n\t\t\t<input type=\"week\" id=\"tzpweek\">\n\t\t</div>\n\t\t<div>\n\t\t\t<a id=\"tzpClrlink\" href=\"fake\" class=\"\">unvisited</a>\n\t\t\t<a id=\"tzpClrvisited-link\" href=\"\" class=\"\">visited</a>\n\t\t</div>\n\t</div>\n\n\t<table>\n\t\t<tr><td><a name=\"top\"></a><h2>TorZillaPrint</h2></td></tr>\n\t\t<tr><td class=\"blurb\" id=\"index\"><a id=\"tzpLink\" class=\"return\" href=\"index.html\">return to TZP index</a></td></tr>\n\t</table>\n\n\t<span id=\"tzpContent\">\n\t<!--FPs-->\n\t<table id=\"tbfp\">\n\t\t<col width=\"27%\"><col width=\"73%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"finger\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#finger\">loose <span class=\"perf\"><sup>1</sup></span> fingerprints</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#top\">&#9650;</a> <span class=\"c perf\" id=\"perfAll\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#screend\">&#9660;</a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td>\n\t\t\t<div class=\"btn-left\"><span class=\"btn btn0\" onClick=\"outputSection(`all`)\">[ re-run ]</span></div>\n\t\t\tprototype | proxy <sup>2</sup></td>\n\t\t\t<td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"protohash\"></span>\n\t\t\t\t<div class=\"btn-right c\" id=\"protohealth\"></div>\n\t\t\t</td></tr>\n\t\t<tr><td>document</td><td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"documenthash\"></span>\n\t\t\t\t<span class=\"c\" id=\"documentbtns\"></span>\n\t\t\t\t<div class=\"btn-right c\" id=\"documenthealth\"></div>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\"><span class=\"no_color\">fingerprints are always \n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/arkenfox/TZP/#-fingerprints-are-always-loose\">loose</a>\n\t\t\t<sup>1</sup>, prototype/proxy lies by\n\t\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a>\n\t\t\t\t<sup>2</sup>, json format by\n\t\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/lydell/json-stringify-pretty-compact\">Simon Lydell</a>\n\t\t\t</span>\n\t\t</span></td></tr>\n\t</table>\n\n\t<!--screen-->\n\t<table id=\"tb1\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"screen\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#screen\">screen</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#finger\">&#9650;</a> <span class=\"c perf\" id=\"perfscreen\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#uad\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"screend\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn1 btn\" onClick=\"outputSection(1)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"screenhash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<!--position-->\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">availLeft<br>availTop<br>left<br>top</span></div> &nbsp; screen</td>\n\t\t\t<td class=\"mono\"><span class=\"c\" id=\"position_screen\"></span> <!-- used to calculate overlayCharLen -->\n\t\t\t<div class=\"btn-right s99\">positions</div></td></tr>\n\t\t<tr><td>\n\t\t\t<div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t\t<span class=\"ttxt\">mozInnerScreenX<br>mozInnerScreenY<br>screenX<br>screenY</span></div>\n\t\t\t\t&nbsp; window</td><td class=\"mono border-bottom\"><span class=\"c\" id=\"position_window\"></span>\n\t\t\t</td></tr>\n\t\t<!--device orientation-->\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">type<br>angle<br>orientation + aspect-ratio</span></div>\n\t\t\t\t&nbsp; [device] orientation</td>\n\t\t\t<td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"orientation_device_summary\"></span>\n\t\t\t\t<span class=\"c\" id=\"orientation_device\"></span>\n\t\t\t\t<div class=\"btn-right s99\"><span id=\"labelS\" class=\"btn btnright\" onClick=\"togglerows('S','btn')\">[+]</span></div>\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr class=\"togS\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">mozOrientation<br>orientation.angle<br>orientation.type</span></div>\n\t\t\t&nbsp; device</td>\n\t\t\t<td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"orientation_mozOrientation\"></span>\n\t\t\t\t| <span class=\"c\" id=\"orientation_orientation.angle\"></span>\n\t\t\t\t| <span class=\"c\" id=\"orientation_orientation.type\"></span>\n\t\t\t<div class=\"btn-right s99\">orientation</div>\n\t\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>[iframe] device</td>\n\t\t\t<td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"orientation_mozOrientation_iframe\"></span>\n\t\t\t\t| <span class=\"c\" id=\"orientation_orientation.angle_iframe\"></span>\n\t\t\t\t| <span class=\"c\" id=\"orientation_orientation.type_iframe\"></span>\n\t\t\t</td></tr>\n\t\t<tr class=\"togS\">\n\t\t\t<td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxtb\">-moz-device-orientation<br>\n\t\t\tdevice-aspect-ratio</span></div> &nbsp; [css] device</td>\n\t\t\t<td class=\"mono\"><span id=\"cssOm\"></span> | <span id=\"cssDAR\"></span></td>\n\t\t</tr>\n\t\t<tr class=\"togS\">\n\t\t\t<td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxtb\">-moz-device-orientation<br>\n\t\t\tdevice-aspect-ratio</span></div> &nbsp; [matchMedia] device</td>\n\t\t\t<td class=\"mono border-bottom\">\n\t\t\t\t<span class=\"c\" id=\"orientation_-moz-device-orientation\"></span> | <span class=\"c\" id=\"orientation_device-aspect-ratio\"></span>\n\t\t\t</td>\n\t\t</tr>\n\t\t<!--screen-->\n\t\t<tr><td>screen</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"screen_summary\"></span>\n\t\t\t<span class=\"c\" id=\"sizes_screen\"></span>\n\t\t\t<span class=\"c\" id=\"screen_aspect_ratio\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>\n\t\t\t<div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt cssrange\">range 400-2560</span></div>\n\t\t\t&nbsp; [min-device-] css <sup>1</sup></td>\n\t\t\t<td class=\"mono\"><span id=\"S\"></span><div class=\"btn-right s99\">screen</div></td>\n\t\t</tr>\n\t\t<tr class=\"togS\"><td>iframe</td><td class=\"c mono\" id=\"screen_iframe\"></td></tr>\n\t\t<tr class=\"togS\"><td>matchMedia <sup>2</sup></td><td class=\"c mono\" id=\"screen_media\"></td></tr>\n\t\t<tr class=\"togS\"><td>screen</td><td class=\"mono border-bottom\"><span class=\"c\" id=\"screen_screen\"></span></td></tr>\n\t\t<!--available-->\n\t\t<tr><td>available</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"available_summary\"></span>\n\t\t\t<span class=\"c\" id=\"sizes_available\"></span>\n\t\t\t<span class=\"c\" id=\"size_dock\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>iframe</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"available_iframe\"></span>\n\t\t\t<div class=\"btn-right s99\">available</div>\n\t\t\t<span class=\"c\" id=\"screen_sizes\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>screen</td><td class=\"mono border-bottom\">\n\t\t\t<span class=\"c\" id=\"available_screen\"></span>\n\t\t</td></tr>\n\t\t<!--outer-->\n\t\t<tr class=\"togA\"><td>initial outer</td><td class=\"mono\" id=\"initial_outer\"></td></tr>\n\t\t<tr><td>outer</td><td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"outer_summary\"></span>\n\t\t\t\t<span class=\"c\" id=\"sizes_outer\"></span>\n\t\t\t\t<span class=\"c\" id=\"size_chrome\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>iframe</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"outer_iframe\"></span>\n\t\t\t<div class=\"btn-right s99\">outer</div>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td>window</td><td class=\"mono border-bottom\">\n\t\t\t<span class=\"c\" id=\"outer_window\"></span>\n\t\t\t<span class=\"c\" id=\"window_sizes\"></span>\n\t\t</td></tr>\n\t\t<!--window orientation-->\n\t\t<tr class=\"togS\">\n\t\t\t<td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt\">aspect-ratio<br>orientation</span></div> &nbsp; [css] window</td>\n\t\t\t<td class=\"mono\"><span id=\"cssAR\"></span> | <span id=\"cssO\"></span><div class=\"btn-right s99\">orientation</div>\n\t\t</td></tr>\n\t\t<tr class=\"togS\">\n\t\t\t<td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt\">aspect-ratio<br>orientation</span>\n\t\t\t\t</div> &nbsp; [matchMedia] window</td>\n\t\t\t<td class=\"mono border-bottom\">\n\t\t\t\t<span class=\"c\" id=\"orientation_aspect-ratio\"></span> | <span class=\"c\" id=\"orientation_orientation\"></span>\n\t\t</td></tr>\n\t\t<!--inner-->\n\t\t<tr class=\"togA\"><td>initial inner</td><td class=\"c mono\" id=\"initial_inner\"></td></tr>\n\t\t<tr><td>inner</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"inner_summary\"></span>\n\t\t\t<span class=\"c\" id=\"sizes_inner\"></span>\n\t\t\t<span class=\"c\" id=\"size_newwin\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"togS\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt cssrange\">range 400-2560</span></div>\n\t\t\t&nbsp; [min-] css <sup>1, 3</sup></td>\n\t\t\t<td class=\"mono\"><span id=\"D\"></span>\n\t\t\t<div class=\"btn-right s99\">inner</div></td></tr>\n\t\t<tr id=\"A1\" class=\"hidden\"><td>document</td><td class=\"c mono\" id=\"inner_document\"></td></tr>\n\t\t<tr class=\"togS\"><td>matchMedia <sup>2</sup></td><td class=\"c mono\" id=\"inner_media\"></td></tr>\n\t\t<tr class=\"togS\"><td>[small viewport] sv*</td><td class=\"c mono\" id=\"inner_viewport\"></td></tr>\n\t\t<tr class=\"A2 togS\"><td>window</td>\n\t\t\t<td class=\"mono border-bottom\">\n\t\t\t<span class=\"c\" id=\"inner_window\"></span><span class=\"c\" id=\"dynamic_note\"></span>\n\t\t\t</td></tr>\n\t\t<!--viewport-->\n\t\t<tr class=\"togA\"><td>[large viewport] lv*</td><td class=\"mono border-top\">\n\t\t\t<span id=\"viewport_large\"></span>\n\t\t\t<div class=\"btn-right s99\">viewport</div>\n\t\t</td></tr>\n\t\t<tr class=\"togA\"><td>dynamic toolbar</td><td class=\"c mono\" id=\"dynamic_toolbar\"></td></tr>\n\t\t<tr class=\"A2\"><td>viewport</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"viewport_summary\"></span>\n\t\t\t<span class=\"c\" id=\"sizes_viewport\"></span>\n\t\t</td></tr>\n\t\t<tr class=\"A2 togS\"><td>document</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"viewport_document\"></span>\n\t\t\t<div class=\"btn-right s99\">viewport</div>\n\t\t</td></tr>\n\t\t<tr class=\"A2 togS\"><td>element <sup>4</sup></td><td class=\"c mono\" id=\"viewport_element\"></td></tr>\n\t\t<tr class=\"A2 togS\"><td>visualViewport</td><td class=\"c mono\" id=\"viewport_visualViewport\"></td></tr>\n\t\t<!--other-->\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">[css] display-mode<br>display-mode<br>fullScreen<br>mozFullScreenEnabled</span></div>\n\t\t\t&nbsp; display-mode | fullscreen</td>\n\t\t\t<td class=\"mono border-top\"><span id=\"cssDM\"></span> | <span class=\"c\" id=\"display-mode\"></span>\n\t\t\t\t| <span class=\"c\" id=\"windowfullScreen\"></span> | <span class=\"c\" id=\"mozFullScreenEnabled\"></span></td></tr>\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('fullscreenElement')\">[ run ]</span> fullscreenElement <sup>4</sup></td>\n\t\t\t<td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"fullscreenElement\"></span>\n\t\t\t\t<div id=\"btnFS\" class=\"btn-right btn1\" onClick=\"exitUserFS()\">[ exit ]</div>\n\t\t\t</td></tr>\n\t\t<tr><td><span class=\"mono no_color\">[F11]</span> &nbsp; fullscreen</td><td class=\"c mono\" id=\"fsSize\"></td></tr>\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('newwin')\">[ run ]</span>\n\t\t\t<div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">attempts to open a new blank<br>window as big as possible<br>and grab the dimensions</span></div>\n\t\t\t&nbsp; new window</td><td class=\"mono border-bottom\"><span class=\"c\" id=\"newwin\"></span></td></tr>\n\t\t<!-- pixels-->\n\t\t<tr><td>[div] dpi</td><td class=\"mono\"><span class=\"c\" id=\"dpi_div\"></span>\n\t\t\t<div class=\"btn-right s99\">pixels</div></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt\">range 40-400</span></div>\n\t\t\t&nbsp; [css min-resolution] dpi</td>\n\t\t\t<td class=\"mono\"><span id=\"P\"></span><span class=\"cssc\" id=\"pixels_dpi_css\"></span>\n\t\t\t\t<div class=\"btn-right c\" id=\"pixels_match\"></div>\n\t\t\t</td></tr>\n\t\t<tr><td>[matchMedia] dpi</td><td class=\"mono\"><span class=\"c\" id=\"pixels_dpi\"></span></td></tr>\n\t\t<tr><td>[matchMedia] dppx | dpcm</td><td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"pixels_dppx\"></span> | <span class=\"c\" id=\"pixels_dpcm\"></span>\n\t\t\t</td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">-moz-device-pixel-ratio<br>-webkit-device-pixel-ratio<br></span></div>\n\t\t\t\t&nbsp; *device-pixel-ratio</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"pixels_-moz-device-pixel-ratio\"></span>\n\t\t\t\t| <span class=\"c\" id=\"pixels_-webkit-device-pixel-ratio\"></span>\n\t\t\t</td></tr>\n\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxtb\">window<br>iframe</span></div>\n\t\t\t\t&nbsp; devicePixelRatio</td><td class=\"mono\">\n\t\t\t\t<span class=\"c\" id=\"pixels_devicePixelRatio\"></span> | <span class=\"c\" id=\"pixels_devicePixelRatio_iframe\"></span>\n\t\t\t</td></tr>\n\t\t<tr><td>[border] devicePixelRatio</td><td class=\"mono\"><span class=\"c\" id=\"devicePixelRatio_border\"></span></td></tr>\n\t\t<tr class=\"A2\"><td>visualViewport scale</td><td class=\"c mono\" id=\"visualViewport_scale\"></td></tr>\n\n\t\t<tr><td colspan=\"2\"></td></tr> <!--space-->\n\t\t<tr><td colspan=\"2\"><span class=\"normal\"><span class=\"no_color\">code based on work by </span><a target=\"_blank\" class=\"blue\"\n\t\t\thref=\"https://arthuredelstein.github.io/tordemos/media-query-fingerprint.html\">Arthur Edelstein</a> <sup>1</sup>\n\t\t\t<span class=\"no_color\">, </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://canvasblocker.kkapsner.de/test\">kkapsner</a><span class=\"no_color\"> & </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/kkapsner/CanvasBlocker\">CanvasBlocker</a> <sup>2</sup>\n\t\t\t<span class=\"no_color\">, </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://blog.pastly.net/posts/2016-09-04-how-css-alone-can-help-track-you/\">Matt Traudt</a> <sup>3</sup>\n\t\t\t<span class=\"no_color\"> and </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/earthlng/testpages\">earthlng</a> <sup>4</sup>\n\t\t</span></td></tr>\n\t</table>\n\n\t<!--ua-->\n\t<table id=\"tb2\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"ua\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#ua\">agent</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#screen\">&#9650;</a> <span class=\"c perf\" id=\"perfagent\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#featured\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"uad\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn2 btn\" onClick=\"outputSection(2)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"agenthash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<!--agent*-->\n\t\t<tr><td><span id=\"labelUA\" class=\"btn btn0\" onClick=\"togglerows('UA','btn')\">[+]</span>\n\t\t\tuserAgent</td><td class=\"c mono\" id=\"useragent\"></td></tr>\n\t\t<tr class=\"togUA\"><td>appCodeName</td><td class=\"c mono spaces\" id=\"useragent_appCodeName\"></td></tr>\n\t\t<tr class=\"togUA\"><td>appName</td><td class=\"c mono spaces\" id=\"useragent_appName\"></td></tr>\n\t\t<tr class=\"togUA\"><td>appVersion</td><td class=\"c mono spaces\" id=\"useragent_appVersion\"></td></tr>\n\t\t<tr class=\"togUA\"><td>buildID</td><td class=\"c mono spaces\" id=\"useragent_buildID\"></td></tr>\n\t\t<tr class=\"togUA\"><td>oscpu</td><td class=\"c mono spaces\" id=\"useragent_oscpu\"></td></tr>\n\t\t<tr class=\"togUA\"><td>platform</td><td class=\"c mono spaces\" id=\"useragent_platform\"></td></tr>\n\t\t<tr class=\"togUA\"><td>product</td><td class=\"c mono spaces\" id=\"useragent_product\"></td></tr>\n\t\t<tr class=\"togUA\"><td>productSub</td><td class=\"c mono spaces\" id=\"useragent_productSub\"></td></tr>\n\t\t<tr class=\"togUA\"><td>userAgent</td><td class=\"c mono spaces\" id=\"useragent_userAgent\"></td></tr>\n\t\t<tr class=\"togUA\"><td>vendor</td><td class=\"c mono spaces\" id=\"useragent_vendor\"></td></tr>\n\t\t<tr class=\"togUA\"><td>vendorSub</td><td class=\"c mono spaces\" id=\"useragent_vendorSub\"></td></tr>\n\t\t<tr><td>\n\t\t\t<!--<span id=\"labelUAD\" class=\"btn btn0\" onClick=\"togglerows('UAD','btn')\">[+]</span>-->\n\t\t\tuserAgentData</td><td class=\"c mono\" id=\"useragentdata\"></td></tr>\n\t\t<!--<tr class=\"togUAD\"><td>coming soon</td><td class=\"c mono\" id=\"uad_item1\"></td></tr>-->\n\n\t\t<tr><td colspan=\"2\" class=\"center\">------</td></tr>\n\t\t<tr><td><span id=\"labelAI\" class=\"btn btn0\" onClick=\"togglerows('AI','btn')\">[+]</span>\n\t\t\tiframes <sup>1</sup></td><td class=\"c mono\" id=\"uaIframes\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>[contentWindow] document root</td><td class=\"c mono\" id=\"agent_content_docroot\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>[contentWindow] with URL</td><td class=\"c mono\" id=\"agent_content_with_url\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>[window] document root</td><td class=\"c mono\" id=\"agent_window_docroot\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>[window] with URL</td><td class=\"c mono\" id=\"agent_window_with_url\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>iframe access</td><td class=\"c mono\" id=\"agent_iframe_access\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>nested</td><td class=\"c mono\" id=\"agent_nested\"></td></tr>\n\t\t\t<tr class=\"togAI\"><td>window access</td><td class=\"c mono\" id=\"agent_window_access\"></td></tr>\n\t\t<tr><td><span id=\"labelAW\" class=\"btn btn0\" onClick=\"togglerows('AW','btn')\">[+]</span>\n\t\t\tworkers</td><td class=\"mono s99\" id=\"uaWorkers\">summary not coded</td></tr>\n\t\t\t<tr class=\"togAW\"><td>worker</td><td class=\"c mono\" id=\"agent_worker\"></td></tr>\n\t\t\t<tr class=\"togAW\"><td>shared worker</td><td class=\"c mono\" id=\"agent_worker_shared\"></td></tr>\n\t\t\t<tr class=\"togAW\"><td>service worker</td><td class=\"c mono\" id=\"agent_worker_service\"></td></tr>\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('agent_open')\">[ run ]</span>\n\t\t\twindow.open</td><td class=\"c mono\" id=\"agent_open\"></td></tr>\n\t\t<tr><td colspan=\"3\"><span class=\"normal\"><span class=\"no_color\">iframe code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://canvasblocker.kkapsner.de/test\">kkapsner</a><span class=\"no_color\"> & </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a> <sup>1</sup>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--fd-->\n\t<table id=\"tb3\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"feature\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#feature\">feature detection</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#ua\">&#9650;</a> <span class=\"c perf\" id=\"perffeature\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#regiond\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"featured\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"normal\">\n\t\t\t<span class=\"no_color\">These tests are to show that you cannot hide your engine\n\t\t\t<a class=\"blue\" target=\"blank\" href=\"tests/engineprop.html\">[PoC1]</a>\n\t\t\t+ <a class=\"blue\" target=\"blank\" href=\"tests/engine.html\">[PoC2]</a>, version\n\t\t\t<a class=\"blue\" target=\"blank\" href=\"tests/versions.html\">[PoC]</a> or OS\n\t\t\t<a class=\"blue\" target=\"blank\" href=\"tests/os.html\">[PoC]</a>.</span>\n\t\t</span></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn3 btn\" onClick=\"outputSection(3)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"featurehash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>[css] branding</td><td class=\"mono\" id=\"tzpWordmark\"></td></tr>\n\t\t<tr><td>[css] browser</td><td class=\"mono\" id=\"tzpResource\"></td></tr>\n\t\t<tr><td>[browser] architecture</td><td class=\"c mono\" id=\"browser_architecture\"></td></tr>\n\t\t<tr><td>[&infin;] architecture</td><td class=\"c mono\" id=\"infinity_architecture\"></td></tr>\n\t\t<tr><td>browser</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"browser\"></span> | <span class=\"c\" id=\"logo\"></span> | <span class=\"c\" id=\"wordmark\"></span>\n\t\t\t</td></tr>\n\t\t<tr><td>version</td><td class=\"c mono\" id=\"version\"></td></tr>\n\t\t<tr><td>os</td><td class=\"c mono\" id=\"os\"></td></tr>\n\t\t<tr><td>[css] os <sup>1</sup></td>\n\t\t\t<td><span style=\"font-family: Arimo, DejaVu Serif, ABR;\">Linux</span><span\n\t\t\t\tstyle=\"font-family: Lucida Grande, ABR;\">Mac</span><span\n\t\t\t\tstyle=\"font-family: Segoe UI, ABR;\">Windows</span><span\n\t\t\t\tstyle=\"font-family: Roboto, Droid Sans, ABR;\">Android</span>\n\t\t</td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\">\n\t\t\t<span class=\"no_color\">code based on work by </span><a target=\"_blank\" class=\"blue\"\n\t\t\thref=\"https://arthuredelstein.github.io/tordemos/os-detection-font-css.html\">Arthur Edelstein</a> <sup>1</sup>\n\t\t</span></td></tr>\n\t</table>\n\n\t<!--region-->\n\t<table id=\"tb4\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"region\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#region\">region</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#feature\">&#9650;</a> <span class=\"c perf\" id=\"perfregion\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#headersd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"regiond\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn4 btn\" onClick=\"outputSection(4)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"regionhash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<!--geo-->\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">navigator<br>window</span></div>\n\t\t\t&nbsp; geolocation</td><td class=\"mono border-bottom\">\n\t\t\t\t<span class=\"c\" id=\"geolocation_navigator\"></span> | <span class=\"c\" id=\"geolocation\"></span>\n\t\t\t\t<div class=\"btn-right s99\">geo</div>\n\t\t\t</td></tr>\n\t\t<!--lang-->\n\t\t<tr><td>language</td><td class=\"c mono\" id=\"language\"></td></tr>\n\t\t<tr><td>languages</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"languages\"></span>\n\t\t\t<div class=\"btn-right s99\">languages</div></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">lowercase + sorted</span></div> &nbsp; [system] languages</td>\n\t\t\t<td class=\"mono border-bottom\"><span class=\"c\" id=\"languages_system\"></span></td></tr>\n\t\t<!--locale-->\n\t\t<tr><td>locale</td><td class=\"c mono\" id=\"locale\"></td></tr>\n\t\t<tr><td>[intl] locale</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"locale_intl\"></span>\n\t\t\t<div class=\"btn-right s99\">intl</div></td></tr>\n\t\t<tr><td>[tolocalstring] locale</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"locale_tolocalestring\"></span>\n\t\t\t<span class=\"c\" id=\"locale_tolocalestring_matches_intl\"></span>\n\t\t\t<div class=\"btn-right\" id=\"intl_data\"></div>\n\t\t</td></tr>\n\t\t<tr><td>[intl] dates</td><td class=\"mono\">\n\t\t\t<span clas=\"c\" id=\"dates_intl\"></span>\n\t\t\t<div class=\"btn-right\" id=\"intl_perf\"></div>\n\t\t</td></tr>\n\t\t<tr><td>[to*string] dates</td><td class=\"mono border-bottom\">\n\t\t\t<span class=\"c\" id=\"dates_to*string\"></span><span class=\"c\" id=\"dates_to*string_matches_intl\"></span>\n\t\t</td></tr>\n\t\t<!--l10n-->\n\t\t<!--<tr><td>css</td><td class=\"c mono\" id=\"l10n_css\"></td></tr>-->\n\t\t<tr><td>[parsererror] direction</td>\n\t\t\t<td class=\"c mono\" id=\"l10n_parsererror_direction\">\n\t\t</td></tr>\n\t\t<tr><td>media messages</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"l10n_media_messages\"></span>\n\t\t\t<div class=\"btn-right s99\">l10n</div>\n\t\t</td></tr>\n\t\t<tr><td>reporting messages</td><td class=\"c mono\" id=\"l10n_reporting_messages\"></td></tr>\n\t\t<tr><td>validation messages</td><td class=\"c mono\" id=\"l10n_validation_messages\"></td></tr>\n\t\t<tr><td>XML messages</td><td class=\"c mono\" id=\"l10n_xml_messages\"></td></tr>\n\t\t<tr><td>XML prettyprint</td><td class=\"c mono\" id=\"l10n_xml_prettyprint\"></td></tr>\n\t\t<tr><td>XSLT messages</td><td class=\"c mono\" id=\"l10n_xslt_messages\"></td></tr>\n\t\t<tr><td>XSLT sort</td><td class=\"mono border-bottom\"><span class=\"c\" id=\"l10n_xslt_sort\"></span></td></tr>\n\t\t<!--tz-->\n\t\t<tr><td><span id=\"labelTT\" class=\"btn btn0\" onClick=\"togglerows('TT','btn')\">[+]</span>\n\t\t\ttimezone</td><td class=\"c mono\" id=\"timezone\"></td></tr>\n\t\t<tr class=\"togTT\"><td>timeZone</td><td class=\"c mono\" id=\"timezone_timeZone\"></td></tr>\n\t\t<tr class=\"togTT\"><td>timeZoneId</td><td class=\"c mono\" id=\"timezone_timeZoneId\"></td></tr>\n\t\t<tr class=\"togTT\"><td>zonedDateTimeISO</td><td class=\"c mono\" id=\"timezone_zonedDateTimeISO\"></td></tr>\n\t\t<!--tz offset-->\n\t\t<tr><td><span id=\"labelTL\" class=\"btn btn0\" onClick=\"togglerows('TL','btn')\">[+]</span>\n\t\t\t[offset] timezone</td>\n\t\t\t<td class=\"mono\"><span class=\"c\" id=\"timezone_offset\"></span>\n\t\t\t<div class=\"btn-right s99\">timezone</div>\n\t\t</td></tr>\n\t\t<!--date-->\n\t\t<tr class=\"togTL\"><td>plainDateISO</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"timezone_offset_plainDateISO\"></span>\n\t\t\t<div class=\"btn-right-inset s99 border-top\">date</div>\n\t\t</td></tr>\n\t\t<tr class=\"togTL\"><td>toDateString</td><td class=\"c mono\" id=\"timezone_offset_toDateString\"></td></tr>\n\t\t<!--time-->\n\t\t<tr class=\"togTL\"><td>plainTimeISO</td><td class=\"mono\">\n\t\t\t<span class=\"c spaces\" id=\"plainTimeISOspaces\"></span>\n\t\t\t<span class=\"c\" id=\"timezone_offset_plainTimeISO\"></span>\n\t\t\t<div class=\"btn-right-inset s99 border-top\">time</div>\n\t\t</td></tr>\n\t\t<tr class=\"togTL\"><td>toTimeString</td><td class=\"mono\">\n\t\t\t<span class=\"c spaces\" id=\"toTimeStringspaces\"></span>\n\t\t\t<span class=\"c\" id=\"timezone_offset_toTimeString\"><span>\n\t\t</td></tr>\n\t\t<!--datetime-->\n\t\t<tr class=\"togTL\"><td>plainDateTimeISO</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"timezone_offset_plainDateTimeISO\"></span>\n\t\t\t<div class=\"btn-right-inset s99 border-top\">datetime</div>\n\t\t</td></tr>\n\t\t<tr class=\"togTL\"><td>timeZone</td><td class=\"c mono\" id=\"timezone_offset_timeZone\"></td></tr>\n\t\t<tr class=\"togTL\"><td>toLocaleDateString</td><td class=\"c mono\" id=\"timezone_offset_toLocaleDateString\"></td></tr>\n\t\t<tr class=\"togTL\"><td>toLocaleString</td><td class=\"c mono\" id=\"timezone_offset_toLocaleString\"></td></tr>\n\t\t<tr class=\"togTL\"><td>toLocaleTimeString</td><td class=\"c mono\" id=\"timezone_offset_toLocaleTimeString\"></td></tr>\n\t\t<tr class=\"togTL\"><td>toString</td><td class=\"c mono\" id=\"timezone_offset_toString\"></td></tr>\n\t\t<tr class=\"togTL\"><td>zonedDateTimeISO</td><td class=\"c mono\" id=\"timezone_offset_zonedDateTimeISO\"></td></tr>\n\t\t<!--lastmod-->\n\t\t<tr class=\"togTL\"><td>iframe</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"timezone_offset_iframe\"></span>\n\t\t\t<div class=\"btn-right-inset s99 border-top\">lastModified</div>\n\t\t\t</td></tr>\n\t\t<tr class=\"togTL\"><td>parseFromString</td><td class=\"c mono\" id=\"timezone_offset_parseFromString\"></td></tr>\n\t\t<tr class=\"togTL\"><td>parseHTMLUnsafe</td><td class=\"c mono\" id=\"timezone_offset_parseHTMLUnsafe\"></td></tr>\n\t\t<tr class=\"togTL\"><td>EXSLT</td><td class=\"c mono\" id=\"timezone_offset_exslt\"></td></tr>\n\t\t<!--tz offsets-->\n\t\t<tr><td><span id=\"labelTO\" class=\"btn btn0\" onClick=\"togglerows('TO','btn')\">[+]</span>[offsets] timezone</td>\n\t\t\t<td class=\"mono\"><div class=\"btn-right c\" id=\"timezone_offsets_data\"></div>\n\t\t\t<span class=\"c\" id=\"timezone_offsets\"></span></td></tr>\n\t\t<tr class=\"togTO\"><td>[get] components</td><td class=\"c mono\" id=\"timezone_offsets_components\"></td></tr>\n\t\t<tr class=\"togTO\"><td>[getUTC] components</td><td class=\"c mono\" id=\"timezone_offsets_components_utc\"></td></tr>\n\t\t<tr class=\"togTO\"><td>date</td><td class=\"c mono\" id=\"timezone_offsets_date\"></td></tr>\n\t\t<tr class=\"togTO\"><td><a class=\"blue\" href=\"tests/timezones.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; date.parse</td><td class=\"c mono\" id=\"timezone_offsets_date.parse\"></td></tr>\n\t\t<tr class=\"togTO\"><td>date.valueOf</td><td class=\"c mono\" id=\"timezone_offsets_date.valueOf\"></td></tr>\n\t\t<tr class=\"togTO\"><td>getTime</td><td class=\"c mono\" id=\"timezone_offsets_getTime\"></td></tr>\n\t\t<tr class=\"togTO\"><td>getTimezoneOffset</td><td class=\"c mono\" id=\"timezone_offsets_getTimezoneOffset\"></td></tr>\n\t\t<tr class=\"togTO\"><td>offsetNanoseconds</td><td class=\"c mono\" id=\"timezone_offsets_offsetNanoseconds\"></td></tr>\n\t\t<tr class=\"togTO\"><td>Symbol.toPrimitive</td><td class=\"c mono\" id=\"timezone_offsets_Symbol.toPrimitive\"></td></tr>\n\t\t<tr class=\"togTO\"><td>timeZoneName</td><td class=\"c mono\" id=\"timezone_offsets_timeZoneName\"></td></tr>\n\t\t<!--details-->\n\t\t<tr><td colspan=\"2\" class=\"showhide\">\n\t\t\t<span id=\"labelL\" class=\"btnb\" onClick=\"togglerows('L')\">&#9660; show details</span></td></tr>\n\t\t<!--date/time + formatting-->\n\t\t<tr class=\"togL\"><td></td><td class=\"s4\">[to*string] dates</td></tr>\n\t\t\t<tr class=\"togL\"><td>toTimeString</td><td class=\"c\" id=\"ldt1\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>date/time</td><td class=\"c\" id=\"ldt2\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>toString</td><td class=\"c\" id=\"ldt3\"></td></tr>\n\t\t\t<!--options-->\n\t\t\t<tr class=\"togL\"><td>toLocaleString</td><td class=\"c\" id=\"ldt4\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>toLocaleDateString</td><td class=\"c\" id=\"ldt5\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>toLocaleTimeString</td><td class=\"c\" id=\"ldt6\"></td></tr>\n\t\t\t<!--no options-->\n\t\t\t<tr class=\"togL\"><td>toLocaleString</td><td class=\"c\" id=\"ldt7\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>[Typed Array] toLocaleString</td><td class=\"c\" id=\"ldt8\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>toLocaleDateString</td><td class=\"c\" id=\"ldt9\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>toLocaleTimeString</td><td class=\"c\" id=\"ldt10\"></td></tr>\n\t\t<!--Intl.DateTimeFormat-->\n\t\t<tr class=\"togL\"><td></td><td class=\"s4\">[intl] dates</td></tr>\n\t\t\t<tr class=\"togL\"><td>DateTimeFormat</td><td class=\"c\" id=\"ldt11\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>[formatToParts] DateTimeFormat</td><td class=\"c\" id=\"ldt12\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>DateTimeFormat</td><td class=\"c\" id=\"ldt13\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>timeZoneNames</td><td class=\"c\" id=\"ldt14\"></td></tr>\n\t\t\t<tr class=\"togL\"><td>formatrange</td><td class=\"c\" id=\"ldt15\"></td></tr>\n\t\t<tr class=\"togL\" colspan=\"2\"><td></td></tr>\n\t</table>\n\n\t<!--headers-->\n\t<table id=\"tb5\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"headers\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#headers\">headers</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#region\">&#9650;</a> <span class=\"c perf\" id=\"perfheaders\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#storaged\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"headersd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn5 btn\" onClick=\"outputSection(5)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"headershash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>connection</td><td class=\"c mono\" id=\"connection\"></td></tr>\n\t\t<tr><td>doNotTrack</td><td class=\"c mono\" id=\"doNotTrack\"></td></tr>\n\t\t<tr><td>globalPrivacyControl</td><td class=\"c mono\" id=\"globalPrivacyControl\"></td></tr>\n\t\t<tr><td>onLine</td><td class=\"c mono\" id=\"onLine\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t</table>\n\n\t<!--storage-->\n\t<table id=\"tb6\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"storage\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#storage\">cookies & storage</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#headers\">&#9650;</a> <span class=\"c perf\" id=\"perfstorage\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#devicesd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"storaged\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn6 btn\" onClick=\"outputSection(6)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"storagehash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>caches</td><td class=\"c mono\" id=\"caches\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">session | persistent<br>tests are JS 1st party</span></div>\n\t\t\t\t&nbsp; cookies</td><td class=\"c mono\" id=\"ctest\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">dom.cookieStore.enabled</span></div>\n\t\t\t\t&nbsp; cookieStore</td><td class=\"c mono\" id=\"cstest\"></td></tr>\n\t\t<tr><td>localStorage</td><td class=\"c mono\" id=\"localStorage\"></td></tr>\n\t\t<tr><td>sessionStorage</td><td class=\"c mono\" id=\"sessionStorage\"></td></tr>\n\t\t<tr><td>indexedDB</td><td class=\"mono\"><span class=\"c\" id=\"indexedDB\"></span>\t| <span class=\"c\" id=\"indexedDB_test\"></span></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">service | shared | worker</span></div>\n\t\t\t&nbsp; workers</td><td class=\"c mono\" id=\"workers\">\n\t\t</td></tr>\n\t\t<tr><td>[tests] workers</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"worker_service_test\"></span>\n\t\t\t| <span class=\"c\" id=\"worker_shared_test\"></span>\n\t\t\t| <span class=\"c\" id=\"worker_test\"></span>\n\t\t</td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt\">dom.fs.enabled</span></div>\n\t\t\t&nbsp; file system</td><td class=\"c mono\" id=\"filesystem\"></td></tr>\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('storage_manager')\">[ run ]</span>\n\t\t\tstorage manager</td><td class=\"c mono\" id=\"storage_manager\"></td></tr>\n\t\t<tr><td>storage quota</td><td class=\"c mono\" id=\"storage_quota\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t</table>\n\n\t<!--devices-->\n\t<table id=\"tb7\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"devices\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#devices\">devices & hardware</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#storage\">&#9650;</a> <span class=\"c perf\" id=\"perfdevices\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#svgd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"devicesd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn7 btn\" onClick=\"outputSection(7)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"deviceshash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>battery</td><td class=\"mono c\" id=\"battery\"></td></tr>\n\t\t<tr><td>[pixel | color] depth</td>\n\t\t\t<td class=\"mono\"><span class=\"c\" id=\"pixelDepth\"></span> | <span class=\"c\" id=\"colorDepth\"></span></td></tr>\n\t\t<tr><td>deviceMemory</td><td class=\"c mono\" id=\"deviceMemory\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">[css | matchmedia] device-posture<br>navigator.devicePosture</span></div>\n\t\t\t&nbsp; devicePosture</td><td class=\"mono\">\n\t\t\t<span id=\"cssDP\"></span>\n\t\t\t| <span class=\"c\" id=\"devicePosture_device-posture\"></span>\n\t\t\t| <span class=\"c\" id=\"devicePosture_devicePosture\"></span>\n\t\t\t</td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">dom.security.featurePolicy.webidl.enabled</span></div>\n\t\t\t&nbsp; featurePolicy</td><td class=\"c mono\" id=\"featurePolicy\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">dom.maxHardwareConcurrency</span></div>\n\t\t\t&nbsp; hardwareConcurrency</td><td class=\"c mono\" id=\"hardwareConcurrency\"></td></tr>\n\t\t<tr><td>keyboard</td><td class=\"c mono\" id=\"keyboard\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">media.navigator.enabled<br>media.peerconnection.enabled</span></div>\n\t\t\t&nbsp; media devices</td><td class=\"c mono\" id=\"mediaDevices\"></td></tr>\n\t\t<tr><td>[constraints] media devices</td><td class=\"c mono\" id=\"mediaDevices_constraints\"></td></tr>\n\t\t<tr><td>memory</td><td class=\"c mono\" id=\"memory\"></td></tr>\n\t\t<tr><td>permissions</td><td class=\"c mono\" id=\"permissions\"></td></tr>\n\t\t<tr><td id=\"tzpPointer\"><span class=\"btn btn0\">[ run ]</span>\n\t\t\t&nbsp; pointer event <sup>1</sup></td><td class=\"c mono\" id=\"pointer_event\"></td></tr>\n\t\t<tr><td>recursion</td><td class=\"c mono\" id=\"recursion\"></td></tr>\n\t\t<tr><td>screen.isExtended</td><td class=\"c mono\" id=\"screen_isextended\"></td></tr>\n\t\t<tr><td>touch</td><td class=\"c mono\" id=\"touch\"></td></tr>\n\t\t<tr><td>viewport-segments</td><td class=\"mono\"><span id=\"cssVS\"></span>\n\t\t\t<span class=\"cssc\" id=\"viewport-segments_css\"></span>\n\t\t\t| <span class=\"c\" id=\"viewport-segments\"></span></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\"><span class=\"no_color\">pointer code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://patrickhlauke.github.io/touch/\">Patrick Lauke</a> <sup>1</sup>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--svg-->\n\t<table id=\"tb8\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"svg\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#svg\">svg</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#devices\">&#9650;</a> <span class=\"c perf\" id=\"perfsvg\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#canvasd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"svgd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td>test</td><td class=\"mono\">result</td></tr>\n\t</table>\n\n\t<!--canvas-->\n\t<table id=\"tb9\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"canvas\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#canvas\">canvas</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#svg\">&#9650;</a> <span class=\"c perf\" id=\"perfcanvas\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#webgld\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"canvasd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"intro\"><span class=\"normal\"><span class=\"no_color\">These tests are only checking for\n\t\t\tprotection, not entropy. Additional canvas tests [iframes, workers, offscreen] can be found at\n\t\t\t<a target=\"blank\" class=\"blue\" href=\"https://canvasblocker.kkapsner.de/test/test.html\">CanvasBlocker</a></span>\n\t\t\t</span></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn9 btn\" onClick=\"outputSection(9)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"canvashash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/canvasnoise.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; <div class=\"ttip\"><span class=\"icon\">[ i ]</span><span class=\"ttxt\">random per run</span></div>\n\t\t\t&nbsp; getImageData</td><td class=\"c mono\" id=\"getImageData\"></td></tr>\n\t\t<tr><td>[solid] getImageData</td><td class=\"c mono\" id=\"getImageData_solid\"></td></tr>\n\t\t<tr><td>isPointInPath</td><td class=\"c mono\" id=\"isPointInPath\"></td></tr>\n\t\t<tr><td>isPointInStroke</td><td class=\"c mono\" id=\"isPointInStroke\"></td></tr>\n\t\t<tr><td>toBlob</td><td class=\"c mono\" id=\"toBlob\"></td></tr>\n\t\t<tr><td>[solid] toBlob</td><td class=\"c mono\" id=\"toBlob_solid\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/canvasrfp.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; toDataURL</td><td class=\"c mono\" id=\"toDataURL\"></td></tr>\n\t\t<tr><td>[solid] toDataURL</td><td class=\"c mono\" id=\"toDataURL_solid\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\">\n\t\t\t<span class=\"no_color\">canvas code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://canvasblocker.kkapsner.de/test\">kkapsner</a><span class=\"no_color\"> & </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/kkapsner/CanvasBlocker\">CanvasBlocker</a>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--webgl-->\n\t<table id=\"tb10\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"webgl\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#webgl\">webgl</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#canvas\">&#9650;</a> <span class=\"c perf\" id=\"perfwebgl\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#audiod\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"webgld\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn10 btn\" onClick=\"outputSection(10)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"webglhash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>experimental</td><td class=\"mono\">result</td></tr>\n\t\t<tr><td>webgl</td><td class=\"mono\">result</td></tr>\n\t\t<tr><td>webgl2</td><td class=\"mono\">result</td></tr>\n\t\t<tr><td colspan=\"3\"><span class=\"normal\"><span class=\"no_color\">webgl code by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://gist.github.com/abrahamjuliot/7baf3be8c451d23f7a8693d7e28a35e2\">Abraham Juliot</a>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--audio-->\n\t<table id=\"tb11\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"audio\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#audio\">audio</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#webgl\">&#9650;</a> <span class=\"c perf\" id=\"perfaudio\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#fontsd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"audiod\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn11 btn\" onClick=\"outputSection(11)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"audiohash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>audioContext <sup>1</sup></td><td class=\"c mono\" id=\"audioContext\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">copyFromChannel<br>getChannelData<br>sum of buffer</span></div>\n\t\t\t&nbsp; offlineAudioContext <sup>2</sup></td><td class=\"c mono\" id=\"offlineAudioContext\"></td></tr>\n\t\t<tr><td colspan=\"2\" class=\"center\">------</td></tr>\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('audio_test')\">[ run ]</span>\n\t\t\thash</td><td class=\"gc mono\" id=\"audio_test\"></td></tr>\n\t\t<tr><td>OscillatorNode <sup>1</sup></td><td class=\"uaudio_test gc mono\" id=\"audio_test_oscillator\"></td></tr>\n\t\t<tr><td>+DynamicsCompressor <sup>1</sup></td><td class=\"uaudio_test gc mono\" id=\"audio_test_oscillator_compressor\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\"><span class=\"no_color\">audio code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://audiofingerprint.openwpm.com/\">openWPM</a> <sup>1</sup>\n\t\t\t<span class=\"no_color\"> and </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://canvasblocker.kkapsner.de/test\">kkapsner</a><span class=\"no_color\"> & </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/kkapsner/CanvasBlocker\">CanvasBlocker</a> <sup>2</sup>\n\t\t</span></td></tr>\n\t</table>\n\n\t<!--fonts-->\n\t<table id=\"tb12\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"fonts\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#fonts\">fonts</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#audio\">&#9650;</a> <span class=\"c perf\" id=\"perffonts\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#codecsd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"fontsd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn12 btn\" onClick=\"outputSection(12)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"fontshash\"></span>\n\t\t\t<div class=\"btn-right smaller\"><span class=\"c\" id=\"fntBtn\"></span></div>\n\t\t</td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">browser.display.use_document_fonts</span></div>\n\t\t\t&nbsp; document fonts</td><td class=\"c mono\" id=\"document_fonts\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">layout.css.font-tech.enabled</span></div>\n\t\t\t&nbsp; font-format | font-tech</td><td class=\"mono\"><span class=\"c\" id=\"font-format\"></span> | <span class=\"c\" id=\"font-tech\"></span>\n\t\t</td></tr>\n\t\t<!--fonts-->\n\t\t<tr><td>[faces] fonts</td><td class=\"mono border-top\">\n\t\t\t<div class=\"btn-right c\" id=\"font_detection\"></div><span id=\"font_faces\"><span></td></tr>\n\t\t<tr><td>[offscreen] fonts</td><td class=\"mono c\" id=\"font_offscreen\"></td></tr>\n\t\t<tr><td><span id=\"labelFS\" class=\"btn btn0\" onClick=\"togglerows('FS','btn')\">[+]</span>\n\t\t\t[sizes | names] fonts <sup>1</sup></td>\n\t\t\t<td class=\"mono\"><span class=\"c\" id=\"font_sizes\"></span> | <span class=\"c\" id=\"font_names\"></span></td>\n\t\t</tr>\n\t\t<tr class=\"togFS\"><td>client</td><td class=\"c mono\" id=\"font_sizes_client\"></td></tr>\n\t\t<tr class=\"togFS\"><td>offset</td><td class=\"c mono\" id=\"font_sizes_offset\"></td></tr>\n\t\t<tr class=\"togFS\"><td>scroll</td><td class=\"c mono border-bottom\" id=\"font_sizes_scroll\"></td></tr>\n\t\t<tr class=\"togFS\"><td>pixel</td><td class=\"c mono\" id=\"font_sizes_pixel\"></td></tr>\n\t\t<tr class=\"togFS\"><td>pixelsize</td><td class=\"c mono border-bottom\" id=\"font_sizes_pixelsize\"></td></tr>\n\t\t<tr class=\"togFS\"><td>perspective</td><td class=\"c mono\" id=\"font_sizes_perspective\"></td></tr>\n\t\t<tr class=\"togFS\"><td>transform</td><td class=\"c mono border-bottom\" id=\"font_sizes_transform\"></td></tr>\n\t\t<tr class=\"togFS\"><td>[domrect] bounding</td><td class=\"c mono\" id=\"font_sizes_domrectbounding\"></td></tr>\n\t\t<tr class=\"togFS\"><td>bounding range</td><td class=\"c mono\" id=\"font_sizes_domrectboundingrange\"></td></tr>\n\t\t<tr class=\"togFS\"><td>client</td><td class=\"c mono\" id=\"font_sizes_domrectclient\"></td></tr>\n\t\t<tr class=\"togFS\"><td>client range</td><td class=\"c mono border-bottom\" id=\"font_sizes_domrectclientrange\"></td></tr>\n\t\t<tr><td>[base sizes] fonts <sup>1</sup></td><td class=\" mono border-top\">\n\t\t\t<span class=\"c\" id=\"font_sizes_base\"></span> | <span class=\"c\" id=\"font_sizes_base_reported\"></span>\n\t\t</td></tr>\n\t\t<tr><td>[maximum sizes] fonts</td><td class=\"c mono\" id=\"font_sizes_max\"></td></tr>\n\t\t<tr><td>[methods] fonts <sup>1</sup></td><td class=\"c mono\" id=\"font_sizes_methods\"></td></tr>\n\t\t<tr><td>[moz] fonts</td><td class=\"c mono\" id=\"fonts_moz\"></td></tr>\n\t\t<tr><td>[system] fonts</td><td class=\"c mono\" id=\"fonts_system\"></td></tr>\n\t\t<tr><td>[widget] fonts</td><td class=\"c mono\" id=\"fonts_widget\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/fontasync.html\" target=\"blank\">[PoC]</a>\n\t\t\t<span id=\"labelFG\" class=\"btn btn0\" onClick=\"togglerows('FG','btn')\">[+]</span>\n\t\t\tglyphs <sup>2</sup></td><td class=\"c mono\" id=\"glyphs\"></td></tr>\n\t\t<tr class=\"togFG\"><td>glyphs</td><td class=\"spaces smaller\" id=\"glyphs_visual\"></td></tr>\n\t\t<tr><td>[test] graphite <sup>3</sup></td><td class=\"c mono\" id=\"graphite\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">proportional font<br>monospace size<br>sans-serif size<br>serif size</span></div>\n\t\t\t&nbsp; script defaults</td><td class=\"c mono\" id=\"script_defaults\"></td></tr>\n\t\t<!--tm-->\n\t\t<tr><td>actualboundingbox | baseline</td><td class=\"mono\">\n\t\t\t<span class=\"c spaces\" id=\"textmetrics_actualboundingbox\"></span> | <span class=\"c\" id=\"textmetrics_baseline\"></span>\n\t\t\t</td></tr>\n\t\t<tr><td>emheight | fontboundingbox</td><td class=\"mono\">\n\t\t\t<span class=\"c spaces\" id=\"textmetrics_emheight\"></span> | <span class=\"c\" id=\"textmetrics_fontboundingbox\"></span>\n\t\t\t</td></tr>\n\t\t<!--<tr><td>[measureText] width <sup>4</sup></td><td class=\"c mono\" id=\"textmetrics_width\"></td></tr>-->\n\t\t<tr><td>[css | test] woff2 <sup>4</sup></td><td class=\"mono\">\n\t\t\t<span id=\"cssWoff2\"></span> | <span class=\"c\" id=\"woff2\"></span></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t\t<!--creds-->\n\t\t<tr><td colspan=\"2\"><span class=\"normal\">\n\t\t\t<span class=\"no_color\">custom font from </span>\n\t\t\t<a target=\"_blank\" class=\"blue\"\thref=\"https://graphite.sil.org/\">SIL</a> <sup>3</sup>\n\t\t\t<span class=\"no_color\"> and code based on work by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a> <sup>1</sup>\n\t\t\t<span class=\"no_color\">, </span>\n\t\t\t<a target=\"_blank\" class=\"blue\"\n\t\t\t\thref=\"https://www.bamsoftware.com/talks/fc15-fontfp/fontfp.html#demo\">David Fifield & Serge Egelman</a> <sup>2</sup>\n\t\t\t<span class=\"no_color\"> and </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/filamentgroup/woff2-feature-test\">Filament Group</a> <sup>4</sup>\n\t\t</span></td></tr>\n\t</table>\n\n\t<!--codecs-->\n\t<table id=\"tb13\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"3\"><a name=\"codecs\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#codecs\">codecs</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#fonts\">&#9650;</a> <span class=\"c perf\" id=\"perfcodecs\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#cssd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"codecsd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn13 btn\" onClick=\"outputSection(13)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"codecshash\"></span>\n\t\t\t<div class=\"btn-right smaller\"><span id=\"mediaBtn\"></span></div></td></tr>\n\t\t<tr><td>autoplaypolicy</td><td class=\"c mono\" id=\"getAutoplayPolicy\"></td></tr>\n\t\t<tr><td>[user] autoplaypolicy</td><td class=\"c mono\" id=\"getAutoplayPolicy_user\"></td></tr>\n\t\t<tr><td>EME</td><td class=\"c mono\" id=\"eme\"></td></tr>\n\t\t<tr><td>canPlayType</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"audio_canPlayType\"></span> | <span class=\"c\" id=\"video_canPlayType\"></span></td></tr>\n\t\t<tr><td>[RTC] getCapabilities</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"audio_getCapabilities_rtc\"></span> | <span class=\"c\" id=\"video_getCapabilities_rtc\"></span></td></tr>\n\t\t<tr><td>isTypeSupported</td><td class=\"mono\">\n\t\t\t<span class=\"c\" id=\"audio_isTypeSupported\"></span> | <span class=\"c\" id=\"video_isTypeSupported\"></span></td></tr>\n\t\t<tr><td>preload</td><td class=\"c mono\" id=\"preload_htmlmediaelement\"></td></tr>\n\t\t<tr><td colspan=\"2\"></td></tr>\n\t</table>\n\n\t<!--css-->\n\t<table id=\"tb14\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"css\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#css\">css</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#codecs\">&#9650;</a> <span class=\"c perf\" id=\"perfcss\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#elementsd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"cssd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn14 btn\" onClick=\"outputSection(14)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"csshash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td>[css4] colors</td><td class=\"c mono\" id=\"colors_css4\"></td></tr>\n\t\t<tr><td>[deprecated] colors</td><td class=\"c mono\" id=\"colors_deprecated\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/csscolors.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; [-moz-] colors</td><td class=\"c mono\" id=\"colors_moz\"></td></tr>\n\t\t<tr><td><span id=\"labelCS\" class=\"btn btn0\" onClick=\"togglerows('CS','btn')\">[+]</span>\n\t\t\tcomputed styles <sup>1</sup></td><td class=\"c mono\" id=\"computed_styles\"></td></tr>\n\t\t<tr class=\"togCS\"><td>CSSRuleList.style <sup>1</sup></td><td class=\"c mono\" id=\"computed_styles_cssrulelist\"></td></tr>\n\t\t<tr class=\"togCS\"><td>DOMParser <sup>1</sup></td><td class=\"c mono\" id=\"computed_styles_domparser\"></td></tr>\n\t\t<tr class=\"togCS\"><td>getComputedStyle <sup>1</sup></td><td class=\"c mono\" id=\"computed_styles_getcomputed\"></td></tr>\n\t\t<tr class=\"togCS\"><td>HTMLElement.style <sup>1</sup></td><td class=\"c mono\" id=\"computed_styles_htmlelement\"></td></tr>\n\n\t\t<!--media: matchmedia + @media-->\n\t\t<tr><td><span id=\"labelMM\" class=\"btn btn0\" onClick=\"togglerows('MM','btn')\">[+]</span>\n\t\t\tmedia</td><td class=\"c mono\" id=\"media\"></td></tr>\n\t\t<tr class=\"togMM\"><td>color</td><td class=\"mono\"><span id=\"cssC\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_color_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_color\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>color-gamut</td><td class=\"mono\"><span id=\"cssCG\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_color-gamut_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_color-gamut\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>dynamic-range</td><td class=\"mono\"><span id=\"cssDR\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_dynamic-range_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_dynamic-range\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>forced-colors</td><td class=\"mono\"><span id=\"cssFC\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_forced-colors_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_forced-colors\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>any-hover</td><td class=\"mono\"><span id=\"cssAH\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_any-hover_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_any-hover\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>hover</td><td class=\"mono\"><span id=\"cssH\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_hover_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_hover\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>inverted-colors</td><td class=\"mono\"><span id=\"cssIC\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_inverted-colors_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_inverted-colors\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>any-pointer</td><td class=\"mono\"><span id=\"cssAP\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_any-pointer_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_any-pointer\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>pointer</td><td class=\"mono\"><span id=\"cssP\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_pointer_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_pointer\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>prefers-color-scheme</td><td class=\"mono\"><span id=\"cssPCS\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_prefers-color-scheme_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_prefers-color-scheme\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>prefers-contrast</td><td class=\"mono\"><span id=\"cssPC\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_prefers-contrast_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_prefers-contrast\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>prefers-reduced-data</td><td class=\"mono\"><span id=\"cssPRD\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_prefers-reduced-data_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_prefers-reduced-data\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>prefers-reduced-motion</td><td class=\"mono\"><span id=\"cssPRM\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_prefers-reduced-motion_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_prefers-reduced-motion\"></span></td></tr>\t\t\n\t\t<tr class=\"togMM\"><td>prefers-reduced-transparency</td><td class=\"mono\"><span id=\"cssPRT\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_prefers-reduced-transparency_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_prefers-reduced-transparency\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>update</td><td class=\"mono\"><span id=\"cssUD\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_update_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_update\"></span></td></tr>\n\t\t<tr class=\"togMM\"><td>video-dynamic-range</td><td class=\"mono\"><span id=\"cssVDR\"></span>\n\t\t\t<span class=\"cssc\" id=\"media_video-dynamic-range_css\"></span>\n\t\t\t| <span class=\"c\" id=\"media_video-dynamic-range\"></span></td></tr>\n\n\t\t<tr><td>site colors</td><td class=\"c mono\" id=\"site_colors\"></td></tr>\n\t\t<tr><td>site styles</td><td class=\"c mono\" id=\"site_styles\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">layout.css.always_underline_links</span></div>\n\t\t\t&nbsp; underline links</td><td class=\"c mono\" id=\"underline_links\"></td></tr>\n\t\t<tr><td colspan=\"3\"><span class=\"normal\"><span class=\"no_color\">code by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a> <sup>1</sup>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--elements-->\n\t<table id=\"tb15\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"elements\"></a>\n\t\t\t<div class=\"nav-title\">\n\t\t\t\t<a href=\"#elements\">elements</a>\n\t\t\t\t<div class=\"nav-up\"><a href=\"#css\">&#9650;</a> <span class=\"c perf\" id=\"perfelements\"></span></div>\n\t\t\t\t<div class=\"nav-down\"><a href=\"#ciphersd\">&#9660;</a></div>\n\t\t\t\t<div class=\"nav-right\"><a name=\"elementsd\"></a></div>\n\t\t\t</div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn15 btn\" onClick=\"outputSection(15)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"elementshash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/domrectspoof.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; DOMRect</td><td class=\"c mono\" id=\"domrect\"></td></tr>\n\t\t<tr><td>HTMLElement keys <sup>1</sup></td><td class=\"c mono\" id=\"htmlelement_keys\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/elementfont.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; font</td><td class=\"c mono\" id=\"element_font\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/elementforms.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; forms</td><td class=\"c mono\" id=\"element_forms\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">mathml.disabled</span></div>\n\t\t\t&nbsp; mathml</td><td class=\"c mono\" id=\"element_mathml\"></td></tr>\n\t\t<tr><td><a class=\"blue\" href=\"tests/elementother.html\" target=\"blank\">[PoC]</a>\n\t\t\t&nbsp; other</td><td class=\"c mono\" id=\"element_other\"></td></tr>\n\t\t<tr><td>[auto | thin] scrollbars</td><td class=\"c mono\" id=\"element_scrollbars\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"no_color\">code by </span><a target=\"_blank\" class=\"blue\"\n\t\t\thref=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a> <sup>1</sup>\n\t\t</td></tr>\n\t</table>\n\n\t<!--ciphers-->\n\t<table id=\"tb16\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"ciphers\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#ciphers\">ciphers / tls</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#elements\">&#9650;</a></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#timingd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"ciphersd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td>test</td><td class=\"mono\">result</td></tr>\n\t</table>\n\n\t<!--timing-->\n\t<table id=\"tb17\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"timing\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#timing\">timing</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#ciphers\">&#9650;</a> <span class=\"c perf\" id=\"perftiming\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#miscd\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"timingd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn99 btn\" style=\"cursor: default;\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"timinghash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<!--manual-->\n\t\t<tr><td><span class=\"btn btn0\" onClick=\"outputUser('timing_audio')\">[ run ]</span>\n\t\t\t<span id=\"labelTA\" class=\"btn btn0\" onClick=\"togglerows('TA','btn')\">[+]</span>\n\t\t\taudio</td><td class=\"c mono\" id=\"timing_audio\"></td></tr>\n\t\t<tr class=\"togTA\"><td>contexttime</td><td class=\"c mono utiming_audio\" id=\"timing_audio_contexttime\"></td></tr>\n\t\t<tr class=\"togTA\"><td>performancetime</td><td class=\"c mono utiming_audio\" id=\"timing_audio_performancetime\"></td></tr>\n\t\t<!--auto-->\n\t\t<tr><td><span id=\"labelTP\" class=\"btn btn0\" onClick=\"togglerows('TP','btn')\">[+]</span>\n\t\t\ttiming precision</td><td class=\"c mono\" id=\"timing_precision\"></td></tr>\n\t\t<tr class=\"togTP\"><td>currenttime</td><td class=\"c mono\" id=\"timing_precision_currenttime\"></td></tr>\n\t\t<tr class=\"togTP\"><td>date</td><td class=\"c mono\" id=\"timing_precision_date\"></td></tr>\n\t\t<tr class=\"togTP\"><td>EXSLT</td><td class=\"c mono\" id=\"timing_precision_exslt\"></td></tr>\n\t\t<tr class=\"togTP\"><td> [temporal] instant</td><td class=\"c mono\" id=\"timing_precision_instant\"></td></tr>\n\t\t<tr class=\"togTP\"><td>mark</td><td class=\"c mono\" id=\"timing_precision_mark\"></td></tr>\n\t\t<tr class=\"togTP\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">dom.enable_performance_navigation_timing</span></div>\n\t\t\t&nbsp; navigation</td><td class=\"c mono\" id=\"timing_precision_navigation\"></td></tr>\n\t\t<tr class=\"togTP\"><td>now</td><td class=\"c mono\" id=\"timing_precision_now\"></td></tr>\n\t\t<tr class=\"togTP\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">dom.enable_performance</span></div>\n\t\t\t&nbsp; performance</td><td class=\"c mono\" id=\"timing_precision_performance\"></td></tr>\n\t\t<tr class=\"togTP\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">privacy*reduceTimerPrecision</span></div>\n\t\t\t&nbsp; reducetimer</td><td class=\"c mono\" id=\"timing_precision_reducetimer\"></td></tr>\n\t\t<tr class=\"togTP\"><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">dom.enable_resource_timing</span></div>\n\t\t\t&nbsp; resource</td><td class=\"c mono\" id=\"timing_precision_resource\"></td></tr>\n\t\t<tr class=\"togTP\"><td>timestamp</td><td class=\"c mono\" id=\"timing_precision_timestamp\"></td></tr>\n\t</table>\n\n\t<!--misc-->\n\t<table id=\"tb18\">\n\t\t<col width=\"31%\"><col width=\"69%\">\n\t\t<thead><tr><th colspan=\"2\"><a name=\"misc\"></a>\n\t\t\t<div class=\"nav-title\"><a href=\"#misc\">miscellaneous</a>\n\t\t\t<div class=\"nav-up\"><a href=\"#timing\">&#9650;</a> <span class=\"c perf\" id=\"perfmisc\"></span></div>\n\t\t\t<div class=\"nav-down\"><a href=\"#theend\">&#9660;</a></div>\n\t\t\t<div class=\"nav-right\"><a name=\"miscd\"></a></div></div>\n\t\t</th></tr></thead>\n\t\t<tr><td colspan=\"2\" class=\"secthash\">\n\t\t\t<div class=\"btn-left\"><span class=\"btn18 btn\" onClick=\"outputSection(18)\">[ re-run ]</span></div>\n\t\t\t<span class=\"c\" id=\"mischash\"></span>\n\t\t\t<div class=\"btn-right\"></div>\n\t\t</td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">dom.use_components_shim</span></div>\n\t\t\t&nbsp; component interfaces</td><td class=\"c mono\" id=\"component_interfaces\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtx\">Firefox 74+ : javascript.options.<br>property_error_message_fix</span></div>\n\t\t\t&nbsp; error message fix</td><td class=\"c mono\" id=\"error_message_fix\"></td></tr>\n\t\t<tr><td>[css] math</td><td class=\"c mono\" id=\"math_css\"></td></tr>\n\t\t<tr><td>[other] math</td><td class=\"c mono\" id=\"math_other\"></td></tr>\n\t\t<tr><td>[trigonometric] math</td><td class=\"c mono\" id=\"math_trig\"></td></tr>\n\t\t<tr><td>navigator keys</td><td class=\"c mono\" id=\"navigator_keys\"></td></tr>\n\t\t<tr><td>pdf</td><td class=\"c mono\" id=\"pdf\"></td></tr>\n\t\t<tr><td>speech engines</td><td class=\"c mono\" id=\"speech_engines\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxt\">svg.disabled</span></div>\n\t\t\t&nbsp; svg</td><td class=\"c mono\" id=\"svg_enabled\"></td></tr>\n\t\t<tr><td><div class=\"ttip\"><span class=\"icon\">[ i ]</span>\n\t\t\t<span class=\"ttxtb\">javascript.options.wasm</span></div>\n\t\t\t&nbsp; wasm</td><td class=\"c mono\" id=\"wasm\"></td></tr>\n\t\t<tr><td>webdriver</td><td class=\"c mono\" id=\"webdriver\"></td></tr>\n\t\t<tr><td>window functions</td><td class=\"c mono\" id=\"window_functions\"></td></tr>\n\t\t<tr><td>window properties <sup>1</sup></td>\n\t\t\t<td class=\"mono\"><span class=\"c\" id=\"window_properties\"></span><span class=\"c\" id=\"consolestatus\"></span></td></tr>\n\t\t<tr><td>[tampered] window properties <sup>1</sup></td>\n\t\t\t<td class=\"c mono\" id=\"window_properties_tampered\"></td></tr>\n\t\t<tr><td colspan=\"2\"><span class=\"normal\"><span class=\"no_color\">code by </span>\n\t\t\t<a target=\"_blank\" class=\"blue\" href=\"https://github.com/abrahamjuliot/creepjs\">CreepJS</a> <sup>1</sup>\n\t\t\t</span></td></tr>\n\t</table>\n\n\t<!--perf-->\n\t<table id=\"tbperf\">\n\t\t<col width=\"1%\"><col width=\"10%\"><col width=\"89%\">\n\t\t<thead><tr><th colspan=\"3\" class=\"showhide\">\n\t\t\t<span id=\"labelP\" class=\"btnb\" onClick=\"togglerows('P','perf & debugging')\">&#9660; show perf & debugging</span></th></tr></thead>\n\t\t<tr class=\"togP\"><td colspan=\"2\">global perf\n\t\t\t<br><span class=\"btnb mono no_color\" onClick=\"output_perf('all',true)\">[<span id=\"perfGBtn\">more</span>]</span>\n\t\t\t</td><td class=\"mono spaces gc\" id=\"perfG\"></td></tr>\n\t\t<tr class=\"togP\"><td colspan=\"2\">click perf\n\t\t\t<br><span class=\"btnb mono no_color\" onClick=\"output_perf('x',true)\">[<span id=\"perfSBtn\">more</span>]</span>\n\t\t\t</td><td colspan=\"2\" class=\"mono spaces gc\" id=\"perfS\"></td></tr>\n\t</table>\n\t<a name=\"theend\" id=\"theend\"></a><br>\n\t</span> <!--end tzpcontent-->\n\t<div id='blockmsg' style=\"display: none;\"></div><!--block-->\n\n\t<!--overlay-->\n\t<div id=\"modaloverlay\" onClick=\"metricsAction('close')\"></div>\n\t<div id=\"overlay\" class='mono'>\n\t\t<div id=\"overlaytop\">\n\t\t\t<span id=\"metricsTitle\"></span>\n\t\t\t<span id=\"overlaybuttons\">\n\t\t\t\t<span class='btn0 btnc' onClick=\"metricsAction('download')\" id=\"metricDownload\">[DOWNLOAD]</span>\n\t\t\t\t<span class='btn0 btnc' onClick=\"metricsAction('console')\" id=\"metricsConsole\">[CONSOLE]</span>\n\t\t\t\t<span class='btn0 btnc' onClick=\"copyclip('metricsDisplay')\" id=\"metricsBtnCopy\"><abbr title=\"Ctrl+C\">[COPY]</abbr></span>\n\t\t\t\t<span class='btn0 btnc' onClick=\"metricsAction('close')\"><abbr title=\"Esc\">[CLOSE]</abbr></span>\n\t\t\t\t<br><br>\n\t\t\t\t<div id=\"metricOptions\"><br><p><u>FORMAT</u></p>\n\t\t\t\t\t<p id=\"optDetail\">detail <input type=\"radio\" name=\"optOverlay\" id=\"optFormat_detail\" value=\"_detail\" checked onchange=\"metricsAction()\"></p>\n\t\t\t\t\t<p>summary <input type=\"radio\" name=\"optOverlay\" id=\"optFormat_summary\" value=\"_summary\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t<p id=\"optFlat\">flat <input type=\"radio\" name=\"optOverlay\" id=\"optFormat_flat\" value=\"_flat\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t<p id=\"optList\">list <input type=\"radio\" name=\"optOverlay\" id=\"optFormat_list\" value=\"_list\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t<span id=\"groupHealth\">–––<p>all <input type=\"radio\" name=\"optHealth\" checked id=\"healthAll\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t\t<p><span id=\"overlay_tick\" class=\"good\">✓ </span><input type=\"radio\" name=\"optHealth\" id=\"healthPass\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t\t<p><span id=\"overlay_cross\" class=\"bad\">✗ </span><input type=\"radio\" name=\"optHealth\" id=\"healthFail\" onchange=\"metricsAction()\"></p>\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</span>\n\t\t</div>\n\t\t<div id=\"overlaycontent\">\n\t\t\t<span id=\"metricsDisplay\" class=\"spaces\"></span>\n\t\t\t<br><br><div id=\"overlayInfo\" class='faint spaces'></div>\n\t\t</div>\n\t\t<div id=\"overlaykit\" class=\"hidden\"></div>\n\t</div>\n\n\t</span> <!-- end translate -->\n\t</div> <!-- end tzpBody -->\n\n\t<script src=\"js/prototypeLies.js\"></script>\n\t<script src=\"js/region.js\"></script>\n\t<script src=\"js/fonts.js\"></script>\n\t<script src=\"js/screen.js\"></script>\n\t<script src=\"js/storage.js\"></script>\n\t<script src=\"js/devices.js\"></script>\n\t<script src=\"js/audio.js\"></script>\n\t<script src=\"js/css.js\"></script>\n\t<script src=\"js/elements.js\"></script>\n\t<script src=\"js/misc.js\"></script>\n\t<script src=\"js/codecs.js\"></script>\n\t<script src=\"js/canvas.js\"></script>\n\t<script src=\"js/webgl.js\"></script>\n\t<script src=\"js/iframes.js\"></script>\n\t<script src=\"js/user.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "tzpiframe.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n<title>TZP iframed</title>\n<style>\nbody {background-color: white; color: black;}\n.vwh {\n\theight: 100vh;\n\twidth: 100vw;\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tborder: 0px;\n}\n</style>\n</head>\n<body>\n\n<iframe id=\"iframe\" class=\"vwh\"></iframe>\n\n<script>\n'use strict';\ntry {\n\tlet iframe = document.getElementById('iframe')\n\tiframe.src='tzp.html'\n} catch(e) {console.log(e)}\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "xml/xmlunstyled.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><a>a</a>\n"
  },
  {
    "path": "xml/xslterror.xml",
    "content": "<?xml version=\"1.0\"?><?xml-stylesheet type=\"text/xsl\" href=\"\"?><a>a</a>\n"
  }
]