PageSourceSearch

https://qacounter.personyze.com/stat-track-lib.js

js personyze.com collected 2026-09-25 14:58:43 UTC 567,680 bytes, 16,167 lines download raw bytes

1(function(){ // begin ns
2const NEW_ELEM_NS_VAR_NAME = null;
3const NEW_ELEM_NS_NAME = '_S_T';
4if (!self[NEW_ELEM_NS_NAME]) self[NEW_ELEM_NS_NAME] = {};
5const NS = self[NEW_ELEM_NS_NAME];
6const NEW_ELEM_NDEBUG = true;
7const NEW_ELEM_OS = 'default';
8const NEW_ELEM_AGENT = 'default';
9
10const CONF__PARTIAL_BOOTSTRAP_CLASSES = "S-0T S-1T S-2T S-3T S-4T";
11const CONF__PARTIAL_BOOTSTRAP_CLASSES_WEAK = "S-0T S-1T S-2T S-3T"; // remove one class to make selectors weaker
12const CONF__BIG_SCREEN_WIDTH = 600;
13const CONF__COUNTER_WWW_HOSTNAME = "qacounter.personyze.com";
14const CONF__COUNTER_WWW_HOSTNAME_NO_SERVER_CACHE = "qacounter.personyze.com";
15const CONF__COUNTER_WWW_HOSTNAME_EXTERNAL_MEDIA = "qapic.personyze.com";
16const CONF__GUI_HOST = "qa.personyze.com";
17const CONF__USER_AGENT_HTML_ID_DOCUMENT = "s7i9fh";
18const CONF__MIN_HTML_ID = -43;
19const CONF__USER_AGENT_CSS_NOT = "*[class^='cke_']";
20const CONF__LIVE_PROXY_URL = "\/\/qa.personyze.com\/live\/proxy.php";
21const CONF__CLIENT_OVERRIDES_PARAM_NAME = "_S_T";
22const CONF__CLIENT_OVERRIDES_PARAM_NAME_2 = "personyze";
23const CONF__SITE_DB_ITEM_STATUSES = ["viewed","delivered","extra","favorite","goal"];
24const CONF__SITE_DB_ITEM_STATUSES_TITLES = {"products":["Shown","Confirmed view","Added to cart","Added to favorites","Purchased"],"articles":["Shown","Confirmed view","Commented","Liked","Reached goal","Delivered"]};
25const CONF__SOCIAL_FIELDS = ["custom_i_1","custom_i_2","custom_i_3","custom_i_4","custom_f_1","custom_f_2","custom_t_1","custom_t_2","custom_t_3","custom_t_4","custom_t_5","custom_t_6","custom_d_1","custom_d_2","fb_id","first_name","last_name","email","sex","birthday","timezone","current_city","current_state","current_country","hometown_country","website","interests","birth_year_from","birth_year_to","phone","opted_in","conversion_last","conversion_history","last_interest","latitude","longitude","postal_code","industry","account_type","lead_source","first_click_id"];
26const CONF__PRODUCT_STATS_LAST_GOAL_USER_REQUIRE_SOCIAL_FIELDS = ["first_name"];
27const CONF__STATE_LIMIT = 64;
28const CONF__SESSION_FINAL_EXPIRE_TIME = 5400;
29const CONF__DOT_IMAGE_SRC = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
30const CONF__HTML_CUSTOMIZER_OPTIMAL_LIMIT = 5;
31const CONF__HTML_CUSTOMIZER_MAX_SHOW_LIMIT = 5;
32const CONF__HTML_CUSTOMIZER_MAX_LIMIT = 100;
33const CONF__IMPORT_ROWS_FIRST_PHASE_VOLUME = 0.3;
34const CONF__WEBPUSH_PUBLIC_KEY = "BDJAHUDJ\/vrFTXMfGDsoQqtyEfKh9BqWzpS+JDo2lGAeK75y7dYvwirc0fdqGpUrpSPiBv3OwSCnFYfMXLcBIHI=";
35const CONF__EXTERNAL_MEDIA_IMAGE_TYPES = {"png":"PNG (transparent)","jpeg":"JPEG"};
36const CONF__PRODUCT_STATS_N_LAST_GOAL_USERS = 3;
37const CONF__PERSONYZE_COMMANDS_JS_VAR_NAME = "personyze";
38const CONF__GUI_TEXT_TRANSLATION_ATTRS = ["title","label","alt","placeholder"];
39const CONF__STAT_TRACK_LIB_JS = "\/\/qacounter.personyze.com\/stat-track-lib.js";
40const CONF__INTEREST_TITLE = {"interest":"Interest","category":"Category","tag":"Tag","family":"Family","cross category":"Cross-sell category","upsale category":"Upsell category"};
41const CONF__INTEREST_TITLES = {"interest":"Interests","category":"Categories","tag":"Tags","family":"Families","cross category":"Cross-sell categories","upsale category":"Upsell categories"};
42const CONF__INTEREST_LAST_EVENT_NAMES = {"interest":"Interests","category":"Categories","tag":"Tags","family":"Families","cross category":"Cross-sell categories","upsale category":"Upsell categories"};
43const CONF__SESSION_EXPIRE_TIME = 5400;
44const CONF__EVENT_SEND_MODE = ["page_events","mouse over event","grab url","placeholder (no)","focus","grab meta tag","placeholder","grab html","placeholder (extra)","placeholder (closed)","link_in","text input","text input (blur)","click event","click event page","load event","grab volatile html","grab cookie","placeholder (goal)","custom event","social","log interaction","log interest","fire action","grab ga","grab gtm","grab integration","log item delivery"];
45const CONF__EVENTS_REMAIN_IN_COOKIE_FOR_N_MSECS = 5000;
46const CONF__IDENTITY_PROVIDERS = {"auth0":{"title":"Auth0","logo":"auth0-logo.svg"},"xecurify":{"title":"","logo":"xecurify-139x37.png"},"onelogin":{"title":"","logo":"onelogin-logo.svg"},"okta":{"title":"","logo":"okta-logo.svg"}};
47const CONF__GOOGLE_SSO_CLIENT_ID = "768289160396-ts0ee5f34kr5nkcdrj3cbnrl5jfhiq2e.apps.googleusercontent.com";
48const CONF__MICROSOFT_SSO_CLIENT_ID = "0851e243-6c5e-4fc5-a0c4-2dd6cfd315f4";
49const CONF__JSAPI_DELAY_TO_ACCUMULATE_QUERIES = 0;
50const CONF__ACCOUNT_FEATURES = {"sdk":"Mobile App Personalization","ab-wizard":"A\/B testing","products":"E-commerce recommendations","articles":"Content recommendations","email":"Email Personalization (send email from Personyze)","external-media":"Email Personalization for third party email templates","webpush":"Push notification","crm":"CRM targeting (the CRM wizard)"};
51const CONF__DEFAULT_PLACEHOLDER_ORDER = 999;
52
53const CONF__SPINNER = `<!--By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL--><svg width="100" height="100" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#000"><g fill="none" fill-rule="evenodd"><g transform="translate(1 1)" stroke-width="2"><circle stroke-opacity=".5" cx="18" cy="18" r="18"/><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/></path></g></g></svg>`;
54const CONF__SPINNER_COLOR_REPLACE_FROM = '#000';
55const CONF__SPINNER_WIDTH_REPLACE_FROM = ' width="100"';
56const CONF__SPINNER_HEIGHT_REPLACE_FROM = ' height="100"';
57const CONF__SPINNER_ADD_PADDING_REPLACE_FROM = 'viewBox="0 0 38 38"';
58const CONF__SPINNER_ADD_PADDING_REPLACE_TO = 'viewBox="-20 -20 78 78"';
59
60const CONF__ACTIONS_EXTRA_NOT_CLICK_STR = `1504958690,1622875429,492188746,3568666300,3901546772,2013142013`;
61const CONF__ACTIONS_RECOM_STR = `1504958690,1393331489,2704108379,4240931881,2270359400,3978514375,2528431140,2013142013,4216680521`;
62const CONF__ACTIONS_RECOM_NONFORM_STR = `1393331489,2704108379,4240931881,2270359400,3978514375,2528431140,4216680521`;
63const CONF__ACTIONS_RECOM_FORM_STR = `1504958690,2013142013`;
64const CONF__ACTIONS_OFFLINE_STR = `951527869,886063759,1335662688,3949927194,1273336257,1199766259,3298344050`;
65const CONF__ACTIONS_EXTERNAL_MEDIA_STR = `2533288044,2593751902,1869681987`;
66const CONF__ACTION_TYPES =
67{	"Live": {id: 4080894865},
68"Popup Trigger Part": {id: 902801468},
69};
70const CONF__ACTIONS_WITHOUT_BLANK =
71{	'1504958690': true,'2704108379': true,'2533288044': true,'2593751902': true,'492188746': true,'2672959221': true,'3901546772': true,'2270359400': true,'3978514375': true,'2528431140': true,'2013142013': true,'4216680521': true};
72const CONF__DEBUGGER_REDIR_URL_Q = "\/debugger\/redir.tsx?";
73const CONF__COLORS_STOCK = ["#E9586E","#2061B2","#FF9900","#920092","#008CB5","#DD4477","#66AA00","#316395","#b82e2e","#22aa99"];
74const CONF__GUI_NO_YOUTUBE_GUIDES = false;
75const CONF__HOST_ROLES = ["tracker","archive","slave","master"];
76const CONF__REFERRER_TYPE_TYPE = ["regular referrer","direct","organic search","commercial search","internal search","commercial","internal","social","general affiliate network","contextual affiliate network","contextual ads","contextual ads search","index","bookmarking","marketing","review","blog","media","government"];
77const CONF__AB_TEST_USE_GAMMA = false;
78const CONF__GOOGLE_MAPS_APP_ID_FOR_SCREENSHOTS = "";
79const CONF__CALC_SUMMARY_FOR_N_GOAL_EVENTS = 12;
80const CONF__SITE_DB_INTERACTION_STATS_FOR_PERIOD = ["all","recently","week","4day","2day","1day"];
81const CONF__PAYMENT_CURRENCY_FORMAT = "* \u20aa";
82const CONF__PAYMENT_CURRENCY_FORMAT_PREFIX = "";
83const CONF__PAYMENT_CURRENCY_FORMAT_SUFFIX = " \u20aa";
84const CONF__ACTION_SCREENSHOT_DIV_WIDTH = 320;
85const CONF__ACTION_SCREENSHOT_DIV_HEIGHT = 240;
86
87const CONF__CAMPAIGNS_MENUS =
88{	all:
89	[	{	text: 'Web Browser',
90			icon: 'desktop',
91			options:
92			[	{"value":"actions","text":"Targeting and Personalization","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/actions.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/targeting-and-personalization-2","title":"Tailor banners, popups, and on-site messages based on over 70 visitor traits.","condition_type":"tracker"},
93{"value":"ab","text":"A\/B Testing","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/ab.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/a-b-testing","title":"Test pages, banners, or promotions and automatically select the winning variation.","condition_type":"tracker"},
94{"value":"products_recom","text":"Product Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/product-recommendations-wizard","title":"Recommend products tailored to each visitor\u2019s interests, enhanced by aggregate trends and dynamic filters.","condition_type":"tracker"},
95{"value":"articles_recom","text":"Content Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/articles_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/content-recommendations-wizard","title":"Recommend relevant articles to each visitor based on their interests, popular content, and smart targeting filters.","condition_type":"tracker"}			]
96		},
97		{	text: 'Email',
98			icon: 'envelope',
99			options:
100			[	{"value":"offline_email","text":"Email Personalization","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/personalized-re-targeting-emails\/","title":"Send automated, personalized emails with dynamic content and AI-based recommendations. Broadcast directly or integrate with your third-party email provider.","condition_type":"offline"},
101{"value":"external_media_products_recom","text":"Add Product Recommendations to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML code to embed Personyze recommendations into third-party email templates, such as shipping confirmations or invoices.","condition_type":"offline"},
102{"value":"external_media_articles_recom","text":"Add Content Recommendations to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML code to embed Personyze content recommendations into third-party email templates, such as newsletters or system alerts.","condition_type":"offline"},
103{"value":"external_media","text":"Add Personalized Banners to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML snippets to embed personalized banners directly into your emails.","condition_type":"offline"}			]
104		},
105		{	text: 'Application/API',
106			icon: 'mobile',
107			options:
108			[	{"value":"sdk","text":"Targeting and Personalization for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/actions_app.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/targeting-and-personalization-2","title":"Deliver personalized banners, popups, and messages within your Android\/iOS app using behavioral data and 70+ visitor attributes.","condition_type":"tracker"},
109{"value":"products_recom_sdk","text":"Product Recommendations for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/product-recommendations-wizard","title":"Deliver tailored product recommendations in your Android\/iOS app using a mix of user interests, crowd trends, and custom logic.","condition_type":"tracker"},
110{"value":"articles_recom_sdk","text":"Content Recommendations for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/articles_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/content-recommendations-wizard","title":"Deliver personalized article recommendations in your Android\/iOS app by combining user interests, crowd behavior, and smart filtering.","condition_type":"tracker"},
111{"value":"external_json_products_recom","text":"JSON API Product Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom_json.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-for-third-party-applications","title":"Enable your application to fetch personalized product recommendations from Personyze via JSON API.","condition_type":"offline"},
112{"value":"external_json_articles_recom","text":"JSON API Content Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom_json.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-for-third-party-applications","title":"Fetch personalized content recommendations from Personyze using a simple JSON API query.","condition_type":"offline"}			]
113		},
114		{	text: 'Web Push',
115			icon: 'bell-o',
116			options:
117			[	{"value":"webpush_subscribe","text":"Web Push Subscription","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/how-to-send-web-push-notifications-with-personyze\/","title":"Set up a popup or banner prompting visitors to subscribe to your site's push notifications.","condition_type":"tracker"},
118{"value":"webpush","text":"Send Web Push","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/how-to-send-web-push-notifications-with-personyze\/","title":"Create and send targeted push notifications to your subscribed visitors.","condition_type":"offline"}			]
119		}
120	]
121};
122
123const CONF__CONDBUILDER_MENUS =
124{			tracker:
125		[	{	text: 'Visitor context & attributes',
126				icon: 'star',
127				options:
128				[	{"value":"pages","text":"Pages Visited","icon":"files-o","title":"Target visitors who are currently viewing, or have viewed, specific pages during this session.\n\t\t\tExample: Full URL equals \"http:\/\/www.website.com\/\" or URL parameter \"Plan\" equals \"Free Trial\"."},
129{"value":"countries_cities","text":"Countries\/Cities","icon":"map-marker","title":"Target visitors located in specific countries, regions, or cities."},
130{"value":"user_profile","text":"User CRM\/ABM\/Profile data","icon":"id-card-o","title":"Target visitors based on CRM or profile data (demographics, company, account type, etc.). Requires configuration in Settings &gt; Visitor Attributes.\n\t\t\t<a href=\"https:\/\/wiki.personyze.com\/knowledgebase\/crm-integration\/\">Learn more<\/a>"},
131{"value":"user_lists","text":"User Lists (profile data)","icon":"list-alt","title":"Target specific users by uploading a list (Spreadsheet, CSV, RSS, etc.)."},
132{"value":"system","text":"Device and System","icon":"desktop","title":"Target visitors based on their system specifications (Device, OS, Browser, etc.)."},
133{"value":"visit_type","text":"Visitor Type","icon":"user","title":"Target specific visitor types, such as new visitors, returning visitors, or those who have visited the site a specific number of times."},
134{"value":"date_time","text":"Date and Time","icon":"calendar","title":"Trigger the campaign based on specific dates and times. Configure date ranges, days of the week\/month, and select either visitor-local or fixed time zones."},
135{"value":"container_last_value","text":"Session Attributes","icon":"database","title":"Target visitors based on real-time data extracted from the page, such as cookies, variables, or form inputs (e.g., Cart Value &gt; $100)."}				]
136			},
137			{	text: 'Behavior & engagement',
138				icon: 'star',
139				options:
140				[	{"value":"site_db_products_many","text":"E-Commerce","icon":"shopping-cart","title":"Target based on what products the visitor viewed, added to cart, or bought. You can utilize all product attributes, such as product categories or brands.\n\t\t\tExample: Include visitors who had Apple products worth more than $1000 in their cart in the last 7 days.\n\t\t\t(Requires Personyze product interaction tracking)."},
141{"value":"site_db_articles_many","text":"Content Interaction","icon":"newspaper-o","title":"Target based on content interactions (views, favorites, subscriptions, etc.). Example: Target visitors who read at least 3 articles by \"John Smith\" in the last 30 days. (Requires Personyze content interaction tracking)."},
142{"value":"time_total","text":"Time on Site","icon":"clock-o","title":"Target visitors based on the total time spent on the site. Note: Checks occur periodically, so activation may not be precise to the second."},
143{"value":"landing_pages","text":"Landing Pages","icon":"paper-plane","title":"Target visitors based on their landing page (the first page they visited in the current session). You can include or exclude visitors who arrived via specific URLs, paths, or parameters."},
144{"value":"referrers","text":"Traffic Sources","icon":"bus","title":"Target visitors arriving from specific referring websites. Note: Referrer data may be unavailable when navigating from HTTPS to HTTP sites."},
145{"value":"first_source","text":"First Source","icon":"globe","title":"<p><font color=\"brown\">Where the visitor came from the <b>first time they were ever seen<\/b> &#x2014; not this session.<\/font><\/p><p>\n\t\t\t\t\"Traffic Sources\", \"Keywords\" and \"Ad campaigns\" all describe the visit happening now. This describes the visit that first brought the person to the site, recorded once on their first ever page view and never overwritten afterwards.\n\t\t\t<\/p><p><strong>For Example:<\/strong> a visitor who first arrived from a Google ad, left, and came back a week later by typing your domain reads as \"direct\" to every other rule and as <i>Paid: Google Ads<\/i> to this one. That is the visit the ad platform is billing you for.\n\t\t\t<\/p>"},
146{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Interests. You need to set up Interests in <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20interests\">recommendation settings<\/a>. To send information about visitor's interests, use containers with \"Store to social table\" set to \"Interest\". Or use client API."},
147{"value":"actions_executed","text":"Personyze Actions State","icon":"gears","title":"Target based on visitor interactions with campaign content during the current session.\n\t\t\tExample: Exclude visitors who clicked on the \"Campaign 7 banner\"."},
148{"value":"conditions_matched","text":"Personyze Campaigns Matched","icon":"bullhorn","title":"Target visitors who have matched specific Personyze campaigns during the current session. Example: Include only visitors who matched the \"Signup from PPC\" campaign."},
149{"value":"network","text":"IP addresses","icon":"hdd-o","title":"Target specific IP addresses or network ranges (CIDR)."},
150{"value":"page_views","text":"Number of Page Views","icon":"eye","title":"The number of documents viewed in the current session. Each PersonyzeTracker.inst.navigate() call is considered one document view."},
151{"value":"page_views_count","text":"Views of Specific Pages","icon":"files-o","title":"<p>\n\t\t\t\tTarget visitors who have visited specific pages more (or less) frequently than other pages on the site.\n\t\t\t<\/p>"},
152{"value":"page_popularity","text":"Most Popular Page","icon":"heart","title":"Target visitors based on their most frequently visited pages across all sessions."},
153{"value":"keywords_v2","text":"Keywords","icon":"search","title":"<p>Target based on Search Keywords. Note: This primarily applies to commercial (PPC) keywords, as search engines often encrypt organic keyword data.<\/p>"},
154{"value":"landing_url_params_v2","text":"Ad campaigns (\"utm\")","icon":"bullseye","title":"<p><font color=\"brown\">Matches parameters on the <b>landing page<\/b> (the first page of the session).<\/font><\/p><p>
154\n\t\t\t\tTarget Campaign Data or Keywords found in the URL (e.g., UTM parameters from Google Ads, custom parameters).\n\t\t\t<\/p><p><strong>For Example:<\/strong> A visitor arrived at your homepage from a specific Google Adwords Campaign, is redirected to the most relevant product page\n\t\t\t<\/p>"},
155{"value":"weather_all","text":"Weather","icon":"sun-o","title":"Target visitors based on the local weather forecast (determined by IP\/Network location or GPS)."},
156{"value":"rotation","text":"Limit Audience","icon":"pie-chart","title":"Limit the campaign's deployment to a random percentage of visitors, sessions, or page views. This rule is designed to control audience volume (e.g., for gradual rollouts) and is not intended for A\/B testing."}				]
157			},
158			{	text: 'Integrations',
159				icon: 'star',
160				options:
161				[	{"value":"tealium","text":"Tealium Audiences","icon":"bullhorn","title":"<p>\n\t\t\t\tTarget based on Tealium Audiences and Badges (Requires Tealium Integration).\n\t\t\t<\/p>"},
162{"value":"hubspot","text":"Hubspot integration","icon":"bullhorn","title":"Target based on Hubspot data (Account Type, Funnel Stage, etc.). Requires Hubspot Integration."},
163{"value":"leadfeeder","text":"Leadfeeder integration","icon":"bullhorn","title":"Target based on Leadfeeder ABM data (Company Name, Industry, etc.) via reverse IP lookup. Requires Leadfeeder Integration."},
164{"value":"sixsense","text":"6sense integration","icon":"bullhorn","title":"Target based on 6sense ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires 6sense Integration."},
165{"value":"leadrebel","text":"LeadRebel integration","icon":"bullhorn","title":"Target based on LeadRebel ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires LeadRebel Integration."},
166{"value":"clearbit","text":"Clearbit integration","icon":"bullhorn","title":"Target based on Clearbit ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires Clearbit Integration."},
167{"value":"albacross","text":"Albacross integration","icon":"bullhorn","title":"Target based on Albacross ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires Albacross Integration."},
168{"value":"zoominfo","text":"ZoomInfo integration","icon":"bullhorn","title":"Target based on ZoomInfo B2B data (Company Name, Industry, Revenue, Employee Count, etc.) via reverse IP lookup. Requires ZoomInfo Integration."}				]
169			}
170		],
171		offline:
172		[	{	text: 'Add targeting rules:',
173				icon: 'star',
174				options:
175				[	{"value":"social","text":"User Profile Data","icon":"id-card-o","title":"Include or exclude visitors based on persistent profile attributes (e.g., demographics, account type) synced from CRM or other external sources."},
176{"value":"date","text":"Date Range (Active From\/To)","icon":"calendar","title":"Specify the date range during which this audience is active."},
177{"value":"time_since_last_visit","text":"Time Since Last Visit","icon":"calendar","title":"Target users based on the duration elapsed since their last session.\n\t\t\t<p><b> Example: <\/b> Target visitors who haven't visited the website for over a month. <\/p>"},
178{"value":"session_counter","text":"Total Session Count","icon":"refresh","title":"The total number of sessions tracked for this visitor.\n\t\t\t<p><b> Example: <\/b> Target visitors who have browsed the website more than 5 times. <\/p>"},
179{"value":"site_db_products_many","text":"Product Interactions (E-Commerce)","icon":"shopping-cart","title":"<p>Target users based on specific product interactions (viewed, added to cart, purchased).<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20products\" target=\"_blank\">containers<\/a>.<\/p>"},
180{"value":"site_db_articles_many","text":"Content Interactions (Articles)","icon":"newspaper-o","title":"<p>Target users based on article viewing history.<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20articles\" target=\"_blank\">containers<\/a>.<\/p>"},
181{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Target users based on interests derived from content consumption or CRM data. Ensure interests are configured in <a href=\"\/panel#cat=Account%20settings%2FRecommendations%2FInterests\">recommendation settings<\/a>."},
182{"value":"actions_executed","text":"Actions executed","icon":"gears","title":"Target users who have previously been exposed to specific campaigns or actions."}				]
183			}
184		],
185		page_group:
186		[	{"value":"pages","text":"Page Visits","icon":"files-o","title":"Match users based on specific page visits."},
187{"value":"device_type","text":"Device Type","icon":"mobile-phone","title":"Target users based on their device type (Desktop, Tablet, or Mobile)."}		],
188		sdk:
189		[	{	text: 'Add targeting rules:',
190				icon: 'star',
191				options:
192				[	{"value":"behavior","text":"Behavioral and demographic","icon":"street-view","title":"Target visitors based on their visit frequency, timing, page view count, geographic location, or GPS coordinates."},
193{"value":"pages_sdk","text":"Documents","icon":"files-o","title":"Target based on documents the user is viewing or has viewed during the session (via PersonyzeTracker)."},
194{"value":"social","text":"User CRM\/ABM\/Profile data","icon":"id-card-o","title":"Target visitors based on CRM or profile data (e.g., demographics, company, account type). Requires configuration in <a href=\"https:\/\/personyze.com\/panel\/id\/#cat=Account%20settings%2FCRM%20integration%2FSite%20dynamic%20data\/newcondition\">Settings &gt; Visitor Attributes<\/a>. Data can be sourced from CRM integrations, real-time extraction, API calls, or file uploads. <a href=\"https:\/\/wiki.personyze.com\/knowledgebase\/crm-integration\/\">Learn more<\/a>"},
195{"value":"site_db_products_many","text":"E-Commerce","icon":"shopping-cart","title":"Target based on what products the visitor viewed, added to cart, or bought. You can utilize all product attributes, such as product categories or brands.\n\t\t\tExample: Include visitors who had Apple products worth more than $1000 in their cart in the last 7 days.\n\t\t\t(Requires Personyze product interaction tracking)."},
196{"value":"site_db_articles_many","text":"Content Interaction","icon":"newspaper-o","title":"Target based on content interactions (views, favorites, subscriptions, etc.). Example: Target visitors who read at least 3 articles by \"John Smith\" in the last 30 days. (Requires Personyze content interaction tracking)."},
197{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Interests. You need to set up Interests in <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20interests\">recommendation settings<\/a>. To send information about visitor's interests, use containers with \"Store to social table\" set to \"Interest\". Or use client API."},
198{"value":"actions_executed","text":"Personyze Actions State","icon":"gears","title":"Target based on visitor interactions with campaign content during the current session.\n\t\t\tExample: Exclude visitors who clicked on the \"Campaign 7 banner\"."},
199{"value":"conditions_matched","text":"Personyze Campaigns Matched","icon":"bullhorn","title":"Target visitors who have matched specific Personyze campaigns during the current session. Example: Include only visitors who matched the \"Signup from PPC\" campaign."},
200{"value":"system","text":"Device and System","icon":"desktop","title":"Target visitors based on their system specifications (Device, OS, Browser, etc.)."},
201{"value":"network","text":"IP addresses","icon":"hdd-o","title":"Target specific IP addresses or network ranges (CIDR)."},
202{"value":"weather_all","text":"Weather","icon":"sun-o","title":"Target visitors based on the local weather forecast (determined by IP\/Network location or GPS)."}				]
203			}
204		],
205		alert:
206		[	{	text: "Add rules",
207				options:
208				[	{"value":"n_executed","text":"Executions","icon":"","title":"Total times the action was executed or the audience matched."},
209{"value":"bounce_rate","text":"Bounce Rate","icon":"","title":"Average bounce rate over the peri
209od.\n\t\t\t<p><strong> Example: <\/strong> Less than 60% <\/p>"},
210{"value":"conversion_rate","text":"Conversion Rate","icon":"","title":"Average conversion rate over the period."},
211{"value":"ctr","text":"Click-Through Rate (CTR)","icon":"","title":"Average CTR over the period."},
212{"value":"n_sessions","text":"Visits (Sessions)","icon":"","title":"Total visits (sessions) where the audience matched or the action executed."},
213{"value":"n_sessions_extra","text":"Sessions with Engagement (Clicks\/Actions)","icon":"","title":"Number of sessions involving clicks or form submissions."},
214{"value":"n_times","text":"Reach (Impressions)","icon":"","title":"Total times the action was shown or the audience matched."},
215{"value":"n_visits_per_session","text":"Average Page Views per Visit","icon":"","title":"Average number of page views per visit (how many pages a single visitor viewed).\n\t\t\tThis number will be tested for each given time period."},
216{"value":"time","text":"Date","icon":"","title":"Trigger alert on a specific date."},
217{"value":"bounce_rate_cmp","text":"Bounce Rate Comparison","icon":"","title":"Compare the bounce rate of two actions or audiences."},
218{"value":"conversion_rate_cmp","text":"Conversion Rate Comparison","icon":"","title":"Compare the conversion rates of two actions or audiences."},
219{"value":"ctr_cmp","text":"Click-Through Rate (CTR) Comparison","icon":"","title":"Compare the CTR of two actions or audiences."},
220{"value":"n_sessions_cmp","text":"Visits (Sessions) Comparison","icon":"","title":"Compare the visit counts of two actions or audiences."},
221{"value":"n_sessions_extra_cmp","text":"Engagement Comparison","icon":"","title":"Compare the number of clicks between two actions or audiences."},
222{"value":"n_times_cmp","text":"Impression Comparison","icon":"","title":"Compare the impression counts of one action or audience against another."},
223{"value":"n_visits_per_session_cmp","text":"Average Page Views Comparison","icon":"","title":"Compare the average page views per visit between two actions or audiences."}				]
224			}
225		],
226		report_users:
227		[	{	text: "Add rules",
228				options:
229				[	{"value":"days_till_birthday","text":"Days Until Birthday","icon":"birthday-cake","title":"Days remaining until the visitor's birthday. Zero means today, 1 means tomorrow, etc. <p> Requires Facebook Integration. <\/p><p><a href=\"https:\/\/wiki.personyze.com\/audience-targeting\/actions-extended-page\/action-facebook-connect\/\" target=\"_blank\">Read more.<\/a><\/p><p><b> Example: <\/b> Target visitors whose birthday is within 7 days to offer a special \"Happy Birthday\" gift. <\/p>"},
230{"value":"date","text":"Date Range (Active From\/To)","icon":"calendar","title":"Specify the date range during which this audience is active."},
231{"value":"email","text":"User's Email","icon":"envelope","title":"The visitor's email address. <p> Requires Facebook Integration or CRM sync. <\/p><p><a href=\"https:\/\/wiki.personyze.com\/audience-targeting\/actions-extended-page\/action-facebook-connect\/\" target=\"_blank\">Read more.<\/a><\/p><p><b> Example: <\/b> The visitor's email contains \"@microsoft.com\". <\/p>"},
232{"value":"time_since_last_visit","text":"Time Since Last Visit","icon":"calendar","title":"Time elapsed since the visitor's previous session.\n\t\t\t<p><b> Example: <\/b> Target visitors who haven't visited the website for over a month. <\/p>"},
233{"value":"session_counter","text":"Total Session Count","icon":"refresh","title":"The total number of sessions tracked for this visitor.\n\t\t\t<p><b> Example: <\/b> The visitor has browsed the website 5 times since their first visit. <\/p>"},
234{"value":"actions","text":"Campaign Actions History","icon":"gears","title":"Target users who have been served specific campaign actions (e.g. banners shown, emails sent)."},
235{"value":"actions_executed","text":"Detailed Campaign History","icon":"gears","title":"Target users based on detailed campaign execution logs (e.g., specific emails delivered or banners viewed)."},
236{"value":"user_products_interaction","text":"Product Interaction History","icon":"shopping-cart","title":"Target users based on their history with products (Views, Cart, Purchases, or Wishlist)."},
237{"value":"site_db_products_many","text":"Product Interactions (E-Commerce)","icon":"shopping-cart","title":"<p>Target users who viewed, added to cart, or purchased products.<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20products\" target=\"_blank\">containers<\/a>.<\/p>"},
238{"value":"subst_user_data_exists","text":"Recommendation Results Exist","icon":"","title":"Target users for whom the selected recommendation algorithm returns valid results."},
239{"value":"social","text":"User Profile Data","icon":"id-card-o","title":"Target visitors based on persistent profile attributes (e.g., demographics, account type) synced from CRM or external sources."},
240{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Target users based on interests derived from content consumption or CRM data. Note: Interests must be configured in <a href=\"\/panel#cat=Account%20settings%2FRecommendations%2FInterests\">recommendation settings<\/a>."},
241{"value":"date_column_updated","text":"Date Column Updated","icon":"","title":"Target users based on when a specific database column was last updated."}				]
242			}
243		]
244	};
245
246const CONF__ACTIONS_MENU_TRACKER =
247[	['Most popular', 83635035,4080894865,3417805607,40972697,2704108379,4216680521,1623025020,492188746],
248	['Change page content or look', 4080894865,83635035],
249	['Find and replace content', 1623025020],
250	['Integration with other systems', 1170812618,3890590501],
251	['Recommendations', 4216680521,2704108379,4240931881,1393331489,3978514375,2528431140],
252	['Forms, remarketing emails', 492188746,2013142013,1504958690,642995056,3648728633],
253	['Advanced', 3417805607,2613519016,981138557],
254	//['New actions', 3308639366],
255];
256const CONF__ACTIONS_MENU_OFFLINE =
257[	['Select action', 3949927194,886063759,951527869,2773691522,3162034379,3298344050,1199766259,1273336257]
258];
259const CONF__ACTION_JS_IDS_TRACKER_DEV = [804653952,2120610408,764541747,3167387044,3473854072,2672959221];
260const CONF__ACTION_JS_IDS_OFFLINE_DEV = [];
261const CONF__ACTION_JS_IDS_SDK_FILTER = [83635035,4216680521,4240931881,2704108379,1170812618];
262const CONF__ACTION_JS_IDS_TRACKER_DEFAULT = [4080894865,83635035,492188746,3417805607];
263const CONF__ACTION_JS_IDS_ALERT = [3987952863,2557502939,1170812618];
264const CONF__ACTION_JS_IDS_HTML_NO_CLICK = [1504958690,1748090321,2704108379,1622875429,4294311668,492188746,2672959221,1792104046,83635035,1143393089,3568666300,4080894865,3308639366,3901546772,1062827661,2270359400,3978514375,2528431140,2013142013,4216680521,2999110396,2120610408,40972697,2870079774,316
2647387044];
265const CONF__ACTION_JS_IDS_FORM_NO_CLICK = [1504958690,492188746,3568666300,3901546772,2013142013];
266const CONF__ACTION_JS_IDS_FB_NO_CLICK = [1622875429];
267const CONF__ACTION_JS_IDS_REPORT_USERS = [3003926892];
268const CONF__ACTION_JS_IDS_FORM = [1504958690,492188746,3568666300,3901546772,2013142013];
269const CONF__ACTION_JS_IDS_RECOM_PRODUCTS = [4240931881,3978514375,2528431140,2013142013,4216680521];
270const CONF__ACTION_JS_IDS_RECOM_ARTICLES = [1504958690,1393331489,2704108379,2270359400];
271const CONF__ACTION_JS_IDS_OFFLINE_EMAIL = [951527869,886063759,1335662688,3949927194];
272const CONF__ACTION_JS_IDS_EMAIL = [951527869,886063759,1335662688,3949927194];
273const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA = [2533288044,2593751902,1869681987];
274const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_NO_RECOM = [1869681987];
275const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_PRODUCTS = [2593751902];
276const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_ARTICLES = [2533288044];
277const CONF__ACTION_JS_IDS_HTML_CONTENT = [1504958690,1748090321,2704108379,1622875429,4294311668,492188746,2672959221,1792104046,83635035,1143393089,3568666300,4080894865,3308639366,3901546772,1062827661,2270359400,3978514375,2528431140,2013142013,4216680521,2999110396,2120610408,40972697,2870079774,3167387044];
278const CONF__ACTION_JS_IDS_RECOM_PRODUCTS_SDK = [4240931881,3978514375,2528431140,4216680521];
279const CONF__ACTION_JS_IDS_RECOM_ARTICLES_SDK = [1393331489,2704108379,2270359400];
280const CONF__ACTION_JS_IDS_OFFLINE_EMAIL_WEBHOOK = [951527869,886063759,1335662688,3949927194]; // 'webhook' tag removed: the Webhook action is client-only (no exec_archive), so offering it in offline campaigns configured an action that never ran
281const CONF__ACTION_JS_IDS_WEBPUSH_SUBSCRIBE = [3648728633];
282const CONF__ACTION_JS_IDS_WEBPUSH = [1273336257,1199766259,3298344050];
283const CONF__ACTION_JS_IDS_EXTRA_NOT_CLICK = [1504958690,1622875429,492188746,3568666300,3901546772,2013142013];
284const CONF__ACTION_JS_ID_LIVE = 4080894865;
285const CONF__ACTION_JS_ID_OFFLINE_EMAIL_WIZARD = 1335662688;
286const CONF__ACTION_JS_ID_EXTERNAL_JSON_PRODUCTS = 3281157928;
287const CONF__ACTION_JS_ID_EXTERNAL_JSON_ARTICLES = 3488945178;
288const CONF__ACTION_JS_ID_GOOGLE_MAPS = 4294311668;
289
290const CONF__CAMPAIGNS_STATS_WIDGETS = {"actions":"tracker","ab":"tracker","ai_assistant":"tracker","meetings":"tracker","social":"tracker","legacy":"tracker","products_recom":"products_recom","products_recom_sdk":"products_recom","articles_recom":"articles_recom","articles_recom_sdk":"articles_recom","products_extra":"products_extra","offline_email":"offline_email","external_media":"external_media","external_media_products_recom":"external_media_products_recom","external_media_articles_recom":"external_media","external_json_products_recom":"external_media","external_json_articles_recom":"external_media","webpush_subscribe":"webpush_subscribe","webpush":"webpush"};
291
292const CONF__TRACKER_CODE_HIDE_DESC = "\t<h3>Code that hides content during loading<\/h3>\n\t\t<p>\n\t\t\tAsynchronous version of Personyze tracking code. You should embed this code in the &lt;head> of your master page or theme. We don't recommend using this version in a tag manger.\n\t\t<\/p>\n\t\t<p>\n\t\t\tIn this version, Personyze code is loaded asynchronously with the rest of the page and detects all page elements designated as placeholders for personalization actions to potentially alter their content and makes them invisible to the visitor until the personalization content is loaded successfully. We recommend using this code when your personalized content contains big file images or editing existing homepage sliders and want to make sure that generic content is showing even for a split second.\n\t\t<\/p>\n\t";
293const CONF__TRACKER_CODE_NOHIDE_DESC = "\t<h3>Asynchronous tracking code<\/h3>\n\t\t<p>\n\t\t\tAsynchronous tracking code - Embed in the &lt;head> of your master page or use a tag manager.\n\t\t<\/p>\n\t\t<p>\n\t\t\tAsynchronous version of Personyze tracking code should be embedded in the &lt;head> of your master page or theme. You can use a tag manager. We recommend to set it to run with the highest priority.\n\t\t<\/p>\n\t";
294
295if (!NS['declare_template'])
296{	NS['template_sources'] = [];
297	NS['declare_template'] = function(name, wrap_id, html, arg_names, func)
298	{	this['template_sources'].push({name, wrap_id, html, arg_names, func});
299	}
300}
301
302// ElemsAnnotator
303function _new_elem_tmp_0(ns)
304	{	var annotated_elems = [];
305
306		function do_annotate(node, with_message, icon_name)
307		{	if (!node)
308			{	alert(with_message);
309				return;
310			}
311			var doc = node.ownerDocument || document;
312			var color = icon_name=='info' ? 'var(--color-info, #4694FB)' : icon_name=='var(--color-warning, #FACC23)' ? 'rgb(144, 152, 16)' : 'var(--color-danger, #BD362F)';
313			var dim = with_message ? null : node.cloneNode ? ns.new_elem(node).dim() : node;
314			var p_by_id = {};
315			var close_html = '<hr id="x" class="$round_x" style="float:right; font-size:9px; margin-left:1.2em; background-color:var(--color-info, #4694FB); color:var(--color-info_text, #FFF)">';
316			ns.new_elem
317			(	(	with_message ?
318					'<div id="popup" class="$popup" data-scroll_to_near_node="1" style="position:absolute; border:solid '+color+' 1px;
318 color:'+color+'" data-design="hint">'+close_html+with_message+'</div>'
319					:
320					'<div id="popup" class="$popup" data-scroll_to_near_node="1" style="position:absolute; border:solid '+color+' 2px; border-radius:15px; z-index:'+0x7FFFFFFF+'; width:'+(dim.width+8)+'px; height:'+(dim.height+8)+'px">'+close_html+'</div>'
321				),
322				null,
323				{	x:
324					{	onclick()
325						{	popup._hide({template: 'animator_fade'});
326						}
327					}
328				},
329				p_by_id
330			);
331			var popup = p_by_id.popup;
332			var is_form_tip = false;
333			// Stable hooks for site/template CSS: the tip renders into the global popup
334			// parent on document.body, so overrides must be UNSCOPED - these attributes
335			// are the only way to target it. data-form-input carries the annotated
336			// input's name; data-form the owning $cform's name (first class token).
337			popup.classList.add('pz-form-tip');
338			if (node?.getAttribute)
339			{	var input_name = node.getAttribute('data-cform-input');
340				if (input_name)
341				{	popup.setAttribute('data-form-input', input_name);
342				}
343				for (var fe=node; fe && fe.getAttribute; fe=fe.parentNode)
344				{	if (typeof(fe._show_message)=='function' && typeof(fe._validate)=='function')
345					{	is_form_tip = true;
346						var form_name = ((fe.getAttribute('class') || '').match(/^\s*(\S+)/) || [])[1];
347						if (form_name)
348						{	popup.setAttribute('data-form', form_name);
349						}
350						break;
351					}
352				}
353			}
354			if (is_form_tip && with_message)
355			{	popup.style.cssText += ';box-sizing:border-box;max-width:280px;padding:11px 13px;'+
356					'border:1px solid '+color+';border-left:4px solid '+color+';border-radius:10px;'+
357					'background:#fff;font:600 13px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;'+
358					'box-shadow:0 6px 20px rgba(15,23,42,0.16)';
359			}
360			popup._show
361			(	{template: 'animator_fade'},
362				with_message ? node :
363				{	left: dim.left - 4,
364					top: dim.top - 4
365				}
366			);
367			annotated_elems.push(popup);
368			return {elem: popup};
369		}
370
371		return class ElemsAnnotator
372		{	constructor(icon_name_or_parent_elems_annotator, show_elem_func)
373			{	var icon_name = typeof(icon_name_or_parent_elems_annotator)=='string' ? icon_name_or_parent_elems_annotator : null;
374				var parent = typeof(icon_name_or_parent_elems_annotator)=='object' ? icon_name_or_parent_elems_annotator : null;
375				var show_elem_funcs_stack = parent ? parent.show_elem_funcs_stack : null;
376				if (show_elem_funcs_stack)
377				{	show_elem_funcs_stack = show_elem_funcs_stack.concat(show_elem_func ? [show_elem_func] : []); // clone
378				}
379				else if (show_elem_func)
380				{	show_elem_funcs_stack = [show_elem_func];
381				}
382				this.items = parent ? parent.items : [];
383				this.show_elem_funcs = parent ? parent.show_elem_funcs : [];
384				this.show_elem_funcs_stack = show_elem_funcs_stack;
385				this.icon_name = parent ? parent.icon_name : icon_name;
386				this.timer = 0;
387				this.is_top = !parent;
388			}
389
390			add(elem, message, show_elem_func, show_elem_func_owner)
391			{	if (elem)
392				{	this.items.push({elem, message});
393				}
394				var show_elem_funcs_stack = this.show_elem_funcs_stack;
395				if (show_elem_funcs_stack)
396				{	for (var i=0, i_end=show_elem_funcs_stack.length; i<i_end; i++)
397					{	this.show_elem_funcs.push({func:show_elem_funcs_stack[i], owner:null});
398					}
399					this.show_elem_funcs_stack = null;
400				}
401				if (show_elem_func)
402				{	this.show_elem_funcs.push({func:show_elem_func, owner:show_elem_func_owner});
403				}
404				return this;
405			}
406
407			add_if_empty(elem, message, show_elem_func, show_elem_func_owner)
408			{	if (this.items.length == 0)
409				{	this.add(elem, message, show_elem_func, show_elem_func_owner);
410				}
411				return this;
412			}
413
414			merge(elems_annotator)
415			{	for (var i=0, i_end=elems_annotator.items.length; i<i_end; i++)
416				{	this.items.push(elems_annotator.items[i]);
417				}
418				if (i_end > 0)
419				{	var show_elem_funcs_stack = this.show_elem_funcs_stack;
420					if (show_elem_funcs_stack)
421					{	for (var i=0, i_end=show_elem_funcs_stack.length; i<i_end; i++)
422						{	this.show_elem_funcs.push({func:show_elem_funcs_stack[i], owner:null});
423						}
424						this.show_elem_funcs_stack = null;
425					}
426					for (var i=0, i_end=elems_annotator.show_elem_funcs.length; i<i_end; i++)
427					{	this.show_elem_funcs.push(elems_annotator.show_elem_funcs[i]);
428					}
429				}
430				return this;
431			}
432
433			get_first_message()
434			{	return this.items.length ? this.items[0].message : null;
435			}
436
437			annotate(hide_after, hide_onclick)
438			{	this.unannotate(true);
439				var elems = null;
440				var remove = null;
441				var unannotate = () =>
442				{	this.unannotate();
443					if (remove)
444					{	remove();
445						remove = null;
446					}
447				};
448				var call_balancer = new ns.CallBalancer
449				(	-1,
450					() =>
451					{	var scroll_to_elem = null;
452						for (var i=0, i_end=this.items.length; i<i_end; i++)
453						{	var item = this.items[i];
454							let message = item.message || '';
455							if (message instanceof Error)
456							{	ns.error(message);
457								message = message.message || message.description;
458							}
459							let elem = item.elem;
460							let cb = new ns.CallBalancer
461							(	-1,
462								() =>
463								{	for (; elem && !elem.offsetLeft && !elem.offsetTop; elem=elem._ne__switch_elem || elem.parentNode);
464									elems = do_annotate.call(this, elem, message, this.icon_name);
465									if (!scroll_to_elem)
466									{	scroll_to_elem = elem;
467									}
468									if (hide_onclick && elem)
469									{	var listeners = {};
470										ns.new_elem(elem).add_props
471										(	{	onclick:
472												{	func: unannotate,
473													listeners
474												}
475											}
476										);
477										remove = ns.pair_funcs(remove, listeners.onclick.remove);
478									}
479								}
480							);
481							if (!elem.offsetWidth)
482							{	for (var e=elem; e && !e.offsetWidth; e=e._ne__switch_elem || e.parentNode)
483								{	if (typeof(e._show) == 'function')
484									{	e._show({template: 'animator_swap_top', ontransitionend: cb.dec});
485									}
486								}
487							}
488							cb.inc();
489						}
490						if (scroll_to_elem)
491						{	ns.new_elem(scroll_to_elem).scroll_into_view({});
492						}
493					}
494				);
495				var show_elem_funcs = this.show_elem_funcs;
496				for (var i=0, i_end=show_elem_funcs.length; i<i_end; i++)
497				{	show_elem_funcs[i].func.call(show_elem_funcs[i].owner || this, call_balancer.dec());
498				}
499				call_balancer.inc();
500				if (hide_after)
501				{	this.timer = setTimeout(unannotate, hide_after);
502				}
503				return elems; // returns only if there were no show_elem_func funcs (otherwise do_annotate() happens asynchronously)
504			}
505
506			unannotate(no_animation, ondone)
507			{	if (this.timer)
508				{	clearTimeout(this.timer);
509					this.timer = 0;
510				}
511				for (var i=0; i<annotated_elems.length; i++)
512				{	annotated_elems[i]._hide(no_animation ? null : {template: 'animator_fade'});
513				}
514				annotated_elems.splice(0, annotated_elems.length);
515				if (ondone) ondone.call(this);
516			}
517
518			annotate_if_top(no_hide_onclick)
519			{	if (this.is_top)
520				{	this.annotate(0, !no_hide_onclick);
521				}
522				return this.items.length == 0;
523			}
524		}
525	}
526
527NS['declare_template']
528(	'personyze_button_dont_show_again',
529	null,
530	null,
531	['ns', 'elem', 'action_id', 'n_sessions'],
532	function(ns, elem, action_id, n_sessions)
533	{	elem.style.cursor = 'pointer';
534		ns.new_elem(elem).add_props
535		(	{	onclick:
536				{	func(event)
537					{	if (!action_id)
538						{	action_id = ns.get_owner_action?.(elem)?.action_id;
539						}
540						if (n_sessions > 0)
541						{	ns.disable_action(action_id, n_sessions);
542						}
543						ns.dismiss_action(action_id);
544						event.preventDefault();
545					}
546				}
547			}
548		);
549	}
550);
551
552NS['declare_template']
553(	'personyze_button_popup_close',
554	null,
555	null,
556	['ns', 'elem', 'action_id'],
557	function(ns, elem, action_id)
558	{	elem.style.cursor = 'pointer';
559		ns.new_elem(elem).add_props
560		(	{	onclick:
561				{	func(event)
562					{	if (!action_id)
563						{	action_id = ns.get_owner_action?.(elem)?.action_id;
564						}
565						ns.dismiss_action(action_id);
566						event.preventDefault();
567					}
568				}
569			}
570		);
571	}
572);
573
574NS['declare_template']
575(	'personyze_button_popup_minimize',
576	null,
577	null,
578	['ns', 'elem', 'value', 'action_id'],
579	function(ns, elem, value, action_id)
580	{	elem.style.cursor = 'pointer';
581		ns.new_elem(elem).add_props
582		(	{	onclick:
583				{	func(event)
584					{	var action = action_id ? ns.act_params[action_id] : ns.get_owner_action && ns.get_owner_action(elem);
585						if (action)
586						{	if (action._minimize_switch)
587							{	action._minimize_switch(value);
588							}
589							else if (action.popup?._hide)
590							{	action.popup._hide({template: 'animator_fade'});
591							}
592							else
593							{	ns.dismiss_action(action.action_id);
594							}
595						}
596						event.preventDefault();
597					}
598				}
599			}
600		);
601	}
602);
603
604NS['declare_template']
605(	'personyze_message_box',
606	null,
607	null,
608	['ns', 'elem'],
609	function(ns, elem)
610	{	ns.new_elem
611		(	elem,
612			function(subelem)
613			{	if (subelem.classList.contains('message-box-close'))
614				{	ns.new_elem(subelem).add_props
615					(	{	style: 'cursor:pointer',
616							onclick:
617							{	func()
618								{	elem.parentNode.removeChild(elem);
619								}
620							}
621						}
622					);
623				}
624			}
625		);
626	}
627);
628
629NS['declare_template']
630(	'a_add_params',
631	null,
632	null,
633	['ns', 'elem', 'params'],
634	function(ns, elem, params) {
635		var a = elem.getElementsByTagName('a'),
636			d, l, p;
637
638		for (var i = a.length - 1; i >= 0; i--) {
639			l = new ns.Location(a[i].href);
640
641			if (!l.hostname) continue;
642
643			if (params) {
644				p = {};
645
646				for (var j = 0, n = params.length; j < n; j++) {
647					d = params[j].domain.trim();
648
649					if (d && d !== '*') {
650						if (!params[j].regexp) params[j].regexp = new RegExp('^' + d.replace(/([^\w*])/g, '\\$1').replace(/\*/g, '[^\.]*?') + '$');
651						if (!params[j].regexp.test(l.hostname)) continue;
652					}
653
654					p[params[j].name] = params[j].value;
655				}
656
657				l.params(p);
658			}
659			a[i].href = l;
660		}
661	}
662);
663
664NS['declare_template']
665(	'html_loader',
666	null,
667	null,
668	['ns', 'document', 'elem', 'params', 'report_tplload'],
669	function(ns, document, elem, params, report_tplload)
670	{	if (!params || typeof(params)!='object' || !ns.setup)
671		{	if (!ns.setup)
672			{	ns.new_elem('<hr class="$icon spinner $hint" title="HTML LOADER - will be substituted with the loaded content">', elem);
673			}
674			report_tplload();
675			return;
676		}
677		let url = ns.compose_url(params);
678		let {selector}
678 = params;
679
680		let action = ns.get_owner_action(elem);
681
682		let xhr = new XMLHttpRequest;
683		xhr.open("GET", url, true);
684		xhr.onreadystatechange = () =>
685		{	if (xhr.readyState == 4)
686			{	if (xhr.status != 200)
687				{	onerror("Status: "+xhr.status);
688				}
689				else
690				{	let response = xhr.response || xhr.responseXML?.documentElement || xhr.responseText;
691					if (response != null)
692					{	onsuccess(response);
693					}
694					else
695					{	onerror("Invalid response from server");
696					}
697				}
698			}
699		};
700		xhr.send();
701
702		function onerror(message)
703		{	action.fire_event('onerror', new Error(message));
704			report_tplload();
705		}
706
707		function onsuccess(data)
708		{	let is_external_doc = typeof(data) != 'string';
709			let is_wrapper = false;
710
711			if (!is_external_doc)
712			{	data = ns.new_elem('div', null, {innerHTML: data});
713				is_wrapper = true;
714			}
715
716			if (selector)
717			{	if (/^[\w\-]+$/.test(selector))
718				{	selector = '#'+selector+','+selector;
719				}
720				data = ns.joyquery(selector, data).get(0);
721				if (!data)
722				{	return onerror(`Element not found: ${selector}`);
723				}
724				is_wrapper = false;
725			}
726
727			if (is_external_doc)
728			{	data = document.importNode(data, true);
729			}
730			if (!is_wrapper)
731			{	data = ns.new_elem('div', null, {innerHTML: data});
732			}
733
734			while (data.firstChild)
735			{	elem.appendChild(data.firstChild);
736			}
737
738			report_tplload();
739		}
740	}
741);
742
743// _s_t_loaded
744function _new_elem_tmp_1(ns, infrequent_func, ftime, new_elem)
745	{			const SESSION_COOKIE_PROPS =
746		[	'_si',
747			'_sid',
748			'_inew',
749			'_ls',
750			'_lurl', // last URL
751			'_lrfr', // last referrer
752			'_la', // last activity
753			'_so', // social sources
754			'_pp', // popup state, e.g. '12,20' - for action IDs 12 and 20 the initial state is not default
755			'_bh',
756			'_ane', // actions not executed
757			'_te', // switch 1: the explicit testing-mode switch - see ns.te()
758			'_te_ip', // switch 2: the account's testing-mode IP - see ns.te_ip()
759			'_tev', // format version of '_te' - see init_testing_mode()
760			'_nay', // counter for action 'yes' event
761			'_nae', // counter for action 'extra' event
762			'_nac', // counter for action 'close' event
763		];
764		// Bumping this discards every '_te' written by an older lib. See init_testing_mode().
765		const TE_VERSION = 1;
766		const RE_SUBST_USER_DATA = /\$\{([\w\-]*)((?::[\w\-]+(?:\((?:'[^']*'|"[^"]*"|[^'"\)])*\))?)*)\}/g;
767		const RE_SUBST_USER_DATA_FUNC = /\s*:([\w\-]+)(?:\(((?:'[^']*'|"[^"]*"|[^'"\)]+)*)\))?\s*/g;
768		const RE_SUBST_USER_DATA_FUNC_ARGS = /\s*(?:'[^']*')+\s*|\s*(?:"[^"]*")+\s*|[^'"\),]+/g;
769		const RE_SUBST_USER_DATA_AP = /''/g;
770		const RE_SUBST_USER_DATA_QT = /""/g;
771		const RE_SUBST_USER_DATA_ESC = /([\.*+?^$|{}()\[\]\/\\])/g;
772		const RE_CUT_SCRIPTS = /<script((?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s</>="']+|"[^"]*"|'[^']*')))*)>([\S\s]*?)<\/script>/gi;
773		const RE_CSS_ID = /^[^\.#*\[:>+~,'"]+$/;
774		const RE_SPACES_G = /\s+/g;
775		const RE_GOOD_URL_PARAM = /^[^\[\]]+$/;
776		const RE_GHM_NUMBER = /([\\+\\-]?\\b\\d+\\.?\\d*)\\b/;
777		const RE_OBJ_FOLLOW_PATH = /(?:^|\.)\s*([^\.\[\]"']+)|\s*\[\s*([^\]]+)\s*\]/g;
778		const RE_LOOKS_LIKE_VIEWED = /impression|view.?item|item.?view/i;
779		const RE_LOOKS_LIKE_ADD_TO_CART = /add[_\-]?to[_\-]?(?:cart|basket)|add[_\-]?product|add[_\-]?item[_\-]?cart|cart[_\-]?add[_\-]?item/i;
780		const RE_LOOKS_LIKE_REMOVE_FROM_CART = /remove[_\-]?from[_\-]?(?:cart|basket)|remove[_\-]?product|remove[_\-]?item[_\-]?cart|cart[_\-]?remove[_\-]?item/i;
781		const RE_LOOKS_LIKE_PURCHASE = /purchase|bought|buy[_\-]?item|buy[_\-]?prod/i;
782		const RE_PRODUCT_REC = /price|quantity|^sku$/i;
783		const RE_PRODUCT_ID = /^id$|\bsku\b|\bbar[_\-]?code|bar[_\-]?code\b|prod(?:uct)?[_\-]?id\b|item[_\-]?id\b|\bitem[_\-]?id|id[_\-]?item\b|\bid[_\-]?item|cart[_\-]?item\b|\bcart[_\-]?item|item[_\-]?cart\b|\bitem[_\-]?cart/i;
784
785		const LOG_EVENT_VALUE_NO_OVERRIDE = 1;
786		const LOG_EVENT_VALUE_OVERRIDE = 2;
787		const LOG_EVENT_VALUE_SUM = 4;
788		const LOG_EVENT_VALUE_LIST_COMMA = 8;
789		const LOG_EVENT_VALUE_LIST_AMP = 16;
790		const LOG_EVENT_SEND_WITH_DELAY = 32; // send with delay (if none of LOG_EVENT_SEND_* is specified, will not send, only will save to cookie to send next time)
791		const LOG_EVENT_SEND_NOW = 64; // let other events to accumulate (0 sec), then send (with delay of 0)
792		const LOG_EVENT_SEND_IMMED = 128; // send immediately
793
794		const XML_ELEMENT_NODE = 1;
795		const XML_TEXT_NODE = 3;
796		const XML_CDATA_SECTION_NODE = 4;
797
798		/*	Back-off for container element searches. init_containers() re-runs the search of every
799			container that is still un-inited, on the retry timer and on every DOM change, and a selector
800			that matches nothing on this page never stops being un-inited - so the search repeats for as
801			long as the tab lives, always walking the whole document to reach the same empty answer. Our
802			own panel is the worst case (it carries account 362's tracking code, and 86 of that account's
803			87 containers describe elements of the marketing site, none of which exist there): a pass cost
804			77ms, the timer fired every 1.5s, and an open panel tab burned about a tenth of a core doing
805			nothing else.
806			The delay applies to ONE case only: nothing about the element tree has changed since this
807			container last looked. Then the same walk must reach the same empty answer, and the only
808			thing that could answer differently is something nothing observes - an attribute edit, or
809			text that a `:empty` or custom-function selector reads - so it is worth re-asking on a
810			doubling delay up to CONT_RETRY_IDLE_MAX rather than every 1.5s for ever. The first
811			CONT_RETRY_FREE passes are not delayed even then, so a page that settles still gets a few
812			free looks.
813			A structural change is never delayed, whatever the container's history. That distinction
814			cost us: an earlier version also delayed those (up to 8s, "the answer is at most 8s stale"),
815			which is wrong in kind rather than in degree - the element a container waits for may exist
816			for only a moment (a toast, a step in a checkout flow, a view the visitor passes through),
817			and skipping the look it appeared in is not a late answer but no answer at all. Measured on
818			the dev tracker: with that delay, an element that lived 2s was missed outright. Searching on
819			every structural batch is what the tracker always did, and it is affordable - the selector
820			engine hands plain CSS to the browser (joyquery's try_builtin), so a pending container costs
821			about 0.02ms a look.
822			Anything that genuinely changes the world (a new page in an SPA, a fresh set of actions or
823			conditions) calls reset_conts_retry() and starts over. */
824		const CONT_RETRY_FREE = 6;
825		const CONT_RETRY_STEP = 1500;
826		const CONT_RETRY_IDLE_MAX = 30000;
827		/*	The container types whose init walks the DOM (see the `ns.id()` call in init_container) - the
828			only ones the back-off applies to. A container that still owes a cookie, variable, storage,
829			url or meta-tag read is cheap and keeps running on every pass, because 'grab variable' in
830			particular is deliberately watched for ever. */
831		const CONT_SELECTOR_TYPES = 3071;
832
833		var commands_queue = window[CONF__PERSONYZE_COMMANDS_JS_VAR_NAME] || [];
834		var commands_override_action_id_for = null;
835		var commands_cur_override_action_id = 0;
836		var commands_cur_manually_reported = false;
837		var want_end_session = false;
838		var want_new_navigation = false;
839		var send_timer;
840		var unblock_all_timer;
841		var unblock_qelem_timer;
842		/** What `#-S-T-H` currently covers: [{c, html_id}] per hidden container. **/
843		var hidden_conts = null;
844		var conts_timer; // setInterval for initializing containers
845		var conts_observer = null; // MutationObserver for initializing containers
846		var conts_dom_gen = 0; // bumped by on_conts_mutation() whenever the element tree changed - see conts_retry_wait()
847		var conts_read_text = false; // some container re-reads an element it already found, so even a text-only change is worth a pass - see on_conts_mutation()
848		var executed_actions = {}; // {action_id: state}, where state===0 means started stage_1 of execution, state===1 means auto set to shown, state===2 means reported shown, state===3 means shown and "already shown" message reported, typeof(state)=='string' means error
849		var html_input_state = {};
850		var events_sent_times = 0;
851		var start_time = 0; // is used by install_focus
852		var is_sync_blocking = false;
853		var wants_to_block = false;
854		var blocks = null; // currently blocking action IDs
855		var current_conditions = {}; // e.g. {c1:0, c2:0, c10:[9,0,0]} - keys - IDs of conditions matched on this page. only conditions that appear in containers or sticky are listed. regular conds appear as c1:0, sticky conds appear as c10:[remain_sessions, match_again, hours?1:0]
856		var current_pagegroups = [];
857		var current_interests = []; // TODO: strongen interests according to page content
858		var time_formats = {time: '%H:%M:%S', time_short: '%H:%M', date: '%Y %b %d', date_short: '%y/%m/%d', datetime: '%Y %b %d %H:%M:%S', datetime_short: '%y/%m/%d %H:%M'};
859		var first_log_visit_sent = false;
860		var is_parallel = false; // parallel asynchronous load
861		var is_ready = false; // will be set to true when first log-visit will finish (bringing actions and containers)
862		var actions_q = [];
863		var personyze_override = null;
864		var default_containers = {};
865		var conts_map = {};
866		var timers_ac = {}; // timers for delayed actions
867		var not_found_html_changes = new Map;
868		var ondropcss = null;
869		var onevent = {};
870		var onerr = {};
871		var use_location = null;
872		var actions_subst_user_data = {};
873		var cur_visit_id = 0;
874		var event_obj = null;
875		var flush_events = () => {};
876		var ins_status_install = false;
877		var ins_status_log_visit = false;
878		var ins_status = null;
879		var h_onscroll = null;
880		var onscroll_funcs = null;
881		var send_request_onload = null;
882		var send_request_onerror = null;
883		var actions_history = null;
884		var add_to_cart_func = null;
885		var add_to_wishlist_func = null;
886		var remove_from_cart_func = null;
887		var event_send_with_delay = 0;
888		var event_send_now = 0;
889		var send_after_unload = false;
890		var ga_is_active = false;
891		/*	Account-level default destinations for automatic per-action reporting
892			(acc_settings.default_send_to_dests). Google Analytics is not in this
893			list: it has its own switch and its own call, unchanged since forever. */
894		var default_send_dests = '';
895		var ga_is_gtm = false;
896		var ga_queue = [];
897		var gtm_data_layer_name = null;
898		var gtm_grab = [];
899		var gtm_product_id_column = null;
900		var gtm_key = null;
901		var gtm_inst = null;
902		var gtm_history = [];
903		var ga_history = [];
904		var tealium_try_init_till = Date.now() + 20_000; // 20 seconds
905		var tealium_init_timer = null;
906		var tealium_storage_name = null;
907		var tealium_audiences = null;
908		var when_action_executed = {};
909		var host_id = 0;
910		var force_unblock_all = false;
911		/*	Consent hold. Only ever set when the site's snippet asked for it (3rd argument to
912			ns.setup); every other installation behaves exactly as before, which is the point --
913			this file is served from ONE url to every account, so the default path must not change.
914
915			`consent_denied` makes the existing `is_on()` gate in ns.setup() take its OFF branch
916			without touching the cookie. Writing an opt-out cookie to represent "hasn't consented
917			yet" would be wrong twice over: it is storage before consent, and it would persist a
918			refusal the visitor never made. */
919		var consent_denied = false;
920		var consent_pending = null; // [server_id, domains] captured while waiting
921		/*	Sent as `cs=1` on every request once consent has been granted, and never sent otherwise.
922			It is what lets the server tell "this account has consent mode on and the visitor agreed"
923			apart from "this account has consent mode on and the snippet was pasted without the third
924			argument, so the library never waited for anyone". The second case is a misconfiguration
925			that would otherwise track silently and look identical to a working install; the server
926			refuses it. Absent rather than `cs=0` when not granted, so that the request every existing
927			installation sends is byte-for-byte what it sends today. */
928		var consent_granted = false;
929		var tracker_events = new TrackerEvents;
930		var before_unload_time = 0;
931		var notifications = null;
932		var cur_notification_a = 0;
933		var add_to_cart_success_html = null;
934		var add_to_cart_error_html = null;
935		var add_to_wishlist_success_html = null;
936		var add_to_wishlist_error_html = null;
937		var articles_delivered = [];
938		var articles_delivered_timer = null;
939		var stat_track_u_id = null; // cookie backup
940		var _stat_track_s_id = null; // cookie backup
941
942		
943		function Mark1() {};
944		function Mark2() {};
945
946		function ins_status_done(is_log_visit)
947		{	if (!is_log_visit)
948			{	ins_status_install = true;
949			}
950			else
951			{	ins_status_log_visit = true;
952			}
953			if (ins_status_install && ins_status_log_visit)
954			{	log_visit_2();
955			}
956		}
957
958		function onend()
959		{	use_location = null;
960			if (want_end_session)
961			{	want_end_session = false;
962				ns.cookie_obj('_stat_track_s_id', null, true);
963				tracker_events.set(null);
964			}
965			var {containers} = ns;
966			for (var c_id in containers)
967			{	if (containers.hasOwnProperty(c_id))
968				{	var c = containers[c_id];
969					if (c?.onflush)
970					{	c.onflush();
971						c.onflush = null;
972					}
973					c.inited &= ~8192;
974					c.tr_clicked = null;
975				}
976			}
977			events_sent_times = 0;
978			var aa = executed_actions;
979			executed_actions = {};
980			for (var a in aa)
981			{	if (aa.hasOwnProperty(a) && !ns.dismiss_action(a) && typeof(aa[a])!='string')
982				{	executed_actions[a] = aa[a];
983				}
984			}
985		}
986
987		function TrackerEvents()
988		{	this.sent_events = {};
989			this.buffered_mode = 0;
990
991			this.get = function()
992			{	if (window.sessionStorage)
993				{	try
994					{	var value = sessionStorage['_S_T.bf'];
995						return !value || value=='[]' || value=='"[]"' ? {} : JSON.parse(value) || {}; // i can't catch who is setting [] on Safari
996					}
997					catch (e)
998					{	return {};
999					}
1000				}
1001				else
1002				{	return ns.cookie_obj('_stat_track_s_id');
1003				}
1004			};
1005
1006			this.set = function(stored_events)
1007			{	if (window.sessionStorage)
1008				{	sessionStorage['_S_T.bf'] = JSON.stringify(stored_events || {});
1009				}
1010				else
1011				{	return ns.cookie_obj('_stat_track_s_id', stored_events, true);
1012				}
1013			};
1014
1015			this.add = function(c_id, value, mode, container_mask, rid_e)
1016			{	let m = this.get();
1017				let reset = true;
1018				let no_replace_add = 1;
1019				let time = Date.now();
1020				this.buffered_mode |= mode & (LOG_EVENT_SEND_WITH_DELAY|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_IMMED);
1021				// if rid_e < 0 then mode must be LOG_EVENT_VALUE_LIST_COMMA, and value must be single integer
1022				if (mode & (LOG_EVENT_VALUE_NO_OVERRIDE|LOG_EVENT_VALUE_OVERRIDE|LOG_EVENT_VALUE_SUM|LOG_EVENT_VALUE_LIST_COMMA|LOG_EVENT_VALUE_LIST_AMP))
1023				{	no_replace_add = 0;
1024					for (let i in m)
1025					{	if (m.hasOwnProperty(i))
1026						{	let sen_time = this.sent_events[i];
1027							if (sen_time && time-sen_time>=5000 && i.charAt(0)!='_')
1028							{	delete m[i];
1029								delete this.sent_events[i];
1030								continue;
1031							}
1032							var prefix = '_'+container_mask+'_'+rid_e+'_';
1033							if (i.substring(i.lastIndexOf(prefix) + prefix.length) == c_id)
1034							{	// found
1035								if (mode & LOG_EVENT_VALUE_NO_OVERRIDE)
1036								{	if (m[i] == value) reset = false;
1037								}
1038								else if (mode & LOG_EVENT_VALUE_SUM)
1039								{	value -= -m[i];
1040								}
1041								else if (mode & (LOG_EVENT_VALUE_LIST_COMMA|LOG_EVENT_VALUE_LIST_AMP))
1042								{	let delim = mode & LOG_EVENT_VALUE_LIST_AMP ? '&' : ',';
1043									let res = (m[i]+'').split(delim);
1044									function key(kv)
1045									{	let pos = mode&LOG_EVENT_VALUE_LIST_AMP ? kv.indexOf('=') : -1;
1046										return pos==-1 ? kv : kv.substr(0, pos);
1047									}
1048									let keys = [];
1049									for (let j=0; j<res.length; j++)
1050									{	keys[j] = key(res[j]);
1051									}
1052									for (let new_kv of (value+'').split(delim))
1053									{	let k = key(new_kv);
1054										let pos = keys.indexOf(k);
1055										if (pos!=-1 && k=='TP' && (mode&LOG_EVENT_VALUE_LIST_AMP))
1056										{	res[pos] += ','+new_kv.substring(3);
1057										}
1058										else
1059										{	res[pos==-1 ? res.length : pos] = new_kv;
1060										}
1061									}
1062									m[i] = res.join(delim);
1063									reset = false;
1064								}
1065								if (reset) delete m[i];
1066							}
1067						}
1068					}
1069				}
1070				if (reset && c_id)
1071				{	let sid_obj = ns.cookie_obj('_stat_track_s_id');
1072					m[((ns.secs() - sid_obj._sid + no_replace_add) | 0)+'_'+container_mask+'_'+rid_e+'_'+c_id] = value; // make "replace events" be earlier
1073				}
1074				this.set(m);
1075			};
1076
1077			this.cleanup = function(error)
1078			{	var m = this.get();
1079				var time = Date.now();
1080				for (var i in this.sent_events)
1081				{	if (this.sent_events.hasOwnProperty(i))
1082					{	if (!error || time-this.sent_events[i]>=5000)
1083						{	delete m[i];
1084							delete this.sent_events[i];
1085						}
1086					}
1087				}
1088				this.set(m);
1089			};
1090		}
1091
1092		function get_personyze_override()
1093		{	if (!personyze_override)
1094			{	personyze_override = {a: null, t: null, u: null};
1095				var str = ns.get_param(CONF__CLIENT_OVERRIDES_PARAM_NAME) || ns.get_param(CONF__CLIENT_OVERRIDES_PARAM_NAME_2);
1096				if (str)
1097				{	var arr = str.split(',');
1098					for (var i=0; i<arr.length; i++)
1099					{	if (arr[i] == 'testing')
1100						{	personyze_override.t = 1;
1101						}
1102						else if (arr[i] == 'production')
1103						{	personyze_override.t = 0;
1104						}
1105						else
1106						{	var what = arr[i].charAt(0);
1107							var data = arr[i].substring(1);
1108							if (what == 'a')
1109							{	if (parseInt(data))
1110								{	if (!personyze_override.a)
1111									{	personyze_override.a = [];
1112									}
1113									personyze_override.a.push(parseInt(data));
1114								}
1115							}
1116							else if (what == 'u')
1117							{	data = data.match(/^(\-?\d+)(n?)(o?)(u?)_(\d+)_(\d+)_([+\-\d:;]*)_(\d+)_(\d+)$/);
1118								if (data)
1119								{	personyze_override.u =
1120									{	uid: data[1],
1121										no_count: !!data[2],
1122										off: !!data[3],
1123										inew: data[4] ? 2 : 0,
1124										st: data[5],
1125										ls: data[6],
1126										sy: data[7].replace(/;/g, ','),
1127										sid: data[8],
1128										bh: data[9]
1129									};
1130								}
1131							}
1132						}
1133					}
1134				}
1135			}
1136			return personyze_override;
1137		}
1138
1139		function exec_ondropcss()
1140		{	if (ondropcss)
1141			{	ondropcss();
1142				if (ns.IS_MSIE)
1143				{	document.body.style.visibility = 'visible'; // fixes many bugs
1144				}
1145				ondropcss = null;
1146			}
1147		}
1148
1149		function exec_onevent()
1150		{	for (var f in onevent)
1151			{	if (onevent.hasOwnProperty(f) && typeof(onevent[f])=='function')
1152				{	ns.pcall(onevent[f]);
1153				}
1154			}
1155		}
1156
1157		function exec_onerr(args)
1158		{	for (var f in onerr)
1159			{	if (onerr.hasOwnProperty(f) && typeof(onerr[f])=='function')
1160				{	ns.pcall(onerr[f], null, args);
1161				}
1162			}
1163		}
1164
1165		function click_event_page(c_id, onload, onerror)
1166		{	want_new_navigation = true;
1167			log_event(c_id, 1, (event_send_with_delay&16384 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&16384 ? LOG_EVENT_SEND_NOW : 0), 16, 0, onload, onerror);
1168		}
1169
1170		/**	HOW WIDELY A WYSIWYG CHANGE MAY LAND.
1171
1172			`qelem_id` is the shortest selector that was unique on the page the change was recorded on,
1173			so everywhere else it is deliberately under-specified: `div.left > h1` finds a heading on
1174			almost any page and the change lands there looking like it worked. The editor now records
1175			what the author meant, and this is where that is honoured.
1176
1177			  change.scope 'page'  - only change.scope_url, compared by scope_url_key()
1178			  change.scope 'shape' - only where change.scope_fp still describes the element
1179			  change.scope 'any', or NO scope at all - wherever the selector matches
1180
1181			No scope means the change was recorded before the setting existed. It keeps applying exactly
1182			where it always did; nothing about an existing account changes because this shipped.
1183
1184			Twin of `scopeUrlKey`/`fingerprintMatches` in webdriver-apps (fresh-service). Pinned by
1185			src/fresh-service/webdriver-apps/src/modules/pageeditor/scopedChanges.test.ts, which reads
1186			both files - there is no runtime where they meet.
1187		 **/
1188
1189		/**	Query parameters that say where the visitor came from, not which page this is. A change
1190			scoped to one URL has to survive the same page arriving from an ad. **/
1191		var SCOPE_URL_NOISE = /^(utm_|_S_T$|gclid$|fbclid$|msclkid$|dclid$|ttclid$|twclid$|igshid$|mc_eid$|_ga$)/i;
1192
1193		function scope_url_key(href)
1194		{	try
1195			{	var url = new URL(href);
1196				var names = [];
1197				url.searchParams.forEach(function(v, name) {if (!SCOPE_URL_NOISE.test(name)) names.push(name)});
1198				names.sort();
1199				var query = names.map(function(name) {return name+'='+url.searchParams.get(name)}).join('&');
1200				return url.origin + url.pathname.replace(/\/$/, '') + (query ? '?'+query : '');
1201			}
1202			catch (e)
1203			{	return href;
1204			}
1205		}
1206
1207		/**	Is this element the same thing the fingerprint was taken from? Every recorded level must
1208			line up with an ancestor: same tag, and - where classes were recorded - at least ONE of them
1209			still present. One is enough on purpose: a page that renamed a modifier class is still the
1210			same page, and a change that silently stops applying is worse than one that lands wrong,
1211			because nobody notices it for weeks.
1212		 **/
1213		function scope_fp_matches(elem, fp)
1214		{	if (!fp || !fp.length)
1215			{	return true;
1216			}
1217			var node = elem;
1218			for (var i=0; i<fp.length; i++)
1219			{	var step = fp[i];
1220				if (!node || !node.localName || node.localName!=step.t)
1221				{	return false;
1222				}
1223				if (step.c && step.c.length)
1224				{	var ok = false;
1225					for (var j=0; j<step.c.length; j++)
1226					{	if (node.classList && node.classList.contains(step.c[j]))
1227						{	ok = true;
1228							break;
1229						}
1230					}
1231					if (!ok)
1232					{	return false;
1233					}
1234				}
1235				node = node.parentElement;
1236			}
1237			return true;
1238		}
1239
1240		/**	Is this page one the change is allowed on at all? Only 'page' can answer no here; 'shape'
1241			is decided per element, by scope_fp_matches(). **/
1242		function scope_allows_page(change)
1243		{	return change.scope!='page' || !change.scope_url || scope_url_key(change.scope_url)==scope_url_key(location.href);
1244		}
1245
1246		/**	This function is appreciated to be as similar to page_editor's.apply_html_changes() as possible
1247		 **/
1248		function apply_html_change_on_elem(elem, change, action_id, set_img_src)
1249		{	function subst_attrs(attrs, action_id, except)
1250			{	if (!attrs) return null;
1251				var c_attrs = {};
1252				for (var i in attrs)
1253				{	if (attrs.hasOwnProperty(i) &&  i!=except)
1254					{	c_attrs[i] = ns.subst_user_data_action(attrs[i], '', action_id);
1255					}
1256				}
1257				return c_attrs;
1258			}
1259
1260			function put_elem(elem, put_elem, where)
1261			{	ns.new_elem(put_elem).add_to_parent(where=='before' ? {nextSibling:elem} : where=='after' ? {previousSibling:elem} : elem);
1262			}
1263
1264			function pick_node(ref_node, where)
1265			{	return where=='before' ? ref_node.previousSibling : where=='after' ? ref_node.nextSibling : ref_node.lastChild;
1266			}
1267
1268			function edit_text_or_html(elem, is_html, change_text, action_id)
1269			{	let text = '';
1270				let base = is_html ? elem.innerHTML : elem.data;
1271				for (let i=0, i_end=change_text.length; i<i_end; i++)
1272				{	let h = change_text[i];
1273					if (typeof(h) != 'string')
1274					{	let pos=h[0], len=h[1]|0;
1275						if (pos < 0) pos += base.length;
1276						if (len <= 0) len = base.length + len - pos;
1277						h = base.substr(pos, len);
1278					}
1279					text += ns.subst_user_data_action(h, '', action_id);
1280				}
1281				if (!is_html)
1282				{	elem.data = text;
1283				}
1284				else
1285				{	patch_r(elem, ns.new_elem(elem.nodeName, null, {innerHTML: text}));
1286					function patch_r(e, new_e)
1287					{	// patch children
1288						let child = e.firstChild;
1289						let new_child = new_e.firstChild;
1290						while (child || new_child)
1291						{	let next = child?.nextSibling;
1292							let new_next = new_child?.nextSibling;
1293							if (!child)
1294							{	e.appendChild(new_child);
1295							}
1296							else if (!new_child)
1297							{	e.removeChild(child);
1298							}
1299							else if (child.nodeType != XML_ELEMENT_NODE)
1300							{	if (child.nodeType==XML_TEXT_NODE && new_child.nodeType==XML_TEXT_NODE)
1301								{	// assign text on the left to text on the right
1302									child.data = new_child.data;
1303								}
1304								else
1305								{	// remove text on the left, and redo on the right
1306									e.removeChild(child);
1307									new_next = new_child;
1308								}
1309							}
1310							else if (new_child.nodeType != XML_ELEMENT_NODE)
1311							{	// insert text from right to left, and redo on the left
1312								e.insertBefore(new_child, child);
1313								next = child;
1314							}
1315							else if (child.nodeName != new_child.nodeName)
1316							{	let has_elem_to_patch = false;
1317								for (let n=new_next; n && !has_elem_to_patch; n=n.nextElementSibling)
1318								{	has_elem_to_patch = child.nodeName == n.nodeName;
1319								}
1320								if (has_elem_to_patch)
1321								{	// insert element from right to left, and redo on the left
1322									e.insertBefore(new_child, child);
1323									next = child;
1324								}
1325								else
1326								{	// remove element on the left, and redo on the right
1327									e.removeChild(child);
1328									new_next = new_child;
1329								}
1330							}
1331							else
1332							{	// patch attributes
1333								let names = [];
1334								for (let i=0, i_end=new_child.attributes.length; i<i_end; i++)
1335								{	let attr = new_child.attributes[i];
1336									try
1337									{	child.setAttribute(attr.name, attr.value);
1338										names[names.length] = attr.name;
1339									}
1340									catch (e)
1341									{	ns.error(e);
1342									}
1343								}
1344								for (let i=0; i<child.attributes.length; i++)
1345								{	let attr = child.attributes[i];
1346									if (names.indexOf(attr.name) == -1)
1347									{	child.removeAttribute(attr.name);
1348										i--;
1349									}
1350								}
1351								// patch element
1352								patch_r(child, new_child);
1353							}
1354							child = next;
1355							new_child = new_next;
1356						}
1357					}
1358				}
1359			}
1360
1361			function new_elem_changes(elem_props, action_id)
1362			{	if (typeof(elem_props) == 'string')
1363				{	return document.createTextNode(ns.subst_user_data_action(elem_props, '', action_id));
1364				}
1365				else
1366				{	let [node_name, node_attrs, node_child_nodes, node_inner_html] = elem_props;
1367					let el_add = document.createElement(node_name);
1368					if ((node_name=='video' || node_name=='audio') && typeof(el_add.play)!='function')
1369					{	el_add = document.createElement('embed');
1370					}
1371					if (node_inner_html)
1372					{	node_inner_html = ns.subst_user_data_action(node_inner_html, '', action_id);
1373					}
1374					ns.new_elem(el_add).add_props
1375					(	{	attributes: subst_attrs(node_attrs, action_id),
1376							innerHTML: node_inner_html
1377						}
1378					);
1379					if (node_child_nodes && node_inner_html==null)
1380					{	for (let i=0, i_end=node_child_nodes.length; i<i_end; i++)
1381						{	el_add.appendChild(new_elem_changes(node_child_nodes[i], action_id));
1382						}
1383					}
1384					el_add = ns.new_elem(el_add, null); // process templates
1385					return el_add;
1386				}
1387			}
1388
1389			let last_add_elem = null;
1390
1391			// set_img_src
1392			let img_src = change.attrs?.src && elem.nodeName.toUpperCase()=='IMG' && change.attrs.src;
1393			if (img_src)
1394			{	set_img_src.push
1395				(	{	elem: elem,
1396						src: ns.subst_user_data_action(img_src, '', action_id)
1397					}
1398				);
1399			}
1400
1401			if (change.inner_html)
1402			{	edit_text_or_html(elem, true, change.inner_html, action_id);
1403				elem = ns.new_elem(elem, null); // process templates
1404			}
1405			if (change.text)
1406			{	let {where} = change;
1407				// 1) pick the node
1408				let node = pick_node(elem, where);
1409				if (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE)
1410				{	// handle adjacent text nodes
1411					let ref_node = node;
1412					while (true)
1413					{	let further_node = pick_node(ref_node, where=='inside' ? 'before' : where);
1414						if (further_node.nodeType!=XML_TEXT_NODE && further_node.nodeType!=XML_CDATA_SECTION_NODE)
1415						{	break;
1416						}
1417						if (further_node.length > 0)
1418						{	if (where == 'after')
1419							{	node.data = node.data + further_node.data;
1420							}
1421							else
1422							{	node.data = further_node.data + node.data;
1423							}
1424							further_node.parentNode.removeChild(further_node);
1425						}
1426						else
1427						{	// empty text nodes are used as stubs, so no touch
1428							ref_node = further_node;
1429						}
1430					}
1431				}
1432				else
1433				{	// create new node if not found
1434					node = document.createTextNode('');
1435					put_elem(elem, node, where);
1436				}
1437				// 2) edit node's text
1438				edit_text_or_html(node, false, change.text, action_id);
1439			}
1440			ns.new_elem(elem).add_props({attributes: subst_attrs(change.attrs, action_id, img_src ? 'src' : null)});
1441			if (change.attrs)
1442			{	if (change.attrs['class'])
1443				{	elem.className = change.attrs['class'];
1444				}
1445			}
1446			if (change.dup)
1447			{	for (let k=change.dup-1; k>=0; k--)
1448				{	elem.parentNode.insertBefore(ns.new_elem(elem).clone_node(), elem);
1449				}
1450			}
1451			if (change.mv)
1452			{	let el_add = ns.qelem(change.mv_qelem_id);
1453				if (el_add)
1454				{	put_elem(el_add, elem, change.mv);
1455				}
1456			}
1457			if (change.rm_shallow)
1458			{	while (elem.firstChild)
1459				{	elem.parentNode.insertBefore(elem.firstChild, elem);
1460				}
1461				elem.parentNode.removeChild(elem);
1462			}
1463			if (change.rm)
1464			{	elem.parentNode.removeChild(elem);
1465			}
1466			if (change.e_shallow || change.e)
1467			{	let el_add = new_elem_changes(change.e_shallow || change.e, action_id);
1468				if (change.e)
1469				{	put_elem(elem, el_add, change.where);
1470				}
1471				else
1472				{	let n_next = change.n_next-0 || 1;
1473					elem.parentNode.insertBefore(el_add, elem);
1474					el_add.appendChild(elem);
1475					while (--n_next>0 && el_add.nextSibling)
1476					{	el_add.appendChild(el_add.nextSibling);
1477					}
1478				}
1479				last_add_elem = el_add;
1480			}
1481			if (change.events)
1482			{	for (let k=0; k<change.events.length; k++)
1483				{	let event = change.events[k];
1484					let {onwhat, custom_event, value, action_state, js, once, fire_action, rm_elem} = event;
1485					let e_action_id = event.action_id || event.action_goal;
1486					let func=null, remove=null;
1487					if (custom_event > 0)
1488					{	func = function()
1489						{	remove();
1490							ns.custom_event(custom_event, value);
1491						};
1492					}
1493					else if (fire_action > 0)
1494					{	func = function()
1495						{	remove();
1496							do_exec_action(fire_action, null, null, null, true); // TODO: ns.push(['Fire action', fire_action]);
1497						};
1498					}
1499					else if (e_action_id > 0)
1500					{	func = function()
1501						{	remove();
1502							ns.action_state(e_action_id, action_state || 'goal');
1503						};
1504					}
1505					else if (e_action_id == -1)
1506					{	func = function()
1507						{	remove();
1508							ns.action_state(change.action_id || action_id, action_state || 'goal');
1509						};
1510					}
1511					else if (js)
1512					{	func = function()
1513						{	if (once) remove();
1514							ns.inject_code(js);
1515						};
1516					}
1517					else if (rm_elem)
1518					{	func = function()
1519						{	var elem = ns.id(rm_elem);
1520							if (elem)
1521							{	elem.style.display = 'none';
1522							}
1523						};
1524					}
1525					if (func)
1526					{	let listeners = {};
1527						ns.new_elem(elem).add_props({['on'+onwhat]: {func, capture: true, listeners}});
1528						remove = listeners['on'+onwhat].remove;
1529					}
1530				}
1531			}
1532			return last_add_elem;
1533		}
1534
1535		function get_cookie_domain()
1536		{	var domain = '';
1537			var l_host = location.host;
1538			var ast = '';
1539			if (l_host.indexOf('www.') == 0)
1540			{	l_host = l_host.substring(4);
1541				ast = '.';
1542			}
1543			var {domains} = ns;
1544			for (var host in domains)
1545			{	if (domains.hasOwnProperty(host))
1546				{	if (l_host == host)
1547					{	if (!domain) domain = ast+host;
1548					}
1549					else if (host.substr(0, 2) == '*.')
1550					{	host = host.substring(2);
1551						var pos = l_host.lastIndexOf(host);
1552						if (pos!=-1 && pos+host.length==l_host.length)
1553						{	if (host.indexOf('.')==-1 || host.match(/^co\.\w{2,3}$/)) // *.com or *.co.il
1554							{	var parts = l_host.split('.');
1555								if (parts.length>1 && parts[parts.length - 1].length<=2)
1556								{	var p = parts.pop();
1557									parts[parts.length - 1] += '.'+p;
1558								}
1559								host = '';
1560								for (var i=0; i<2 && parts.length>0; i++)
1561								{	var p = parts.pop();
1562									host = '.'+p+host;
1563								}
1564							}
1565							else
1566							{	host = '.'+host;
1567							}
1568							if (domain.charAt(0)!='.' || host.length<domain.length)
1569							{	domain = host;
1570							}
1571						}
1572					}
1573				}
1574			}
1575			return domain || ast+l_host;
1576		}
1577
1578		function exec_actions(a, ondone)
1579		{				if (!a)
1580			{	a = [];
1581			}
1582			// override actions and conditions
1583			if (!window['_S_T_ACTIONS_DISABLED'])
1584			{	if (!event_obj)
1585				{	event_obj = {};
1586					let props = get_request_props(1, 0, 1);
1587					if (props)
1588					{	for (let i of Object.keys(props))
1589						{	event_obj[i] = props[i];
1590						}
1591					}
1592					let {user_data} = ns;
1593					for (let i of Object.keys(user_data))
1594					{	event_obj[i] = user_data[i];
1595					}
1596				}
1597				let call_balancer = new ns.CallBalancer(-1, ondone);
1598				// override actions
1599				let override_actions = cur_visit_id && get_personyze_override().a || null;
1600				if (override_actions)
1601				{	a.unshift([cur_visit_id].concat(override_actions));
1602				}
1603				// execute actions
1604				for (let i=0; i<a.length; i++)
1605				{	let a_i = a[i];
1606					let rid = a_i[0];
1607					let aa = a_i.slice(1).sort
1608					(	(a, b) =>
1609						(	(_S_T.act_params[a]?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - (_S_T.act_params[b]?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER)
1610							||
1611							(a - b)
1612						)
1613					);
1614					for (let aid of aa)
1615					{	let bypass_others = do_exec_action(aid+'', rid, call_balancer.dec(), null, false, override_actions?.indexOf(aid|0)>=0);
1616						if (bypass_others)
1617						{	return true;
1618						}
1619					}
1620				}
1621				call_balancer.inc();
1622			}
1623		}
1624
1625		function actions_q_done(a, rid)
1626		{	var q = actions_q;
1627			for (var i=0; i<q.length; i++)
1628			{	if (q[i][0] == rid)
1629				{	for (var j=1; j<q[i].length; j++)
1630					{	if (q[i][j] == a)
1631						{	if (q[i].length == 2)
1632							{	q.splice(i, 1);
1633							}
1634							else
1635							{	q[i].splice(j, 1);
1636							}
1637							return;
1638						}
1639					}
1640				}
1641			}
1642		}
1643
1644		function actions_q_add(actions)
1645		{	actions_q = actions_q.concat(ns.clone_obj(actions));
1646		}
1647
1648		function do_clear_actions(action_ids)
1649		{	let dismissed_ids = [];
1650			if (action_ids?.length)
1651			{	for (let i=0; i<action_ids.length; i++)
1652				{	action_ids[i] |= 0;
1653					let action_id = action_ids[i];
1654					delete executed_actions[action_id];
1655					let params = ns.act_params[action_id];
1656					if (params?.dismiss)
1657					{	try
1658						{	params.dismiss();
1659							dismissed_ids.push(action_id);
1660						}
1661						catch (e)
1662						{	ns.error(e);
1663						}
1664					}
1665				}
1666				let cc = ns.containers;
1667				for (let c_id in cc)
1668				{	let c = cc[c_id];
1669					if (cc.hasOwnProperty(c_id) && c?.a)
1670					{	for (let i=0; i<c.a.length; i++)
1671						{	let action_id = c.a[i]|0;
1672							if (action_ids.indexOf(action_id) != -1)
1673							{	delete executed_actions[action_id];
1674								c.html[i].detach?.();
1675								c.a.splice(i, 1);
1676								c.html.splice(i, 1);
1677								dismissed_ids.push(action_id);
1678								i--;
1679							}
1680						}
1681					}
1682				}
1683			}
1684			return dismissed_ids;
1685		}
1686
1687		function init_activity_events()
1688		{	/*function is_url_in(u)
1689			{	var loc = new ns.Location(u);
1690				return loc.hostname==location.hostname || (loc.hostname in ns.domains) || loc.protocol=='mailto:' || loc.protocol=='javascript:';
1691			}*/
1692			function init_activity_event(c_id)
1693			{	if (c_id==-5 || c_id==-6 || c_id==-7 || c_id==-8)
1694				{	var e_name = ['onmouseup', 'onmouseup', 'onkeyup', 'onscroll'][-5 - c_id];
1695					var last = 0;
1696					ns.add_e
1697					(	e_name=='onscroll' ? window : document.body, e_name, function(event)
1698						{	var now = Date.now();
1699							if
1700							(	c_id==-5 && event.which==1 ||
1701								c_id==-6 && event.which==3 ||
1702								c_id==-7 ||
1703								c_id==-8 && now-last>999
1704							)
1705							{	log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&1 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&1 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id);
1706								if (c_id == -8)
1707								{	last = now;
1708								}
1709							}
1710						},
1711						c_id
1712					);
1713				}
1714			}
1715			init_activity_event(-5);
1716			init_activity_event(-6);
1717			init_activity_event(-7);
1718			init_activity_event(-8);
1719			/*init_activity_event(-9);
1720			init_activity_event(-10);*/
1721		}
1722
1723		function init_container(c_id, c, noraise, is_trigger_click)
1724		{	let want_monitor_containers = false;
1725			if (c.cn?.length)
1726			{	var ok=0, found=0;
1727				for (var i=c.cn.length-1; i>=0; i--)
1728				{	if (c.cn[i] < 0)
1729					{	if (current_conditions.hasOwnProperty('c'+(-c.cn[i])))
1730						{	return want_monitor_containers; // don't use container if condition matches
1731						}
1732					}
1733					else
1734					{	found = 1; // if there were no positive containers - that means ok
1735						if (current_conditions.hasOwnProperty('c'+c.cn[i])) ok = 1; // use container if condition matches
1736					}
1737				}
1738				if (found && !ok)
1739				{	return want_monitor_containers;
1740				}
1741			}
1742			var tp = c.tp & ~c.inited;
1743			// assume: tp!=0
1744			var html_id = c.hi;
1745			var html_attr = c.ha;
1746			if (((tp) & 3071))
1747			{	var elems = c.found_elems;
1748				if (elems?.find(e => !ns.new_elem(e).is_node_linked()))
1749				{	elems = c.found_elems = null;
1750				}
1751				if (!elems)
1752				{	try
1753					{	let found = ns.id(c.hi, true);
1754						if (found.length)
1755						{	if (!c.mm)
1756							{	found.length = 1;
1757							}
1758							elems = c.found_elems = found;
1759							c.found_elems_tr = null; // refind
1760						}
1761					}
1762					catch (e)
1763					{
1764					}
1765				}
1766			}
1767			if (c.tr && !is_trigger_click)
1768			{	let triggers = c.found_elems_tr;
1769				if (triggers?.find(e => !ns.new_elem(e).is_node_linked()))
1770				{	triggers = c.found_elems_tr = null;
1771				}
1772				if (!triggers)
1773				{	triggers = c.found_elems_tr = ns.id(c.tr, true);
1774					for (let tr of triggers)
1775					{	ns.add_e
1776						(	tr,
1777							'onclick',
1778							() =>
1779							{	c.found_elems = null; // refind
1780								if (c.tr_clicked == null) // null: !clicked && !data; function: !clicked && data; true: clicked && !data; false: clicked && data (sent)
1781								{	c.tr_clicked = true;
1782									init_container(c_id, c, noraise, 1);
1783								}
1784								else if (typeof(c.tr_clicked) == 'function')
1785								{	c.tr_clicked();
1786									c.tr_clicked = false;
1787								}
1788							},
1789							'ctrigger'
1790						);
1791						if (!c.tr_mm)
1792						{	break;
1793						}
1794					}
1795				}
1796				return want_monitor_containers;
1797			}
1798			if (!elems && c.html?.length)
1799			{	elems = c.found_elems = try_create_container(c_id);
1800				html_attr = '';
1801			}
1802			for (let elem of elems || [])
1803			{	if (((tp) & 1))
1804				{	ns.add_style(elem, c.em);
1805					if (c.a != null)
1806					{	try
1807						{	for (let i=0; i<c.html.length; i++)
1808							{	if (i >= c.uc)
1809								{	if (c.cc>0 && !c.h_cycle)
1810									{	c.h_cycle = setInterval
1811										(	() =>
1812											{	if (c.html.length <= c.uc)
1813												{	clearInterval(c.h_cycle);
1814													c.h_cycle = null;
1815													for (let i=0; i<c.html.length; i++)
1816													{	c.html[i].attach(elem, i);
1817													}
1818												}
1819												else
1820												{	let first = c.html.shift();
1821													if (c.uc == 1)
1822													{	c.html.push(first);
1823														c.html[i-1].swap(elem, i-1, first);
1824													}
1825													else
1826													{	first.detach();
1827														c.html.push(first);
1828														c.html[i-1].attach(elem, i-1);
1829													}
1830												}
1831											},
1832											c.cc*1000
1833										);
1834									}
1835									break;
1836								}
1837								c.html[i].attach(elem, i); // this calls unblock when done
1838							}
1839							if (!noraise)
1840							{	log_event(c_id, c.a.join(), LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&64 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&64 ? LOG_EVENT_SEND_NOW : 0), 1, c.rid);
1841							}
1842						}
1843						catch (e)
1844						{	ns.error('Couldn\'t insert HTML to container '+c_id, e.message, e);
1845						}
1846						c.inited |= 1;
1847					}
1848				}
1849				if (((tp) & 8))
1850				{	ns.add_e
1851					(	elem,
1852						'onclick',
1853						() =>
1854						{	log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&8192 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8192 ? LOG_EVENT_SEND_NOW : 0), 8);
1855						},
1856						c_id
1857					);
1858					c.inited |= 8;
1859				}
1860				if (((tp) & 16))
1861				{	ns.add_e
1862					(	elem,
1863						'onclick',
1864						() =>
1865						{	click_event_page(c_id);
1866						},
1867						c_id
1868					);
1869					c.inited |= 16;
1870				}
1871				if (((tp) & 32))
1872				{	ns.add_e
1873					(	elem,
1874						'onmouseover',
1875						() =>
1876						{	log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&2 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2 ? LOG_EVENT_SEND_NOW : 0), 32);
1877						},
1878						c_id
1879					);
1880					c.inited |= 32;
1881				}
1882				if (((tp) & 2048))
1883				{	log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&32768 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&32768 ? LOG_EVENT_SEND_NOW : 0), 2048);
1884					c.inited |= 2048;
1885				}
1886				if (((tp) & 454))
1887				{	var is_input = typeof(elem.value) == 'string';
1888					if (is_input && elem.type!='hidden')
1889					{	let grab = infrequent_func
1890						(	event =>
1891							{	var value = ns.apply_ghm(elem.value, c.ghm);
1892								// `c.vl` is the last value sent for this container, as it is on the
1893								// grab_html() path. Without it the unconditional read below would
1894								// re-send the same value on every init_containers pass, and those
1895								// run on every DOM mutation.
1896								if (value && value!=c.vl && (!is_radio || elem.checked))
1897								{	c.vl = value;
1898									//	The html grab types are in this mask as well as the text-input ones,
1899									//	because this branch runs for them too: it is entered for any of
1900									//	'grab static html / grab html / grab volatile html / text input /
1901									//	text input search' whose element turns out to be an input.
1902									//	Without them the mask came out 0 for an html-typed container - the
1903									//	value travelled but was claimed by no container type, and was stored
1904									//	empty. The event was logged, so the UI showed a row with "(empty)"
1905									//	rather than nothing, which reads as "we grabbed nothing" instead of
1906									//	"this container is the wrong type".
1907									//	It matters because the panel cannot always say "this is an input":
1908									//	a selector typed by hand saves as 'grab volatile html', and that is a
1909									//	perfectly reasonable thing for a user to do. Reading an input's value
1910									//	whatever page-element variant it was saved under is what they mean
1911									//	either way - grab_html() below would only ever read its (empty) text.
1912									//	Same mask grab_html() uses, so the server sees the shape it already
1913									//	handles for a page grab.
1914									log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event.type=='blur' ? (event_send_with_delay&4096 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4096 ? LOG_EVENT_SEND_NOW : 0) : (event_send_with_delay&2048 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2048 ? LOG_EVENT_SEND_NOW : 0)), c.tp & 49606);
1915								}
1916							},
1917							1000
1918						);
1919						function add_events(elem)
1920						{	ns.add_e(elem, 'onkeyup', grab, c_id);
1921							ns.add_e(elem, 'onmouseup', grab, c_id);
1922							ns.add_e(elem, 'onblur', grab, c_id);
1923							ns.add_e(elem, 'onchange', grab, c_id);
1924							//	Read what is ALREADY in the field, not only what the visitor types into
1925							//	it. A checkout that fills in a returning customer's email, a logged-in
1926							//	profile form, a browser autofill: in all of them not one of the events
1927							//	above ever fires, and the container stayed empty forever. The
1928							//	grab_html() path (everything that is not an input) has always read
1929							//	immediately like this - inputs were the odd one out.
1930							//	A field that is empty now and filled later is still caught: these
1931							//	container types are deliberately never marked `inited` (see below), so
1932							//	init_container runs again on every DOM change, and the `c.vl` guard
1933							//	above is what keeps that from re-sending.
1934							//	With a trigger configured this line is unchanged in effect: the branch
1935							//	at the top of init_container returns early unless this pass IS the
1936							//	trigger click, so we only get here when the trigger has just fired.
1937							grab.call(elem, {type: 'blur'});
1938						}
1939						var is_radio = elem.type == 'radio';
1940						if (!is_radio)
1941						{	add_events(elem);
1942						}
1943						else
1944						{	var elems = ns.id(c.hi, true);
1945							for (var i in elems)
1946							{	if (elems[i]?.type == 'radio')
1947								{	add_events(elems[i]);
1948								}
1949							}
1950						}
1951						//c.inited |= c.tp & 49606;
1952					}
1953					else if (!c.inited_event && ((tp) & 384))
1954					{	c.inited_event = 1;
1955						c.ha = html_attr;
1956						c.onflush = function() {grab_html(c_id)};
1957					}
1958					if (grab_html(c_id) !== false)
1959					{	c.inited |= c.tp & 49216; // 'grab html' and 'grab volatile html' (and 'text input' and 'text input search' which are synonyms) must never be initialized, so grab continues
1960					}
1961				}
1962				if (((tp) & 512) && cur_visit_id) // cur_visit_id is false before the user data are ready
1963				{	var v = ns.subst_user_data(c.udf);
1964					if (html_attr=='<' || html_attr=='>')
1965					{	var node = html_attr=='<' ? elem.previousSibling : elem.nextSibling;
1966						if (node && (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE))
1967						{	node.data = v;
1968							c.inited |= 512;
1969						}
1970					}
1971					else if (html_attr)
1972					{	elem.setAttribute(html_attr, v);
1973						c.inited |= 512;
1974					}
1975					else if (typeof(elem.value) == 'string')
1976					{	elem.value = v;
1977						c.inited |= 512;
1978					}
1979					else
1980					{	elem.textContent = v;
1981						c.inited |= 512;
1982					}
1983				}
1984			}
1985			if (((tp) & 8192))
1986			{	var value = ns.apply_ghm(ns.get_param(html_id), c.ghm);
1987				if (value != '')
1988				{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&4 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4 ? LOG_EVENT_SEND_NOW : 0), tp & 8192);
1989					c.inited |= 8192;
1990				}
1991			}
1992			if (((tp) & 65536))
1993			{	var value = ns.apply_ghm(ns.get_page_grab_val._meta(html_id), c.ghm);
1994				if (value) log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&32 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&32 ? LOG_EVENT_SEND_NOW : 0), tp & 65536);
1995				c.inited |= tp & 65536;
1996			}
1997			if (((tp) & 4096))
1998			{	var value = ns.apply_ghm(ns.get_cookie(html_id), c.ghm);
1999				want_monitor_containers = !value;
2000				if (value)
2001				{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 4096);
2002					c.inited |= tp & 4096;
2003				}
2004			}
2005			if (((tp) & 537001984))
2006			{	if (window.localStorage)
2007				{	var value = ns.apply_ghm(obj_follow_path(((tp) & 536870912) ? localStorage._6senseCompanyDetails : localStorage, html_id), c.ghm);
2008					want_monitor_containers = !value;
2009					if (value)
2010					{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 537001984);
2011						c.inited |= tp & 537001984;
2012					}
2013				}
2014			}
2015			if (((tp) & 262144))
2016			{	if ('sessionStorage' in window)
2017				{	var value = ns.apply_ghm(obj_follow_path(sessionStorage, html_id), c.ghm);
2018					want_monitor_containers = !value;
2019					if (value)
2020					{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 262144);
2021						c.inited |= tp & 262144;
2022					}
2023				}
2024			}
2025			if (((tp) & 1048576))
2026			{	var value = obj_follow_path(window, html_id, undefined, !c.wv ? null : grab);
2027				//want_monitor_containers = value == null;
2028				want_monitor_containers = true; // don't stop monitoring variables
2029				grab(value);
2030				function grab(value)
2031				{	value = ns.apply_ghm(value, c.ghm);
2032					if (value)
2033					{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 1048576);
2034						// don't stop monitoring variables
2035						//c.inited |= tp & 1048576;
2036					}
2037				}
2038			}
2039			if (((tp) & 1024))
2040			{	c.inited |= tp & 1024;
2041			}
2042			if(!c.vl) c.vl = value;
2043			if (!(c.tp & ~c.inited))
2044			{	c.found_elems = null; // not needed, so free memory
2045			}
2046
2047			return want_monitor_containers;
2048		}
2049
2050		function obj_follow_path(obj, path, set_value, onchange)
2051		{	let orig_obj = obj;
2052			let last_obj = null;
2053			let last_w = null;
2054			let not_found = false;
2055			let uninstall_watchers = [];
2056			path += '';
2057			RE_OBJ_FOLLOW_PATH.lastIndex = 0;
2058			let match;
2059			while ((match = RE_OBJ_FOLLOW_PATH.exec(path)))
2060			{	let [a, dot_w, index_w] = match;
2061				if (typeof(obj) == 'string')
2062				{	try
2063					{	obj = JSON.parse(obj);
2064					}
2065					catch (e)
2066					{	obj = undefined;
2067					}
2068				}
2069				if (typeof(obj)!='object' || !obj)
2070				{	if (set_value===undefined || !last_obj || last_w==null) // last_w==null means not matched wildcard (or initial value)
2071					{	obj = undefined;
2072						not_found = true;
2073						break;
2074					}
2075					obj = last_obj[last_w] = {};
2076				}
2077				// assume: typeof(obj) == 'object'
2078				if (index_w)
2079				{	let c = index_w.charAt(0);
2080					try
2081					{	if (c == '"')
2082						{	dot_w = JSON.parse(index_w)+'';
2083						}
2084						else if (c == "'")
2085						{	dot_w = eval(index_w)+'';
2086						}
2087						else
2088						{	dot_w = parseFloat(index_w);
2089							if (isNaN(dot_w))
2090							{	throw new Error;
2091							}
2092							dot_w += '';
2093						}
2094					}
2095					catch (e)
2096					{	throw new Error('Invalid expression: '+path);
2097					}
2098				}
2099				else
2100				{	dot_w = !dot_w ? '' : dot_w.trim();
2101					if (dot_w.indexOf('*')!=-1 || dot_w.indexOf('?')!=-1)
2102					{	let re = new RegExp
2103						(	'^'+
2104							dot_w.replace
2105							(	/[^\w\s]/g,
2106								c => c=='*' ? '[\\S\\s]*?' : c=='?' ? '[\\S\\s]' : '\\'+c
2107							)+
2108							'$'
2109						);
2110						let found = false;
2111						for (dot_w in obj)
2112						{	if (obj.hasOwnProperty(dot_w) && re.test(dot_w))
2113							{	found = true;
2114								break;
2115							}
2116						}
2117						if (!found)
2118						{	dot_w = null; // not matched wildcard
2119						}
2120					}
2121				}
2122				if (last_obj)
2123				{	install_watcher(last_obj, last_w, false);
2124				}
2125				last_obj = obj;
2126				last_w = dot_w; // null for not matched wildcard
2127				if (dot_w == null)
2128				{	obj = undefined;
2129				}
2130				else
2131				{	obj = obj[dot_w];
2132					if (obj===undefined && last_obj==window)
2133					{	// consts cannot be accessed through window object, so try eval
2134						try
2135						{	obj = eval(dot_w);
2136						}
2137						catch (e)
2138						{
2139						}
2140					}
2141				}
2142			}
2143			if (!not_found)
2144			{	// set
2145				if (set_value!==undefined && !not_found)
2146				{	if (last_obj == null)
2147					{	throw new Error("No matching variable");
2148					}
2149					obj = last_obj[last_w] = set_value;
2150				}
2151				// onchange
2152				install_watcher(last_obj, last_w, true);
2153			}
2154			else
2155			{	install_watcher(last_obj, last_w, false);
2156			}
2157			function install_watcher(last_obj, last_w, is_final)
2158			{	if (onchange && last_obj && Object.defineProperty && Object.getOwnPropertyDescriptor)
2159				{	let desc = Object.getOwnPropertyDescriptor(last_obj, last_w) || {writable: true, configurable: true, enumerable: false};
2160					if (desc.configurable && !(desc.get?.(new Mark1) instanceof Mark2)) // if watcher not yet installed
2161					{	Object.defineProperty
2162						(	last_obj,
2163							last_w,
2164							{	configurable: true,
2165								enumerable: true,
2166								get(mark)
2167								{	if (mark instanceof Mark1)
2168									{	return new Mark2;
2169									}
2170									else if ('get' in desc)
2171									{	return desc.get?.();
2172									}
2173									else
2174									{	return desc.value;
2175									}
2176								},
2177								set(new_value)
2178								{	if ('set' in desc)
2179									{	desc.set?.(new_value);
2180									}
2181									else
2182									{	desc.value = new_value;
2183									}
2184									new_value = desc.get ? desc.get() : desc.value;
2185									if (is_final)
2186									{	onchange(new_value);
2187									}
2188									else if (typeof(new_value)=='object' && new_value)
2189									{	while (uninstall_watchers.length)
2190										{	uninstall_watchers.shift()();
2191										}
2192										obj = obj_follow_path(orig_obj, path, undefined, onchange);
2193										if (obj !== undefined)
2194										{	onchange(obj);
2195										}
2196									}
2197								}
2198							}
2199						);
2200					}
2201					uninstall_watchers[uninstall_watchers.length] = function()
2202					{	Object.defineProperty(last_obj, last_w, desc);
2203					};
2204				}
2205			}
2206			// get
2207			let type = typeof(obj);
2208			if (type == 'boolean')
2209			{	obj = obj ? 1 : 0;
2210			}
2211			else if (type!='number' && type!='string')
2212			{	obj = undefined;
2213			}
2214			return obj;
2215		}
2216
2217		function try_create_container(c_id)
2218		{	if (default_containers.hasOwnProperty(c_id))
2219			{	return default_containers[c_id];
2220			}
2221			var elem = null;
2222			if (document.body)
2223			{	if (c_id==-19 || c_id==-20)
2224				{	elem = ns.new_elem
2225					(	'<div style="text-align:center"><div style="display:inline-block; text-align:left; text-align:start"></div></div>',
2226						c_id==-20 ? document.body : {parentNode: document.body, nextSibling: document.body.firstChild}
2227					).firstChild;
2228					default_containers[c_id] = elem;
2229				}
2230			}
2231			return elem ? [elem] : null;
2232		}
2233
2234		function grab_html(c_id)
2235		{	let c = ns.containers[c_id];
2236			let ha = c.ha;
2237			let obj = ns.id(c.hi);
2238			if (obj)
2239			{	let s = '';
2240				if (ha=='<' || ha=='>')
2241				{	let node = ha=='<' ? obj.previousSibling : obj.nextSibling;
2242					if (node && (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE))
2243					{	s = node.data;
2244					}
2245				}
2246				else
2247				{	s = ha ? obj.getAttribute(ha) : obj.nodeName=='META' ? obj.content : ns.inner_text(obj);
2248				}
2249				s = ns.apply_ghm(s, c.ghm);
2250				if (s)
2251				{	ns.containers[c_id].vl = s;
2252					log_event(c_id, s, ((c.tp) & 256) ? (event_send_with_delay&65536 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&65536 ? LOG_EVENT_SEND_NOW : 0) : LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&128 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&128 ? LOG_EVENT_SEND_NOW : 0), c.tp & 49600);
2253					return true; // true: grabbed
2254				}
2255			}
2256			return false; // false: not grabbed
2257		}
2258
2259		function check_for_actions()
2260		{	log_event(0, '', LOG_EVENT_SEND_NOW);
2261		}
2262
2263		function log_event(c_id, value, mode, container_mask, rid, onload, onerror) // if rid is specified then report about occupied container, otherwise initiate event
2264		{	container_mask |= 0;
2265			rid |= 0;
2266			if (c_id && !ns.containers.hasOwnProperty(c_id))
2267			{	if (conts_map.hasOwnProperty(c_id))
2268				{	// legacy format: former version was like this: log_event(html_id, ...)
2269					c_id = conts_map[c_id][0]; // take first
2270				}
2271				else if (!(c_id < 0))
2272				{	// no such container
2273					var errmsg = 'No such container: '+c_id;
2274					ns.error(errmsg);
2275					(onerror || onload)?.(errmsg);
2276					return;
2277				}
2278			}
2279			else if ((mode & LOG_EVENT_SEND_WITH_DELAY) && c_id && ns.containers[c_id].qs)
2280			{	// raise mode to LOG_EVENT_SEND_NOW
2281				mode &= ~LOG_EVENT_SEND_WITH_DELAY;
2282				mode |= LOG_EVENT_SEND_NOW;
2283			}
2284			if (c_id > 0)
2285			{	let value_hash = ns.strhash(value);
2286				if (value_hash === html_input_state[c_id])
2287				{	return; // value isn't changed
2288				}
2289				html_input_state[c_id] = value_hash;
2290			}
2291			flush_events(mode & LOG_EVENT_SEND_IMMED);
2292			if (c_id)
2293			{	let rid_e = rid || (cur_visit_id ? 'e'+cur_visit_id : 'e');
2294				tracker_events.add(c_id, value, mode, container_mask, rid_e);
2295			}
2296			if (mode & (LOG_EVENT_SEND_WITH_DELAY|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_IMMED))
2297			{	if (c_id && events_sent_times++ >= 100)
2298				{	return;
2299				}
2300				send_request
2301				(	mode,
2302					0,
2303					0,
2304					mode & LOG_EVENT_SEND_NOW ? 0 : null,
2305					ns.pair_funcs
2306					(	onload,
2307						error => {tracker_events.cleanup(error)} // if !onerror, in case of error cleanup(error) will be called
2308					),
2309					onerror
2310				);
2311			}
2312			else
2313			{	setTimeout
2314				(	() => {tracker_events.cleanup('Error')},
2315					5000				);
2316				onload?.();
2317			}
2318		}
2319
2320		function events_to_str(obj)
2321		{	var n_html_ids = [];
2322			for (var n_html_id in obj)
2323			{	if (obj.hasOwnProperty(n_html_id))
2324				{	n_html_ids.push(n_html_id);
2325				}
2326			}
2327			n_html_ids.sort();
2328			var str='', delim='';
2329			// event format: "{$session_time}_{$record_id}_{$container_id}_{$container_mask}_{$value}"
2330			function escape_value(str) // i want to use '&' as field delimiter, so it must not appear in value
2331			{	return (str+'').replace
2332				(	/[\\&]/g,
2333					m =>  m=='\\' ? '\\\\' : '\\a'
2334				);
2335			}
2336			var c = ns.containers;
2337			for (var i=0; i<n_html_ids.length; i++)
2338			{	// m[1]: time offset since session start; m[2]: container_mask; m[3]: rid (record id); m[4]: container_id
2339				var m = n_html_ids[i].match(/^(\d+)_(\d+)_(e?-?\d*)_(-?\d+)/);
2340				if (m)
2341				{	if
2342					(	(	c[m[4]] || (m[4]>=-43 && m[4]<=-1)
2343						)
2344						// filter empty "text input", "text input search", "grab static html", "grab html", "grab volatile html", "grab cookie", "grab url"
2345						&&
2346						!(c[m[4]] && ((c[m[4]].tp) & 537342406) && !(obj[n_html_ids[i]]+'').match(/\S/))
2347					)
2348					{	/* {$session_time}_{$record_id}_{$container_id}_{$container_mask}_{$value} */
2349						var cid = m[4];
2350						str += delim+m[1]+'_'+m[3]+'_'+cid+'_'+m[2]+'_'+escape_value(obj[n_html_ids[i]] || '');
2351						delim = '&';
2352					}
2353				}
2354			}
2355			return str;
2356		}
2357
2358		function get_protocol()
2359		{	//return (location.protocol=='https:' || ns.get_location().protocol=='https:' ? 'https:' : 'http:');
2360			return 'https:';
2361		}
2362
2363		function send_request(mode, is_load, unused, alt_timeout, onload, onerror, checker_func)
2364		{	log_new_tealium_audiences();
2365			if (is_load)
2366			{	send_request_onload = ns.pair_funcs(send_request_onload, exec_onevent);
2367			}
2368			send_request_onload = ns.pair_funcs(send_request_onload, onload);
2369			send_request_onerror = ns.pair_funcs(send_request_onerror, onerror || onload);
2370			if (is_load)
2371			{	first_log_visit_sent = true;
2372			}
2373			if (mode & LOG_EVENT_SEND_NOW)
2374			{	clearTimeout(send_timer);
2375				send_timer = null;
2376			}
2377			if (mode&(LOG_EVENT_SEND_IMMED|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_WITH_DELAY) && !is_ready && !is_load)
2378			{	mode = LOG_EVENT_SEND_WITH_DELAY;
2379				/*	And drop the immediate timeout that came with the mode we just dropped.
2380					`log_event` picks `alt_timeout` from the ORIGINAL mode -- 0 for SEND_NOW --
2381					so without this the deferral below re-fires `do_send()` 0ms later and the
2382					downgrade buys nothing at all. */
2383				alt_timeout = null;
2384			}
2385			/*	`first_log_visit_sent` means the page view was DISPATCHED, not that it came back.
2386				`is_ready` is the one that means answered (see its declaration). Bet
2386ween the two
2387				the page has no `cur_visit_id`, and st_log_visit_* refuses an events-only request
2388				for a session it holds no visit row for -- "Events not logged: this session has no
2389				page view on record". Sending on the weaker flag is what put ~15,000 events a day
2390				on guns.com (6864) into that error: its account has `grab cookie` in
2391				`event_send_now`, so container 48 fires a request of its own the moment the cookie
2392				is read, which is while the page view is still in flight.
2393
2394				Deferring instead costs at most `event_load_timeout`, and the timer path calls
2395				`do_send()` directly, so a page view that never answers still gets its events out
2396				rather than holding them for ever. Same reason the 'user data' containers and the
2397				focus event already wait for `cur_visit_id`. */
2398			if ((is_load || is_ready) && (!send_timer || mode&LOG_EVENT_SEND_IMMED))
2399			{	clearTimeout(send_timer);
2400				send_timer = null;
2401				function do_send()
2402				{	if (is_load)
2403					{	exec_commands();
2404					}
2405					var props = get_request_props(0, is_load);
2406					if (!props || !is_load && !props.ev)
2407					{	return;
2408					}
2409					var params = '';
2410					var delim = '?';
2411					for (var i in props)
2412					{	if (props.hasOwnProperty(i))
2413						{	params += delim+i+'='+encodeURIComponent(props[i]);
2414							delim = '&';
2415						}
2416					}
2417					let log_visit_url = !NEW_ELEM_NDEBUG ? "\/\/counter-backend.personyze.com\/stat-track-log-visit.js.php" : '\/\/qacounter.personyze.com\/stat-track-log-visit.js.php';
2418					if (Date.now()-before_unload_time > 4000)
2419					{	ns.load_scripts
2420						(	{	[get_protocol()+log_visit_url+params]: checker_func
2421							},
2422							() =>
2423							{	var func = send_request_onload;
2424								send_request_onload = null;
2425								send_request_onerror = null;
2426								func?.();
2427							},
2428							error =>
2429							{	// The response script did not run: blocked by an extension or a CSP,
2430								// offline, or the server answered a non-2xx (a <script> is never
2431								// executed then). Used to be completely silent - the page simply had
2432								// no personalization and no explanation for it. `error` arrives as a
2433								// string from some load_scripts paths and as an Error from others.
2434								var reason = (error && (error.message || ''+error)) || 'blocked, offline, or server error';
2435								te_errmsg(`tracker request failed, so no personalization on this page - ${reason}`);
2436								var func = send_request_onerror;
2437								send_request_onload = null;
2438								send_request_onerror = null;
2439								func?.(error);
2440							}
2441						);
2442					}
2443					else
2444					{	navigator.sendBeacon(get_protocol()+log_visit_url+params);
2445					}
2446				}
2447				if (mode & LOG_EVENT_SEND_IMMED)
2448				{	ns.reg_onready(do_send);
2449				}
2450				else
2451				{	/*	send_timer must be forgotten the moment the send it stands for happens, because
2452						the guard above reads it as "a delayed send is already pending, this one will
2453						ride along with it". Leaving the fired timer's id in place made that claim
2454						permanent: nothing else clears send_timer except a SEND_NOW event or rollback(),
2455						so after the first delayed send every later one was answered with "already
2456						pending" and silently dropped - no request, no error.
2457						A page that reloads never noticed, because each load starts a fresh
2458						send_timer. An SPA does not reload, and log_navigation() is sent as
2459						'click event page', which an account can configure as send-with-delay (5219
2460						does) - so from its second navigation on, that tab asked the server for
2461						nothing ever again: no page views, and no actions for any page the visitor
2462						went to next. Reload was the only cure, which is exactly how it was reported. */
2463					send_timer = setTimeout
2464					(	() =>
2465						{	send_timer = null;
2466							do_send();
2467						},
2468						alt_timeout==null ? 6000 : alt_timeout
2469					);
2470				}
2471			}
2472		}
2473
2474		function get_pardot_visitor_id()
2475		{	var v = document.cookie.match(/(?:^|;\s*)visitor_id\d{2,}=([^;]+)/);
2476			return v?.[1] || '';
2477		}
2478
2479		function get_request_props(is_short, is_load, no_events)
2480		{	let s=screen, n=navigator;
2481			let rotated = innerWidth>innerHeight && n.vendor=="Apple Computer, Inc." && (/\biPhone\b|\biPad\b/.test(n.userAgent) || n.maxTouchPoints>1);
2482			// props
2483			let id = make_id(is_load, no_events || !ns.containers);
2484			if (!id) return null;
2485			let events = events_to_str(id.events);
2486			ns.uid = id.id;
2487			let props =
2488			{	fr: get_f(),
2489				te: ns.te(),
2490				sr: ns.server_id,
2491				ld: is_load ? 1 : 0,
2492				ur: ns.get_location().href,
2493				rf: id.rf,
2494				id: id.id,
2495				si: id.si,
2496				ss: id.ss,
2497				nw: id.nw,
2498				st: id.st,
2499				ls: id.ls,
2500				rs: id.rs,
2501				sy: id.sy,
2502				bh: id.bh,
2503				hs: id.hs,
2504				dg: id.dg,
2505				ps: id.ps, // must be sorted in descending order
2506				ev: events,
2507				hu: ns.get_cookie('hubspotutk') || '',
2508				ao: document.cookie.match(/(?:^|;)\s*wp\d+="?([^;"]{50,150})/)?.[1] || '',
2509				pr: get_pardot_visitor_id(),
2510				tm: ns.secs(),
2511				pl: n.platform + (n.cpuClass && n.platform.indexOf(n.cpuClass)==-1 ? ' '+n.cpuClass : ''), // important to send platform regardless to is_short
2512				sc: rotated ? s.height+'x'+s.width+'x'+s.colorDepth : s.width+'x'+s.height+'x'+s.colorDepth,
2513				th: ns.hash(document.title),
2514				ck: '?',
2515				aj: '?',
2516				jv: '?',
2517				fl: -1,
2518				sl: -1,
2519				mp: -1,
2520				cn: '?',
2521				vl: -1,
2522				qt: -1
2523			};
2524			/*	Set only under consent mode, and only after grant() -- see `consent_granted`. Added
2525				after the literal rather than in it so that a request from an installation that never
2526				asked for consent mode carries no extra parameter at all. (`cn` above is unrelated:
2527				it is one of the browser-plugin probes.) */
2528			if (consent_granted)
2529			{	props.cs = 1;
2530			}
2531			/*	Hosted landing page impression token. Present ONLY on a page our own landing-page
2532				route served — it sets window._S_T_LP to a signed token naming the exact variant
2533				it rendered (see mod_www/pages/lp). Echoing it lets the server write the campaign
2534				log row the server render could not (the fork fix). Strictly guarded on the
2535				global, so every ordinary installation — the whole reason this file is hot —
2536				sends nothing. The server HMAC-verifies it; a forged value is ignored, not
2537				trusted. Only on the page-LOAD request: `!is_short` also let every EVENT ping
2538				re-send the same token (do_send passes is_short=0 regardless), and the server's
2539				duplicate suppression is a read-then-insert that two overlapping pings can race
2540				past. One view, one send. */
2541			if (is_load && typeof(window._S_T_LP) == 'string' && window._S_T_LP)
2542			{	props.lp = window._S_T_LP;
2543			}
2544			/*	The same token, arriving the only other way it can: on the URL. A page test's CONTROL
2545				group serves no page of ours — the visitor is 302'd to a page on the customer's own
2546				site — so there is no document of ours to carry a global, and the token rides in
2547				`_pz_lp` instead (mod_landing_page/impression_url.ts). Without this the control arm
2548				books no impressions at all and the whole test reports one arm against silence.
2549
2550				Cheap and quiet for everyone else: an indexOf on a string we already have, and the
2551				parameter is namespaced so nothing else produces it. The value is HMAC-verified
2552				server-side exactly like the global, so a hand-typed one is ignored.
2553
2554				The parameter is then removed — from the address bar (the visitor arrived at the
2555				customer's page and should see the customer's URL, not our plumbing, and a clean URL
2556				is also what they copy, share and bookmark), and from `ur`, the URL this request
2557				RECORDS: the props literal above captured the href with the live token still in it,
2558				which persisted a replayable signed token into reporting data and fragmented the
2559				customer's landing page into one URL row per visitor. Load pings only, same reason
2560				as the global above. */
2561			if (is_load && !props.lp && location.search.indexOf('_pz_lp=') != -1)
2562			{	try
2563				{	var _lp_u = new URL(location.href);
2564					var _lp_t = _lp_u.searchParams.get('_pz_lp');
2565					if (_lp_t)
2566					{	props.lp = _lp_t;
2567						_lp_u.searchParams.delete('_pz_lp');
2568						props.ur = _lp_u.href;
2569						if (window.history && history.replaceState)
2570						{	history.replaceState(null, '', _lp_u.href);
2571						}
2572					}
2573				}
2574				catch (e) {} // URL unsupported or a malformed href: measurement is never worth an exception on a customer's page
2575			}
2576			function get_versions()
2577			{	var a = {
2578					"QuickTime": {
2579						progID: ["QuickTime.QuickTime", "QuickTimeCheckObject.QuickTimeCheck.1"],
2580						classID: "02BF25D5-8C17-4B23-BC80-D3488ABDDC6B",
2581						getAXVInfo(obj) {
2582							var v = obj?.QuickTimeVersion ? obj.QuickTimeVersion.toString(16) : -1;
2583							return v == -1 ? -1 : v.substring(0,1);
2584						}
2585					},
2586					"VLC": {
2587						progID: [],//["VideoLAN.VLCPlugin"], popup alert in IE>=7 - do not use!
2588						classID: ""
2589					},
2590					"Windows Media": {
2591						progID: ["WMPlayer.OCX", "MediaPlayer.MediaPlayer.1"],
2592						classID: "22D6f312-B0F6-11D0-94AB-0080C74C7E95", // WMP6 -> semms to work a lot better, don't know why
2593						getAXVInfo(obj) {
2594							var v = obj?.versionInfo || "";
2595							v = parseInt(v);
2596							return isNaN(v) ? -1 : v;
2597						}
2598					},
2599					"Silverlight": {
2600						progID: ["AgControl.AgControl"],
2601						classID: ""
2602					},
2603					"Flash": {
2604						progID: ["ShockwaveFlash.ShockwaveFlash"],
2605						classID: "D27CDB6E-AE6D-11CF-96B8-444553540000"
2606					}
2607				};
2608				var f=a["Flash"].progID;
2609				var f0=f[0];
2610				for(var i=15;i>2;--i) {
2611					f[15-i]=f0+'.'+i;
2612				}
2613
2614				var getInfo = function(name) {
2615					var version = -1;
2616					var getVersionFromPlugin = function(plugin) {
2617						if (!plugin.name) plugin = { name: plugin, description: name };
2618						var matches = /[\d][\d\.]*/.exec(plugin.name);
2619						if (matches && plugin.name.indexOf("Java") == -1) return matches[0];
2620						matches = /[\d\.]+/.exec(plugin.description);
2621						return matches ? matches[0] : "";
2622					};
2623					if (navigator.plugins?.length) {
2624						for(var i=0;i<navigator.plugins.length;++i) {
2625							try
2626							{	var plugin = navigator.plugins[i];
2627								if (plugin.name.indexOf(name) != -1) {
2628									version = parseInt(getVersionFromPlugin(plugin));
2629									if (isNaN(version)) version = -1;
2630									break;
2631								}
2632							}
2633							catch (e)
2634							{
2635							}
2636						}
2637					} else {
2638						var getProgIdForAX = function(progID) {
2639							if (!progID) return null;
2640							for (var i=0; i<progID.length; i++) {
2641								try {
2642									var obj = new ActiveXObject(progID[i]);
2643									return [progID[i], obj];
2644								}
2645								catch(e) {}
2646							}
2647							return null;
2648						};
2649						var progID_obj = getProgIdForAX(a[name]?.progID);
2650						if (progID_obj) {
2651							if (a[name].getAXVInfo) {
2652								version = a[name].getAXVInfo(progID_obj[1]);
2653							} else {
2654								version = getVersionFromPlugin(progID_obj[0]);
2655							}
2656						} else {
2657							version = -1;
2658						}
2659					}
2660					return version;
2661				};
2662				var names = ['QuickTime', 'VLC', 'Windows Media', 'Silverlight', 'Flash'];
2663				var res = [];
2664				for(var i=0; i<names.length; ++i) {
2665					res[i] = getInfo(names[i]);
2666				}
2667				return res;
2668			}
2669			function yn(v)
2670			{	return v===false ? 'n' : v===true ? 'y' : '?';
2671			}
2672			if (!is_short) {
2673				var vv = get_versions();
2674				var props_ex = {
2675					ck: yn(n.cookieEnabled),
2676					aj: window.XMLHttpRequest ? 'y' : 'n',
2677					jv: yn(n.javaEnabled ? n.javaEnabled() : false),
2678					fl: vv[4],
2679					sl: vv[3],
2680					mp: vv[2],
2681					cn: yn(!!document.createElement('canvas').getContext),
2682					vl: vv[1],
2683					qt: vv[0]
2684				};
2685				for (var i in props_ex)
2686				{	if (props_ex.hasOwnProperty(i))
2687					{	props[i] = props_ex[i];
2688					}
2689				}
2690			}
2691			return props;
2692		}
2693
2694		function make_id(is_load, no_events)
2695		{	function gen_uid()
2696			{	var uid;
2697				do
2698				{	uid = Math.floor(Math.random()*0xFFFFFFFF) & 0xFFFFFFFF;
2699				}
2700				while (uid>=0 && uid<500000000);
2701				return uid;
2702			}
2703			var secs = ns.secs();
2704			var uid_obj = ns.cookie_obj('stat_track_u_id');
2705			var sid_obj = ns.cookie_obj('_stat_track_s_id');
2706			var override = get_personyze_override().u;
2707			var override_uid = false;
2708			if (override)
2709			{	override_uid = override.uid && override.uid!=uid_obj.uid;
2710				for (var prop in override)
2711				{	if (override.hasOwnProperty(prop))
2712					{	uid_obj[prop] = override[prop];
2713						if (SESSION_COOKIE_PROPS.indexOf('_'+prop) != -1)
2714						{	sid_obj['_'+prop] = override[prop];
2715						}
2716					}
2717				}
2718			}
2719			// user
2720			var uid = uid_obj.uid - 0;
2721			var st = uid_obj.st || 1;
2722			var sy = uid_obj.sy || '';
2723			var uls = uid_obj.ls;
2724			var off = uid_obj.off || '';
2725			var dg = window['_S_T_PROXY_BUFFER_KEY'] || uid_obj.dg || '';
2726			var noacts = uid_obj.noacts;
2727			var nocmps = uid_obj.nocmps; // campaign-level frequency-cap suppressions (see camp_cap_event)
2728			var f = uid_obj.f || '';
2729			var hs = uid_obj.hs-0 || 0;
2730			// session
2731			var sid = sid_obj._sid;
2732			var ls = sid_obj._ls;
2733			var bh = sid_obj._bh || '';
2734			//
2735			var nw = 3;
2736			if (!uid)
2737			{	// new user and new session
2738				uid = gen_uid();
2739				sid = secs;
2740				ls = secs;
2741			}
2742			else if (override)
2743			{	nw = override_uid ? 3 : 1;
2744			}
2745			else if (((sid_obj._la-0)+5400>secs) && (sid-0)+5400>secs)
2746			{	// old session (maybe of a new user)
2747				nw = sid_obj._inew*2; // inew can be 0 (not new user) or 1 (is new user)
2748			}
2749			else
2750			{	// new session of (already) old user
2751				sid = secs;
2752				nw = 1;
2753				st++;
2754				ls = uls;
2755			}
2756			//
2757			var new_sessions_arr = [];
2758			if (nw & 1)
2759			{	if (!is_load)
2760				{	return null; // request that came before page load (maybe false request that is noticed in FF) or that came after user erased his cookies but before he refreshed the page
2761				}
2762				// past sessions
2763				new_sessions_arr = ns.get_ps(sid);
2764			}
2765			ns.cookie_obj('stat_track_u_id', {uid, f, st, sy, ls, off, noacts, nocmps, dg, hs}, 1);
2766			var sid_obj_no_events = {};
2767			for (var i=0; i<SESSION_COOKIE_PROPS.length; i++)
2768			{	var prop = SESSION_COOKIE_PROPS[i];
2769				sid_obj_no_events[prop] = sid_obj[prop] || '';
2770				delete sid_obj[prop];
2771			}
2772			var is_using_session_storage = !!window.sessionStorage;
2773			var events = is_using_session_storage ? tracker_events.get() : sid_obj;
2774			sid_obj = nw&1 ? {_sid: sid, _inew: nw>>1, _ls: ls, _te: sid_obj_no_events._te, _te_ip: sid_obj_no_events._te_ip, _tev: sid_obj_no_events._tev} : sid_obj_no_events;
2775			// put events back, so they stay in cookie
2776			for (var prop in events)
2777			{	if (events.hasOwnProperty(prop))
2778				{	if (!is_using_session_storage)
2779					{	sid_obj[prop] = events[prop];
2780					}
2781					if (!no_events)
2782					{	if (tracker_events.sent_events[prop])
2783						{	delete events[prop];
2784						}
2785						else
2786						{	tracker_events.sent_events[prop] = Date.now();
2787						}
2788					}
2789				}
2790			}
2791			if (no_events)
2792			{	events = {};
2793			}
2794			//
2795			sid_obj._la = secs;
2796			var lurl = sid_obj._lurl;
2797			var lrfr = sid_obj._lrfr;
2798			var is_refresh = 0;
2799			if (is_load && !override)
2800			{	var curl = ns.hash(ns.get_location().href);
2801				var crfr = ns.hash(ns.get_referrer());
2802				if (lurl==curl && lrfr==crfr)
2803				{	is_refresh = 1;
2804				}
2805				sid_obj._lurl = curl;
2806				sid_obj._lrfr = crfr;
2807			}
2808			ns.cookie_obj('_stat_track_s_id', sid_obj, 1);
2809			if (is_using_session_storage && !no_events)
2810			{	tracker_events.set(events);
2811			}
2812			if (nw & 1)
2813			{	sy = new SessCnt('sy').set(0, 1);
2814				new SessCnt('noacts').set(0, 1);
2815				new SessCnt('nocmps').set(0, 1);
2816			}
2817			return {
2818				id: uid, // user_id
2819				ss: sid, // session_id
2820				si: sid_obj._si|0,
2821				nw, // is_new: is_new&1 means new session, is_new&2 means new user
2822				st, // session_counter
2823				ls, // last_session_time
2824				rs: is_refresh, // is_refresh
2825				sy, // sticky conditions
2826				rf: ns.get_referrer(), // referrer
2827				bh, // bh
2828				hs, // hs
2829				dg, // dg
2830				events, // events
2831				ps: new_sessions_arr.join(',') // past sessions
2832			};
2833		}
2834
2835		function install_focus()
2836		{	start_time = Date.now(); // presicion of 1 sec is not enough; 1.9+1.9+1.9 sec != 3 sec
2837			let curr = start_time;
2838			let focused = true;
2839			let t_on_p_total = 0; // time on page total (sec)
2840			let t_on_p_focused = 0; // time on page in focus (sec)
2841			let focus_last_call = 0;
2842			function onfocus()
2843			{	if (!focused)
2844				{	focused = true;
2845					curr = Date.now();
2846				}
2847			}
2848			function onblur()
2849			{	if (focused)
2850				{	focused = false;
2851					let time = Date.now();
2852					t_on_p_focused += time - curr;
2853					t_on_p_total = time - start_time;
2854				}
2855			}
2856			function raise_event_focus(immed)
2857			{	let secs = ns.secs();
2858				if (cur_visit_id && (immed || focus_last_call < secs-1))
2859				{	focus_last_call = secs;
2860					log_event(-2, Math.floor(t_on_p_focused/1000)+'/'+Math.floor(t_on_p_total/1000), LOG_EVENT_VALUE_OVERRIDE|(!immed ? (event_send_with_delay&16 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&16 ? LOG_EVENT_SEND_NOW : 0) : send_after_unload ? LOG_EVENT_SEND_IMMED : 0), 0, cur_visit_id);
2861				}
2862			}
2863			function do_flush_events(immed)
2864			{	let time = Date.now();
2865				t_on_p_total = time - start_time;
2866				if (focused)
2867				{	t_on_p_focused += time - curr;
2868					curr = time;
2869				}
2870				flush_events = () => {};
2871				raise_event_focus(immed);
2872				flush_events = do_flush_events;
2873			}
2874			ns.add_e(window, 'onfocus', onfocus, 'install focus');
2875			ns.add_e(window, 'onblur', onblur, 'install focus');
2876			ns.add_e
2877			(	window,
2878				'onbeforeunload',
2879				() =>
2880				{	before_unload_time = Date.now();
2881					do_flush_events(true);
2882				},
2883				'install focus'
2884			);
2885			flush_events = do_flush_events;
2886		}
2887
2888		function set_conts(containers)
2889		{	// set
2890			ns.containers = {};
2891			conts_map = {};
2892			for (var c_id in containers)
2893			{	if (containers.hasOwnProperty(c_id))
2894				{	if ('hi' in containers[c_id])
2895					{	ns.containers[c_id] = containers[c_id];
2896					}
2897					else // legacy format
2898					{	containers[c_id].hi = c_id;
2899						ns.containers[containers[c_id].id] = containers[c_id];
2900					}
2901					var html_id = containers[c_id].hi;
2902					if (!conts_map[html_id])
2903					{	conts_map[html_id] = [c_id];
2904					}
2905					else
2906					{	conts_map[html_id].push(c_id);
2907					}
2908				}
2909			}
2910			ns.reg_onready
2911			(	() =>
2912				{	init_activity_events();
2913					init_containers();
2914				}
2915			);
2916		}
2917
2918		function set_domains(domains)
2919		{	if (!ns.domains)
2920			{	if (domains == '')
2921				{	domains = ns.get_location().host;
2922					if (domains.substr(0,4) == 'www.') domains = domains.substring(4);
2923				}
2924				domains = domains.split(/\s+/);
2925				var ds = {};
2926				for (var i=domains.length-1; i>=0; i--)
2927				{	ds[domains[i]] = 1;
2928				}
2929				ns.domains = ds;
2930			}
2931		}
2932
2933		function set_user_data(user_data)
2934		{	for (var i in user_data)
2935			{	if (user_data.hasOwnProperty(i))
2936				{	ns.user_data[i] = user_data[i];
2937				}
2938			}
2939		}
2940
2941		function nth_word(str, expr)
2942		{	var arr=str.split(/\s+/), arr2=[], arr3=[];
2943			expr = expr.replace(/[^\d\+\-\*\/Nn]/g, '').replace(/(\d)[Nn]/g, '$1*n').replace(/[Nn](\d)/g, 'n*$1').substr(0, 64);
2944			try
2945			{	for (var n=0; n<arr.length*2; n++)
2946				{	var res = eval(expr.replace(/[Nn]/g, n));
2947					if (res>0 && res<=arr.length) arr2[res-1] = true;
2948				}
2949				for (n=0; n<arr.length; n++) if (arr2[n]) arr3.push(arr[n]);
2950				str = arr3.join(' ');
2951			}
2952			catch (e)
2953			{
2954			}
2955			return str;
2956		}
2957
2958		function get_f()
2959		{	var f = ns.cookie_obj('stat_track_u_id').f;
2960			return f && parseInt(((','+f).match(new RegExp(','+ns.server_id+':(\\d+)')) || [])[1] || f.indexOf(':')==-1 && f) || 0;
2961		}
2962
2963		/*	Whether the factor in the cookie is one that not every app host serves yet, so the js urls built from it
2964			must carry the 'b' suffix. Stored next to the factor rather than derived, because the very first script
2965			url of a page view is built before any log_visit answer for that view has come back.
2966		 */
2967		function get_fb()
2968		{	var f = ns.cookie_obj('stat_track_u_id').f;
2969			return !!(f && (','+f).match(new RegExp(','+ns.server_id+':\\d+b')));
2970		}
2971
2972		function set_f(f, dirty, and_load_index)
2973		{	var old_f = get_f();
2974			var obj = {};
2975			obj[ns.server_id] = {remain_sessions: f, dirty: dirty ? 1 : 0};
2976			new SessCnt('f').add(obj, 1);
2977			// Only a rising factor reloads index.js, never a change of the dirty flag alone: a flag flip means a
2978			// rebuild has just started somewhere, and the build we are already running is still a perfectly good
2979			// one. Reloading on it would make every publish stampede every open page.
2980			if (and_load_index && old_f && old_f<f && get_f()==f) // check that new factor was applied. otherwise eternal loop of reloading 'index.js' can be set
2981			{	var obj = {};
2982				obj[ns.get_js_url('index')] = null;
2983				ns.load_scripts(obj);
2984			}
2985		}
2986
2987		class SessCnt // sessions countdown
2988		{	constructor(cookie_prop)
2989			{	this.cookie_prop = cookie_prop;
2990			}
2991
2992			get()
2993			{	var str = ns.cookie_obj('stat_track_u_id')[this.cookie_prop] || '';
2994				var obj = {};
2995				var now_min = Math.floor(ns.secs()/60);
2996				str.replace
2997				(	/(\d+):([\+\-]?)(t?)(\d+)(h?)(b?)/g,
2998					($0, $1, $2, $3, $4, $5, $6) =>
2999					{	if ($3)
3000						{	/*	Absolute-expiry entry: the value is an expiry stamp in minutes since epoch
3001								(see disable_action). Expired entries are dropped right here, at read time,
3002								so a suppression can end mid-session — unlike sessions/hours counters,
3003								which are only re-examined when a new session starts.
3004								The 't' sits between the sign and the digits so that the pre-'t' tracker's
3005								regex cannot match the entry at all: a cached old build skips it (the
3006								suppression fails open) instead of reading the stamp as a sessions count.
3007							 */
3008							if (now_min < parseInt($4))
3009							{	obj[$1] = {till_min: parseInt($4), match_again: $2=='+'};
3010							}
3011							else if ($2 != '+')
3012							{	obj[$1] = {remain_sessions: -1, match_again: false}; // ended like a non-match_again counter
3013							}
3014						}
3015						else
3016						{	obj[$1] = {remain_sessions: parseInt($2+$4), match_again: $2=='+', hours: $5?1:0, dirty: $6?1:0}
3017						}
3018					}
3019				);
3020				return obj;
3021			}
3022
3023			set(obj, dec) // dec - decrement all counters (practical when starting a new session)
3024			{	if (!obj) obj = this.get();
3025				var ls = ns.cookie_obj('stat_track_u_id').ls || 0;
3026				var passed_hr = ls ? (ns.secs() - ls)/60/60 : 0;
3027				var str = '';
3028				var ended;
3029				for (var i in obj)
3030				{	if (parseInt(i) == i)
3031					{	var oi = obj[i];
3032						if (oi.till_min != null)
3033						{	// absolute-expiry entry; get() already dropped it if expired, so nothing to decrement
3034							str += i+':'+(oi.match_again ? '+' : '')+'t'+oi.till_min+',';
3035							continue;
3036						}
3037						if (dec)
3038						{	if (!oi.hours)
3039							{	ended = oi.remain_sessions==0 ? 0 : --oi.remain_sessions <= 0;
3040							}
3041							else
3042							{	ended = passed_hr > oi.remain_sessions;
3043							}
3044							if  (ended)
3045							{	if (!oi.match_again)
3046								{	oi.remain_sessions = -1; // -1 indicates "cannot be matched in future"
3047								}
3048								else
3049								{	continue;
3050								}
3051							}
3052						}
3053						// The trailing 'b' is only ever written by the 'f' (factor) counter -- see set_f(). It has no
3054						// meaning for the sticky-condition counters, and an old cookie without it simply reads as 0.
3055						str += i+':'+(oi.match_again ? '+' : '')+oi.remain_sessions+(oi.hours ? 'h' : '')+(oi.dirty ? 'b' : '')+',';
3056					}
3057				}
3058				str = str.substr(0, str.length-1);
3059				var set_cookie = {};
3060				set_cookie[this.cookie_prop] = str;
3061				ns.cookie_obj('stat_track_u_id', set_cookie);
3062				return str;
3063			}
3064
3065			clear(arr)
3066			{	var obj = this.get();
3067				for (var i=0, j=arr.length; i<j; i++)
3068				{	delete obj[arr[i]];
3069				}
3070				this.set(obj);
3071			}
3072
3073			add(obj, override)
3074			{	var cur_obj = this.get();
3075				for (var i in obj)
3076				{	if (override || !(i in cur_obj))
3077					{	cur_obj[i] = obj[i];
3078					}
3079				}
3080				this.set(cur_obj);
3081			}
3082		}
3083
3084		function find_stylesheet(sel, all)
3085		{	var found = [];
3086			for (var i=0; i<document.styleSheets.length; i++)
3087			{	try
3088				{	var style = document.styleSheets[i];
3089					var rules = style.cssRules || style.rules;
3090					if (rules?.[0]?.selectorText?.toUpperCase()?.indexOf(sel) == 0)
3091					{	if (!all) return style;
3092						found.push(style);
3093					}
3094				}
3095				catch (e)
3096				{
3097				}
3098			}
3099			return all ? found : null;
3100		}
3101
3102		function block_action(action_id, is_attached)
3103		{				if (blocks)
3104			{	blocks[action_id] = is_attached ? 2 : 1;
3105			}
3106		}
3107
3108		function unblock_action(action_id)
3109		{				if (blocks)
3110			{	delete blocks[action_id];
3111			}
3112		}
3113
3114		/**	Drop the WYSIWYG hide, whatever the containers are doing.
3115
3116			Called on its own short backstop, and again by unblock_all() - whichever comes first.
3117			Idempotent: find_stylesheet returns nothing the second time.
3118		 **/
3119		/**	The decision has arrived. Stop hiding the containers it is not going to fill.
3120
3121			`pre_init_containers` runs BEFORE the round trip, so it hides every container an action
3122			could arrive in - it has no way to know which ones will. That is right, and it is also
3123			where the waiting used to end: the release is driven by delivered actions reporting, so
3124			a container the visitor was never going to be shown anything in stayed covered for the
3125			whole of `unblock_containers_after`.
3126
3127			On personyze.com's homepage that was five containers, three of them 562, 780 and 1446
3128			pixels tall, none with a delivered action - a second of missing page for nothing
3129			(Jacob, 27 Aug 26). And because the library loads after first paint, it is not a
3130			pre-hide at all by then: it removes content the visitor is already reading.
3131
3132			Here the answer is in hand. Every container carries `c.ac`, which is authoritative -
3133			an action not in it cannot be placed there - so a container with no DELIVERED action in
3134			its list has nothing coming, and its hide can go now rather than on a timer. The rest
3135			keep theirs and are released exactly as before, when their action reports.
3136
3137			CONSERVATIVE ON PURPOSE: releasing a hide early is the one failure that matters here -
3138			the visitor sees the original content, then sees it change, which is the whole thing
3139			this machinery exists to prevent. So anything unexpected in the shape of `actions`
3140			means nothing is released and the old timing stands.
3141		 **/
3142		function narrow_container_hide(actions)
3143		{	if (!hidden_conts || !hidden_conts.length || !blocks)
3144			{	return;
3145			}
3146			/*	Each row is `[record_id, action_id, action_id, ...]` - the record 
3146id first, which is
3147				why this starts at 1. Same shape `memorize_not_executed` relies on when it writes
3148				them out as `rid:aid aid`. */
3149			if (!Array.isArray(actions))
3150			{	return;
3151			}
3152			let delivered = new Set();
3153			for (let i=0; i<actions.length; i++)
3154			{	let row = actions[i];
3155				if (!Array.isArray(row))
3156				{	return; // not the shape we know - leave everything alone
3157				}
3158				for (let j=1; j<row.length; j++)
3159				{	delivered.add(row[j]|0);
3160				}
3161			}
3162			let keep = [];
3163			for (let i=0; i<hidden_conts.length; i++)
3164			{	let {c} = hidden_conts[i];
3165				let wanted = false;
3166				if (Array.isArray(c.ac))
3167				{	for (let j=0; j<c.ac.length; j++)
3168					{	if (c.ac[j]!=null && delivered.has(c.ac[j]|0))
3169						{	wanted = true;
3170							break;
3171						}
3172					}
3173				}
3174				if (wanted)
3175				{	keep.push(hidden_conts[i]);
3176				}
3177			}
3178			if (keep.length == hidden_conts.length)
3179			{	return; // every one of them is still expecting something
3180			}
3181			hidden_conts = keep;
3182			let found = find_stylesheet('#-S-T-H', true);
3183			for (let i=0; i<found.length; i++)
3184			{	let st = found[i];
3185				st.disabled = true;
3186				st.ownerNode?.parentNode?.removeChild(st.ownerNode);
3187			}
3188			if (keep.length)
3189			{	let plain = '', evil = '';
3190				for (let i=0; i<keep.length; i++)
3191				{	let {html_id} = keep[i];
3192					if (html_id.indexOf('[')!=-1 || html_id.indexOf('(')!=-1)
3193					{	evil += ', ' + html_id;
3194					}
3195					else
3196					{	plain += ', ' + html_id;
3197					}
3198				}
3199				add_stylesheet('#-S-T-H'+plain+' {visibility:hidden !important}');
3200				add_stylesheet('#-S-T-H'+evil+' {visibility:hidden !important}');
3201			}
3202		}
3203
3204		function unblock_qelem()
3205		{	clearTimeout(unblock_qelem_timer);
3206			let found = find_stylesheet('#-S-T-Q', true);
3207			for (let i=0; i<found.length; i++)
3208			{	let c = found[i];
3209				c.disabled = true;
3210				c.ownerNode?.parentNode?.removeChild(c.ownerNode);
3211			}
3212		}
3213
3214		function unblock_all()
3215		{				if (blocks && (force_unblock_all || !Object.keys(blocks).length))
3216			{	unblock_qelem();
3217				let found = find_stylesheet('#-S-T-H', true);
3218				for (let i=0; i<found.length; i++)
3219				{	let c = found[i];
3220					c.disabled = true;
3221					c.ownerNode?.parentNode?.removeChild(c.ownerNode);
3222					blocks = null;
3223				}
3224			}
3225		}
3226
3227		/**	Can any action ever be put into this container?
3228
3229			`c.ac` is the list of actions that may render into it, and it is authoritative rather than
3230			advisory: placing an action looks the container up by exactly this list
3231			(`c.ac.indexOf(action_id)`), so an action absent from it cannot arrive here by any route.
3232			An empty one therefore means a hide that is waiting for something that cannot happen - and
3233			it waits the full `unblock_containers_after`, because the release is driven by an action
3234			reporting and no action is coming.
3235
3236			These are placeholders whose actions were deleted, and they accumulate. On personyze.com's
3237			own account two of them - the nav spacer and the footer - had been hiding themselves on
3238			every page view of every page, indefinitely, for nobody. Found while chasing a flicker in
3239			the header (Jacob, 27 Aug 26): five containers were hidden on that page and not one had an
3240			action delivered to the visit.
3241
3242			The list is stored with a null in it when there are none (`ac:[null]`), so a length check is
3243			not enough and never was.
3244		 **/
3245		function add_stylesheet(cssText)
3246		{	var s = document.createElement('style');
3247			s.setAttribute("type", "text/css");
3248			if (s.styleSheet) // IE does it this way
3249			{	s.styleSheet.cssText = cssText;
3250			}
3251			else
3252			{	s.appendChild(document.createTextNode(cssText));
3253			}
3254			document.getElementsByTagName('head')[0].appendChild(s);
3255		}
3256
3257		function can_hold_an_action(c)
3258		{	if (Array.isArray(c.ac))
3259			{	for (let i=0; i<c.ac.length; i++)
3260				{	if (c.ac[i] != null)
3261					{	return true;
3262					}
3263				}
3264			}
3265			return false;
3266		}
3267
3268		/**	Has the visitor actually SEEN the page content yet?
3269
3270			This is the question the pre-hide's whole existence turns on. Hiding an element the
3271			visitor has never seen costs nothing and buys the anti-flicker; hiding one they are
3272			already looking at IS a flicker - the content vanishes mid-read and comes back
3273			changed or unchanged a moment later. Which of the two we are about to do depends on
3274			one thing only: whether a frame with this content has reached the screen.
3275
3276			Two ways the answer is "no" even on a page that is loading normally:
3277
3278			- No paint has happened. The browser says so directly (paint timing entries), and
3279			  the sync-install case says it structurally: the library running before <body>
3280			  exists means nothing can have painted.
3281			- The snippet's own inline pre-hide (`#pz-prehide`) has covered these elements since
3282			  the FIRST BYTE of the page. Frames have painted, but the covered elements were in
3283			  none of them - taking over that hide shows the visitor nothing new, and declining
3284			  to take it over would REVEAL unpersonalized content mid-decision, manufacturing
3285			  the exact flash the snippet author paid to prevent.
3286
3287			CONSERVATIVE BY CONSTRUCTION: "seen" requires PROOF (a paint-timing entry, in a
3288			browser that records them). No paint API, an error, an empty list - all read as
3289			"not seen", and the pre-hide behaves exactly as it always did. The gate can only
3290			ever remove a hide that would have been applied to visible content, never add one.
3291		 **/
3292		function content_already_seen()
3293		{	if (document.getElementById('pz-prehide'))
3294			{	return false;
3295			}
3296			try
3297			{	if (performance.getEntriesByType('paint').length)
3298				{	return true;
3299				}
3300			}
3301			catch (e)
3302			{
3303			}
3304			return false;
3305		}
3306
3307		function pre_init_containers(actions_qelem_ids)
3308		{	if (!window['_S_T_ACTIONS_DISABLED'])
3309			{	/* hide initial content of containers and try to set style before containers become visible on page */
3310				blocks = {};
3311				hidden_conts = [];
3312				var stylesheet = ''; // container styles (for empty/occupied containers)
3313				var stylesheet_hide = ''; // block containers
3314				var stylesheet_hide_evil = ''; // block containers for evil selectors (otherwise MSIE explodes)
3315				/*	THE WYSIWYG SELECTORS HIDE SEPARATELY, and are released separately.
3316
3317					`actions_qelem_ids` is account-wide and page-blind: every element any page-editor
3318					action has ever touched, anywhere on the site, pre-hidden on every page. That is
3319					fine while it lasts a frame. It stopped being fine when one action held the page -
3320					the hide is ONE stylesheet, so an action waiting on a template held the header of
3321					a page it has nothing to do with for a full `unblock_containers_after`.
3322
3323					A container is worth waiting a second for: something is going to be attached to it
3324					and the wait is the point. A WYSIWYG change is not - it is applied synchronously
3325					inside the action's `onexec`, so if it has not happened by now it is not going to
3326					happen this pass. Hence its own sheet, its own sentinel, and its own much shorter
3327					backstop. */
3328				var stylesheet_hide_q = '';
3329				var stylesheet_hide_q_evil = '';
3330				function add_hide(html_id, is_qelem)
3331				{	var evil = html_id.indexOf('[')!=-1 || html_id.indexOf('(')!=-1;
3332					if (is_qelem)
3333					{	if (evil) stylesheet_hide_q_evil += ', ' + html_id;
3334						else      stylesheet_hide_q += ', ' + html_id;
3335					}
3336					else if (evil)
3337					{	stylesheet_hide_evil += ', ' + html_id;
3338					}
3339					else
3340					{	stylesheet_hide += ', ' + html_id;
3341					}
3342				}
3343				var {containers} = ns;
3344				for (var c_id in containers)
3345				{	var c = containers[c_id];
3346					if (((c.tp) & 1))
3347					{	var style = c.em;
3348						var html_id = c.hi;
3349						if (typeof(c.em) == 'string')
3350						{	if (html_id.join)
3351							{	html_id = ns.qelem_id_to_css(html_id);
3352							}
3353							if (typeof(html_id) == 'string')
3354							{	//if (html_id.charAt(0)!='#' && html_id.charAt(0)!='.') html_id = '#' + html_id;
3355								if (html_id.indexOf('{')==-1 && html_id.indexOf('}')==-1 && c.em.indexOf('{')==-1 && c.em.indexOf('}')==-1)
3356								{	stylesheet += html_id+'{'+c.em+'}\n';
3357								}
3358								if (can_hold_an_action(c))
3359								{	hidden_conts.push({c: c, html_id: html_id});
3360									add_hide(html_id);
3361								}
3362							}
3363						}
3364					}
3365				}
3366				// actions_qelem_ids
3367				for (var i=0; i<actions_qelem_ids.length; i++)
3368				{	try
3369					{	add_hide(ns.qelem_id_to_css(typeof(actions_qelem_ids[i])=='string' && actions_qelem_ids[i].charAt(0)=='[' ? eval(actions_qelem_ids[i]) : actions_qelem_ids[i]), true);
3370					}
3371					catch (e)
3372					{
3373					}
3374				}
3375				// inject
3376				stylesheet_hide = '#-S-T-H'+stylesheet_hide+' {visibility:hidden !important}';
3377				stylesheet_hide_evil = '#-S-T-H'+stylesheet_hide_evil+' {visibility:hidden !important}';
3378				stylesheet_hide_q = '#-S-T-Q'+stylesheet_hide_q+' {visibility:hidden !important}';
3379				stylesheet_hide_q_evil = '#-S-T-Q'+stylesheet_hide_q_evil+' {visibility:hidden !important}';
3380				add_stylesheet(stylesheet);
3381				add_stylesheet(stylesheet_hide);
3382				add_stylesheet(stylesheet_hide_evil);
3383				add_stylesheet(stylesheet_hide_q);
3384				add_stylesheet(stylesheet_hide_q_evil);
3385				/*	THE HANDOVER, and AFTER our own hide is in place - not before, or the elements
3386					are uncovered for a frame and the flicker we came to prevent happens on the way
3387					in. The pasted snippet may carry an inline pre-hide of these same elements: it
3388					starts at the first byte of the page, where this library cannot, because it has
3389					to download first. Ours is up now, so theirs comes out.
3390
3391					Absent on an installation whose code predates it, which simply means the hiding
3392					starts when the library does, exactly as it always did. */
3393				var prehide = document.getElementById('pz-prehide');
3394				if (prehide && prehide.parentNode)
3395				{	prehide.parentNode.removeChild(prehide);
3396				}
3397			}
3398		}
3399
3400		function reg_unblock()
3401		{	ns.reg_onready
3402			(	() =>
3403				{						if (blocks)
3404					{	for (var action_id in blocks)
3405						{	if (blocks[action_id] != 2)
3406							{	delete blocks[action_id];
3407							}
3408						}
3409						unblock_all();
3410					}
3411				}
3412			);
3413		}
3414
3415		function memorize_not_executed()
3416		{	var ane = ns.cookie_obj('_stat_track_s_id')._ane;
3417			ns.cookie_obj('_stat_track_s_id', {_ane: ''});
3418			ns.add_e
3419			(	window,
3420				'onbeforeunload',
3421				function()
3422				{	var q = actions_q;
3423					for (var i=0; i<q.length; i++)
3424					{	q[i] = q[i].join(' ').replace(' ', ':');
3425					}
3426					var ane = q.join(';');
3427					actions_q = [];
3428					if (ane != ' ')
3429					{	ns.cookie_obj('_stat_track_s_id', {_ane: ane});
3430					}
3431				},
3432				'st ane'
3433			);
3434			if (ane)
3435			{	ane = ane.split(';');
3436				for (var i=0; i<ane.length; i++)
3437				{	var pos = ane[i].indexOf(':');
3438					if (pos != -1)
3439					{	var rid = ane[i].substr(0, pos);
3440						var aids = ane[i].substring(pos+1).split(' ');
3441						for (var j=0; j<aids.length; j++)
3442						{	log_event(-11, aids[j], LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
3443						}
3444					}
3445				}
3446			}
3447		}
3448
3449		function log_visit_2()
3450		{				var {new_f, factor_dirty, visit_id, actions, clear_conds, clear_actions} = ins_status;
3451						if (want_new_navigation)
3452			{	want_new_navigation = false;
3453				onend();
3454				install_focus();
3455				memorize_not_executed();
3456				html_input_state = {};
3457				events_sent_times = 0;
3458			}
3459			cur_visit_id = visit_id || 0;
3460			// process received data
3461			set_f(new_f, factor_dirty, 1);
3462			// add sticky conditions
3463			if (current_conditions)
3464			{	var obj = {};
3465				for (var i in current_conditions)
3466				{	if (current_conditions.hasOwnProperty(i) && current_conditions[i]) // if is sticky
3467					{	obj[i.substring(1)] = {remain_sessions: current_conditions[i][0], match_again: current_conditions[i][1], hours: current_conditions[i][2]};
3468					}
3469				}
3470				new SessCnt('sy').add(obj);
3471			}
3472			//
3473			if (clear_conds?.length)
3474			{	new SessCnt('sy').clear(clear_conds);
3475			}
3476			if (clear_actions?.length)
3477			{	do_clear_actions(clear_actions);
3478			}
3479			var call_balancer = is_ready ? null : new ns.CallBalancer(-1, reg_unblock);
3480			var bypass_others = exec_actions
3481			(	actions,
3482				() =>
3483				{	call_balancer?.dec(),
3484					reset_conts_retry(); // fresh actions and conditions: a container that kept coming up empty may be placeable now
3485					init_containers();
3486					unblock_all();
3487				}
3488			);
3489			if (bypass_others && ondropcss)
3490			{	var prev = ondropcss;
3491				ondropcss = null;
3492				setTimeout(prev, 3000);
3493			}
3494			exec_ondropcss();
3495						if (!is_ready)
3496			{	call_balancer.inc();
3497				is_ready = true;
3498			}
3499			exec_onevent();
3500		}
3501
3502		var debug_time_start = 0;
3503		function debug_time(message)
3504		{	if (!debug_time_start)
3505			{	debug_time_start = Date.now();
3506			}
3507			var diff = Date.now() - debug_time_start;
3508			ns.info(message+': '+diff);
3509		}
3510
3511		function logmsg(msg)
3512		{	console.log('Personyze: '+msg);
3513		}
3514
3515		function errmsg(msg)
3516		{	// console.error (not .log) so that these survive a console filtered to errors,
3517			// and so that a stack trace points at what actually failed
3518			console.error('Personyze: '+msg);
3519		}
3520
3521		function te_errmsg(msg)
3522		{	// Only for people in testing mode - site visitors must not see these.
3523			// Guarded, because an error can be reported before the lib body has assigned te_eff().
3524			if (ns.te_eff && ns.te_eff())
3525			{	errmsg(msg);
3526			}
3527		}
3528
3529		function make_url_kv(k, v)
3530		{	v = !v ? '' : v.trim();
3531			return !v ? '' : encodeURIComponent(k)+'='+encodeURIComponent(v)+'&';
3532		}
3533
3534		function submit(action_id, data, onload, onerror)
3535		{	action_id |= 0;
3536			try
3537			{	if (!action_id)
3538				{	throw new Error('No action ID given');
3539				}
3540				else
3541				{	var post = '';
3542					for (var p in data)
3543					{	if (data.hasOwnProperty(p))
3544						{	var v = data[p];
3545							p = p.trim();
3546							if (v && typeof(v)!='function' && RE_GOOD_URL_PARAM.test(p))
3547							{	if (typeof(v) != 'object')
3548								{	post += make_url_kv(p, v);
3549								}
3550								else
3551								{	for (var sub_p in v)
3552									{	if (v.hasOwnProperty(sub_p) && RE_GOOD_URL_PARAM.test(sub_p))
3553										{	post += make_url_kv(p+'['+sub_p+']', v[sub_p]);
3554										}
3555									}
3556								}
3557							}
3558						}
3559					}
3560					if (!post)
3561					{	throw new Error('The form was empty');
3562					}
3563					else
3564					{	var id = make_id(0, 1);
3565						var delim = '?';
3566						var url = get_protocol()+'\/\/qacounter.personyze.com\/submit.php'+delim+`sr=${ns.server_id|0}&bh=${id.bh}&id=${id.id}&si=${id.si}&ss=${id.ss}&tm=${ns.secs()}&vi=${cur_visit_id}&ai=${action_id}&ps=${id.ps}`;
3567						var xhr = new XMLHttpRequest;
3568						xhr.open('POST', url);
3569						xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
3570						function report_error(error)
3571						{	if (!error) error = 'Request failed';
3572							ns.act_params[action_id].fire_event('onerror', error);
3573							var onerror_or_onload = onerror || onload;
3574							if (onerror_or_onload)
3575							{	onerror_or_onload(new Error(error));
3576							}
3577						}
3578						xhr.onerror = function()
3579						{	report_error();
3580						};
3581						xhr.onload = function()
3582						{	var result = this.responseText || '';
3583							var error = '';
3584							for (let sub of result.match(/\/\/[^\r\n]*[\r\n]*/g))
3585							{	let sub_t = sub.substring(2).trim();
3586								if (sub_t=='OK' || sub_t=='Error')
3587								{	error = sub_t=='OK' ? null : result.substring(result.indexOf(sub) + sub.length).trim();
3588									break;
3589								}
3590							}
3591							if (error == null)
3592							{	ns.act_params[action_id].fire_event('onextra');
3593								if (onload) onload();
3594								eval(result);
3595							}
3596							else
3597							{	report_error(error);
3598							}
3599						};
3600						xhr.send(post);
3601					}
3602				}
3603			}
3604			catch (e)
3605			{	if (onerror || onload) (onerror || onload)(e);
3606			}
3607		}
3608
3609		function exec_commands()
3610		{	let system_is_on = ns.system.is_on();
3611			// get q
3612			let q = commands_queue;
3613			let q_override_action_id_for = commands_override_action_id_for;
3614			commands_queue = [];
3615			commands_override_action_id_for = null;
3616			// do q
3617			for (let i=0, i_end=q.length; i<i_end; i++)
3618			{	let command = q[i];
3619				if (command)
3620				{	let override_action_id = q_override_action_id_for?.[i]|0;
3621					let command_name = ' '+(command[0]+'').trim().replace(RE_SPACES_G, ' ').toLowerCase()+' ';
3622					let command_args_length = command.length;
3623					let onload = null;
3624					let onerror = null;
3625					if (typeof(command[command_args_length-1]) == 'function')
3626					{	onerror = onload;
3627						onload = command[--command_args_length];
3628					}
3629					if (typeof(command[command_args_length-1]) == 'function')
3630					{	onerror = onload;
3631						onload = command[--command_args_length];
3632					}
3633					let tokens = {};
3634					let n_tokens = 0;
3635					function has(token)
3636					{	if (!tokens.hasOwnProperty(token))
3637						{	let i = command_name.indexOf(token);
3638							let yes = i!=-1 && command_name.charAt(i-1)==' ' && command_name.charAt(i+token.length)==' ';
3639							if (yes)
3640							{	command_name = command_name.substr(0, i)+command_name.substring(i+token.length+1);
3641								n_tokens++;
3642							}
3643							tokens[token] = yes;
3644						}
3645						return tokens[token];
3646					}
3647					function only(n)
3648					{	return n_tokens==n && command_name==' ';
3649					}
3650					let args = [];
3651					function get_scalar_arg(n, err_msg)
3652					{	var arg = command[1 + n];
3653						if (!(typeof(arg)=='number' || typeof(arg)=='string' && (arg = (arg+'').trim())))
3654						{	throw new Error('Command '+command[0]+': '+err_msg);
3655						}
3656						args[n] = arg;
3657					}
3658					function get_object_arg(n, err_msg)
3659					{	let arg = command[1 + n];
3660						if (!(arg && typeof(arg)=='object'))
3661						{	throw new Error('Command '+command[0]+': '+err_msg);
3662						}
3663						args[n] = arg;
3664					}
3665					function get_key_value_arg(n, lc_key)
3666					{	let kv = {};
3667						for (let i=n+1; i<command_args_length;)
3668						{	let k = (command[i++]+'').trim();
3669							if (!k)
3670							{	throw new Error('Command '+command[0]+': Parameter name expected');
3671							}
3672							else
3673							{	k = k.replace(RE_SPACES_G, ' ');
3674								if (lc_key)
3675								{	k = k.toLowerCase();
3676								}
3677								kv[k] = command[i++];
3678							}
3679						}
3680						args[n] = kv;
3681					}
3682					function get_flat_array_of_scalar_arg(n, err_if_empty)
3683					{	let array = [];
3684						for (let i=n+1; i<command_args_length;)
3685						{	get_scalar_arg(i-1, 'Must be nonempty scalar value');
3686							array[array.length] = args[i++-1];
3687						}
3688						if (err_if_empty && array.length==0)
3689						{	throw new Error(err_if_empty);
3690						}
3691						args[n] = array;
3692					}
3693					try
3694					{	let interest_class = null;
3695						if (has('on') && has('ready') && only(2))
3696						{	// command: On Ready
3697							// do nothing
3698						}
3699						else if (system_is_on)
3700						{	if (has('product') && (has('viewed') || has('liked') || has('added to cart') || has('removed from cart') || has('unliked') || has('purchased')) && only(2))
3701							{	// command: Product Viewed
3702								// command: Product Liked
3703								// command: Product Added to cart
3704								// command: Product Purchased
3705								// command: Product Unliked
3706								// command: Product Removed from cart
3707								get_scalar_arg(0, 'Please, provide Product ID (number or string)');
3708								get_key_value_arg(1, true);
3709								let c_id = has('viewed') ? -22 : has('liked') ? -24 : has('added to cart') ? -23 : has('removed from cart') ? -26 : has('unliked') ? -27 : -25;
3710								let action_id = override_action_id || (args[1].action_id>0 ? parseInt(args[1].action_id) : 0);
3711								let quantity = args[1].quantity>0 ? parseInt(args[1].quantity) : 0; // 0 when purchasing means use what was set when added to cart
3712								let key = String(args[1].key || '').toLowerCase()=='sku' ? ' sku' : '';
3713								log_event(c_id, encodeURIComponent(action_id+' '+quantity+key+':'+args[0]), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror);
3714								onload = null;
3715							}
3716							else if (has('products') && (has('purchased') || has('removed from cart') || has('unliked')) && only(2))
3717							{	// command: Products Purchased
3718								// command: Products Unliked
3719								// command: Products Removed from cart
3720								let c_id = has('unliked') ? -30 : has('purchased') ? -28 : -29;
3721								log_event(c_id, '1', (event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror);
3722								onload = null;
3723							}
3724							else if (has('article') && (has('viewed') || has('liked') || has('commented') || has('unliked') || has('goal')) && only(2))
3725							{	// command: Article Viewed
3726								// command: Article Liked
3727								// command: Article Commented
3728								// command: Article Unliked
3729								// command: Article Goal
3730								get_scalar_arg(0, 'Please, provide Article ID (number or string)');
3731								get_key_value_arg(1, true);
3732								let is_viewed = has('viewed');
3733								let c_id = is_viewed ? -31 : has('liked') ? -33 : has('commented') ? -32 : has('unliked') ? -35 : -34;
3734								let action_id = override_action_id || (args[1].action_id>0 ? parseInt(args[1].action_id) : 0);
3735								log_event(c_id, encodeURIComponent(action_id+' 1:'+args[0]), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror);
3736								if (is_viewed)
3737								{	wait_for_article_delivery(args[0]);
3738								}
3739								onload = null;
3740							}
3741							else if
3742							(	(																															 has
3743											(														(interest_class='upsale category') && 													'upsell category'
3744																							)
3745																																|| has
3746											(														'upsell categories'
3747																							)
3748																																																			|| has
3749											(														(interest_class='cross category') && 													'cross-sell category'
3750																							)
3751																																|| has
3752											(														'cross-sell categories'
3753																							)
3754																																																			|| has
3755											(														interest_class=													'family'
3756																							)
3757																																|| has
3758											(														'families'
3759																							)
3760																																																			|| has
3761											(														interest_class=													'tag'
3762																							)
3763																																|| has
3764											(														'tags'
3765																							)
3766																																																			|| has
3767											(														interest_class=													'category'
3768																							)
3769																																|| has
3770											(														'categories'
3771																							)
3772																																																			|| has
3773											(														interest_class=													'interest'
3774																							)
3775																																|| has
3776											(														'interests'
3777																							)
3778																																						) && only(1)
3779							)
3780							{	// command: Interest
3781								get_flat_array_of_scalar_arg(0);
3782								interest_class = (interest_class || '').replace('cross-sale', 'cross').replace('-', '');
3783								let interests = args[0];
3784								for (var j=0; j<interests.length; j++)
3785								{	interests[j] = encodeURIComponent('0 '+interest_class+':'+interests[j]); // rank 0
3786								}
3787								log_event(-37, interests.join('&'), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&4194304 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4194304 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror);
3788								onload = null;
3789							}
3790							else if (has('event') && only(1))
3791							{	// command: Event
3792								get_scalar_arg(0, 'Please, provide event name');
3793								get_scalar_arg(1, 'Please, provide event value');
3794								ns.custom_event(args[0], args[1], onload, onerror);
3795								onload = null;
3796							}
3797							else if ((has('person') || has('user')) && has('profile') && only(2))
3798							{	// command: User profile
3799								get_key_value_arg(0);
3800								ns.social('IN', args[0], onload, onload);
3801								onload = null;
3802							}
3803							else if (has('delegate identity') && only(1))
3804							{	// command: Delegate identity
3805								get_flat_array_of_scalar_arg(0);
3806								let urls = args[0];
3807								ns.pass_cookie(urls.length ? urls : null);
3808							}
3809							else if (has('navigate') && only(1))
3810							{	// command: Navigate
3811								click_event_page('-17', onload, onerror);
3812								onload = null;
3813							}
3814							else if (has('submit') && only(1))
3815							{	// command: Submit
3816								get_scalar_arg(0, 'Please, provide action id');
3817								get_object_arg(1, 'No data given');
3818								submit(args[0], args[1], onload, onerror);
3819								onload = null;
3820							}
3821							else if (has('fire') && has('action') && only(2))
3822							{	// command: Fire action
3823								get_flat_array_of_scalar_arg(0, 'Please, provide action id');
3824								if (!(args[0] > 0))
3825								{	throw new Error('Invalid action id');
3826								}
3827								let action_ids = args[0];
3828								log_event(-38, action_ids.join(','), LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8388608 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8388608 ? LOG_EVENT_SEND_NOW : 0), 0, 0, onload, onerror);
3829								onload = null;
3830							}
3831							else if (has('when') && has('action') && has('executed') && only(3))
3832							{	// command: When action executed
3833								get_scalar_arg(0, 'Please, provide action id');
3834								if (!(args[0] > 0))
3835								{	throw new Error('Invalid action id');
3836								}
3837								let action_id = args[0]|0;
3838								if (!when_action_executed[action_id])
3839								{	when_action_executed[action_id] = [];
3840								}
3841								when_action_executed[action_id].push({onload, onerror});
3842								if (!(executed_actions[action_id] > 0))
3843								{	onload = null;
3844								}
3845							}
3846							else
3847							{	throw new Error('unknown command: '+command[0]);
3848							}
3849						}
3850						else
3851						{	onload = null;
3852						}
3853						onload?.();
3854					}
3855					catch (e)
3856					{	logmsg(e.message);
3857						onerror?.(e);
3858					}
3859				}
3860			}
3861		}
3862
3863		function get_elem_visible_prc(elem)
3864		{	// viewport
3865			var vp_left = 0;
3866			var vp_top = 0;
3867			var vp_right = document.documentElement.clientWidth;
3868			var vp_bottom = document.documentElement.clientHeight;
3869			// overflow (hidden, scroll, etc.)
3870			for (var e=elem.parentNode; e && e.nodeName!='BODY'; e=e.parentNode)
3871			{	if (e.offsetHeight<e.scrollHeight || e.offsetWidth<e.scrollWidth)
3872				{	if (ns.new_elem(e).style().overflow != 'visible')
3873					{	var rect = e.getBoundingClientRect();
3874						vp_left = Math.max(vp_left, rect.left);
3875						vp_top = Math.max(vp_top, rect.top);
3876						vp_right = Math.min(vp_right, rect.right);
3877						vp_bottom = Math.min(vp_bottom, rect.bottom);
3878					}
3879				}
3880			}
3881			// elem coordinates
3882			if (elem.nodeType == XML_TEXT_NODE)
3883			{	let range = document.createRange();
3884				range.selectNodeContents(elem);
3885				var rects = range.getClientRects();
3886			}
3887			else
3888			{	rects = elem.getClientRects();
3889			}
3890			var square = 0;
3891			var visible_square = 0;
3892			for (var i=rects.length-1; i>=0; i--)
3893			{	var rect = rects[i];
3894				var vis_v = 1 - Math.min(rect.height, Math.max(0, vp_top - rect.top) + Math.max(0, rect.bottom - vp_bottom)) / rect.height;
3895				var vis_h = 1 - Math.min(rect.width, Math.max(0, vp_left - rect.left) + Math.max(0, rect.right - vp_right)) / re
3895ct.width;
3896				var sq = rect.width*rect.height;
3897				square += sq;
3898				visible_square += !sq ? 0 : sq*vis_v*vis_h;
3899			}
3900			return square ? visible_square/square : 0;
3901		}
3902
3903		function is_elem_delivered(elem)
3904		{	try
3905			{	return get_elem_visible_prc(elem) >= 2/3;
3906			}
3907			catch (e)
3908			{	ns.error(e);
3909				return true;
3910			}
3911		}
3912
3913		/**	Call `callback` once `elem` counts as delivered (the 2/3 rule of
3914			`is_elem_delivered`), then stop. Returns a function that stops watching.
3915
3916			IntersectionObserver, not the scroll listener alone: an element reaches the
3917			viewport with no scroll at all when a tab or accordion opens it, when lazy
3918			content above it collapses, when an image finishes loading and shifts the
3919			layout, on a resize, or when it is appended into view. Every one of those
3920			was a confirmed view nobody reported, because the only trigger was
3921			`onscroll`.
3922
3923			The observer is only the TRIGGER; what counts as seen stays
3924			`is_elem_delivered`, which knows about text nodes, multi-rect elements and
3925			nested scroll containers. So this changes WHEN we look, never what counts —
3926			and where IntersectionObserver is missing, the old scroll listener still
3927			does the looking.
3928		 **/
3929		function reg_ondelivered(elem, callback)
3930		{	let done = false;
3931			let stop = () => {};
3932			function check()
3933			{	if (!done && is_elem_delivered(elem))
3934				{	done = true;
3935					stop();
3936					callback();
3937				}
3938			}
3939			// IntersectionObserver takes an Element; for a text node watch its parent
3940			// and let the check itself measure the node.
3941			let target = elem?.nodeType==1 ? elem : elem?.parentNode?.nodeType==1 ? elem.parentNode : null;
3942			if (window.IntersectionObserver && target)
3943			{	// Thresholds around the 2/3 rule rather than only 0: an element that is
3944					// already partly visible gets no entry callback, and without a middle
3945					// threshold a slow reveal could settle just under 2/3 and never fire again.
3946				let observer = new IntersectionObserver(() => {ns.pcall(check)}, {threshold: [0, 1/3, 2/3, 1]});
3947				stop = () =>
3948				{	observer?.disconnect();
3949					observer = null;
3950				};
3951				try
3952				{	observer.observe(target);
3953				}
3954				catch (e)
3955				{	ns.error(e);
3956				}
3957			}
3958			else
3959			{	let h_onscroll = reg_onscroll(check);
3960				stop = () =>
3961				{	h_onscroll?.();
3962					h_onscroll = null;
3963				};
3964			}
3965			return () => {if (!done) stop()};
3966		}
3967
3968		function wait_for_article_delivery(internal_id)
3969		{	if (articles_delivered.indexOf(internal_id) == -1)
3970			{	const WAIT_TIME = 10_000;
3971				const THRESHOLD = 0.75;
3972				let article_elem = document.querySelector('article');
3973				if (!article_elem)
3974				{	// Find element that includes the biggest picture, and the biggest text inside it
3975					let big_imgs = [...document.querySelectorAll('img')].map
3976					(	e =>
3977						{	let {width, height} = e.getBoundingClientRect();
3978							let {opacity, visibility} = getComputedStyle(e);
3979							return [e, opacity<1 || visibility=='hidden' ? 0 : width * height];
3980						}
3981					).sort((a, b) => b[1] - a[1]);
3982
3983					let big_texts = [...document.querySelectorAll('p,h1,h2,h3,h4,h5,h6,b,strong,u,em,th,td,caption')].map
3984					(	e =>
3985						{	let {fontSize, fontWeight, opacity, visibility} = getComputedStyle(e);
3986							return [e, opacity<1 || visibility=='hidden' ? 0 : parseFloat(fontSize)*1000 + 1*fontWeight];
3987						}
3988					).sort((a, b) => b[1] - a[1]);
3989
3990					function find_common_elem(e1, e2)
3991					{	if (e1 && e2)
3992						{	for (let p1=e1.parentNode; p1 && p1!=document.body; p1=p1.parentNode)
3993							{	if (ns.new_elem(e2).has_parent(p1))
3994								{	return p1;
3995								}
3996							}
3997						}
3998					}
3999
4000					article_elem = find_common_elem(big_imgs[0], big_texts[0]) || document.body;
4001				}
4002
4003				let till = Date.now() + WAIT_TIME;
4004				let h_onscroll = null;
4005
4006				clearTimeout(articles_delivered_timer);
4007				if (covered_prc() >= THRESHOLD)
4008				{	iter();
4009				}
4010				else
4011				{	h_onscroll = ns.add_e(window, 'onscroll', iter, 'article delivery');
4012				}
4013
4014				function covered_prc()
4015				{	let {top, bottom} = article_elem.getBoundingClientRect();
4016					return (innerHeight-top) / (bottom-top);
4017				}
4018
4019				function iter()
4020				{	if (covered_prc() >= THRESHOLD)
4021					{	let rem = Math.max(0, till - Date.now());
4022						h_onscroll?.remove();
4023						clearTimeout(articles_delivered_timer);
4024						articles_delivered_timer = setTimeout
4025						(	() =>
4026							{	if (articles_delivered.length >= 10)
4027								{	articles_delivered.shift();
4028								}
4029								articles_delivered.push(internal_id);
4030								log_event(-41, encodeURIComponent('0 1:'+internal_id), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&134217728 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&134217728 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id);
4031							},
4032							rem
4033						);
4034					}
4035				}
4036			}
4037		}
4038
4039		function reg_onscroll(func)
4040		{	if (!onscroll_funcs)
4041			{	onscroll_funcs = [];
4042				h_onscroll = ns.add_e
4043				(	window,
4044					'onscroll',
4045					infrequent_func
4046					(	() =>
4047						{	if (onscroll_funcs)
4048							{	for (var i=0, i_end=onscroll_funcs.length; i<i_end; i++)
4049								{	ns.pcall(onscroll_funcs[i]);
4050								}
4051							}
4052						},
4053						300
4054					),
4055					'reg_onscroll'
4056				);
4057			}
4058			onscroll_funcs.push(func);
4059			return () =>
4060			{	if (onscroll_funcs)
4061				{	var i = onscroll_funcs.indexOf(func);
4062					if (i != -1)
4063					{	onscroll_funcs.splice(i, 1);
4064						if (!onscroll_funcs.length)
4065						{	onscroll_funcs = null;
4066							h_onscroll.remove();
4067							h_onscroll = null;
4068						}
4069					}
4070				}
4071			};
4072		}
4073
4074		function rollback(including)
4075		{	onend();
4076			clearTimeout(send_timer);
4077			send_timer = null;
4078			// block
4079			clearTimeout(unblock_all_timer);
4080			force_unblock_all = true;
4081			unblock_all();
4082			// actions
4083			for (let a in timers_ac)
4084			{	clearTimeout(timers_ac[a]);
4085			}
4086			timers_ac = {};
4087			// event handlers bound to elements
4088			function cleanup_iter(subelem)
4089			{	let evts = subelem._S_T_EVT;
4090				if (evts)
4091				{	for (let html_id in evts)
4092					{	let h = evts[html_id];
4093						try {h.remove()} catch (e) {ns.error(e)} // "h" may not have remove(), if it's inserted through Object.prototype
4094					}
4095					delete subelem._S_T_EVT;
4096				}
4097			}
4098			cleanup_iter(window);
4099			cleanup_iter(document.body);
4100			// containers
4101			let {containers} = ns;
4102			for (let c_id in containers)
4103			{	if (containers.hasOwnProperty(c_id))
4104				{	let c = containers[c_id];
4105					clearInterval(c.h_cycle);
4106					c.h_cycle = null;
4107				}
4108			}
4109			clearInterval(conts_timer);
4110			ns.new_elem(document.body, cleanup_iter);
4111			// lno
4112			ns.lno?.();
4113			// uacss
4114			ns.uacss.rollback();
4115			//
4116			h_onscroll?.remove();
4117			h_onscroll = null;
4118			clearTimeout(articles_delivered_timer);
4119		}
4120
4121		function add_action_to_history(action_id, rules)
4122		{	if (window.localStorage)
4123			{	let cur_time = Math.floor(Date.now() / 1000);
4124
4125				// 1. Read actions_history from storage
4126				if (!actions_history)
4127				{	try
4128					{	let obj = JSON.parse(localStorage.stat_track_actions);
4129						actions_history = {};
4130						// convert from storage format
4131							// i'll use 10-second presicion to save 1 digit, and to ignore too shortly presented actions
4132						for (let a in obj)
4133						{	if (a > 0)
4134							{	let history = obj[a];
4135								let at = history[0];
4136								let i, i_end=history.length;
4137								for (i=1; i<i_end; i++)
4138								{	at += history[i]*10;
4139									history[i] = at;
4140								}
4141								// delete old
4142								let threshold = cur_time - 2678400;
4143								for (i=0; i<i_end && history[i]<threshold; i++);
4144								history.splice(0, i);
4145								if (history.length)
4146								{	actions_history[a] = history;
4147								}
4148							}
4149						}
4150					}
4151					catch (e)
4152					{	actions_history = {};
4153					}
4154				}
4155
4156				// 2. Can execute this action now?
4157				if (rules)
4158				{	function show_then_break({show, brk})
4159					{	let history = actions_history[action_id];
4160						if (history)
4161						{	let i = history.length - 1;
4162							let showing_till = history[i];
4163							if (cur_time-showing_till < brk) // if not passed brk seconds
4164							{	while (i>0 && showing_till-history[i-1]<=show) i--;
4165								let showing_since = history[i];
4166								if (cur_time-showing_since >= show)
4167								{	return false;
4168								}
4169							}
4170						}
4171						return true;
4172					}
4173
4174					function time_since_action_gt({action_id, time, is_first})
4175					{	let history = actions_history[action_id];
4176						if (!history)
4177						{	return true;
4178						}
4179						let at = history[is_first ? 0 : history.length-1];
4180						return cur_time-at > time;
4181					}
4182
4183					function time_since_action_lt({action_id, time, is_first})
4184					{	let history = actions_history[action_id];
4185						if (!history)
4186						{	return false;
4187						}
4188						let at = history[is_first ? 0 : history.length-1];
4189						return cur_time-at <= time;
4190					}
4191
4192					let yes =
4193					(	(!rules.show_then_break || show_then_break(rules.show_then_break))
4194						&&
4195						(!rules.time_since_action_gt || time_since_action_gt(rules.time_since_action_gt))
4196						&&
4197						(!rules.time_since_action_lt || time_since_action_lt(rules.time_since_action_lt))
4198					);
4199
4200					if (!yes)
4201					{	return false; // can not execute
4202					}
4203				}
4204
4205				// 3. Add to history
4206				actions_history[action_id] = (actions_history[action_id] || []).concat([cur_time]);
4207
4208				// 4. Store actions_history
4209				let obj = {};
4210				for (let a in actions_history)
4211				{	if (a > 0)
4212					{	let history = actions_history[a];
4213						let history_2 = [history[0]];
4214						for (let i=1; i<history.length; i++)
4215						{	let offset = Math.floor((history[i] - history[i-1]) / 10); // 10-second presicion
4216							if (offset > 0)
4217							{	history_2[i] = offset;
4218							}
4219							else
4220							{	history.splice(i--, 1);
4221							}
4222						}
4223						obj[a] = history_2;
4224					}
4225				}
4226				localStorage.stat_track_actions = JSON.stringify(obj);
4227			}
4228			return true; // can execute
4229		}
4230
4231		/**	Campaign-level frequency caps. `ns.camp_caps` is emitted into the account's
4232			compiled index.js from each condition's `freq_cap_def` column:
4233			{caps: {<cond_id>: {v, k, c, e, ws, wt, hs, ht, n: [action ids]}}, acts: {<action_id>: {d: [cond ids], f}}}
4234			- caps: v/k/c = views/clicks/closes thresholds (0 = off); e = 1 counts views
4235			  at delivery rather than actual display; the counting window is ws sessions
4236			  (1 = this session) or wt seconds; on threshold the campaign is hidden for
4237			  hs sessions or ht seconds, and the counters start over — a fixed window
4238			  that resets, not a sliding one.
4239			- acts: d = capped campaigns delivering the action, f = 1 when an uncapped
4240			  campaign also delivers it (then the action is never blocked, because that
4241			  campaign is entitled to show it).
4242			Counts live in localStorage (per-origin, like actions_history); the
4243			suppression entry goes to the 'nocmps' sessions-countdown cookie, so once
4244			tripped the hide holds domain-wide.
4245		 **/
4246		function camp_cap_event(action_id, kind, at_exec)
4247		{	var cc = ns.camp_caps;
4248			if (cc && window.localStorage)
4249			{	var st = ns.cookie_obj('stat_track_u_id').st - 0 || 1;
4250				var cur_min = Math.floor(ns.secs() / 60);
4251				var state;
4252				try
4253				{	state = JSON.parse(localStorage.stat_track_camp_caps) || {};
4254				}
4255				catch (e)
4256				{	state = {};
4257				}
4258				var suppressed = new SessCnt('nocmps').get();
4259				var mod = false;
4260				for (var c_id in cc.caps)
4261				{	var cap = cc.caps[c_id];
4262					var threshold = kind=='v' ? cap.v : kind=='k' ? cap.k : cap.c;
4263					// views honour the per-cap "count at delivery vs at display" choice; a click is a click and a close is a close either way
4264					if (threshold > 0 && (kind!='v' || !cap.e == !at_exec) && cap.n.indexOf(action_id|0) != -1 && !(c_id in suppressed))
4265					{	var cnt = (state[c_id] = state[c_id] || {})[kind];
4266						// start a fresh window on first count, or when the old window expired
4267						if (!cnt || !cnt[0] || (cap.wt > 0 ? cur_min > cnt[1] : st > cnt[1]))
4268						{	cnt = [0, cap.wt > 0 ? cur_min + Math.ceil(cap.wt/60) : st + (cap.ws > 1 ? cap.ws : 1) - 1];
4269						}
4270						cnt[0]++;
4271						state[c_id][kind] = cnt;
4272						mod = true;
4273						if (cnt[0] >= threshold)
4274						{	var set_cookie = {};
4275							set_cookie[c_id] = cap.ht > 0 ? {till_min: Math.ceil((ns.secs()+cap.ht)/60), match_again: 1} : {remain_sessions: cap.hs > 1 ? cap.hs : 1, match_again: 1};
4276							new SessCnt('nocmps').add(set_cookie, 1);
4277							delete state[c_id]; // both counters start over once the hide expires
4278						}
4279					}
4280				}
4281				if (mod)
4282				{	try
4283					{	localStorage.stat_track_camp_caps = JSON.stringify(state);
4284					}
4285					catch (e)
4286					{
4287					}
4288				}
4289			}
4290		}
4291
4292		/**	Whether every capped campaign that delivers this action is currently hidden
4293			by its frequency cap (and no uncapped campaign delivers it too).
4294		 **/
4295		function camp_cap_blocked(action_id)
4296		{	var cc = ns.camp_caps;
4297			var e = cc && cc.acts && cc.acts[action_id];
4298			if (e && !e.f)
4299			{	var suppressed = new SessCnt('nocmps').get();
4300				var i = e.d.length - 1;
4301				while (i>=0 && e.d[i] in suppressed) i--;
4302				return i < 0;
4303			}
4304			return false;
4305		}
4306
4307		function add_to_cart_or_to_wishlist(what, products, action_id, arg)
4308		{	function done()
4309			{	clearTimeout(h_done);
4310				commands_cur_override_action_id = 0;
4311			}
4312			try
4313			{	if (products!=null && !Array.isArray(products))
4314				{	products = [products];
4315				}
4316				// convert products
4317				if (products)
4318				{	let products_valid = [];
4319					for (let i=0, i_end=products.length; i<i_end; i++)
4320					{	let obj = products[i];
4321						if (obj)
4322						{	if (typeof(obj) != 'object')
4323							{	obj = {internal_id: obj};
4324							}
4325							if (!(obj.quantity >= 1))
4326							{	obj.quantity = 1;
4327							}
4328							obj.internal_id += '';
4329							obj.quantity |= 0;
4330							products_valid.push(obj);
4331						}
4332					}
4333					products = products_valid;
4334				}
4335				//
4336				if (!products || !products.length)
4337				{	throw new Error('No products provided');
4338				}
4339				var add_func = what=='add_to_cart' ? add_to_cart_func : what=='add_to_wishlist' ? add_to_wishlist_func : remove_from_cart_func;
4340				if (!add_func)
4341				{	if (what == 'add_to_cart')
4342					{	throw new Error('No add to cart function defined in account');
4343					}
4344					else if (what == 'remove_from_cart')
4345					{	throw new Error('No remove from cart function defined in account');
4346					}
4347				}
4348				var success_html = what=='add_to_cart' ? add_to_cart_success_html : what=='add_to_wishlist' ? add_to_wishlist_success_html : '';
4349				var error_html = what=='add_to_cart' ? add_to_cart_error_html : what=='add_to_wishlist' ? add_to_wishlist_error_html : '';
4350				commands_cur_override_action_id = action_id;
4351				commands_cur_manually_reported = false;
4352				var res = add_func ? add_func(ns.clone_obj(products), arg) : Promise.resolve();
4353				if (!(res instanceof Promise))
4354				{	throw new Error(`${what}() must return Promise object`);
4355				}
4356				var h_done = setTimeout(done, 10000);
4357				return res.then
4358				(	result =>
4359					{	done();
4360						if (success_html)
4361						{	ns.new_elem(success_html, document.body);
4362						}
4363						if (!commands_cur_manually_reported)
4364						{	return new Promise
4365							(	resolve =>
4366								{	for (var i=0, i_end=products.length; i<i_end; i++)
4367									{	let args = [what=='add_to_cart' ? 'Product added to cart' : what=='add_to_wishlist' ? 'Product Liked' : 'Product removed from cart', products[i].internal_id];
4368										if (action_id > 0)
4369										{	args.push('action_id');
4370											args.push(action_id);
4371										}
4372										if (i == i_end-1)
4373										{	args.push(() => {resolve(result)});
4374										}
4375										ns.push(args);
4376									}
4377								}
4378							);
4379						}
4380						else
4381						{	return result;
4382						}
4383					},
4384					error =>
4385					{	done();
4386						if (error_html)
4387						{	ns.new_elem(error_html, document.body);
4388						}
4389						throw error;
4390					}
4391				);
4392			}
4393			catch (e)
4394			{	done();
4395				return Promise.reject(e.message);
4396			}
4397		}
4398
4399		function has_container_type(container_mask)
4400		{	for (let c_id in ns.containers)
4401			{	let c = ns.containers[c_id];
4402				if (c.tp & container_mask)
4403				{	return true;
4404				}
4405			}
4406		}
4407
4408		function ga_flush_queue()
4409		{	try
4410			{	for (let rec; rec = ga_queue.shift();)
4411				{	let [category, action, label, value, noninteraction] = rec;
4412					gtag_event(action, label);
4413				}
4414			}
4415			catch (e)
4416			{	console.error(e);
4417			}
4418		}
4419
4420		function gtag_event(action, status)
4421		{	let parts = action.split(' - ');
4422			let event = parts.shift();
4423			let name = parts.join(' - ');
4424			if (ga_is_gtm)
4425			{	gtag('event', 'personyze_'+event.toLowerCase(), !status ? {name} : {name, status});
4426			}
4427			else
4428			{	// This is old and invalid code, but some clients rely on it
4429				let data = {event, action: name};
4430				if (status)
4431				{	data.status = status;
4432				}
4433				let dl = window[gtm_data_layer_name || 'dataLayer'];
4434				dl.push(data);
4435			}
4436		}
4437
4438		function gtag()
4439		{	let dl = window[gtm_data_layer_name || 'dataLayer'];
4440			dl.push(arguments);
4441		}
4442
4443		function gtm_hook()
4444		{	if (!gtm_inst && gtm_data_layer_name && (gtm_grab.length || has_container_type(69206016)))
4445			{	let data_layer = window[gtm_data_layer_name];
4446				if (data_layer && typeof(data_layer.push)=='function' && data_layer.push!=Array.prototype.push)
4447				{	gtm_inst = data_layer.push;
4448					data_layer.push = function()
4449					{	// 1. Sniff
4450						let args = [];
4451						for (let i=0, i_end=arguments.length|0; i<i_end; i++)
4452						{	args[i] = arguments[i];
4453							sniff(args[i]);
4454						}
4455						// 2. Forward
4456						gtm_inst.apply(window[gtm_data_layer_name], args);
4457					};
4458					for (let i=0, i_end=data_layer.length|0; i<i_end; i++)
4459					{	sniff(data_layer[i]);
4460					}
4461					function sniff(arg)
4462					{	let n_added = 0;
4463						if (arg)
4464						{	// The dataLayer half of _S_T.trigger(): a pushed {event: 'x'}
4465							// fires the custom trigger 'x' under the site's own name. Rides
4466							// this hook rather than wrapping push() a second time — note
4467							// that means it only runs where gtm_hook() installs at all,
4468							// which today is accounts with GTM grabbing configured.
4469							if (typeof(arg.event) == 'string')
4470							{	ns.trigger(arg.event);
4471							}
4472							gtm_history.unshift(arg);
4473							if (gtm_history.length > 5)
4474							{	gtm_history.pop();
4475							}
4476							if ('gtm.uniqueEventId' in arg)
4477							{	ga_history.unshift(arg);
4478								if (ga_history.length > 5)
4479								{	ga_history.pop();
4480								}
4481							}
4482							try
4483							{	// 1. E-commerce
4484								let arg_conv = conv_gtag_event(arg);
4485								let {event} = arg_conv;
4486								let {impressions, items, detail, click, add, remove, checkout, purchase} = arg_conv.ecommerce || {};
4487								add_products(impressions, 'Product Viewed', 'viewed');
4488								add_products(detail?.products, 'Product Viewed', 'viewed');
4489								add_products(click?.products, 'Product Viewed', 'viewed');
4490								add_products(add?.products, 'Product Added to cart', 'extra');
4491								add_products(remove?.products, 'Product Removed from cart', 'extra');
4492								add_products(checkout?.products, 'Product Liked', 'favorite');
4493								add_products(purchase?.products, 'Product Purchased', 'goal');
4494								if (RE_LOOKS_LIKE_ADD_TO_CART.test(event))
4495								{	add_products(items, 'Product Added to cart', 'extra');
4496								}
4497								else if (RE_LOOKS_LIKE_REMOVE_FROM_CART.test(event))
4498								{	add_products(items, 'Product Removed from cart', 'extra');
4499								}
4500								else if (RE_LOOKS_LIKE_PURCHASE.test(event))
4501								{	add_products(items, 'Product Purchased', 'goal');
4502								}
4503								else
4504								{	add_products(items, 'Product Viewed', 'viewed');
4505								}
4506								if (n_added == 0)
4507								{	zen_add_products(arg_conv, RE_LOOKS_LIKE_VIEWED, 'Product Viewed', 'viewed');
4508									zen_add_products(arg_conv, RE_LOOKS_LIKE_ADD_TO_CART, 'Product Added to cart', 'extra');
4509									zen_add_products(arg_conv, RE_LOOKS_LIKE_REMOVE_FROM_CART, 'Product Removed from cart', 'extra');
4510									zen_add_products(arg_conv, RE_LOOKS_LIKE_PURCHASE, 'Product Purchased', 'goal');
4511								}
4512								// 2. Containers
4513								for (let c_id in ns.containers)
4514								{	let c = ns.containers[c_id];
4515									if (((c.tp) & 69206016))
4516									{	if (!('hi_fn' in c))
4517										{	try
4518											{	c.hi_fn = new Function('history', 'return '+c.hi);
4519											}
4520											catch (e)
4521											{	c.hi_fn = null;
4522												ns.error(e);
4523											}
4524										}
4525										if (c.hi_fn)
4526										{	try
4527											{	let value;
4528												if (((c.tp) & 67108864) || ('gtm.uniqueEventId' in arg))
4529												{	value = ns.apply_ghm(c.hi_fn.call(arg, ((c.tp) & 67108864) ? gtm_history : ga_history), c.ghm);
4530												}
4531												if (value)
4532												{	function send()
4533													{	log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&33554432 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&33554432 ? LOG_EVENT_SEND_NOW : 0), ((c.tp) & 69206016));
4534													}
4535													// c.tr_clicked - null: !clicked && !data; function: !clicked && data; true: clicked && !data; false: clicked && data (sent)
4536													if (!c.tr || c.tr_clicked===true)
4537													{	send();
4538														c.tr_clicked = false;
4539													}
4540													else if (c.tr_clicked == null)
4541													{	c.tr_clicked = send;
4542													}
4543												}
4544											}
4545											catch (e)
4546											{	ns.error(e);
4547											}
4548										}
4549									}
4550								}
4551							}
4552							catch (e)
4553							{	ns.error(e);
4554							}
4555						}
4556						function conv_gtag_event(arg)
4557						{	if (arg.length===3 && arg[0]==='event' && typeof(arg[1])=='string' && typeof(arg[2])=='object')
4558							{	return {event: arg[1], ...arg[2]};
4559							}
4560							else
4561							{	return arg;
4562							}
4563						}
4564						function add_products(products, event_name, status)
4565						{	let to_add = [];
4566							if (products && gtm_grab.indexOf(status)!=-1)
4567							{	for (let i=0, i_end=products.length; i<i_end; i++)
4568								{	let product = products[i];
4569									if (product)
4570									{	let id_column = gtm_product_id_column || get_id_field(product);
4571										let product_id = product[id_column];
4572										if (product_id && (typeof(product_id)=='string' || typeof(product_id)=='number' || typeof(product_id)=='bigint'))
4573										{	let data = [event_name, product_id];
4574											if (product.quantity > 0)
4575											{	data.push('Quantity', product.quantity);
4576											}
4577											if (gtm_key)
4578											{	data.push('key', gtm_key);
4579											}
4580											to_add.push(data);
4581											n_added++;
4582										}
4583									}
4584								}
4585							}
4586							if (event_name!='Product Viewed' || to_add.length<=1)
4587							{	for (let i=0, i_end=to_add.length; i<i_end; i++)
4588								{	ns.push(to_add[i]);
4589								}
4590							}
4591						}
4592						function zen_add_products(arg, re, event_name, status)
4593						{	let rec = looks_like(looks_like(arg, re), RE_PRODUCT_REC, true);
4594							if (rec)
4595							{	add_products([typeof(rec.cloneNode)!='function' ? rec : {[gtm_product_id_column || 'id']: rec.value}], event_name, status);
4596							}
4597						}
4598					}
4599					function looks_like(arg, re, deep, is_get_p)
4600					{	if (arg)
4601						{	for (let p in arg)
4602							{	let v = arg[p];
4603								if (typeof(v) == 'string')
4604								{	if (re.test(deep ? p : v))
4605									{	return is_get_p ? p : arg;
4606									}
4607								}
4608								else if (deep && v && typeof(v)=='object')
4609								{	if (typeof(v.cloneNode) != 'function')
4610									{	let result = looks_like(v, re, true);
4611										if (result)
4612										{	return result;
4613										}
4614									}
4615									else
4616									{	let elements = v.form?.elements || [v];
4617										let found;
4618										let quality = 0;
4619										for (let i=0, i_end=elements.length; i<i_end; i++)
4620										{	let {id, name, className, dataset} = elements[i];
4621											let rec = {id, name, className, ...dataset};
4622											p = looks_like(rec, RE_PRODUCT_ID, false, true);
4623											if (p && p.length>quality)
4624											{	quality = p.length;
4625												found = elements[i];
4626											}
4627										}
4628										if (found)
4629										{	return found;
4630										}
4631									}
4632								}
4633							}
4634						}
4635					}
4636					function get_id_field(rec)
4637					{	let found = [];
4638						for (let p in rec)
4639						{	if (RE_PRODUCT_ID.test(p))
4640							{	found[found.length] = p;
4641							}
4642						}
4643						found.sort((a, b) => b.length - a.length);
4644						return found[0];
4645					}
4646				}
4647			}
4648		}
4649
4650		function notification(message, props)
4651		{	ns.load_libs
4652			(	'notifications',
4653				() =>
4654				{	if (!notifications)
4655					{	notifications = _S_T.new_elem('<input class="$notifications">');
4656					}
4657					notifications._show(message, props);
4658				}
4659			);
4660		}
4661
4662		function action_notification(a, message, props)
4663		{	notification(message, props);
4664			cur_notification_a = a;
4665		}
4666
4667		function set_executed_action_state(a, state)
4668		{	executed_actions[a] = state;
4669			if (a==cur_notification_a && state===2)
4670			{	notifications._hide();
4671			}
4672		}
4673
4674		function do_exec_action(a, rid, ondone, override_te, ignore_noacts, is_action_override)
4675		{	var bypass_others = false;
4676			var executed = false;
4677			function call_ondone()
4678			{	actions_q_done(a, rid);
4679				if (ondone)
4680				{	ondone();
4681					ondone = null;
4682				}
4683			}
4684			function cut_err_msg(message)
4685			{	return !message ? '(error)' : (message+'').slice(-100);
4686			}
4687			function onerror(message, remain_filtered, is_expected)
4688			{	if (message)
4689				{	// Expected outcomes (already shown, no placeholder yet, presenting rules) stay
4690					// at log level. A genuine failure goes to console.error, so it survives a
4691					// console filtered to errors instead of hiding in the site's own log noise.
4692					(is_expected ? logmsg : errmsg)(`Couldn't execute action ${a}: ${message}`);
4693					exec_onerr([a, message]);
4694					if (is_action_override)
4695					{	action_notification(a, `Couldn't execute action: ${message}`, {design: 'warning'});
4696					}
4697				}
4698				call_ondone();
4699				if (!executed)
4700				{	log_event(-11, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4701					executed = true;
4702					if (ns.act_params[a])
4703					{	ns.act_params[a].no_send_ane = true; // just sent
4704					}
4705					if (!remain_filtered)
4706					{	set_executed_action_state(a, (message || 'Error') + '');
4707						do_whens('onerror', message ? new Error(message) : null);
4708					}
4709				}
4710				log_event(-14, a+' '+cut_err_msg(message), (event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4711			}
4712			function do_whens(what, error)
4713			{	let whens = when_action_executed[a] || [];
4714				for (let when of whens)
4715				{	try
4716					{	if (what == 'onload')
4717						{	when.onload?.();
4718						}
4719						else
4720						{	when.onerror?.(error);
4721						}
4722					}
4723					catch (e)
4724					{	console.error(e);
4725					}
4726				}
4727			}
4728			function action_event_cnt(n, event_name)
4729			{	let hit = true;
4730				if (n > 1)
4731				{	hit = false;
4732					let name = ({onextra: '_nae', onclose: '_nac'})[event_name] || '_nay';
4733					let cnts = ns.cookie_obj('_stat_track_s_id')[name] || '';
4734					let cnt;
4735					cnts = cnts.replace(new RegExp(`((?:^|,)${a}=)(\\d+)`), (a, e, n) => e+(cnt = +n + 1));
4736					if (!cnt)
4737					{	cnts += (cnts ? ',' : '') + `${a}=1`;
4738					}
4739					else
4740					{	hit = cnt >= n;
4741					}
4742					ns.cookie_obj('_stat_track_s_id', {[name]: cnts});
4743				}
4744				return hit;
4745			}
4746			if (executed_actions.hasOwnProperty(a) && typeof(executed_actions[a])!='string')
4747			{	if (!is_action_override && executed_actions[a]===2)
4748				{	set_executed_action_state(a, 3);
4749					onerror(`Action ${a} already executed on this page`, true, true);
4750				}
4751				return false;
4752			}
4753			if (is_action_override)
4754			{	for (let c_id in ns.containers)
4755				{	if (ns.containers[c_id].a?.indexOf(a) >= 0)
4756					{	setTimeout
4757						(	() =>
4758							{	if (executed_actions[a]===0 || executed_actions[a]===1)
4759								{	onerror(`No placeholder found for the action`, true, true);
4760								}
4761							},
4762							2000
4763						);
4764						break;
4765					}
4766				}
4767			}
4768			else if (!ignore_noacts)
4769			{	if (a in new SessCnt('noacts').get())
4770				{	onerror(`Action ${a} is asked not to be shown again`, false, true);
4771					return false;
4772				}
4773				if (camp_cap_blocked(a))
4774				{	onerror(`Action ${a} is hidden by a campaign frequency cap`, false, true);
4775					return false;
4776				}
4777			}
4778			set_executed_action_state(a, 0);
4779			function stage_3()
4780			{	var params = ns.act_params[a];
4781				if (params)
4782				{	// presenting_rules?
4783					if (!add_action_to_history(a, params.presenting_rules))
4784					{	onerror(`Action ${a} will not be shown this time, according to presenting rules`, false, true);
4785						return;
4786					}
4787					camp_cap_event(a, 'v', 1); // campaign caps that count views at delivery
4788					//
4789					var act_proto = ns.act[params.action_type];
4790					for (var i in act_proto)
4791					{	if (act_proto.hasOwnProperty(i))
4792						{	params[i] = act_proto[i];
4793						}
4794					}
4795					params.action_id = a;
4796					params.visit_id = rid;
4797					var ev_done = {};
4798					var ev_done_something = false;
4799					// One confirmed view per execution for the campaign caps: a popup now
4800					// reports both `onshown` and `ondelivered`, and either may legitimately
4801					// be the first to arrive depending on the action type.
4802					var cap_view_counted = false;
4803					params.fire_event = function(event_name, arg, flag)
4804					{	if (event_name == 'onerror')
4805						{	onerror(arg);
4806						}
4807						executed = true;
4808						params.no_send_ane = true; // executed, or onerror just sent ane
4809						if (!ev_done[event_name] && (event_name=='ongoal' || !ev_done.onclose))
4810						{	let event_name_for_auto_actions = event_name;
4811							switch (event_name)
4812							{	case 'ondelivered':
4813									log_event(-21, a+(arg ? ' '+arg : ''), (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4814									if (flag == 'immed-delivered')
4815									{	event_name_for_auto_actions = 'onshown';
4816									}
4817								break;
4818								case 'onproductsdelivered':
4819									// Batched per-item impressions of one widget render, sent by
4820									// install_report_items below; `arg` is the ready &-joined field list.
4821									log_event(-42, arg, (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4822								break;
4823								case 'onarticlesdelivered':
4824									log_event(-43, arg, (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4825								break;
4826								case 'onextra':
4827									ev_done.ondelivered = true;
4828									log_event(-12, a+(arg ? ' '+arg : ''), (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4829								break;
4830								case 'onclose':
4831									log_event(-13, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&512 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&512 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4832								break;
4833								case 'ongoal':
4834									log_event(-15, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&262144 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&262144 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4835								break;
4836								case 'onshown':
4837									if (params.send_executed_yes && !ev_done_something)
4838									{	log_event(-18, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid);
4839										if (!(executed_actions[a] > 0))
4840										{	set_executed_action_state(a, 2);
4841											do_whens('onload');
4842										}
4843									}
4844								break;
4845							}
4846							if (params.is_blocking)
4847							{	unblock_action(a);
4848							}
4849							call_ondone();
4850							ev_done[event_name] = true;
4851							ev_done[event_name_for_auto_actions] = true;
4852							ev_done_something = true;
4853							/*	Campaign frequency caps. A "view" here is a CONFIRMED view, which is
4854								not the same event for every action type:
4855								- in a container: `ondelivered` — fired when 2/3 of the element is in
4856								  the viewport, immediately or on the scroll that brings it there.
4857								  Its sibling `onshown` (flag 'not-delivered') means "rendered below
4858								  the fold", which is precisely not a view, so it must not count.
4859								- popup/bar/anything painted over the page: `onshown` IS the view;
4860								  those never fire `ondelivered` at all.
4861								Counting `event_name_for_auto_actions=='onshown'` (what this did first)
4862								got both container cases backwards: it counted renders nobody saw and
4863								skipped the scroll-into-view confirmations. */
4864							if (!cap_view_counted && (event_name=='ondelivered' || event_name=='onshown' && flag!='not-delivered'))
4865							{	cap_view_counted = true;
4866								camp_cap_event(a, 'v', 0);
4867							}
4868							else if (event_name == 'onextra')
4869							{	camp_cap_event(a, 'k');
4870							}
4871							else if (event_name == 'onclose')
4872							{	camp_cap_event(a, 'c');
4873							}
4874							if (params.ga_action_name)
4875							{	ns.ga_send('action', params.ga_action_name, event_name.substring(2));
4876							}
4877							let event_aa = params[event_name_for_auto_actions+'_auto_actions'];
4878							if (event_aa)
4879							{	let {dont_show_again, dont_show_again_a, user_data, fire_action, dismiss, goal, custom_event_id, ga_category, ga_action} = event_aa;
4880								//
4881								if (dont_show_again > 0)
4882								{	// legacy format
4883									ns.disable_action(a, dont_show_again);
4884								}
4885								else if (dont_show_again)
4886								{	let {n, sessions, secs} = dont_show_again;
4887									if (action_event_cnt(n, event_name))
4888									{	ns.disable_action(a, sessions, secs);
4889									}
4890								}
4891								if (dont_show_again_a)
4892								{	let {action_ids, n, sessions, secs} = dont_show_again_a;
4893									if (action_ids && (sessions>0 || secs>0))
4894									{	if (action_event_cnt(n, event_name))
4895										{	for (let i=0; i<action_ids.length; i++)
4896											{	ns.disable_action(action_ids[i], sessions, secs);
4897											}
4898										}
4899									}
4900								}
4901								if (user_data?.length > 0)
4902								{	let ud = {};
4903									for (let i=user_data.length-1; i>=0; i--)
4904									{	ud[user_data[i].name] = ns.subst_user_data_action(user_data[i].value, '', a);
4905									}
4906									ns.social('IN', ud);
4907								}
4908								if (fire_action > 0)
4909								{	do_exec_action(fire_action, null, null, null, true); // TODO: ns.push(['Fire action', fire_action]);
4910								}
4911								if (dismiss)
4912								{	for (let i=dismiss.length-1; i>=0; i--)
4913									{	ns.dismiss_action(dismiss[i]);
4914									}
4915								}
4916								if (goal)
4917								{	params.fire_event('ongoal');
4918								}
4919								if (custom_event_id)
4920								{	try
4921									{	ns.custom_event(custom_event_id, event_aa.custom_event_value);
4922									}
4923									catch (e)
4924									{	ns.error(e);
4925									}
4926								}
4927								/*	Destinations for this event. Google Analytics is one of them,
4928									not a fixture: an action can report to Segment alone.
4929									An EMPTY list means the action predates the field, and those
4930									have always gone to Google Analytics -- so empty keeps meaning
4931									exactly that, and nobody's tracking changes under them. */
4932								/*	'-' means the editor was used and every box was cleared: report
4933									nowhere. Distinct from '' -- which is an action saved before this
4934									field existed, and those have always gone to Google Analytics.
4935									Without the distinction, unticking everything would silently keep
4936									sending to GA. */
4937								let dests = event_aa.ga_dests=='-' ? [] : (event_aa.ga_dests || '').split(',').filter(function(d) {return !!d;});
4938								let want_ga = event_aa.ga_dests ? dests.indexOf('ga')!=-1 : true;
4939								if (want_ga && ga_category && ga_action)
4940								{	ns.do_ga_send(ga_category, ga_action, event_aa.ga_label, event_aa.ga_value, event_aa.ga_noninteraction);
4941								}
4942								let others = dests.filter(function(d) {return d!='ga';});
4943								/*	The other tools have no category/label/value shape, so the
4944									event is named for the action and the GA-ish fields ride along
4945									as properties. They also do not need a category to be usable,
4946									which is why this is not gated on ga_category. */
4947								if (others.length && ga_action)
4948								{	ns.send_event_to(others, ga_action, {category: ga_category || '', label: event_aa.ga_label || '', value: event_aa.ga_value || ''});
4949								}
4950							}
4951							let event_code = event_aa?.js || params[event_name_for_auto_actions];
4952							if (event_code)
4953							{	event_obj.vid = rid;
4954								event_obj.action_id = a;
4955								event_code = ns.subst_user_data_action(event_code, '', a);
4956								try
4957								{	ns.inject_code.call(event_obj, event_code);
4958								}
4959								catch (e)
4960								{	ns.error(`Problem during execution of event ${event_name_for_auto_actions} of action ${a} (rid=${rid}). Details: ${e.message}`, e);
4961								}
4962							}
4963						}
4964					};
4965					function exec()
4966					{	try
4967						{	bypass_others = params.onexec?.(a, rid, params);
4968						}
4969						catch (e)
4970						{	params.fire_event('onerror', e.message);
4971						}
4972						if (!params.reports_onerror)
4973						{	/*	Each action is encouraged to call this.fire_event('onerror', message)
4974								if it fails to execute. When i receive such signal i report to the server
4975								about action not being executed. Having this.reports_onerror is the promise
4976								to send fire_event (either positive or negative) even in worst case, so i will
4977								feel free to block the page till i receive a feedback.
4978							*/
4979							if (executed_actions[a] === 0)
4980							{	set_executed_action_state(a, 1);
4981								do_whens('onload');
4982							}
4983							call_ondone();
4984						}
4985					}
4986					var do_after = params.do_after|0;
4987					if (do_after <= 0)
4988					{	if (params.is_blocking)
4989						{	block_action(a, !!params.reports_onerror);
4990						}
4991						ns.reg_onready(exec);
4992					}
4993					else
4994					{	ns.reg_onready
4995						(	() =>
4996							{	clearTimeout(timers_ac[a]);
4997								timers_ac[a] = setTimeout(exec, do_after*1000);
4998							}
4999						);
5000					}
5001				}
5002				else
5003				{	ns.error(`Problem during execution of action ${a} (rid=${rid}). No params received.`);
5004				}
5005			}
5006			function stage_2()
5007			{	let {assets_css, assets_js, js_filenames} = ns.act_params[a];
5008				let head = document.getElementsByTagName('head')[0];
5009				let links = document.getElementsByTagName('link');
5010				let cb = new ns.CallBalancer
5011				(	-1,
5012					() => {ns.load_libs(js_filenames, stage_3, onerror)}
5013				);
5014				if (assets_css?.length)
5015				{	for (let i=0, i_end=assets_css.length; i<i_end; i++)
5016					{	let f = assets_css[i];
5017						if (f.href)
5018						{	for (var j=links.length-1; j>=0; j--)
5019							{	let link = links[j];
5020								if (link.href==f.href && (link.media || '')==(f.media || '') && (link.id || '')==(f.id || '') && (link.className || '')==(f.class_name || ''))
5021								{	break;
5022								}
5023							}
5024							if (j == -1)
5025							{	ns.new_elem
5026								(	'link',
5027									head,
5028									{	rel: 'stylesheet',
5029										type: 'text/css',
5030										href: f.href,
5031										media: f.media || undefined,
5032										id: f.id || undefined,
5033										className: f.class_name || undefined
5034									}
5035								);
5036							}
5037						}
5038					}
5039				}
5040				if (assets_js?.length)
5041				{	js_filenames = ns.clone_obj(js_filenames, 1) || [];
5042					for (let i=0, i_end=assets_js.length; i<i_end; i++)
5043					{	js_filenames.push(assets_js[i]);
5044					}
5045				}
5046				cb.inc();
5047			}
5048			try
5049			{	ns.load_libs(a, stage_2, onerror, override_te);
5050			}
5051			catch (e)
5052			{	onerror(e.message);
5053				return false;
5054			}
5055			if (bypass_others)
5056			{	if (!window.Promise || !(bypass_others instanceof Promise))
5057				{	return true;
5058				}
5059				bypass_others.then(null, err => {onerror(err.message)});
5060			}
5061		}
5062
5063		function init_debugger(buffer_key, try_debuggers, ondone)
5064		{	let win = window._S_T_OPENER || opener || parent;
5065			/*	`try_debuggers` is the server's answer, matched on the debugger's IP - and behind the
5066				live proxy that match cannot be relied on: the injected script replaces
5067				XMLHttpRequest with one that routes through proxy.php, so the visit that computes
5068				`try_debuggers` reaches the tracker from OUR server rather than from the browser,
5069				and the IP it is matched on is only as good as X-Forwarded-For end to end. When it
5070				comes back empty the handshake never starts and the page loads with no editor on it.
5071				In proxy mode we do not need that match: `_S_T_PROXY_BUFFER_KEY` was put here by
5072				proxy.php, which now resolves it against a live `st_debuggers_<sr>` row before it
5073				will fetch anything at all - so the key is server-authenticated by the time it gets
5074				here, and it is folded into `try_debuggers` just below. Seamless mode is untouched:
5075				the condition still requires the server's answer there. */
5076			if (!win || win==self || ns.DEBUGGER_INITED || !try_debuggers && !window['_S_T_PROXY_BUFFER_KEY'])
5077			{	ondone();
5078			}
5079			else
5080			{	ns.DEBUGGER_INITED = true;
5081				if (buffer_key)
5082				{	try_debuggers = !try_debuggers ? buffer_key : try_debuggers+','+buffer_key;
5083				}
5084				try
5085				{	let message_type = 'auth-'+ns.random_id(12);
5086					let listeners = {};
5087					ns.new_elem(window).add_props
5088					(	{	onmessage:
5089							{	func(event)
5090								{	if (event.data?.message_type == message_type)
5091									{	let {debugger_tool, buffer_key, auth} = event.data;
5092									/*	The panel's REAL origin, taken from the handshake rather than from
5093										CONF__GUI_HOST. That constant is baked when the templates are compiled,
5094										and stat-track-lib is served from the counter host for every white
5095										label at once - so it always held the DEFAULT gui host, and a session
5096										opened from a white-labelled panel (plugin.personyze.com,
5097										platform.e-marketer.io, ...) was handed an origin that was not the
5098										panel's. Both directions then failed silently: the page posted its
5099										status to a window that is not there, and refused the panel's reply
5100										because the origin did not match - so the editor never received its
5101										value and never started picking.
5102										This is the window we are already taking `auth` from, so trusting its
5103										origin adds no reach; and it still narrows every later message to that
5104										one origin, which '*' would not.
5105									 */
5106									let gui_origin = event.origin && event.origin!='null' ? event.origin : 'https://'+CONF__GUI_HOST;
5107										if (!buffer_key)
5108										{	ns.cookie_obj('stat_track_u_id', {dg: ''});
5109										}
5110										else
5111										{	ns.cookie_obj('stat_track_u_id', {dg: buffer_key});
5112											do_init_debugger(debugger_tool, buffer_key, auth, gui_origin);
5113											listeners.onmessage.remove();
5114										}
5115										ondone?.();
5116										ondone = null;
5117									}
5118								},
5119								listeners
5120							}
5121						}
5122					);
5123					setTimeout
5124					(	() =>
5125						{	listeners.onmessage.remove();
5126							ondone?.();
5127						},
5128						10000
5129					);
5130					win.postMessage({message_type, try_debuggers, server_id: ns.server_id}, '*');
5131				}
5132				catch (e)
5133				{	ns.error(e);
5134					ondone();
5135				}
5136			}
5137
5138			/*	Simulator tools that have been rewritten as Preact bundles (fresh-service/webdriver-apps/src/apps).
5139				Each entry maps a `debugger_tool` to the single global its bundle defines plus the two
5140				build variants of that bundle; a tool that isn't listed here falls through to the legacy
5141				_S_T_DEBUGGER template library below.
5142				Keep in sync with StatTrackApi::setup_filesystem, which is what actually serves the file.
5143			 */
5144			const WEBDRIVER_TOOLS =
5145			{	/*	The "Find on pages" picker of the new panel's Content Placement screen.
5146					`with_jsapi` for its "Describe this placement" box, which asks the AI service
5147					through the same endpoint the visual editor's assistant uses.
5148				 */
5149				select_placeholder:
5150				{	global_name: 'personyzeSimulatorPlaceholder',
5151					with_jsapi: true,
5152					src: "\/\/qacounter.personyze.com\/webdriver\/simulator-placeholder.js?v=1788040102",
5153					src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-placeholder-debug.js?v=1788040102"				},
5154				/*	The goal-element picker of the new panel's Conversion Tracking screen. A tool of its
5155					own, not a replacement of `select_container`: that one is still what the old-UI
5156					container editor opens, for containers that are not goals.
5157					`with_jsapi` for its "Describe the conversion" box, which asks the AI service
5158					through the same endpoint the visual editor's assistant uses.
5159				 */
5160				select_goal:
5161				{	global_name: 'personyzeSimulatorGoal',
5162					with_jsapi: true,
5163					src: "\/\/qacounter.personyze.com\/webdriver\/simulator-goal.js?v=1788040101",
5164					src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-goal-debug.js?v=1788040101"				},
5165				/*	The data-source picker of the new panel's Visitor Attributes screen. Like select_goal,
5166					a tool of its own rather than a replacement of `select_container_grab`, which the
5167					old-UI container editor still opens, and `with_jsapi` for the same reason - its
5168					"Describe what to grab" box.
5169				 */
5170				select_attribute:
5171				{	global_name: 'personyzeSimulatorAttribute',
5172					with_jsapi: true,
5173					src: "\/\/qacounter.personyze.com\/webdriver\/simulator-attribute.js?v=1788040101",
5174					src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-attribute-debug.js?v=1788040100"				},
5175				/*	The QA Simulator of the new panel's campaign editor. A tool of its own rather than a
5176					replacement of `test_condition`, which the old-UI campaign screen still opens.
5177					`with_jsapi` is what hands a tool the session's SQL endpoint, and hence the auth
5178					token; only the tools that talk to Personyze while they run get it.
5179				 */
5180				qa_simulator:
5181				{	global_name: 'personyzeSimulatorQa',
5182					with_jsapi: true,
5183					src: "\/\/qacounter.personyze.com\/webdriver\/simulator-qa.js?v=1788040102",
5184					src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-qa-debug.js?v=1788040102"				},
5185				/*	The WYSIWYG Visual editor of the new panel's action editor. Like the others it is a
5186					tool of its own rather than a replacement of `live`, which the old-UI action editor
5187					still opens. `with_jsapi` because its on-click editor lists the account's other
5188					actions and its custom-event containers.
5189				 */
5190				live_editor:
5191				{	global_name: 'personyzeSimulatorLive',
5192					with_jsapi: true,
5193					src: "\/\/qacounter.personyze.com\/webdriver\/simulator-live.js?v=1788040102",
5194					src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-live-debug.js?v=1788040101"				}
5195			};
5196
5197			function do_init_debugger(debugger_tool, buffer_key, auth, gui_origin)
5198			{	if (debugger_tool && (["sim","test_condition","qa_simulator"]).indexOf(debugger_tool)==-1)
5199				{	window['_S_T_ACTIONS_DISABLED'] = true;
5200				}
5201				let webdriver_tool = Object.prototype.hasOwnProperty.call(WEBDRIVER_TOOLS, debugger_tool) && WEBDRIVER_TOOLS[debugger_tool];
5202				if (webdriver_tool)
5203				{	do_init_webdriver_debugger(webdriver_tool, buffer_key, auth, gui_origin);
5204					return;
5205				}
5206				let _S_T_DEBUGGER = window['_S_T_DEBUGGER'] = window['_S_T_DEBUGGER'] || {};
5207				_S_T_DEBUGGER.JSAPI_URL = get_protocol()+"//"+"qacounter.personyze.com"+`/debugger_jsapi.tsx?auth=${auth}&sr=${ns.server_id}`;
5208				_S_T_DEBUGGER.host_id = host_id;
5209				ns.new_elem_bootstrap_to(_S_T_DEBUGGER);
5210				ns.new_elem
5211				(	'link',
5212					document.getElementsByTagName('head')[0],
5213					{	rel: 'stylesheet',
5214						media: 'all',
5215						href: get_protocol()+"\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER.css"					}
5216				);
5217								let js = NEW_ELEM_NDEBUG ? "\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER.js" : "\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER-debug.js";
5218								ns.load_scripts
5219				(	{	[get_protocol()+js]: () => _S_T_DEBUGGER.initialization
5220					},
5221					e =>
5222					{	if (e instanceof Error)
5223						{	ns.error(e);
5224						}
5225						_S_T_DEBUGGER.new_elem(document).add_props
5226						(	{	ontplready()
5227								{	_S_T_DEBUGGER.initialization(debugger_tool, buffer_key);
5228								}
5229							}
5230						);
5231					}
5232				);
5233			}
5234
5235			/*	Load one of the WEBDRIVER_TOOLS bundles and hand it the session. Unl
5235ike _S_T_DEBUGGER
5236				these are self-contained classic scripts with no new_elem bootstrap and no stylesheet
5237				of their own, so there is nothing to load besides the one file.
5238			 */
5239			function do_init_webdriver_debugger(webdriver_tool, buffer_key, auth, gui_origin)
5240			{	let {global_name} = webdriver_tool;
5241				/*	The "?v=" baked into both URLs is the bundle's mtime at the time the templates were
5242					compiled. That versions releases correctly, but it doesn't move for `make
5243					compile-webdriver`, which writes the served file without recompiling anything - so
5244					on a debug build, where that is the whole dev loop, add a timestamp as well.
5245				 */
5246				let src = get_protocol() + (NEW_ELEM_NDEBUG ? webdriver_tool.src : webdriver_tool.src_debug) + '&t='+Date.now();
5247				ns.load_scripts
5248				(	{	[src]: [window, global_name]
5249					},
5250					e =>
5251					{	if (e instanceof Error)
5252						{	ns.error(e);
5253							return;
5254						}
5255						let options =
5256						{	buffer_key,
5257							gui_origin: gui_origin || 'https://'+CONF__GUI_HOST,
5258							counter_url: get_protocol()+'//'+CONF__COUNTER_WWW_HOSTNAME
5259						};
5260						if (webdriver_tool.with_jsapi)
5261						{	/*	The account-cached counter host serves a stale copy of anything per-account,
5262								which is why both the SQL endpoint and the action screenshots go to the
5263								uncached one - the same two hosts the legacy _S_T_DEBUGGER used for these.
5264							 */
5265							let no_cache = get_protocol()+'//'+"qacounter.personyze.com";
5266							options.jsapi_url = `${no_cache}/debugger_jsapi.tsx?auth=${auth}&sr=${ns.server_id}`;
5267							options.images_url = `${no_cache}/js/${ns.server_id}/images/actions/`;
5268							options.host_id = host_id;
5269							options.server_id = ns.server_id;
5270						}
5271						window[global_name].start(options);
5272					}
5273				);
5274			}
5275		}
5276
5277		function log_new_tealium_audiences(send_mode=0)
5278		{	try
5279			{	clearTimeout(tealium_init_timer);
5280				if (tealium_storage_name && window.localStorage)
5281				{	let str = localStorage[tealium_storage_name];
5282					if (str)
5283					{	/*	A standalone events-only request has nowhere to land until log_visit() has answered:
5284							`cur_visit_id` is 0 until then, and st_log_visit_* refuses events for a session it
5285							holds no visit row for ("this session has no page view on record"). The 600ms poll
5286							below fires SEND_NOW the moment Tealium fills its localStorage, which on a fast tag
5287							is before the page view has round-tripped - that raced request was ~3% of all event
5288							requests on a Tealium-driven site, and the 5-second cookie retry never rescued it,
5289							because every retry repeated the same too-early request. So while the visit is
5290							unknown, keep polling rather than sending; the audiences then ride a request that
5291							has somewhere to land. Past the 20-second budget send anyway, which is what this
5292							did before. Same reason the 'tp' containers wait for `cur_visit_id`. */
5293						if ((send_mode & LOG_EVENT_SEND_NOW) && !cur_visit_id && Date.now() < tealium_try_init_till)
5294						{	tealium_init_timer = setTimeout(() => log_new_tealium_audiences(LOG_EVENT_SEND_NOW), 600);
5295							return;
5296						}
5297						tealium_try_init_till = 0;
5298						let obj = JSON.parse(str);
5299						let audiences = [];
5300						for (let id of Object.keys(obj.audiences || {}).sort())
5301						{	audiences.push(JSON.stringify(id)+':1');
5302						}
5303						let badges = [];
5304						for (let id of Object.keys(obj.badges || {}).sort())
5305						{	badges.push(JSON.stringify(id)+':1');
5306						}
5307						let new_tealium_audiences = `[{${audiences.join(',')}},{${badges.join(',')}}]`;
5308						if (new_tealium_audiences != tealium_audiences)
5309						{	tealium_audiences = new_tealium_audiences;
5310							log_event(-39, tealium_audiences, send_mode|LOG_EVENT_VALUE_OVERRIDE);
5311						}
5312					}
5313					else if (Date.now() < tealium_try_init_till)
5314					{	tealium_init_timer = setTimeout(() => log_new_tealium_audiences(LOG_EVENT_SEND_NOW), 600);
5315					}
5316				}
5317			}
5318			catch (e)
5319			{	ns.error(e);
5320			}
5321		}
5322
5323		function cookie_object(name, new_value, set_new_value, attrs)
5324		{	name = encodeURIComponent(name);
5325			let obj = {};
5326			if (!set_new_value)
5327			{	if (name=='stat_track_u_id' && stat_track_u_id)
5328				{	Object.assign(obj, stat_track_u_id);
5329				}
5330				else if (name=='_stat_track_s_id' && _stat_track_s_id)
5331				{	Object.assign(obj, _stat_track_s_id);
5332				}
5333				else
5334				{	let str = document.cookie.match(new RegExp('\\b'+name+'=([^;]+)'))?.[1] || window.localStorage?.getItem(name) || '';
5335					decodeURIComponent(str).replace
5336					(	/([^&=]+)=([^&]*)/g,
5337						(a, k, v) =>
5338						{	obj[decodeURIComponent(k)] = decodeURIComponent(v);
5339						}
5340					);
5341				}
5342			}
5343			if (new_value || set_new_value)
5344			{	try
5345				{	add_props_shallow(obj, new_value);
5346					if (name == 'stat_track_u_id')
5347					{	stat_track_u_id = Object.assign({}, obj);
5348					}
5349					else if (name == '_stat_track_s_id')
5350					{	_stat_track_s_id = Object.assign({}, obj);
5351					}
5352					let value = '';
5353					for (let i in obj)
5354					{	if (obj[i]!=null && typeof(obj[i])!='function')
5355						{	value += (value ? '&' : '') + encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
5356						}
5357					}
5358					let exp = !new_value ? new Date(1970, 1, 1) : name.charAt(0)=='_' ? null : new Date(Date.now()+63072000000);
5359					exp = exp==null ? '' : ';expires='+exp.toGMTString();
5360					if (!attrs)
5361					{	attrs = {};
5362					}
5363					let c = name+'='+encodeURIComponent(value)+exp+';path='+(attrs.path || '/')+';domain='+(attrs.domain || location.hostname)+(attrs.secure && ';secure' || '');
5364					if (c.length <= 4096)
5365					{	document.cookie = c;
5366					}
5367					window.localStorage?.setItem(name, value);
5368				}
5369				catch (e)
5370				{	ns.error(e);
5371				}
5372			}
5373			return obj;
5374		}
5375
5376		function reapply_html_changes()
5377		{	const action_ids = [...not_found_html_changes.keys()];
5378			for (let action_id of action_ids)
5379			{	let hc = not_found_html_changes.get(action_id);
5380				not_found_html_changes.delete(action_id);
5381				ns.apply_html_changes(hc, action_id);
5382			}
5383		}
5384
5385		/**	How long this container's element search must still wait before repeating it can tell us
5386			anything new, or 0 for "run it now". See CONT_RETRY_FREE for what the two ceilings mean.
5387			`c.rn` counts the passes that left the container un-inited, `c.rt` is when the last search ran,
5388			and `c.rg` is the DOM generation it saw.
5389		 **/
5390		function conts_retry_wait(c, now)
5391		{	let pending = c.tp & ~c.inited;
5392			if (!(pending & CONT_SELECTOR_TYPES) || (pending & ~CONT_SELECTOR_TYPES))
5393			{	return 0; // nothing to search for, or something cheap is still owed alongside it
5394			}
5395			if (c.found_elems || c.found_elems_tr)
5396			{	// This one is not searching, it is re-reading: 'grab html', 'grab volatile html' and the
5397				// text-input types are deliberately never marked inited (see the grab_html() call in
5398				// init_container) so that a value which changes on the page is picked up on the next
5399				// pass. Delaying them would lose data, and they are not the expensive case - the
5400				// pathological one is a selector whose element is nowhere on the page.
5401				return 0;
5402			}
5403			if (c.rg !== conts_dom_gen)
5404			{	return 0; // the element tree changed since this container looked - the answer can differ, so look now
5405			}
5406			let n = c.rn|0;
5407			if (n <= CONT_RETRY_FREE)
5408			{	return 0;
5409			}
5410			let wait = Math.min(CONT_RETRY_STEP * Math.pow(2, Math.min(n-CONT_RETRY_FREE, 10)), CONT_RETRY_IDLE_MAX) - (now - (c.rt || 0));
5411			return wait>0 ? wait : 0;
5412		}
5413
5414		/**	Forget every container's back-off, because the world just changed enough that a selector which
5415			came up empty a moment ago can match now: a new page in an SPA, a new set of actions or
5416			conditions from the server, the testing switch. Callers follow this with init_containers().
5417		 **/
5418		function reset_conts_retry()
5419		{	let {containers} = ns;
5420			for (let c_id in containers)
5421			{	if (containers.hasOwnProperty(c_id))
5422				{	containers[c_id].rn = 0;
5423				}
5424			}
5425		}
5426
5427		/**	Every DOM change is a reason to look again for the containers we have not placed yet - but not
5428			every DOM change can change the answer. A batch that only rewrote text leaves the element tree
5429			exactly as it was, so no selector can start or stop matching because of it, and re-walking the
5430			document for every container that matches nothing here would buy nothing. On a page whose UI
5431			keeps ticking (a clock, a counter, our own panel) those batches are most of them.
5432			Two things do want a pass even for text alone, and both are asked about rather than assumed: a
5433			container that already found its element and re-reads it on every pass (`conts_read_text`) -
5434			a grabbed price rewritten in place is exactly a characterData change, and dropping it would
5435			lose data - and an html change we have not managed to place, since only a pass re-applies it.
5436			What is left out is the rare selector that reads text without owning an element yet (`:empty`,
5437			or a custom function); that one is retried from the timer pass, within CONT_RETRY_IDLE_MAX.
5438		 **/
5439		function on_conts_mutation(records)
5440		{	for (let i=0; i<records.length; i++)
5441			{	if (records[i].type != 'characterData')
5442				{	conts_dom_gen++;
5443					init_containers();
5444					return;
5445				}
5446			}
5447			if (conts_read_text || not_found_html_changes.size)
5448			{	init_containers();
5449			}
5450		}
5451
5452		let init_containers = infrequent_func
5453		(	() =>
5454			{	gtm_hook();
5455				// containers
5456				clearInterval(conts_timer);
5457				let {containers} = ns;
5458				let want_monitor_containers = false;
5459				let next_pass = CONT_RETRY_IDLE_MAX;
5459 // when the retry timer must come back: the soonest any pending container is due
5460				let now = Date.now();
5461				let read_text = false; // does any pending container hold an element it re-reads? then text alone matters
5462				for (let c_id in containers)
5463				{	let c = containers[c_id];
5464					if (containers.hasOwnProperty(c_id) && (c.tp & ~c.inited))
5465					{	// c is {hi:html_id, ha:html_attr, hc:html_changes, tp:container_types, mm:match_many, tr_mm:match_many_trigger, ac:action_ids, em:style_override_empty, oc:style_override_occupied, ud:units_direction, uc:units_count_max, cc:cycle_actions_period, wv:watch_variable, udf:user_data_format, ghm:grab_html_mask_regexp, cn:[1,2,-3], a:placed_action_id, rid:record_id, html:html, inited:0, rn:failed_passes, rt:last_pass_time, rg:last_pass_dom_gen} // a, rid and html are assigned when action is put; rn, rt and rg are the back-off (see conts_retry_wait)
5466						let wait = conts_retry_wait(c, now);
5467						if (wait > 0)
5468						{	want_monitor_containers = true; // too soon to ask the document the same question again
5469						}
5470						else
5471						{	try
5472							{	c.rt = now;
5473								c.rg = conts_dom_gen;
5474								want_monitor_containers = init_container(c_id, c, false) || want_monitor_containers;
5475								c.rn = !(c.tp & ~c.inited) ? 0 : (c.rn|0) + 1;
5476							}
5477							catch (e)
5478							{	let html_id = c.hi;
5479								if (!ns.cont_errors) ns.cont_errors = {};
5480								if (!ns.cont_errors[html_id]) ns.cont_errors[html_id] = 0;
5481								if (ns.cont_errors[html_id]++ > 12)
5482								{	c.inited = c.tp;
5483								}
5484								ns.error(e);
5485							}
5486							wait = conts_retry_wait(c, now);
5487						}
5488						if (wait < next_pass)
5489						{	next_pass = wait;
5490						}
5491						if (c.found_elems || c.found_elems_tr)
5492						{	read_text = true;
5493						}
5494					}
5495				}
5496				conts_read_text = read_text;
5497				// html_changes
5498				reapply_html_changes();
5499				// restart if something remaining undone
5500				if (!conts_observer)
5501				{	conts_observer = new MutationObserver(on_conts_mutation);
5502					conts_observer.observe
5503					(	document.body,
5504						{	childList: true,
5505							subtree: true,
5506							characterData: true
5507						}
5508					);
5509				}
5510				if (want_monitor_containers)
5511				{	// try to init not inited containers later - as soon as the earliest of them is worth trying again
5512					conts_timer = setInterval(init_containers, Math.max(1500, next_pass));
5513				}
5514			},
5515			150
5516		);
5517
5518		/*	Every "the world changed" caller reaches for immediate_call(): the first pass after log_visit
5519			brings the account's actions, the parallel/async bootstrap, the debugger. Those are exactly the
5520			moments when a selector that came up empty can suddenly match, so an immediate pass also drops
5521			the back-off. */
5522		const conts_immediate_call = init_containers.immediate_call;
5523		init_containers.immediate_call = function()
5524		{	reset_conts_retry();
5525			return conts_immediate_call.apply(this, arguments);
5526		};
5527
5528		ns.server_id = 0;
5529		ns.uid = null;
5530		ns.si = 0; // session_start_time (server time)
5531		ns.user_data = {};
5532		ns.act = {};
5533		ns.act_params = {};
5534		ns.is_te_ip = 0; // switch 2, loaded from '_te_ip' by init_testing_mode() - see ns.te_ip()
5535		ns.SessCnt = SessCnt;
5536		ns.obj_follow_path = obj_follow_path;
5537		ns.rollback = rollback;
5538		ns.init_containers = init_containers;
5539
5540		// Old actions use these:
5541		ns.json_encode = function(value)
5542		{	return JSON.stringify(value);
5543		};
5544		ns.json_decode = function(str)
5545		{	if (str==null || str==='') return str;
5546			return JSON.parse(str);
5547		};
5548
5549		// DEPRECATED: instead use: ns.push(['Fire action', action_id])
5550		ns.fire_action = function(action_id, ondone)
5551		{	do_exec_action(action_id, null, ondone, null, true);
5552		};
5553
5554		ns.get_ps = function(add_cur_sid)
5555		{	var sessions = {};
5556			if (add_cur_sid)
5557			{	sessions[add_cur_sid] = 1;
5558			}
5559			sessions = ns.storage_obj('stat_track_sessions', sessions);
5560			var sessions_arr = [];
5561			for (var prop in sessions)
5562			{	var s = prop|0;
5563				if (s) sessions_arr.push(s);
5564			}
5565			sessions_arr.sort((a, b) => b-a); // descending order
5566			// delete records beyond WConf::$n_remember_past_sessions_time
5567			var new_sessions = {};
5568			for (var s=0; s<12 && s<sessions_arr.length; s++)
5569			{	new_sessions[sessions_arr[s]] = 1;
5570			}
5571			ns.storage_obj('stat_track_sessions', new_sessions, true);
5572			// done
5573			return sessions_arr;
5574		};
5575
5576		/*	The refusal lives in its OWN cookie, not inside `stat_track_u_id`.
5577
5578			A record of "this visitor said no" is the one thing you may store without consent -- you
5579			cannot honour a refusal you are not allowed to remember, so it is strictly necessary for a
5580			service the visitor asked for. The tracking identifier is not. Keeping the flag inside the
5581			tracking cookie meant declining still left one behind, which is why the privacy docs had to
5582			admit "the cookie is set even for visitors who opt out". Now an opted-out visitor carries
5583			nothing but this flag.
5584
5585			Deliberately no identifier in it, and no other field: the moment it holds anything else it
5586			stops being exempt. */
5587		const OPT_OUT_COOKIE = 'stat_track_off';
5588
5589		ns.system =
5590		{	is_on()
5591			{	/*	The legacy flag is still READ, forever. Every visitor who opted out before this
5592					change has their refusal in `stat_track_u_id.off` and nowhere else; ignoring it
5593					would silently opt them all back IN, which is the worst possible regression here --
5594					invisible, and precisely against the wishes they expressed. It is never WRITTEN
5595					again, so the old location drains away as those visitors return. */
5596				return ns.cookie_obj(OPT_OUT_COOKIE).off!='1' && ns.cookie_obj('stat_track_u_id').off!='1';
5597			},
5598
5599			_set(on, ondone)
5600			{	ns.load_libs
5601				(	'index',
5602					() =>
5603					{	ns.cookie_obj(OPT_OUT_COOKIE, {off: on ? '' : 1});
5604						/*	Opting back IN must also clear the legacy flag, or the read above keeps
5605							returning false and the switch appears not to work. Only ever cleared,
5606							never set. */
5607						if (on)
5608						{	ns.cookie_obj('stat_track_u_id', {off: ''});
5609						}
5610						ondone?.();
5611					}
5612				);
5613			},
5614
5615			on(ondone)
5616			{	this._set(1, ondone);
5617			},
5618
5619			off(ondone)
5620			{	this._set(0, ondone);
5621				rollback();
5622			}
5623		};
5624
5625		ns.push = function()
5626		{	for (var i=0; i<arguments.length; i++)
5627			{	var command = arguments[i];
5628				if (command)
5629				{	if (commands_cur_override_action_id > 0)
5630					{	if (!commands_override_action_id_for)
5631						{	commands_override_action_id_for = {};
5632						}
5633						commands_override_action_id_for[commands_queue.length] = commands_cur_override_action_id;
5634						if (typeof(command[0])=='string' && command[0].toLowerCase().indexOf('product')!=-1)
5635						{	commands_cur_manually_reported = true;
5636						}
5637					}
5638					if (commands_queue.length == 0)
5639					{	if (window.queueMicrotask)
5640						{	queueMicrotask(exec_commands);
5641						}
5642						else
5643						{	setTimeout(exec_commands, 0);
5644						}
5645					}
5646					commands_queue.push(command);
5647				}
5648			}
5649		};
5650
5651		ns.end_session = function()
5652		{	want_end_session = true;
5653		};
5654
5655		ns.log_navigation = function()
5656		{	/*	Sent NOW, not on the account's 'click event page' send mode. Those modes exist to batch
5657				events - a click, a grab - where arriving a few seconds later costs nothing. This is not
5658				one of those: it is the SPA's page view, and until the server has answered it the new
5659				page has no actions at all, so the delay is not a delay in reporting but in showing.
5660				5219 has 'click event page' set to send-with-delay, which is event_load_timeout = 6s: on
5661				half the navigations the form appeared 4.7-6.7s after the visitor arrived (measured, 8
5662				trials), and on the other half something else happened to flush the batch early and it
5663				came in ~230ms. That is exactly the "shows about half the time" the customer reported -
5664				the campaign was never missing, it was late, and a visitor does not wait 6s.
5665				A full page load never had this problem because its log-visit goes out with the load
5666				itself. An SPA navigation is the same event and now travels the same way.
5667				LOG_EVENT_SEND_NOW rather than SEND_IMMED: it still lets events raised in this same tick
5668				ride along, and it clears any pending send_timer instead of queueing behind it. */
5669			want_new_navigation = true;
5670			log_event('-17', 1, LOG_EVENT_SEND_NOW, 16);
5671		};
5672
5673		ns.add_style = function(elem, css)
5674		{	ns.new_elem(elem).add_style(css);
5675		};
5676
5677		ns.id = function(id, find_all, ctx_elem)
5678		{	if (id && typeof(id)=='object')
5679			{	return ns.qelem(id, find_all);
5680			}
5681			id = !id ? '' : (id+'').trim();
5682			if (id.charAt(0) == '[')
5683			{	return ns.qelem(JSON.parse(id), find_all);
5684			}
5685			if (!id) return find_all ? [] : null;
5686			if (RE_CSS_ID.test(id))
5687			{	id = `${id},#${id},*[name="${id}"]`;
5688			}
5689			return ns.joyquery(id, ctx_elem).get(find_all ? null : 0);
5690		};
5691
5692		ns.qelem = function(qelem_id, find_all, root_node)
5693		{	const P_CLASS=0, P_ATTRIBUTES=1, P_STYLE=2;
5694			if (typeof(qelem_id) == 'string')
5695			{	return ns.id(qelem_id, find_all);
5696			}
5697			if (qelem_id.length == 0)
5698			{	return find_all ? [root_node] : root_node;
5699			}
5700			function subiter(elem, parent_score, qelem_id, step, return_found)
5701			{	var node_name = qelem_id[step][0];
5702				var node_index = qelem_id[step][1];
5703				var diff = qelem_id[step][2];
5704				for (var e=elem?.firstChild, i=0; e; e=e.nextSibling)
5705				{	var cur_node_name = e.nodeName.toLowerCase();
5706					if (cur_node_name == 'tbody')
5707					{	subiter(e, parent_score, qelem_id, step, return_found);
5708					}
5709					else if (cur_node_name == node_name)
5710					{	var score = 0.1; // node_name matched
5711						var max_score = 0.6; // +0.1 point for node_name matched, +0.5 point for node_index match
5712						if (diff)
5713						{	for (var j=0; j<diff.length; j++)
5714							{	var prop_name = diff[j][0];
5715								var prop_subname = diff[j][1];
5716								var value = diff[j][2];
5717								var my_score = 0;
5718								var match = false;
5719								if (prop_name == P_CLASS)
5720								{	my_score = value ? 1 : 0.5;
5721									match = ((' '+e.className+' ').toLowerCase().indexOf(' '+prop_subname+' ')!=-1) ^ !value;
5722								}
5723								else if (prop_name == P_ATTRIBUTES)
5724								{	my_score = value!=null ? 1 : 0.5;
5725									match = e.getAttribute(prop_subname) == value;
5726								}
5727								else if (prop_name == P_STYLE)
5728								{	my_score = 1;
5729									match = e.style[prop_subname] == value;
5730								}
5731								max_score += my_score;
5732								if (match) score += my_score;
5733							}
5734						}
5735						if (i++ == node_index)
5736						{	score += 0.5; // node_index matched
5737						}
5738						return_found.push({elem:e, score:score/max_score*parent_score});
5739					}
5740				}
5741			}
5742			function iter(elem, parent_score, qelem_id, step)
5743			{	var found = [];
5744				subiter(elem, parent_score, qelem_id, step, found);
5745				if (step+1 >= qelem_id.length)
5746				{	return found;
5747				}
5748				var final_found = [];
5749				for (var i=0; i<found.length; i++)
5750				{	final_found = final_found.concat(iter(found[i].elem, found[i].score, qelem_id, step+1));
5751				}
5752				return final_found;
5753			}
5754			var elem;
5755			if (qelem_id.length)
5756			{	var first_tag = qelem_id[0][0];
5757				var found = first_tag.charAt(0)!='#' ?
5758					iter(root_node || document.body, 1.0, qelem_id, 0) :
5759					iter(document.getElementById(first_tag.substring(1)), 1.0, qelem_id, 1);
5760				found.sort((a, b) => b.score - a.score);
5761				elem = found[0]?.elem;
5762			}
5763			return !find_all ? elem : elem ? [elem] : [];
5764		};
5765
5766		ns.qelem_id_to_css = function(qelem_id)
5767		{	const P_CLASS=0, P_ATTRIBUTES=1, P_STYLE=2;
5768			if (typeof(qelem_id) == 'string')
5769			{	return qelem_id;
5770			}
5771			var css = 'body';
5772			for (var i=0, j=qelem_id.length; i<j; i++)
5773			{	var tag = qelem_id[i][0];
5774				var index = qelem_id[i][1];
5775				var distinct = qelem_id[i][2];
5776				if (i==0 && tag.charAt(0)=='#')
5777				{	css = tag;
5778					continue;
5779				}
5780				var classes='', attrs='';
5781				if (distinct)
5782				{	for (var m=0, n=distinct.length; m<n; m++)
5783					{	var prop_name = distinct[m][0];
5784						var prop_subname = distinct[m][1];
5785						var value = distinct[m][2];
5786						if (prop_name == P_CLASS)
5787						{	if (value)
5788							{	classes += '.'+prop_subname;
5789							}
5790						}
5791						else if (prop_name == P_ATTRIBUTES)
5792						{	if (value)
5793							{	attrs += '['+prop_subname+'="'+value+'"]';
5794							}
5795						}
5796					}
5797				}
5798				if (tag == 'tr')
5799				{	css += ' > tbody';
5800				}
5801				css += ' > ' + tag + classes + attrs;
5802				css += ':nth-of-type(' + (index+1) + ')';
5803			}
5804			return css;
5805		};
5806
5807		/**	The action ran, and has nothing to show on this page. Release the page, report nothing.
5808
5809			An action that declares `reports_onerror` is trusted to signal either way, and the page is
5810			held until it does - `blocks[action_id] = 2`, which the onready sweep deliberately will NOT
5811			clear, because an attached action is exactly the one worth waiting for. Firing any event
5812			releases it (see the `is_blocking` branch in fire_event).
5813
5814			Which left one case with no way out. The WYSIWYG page editor's types stopped firing
5815			`onshown` when every change had been scoped away - correctly, since the action did not
5816			appear and reporting it as shown is how a campaign gets an impression count nobody can
5817			explain. But firing the event was ALSO the only thing that released the block, so a scoped-out
5818			action went on holding the pre-hide stylesheet for the whole page - every container, and
5819			every selector in `actions_qelem_ids`, none of which it had anything to do with - until
5820			`force_unblock_all` fired a second later. A visible hold on the header of a page whose
5821			actions were all for somewhere else. Reported from personyze.com (Jacob, 27 Aug 26).
5822
5823			So: the promise is kept, and the report is not made. Two different things that had been
5824			one line.
5825		 **/
5826		ns.action_did_not_appear = function(action_id)
5827		{	unblock_action(action_id);
5828			unblock_all();
5829		};
5830
5831		ns.apply_html_changes = function(html_changes, action_id)
5832		{	function subst_attrs(attrs, except)
5833			{	if (!attrs) return null;
5834				let c_attrs = {};
5835				for (let i in attrs)
5836				{	if (attrs.hasOwnProperty(i) && i!=except)
5837					{	c_attrs[i] = ns.subst_user_data_action(attrs[i], '', action_id);
5838					}
5839				}
5840				return c_attrs;
5841			}
5842			function put_elem(elem, put_elem, where)
5843			{	ns.new_elem(put_elem).add_to_parent(where=='before' ? {nextSibling: elem} : where=='after' ? {previousSibling: elem} : elem);
5844			}
5845			function textToCanonical(text)
5846			{	return text.trim().replace(RE_SPACES_G, ' ').toLowerCase();
5847			}
5848			let last_add_elem = null;
5849			let set_img_src = [];
5850			let can_continue = true;
5851			// How many changes this page is not meant for. See where it is read, below.
5852			let n_scoped_out = 0;
5853			for (let i=0; i<html_changes.length; i++)
5854			{	let change = html_changes[i];
5855				// Recorded for one URL, and this is not it. Nothing to wait for, so it is skipped
5856				// outright rather than parked in not_found_html_changes to be retried forever.
5857				if (!scope_allows_page(change))
5858				{	n_scoped_out++;
5859					continue;
5860				}
5861				let elems = !can_continue ? [] : ns.qelem(change.qelem_id, true);
5862				if (change.scope=='shape' && change.scope_fp && elems.length)
5863				{	// A plain loop, not filter(): what qelem() returns is array-LIKE, and has been
5864					// read as such everywhere else in this function.
5865					let same = [];
5866					for (let j=0; j<elems.length; j++)
5867					{	if (scope_fp_matches(elems[j], change.scope_fp))
5868						{	same.push(elems[j]);
5869						}
5870					}
5871					if (same.length == 0)
5872					{	// The selector found something, but not the thing that was edited. That is
5873						// the case this whole mechanism exists for: apply nothing, and do not retry.
5874						n_scoped_out++;
5875						continue;
5876					}
5877					elems = same;
5878				}
5879				if (elems.length == 0)
5880				{	change.action_id = action_id;
5881					let hc = not_found_html_changes.get(action_id);
5882					if (!hc)
5883					{	not_found_html_changes.set(action_id, (hc=[]));
5884					}
5885					hc.push(change);
5886				}
5887				else if (change.delay)
5888				{	let {delay} = change;
5889					if (i+1 < html_changes.length)
5890					{	let ms = delay.ms|0;
5891						if (ms>0 && !delay._till)
5892						{	delay._till = Date.now() + ms;
5893							setTimeout(reapply_html_changes, ms);
5894						}
5895						can_continue = elems.length>0 && (!delay._till || Date.now()>=delay._till);
5896						if (can_continue && delay.req_text!=null)
5897						{	can_continue = false;
5898							for (let j=0; j<elems.length; j++)
5899							{	if (!delay.req_text || textToCanonical(elems[j].textContent).indexOf(textToCanonical(delay.req_text)) != -1)
5900								{	can_continue = true;
5901									break;
5902								}
5903							}
5904						}
5905						if (!can_continue)
5906						{	if (elems.length==0 && !delay._hobs)
5907							{	delay._hobs = new MutationObserver
5908								(	() =>
5909									{	let new_elems = ns.qelem(change.qelem_id, true);
5910										if (new_elems.length > 0)
5911										{	delay._hobs.disconnect();
5912											reapply_html_changes();
5913										}
5914									}
5915								);
5916								delay._hobs.observe(document.body, {childList: true, subtree: true});
5917							}
5918							i--; // also add "delay" record to `not_found_html_changes`
5919						}
5920					}
5921				}
5922				else
5923				{	for (let j=0; j<elems.length; j++)
5924					{	last_add_elem = apply_html_change_on_elem(elems[j], change, action_id, set_img_src) || last_add_elem;
5925					}
5926				}
5927			}
5928			/*	WAS THIS PAGE ONE OF THE PAGES THIS ACTION IS FOR?
5929
5930				Read by the WYSIWYG page editor's own types, which fire `onshown` after applying -
5931				and were firing it even on a page where every change had been scoped away, so an
5932				action that did nothing at all still reported as shown. Same rule AI Chat follows:
5933				the event says the action APPEARED, so it waits until it did.
5934
5935				Only "all of them", never "some": a change held back because its element has not
5936				rendered yet is a different thing, and it is retried.
5937			 **/
5938			ns.last_apply_all_scoped_out = html_changes.length>0 && n_scoped_out==html_changes.length;
5939
5940			for (let i=set_img_src.length-1; i>=0; i--)
5941			{	let {elem, src} = set_img_src[i];
5942				if (elem.style.visibility != 'hidden')
5943				{	elem.style.visibility = 'hidden';
5944					let listeners = {};
5945					function onload()
5946					{	elem.style.visibility = 'visible';
5947						ns.deferred
5948						(	() =>
5949							{	listeners.onload.remove();
5950								listeners.onerror.remove();
5951							}
5952						);
5953					}
5954					ns.new_elem(elem).add_props
5955					(	{	onload:
5956							{	func: onload,
5957								listeners
5958							},
5959							onerror:
5960							{	func: onload,
5961								listeners
5962							}
5963						}
5964					);
5965					setTimeout(onload, 1500);
5966				}
5967				elem.src = src;
5968			}
5969			return last_add_elem;
5970		};
5971
5972		ns.cookie_obj = function(name, new_value, set_new_value)
5973		{	let attrs = {domain: get_cookie_domain()};
5974			if (set_new_value)
5975			{	ns.delete_cookie(name);
5976			}
5977			return cookie_object(name, new_value, set_new_value, attrs);
5978		};
5979
5980		ns.storage_obj = function(name, new_value, set_new_value)
5981		{	if (!window.localStorage)
5982			{	let attrs = {domain: get_cookie_domain()};
5983				return cookie_object(name, new_value, set_new_value, attrs);
5984			}
5985			let obj = {};
5986			if (!set_new_value)
5987			{	(localStorage[name] || '').replace
5988				(	/([^&=]+)=([^&]*)/g,
5989					(a, k, v) =>
5990					{	obj[decodeURIComponent(k)] = decodeURIComponent(v);
5991					}
5992				);
5993			}
5994			if (new_value || set_new_value)
5995			{	if (new_value)
5996				{	for (let i of Object.keys(new_value))
5997					{	obj[i] = new_value[i];
5998					}
5999				}
6000				let value = '';
6001				for (let i of Object.keys(obj))
6002				{	if (obj[i]!=null && typeof(obj[i])!='function')
6003					{	value += (value ? '&' : '') + encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
6004					}
6005				}
6006				if (!value)
6007				{	localStorage.removeItem(name);
6008				}
6009				else
6010				{	localStorage.setItem(name, value);
6011				}
6012			}
6013			return obj;
6014		};
6015
6016		/**	Simulator overrides this function.
6017		**/
6018		ns.get_location = function()
6019		{	return location;
6020		};
6021
6022		/**	Simulator overrides this function.
6023		**/
6024		ns.set_location = function(href)
6025		{	location.href = href;
6026		};
6027
6028		/**	Simulator overrides this function.
6029		**/
6030		ns.get_referrer = function()
6031		{	return document.referrer;
6032		};
6033
6034		ns.delete_cookie = function(name)
6035		{	if (!name)
6036			{	ns.delete_cookie('_stat_track_s_id');
6037				ns.delete_cookie('stat_track_u_id');
6038			}
6039			else
6040			{	var domain = location.hostname;
6041				while (true)
6042				{	var c = name+'=;expires='+new Date(1970, 1, 1).toGMTString()+';path=/;domain=.'+domain;
6043					document.cookie = c;
6044					if (document.cookie.indexOf(name+'=') == -1) break;
6045					var c = name+'=;expires='+new Date(1970, 1, 1).toGMTString()+';path=/;domain='+domain;
6046					document.cookie = c;
6047					if (document.cookie.indexOf(name+'=') == -1) break;
6048					domain = domain.split('.');
6049					domain.splice(0, 1);
6050					if (domain.length < 2)
6051					{	ns.error('FAILED TO DELETE COOKIE');
6052						break;
6053					}
6054					domain = domain.join('.');
6055				}
6056			}
6057		};
6058
6059		ns.get_param = function(name, href)
6060		{	if (!href && !use_location)
6061			{	use_location = new ns.Location(ns.get_location().href);
6062			}
6063			var loc = !href ? use_location : new ns.Location(href);
6064			var i;
6065			if (name == '//')
6066			{	return loc.hostname;
6067			}
6068			else if (name == '/')
6069			{	return loc.pathname;
6070			}
6071			else if (name.charAt(0)=='/' && !isNaN(i = name.substring(1)-0) && i>0)
6072			{	return loc.pathname.split('/')[i];
6073			}
6074			else if (name == '#')
6075			{	var hash = !href ? location.hash : loc.hash;
6076				return hash ? hash.substring(1) : undefined;
6077			}
6078			else if (name == '///?')
6079			{	return '//'+loc.hostname+loc.pathname+loc.search;
6080			}
6081			else
6082			{	return loc.params()[name];
6083			}
6084		};
6085
6086		ns.url_replace_param = function(url, name, value)
6087		{	var loc = new ns.Location(url);
6088			var set_params = {};
6089			set_params[name] = value;
6090			loc.params(set_params);
6091			return loc.href;
6092		};
6093
6094		ns.add_e = function(elem, onwhat, listener, event_name)
6095		{	var obj = {};
6096			var listeners = {};
6097			obj[onwhat] = {func: listener, listeners, capture: true};
6098			ns.new_elem(elem).add_props(obj);
6099			var event = listeners[onwhat];
6100			if (event_name)
6101			{	event_name = onwhat+'\t'+event_name;
6102				var cur;
6103				if (!elem._S_T_EVT)
6104				{	elem._S_T_EVT = {};
6105				}
6106				else if ((cur = elem._S_T_EVT[event_name]))
6107				{	cur.remove();
6108				}
6109				elem._S_T_EVT[event_name] = event;
6110			}
6111			return event;
6112		};
6113
6114		ns.reg_onready = function(func)
6115		{	ns.new_elem(document).add_props({ontplready: func});
6116		};
6117
6118		// DEPRECATED
6119		// TODO: remove
6120		ns.cut_scripts = function(html, scripts)
6121		{	html = (html+'').replace
6122			(	RE_CUT_SCRIPTS,
6123				function($0, $1, $2)
6124				{	var src;
6125					if ($1 && ((src=$1.match(/src=(["'])(.*?)\1/i)) || (src=$1.match(/src(=)(\S+)/i))))
6126					{	scripts.push(ns.new_elem('script', null, {src: src[2]}));
6127					}
6128					else if ($2)
6129					{	scripts.push($2);
6130					}
6131					return $0;
6132				}
6133			);
6134			return html;
6135		};
6136
6137		// DEPRECATED
6138		// TODO: remove
6139		ns.exec_cut_scripts = function(elem, scripts)
6140		{	for (var i=0; i<scripts.length; i++)
6141			{	if (scripts[i].cloneNode)
6142				{	elem.appendChild(scripts[i]);
6143				}
6144				else
6145				{	// TODO: remove scripts as text
6146					var global_exec = window.execScript || function(js) {eval.call(window, js)};
6147					global_exec(scripts[i]);
6148				}
6149			}
6150		};
6151
6152		ns.dismiss_action = function(action_id)
6153		{	return !!do_clear_actions([action_id])[0];
6154		};
6155
6156		ns.get_custom_data = function(d, ic)
6157		{	if(!d) return {};
6158			var cds = JSON.parse(d);
6159			var custom_data = {};
6160			for(var i in cds) {
6161				var cd = cds[i];
6162				if(!cd) continue;
6163				var _i = cd.i;
6164				if(cd['type'] == 'selector') {
6165					var val = ns.get_page_grab_val['_'+cd.from](cd.val);
6166				} else if (ic) {
6167					var val = ns.containers[cd.val] ? ns.containers[cd.val].vl : null;
6168				}
6169				if(typeof val != 'undefined') custom_data[_i] = val ? val : cd['default'];
6170
6171			}
6172			return custom_data;
6173		};
6174
6175		ns.get_page_grab_val =
6176		{	_html(v){
6177				var obj = ns.id(v);
6178				return ns.inner_text(obj);
6179			},
6180			_meta(v){
6181				var name = JSON.stringify(v);
6182				var e = ns.joyquery(`meta[name=${name}], meta[property=${name}]`).get(0);
6183				return e?.content || '';
6184			},
6185			_cookie(v){
6186				return ns.get_cookie(v);
6187			},
6188			_lstorage(v){
6189				return window.localStorage?.[v];
6190			},
6191			_sstorage(v){
6192				return window.sessionStorage?.[v];
6193			},
6194			_var(v){
6195				return obj_follow_path(window, v);
6196			},
6197			_url(cd){
6198				return ns.get_param(cd.val);
6199			}
6200		};
6201
6202		ns.apply_ghm = function(text, regexp) // Apply "grab_html_mask_regexp" on text. Will catch first reference, e.g., if applying "\s*(.*?)\s*" on " foobar " will return "foobar". If the first char of mask is '*', it's not a part of PCRE, but it means negative assertion - will replace matched fragment(s) with empty string and return what is remained.
6203		{	text = !text ? '' : (text+'').trim();
6204			if ((regexp = (regexp || '')+''))
6205			{	if (regexp == 'double')
6206				{	// TODO: parse locale number
6207					let m = text.match(RE_GHM_NUMBER);
6208					text = !m || m[1]==null ? '' : m[1];
6209				}
6210				else if (regexp == 'datetime')
6211				{	// TODO: parse locale date
6212				}
6213				else if (regexp.charAt(0) != '*')
6214				{	let m = text.match(new RegExp(regexp));
6215					text = !m || m[1]==null ? '' : m[1];
6216				}
6217				else
6218				{	text = text.replace(new RegExp(regexp.substring(1), 'g'), '');
6219				}
6220			}
6221			text = text.trim().replace(RE_SPACES_G, ' ');
6222			return text.substr(0, 150).trim();
6223		};
6224
6225		ns.inner_text = function(elem)
6226		{	return !elem ? '' : typeof(elem.value)=='string' ? elem.value : elem.textContent;
6227		};
6228
6229		// DEPRECATED: use `ns.scripts_to_ready()`
6230		// TODO: remove
6231		ns.html_scripts_to_ready = function(elem, cut_scripts)
6232		{	if (elem.nodeType == XML_ELEMENT_NODE)
6233			{	let scripts = elem.getElementsByTagName('SCRIPT');
6234				for (let i=0, i_end=scripts.length; i<i_end; i++)
6235				{	let script = scripts[i];
6236					if (!script.src)
6237					{	script.setAttribute('ontplready', (window.execScript ? 'execScript(this.textContent);' : 'eval.call(window, this.textContent);')+(script.getAttribute('ontplready') || ''));
6238					}
6239					else if (cut_scripts)
6240					{	let {src, async, onload, onerror} = script;
6241						cut_scripts[cut_scripts.length] = ns.new_elem('script', null, {src, async, onload, onerror});
6242					}
6243				}
6244			}
6245		};
6246
6247		ns.scripts_to_ready = function(elem, ondone)
6248		{	if (elem.nodeType === XML_ELEMENT_NODE)
6249			{	let html = '';
6250				let scripts = elem.getElementsByTagName('SCRIPT');
6251				for (let i=0, i_end=scripts.length; i<i_end; i++)
6252				{	html += scripts[i].outerHTML;
6253				}
6254				if (html)
6255				{	ns.inject_code(html, false, ondone);
6256					return;
6257				}
6258			}
6259			ondone?.();
6260		};
6261
6262		ns.do_put_html = function(elem, html, css, html_changes, action, with_uacss, n_action, reverse_order, animation)
6263		{	if (html && !html.cloneNode)
6264			{	html += '';
6265				if (!html) throw new Error('Nothing to show');
6266			}
6267			let to_back = [];
6268			let created_path = null;
6269			function select_child(elem, possible_node_names, node_name)
6270			{	let created = null;
6271				let child = elem &&
6272				(	ns.new_elem(elem).select_child_elems(possible_node_names)[0] ||
6273					(	created = ns.new_elem(node_name, elem)
6274					)
6275				);
6276				created_path = created_path || created;
6277				return child;
6278			}
6279			let want_add_before = html_changes?.ae_where == 'before';
6280			let want_add_after = html_changes?.ae_where == 'after';
6281			let want_replace = html_changes?.ae_where == 'replace';
6282			let tag = elem.nodeName;
6283			let is_not_node = !html.cloneNode;
6284			switch (tag)
6285			{	case 'TBODY':
6286					elem = select_child(elem, 'TR', 'TR');
6287				case 'TR':
6288					elem = select_child(elem, 'TH,TD', 'TD');
6289				break;
6290				case 'TABLE':
6291				case 'IMG':
6292				case 'HR':
6293				case 'METER':
6294				case 'PROGRESS':
6295				case 'IFRAME':
6296				case 'EMBED':
6297				case 'VIDEO':
6298				case 'OBJECT':
6299				case 'CANVAS':
6300				case 'SVG':
6301				case 'UL':
6302				case 'OL':
6303				case 'MENU':
6304					want_replace = !want_add_before && !want_add_after;
6305				break;
6306				case 'INPUT':
6307				case 'TEXTAREA':
6308				case 'SELECT':
6309					var want_into_value = is_not_node;
6310				break;
6311			}
6312			let content_elem = elem;
6313			if (want_into_value)
6314			{	elem.value = ns.new_elem('div', null, {innerHTML: html}).textContent;
6315			}
6316			else
6317			{	if (is_not_node)
6318				{	let tmp = document.createElement(html_changes?.add_elem || 'font');
6319					tmp.innerHTML = html;
6320					ns.new_elem(tmp).elem_trim();
6321					if (tmp.firstChild == tmp.lastChild)
6322					{	html = tmp.firstChild;
6323					}
6324					else
6325					{	html = tmp;
6326					}
6327					if (animation)
6328					{	if (!html.classList.contains('$switch-elem'))
6329						{	html.classList.add('$switch-elem');
6330							let inner = ns.new_elem('div', null, {attributes: {'data-case': 'on', 'data-prepared': 1}, innerHTML: html.childNodes});
6331							html.appendChild(inner);
6332						}
6333						else
6334						{	animation = null;
6335						}
6336					}
6337				}
6338				content_elem = html;
6339				html._st__action = action || null;
6340				let placeholder_order = (action?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - 0;
6341				let p_action_id = action?.action_id | 0;
6342				if (want_replace || want_add_before || want_add_after)
6343				{	let ref_node = elem;
6344					let a, p;
6345					if (want_add_before)
6346					{	while ((a = ref_node.previousSibling?._st__action) && ((p = (a.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - placeholder_order)>0 || p==0 && a.action_id>p_action_id))
6347						{	ref_node = ref_node.previousSibling;
6348						}
6349					}
6350					else if (want_add_after)
6351					{	while ((a = ref_node.nextSibling?._st__action) && ((p = (a.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - placeholder_order)<0 || p==0 && a.action_id<p_action_id))
6352						{	ref_node = ref_node.nextSibling;
6353						}
6354						ref_node = ref_node.nextSibling;
6355					}
6356					elem.parentNode.insertBefore(html, ref_node);
6357					if (want_replace)
6358					{	to_back[0] = elem;
6359						elem.parentNode.removeChild(elem);
6360					}
6361				}
6362				else // want inside
6363				{	let ref_node = null;
6364					for (let i=0, i_end=elem.childNodes.length; i<i_end; i++)
6365					{	let n = elem.childNodes[i];
6366						let that_action = n._st__action;
6367						if (!that_action)
6368						{	to_back.push(n);
6369							elem.removeChild(n);
6370							i--;
6371							i_end--;
6372						}
6373						else if (!ref_node)
6374						{	let that_placeholder_order = (that_action.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - 0;
6375							if (that_placeholder_order>placeholder_order || (that_placeholder_order==placeholder_order && that_action.action_id>p_action_id))
6376							{	ref_node = n;
6377							}
6378						}
6379					}
6380					elem.insertBefore(html, ref_node);
6381				}
6382			}
6383			let to_back_style = null;
6384			if (css)
6385			{	to_back_style = elem.style.cssText;
6386				ns.add_style(elem, css);
6387			}
6388			let s=elem.style, cs;
6389			if (elem.style.textAlign=='center' && !s.marginLeft && !s.marginRight && ((cs=ns.new_elem(elem).style('display'))=='block' || cs=='table'))
6390			{	s.marginLeft = s.marginRight = 'auto';
6391			}
6392			if (!want_into_value)
6393			{	if (is_not_node)
6394				{	// process templates: when element is linked to document (needed especially for $responsive)
6395					ns.new_elem
6396					(	html,
6397						null,
6398						{	ontplready()
6399							{	if (action)
6400								{	this._st__action = action;
6401								}
6402								if (animation)
6403								{	this._reinit?.();
6404									this._set_case('on', null, false, animation);
6405								}
6406							},
6407							ontplerror: !action ? null : function(event, error)
6408							{	action.fire_event('onerror', error);
6409							}
6410						}
6411					);
6412					ns.scripts_to_ready(html);
6413				}
6414			}
6415			let revert_for_elem = ns.uacss.supress_for_elem(html, action?.action_id, with_uacss);
6416			let reverted = false;
6417			function revert()
6418			{	if (!reverted && !want_into_value)
6419				{	if (to_back_style != null)
6420					{	elem.style.cssText = to_back_style;
6421					}
6422					if (want_replace)
6423					{	html.parentNode.insertBefore(to_back[0], html);
6424						html.parentNode.removeChild(html);
6425						html = to_back[0];
6426					}
6427					else if (want_add_before || want_add_after)
6428					{	html.parentNode?.removeChild(html);
6429					}
6430					else if (created_path)
6431					{	created_path.parentNode?.removeChild(created_path);
6432					}
6433					else if (n_action == 0)
6434					{	elem.innerHTML = '';
6435						for (let i=0, i_end=to_back.length; i<i_end; i++)
6436						{	elem.appendChild(to_back[i]);
6437						}
6438					}
6439					revert_for_elem();
6440					reverted = true;
6441				}
6442			}
6443			let a = action.action_id;
6444			if (executed_actions[a]===0 || executed_actions[a]===1)
6445			{	set_executed_action_state(a, 2);
6446			}
6447			return {elem: content_elem, revert};
6448		};
6449
6450		ns.put_html = function(action, animation, unused_2, html, unused_3, unused_4, css, no_report_extra, with_uacss, onattach)
6451		{	let a = action.action_id;
6452			let rid = action.visit_id;
6453			let found = [];
6454			let {containers} = ns;
6455			for (let c_id in containers)
6456			{	let c = containers[c_id];
6457				if (c && ((c.tp) & 1))
6458				{	let a_ids = c.ac;
6459					if (Array.isArray(a_ids) && a_ids.indexOf(a|0)!=-1)
6460					{	let n_actions = c.a?.length || 0;
6461						if (n_actions)
6462						{	let to_rm = -1;
6463							for (var k=n_actions; k>=0; k--)
6464							{	if (c.a[k] == a)
6465								{	to_rm = k;
6466									break;
6467								}
6468								else if (to_rm==-1 && c.rid<rid)
6469								{	to_rm = k;
6470								}
6471							}
6472							if (to_rm != -1)
6473							{	c.html[to_rm].detach();
6474								c.html.splice(to_rm, 1);
6475								c.a.splice(to_rm, 1);
6476							}
6477						}
6478						if (c.uc-n_actions > 0 || c.cc>0)
6479						{	if (!c.html) c.html = [];
6480							if (!c.a) c.a = [];
6481							found.push(c_id);
6482							let h_ondelivered = null;
6483							let detach_called = false;
6484							c.html.push
6485							(	{	h_elem: null,
6486									attach(elem, n_action)
6487									{	if (!this.h_elem)
6488										{	for (let i=found.length-1; i>=0; i--)
6489											{	let other_c = containers[found[i]];
6490												if (other_c!=c && Array.isArray(other_c.a))
6491												{	let other_i = other_c.a.indexOf(a|0);
6492													if (other_i != -1)
6493													{	other_c.a.splice(other_i, 1);
6494														other_c.html.splice(other_i, 1);
6495														if (other_c.a.length == 0)
6496														{	other_c.a = null;
6497															other_c.html = null;
6498														}
6499													}
6500												}
6501											}
6502											found = [];
6503											block_action(a, true);
6504											try
6505											{	detach_called = false;
6506												this.h_elem = ns.do_put_html(elem, html, [c.oc, css], c.hc, action, with_uacss, n_action, c.ud=='bt', animation);
6507												let attached_elem = this.h_elem.elem;
6508												if (!no_report_extra)
6509												{	ns.install_report_extra(action, attached_elem);
6510												}
6511												ns.install_report_items(action, attached_elem);
6512												action.init_elem?.(attached_elem);
6513												let is_delivered = is_elem_delivered(attached_elem);
6514												ns.deferred
6515												(	() =>
6516													{	/*	The 'not-delivered' flag marks the one `onshown` that is NOT a
6517															view: the action rendered below the fold, and its confirmed
6518															view arrives later as `ondelivered` (or never). Only the
6519															campaign frequency caps read it — the legacy auto-actions
6520															still see a plain 'onshown', unchanged. */
6521														action.fire_event(is_delivered ? 'ondelivered' : 'onshown', null, is_delivered ? 'immed-delivered' : 'not-delivered');
6522													}
6523												);
6524												if (!is_delivered)
6525												{	h_ondelivered = reg_ondelivered
6526													(	attached_elem,
6527														() =>
6528														{	action.fire_event('ondelivered');
6529															h_ondelivered = null;
6530														}
6531													);
6532												}
6533												if (detach_called) // called from ontplready of action html
6534												{	this.detach();
6535												}
6536											}
6537											catch (e)
6538											{	action.fire_event('onerror', e.message);
6539											}
6540											unblock_action(a);
6541											onattach?.(this.h_elem.elem);
6542										}
6543									},
6544									detach()
6545									{	if (this.h_elem)
6546										{	this.h_elem.revert();
6547											this.h_elem = null;
6548											h_ondelivered?.();
6549											h_ondelivered = null;
6550										}
6551										detach_called = true;
6552									},
6553									is_attached()
6554									{	return !!this.h_elem;
6555									},
6556									/**	Cross-fade this unit in where the outgoing one was.
6557
6558										IN WHERE IT WAS is the whole difficulty. `attach` puts the new content
6559										exactly where the placeholder says - before an element, after it, or
6560										inside it - and then this cross-fade used to take it straight back out
6561										again: `new_elem(html, parent, ...)` APPENDS (see `add_to_parent`: with
6562										a real node as the parent it does `insertBefore(elem, null)`), so the
6563										switch box was created as the parent's last child, the content was
6564										moved into it, and `parent.appendChild(next)` at the end of the
6565										animation left it there.
6566
6567										So the first action of a rotation rendered in the right place and every
6568										one after it dropped to the bottom of the section. Reported on
6569										account 7549's "Below pdpd buttons": the first card sat above the Add
6570										to basket button, and the moment it rotated the next card appeared
6571										under the price, the monthly line and the T&Cs.
6572
6573										The box is therefore moved to the outgoing content's own position, and
6574										the incoming content is put back relative to the BOX rather than to the
6575										parent - so it lands where the box is, which is where the content was.
6576									 **/
6577									swap(elem, n_action, other)
6578									{	if (!other.h_elem)
6579										{	this.attach(elem, n_action);
6580										}
6581										else
6582										{	let prev = other.h_elem.elem;
6583											other.detach();
6584											this.attach(elem, n_action);
6585											let next = this.h_elem.elem;
6586											let parent = next.parentNode;
6587											// Read BEFORE the box moves `next` out of the document.
6588											let anchor = next.nextSibling;
6589											let by_id = {};
6590											let sw = ns.new_elem
6591											(	'<div id="sw" class="$switch-elem" data-case="prev">' +
6592													'<div id="prev" data-case="prev"></div>' +
6593													'<div id="next" data-case="next"></div>' +
6594												'</div>',
6595												parent,
6596												{	prev: {innerHTML: prev},
6597													next: {innerHTML: next},
6598												},
6599												by_id
6600											);
6601											/*	`anchor` is null when the content was the last child, and then the
6602												append new_elem already did is the right position. */
6603											if (anchor && anchor.parentNode === parent)
6604											{	parent.insertBefore(sw, anchor);
6605											}
6606											sw._set_case
6607											(	'next',
6608												null,
6609												false,
6610												{	template: 'animator_fade',
6611													ontransitionend()
6612													{	if (next.parentNode == by_id.next)
6613														{	sw.parentNode?.insertBefore(next, sw);
6614														}
6615														sw.parentNode?.removeChild(sw);
6616													}
6617												}
6618											);
6619										}
6620									}
6621								}
6622							);
6623							c.a.push(a|0);
6624							c.rid = rid;
6625							c.inited &= ~1;
6626							init_container(c_id, c, false);
6627							if (!found.length) return 1; // attach() called
6628						}
6629					}
6630				}
6631			}
6632			if (!found.length)
6633			{	action.fire_event('onerror', 'No suitable container');
6634			}
6635		};
6636
6637		/*	Per-item impression reporting for recommendation widgets: once the attached element's view
6638			is confirmed (the same 2/3-visible rule as `ondelivered`), collect the item ids its cards
6639			already carry in data-personyze-click-target and send them as ONE event per item kind
6640			(-42 products / -43 articles), riding the normal event batch. The server counts them into
6641			st_actions_items_daily - the denominator that turns the existing click rows into a
6642			per-item CTR. fire_event's ev_done guard makes each render report at most once. */
6643		ns.install_report_items = function(action, elem)
6644		{	function report()
6645			{	let targets = elem.querySelectorAll('[data-personyze-click-target]');
6646				let seen = {};
6647				let by_kind = {};
6648				for (let i=0; i<targets.length; i++)
6649				{	let v = (targets[i].getAttribute('data-personyze-click-target') || '').trim();
6650					let sp = v.indexOf(' ');
6651					if (sp > 0)
6652					{	let kind = v.slice(0, sp);
6653						let id = v.slice(sp+1).trim();
6654						if (id && !seen[v] && (kind=='products' || kind=='articles'))
6655						{	seen[v] = true;
6656							(by_kind[kind] = by_kind[kind] || []).push(encodeURIComponent(action.action_id+':'+id));
6657						}
6658					}
6659				}
6660				if (by_kind.products)
6661				{	action.fire_event('onproductsdelivered', by_kind.products.join('&'));
6662				}
6663				if (by_kind.articles)
6664				{	action.fire_event('onarticlesdelivered', by_kind.articles.join('&'));
6665				}
6666			}
6667			if (is_elem_delivered(elem))
6668			{	report();
6669			}
6670			else
6671			{	reg_ondelivered(elem, report);
6672			}
6673		};
6674
6675		ns.install_report_extra = function(action, elem)
6676		{	ns.add_e
6677			(	elem,
6678				'onclick',
6679				event =>
6680				{	var target = event.target;
6681					while (target && target!=elem)
6682					{	// Click target, or interaction with product/article?
6683						var click_target = target.getAttribute('data-personyze-click-target');
6684						if (click_target != null)
6685						{	action.fire_event('onextra', click_target.trim() || undefined);
6686							return;
6687						}
6688						// Close?
6689						var value = target._get_tplname?.();
6690						if (value=='personyze_button_dont_show_again' || value=='personyze_button_popup_close')
6691						{	action.fire_event('onclose');
6692							return; // no
6693						}
6694						target = target.parentNode;
6695					}
6696					// by default, send extra if there are no data-personyze-click-target="" in the html
6697					if (!ns.joyquery('*[data-personyze-click-target]', elem).get(0))
6698					{	action.fire_event('onextra');
6699					}
6700				},
6701				'put_html extra'
6702			);
6703		};
6704
6705		ns.get_owner_action = function(elem)
6706		{	var action;
6707			for (var n=elem; (action = n._st__action)===undefined && (n=n._ne__switch_elem || n.parentNode););
6708			return action;
6709		};
6710
6711		ns.ga_send = function(what, name, event_name)
6712		{	ns.do_ga_send(ns.get_location().host+' '+what, name, event_name, 0, true);
6713			/*	The same automatic report to whatever else the account chose in
6714				Analytics settings, and the page actually has. Named for the action
6715				(`name`), with the event kind as a property -- so a Segment or
6716				Klaviyo timeline reads "Homepage banner / shown" rather than a
6717				GA-shaped category+action pair nobody else uses. */
6718			if (default_send_dests)
6719			{	ns.send_event_to(default_send_dests.split(','), name, {event: event_name, source: 'personyze'});
6720			}
6721		};
6722
6723		ns.do_ga_send = function(category, action, label, value, noninteraction)
6724		{	if (ga_is_active)
6725			{	ga_queue.push([category, action, label, value, noninteraction]);
6726				ns.reg_onready(ga_flush_queue);
6727			}
6728		};
6729
6730		/*	Push one event out to whichever analytics tools the page already has.
6731
6732			Every destination here is a library the customer loaded themselves --
6733			we never inject one. A missing library is a silent skip, not an error:
6734			a page that has Segment but not Tealium should still send to Segment,
6735			and a visitor with an ad blocker should not break the campaign.
6736
6737			Each adapter is split into `ready` and `send` on purpose. Asking "can
6738			this page send to Segment?" must never actually send anything -- a
6739			probe event would land in the customer's own analytics and pollute the
6740			data we are supposed to be feeding.
6741
6742			The adapters share one property object, so the same event lands
6743			identically everywhere. Adding a destination is one entry in this map,
6744			which is what lets the Send-event action, the per-action report
6745			checkbox and anything later all reuse it.
6746		*/
6747		var send_event_adapters =
6748		{	segment:
6749			{	ready: function() { return !!(window.analytics && typeof(analytics.track)=='function'); },
6750				send: function(name, props) { analytics.track(name, props); }
6751			},
6752			tealium:
6753			{	/*	utag.link is Tealium's own "a thing happened" call, present
6754					whenever utag.js is on the page -- the same condition that makes
6755					the audiences/badges side of this integration work. */
6756				ready: function() { return !!(window.utag && typeof(utag.link)=='function'); },
6757				send: function(name, props)
6758				{	var payload = {tealium_event: name};
6759					for (var k in props) payload[k] = props[k];
6760					utag.link(payload);
6761				}
6762			},
6763			ga4:
6764			{	ready: function() { return typeof(window.gtag)=='function'; },
6765				send: function(name, props) { gtag('event', name, props); }
6766			},
6767			datalayer:
6768			{	ready: function()
6769				{	var layer = window[gtm_data_layer_name || 'dataLayer'];
6770					return !!(layer && typeof(layer.push)=='function');
6771				},
6772				send: function(name, props)
6773				{	var payload = {event: name};
6774					for (var k in props) payload[k] = props[k];
6775					window[gtm_data_layer_name || 'dataLayer'].push(payload);
6776				}
6777			},
6778			klaviyo:
6779			{	/*	klaviyo.js exposes `klaviyo`, a command queue taking
6780					push(['track', name, props]); older installs have the same
6781					queue as `_learnq`. Both accept commands before the library
6782					finishes loading, so ready-with-queue is truly ready. */
6783				ready: function()
6784				{	var k = window.klaviyo || window._learnq;
6785					return !!(k && typeof(k.push)=='function');
6786				},
6787				send: function(name, props) { (window.klaviyo || window._learnq).push(['track', name, props]); }
6788			}
6789		};
6790
6791		/*	Returns the destinations that actually accepted the event, so a caller
6792			can report what happened rather than assume. */
6793		ns.send_event_to = function(destinations, name, props)
6794		{	var sent = [];
6795			if (!name)
6796			{	return sent;
6797			}
6798			props = props || {};
6799			for (var i=0; i<(destinations || []).length; i++)
6800			{	var key = (destinations[i]+'').toLowerCase();
6801				var adapter = send_event_adapters[key];
6802				if (!adapter)
6803				{	continue;
6804				}
6805				try
6806				{	if (adapter.ready())
6807					{	adapter.send(name, props);
6808						sent.push(key);
6809					}
6810				}
6811				catch (e)
6812				{	ns.error('Could not send event "'+name+'" to '+key+': '+e.message, e);
6813				}
6814			}
6815			return sent;
6816		};
6817
6818		/*	Which destinations this page could send to right now -- checked, never
6819			sent. The action editor cannot know what a visitor's page has loaded,
6820			so this is what a "nothing arrived" support question gets answered
6821			with. */
6822		ns.send_event_available = function()
6823		{	var out = [];
6824			for (var key in send_event_adapters)
6825			{	try
6826				{	if (send_event_adapters[key].ready())
6827					{	out.push(key);
6828					}
6829				}
6830				catch (e)
6831				{	// a destination whose probe throws is not available
6832				}
6833			}
6834			return out;
6835		};
6836
6837		ns.custom_event = function(id_or_html_id, value, onload, onerror)
6838		{	if (onload && typeof(onload)!='function')
6839			{	throw new Error("custom_event(): onload must be function");
6840			}
6841			if (onerror && typeof(onerror)!='function')
6842			{	throw new Error("custom_event(): onerror must be function");
6843			}
6844			var c = null;
6845			var c_id = null;
6846			if (ns.containers.hasOwnProperty(id_or_html_id))
6847			{	// id
6848				c_id = id_or_html_id;
6849				c = ns.containers[c_id];
6850			}
6851			else if (conts_map.hasOwnProperty(id_or_html_id))
6852			{	// html_id
6853				var c_ids = conts_map[id_or_html_id];
6854				for (var i=c_ids.length-1; i>=0; i--)
6855				{	c_id = c_ids[i];
6856					c = ns.containers[c_id];
6857					if (c.tp & 1024) break;
6858				}
6859			}
6860			if (!c || !(c.tp & 1024))
6861			{	throw new Error('No such custom event container: '+id_or_html_id);
6862			}
6863			value = (value==null ? '' : value+'').substr(0, 1024);
6864			log_event(c_id, value, (event_send_with_delay&524288 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&524288 ? LOG_EVENT_SEND_NOW : 0), c.tp & 50176, 0, onload, onerror);
6865		};
6866
6867		ns.action_state = function(action_id, value)
6868		{	var error = null;
6869			if (!ns.act_params[action_id])
6870			{	error = `action ID ${action_id} not executed`;
6871			}
6872			else if (value!='extra' && value!='closed' && value!='goal')
6873			{	error = 'value must be one of: "extra", "closed", "goal"';
6874			}
6875			if (error)
6876			{	throw new Error("action_state(): "+error);
6877			}
6878			ns.act_params[action_id].fire_event('on'+value);
6879		};
6880
6881		ns.get_action_message = function(action_id, current_status='')
6882		{	let e = executed_actions[action_id];
6883
6884			let container_found = -1;
6885			let {containers} = ns;
6886			for (let c_id in containers)
6887			{	let c = containers[c_id];
6888				if (c && ((c.tp) & 1))
6889				{	if (Array.isArray(c.ac) && c.ac.indexOf(action_id|0)!=-1)
6890					{	container_found = 0;
6891						if (Array.isArray(c.a))
6892						{	let i = c.a.indexOf(action_id|0);
6893							if (i!=-1 && c.html[i].is_attached())
6894							{	container_found = 1;
6895								break;
6896							}
6897						}
6898					}
6899				}
6900			}
6901
6902			// substring match: the server-side status reads "delivered, not executed (runtime
6903			// error or suppressed)" (and just "not executed" from older backends)
6904			return typeof(e)=='string' ? e : container_found==0 && current_status=='ok' ? 'No placeholder found yet' : e===1 && String(current_status).indexOf('not executed')!=-1 ? 'Scheduled' : '';
6905		};
6906
6907		ns.secs = function() // client's local time offset, not unix time
6908		{	var date = new Date();
6909			return Math.floor(date.getTime() / 1000) - date.getTimezoneOffset()*60;
6910		};
6911
6912		ns.hash = function(str) // for compatibility
6913		{	return ns.strhash(str);
6914		};
6915
6916		ns.subst_page_user_data = function(t, cd)
6917		{	for (var i in cd)
6918			{	t = t.replace('${cd_'+i+'}', cd[i]);
6919			}
6920			return t;
6921		};
6922
6923		ns.subst_user_data = function(format, acc, const_data)
6924		{	if (format == null) return '';
6925			var props=null, props_all=null;
6926			var f = function(all, prop, fmt)
6927			{	var v;
6928				if (const_data?.[prop])
6929				{	v = const_data[prop];
6930				}
6931				else if (prop=='_' || !prop)
6932				{	v = acc || '';
6933				}
6934				else if (prop == 'tm')
6935				{	v = Math.floor(Date.now() / 1000) + '';
6936				}
6937				else if (prop == 'vid')
6938				{	v = cur_visit_id;
6939				}
6940				else if (prop == 'id_short')
6941				{	var v = (parseInt(ns.uid)+2147483647) + '';
6942					while (v.length < 10) v = '0' + v;
6943					v = v.substr(0, 6);
6944				}
6945				else
6946				{	v = ns.user_data[prop];
6947				}
6948				if (v == null)
6949				{	if (!props) props = get_request_props(1, 0, 1);
6950					v = props[prop];
6951				}
6952				if (v == null)
6953				{	if (!props_all) props_all = get_request_props(0, 0, 1);
6954					v = props_all[prop];
6955				}
6956				if (v == null) v = '';
6957				v = ''+v;
6958				fmt.replace
6959				(	RE_SUBST_USER_DATA_FUNC,
6960					function(all, func, args_str)
6961					{	var args = [];
6962						if (args_str)
6963						{	args_str.replace
6964							(	RE_SUBST_USER_DATA_FUNC_ARGS,
6965								arg =>
6966								{	arg = !arg ? '' : arg.trim();
6967									if (arg.charAt(0) == "'")
6968									{	arg = arg.substring(1, arg.length-1).replace(RE_SUBST_USER_DATA_AP, "'");
6969									}
6970									else if (arg.charAt(0) == '"')
6971									{	arg = arg.substring(1, arg.length-1).replace(RE_SUBST_USER_DATA_QT, '"');
6972									}
6973									args[args.length] = arg;
6974								}
6975							);
6976						}
6977						switch (func)
6978						{	case 'json':
6979								v = JSON.stringify(v);
6980							break;
6981							case 'pas':
6982								v = "'"+v.replace(/'/g, "''")+"'";
6983							break;
6984							case 'html':
6985								v = ns.htmlspecialchars(v);
6986							break;
6987							case 'url':
6988								v = encodeURIComponent(v);
6989							break;
6990							case 'time':
6991							case 'time_short':
6992							case 'date':
6993							case 'date_short':
6994							case 'datetime':
6995							case 'datetime_short':
6996								v = ftime.strftime(time_formats[func] || '%c', v);
6997							break;
6998							case 'strftime':
6999								v = ftime.strftime(args[0], v);
7000							break;
7001							case 'trim':
7002								v = !v ? '' : (v+'').trim();
7003							break;
7004							case 'substr':
7005								v = v.substr(args[0]>=0 ? args[0] : v.length+parseInt(args[0]), args[1]>=0 ? args[1] : v.length+parseInt(args[1]));
7006							break;
7007							case 'substring':
7008								v = v.substring(args[0]>=0 ? args[0] : v.length+parseInt(args[0]), args[1]>=0 ? args[1] : v.length+parseInt(args[1]));
7009							break;
7010							case 'replace':
7011								var arg = args[0];
7012								var is_re = false;
7013								if (arg.charAt(0) == '/')
7014								{	try
7015									{	arg = eval(arg);
7016										is_re = true;
7017									}
7018									catch (e)
7019									{
7020									}
7021								}
7022								if (!is_re)
7023								{	arg = new RegExp(arg.replace(RE_SUBST_USER_DATA_ESC, '\\$1'), 'gi'); // default is 'i'!
7024								}
7025								v = v.replace(arg, args[1]);
7026								break;
7027							case 'uc':
7028								v = v.toUpperCase();
7029							break;
7030							case 'lc':
7031								v = v.toLowerCase();
7032							break;
7033							case 'nth-word':
7034								v = nth_word(v, args[0] || '');
7035							break;
7036							case 'param':
7037								v = ns.get_param(args[0] || '');
7038							break;
7039							case 'var':
7040								v = window[args[0]];
7041							break;
7042							case 'func':
7043								v = window[args.shift()];
7044								try
7045								{	v = typeof(v)=='function' ? v.apply(window, args) : '';
7046								}
7047								catch (e)
7048								{	v = '';
7049								}
7050							break;
7051							case 'default':
7052								v = v || args[0];
7053							break;
7054						}
7055						if (v == null) v = '';
7056						v = v+'';
7057					}
7058				);
7059				return v;
7060			};
7061			return (format+'').replace(RE_SUBST_USER_DATA, f);
7062		};
7063
7064		ns.subst_user_data_action = function(format, acc, action_id, action_field, const_data)
7065		{	if (format == null) return '';
7066			if (!const_data) const_data = {};
7067			const_data.action_id = action_id;
7068			if (action_field && action_id>0 && format.join && format.length<=2)
7069			{	var data = (actions_subst_user_data[action_id] || {})[action_field] || '';
7070				return !data ? '' : // no need to wrap if no data
7071				(	(format[0] ? ns.subst_user_data(format[0], acc, const_data) : '') +
7072					ns.subst_user_data(data, acc, const_data) + // server side substitution leaves ${...} where it can't substitute, so complete this on client side
7073					(format[1] ? ns.subst_user_data(format[1], acc, const_data) : '')
7074				);
7075			}
7076			else
7077			{	return ns.subst_user_data(format, acc, const_data);
7078			}
7079		};
7080
7081		ns.get_html_ids_of_action = function(action_id)
7082		{	var html_ids = [];
7083			var {containers} = ns;
7084			for (var c_id in containers)
7085			{	if (containers.hasOwnProperty(c_id))
7086				{	var c = containers[c_id];
7087					for (var i=0; i<c.ac.length; i++)
7088					{	if (c.ac[i] == action_id)
7089						{	html_ids.push(c.hi);
7090							break;
7091						}
7092					}
7093				}
7094			}
7095			return html_ids;
7096		};
7097
7098		ns.get_elems_of_action = function(action_id, default_to_body)
7099		{	var ids = ns.get_html_ids_of_action(action_id);
7100			if (ids.length == 0)
7101			{	return default_to_body ? [document.body || document.documentElement] : [];
7102			}
7103			return ns.id(ids.join(','), true);
7104		};
7105
7106		ns.compose_url = function(url_parts)
7107		{	var {href} = url_parts;
7108			if (!href)
7109			{	function getParam(param, ensure_prefix, no_stray)
7110				{	var part = url_parts[param];
7111					if (!part)
7112					{	return ns.get_location()[param];
7113					}
7114					if (ensure_prefix && part.charAt(0)!=ensure_prefix)
7115					{	part = ensure_prefix + part;
7116					}
7117					if (param=='search' && url_parts.edit_query)
7118					{	var subj = ns.get_location()[param];
7119						if (subj.charAt(0) == '?') subj = subj.substring(1);
7120						var params = part.substring(1).split('&');
7121						for (var i=0; i<params.length; i++)
7122						{	var eq = params[i].indexOf('=');
7123							var name = eq==-1 ? params[i] : params[i].substr(0, eq);
7124							if (name == '') continue;
7125							var value = eq==-1 ? null : params[i].substr(eq+1);
7126							var from = subj.indexOf(name+'=');
7127							var to = from==-1 ? -1 : subj.indexOf('&', from+1);
7128							if (to == -1) to = subj.length;
7129							if (value == null) // remove
7130							{	if (from != -1)
7131								{	subj = subj.substr(0, from) + subj.substring(to+1);
7132								}
7133							}
7134							else // edit
7135							{	if (from != -1)
7136								{	subj = subj.substr(0, from+name.length+1) + value + subj.substring(to);
7137								}
7138								else
7139								{	if (subj.length>0 && subj.charAt(subj.length-1)!='&') subj += '&';
7140									subj += name+'='+value;
7141								}
7142							}
7143						}
7144						if (subj.length>0 && subj.charAt(subj.length-1)=='&')
7145						{	subj = subj.substr(0, subj.length-1);
7146						}
7147						part = '?'+subj;
7148					}
7149					if (no_stray && part==ensure_prefix)
7150					{	part = '';
7151					}
7152					return part;
7153				}
7154
7155				href = getParam('protocol') + '//' + getParam('host') + getParam('pathname', '/') + getParam('search', '?', true) + getParam('hash', '#', true);
7156			}
7157			return href;
7158		};
7159
7160		ns.redirect = function(action, url_parts, throw_error)
7161		{	var href = ns.compose_url(url_parts);
7162			if (decodeURI(ns.get_location().href) != decodeURI(href))
7163			{	ns.set_location(href);
7164				return true;
7165			}
7166			if (throw_error) throw new Error("Tried to redirect to same URL: "+href);
7167			return false;
7168		};
7169
7170		ns.get_visit_id = function()
7171		{	return cur_visit_id;
7172		};
7173
7174		/**	Function that other scripts on page can call; returns occured condition IDs - not all, but only that for which there is a container that depends on them, or a page refresh interval, or for which send_to_client is set to true
7175		 **/
7176		ns.get_conditions = function()
7177		{	var cur_conds = [];
7178			for (var i in current_conditions)
7179			{	if (current_conditions.hasOwnProperty(i))
7180				{	cur_conds.push(parseInt(i.substring(1)));
7181				}
7182			}
7183			return cur_conds;
7184		};
7185
7186		/**	function that other scripts on page can call
7187		 **/
7188		ns.get_pagegroups = function()
7189		{	return current_pagegroups.concat([]);
7190		};
7191
7192		/**	Public API: suppress an action for the visitor without closing/removing its
7193			currently shown content (unlike $personyze_button_dont_show_again, which does
7194			both). Alias of disable_action(); the optional for_n_sessions (default 1)
7195			keeps the action suppressed for that many sessions, or pass for_secs to
7196			suppress for wall-clock time instead.
7197		 **/
7198		ns.session_disable_action = function(action_id, for_n_sessions, for_secs)
7199		{	ns.disable_action(action_id, for_n_sessions, for_secs);
7200		};
7201
7202		/**	Public API: suppress action_id for the visitor for for_n_sessions sessions
7203			(default/minimum 1), or when for_secs > 0 for that many seconds of wall-clock
7204			time instead (rounded up to whole minutes — the cookie stores a minutes-since-
7205			epoch expiry stamp). The action's conditions are re-evaluated after that.
7206		 **/
7207		ns.disable_action = function(action_id, for_n_sessions, for_secs)
7208		{	action_id = parseInt(action_id);
7209			for_n_sessions |= 0;
7210			for_secs |= 0;
7211			if (action_id > 0)
7212			{	var set_cookie = {};
7213				if (for_secs > 0)
7214				{	set_cookie[action_id] = {till_min: Math.ceil((ns.secs()+for_secs)/60), match_again: 1};
7215				}
7216				else
7217				{	if (for_n_sessions <= 1) // assert(true <= 1); assert(!(undefined <= 1));
7218					{	for_n_sessions = 1;
7219					}
7220					set_cookie[action_id] = {remain_sessions: for_n_sessions, match_again: 1};
7221				}
7222				new SessCnt('noacts').add(set_cookie, 1);
7223			}
7224		};
7225
7226		ns.need_query_social = function(login_id_type)
7227		{	return (','+ns.cookie_obj('_stat_track_s_id')._so+',').indexOf(','+login_id_type+',') == -1;
7228		};
7229
7230		ns.social = function(login_id_type, data, onload, onerror)
7231		{	function no_need_query_social(login_id_type)
7232			{	var sid = ns.cookie_obj('_stat_track_s_id');
7233				var so_arr=[login_id_type], so_obj={};
7234				so_obj[login_id_type] = null;
7235				(sid._so || '').replace
7236				(	/\b\w\w\b/g,
7237					$0 =>
7238					{	if (!so_obj.hasOwnProperty($0))
7239						{	so_arr.push($0);
7240							so_obj[$0] = null;
7241						}
7242					}
7243				);
7244				if (so_arr.length < 20)
7245				{	ns.cookie_obj('_stat_track_s_id', {_so: so_arr.join(',')});
7246				}
7247			}
7248			if (login_id_type=='IN' || login_id_type=='SA' || login_id_type=='DO' || ns.need_query_social(login_id_type))
7249			{	if (login_id_type!='IN' && login_id_type!='SA')
7250				{	login_id_type = (login_id_type+'').substr(0, 2);
7251					no_need_query_social(login_id_type);
7252				}
7253				let data_arr = ['TP='+login_id_type];
7254				let data_len = 5;
7255				ns.user_data = ns.user_data || {};
7256				for (let i in data)
7257				{	if (typeof(data[i])=='number' || typeof(data[i])=='string')
7258					{	let item = (data[i]+'').substr(0, 200);
7259						if (item)
7260						{	ns.user_data[i] = item;
7261							item = i+'='+encodeURIComponent(item);
7262							data_len += item.length;
7263							if (data_len > 2500) break;
7264							data_arr.push(item);
7265						}
7266					}
7267				}
7268				if (data_arr.length > 1)
7269				{	log_event('-4', data_arr.join('&'), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&1048576 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&1048576 ? LOG_EVENT_SEND_NOW : 0), 0, onload, onerror);
7270				}
7271			}
7272		};
7273
7274		/**	DEPRECATED
7275		 **/
7276		ns.send_user_data = function(data, onload, onerror)
7277		{	ns.social('IN', data, onload, onerror);
7278		};
7279
7280		/**	The escape hatch for "open it when THIS happens", where THIS is something
7281			only the site itself knows: a cart update, a step of a wizard, a video
7282			finishing, a price recalculated. The site calls _S_T.trigger('name') and
7283			any action whose `when.event` names it opens.
7284
7285			Two decisions worth knowing:
7286
7287			**A fired name stays fired for the rest of the page.** Actions arrive
7288			asynchronously and a dataLayer push near the top of <head> beats them
7289			every time, so a trigger that only notified whoever was already listening
7290			would silently never work for the earliest — and most common — events.
7291			A late subscriber therefore fires immediately on a name already seen.
7292			The semantic is "did this happen on this page", not "happens next".
7293
7294			**Names are the site's, unprefixed.** A dataLayer `{event: 'add_to_cart'}`
7295			is triggerable as `add_to_cart`, because the author types the name they
7296			can already see in their own tag manager.
7297		 **/
7298		var custom_trigger_fired = {};
7299		var custom_trigger_subs = {};
7300
7301		ns.trigger = function(name)
7302		{	name = (name==null ? '' : name+'').trim();
7303			if (name)
7304			{	custom_trigger_fired[name] = true;
7305				var subs = custom_trigger_subs[name];
7306				if (subs)
7307				{	// Copy: a subscriber that removes itself while showing would
7308					// otherwise reindex the array mid-loop and skip its neighbour.
7309					subs = subs.slice();
7310					for (var i=0; i<subs.length; i++)
7311					{	ns.pcall(subs[i]);
7312					}
7313				}
7314			}
7315		};
7316
7317		/**	Returns a handle with `remove()`, so it can go straight into an action's
7318			`attached_events` like every other trigger registration. **/
7319		ns.on_custom_trigger = function(name, callback)
7320		{	name = (name==null ? '' : name+'').trim();
7321			var subs = custom_trigger_subs[name] || (custom_trigger_subs[name] = []);
7322			subs.push(callback);
7323			if (custom_trigger_fired[name])
7324			{	// Deferred, not called straight out: a modal popup's do_show() runs
7325				// synchronously, so replaying inline would show the action BEFORE this
7326				// function has returned the handle its caller pushes into
7327				// `attached_events` — and anything that tore the action down in between
7328				// would drain that list before the handle was in it, leaking a listener
7329				// nothing can remove. The re-check covers a teardown during the wait.
7330				setTimeout
7331				(	function()
7332					{	if (subs.indexOf(callback) != -1)
7333						{	ns.pcall(callback);
7334						}
7335					},
7336					0
7337				);
7338			}
7339			return {
7340				remove()
7341				{	var i = subs.indexOf(callback);
7342					if (i != -1)
7343					{	subs.splice(i, 1);
7344					}
7345				}
7346			};
7347		};
7348
7349		/**	Switch 1 - the explicit testing-mode switch, remembered until switched back. Set by the
7350			"_S_T=testing|production" URL parameter, or by
7351			_S_T.te(1) / _S_T.te(0) from the console.
7352
7353			_te (test environment)
7354			0 - no (production)
7355			1 - yes (testing)
7356			2 - no, but show controls (production)
7357
7358			Nothing may write this on behalf of switch 2 (the account's testing-mode IP). That switch
7359			is not the visitor's choice and has to be re-decided on every log visit, so it lives in
7360			'_te_ip' and is owned by the server. Conflating the two is what used to strand ordinary
7361			visitors in testing mode for good: testing IPs auto-fill with whoever last saved a campaign,
7362			so anyone sharing that egress IP got '_te=1' written once - and nothing ever cleared it,
7363			not even changing IP, closing the browser or the account leaving testing mode.
7364		 **/
7365		ns.te = function(value)
7366		{	return ns.cookie_obj('_stat_track_s_id', value==null ? null : {_te: value-0 ? 1 : 2, _tev: TE_VERSION})._te-0 == 1 ? 1 : 0;
7367		};
7368
7369		/**	The mode actually in force - either switch turns testing on, and switch 2 is checked first.
7370			Mirrors what st_log_visit_N does with the same two inputs:
7371			"IF p_is_testing_mode_ip THEN SET p_version_tag_used = 'testing'".
7372		 **/
7373		ns.te_eff = function()
7374		{	return ns.is_te_ip || ns.te();
7375		};
7376
7377		/**	'_te' used to be written on behalf of the testing-mode IP as well as by the explicit switch,
7378			so a '_te' left by an older lib cannot be told apart from a real choice - and the IP-derived
7379			ones are exactly what must go. Any '_te' not carrying the current TE_VERSION is therefore
7380			dropped. The cost is that testers who switched on explicitly add the URL parameter once
7381			more; the alternative is leaving site visitors stranded in testing mode, which is the whole
7382			point of the split.
7383
7384			Also loads switch 2 from the previous page load, because get_js_url() has to choose between
7385			the testing and the production build of a JS file before this page's first response arrives.
7386		 **/
7387		function init_testing_mode()
7388		{	var sid_obj = ns.cookie_obj('_stat_track_s_id');
7389			if (sid_obj._te && sid_obj._tev-0 != TE_VERSION)
7390			{	ns.cookie_obj('_stat_track_s_id', {_te: '', _tev: TE_VERSION});
7391			}
7392			ns.is_te_ip = sid_obj._te_ip-0 ? 1 : 0;
7393		}
7394
7395		/**	Start a fresh session from the testing-mode console, the same way the Simulator's
7396			"Start new session" button does.
7397
7398			Two things this must get right that clearing cookies by hand does not:
7399			- `cookie_obj(..., true)` is used rather than `delete_cookie()`, because every cookie
7400			  is mirrored into localStorage under the same key and is read back from there when
7401			  the cookie is gone. Deleting the cookie alone leaves the mirror, so the old id
7402			  simply returns. Only the write path rewrites both.
7403			- emptying the cookie also drops `_te` and `_te_ip`, which would silently kick the
7404			  tester out of testing mode, so both are put back afterwards.
7405		 **/
7406		ns.te_new_session = function()
7407		{	var was_te = ns.te(), was_te_ip = ns.is_te_ip;
7408			ns.cookie_obj('_stat_track_s_id', {}, true);
7409			tracker_events.set(null); // buffered events belong to the session that just ended
7410			if (was_te)
7411			{	ns.te(1);
7412			}
7413			ns.te_ip(was_te_ip);
7414			location.reload();
7415		};
7416
7417		/**	Become a brand new visitor. Emptying `uid` makes make_id() take its new-user branch,
7418			which starts a new session too. `dg` is preserved so an attached Simulator survives
7419			the reload. `stat_track_actions` holds the per-action display history behind
7420			frequency capping - a "new" visitor that still carries it would keep having campaigns
7421			suppressed, so it goes as well.
7422		 **/
7423		ns.te_new_user = function()
7424		{	var was_te = ns.te(), was_te_ip = ns.is_te_ip;
7425			ns.cookie_obj('stat_track_u_id', {dg: ns.cookie_obj('stat_track_u_id').dg}, true);
7426			ns.cookie_obj('_stat_track_s_id', {}, true);
7427			tracker_events.set(null);
7428			try
7429			{	window.localStorage?.removeItem('stat_track_actions');
7430			}
7431			catch (e)
7432			{
7433			}
7434			ns.storage_obj('stat_track_sessions', {}, true);
7435			if (was_te)
7436			{	ns.te(1);
7437			}
7438			ns.te_ip(was_te_ip);
7439			location.reload();
7440		};
7441
7442		/**	Switch 2 - is this visitor's IP currently one of the account's testing-mode IPs? Not the
7443			visitor's choice, so it is never merged into '_te'. Every log-visit response calls this with
7444			the value the server just computed from the connecting IP, including 0, so leaving the
7445			testing IP takes effect on the very next request.
7446
7447			It is remembered between page loads only because get_js_url() must pick the testing or the
7448			production build of a JS file before the first response comes back. A stale value therefore
7449			costs at most one page load and is corrected by that same load's response - it cannot
7450			outlive the IP the way '_te' used to.
7451
7452			A missing argument means 1: responses generated before the split emit a bare te_ip() and
7453			only ever emitted it to say "yes".
7454		 **/
7455		ns.te_ip = function(value)
7456		{	ns.is_te_ip = value==null || value-0 ? 1 : 0;
7457			ns.cookie_obj('_stat_track_s_id', {_te_ip: ns.is_te_ip});
7458			return ns.is_te_ip;
7459		};
7460
7461		ns.get_js_url = function(js_filename, override_te, is_action)
7462		{	const FACTORIZE_JS_URLS = 0;
7463			const HOST = "qacounter.personyze.com";
7464			const HOST_T = "qacounter.personyze.com";
7465			var factor = 4773;
7466
7467			var te = override_te!=null ? override_te : ns.te_eff();
7468			var host = !NEW_ELEM_NDEBUG || te ? HOST_T : HOST;
7469			var is_action_params = js_filename > 0;
7470			if (is_action || js_filename=='index')
7471			{	js_filename = 'default/'+js_filename;
7472			}
7473			if (!is_action && te)
7474			{	js_filename += '-t';
7475			}
7476			if (!NEW_ELEM_NDEBUG && !is_action_params)
7477			{	js_filename += '-debug';
7478			}
7479			var url = get_protocol()+'//'+host;
7480			if (!is_action)
7481			{	url += "\/js\/"+ns.server_id+'/';
7482				factor = get_f();
7483				if (!factor)
7484				{	// No factor yet (first ever page view). A random number is right: it cannot collide with a real
7485					// version's cache key, so whatever comes back is never mistaken for a published build.
7486					factor = 1000000+Math.floor(Math.random()*1000000);
7487				}
7488				else if (get_fb())
7489				{	/*	A build newer than this factor exists that not every app host serves yet, so right now the
7490						hosts can disagree about what index.js holds. Ask for a DIFFERENT url than the clean one.
7491
7492						These files are served with `expires 30d` and the versioned name is only an alias for one
7493						mutable file per host (nginx `try_files $1$2.js $1.js`), so whichever build answers gets
7494						pinned under the name it was asked by for a month. Sending the unsettled window to its own
7495						'b' key means the clean key is only ever filled during windows when every host serves
7496						exactly the build it names -- and the 'b' key is simply abandoned once the build goes out.
7497					 */
7498					factor += 'b';
7499				}
7500			}
7501			else
7502			{	url += "\/actions\/";
7503			}
7504			if (FACTORIZE_JS_URLS && NEW_ELEM_NDEBUG)
7505			{	return url+js_filename+'-'+factor+'.js';
7506			}
7507			else
7508			{	return url+js_filename+'.js?fr='+factor;
7509			}
7510		};
7511
7512		ns.load_libs = function(js_filenames, onload, onerror, override_te)
7513		{	var url, checker;
7514			if (!js_filenames)
7515			{	return onload?.();
7516			}
7517			if (!js_filenames.join)
7518			{	js_filenames = [js_filenames];
7519			}
7520			var cur_ld = {};
7521			var ld = [cur_ld];
7522			for (var i=0; i<js_filenames.length; i++)
7523			{	let filename = js_filenames[i];
7524				if (typeof(filename) == 'object')
7525				{	url = filename.src;
7526					checker = null;
7527					if (!url)
7528					{	cur_ld = {};
7529						ld.push(cur_ld);
7530						return;
7531					}
7532					var checker_var = filename.check;
7533					if (checker_var)
7534					{	checker_var = checker_var.split(/\s*\.\s*/);
7535						checker = function()
7536						{	var obj = window;
7537							for (var i=0; i<checker_var.length && obj; i++)
7538							{	obj = obj[checker_var[i]];
7539							}
7540							return obj;
7541						};
7542					}
7543				}
7544				else if (filename == 'index')
7545				{	url = ns.get_js_url(filename);
7546					checker = () => ins_status_install;
7547				}
7548				else if (filename > 0)
7549				{	url = ns.get_js_url(filename, override_te);
7550					if (override_te != null)
7551					{	ns.act_params[filename] = null;
7552					}
7553					checker = () => ns.act_params[filename];
7554				}
7555				else
7556				{	url = ns.get_js_url(filename, override_te, true);
7557					checker = () => ns[filename] || ns.act[filename] || ns.templates[filename]; // lib || action_js
7558				}
7559				cur_ld[url] = checker;
7560			}
7561			ns.load_scripts
7562			(	ld,
7563				err_msg =>
7564				{	if (!err_msg)
7565					{	onload?.();
7566					}
7567					else
7568					{	onerror?.(err_msg);
7569					}
7570				}
7571			);
7572		};
7573
7574		ns.pass_cookie_url = function(url, no_count)
7575		{	var u = ns.cookie_obj('stat_track_u_id');
7576			var s = ns.cookie_obj('_stat_track_s_id');
7577			if (url.indexOf('/') == -1)
7578			{	url = '//'+url;
7579			}
7580			return ns.url_replace_param(url, '_S_T', 'u'+u.uid+(no_count ? 'n' : '')+(parseInt(u.off) ? 'o' : '')+(s._inew&2 ? 'u' : '')+'_'+u.st+'_'+u.ls+'_'+(u.sy||'').replace(/,/g, ';')+'_'+s._sid+'_'+(s._bh||''));
7581		};
7582
7583		ns.pass_cookie = function(urls)
7584		{	if (!urls)
7585			{	urls = [];
7586				var {domains} = ns;
7587				for (var d in domains)
7588				{	if (d!=ns.get_location().host && domains.hasOwnProperty(d))
7589					{	if (d.substr(0, 2) == '*.')
7590						{	d = d.substring(2);
7591							if (d in domains) continue;
7592						}
7593						urls.push('//'+d+'/');
7594					}
7595				}
7596			}
7597			var div = ns.new_elem('div', null, {style: 'position:absolute; display:none'});
7598			for (var i=0; i<urls.length; i++)
7599			{	var iframe = ns.new_elem
7600				(	'iframe',
7601					div,
7602					{	src: ns.pass_cookie_url(urls[i], true),
7603						width: 1,
7604						height: 1
7605					}
7606				);
7607			}
7608			document.body.appendChild(div);
7609			setTimeout(() => {try {document.body.removeChild(div)} catch (e) {}}, 10000);
7610			return div;
7611		};
7612
7613		/*	`consent` is the OPT-IN third argument, present only in snippets generated for accounts that
7614			turned consent mode on. Absent (every existing installation) this whole block is skipped and
7615			setup behaves exactly as it always has.
7616
7617			It must be an OBJECT, and that check is load-bearing rather than defensive. Snippets in the
7618			wild pass the domain list as separate arguments instead of one space-separated string --
7619			`setup(6980, "clientes.example.com", "example.com", "*.example.com")` -- so a third argument
7620			is not evidence of consent mode at all; it is somebody's second domain. Testing this for
7621			truthiness took dkv.es (account 6980) dark: the hold engaged, `consent.timeout` was
7622			undefined on a string so it waited the default 3 seconds, nothing called grant() because
7623			nothing knew it should, and the tracker switched itself off. Silently -- the library loads,
7624			`is_on()` is true, and no request is ever made.
7625
7626			So: only a real object opts in. Extra positional arguments stay what they always were,
7627			which is ignored.
7628
7629			Truthy means: do nothing at all until the site tells us the visitor agreed. Nothing means
7630			nothing -- no cookie read, no sessionStorage, no request. Note `ns.system.is_on()` below
7631			reads `stat_track_u_id`, so the hold has to sit ABOVE that call rather than 
7631inside it;
7632			reading a cookie is itself access to the visitor's device.
7633
7634			Deliberately NOT hiding the page while we wait. The tracker's usual trick for avoiding
7635			flicker is `body {visibility:hidden}` until it is ready, and reusing it here would hide the
7636			consent banner the visitor is supposed to be reading -- a blank page until timeout, on every
7637			first visit. So the page renders unpersonalized and personalization appears after consent.
7638
7639			`consent.timeout` is a safety net for a site with a broken or missing CMP, not a delay: a
7640			returning visitor's consent manager answers from its own storage within a frame or two. On
7641			expiry we take the OFF path, because an account that enabled this did so to be able to say
7642			nothing happens without consent. */
7643		/*	Default, so that `_S_T.consent.status()` answers on EVERY site rather than only on one that
7644			engaged the hold -- the site you most need to ask is the one where you are not sure whether
7645			consent mode is involved at all, and there `ns.consent` would otherwise be undefined and the
7646			question would throw instead of answering "off". Replaced wholesale by the real object below
7647			when a hold actually starts; grant/deny are no-ops until then, because with no hold in
7648			progress there is nothing to release. */
7649		ns.consent =
7650		{	status()
7651			{	return consent_denied ? 'denied' : consent_granted ? 'granted' : consent_pending ? 'waiting' : 'off';
7652			},
7653			grant() {},
7654			deny() {}
7655		};
7656
7657		ns.setup = function(server_id, domains, consent)
7658		{	is_sync_blocking = !document.body; // assume: sync tracker code is in <head>
7659						if (consent && typeof consent == 'object' && !consent_pending && !consent_denied && !ns.server_id)
7660			{	consent_pending = [server_id, domains];
7661				let settled = false;
7662				let wait_msec = consent.timeout > 0 ? consent.timeout : 3000;
7663				/*	Expiry means nothing on the page ever answered -- not that the visitor declined. That
7664					is a broken installation rather than a visitor choice, and it is otherwise perfectly
7665					silent: the library loads, `is_on()` is true, no request is made and n
7665o error appears.
7666					dkv.es sat in exactly this state and it took a customer noticing, so it says so.
7667
7668					Unconditional, NOT gated on testing mode, because `ns.te_eff()` reads the
7669					`_stat_track_s_id` cookie -- the very access the hold exists to avoid. Volume is not a
7670					concern: it can only fire on a site that turned consent mode ON and then never wired a
7671					callback, where every visitor is already being lost. A correctly wired site never
7672					reaches this line, and an explicit deny() below prints nothing, because a visitor
7673					saying no is the feature working. */
7674				let timer = setTimeout
7675				(	() =>
7676					{	errmsg
7677						(	'consent mode is on for this account, but nothing called '+
7678							CONF__PERSONYZE_COMMANDS_JS_VAR_NAME+'.consent.grant() or .deny() within '+wait_msec+
7679							'ms, so tracking and personalization stayed OFF. Wire your consent manager to those calls, '+
7680							'or turn consent mode off in the panel.'
7681						);
7682						ns.consent.deny();
7683					},
7684					wait_msec
7685				);
7686				/*	Public API for the CMP adapter. Idempotent on purpose: consent managers fire their
7687					listener more than once (initial state, then again on a change), and a second grant
7688					must not re-run setup and log a second page view. */
7689				ns.consent =
7690				{	/*	Readable from the console, because the failure mode this feature has is a SILENT
7691						one: the library loads, `is_on()` is true, and nothing ever happens. There is no
7692						error to find and no request to inspect, so without somewhere to look, "no
7693						personalization" and "waiting for consent" are indistinguishable from outside.
7694						That is what made the dkv.es breakage take a customer report rather than a
7695						console glance. */
7696					status()
7697					{	return consent_denied ? 'denied' : consent_granted ? 'granted' : consent_pending ? 'waiting' : 'off';
7698					},
7699					grant()
7700					{	if (!settled)
7701						{	settled = true;
7702							clearTimeout(timer);
7703							let args = consent_pending;
7704							consent_pending = null;
7705							consent_granted = true;
7706							/*	Re-entering setup with no `consent` argument runs the ordinary path.
7707								`is_sync_blocking` is recomputed from `!document.body` on the way in, and
7708								by now the body exists -- so the resumed run cannot hide a page the
7709								visitor is already looking at. */
7710							ns.setup(args[0], args[1]);
7711						}
7712					},
7713					deny()
7714					{	if (!settled)
7715						{	settled = true;
7716							clearTimeout(timer);
7717							let args = consent_pending;
7718							consent_pending = null;
7719							consent_denied = true;
7720							ns.setup(args[0], args[1]);
7721						}
7722					}
7723				};
7724				return;
7725			}
7726			if (!ns.server_id)
7727			{	ns.server_id = server_id;
7728				if (!consent_denied && ns.system.is_on())
7729				{	unblock_all_timer = setTimeout
7730					(	() =>
7731						{	force_unblock_all = true;
7732							unblock_all();
7733						},
7734						1000					);
7735					if (is_sync_blocking)
7736					{	var head = document.getElementsByTagName('head')[0];
7737						var style = ns.new_elem('style', head);
7738						var css = 'body {visibility:hidden}';
7739						if (style.styleSheet)
7740						{	style.styleSheet.cssText = css;
7741						}
7742						else
7743						{	style.appendChild(document.createTextNode(css));
7744						}
7745						ns.reg_ondropcss
7746						(	() =>
7747							{	if (style.parentNode)
7748								{	head.removeChild(style);
7749								}
7750							}
7751						);
7752					}
7753					if (window.sessionStorage)
7754					{	try
7755						{	var containers = JSON.parse(sessionStorage['_S_T.c'] || 'null');
7756							domains = domains || JSON.parse(sessionStorage['_S_T.d'] || 'null');
7757							let gtm = JSON.parse(sessionStorage['_S_T.gtm'] || 'null');
7758							//JSON.stringify([new_gtm_data_layer_name, new_gtm_grab, new_gtm_product_id_column]);
7759							tealium_storage_name = sessionStorage['_S_T.tealium'] || tealium_storage_name;
7760							if (gtm)
7761							{	gtm_data_layer_name = gtm[0];
7762								gtm_grab = gtm[1];
7763								gtm_product_id_column = gtm[2];
7764								gtm_key = gtm[3];
7765							}
7766							if (containers)
7767							{	set_conts(containers);
7768							}
7769						}
7770						catch (e)
7771						{	ns.error(e);
7772						}
7773					}
7774					init_testing_mode(); // before any get_js_url(), which picks the testing or production build
7775					if (!domains || !containers)
7776					{	ns.load_libs('index');
7777					}
7778					else
7779					{	set_domains(domains);
7780						if (get_personyze_override().t != null)
7781						{	ns.te(get_personyze_override().t);
7782						}
7783						is_parallel = true;
7784						ns.load_libs
7785						(	'index',
7786							null,
7787							exec_ondropcss // onerror
7788						);
7789						var timer = setTimeout(exec_ondropcss, 1000);
7790						ns.reg_onready
7791						(	() =>
7792							{	init_containers.immediate_call();
7793								send_request
7794								(	LOG_EVENT_SEND_IMMED,
7795									1,
7796									null,
7797									null,
7798									() => // onload
7799									{	clearTimeout(timer);
7800									},
7801									exec_ondropcss, // onerror
7802									() => ins_status_log_visit // checker_func
7803								);
7804							}
7805						);
7806					}
7807				}
7808				else
7809				{	exec_ondropcss();
7810					exec_commands();
7811				}
7812			}
7813			else
7814			{	ns.su2 = server_id;
7815			}
7816			// The whole page is hidden until the decision lands; this is the floor under a decision
7817			// that never arrives at all. See WConf::$hide_body_max for what the number is trading.
7818			setTimeout(exec_ondropcss, 1500);
7819		};
7820
7821		ns.install = function(new_f, hide_body_till_actions, containers, domains, embedded_act_params, actions_qelem_ids, is_async_blocking, new_add_to_cart, new_send_after_unload, new_add_to_wishlist, new_ga_account, new_gtm_data_layer_name, new_gtm_grab, new_gtm_product_id_column, new_remove_from_cart, new_tealium_storage_name, new_gtm_key, new_ga_is_active, new_default_send_dests, new_event_send_with_delay, new_event_send_now, new_ga_is_gtm, new_add_to_cart_success_html, new_add_to_wishlist_success_html, new_add_to_cart_error_html, new_add_to_wishlist_error_html)
7822		{	try
7823			{	if (new_add_to_cart)
7824				{	add_to_cart_func = typeof(new_add_to_cart)=='string' ? eval(new_add_to_cart) : new_add_to_cart;
7825				}
7826				if (new_add_to_wishlist)
7827				{	add_to_wishlist_func = typeof(new_add_to_wishlist)=='string' ? eval(new_add_to_wishlist) : new_add_to_wishlist;
7828				}
7829				if (new_remove_from_cart)
7830				{	remove_from_cart_func = typeof(new_remove_from_cart)=='string' ? eval(new_remove_from_cart) : new_remove_from_cart;
7831				}
7832			}
7833			catch (e)
7834			{	ns.error(e);
7835			}
7836			event_send_now = +new_event_send_now;
7837			event_send_with_delay = +new_event_send_with_delay;
7838			send_after_unload = new_send_after_unload;
7839			ga_is_active = new_ga_is_active;
7840			default_send_dests = new_default_send_dests || '';
7841			ga_is_gtm = new_ga_is_gtm;
7842			gtm_data_layer_name = new_gtm_data_layer_name;
7843			gtm_grab = new_gtm_grab;
7844			gtm_product_id_column = new_gtm_product_id_column;
7845			gtm_key = new_gtm_key;
7846			tealium_storage_name = new_tealium_storage_name;
7847			add_to_cart_success_html = new_add_to_cart_success_html;
7848			add_to_wishlist_success_html = new_add_to_wishlist_success_html;
7849			add_to_cart_error_html = new_add_to_cart_error_html;
7850			add_to_wishlist_error_html = new_add_to_wishlist_error_html;
7851
7852			set_domains(domains);
7853			if (window.sessionStorage)
7854			{	sessionStorage['_S_T.d'] = JSON.stringify(domains);
7855				sessionStorage['_S_T.c'] = JSON.stringify(containers);
7856				sessionStorage['_S_T.gtm'] = JSON.stringify([gtm_data_layer_name, gtm_grab, gtm_product_id_column, gtm_key]);
7857				sessionStorage['_S_T.tealium'] = tealium_storage_name || '';
7858			}
7859			/*	The factor baked into this file is the BUILT version -- the file was written by whichever app host
7860				rebuilt first, so it can be ahead of what has been published to browsers. It must never become our
7861				factor on its own. Adopting it is what used to make the browser re-request index.js under a version
7862				name that a still-lagging host answered with the PREVIOUS build, which `expires 30d` then pinned
7863				under that name for a month; and since install() would then write the older number back into the
7864				cookie, every following page view repeated the same request and got the same poisoned cache entry.
7865				The factor belongs to log_visit now.
7866
7867				Bootstrapping from it when there is no factor at all is still worth doing -- a visitor whose
7868				log_visit never completes would otherwise refetch index.js under a fresh random url on every page
7869				view -- but it goes in marked dirty, so urls stay on the quarantined 'b' key until log_visit
7870				confirms the version is published. This sets a cookie, so it must come after domains are set.
7871			 */
7872			if (!get_f() && new_f)
7873			{	set_f(new_f, 1);
7874			}
7875			set_conts(containers);
7876			for (var i in embedded_act_params)
7877			{	if (embedded_act_params.hasOwnProperty(i) && !ns.act_params[i])
7878				{	ns.act_params[i] = embedded_act_params[i];
7879				}
7880			}
7881			if (!is_ready)
7882			{	install_focus();
7883				memorize_not_executed();
7884				/*	THE PAINT GATE. `is_async_blocking` is the per-account opt-in that runs the
7885					pre-hide even when the library loaded asynchronously - which is the one path
7886					that can arrive AFTER the page is on screen. When it does, the "pre"-hide is
7887					nothing of the sort: it blanks content the visitor is reading and hands it
7888					back a moment later. Measured on personyze.com (Jacob, 28 Aug 26): tracker at
7889					4136ms, first paint at 4180ms - a photo-finish that the tracker loses on any
7890					slow connection, and every loss was a manufactured flash. The flag was ON for
7891					exactly one account in the fleet, because this failure made it
7892					unrecommendable.
7893
7894					So the async opt-in now defers to reality: hide only while the content is
7895					still unseen (which includes elements covered by the snippet's inline
7896					pre-hide since the first byte - see content_already_seen). Once the visitor
7897					has the page, this view behaves as if the flag were off - the change pops in
7898					late rather than the page blinking.
7899
7900					The sync paths are NOT gated, deliberately: `is_sync_blocking` means the
7901					library ran before <body> existed, and `wants_to_block` is set by that same
7902					path - both are pre-paint by construction, and gating them would only add a
7903					way for a support of paint-timing quirks to break the genuine anti-flicker. */
7904				if (wants_to_block || is_sync_blocking || (is_async_blocking && !content_already_seen()))
7905				{	pre_init_containers(actions_qelem_ids);
7906				}
7907				ins_status_done();
7908				if (!is_parallel)
7909				{	ns.reg_onready
7910					(	() =>
7911						{	init_containers.immediate_call();
7912							send_request(LOG_EVENT_SEND_IMMED, 1);
7913						}
7914					);
7915				}
7916			}
7917			if (!hide_body_till_actions || !is_sync_blocking)
7918			{	exec_ondropcss();
7919			}
7920			if (tracker_events.buffered_mode)
7921			{	log_event(0, '', tracker_events.buffered_mode);
7922			}
7923		};
7924
7925		ns.log_visit = function(new_f, visit_id, user_data, log_navigation_on, conds, actions, client_cookie, clear_conds, clear_actions, bh, ga_cond_names, pagegroups, asud, session_start_time, hs, interests, new_host_id, try_debuggers, article_viewed_internal_id, factor_dirty)
7926		{	var f = get_f();
7927			if (f && f<new_f && !is_ready)
7928			{	set_f(new_f, factor_dirty);
7929			}
7930			ins_status = {new_f, factor_dirty, visit_id, actions, clear_conds, clear_actions};
7931			ns.si = session_start_time;
7932			actions_q_add(actions);
7933			/*	THE WYSIWYG BACKSTOP IS ARMED HERE, and deliberately not where the sheet goes up.
7934
7935				Measured from the moment the actions are KNOWN, not from the moment the library
7936				loaded. Those are the same instant on a warm connection and very far apart on a
7937				cold one, and starting the clock at load meant a page whose decision took longer
7938				than the backstop uncovered its elements while the change that was going to alter
7939				them had not arrived yet - the original content flashing before the personalized
7940				content, which is the whole thing this machinery exists to prevent, reintroduced on
7941				exactly the connections that can least afford it.
7942
7943				From here it is a real bound: the actions are in hand, a WYSIWYG change is applied
7944				synchronously inside its action's `onexec`, so anything still not applied is not
7945				going to be applied this pass. Before this point nothing is armed and the sheet
7946				behaves exactly as it always did - `force_unblock_all` remains its backstop, so a
7947				decision that never arrives is still covered. */
7948			if (!unblock_qelem_timer)
7949			{	unblock_qelem_timer = setTimeout(unblock_qelem, 300);
7950			}
7951			narrow_container_hide(actions);
7952			current_conditions = conds;
7953			current_pagegroups = pagegroups;
7954			current_interests = interests;
7955			host_id = new_host_id;
7956			set_user_data(user_data);
7957			if (asud)
7958			{	for (var action_id in asud)
7959				{	if (asud.hasOwnProperty(action_id))
7960					{	actions_subst_user_data[action_id] = asud[action_id];
7961					}
7962				}
7963			}
7964			if (ga_cond_names)
7965			{	for (var i=0; i<ga_cond_names.length; i++)
7966				{	ns.ga_send('condition', ga_cond_names[i]);
7967				}
7968			}
7969			// bh, session_start_time
7970			ns.cookie_obj('_stat_track_s_id', {_bh: bh, _si: session_start_time});
7971			/*	AND READABLE BY AN ACTION. `bh` names the tracker host this session's visitor tables
7972				live on - visitor data is partitioned across an account's hosts, so a reader that
7973				does not know it is guessing. It has always been round-tripped in the cookie and the
7974				query string; an action that needs to tell a service of ours which host to read had
7975				no way to see it. The AI Chat placement passes it on to the chat endpoint. */
7976			ns.best_hosts = bh || '';
7977			// hs, client_cookie
7978			var obj = hs-0 ? {hs: 1} : {};
7979			if (client_cookie)
7980			{	if (client_cookie.id - 0)
7981				{	obj.uid = ns.uid = client_cookie.id-0;
7982				}
7983				if (client_cookie.st - 0)
7984				{	obj.st = client_cookie.st-0;
7985				}
7986				if (client_cookie.ls - 0)
7987				{	obj.ls = client_cookie.ls-0;
7988				}
7989				if ((client_cookie.sy+'').match(/^(\d+:-?\d+)?(,\d+:-?\d+)*$/))
7990				{	obj.sy = client_cookie.sy+'';
7991				}
7992			}
7993			obj = ns.cookie_obj('stat_track_u_id', obj);
7994			ns.install_spa(log_navigation_on);
7995			// debuger + done
7996			init_debugger(obj.dg, try_debuggers, () => {ins_status_done(true)});
7997			if (article_viewed_internal_id)
7998			{	wait_for_article_delivery(article_viewed_internal_id);
7999			}
8000		};
8001
8002		ns.install_spa = function(log_navigation_on)
8003		{	const ON_PATHNAME = 1;
8004			const ON_SEARCH = 2;
8005			const ON_HASH = 4;
8006			if (!ns.lno)
8007			{	let prev_pathname = location.pathname;
8008				let prev_search = location.search;
8009				let prev_hash = location.hash;
8010				let h_timeout = null;
8011				let onchange = infrequent_func
8012				(	() =>
8013					{	clearTimeout(h_timeout);
8014						let {pathname, search, hash} = location;
8015						let mask = (pathname!=prev_pathname && ON_PATHNAME) | (search!=prev_search && ON_SEARCH) | (hash!=prev_hash && ON_HASH);
8016						if (mask)
8017						{	prev_pathname = pathname;
8018							prev_search = search;
8019							prev_hash = hash;
8020							// clear 'grab url'
8021							use_location = null;
8022							let {containers} = ns;
8023							for (let c_id in containers)
8024							{	if (containers.hasOwnProperty(c_id))
8025								{	containers[c_id].inited &= ~8192;
8026								}
8027							}
8028							//
8029							reset_conts_retry(); // another page in this SPA - every selector deserves a fresh, undelayed try
8030							init_containers();
8031							//
8032							if (log_navigation_on & mask)
8033							{	ns.log_navigation();
8034							}
8035							else
8036							{	check_for_actions();
8037							}
8038						}
8039					},
8040					40
8041				);
8042				let listeners = {};
8043				ns.new_elem(window).add_props
8044				(	{	onhashchange:
8045						{	func: onchange,
8046							listeners
8047						}
8048					}
8049				);
8050				let {pushState, replaceState} = history;
8051				if (pushState && replaceState)
8052				{	try
8053					{	history.pushState = function(a, b, c)
8054						{	setTimeout(onchange, 0);
8055							setTimeout(onchange, 100);
8056							setTimeout(onchange, 200);
8057							setTimeout(onchange, 300);
8058							return pushState.call(history, a, b, c);
8059						};
8060						history.replaceState = function(a, b, c)
8061						{	setTimeout(onchange, 0);
8062							setTimeout(onchange, 100);
8063							setTimeout(onchange, 200);
8064							setTimeout(onchange, 300);
8065							return replaceState.call(history, a, b, c);
8066						};
8067					}
8068					catch (e)
8069					{	ns.error(e);
8070					}
8071				}
8072				let interval = setInterval(onchange.immediate_call, 500);
8073				ns.lno = function()
8074				{	listeners.onhashchange?.remove();
8075					history.pushState = pushState;
8076					history.replaceState = replaceState;
8077					clearInterval(interval);
8078					ns.lno = null;
8079				};
8080			}
8081		};
8082
8083		ns.reg_ondropcss = function(f)
8084		{	wants_to_block = true;
8085			if (is_ready)
8086			{	f();
8087			}
8088			else
8089			{	ondropcss = ns.pair_funcs(ondropcss, f);
8090			}
8091		};
8092
8093		ns.reg_onevent = function(name, func)
8094		{	onevent[name] = func;
8095		};
8096
8097		ns.reg_onerr = function(name, func)
8098		{	onerr[name] = func;
8099		};
8100
8101		ns.add_to_cart = function(products, action_id, arg)
8102		{	return add_to_cart_or_to_wishlist('add_to_cart', products, action_id, arg);
8103		};
8104
8105		ns.add_to_wishlist = function(products, action_id, arg)
8106		{	return add_to_cart_or_to_wishlist('add_to_wishlist', products, action_id, arg);
8107		};
8108
8109		ns.remove_from_cart = function(products, action_id, arg)
8110		{	return add_to_cart_or_to_wishlist('remove_from_cart', products, action_id, arg);
8111		};
8112
8113		// public interface
8114		window[CONF__PERSONYZE_COMMANDS_JS_VAR_NAME] = ns;
8115
8116		return true;
8117	}
8118
8119// inject_code
8120function _new_elem_tmp_2(ns)
8121	{	/**	@param {String} code - Javascript code, or <script>, <style> or <link> HTML tags. Also supports MSIE conditional comments (only downlevel-hidden).
8122			@param {boolean} local_scope - If false, will eval javascript code in global scope, so "var" will add variables to window
8123		 **/
8124		function inject_code(code, local_scope, ondone)
8125		{	html_cut_exec_code
8126			(	code,
8127				code =>
8128				{	try
8129					{	if (code)
8130						{	if (local_scope)
8131							{	eval(code); // alter "this" (call()) to affect
8132							}
8133							else
8134							{	var global_exec = window.execScript || function(js) {eval.call(window, js)};
8135								global_exec(code);
8136							}
8137						}
8138					}
8139					catch (e)
8140					{	console.error(e);
8141					}
8142					ondone?.();
8143				}
8144			);
8145		}
8146
8147		/**	@param {String} code - Will cut <script>, <style> and <link> HTML tags, and execute/load assets. Also supports MSIE conditional comments (only downlevel-hidden).
8148		 **/
8149		function html_cut_exec_code(code, ondone)
8150		{	var head = document.getElementsByTagName('head')[0];
8151			var loading_sync = false;
8152			var then = '';
8153			var found = true;
8154			while (found)
8155			{	found = false;
8156				code = (code || '').replace
8157				(	/^\s*(<!--[\S\s]*?-->)\s*|^(\s*<(script|style|noscript)(?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s<\/>="']+|"[^"]*"|'[^']*'))?\s*)*>\s*)([\S\s]*?)(<\/\3\s*>\s*)|^\s*(<link(?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s<\/>="']+|"[^"]*"|'[^']*')))*\/?>)\s*/i,
8158					(all, comment, open, tag, contents, close, link) =>
8159					{	found = true;
8160						if (tag == 'script')
8161						{	if (loading_sync)
8162							{	then += all;
8163							}
8164							else if (contents)
8165							{	contents = contents.replace(/^\s*<!\[CDATA\[|\]\]>\s*$/g, '');
8166								var global_exec = window.execScript || function(js) {eval.call(window, js)};
8167								ns.pcall(global_exec, window, [contents]);
8168							}
8169							else
8170							{	var attributes = {};
8171								open.replace
8172								(	/\s([\w\-:]+)(?:\s*=\s*(?:(["'])([\S\s]*?)\2|([^\s</>="']+)))?/g,
8173									(all, name, qt, value, or_value) =>
8174									{	attributes[name.toLowerCase()] = value || or_value || '';
8175									}
8176								);
8177								var script = ns.new_elem('script', head, {attributes});
8178								if (attributes.async == null)
8179								{	loading_sync = true;
8180									if ('onload' in script)
8181									{	script.onload = function()
8182										{	setTimeout(next, 100);
8183										};
8184									}
8185									else if ('onreadystatechange' in script)
8186									{	script.onreadystatechange = function()
8187										{	if (script.readyState == 'complete')
8188											{	next();
8189											}
8190											else if (script.readyState == 'loaded')
8191											{	setTimeout(next, 3000);
8192											}
8193										};
8194									}
8195									else
8196									{	setTimeout(next, 3000);
8197									}
8198								}
8199							}
8200						}
8201						else if (comment)
8202						{	if (ns.IS_MSIE)
8203							{	var m = comment.match(/^<!--\[\s*if\b([^\]]+)\]>([\S\s]*)<!\[\s*endif\s*\]-->$/i);
8204								if (m)
8205								{	try
8206									{	var ua = navigator.userAgent;
8207										var version = parseFloat(ua.substring(ua.indexOf("MSIE ")+5)) || 0;
8208										if
8209										(	eval
8210											(	m[1].replace
8211												(	/(?:\b([lg])t(e?)\s+)?\bIE(\s+[\d\.]+)?\b/gi,
8212													(all, l_g, e, v) =>
8213													{	return !v ? '1' : '('+version+(!l_g ? '==' : (l_g=='l' || l_g=='L' ? '<' : '>')+(e ? '=' : ''))+v+')';
8214													}
8215												)
8216											)
8217										)
8218										{	return m[2];
8219										}
8220									}
8221									catch (e)
8222									{	window.console?.error(e);
8223									}
8224								}
8225							}
8226						}
8227						else if (tag != 'noscript')
8228						{	ns.new_elem(link || (open+contents+close), head);
8229						}
8230						return '';
8231					}
8232				);
8233			}
8234			if (!loading_sync)
8235			{	next();
8236			}
8237			function next()
8238			{	if (then)
8239				{	html_cut_exec_code(then+code, ondone);
8240				}
8241				else
8242				{	ondone(code);
8243				}
8244				then = code = null; // new call to next() must do nothing
8245			}
8246		}
8247
8248		return inject_code;
8249	}
8250
8251// joyquery
8252function _new_elem_tmp_3(ns)
8253		{	var XML_ELEMENT_NODE = 1;
8254			var XML_TEXT_NODE = 3;
8255			var XML_CDATA_SECTION_NODE = 4;
8256			var XML_DOCUMENT_NODE = 9;
8257			var XML_HTML_DOCUMENT_NODE = 13;
8258	
8259			var TOKENIZER = /[~|^$*!]?=|::|([+\-]?\d+n(?:\s*[+\-]?\d+|\s*[+\-]\s+[+\-]?\d+)?)|((?:[\w_\-\x80-\xFF]|\\(?:[0-9A-Fa-f]{1,6}|.))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*')|(\s+|\/\*[\S\s]*?\*\/)|./gi;
8260			var COMPLEX_NUMBER_TOKENIZER = /^\s*([+\-])\s*([+\-]?\d+)$/;
8261			var UNESCAPER = /\\(?:([0-9A-Fa-f]{1,6})|.)/g;
8262	
8263			var TOKEN_TYPE_COMPLEX_NUMBER = 0;
8264			var TOKEN_TYPE_IDENT = 1;
8265			var TOKEN_TYPE_STRING = 2;
8266			var TOKEN_TYPE_SPACE = 3;
8267			var TOKEN_TYPE_OTHER = 4;
8268	
8269			var SELF = 0;
8270			var CHILD = 1;
8271			var DESCENDANT = 2;
8272			var DESCENDANT_OR_SELF = 3;
8273			var PARENT = 4;
8274			var ANCESTOR = 5;
8275			var ANCESTOR_OR_SELF = 6;
8276			var FOLLOWING_SIBLING = 7;
8277			var FIRST_FOLLOWING_SIBLING = 8;
8278			var PRECEDING_SIBLING = 9;
8279			var FIRST_PRECEDING_SIBLING = 10;
8280	
8281			var AXIS_NAMES =
8282			{	'self': SELF,
8283				'child': CHILD,
8284				'descendant': DESCENDANT,
8285				'descendant-or-self': DESCENDANT_OR_SELF,
8286				'parent': PARENT,
8287				'ancestor': ANCESTOR,
8288				'ancestor-or-self': ANCESTOR_OR_SELF,
8289				'following-sibling': FOLLOWING_SIBLING,
8290				'first-following-sibling': FIRST_FOLLOWING_SIBLING,
8291				'preceding-sibling': PRECEDING_SIBLING,
8292				'first-preceding-sibling': FIRST_PRECEDING_SIBLING
8293			};
8294	
8295			var FUNCTIONS =
8296			{	empty()
8297				{	var node = this.node;
8298					for (var e=node.firstChild; e; e=e.nextSibling)
8299					{	if (e.nodeType!=XML_TEXT_NODE && e.nodeType!=XML_CDATA_SECTION_NODE || e.length)
8300						{	return false;
8301						}
8302					}
8303					return true;
8304				}
8305			};
8306	
8307			var CMP_IN_LOWERCASE_ATTRS =
8308			{	type: 1,
8309				method: 1
8310			};
8311	
8312			var SETTINGS =
8313			{	/*	One account's worth of selectors, not four. The tracker asks for every pending container's
8314					selector on every pass (init_containers in stat-track-lib), so the working set is however
8315					many containers the account has - and at 4 the cache filled up within the first few of
8316					them and was thrown away whole (see the wipe in compile()), so it never once answered a
8317					hit and every pass re-parsed every selector from scratch. Measured on our own panel, where
8318					an account with 87 containers scans a page none of them can match: parsing was about a
8319					seventh of each 77ms pass. An entry is a parsed path - plain objects plus one compiled
8320					`cond` function per step - so even a fat account costs kilobytes, and the wipe still keeps
8321					the ceiling hard. */
8322				compiler_cache_max: 300,
8323				emulate: false
8324			};
8325	
8326			// jQuery uses compiler cache. Why don't i?
8327			var compiler_cache = {length: 0};
8328	
8329			function compile(css, compiler_cache_max, no_context_node, use_element_child, use_has_attribute, is_xml)
8330			{	// cache hit?
8331				var css_for_cache = ((no_context_node ? 1 : 0) | (use_element_child ? 2 : 0) | (use_has_attribute ? 4 : 0) | (is_xml ? 8 : 0)) + css;
8332				if (compiler_cache[css_for_cache])
8333				{	return compiler_cache[css_for_cache];
8334				}
8335				// cache miss
8336				var token_types = [];
8337				var tokens = [];
8338				var i = 0;
8339				css.replace
8340				(	TOKENIZER,
8341					function(token, is_complex_number, is_ident, is_string, is_space)
8342					{	tokens.push(token);
8343						token_types.push(is_complex_number ? TOKEN_TYPE_COMPLEX_NUMBER : is_ident ? TOKEN_TYPE_IDENT : is_string ? TOKEN_TYPE_STRING : is_space ? TOKEN_TYPE_SPACE : TOKEN_TYPE_OTHER);
8344					}
8345				);
8346				function read_ident(can_asterisk, can_empty)
8347				{	var token = tokens[i];
8348					if (can_asterisk && token=='*' || token_types[i]==TOKEN_TYPE_IDENT)
8349					{	i++;
8350						return css_unescape(token);
8351					}
8352					else if (can_empty && (token=='#' || token=='.' || token=='[' || token==':'))
8353					{	return '*';
8354					}
8355				}
8356				function read_string()
8357				{	var token = tokens[i];
8358					if (token_types[i] == TOKEN_TYPE_STRING)
8359					{	i++;
8360						return css_unescape(token.slice(1, -1));
8361					}
8362					var value = parseFloat(token);
8363					if (!isNaN(value))
8364					{	i++;
8365						return value;
8366					}
8367				}
8368				function read_space()
8369				{	if (token_types[i] == TOKEN_TYPE_SPACE)
8370					{	return tokens[i++];
8371					}
8372				}
8373				function zpad4(text)
8374				{	return '000'.substr(0, 4-text.length) + text;
8375				}
8376				function css_unescape(text)
8377				{	if (text.indexOf('\\') == -1)
8378					{	return text;
8379					}
8380					else
8381					{	return text.replace
8382						(	UNESCAPER,
8383							function(match, hexa)
8384							{	if (!hexa)
8385								{	return match.charAt(1);
8386								}
8387								else
8388								{	return eval('"\\u'+zpad4(hexa.length<5 ? hexa : hexa.substr(hexa.length-4))+'"');
8389								}
8390							}
8391						);
8392					}
8393				}
8394				function error(message)
8395				{	throw new Error(message || 'Unsupported selector');
8396				}
8397				function parse_simple_selector(cur_path, axis)
8398				{	var token = read_ident(true, true);
8399					if (!token)
8400					{	return false;
8401					}
8402					if (tokens[i] == '::')
8403					{	i++;
8404						axis = AXIS_NAMES[token];
8405						if (axis == null)
8406						{	error('Unsupported axis: '+token);
8407						}
8408						token = read_ident(true);
8409						if (!token)
8410						{	error();
8411						}
8412					}
8413					var simple_selector =
8414					{	name: token=='*' ? '' : is_xml ? token : token.toUpperCase(),
8415						axis: axis,
8416						from: 1,
8417						limit: 0x7FFFFFFF,
8418						sub: [],
8419						cond: null
8420					};
8421					var conditions = [[], [], []];
8422					function add_condition(name, oper, value, func_args, first_complex_number_arg)
8423					{	if (name==null || value==null)
8424						{	error();
8425						}
8426						var func_arg = func_args && func_args[0];
8427						if (oper == ':from')
8428						{	simple_selector.from = func_arg>1 ? func_arg-0 : 1;
8429							return;
8430						}
8431						if (oper == ':limit')
8432						{	simple_selector.limit = func_arg>0 ? func_arg-0 : 0x7FFFFFFF;
8433							return;
8434						}
8435						if (oper.charAt(0) != ':')
8436						{	name = name.toLowerCase();
8437							var name_js = JSON.stringify(name);
8438							var get_attr = "n.getAttribute("+name_js+")";
8439							value += '';
8440							var use_value = oper!='~=' ? value : ' '+value+' ';
8441							var value_js = JSON.stringify(use_value);
8442							if (CMP_IN_LOWERCASE_ATTRS[name])
8443							{	get_attr = "(n["+name_js+"]?("+get_attr+"||'').toLowerCase():"+get_attr+")";
8444								value_js = "(n["+name_js+"]?"+JSON.stringify(use_value.toLowerCase())+":"+value_js+")";
8445							}
8446						}
8447						var func;
8448						var priority = 0;
8449						switch (oper)
8450						{	case '' :
8451								func = use_has_attribute ? "n.hasAttribute("+name_js+")" : "n.getAttribute("+name_js+")!=null";
8452							break;
8453							case '=':
8454								func = get_attr+"=="+value_js;
8455							break;
8456							case '!=':
8457								func = "("+get_attr+"||'')!="+value_js;
8458							break;
8459							case '^=':
8460								func = "(a="+get_attr+")&&a.substr(0,"+(value.length)+")=="+value_js;
8461							break;
8462							case '$=':
8463								func = "(a="+get_attr+")&&a.substr(a.length-"+(value.length)+")=="+value_js;
8464							break;
8465							case '*=':
8466								func = "(a="+get_attr+")&&a.indexOf("+value_js+")!=-1";
8467							break;
8468							case '|=':
8469								func = "(a="+get_attr+")&&(b="+value_js+")&&(a==b||a.substr(0,"+(value.length+1)+")==b+'-')";
8470							break;
8471							case '~=':
8472								get_attr = "(' '+("+(name!='class' ? "" : "n.className!=null?n.className:")+get_attr+")+' ')";
8473								var try_class_list = name!='class' ? "" : "n.classList?n.classList.contains("+JSON.stringify(value)+"):";
8474								func = "("+try_class_list+get_attr+".indexOf("+(name!='class' ? value_js : JSON.stringify(use_value))+")!=-1)";
8475							break;
8476							case ':root':
8477								func = "n==this.document.documentElement";
8478							break;
8479							case ':first-child':
8480								func = "!n.previousElementSibling";
8481								if (!use_element_child)
8482								{	func = "l(0,1)==0";
8483									priority = 1;
8484								}
8485							break;
8486							case ':last-child':
8487								func = "!n.nextElementSibling";
8488								if (!use_element_child)
8489								{	func = "l(0,1)==l()-1";
8490									priority = 1;
8491								}
8492							break;
8493							case ':only-child':
8494								func = "!n.previousElementSibling&&!n.nextElementSibling";
8495								if (!use_element_child)
8496								{	func = "l()==1";
8497									priority = 1;
8498								}
8499							break;
8500							case ':nth-child':
8501								func = !first_complex_number_arg ? "l(0,1)=="+(func_arg-1) : "(l(0,1)-("+(first_complex_number_arg.real-1)+"))%"+first_complex_number_arg.imag+"==0";
8502								priority = 1;
8503							break;
8504							case ':nth-last-child':
8505								func = !first_complex_number_arg ? "l()-l(0,1)=="+func_arg : "(l()-l(0,1)-("+first_complex_number_arg.real+"))%"+first_complex_number_arg.imag+"==0";
8506								priority = 1;
8507							break;
8508							case ':first-of-type':
8509								func = "l(1,1)==0";
8510								priority = 1;
8511							break;
8512							case ':last-of-type':
8513								func = "l(1,1)==l(1)-1";
8514								priority = 1;
8515							break;
8516							case ':only-of-type':
8517								func = "l(1)==1";
8518								priority = 1;
8519							break;
8520							case ':nth-of-type':
8521								func = !first_complex_number_arg ? "l(1,1)=="+(func_arg-1) : "(l(1,1)-("+(first_complex_number_arg.real-1)+"))%"+first_complex_number_arg.imag+"==0";
8522								priority = 1;
8523							break;
8524							case ':nth-last-of-type':
8525								func = !first_complex_number_arg ? "l(1)-l(1,1)=="+func_arg : "(l(1)-l(1,1)-("+first_complex_number_arg.real+"))%"+first_complex_number_arg.imag+"==0";
8526								priority = 1;
8527							break;
8528							case ':not':
8529								func = "!"+func_arg+".evaluate_one(n)";
8530								priority = 2;
8531							break;
8532							case ':has':
8533								func = func_arg+".evaluate_one(n)";
8534								priority = 2;
8535							break;
8536							case ':any':
8537								func = "("+func_args.join(".evaluate_one(n)||")+".evaluate_one(n))";
8538								priority = 2;
8539							break;
8540							case ':focus':
8541								func = "n==this.document.activeElement";
8542							break;
8543							case ':target':
8544								func = "n.id==this.window.location.hash.substr(1)";
8545							break;
8546							case ':disabled':
8547								func = "(n.disabled&&n.nodeName!='STYLE'||(a=n.parentNode)&&a.disabled)";
8548							break;
8549							case ':enabled':
8550								func = "(b=n.disabled)!=null&&!b&&(n.value!=null||n.label!=null)";
8551							break;
8552							case ':checked':
8553								func = "(n.checked||n.selected)";
8554							break;
8555							case ':hidden':
8556								func = "!n.offsetWidth&&!n.offsetHeight";
8557							break;
8558							case ':link':
8559								func = "n.href&&n.nodeName=='A'";
8560							break;
8561							case ':visited':
8562								func = "0";
8563							break;
8564							case ':input':
8565								func = "n.value!=null&&((a=n.nodeName)=='INPUT'||a=='SELECT'||a=='TEXTAREA'||a=='BUTTON')";
8566							break;
8567							default:
8568								var func_name = oper.substr(1).replace(/-/g, '_');
8569								func_args.splice(0, 0, 'this');
8570								func = '(this.functions.'+func_name+'||this.FUNCTIONS.'+func_name+').call('+func_args.join(',')+')';
8571								priority = 1;
8572							break;
8573						}
8574						conditions[priority].push(func);
8575					}
8576					while (true)
8577					{	token = tokens[i];
8578						if (token == '#')
8579						{	i++;
8580							add_condition('id', '=', read_ident());
8581						}
8582						else if (token == '.')
8583						{	i++;
8584							add_condition('class', '~=', read_ident());
8585						}
8586						else if (token == '[')
8587						{	i++;
8588							read_space();
8589							var name = read_ident(true);
8590							read_space();
8591							token = tokens[i];
8592							var oper = '';
8593							var value = '';
8594							if (token.charAt(token.length-1) == '=')
8595							{	i++;
8596								read_space();
8597								oper = token;
8598								value = read_string();
8599								read_space();
8600							}
8601							if (tokens[i++] != ']')
8602							{	error();
8603							}
8604							add_condition(name, oper, value);
8605						}
8606						else if (token == ':')
8607						{	i++;
8608							var name = read_ident(true);
8609							var func_args = [];
8610							var first_complex_number_arg = null;
8611							if (tokens[i] == '(')
8612							{	while (true)
8613								{	i++;
8614									read_space();
8615									var func_arg;
8616									if (token_types[i] == TOKEN_TYPE_COMPLEX_NUMBER)
8617									{	var complex_number = tokens[i].toLowerCase().split('n');
8618										var real = complex_number[1];
8619										var imag = complex_number[0];
8620										if (!real)
8621										{	real = '0';
8622										}
8623										else if (isNaN(parseInt(real)))
8624										{	var match = real.match(COMPLEX_NUMBER_TOKENIZER);
8625											real = match[1]=='-' ? -match[2] : match[2];
8626										}
8627										func_arg = '{imag:'+imag+',real:'+real+'}';
8628										if (!func_args.length)
8629										{	first_complex_number_arg = {imag:imag, real:real};
8630										}
8631										i++;
8632									}
8633									else
8634									{	func_arg = read_string();
8635										if (func_arg != null)
8636										{	func_arg = JSON.stringify(func_arg);
8637										}
8638										else if (token_types[i]==TOKEN_TYPE_IDENT && FUNCTIONS[name+'.raw_argument'])
8639										{	func_arg = JSON.stringify(tokens[i++]);
8640										}
8641										else
8642										{	func_arg = 'new this.E(this,'+simple_selector.sub.length+')';
8643											simple_selector.sub.push(parse(SELF, true));
8644										}
8645									}
8646									func_args.push(func_arg);
8647									read_space();
8648									if (tokens[i] != ',')
8649									{	break;
8650									}
8651								}
8652								if (tokens[i++] != ')')
8653								{	error();
8654								}
8655							}
8656							add_condition('', ':'+name, '', func_args, first_complex_number_arg);
8657						}
8658						else
8659						{	break;
8660						}
8661					}
8662					conditions = conditions[0].concat(conditions[1]).concat(conditions[2]);
8663					if (conditions[0])
8664					{	simple_selector.cond = new Function('var a,b,n=this.node,l=this.last;return '+(conditions.join('&&')));
8665					}
8666					cur_path.push(simple_selector);
8667					return true;
8668				}
8669				function parse(axis, is_func_args)
8670				{	var initial_axis = axis;
8671					var cur_path = [];
8672					var path = [cur_path];
8673					read_space();
8674					while (tokens[i])
8675					{	if (!parse_simple_selector(cur_path, axis))
8676						{	error();
8677						}
8678						axis = DESCENDANT;
8679						read_space();
8680						var token = tokens[i];
8681						if (token == '>')
8682						{	i++;
8683							axis = CHILD;
8684						}
8685						else if (token=='~' || token=='+')
8686						{	i++;
8687							axis = token=='~' ? FOLLOWING_SIBLING : FIRST_FOLLOWING_SIBLING;
8688						}
8689						else if (token == ',')
8690						{	if (is_func_args)
8691							{	break;
8692							}
8693							i++;
8694							axis = initial_axis;
8695							cur_path = [];
8696							path.push(cur_path);
8697						}
8698						else if (token == ')')
8699						{	break;
8700						}
8701						read_space();
8702					}
8703					return path;
8704				}
8705				var path = parse(no_context_node ? DESCENDANT_OR_SELF : DESCENDANT);
8706				if (tokens[i])
8707				{	error();
8708				}
8709				//
8710				if (compiler_cache.length >= compiler_cache_max)
8711				{	compiler_cache = {length: 0};
8712				}
8713				compiler_cache[css_for_cache] = path;
8714				compiler_cache.length++;
8715				return path;
8716			}
8717	
8718			// class Evaluator
8719			function Evaluator(ctx, h)
8720			{	this.ctx = ctx;
8721				this.h = h;
8722			}
8723			Evaluator.prototype.evaluate = function(node)
8724			{	return evaluate(this.ctx.s[this.h], node, this.ctx.functions);
8725			};
8726			Evaluator.prototype.evaluate_one = function(node)
8727			{	var path = this.ctx.s[this.h];
8728				for (var i=path.length-1; i>=0; i--)
8729				{	var iter = select_matching(path[i], 0, node, null, this.ctx.functions, this.ctx.window, this.ctx.document, 0, NaN, NaN, NaN, NaN, [], [], [], [], 0, false);
8730					if (iter)
8731					{	return iter.result;
8732					}
8733				}
8734			};
8735	
8736			function select_matching(cur_path, step, node, subnode, functions, win, doc, position_range, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, no_enter)
8737			{	var simple_selector = cur_path[step];
8738				var name = simple_selector.name;
8739				var axis = simple_selector.axis;
8740				var from = simple_selector.from;
8741				var limit = simple_selector.limit;
8742				var cond = simple_selector.cond;
8743				var sub_paths = simple_selector.sub;
8744				var is_last_step = step == cur_path.length-1;
8745				var ctx =
8746				{	node: null,
8747					window: win,
8748					document: doc,
8749					FUNCTIONS,
8750					functions,
8751					E: Evaluator,
8752					s: sub_paths,
8753					position: get_position,
8754					last: get_last
8755				};
8756				var use_element_child = node && node.firstElementChild!==undefined;
8757				if (!subnode)
8758				{	subnode = axis==SELF || axis==ANCESTOR_OR_SELF ? node : axis==PARENT || axis==ANCESTOR ? node.parentNode : axis==CHILD ? (use_element_child ? node.firstElementChild : node.firstChild) : axis==FOLLOWING_SIBLING || axis==FIRST_FOLLOWING_SIBLING ? (use_element_child ? node.nextElementSibling : node.nextSibling) : axis==PRECEDING_SIBLING || axis==FIRST_PRECEDING_SIBLING ? (use_element_child ? node.previousElementSibling : node.previousSibling) : axis==DESCENDANT ? node : null;
8759					position = axis==SELF || axis==DESCENDANT || axis==DESCENDANT_OR_SELF || axis==ANCESTOR_OR_SELF ? position : axis==CHILD ? 0 : axis==FOLLOWING_SIBLING || axis==FIRST_FOLLOWING_SIBLING ? position+1 : axis==PRECEDING_SIBLING || axis==FIRST_PRECEDING_SIBLING ? position-1 : NaN;
8760					position_ot = axis==SELF || axis==DESCENDANT || axis==DESCENDANT_OR_SELF ? position_ot : axis==CHILD ? (name?0:NaN) : NaN;
8761				}
8762				var inc_position = axis==PRECEDING_SIBLING ? -1 : +1;
8763				function get_last(is_ot, is_p)
8764				{	var value = is_ot ? (is_p ? position_ot : last_ot) : (is_p ? position : last);
8765					if (isNaN(value))
8766					{	if (!is_ot)
8767						{	if (isNaN(position))
8768							{	position = 0;
8769								for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling))
8770								{	if (n.nodeType == XML_ELEMENT_NODE)
8771									{	position++;
8772									}
8773								}
8774								if (is_p)
8775								{	return position;
8776								}
8777							}
8778							last = position + 1;
8779							for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling))
8780							{	if (n.nodeType == XML_ELEMENT_NODE)
8781								{	last++;
8782								}
8783							}
8784							value = last;
8785						}
8786						else
8787						{	var node_name = subnode.nodeName;
8788							if (node_name == name)
8789							{	if (isNaN(position_ot))
8790								{	position_ot = 0;
8791									for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling))
8792									{	if (n.nodeName == node_name)
8793										{	position_ot++;
8794										}
8795									}
8796									if (is_p)
8797									{	return position_ot;
8798									}
8799								}
8800								last_ot = position_ot + 1;
8801								for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling))
8802								{	if (n.nodeName == node_name)
8803									{	last_ot++;
8804									}
8805								}
8806								value = last_ot;
8807							}
8808							else
8809							{	var p = 0;
8810								for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling))
8811								{	if (n.nodeName == node_name)
8812									{	p++;
8813									}
8814								}
8815								if (!is_p)
8816								{	p++;
8817									for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling))
8818									{	if (n.nodeName == node_name)
8819										{	p++;
8820										}
8821									}
8822								}
8823								value = p;
8824							}
8825						}
8826					}
8827					return value;
8828				}
8829				function get_position(is_ot)
8830				{	return get_last(is_ot, true);
8831				}
8832				while (true)
8833				{	var next;
8834					if (axis==SELF || axis==PARENT)
8835					{	next = null;
8836					}
8837					else if (axis == FIRST_FOLLOWING_SIBLING)
8838					{	next = subnode && subnode.nodeType!=XML_ELEMENT_NODE ? subnode.nextSibling : null;
8839					}
8840					else if (axis == FIRST_PRECEDING_SIBLING)
8841					{	next = subnode && subnode.nodeType!=XML_ELEMENT_NODE ? subnode.previousSibling : null;
8842					}
8843					else if (axis==CHILD || axis==FOLLOWING_SIBLING)
8844					{	next = subnode && (use_element_child ? subnode.nextElementSibling : subnode.nextSibling);
8845					}
8846					else if (axis == PRECEDING_SIBLING)
8847					{	next = subnode && (use_element_child ? subnode.previousElementSibling : subnode.previousSibling);
8848					}
8849					else if (axis==ANCESTOR || axis==ANCESTOR_OR_SELF)
8850					{	next = subnode && subnode.parentNode;
8851						position = NaN;
8852						last = NaN;
8853						position_ot = NaN;
8854						last_ot = NaN;
8855					}
8856					else
8857					{	var n2;
8858						if (subnode == null) // DESCENDANT_OR_SELF
8859						{	subnode = node;
8860						}
8861						else if ((n2 = use_element_child ? subnode.firstElementChild : subnode.firstChild) && !no_enter)
8862						{	subnode = n2;
8863							positions[n_positions] = position;
8864							lasts[n_positions] = last;
8865							position_ots[n_positions] = position_ot;
8866							last_ots[n_positions] = last_ot;
8867							n_positions++;
8868							position = 0;
8869							last = NaN;
8870							position_ot = name ? 0 : NaN;
8871							last_ot = NaN;
8872						}
8873						else
8874						{	no_enter = false;
8875							while (true)
8876							{	if (subnode == node)
8877								{	return;
8878								}
8879								if ((n2 = use_element_child ? subnode.nextElementSibling : subnode.nextSibling))
8880								{	subnode = n2;
8881									break;
8882								}
8883								subnode = subnode.parentNode;
8884								n_positions--;
8885								position = positions[n_positions];
8886								last = lasts[n_positions];
8887								position_ot = position_ots[n_positions];
8888								last_ot = last_ots[n_positions];
8889							}
8890						}
8891						next = subnode;
8892					}
8893					if (!subnode)
8894					{	break;
8895					}
8896					var is_found = false;
8897					if (subnode.nodeType == XML_ELEMENT_NODE)
8898					{	if (!name || subnode.nodeName==name)
8899						{	ctx.node = subnode;
8900							if (!cond || cond.call(ctx))
8901							{	if (!is_last_step)
8902								{	var next_axis = cur_path[step + 1].axis;
8903									if (next_axis==DESCENDANT || next_axis==DESCENDANT_OR_SELF)
8904									{	no_enter = true; // no_enter is only used when axis==DESCENDANT || axis==DESCENDANT_OR_SELF
8905									}
8906								}
8907								position_range++;
8908								is_found = from <= position_range;
8909								var maybe_found_node = subnode;
8910							}
8911							position_ot += inc_position;
8912						}
8913						position += inc_position;
8914					}
8915					subnode = next;
8916					if (is_found)
8917					{	if (!is_last_step)
8918						{	var next_iter_found = select_matching(cur_path, step+1, maybe_found_node, null, functions, win, doc, 0, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, false);
8919						}
8920						if (is_last_step || next_iter_found)
8921						{	var next_iter = !subnode || from-1+limit <= position_range ? null : function()
8922							{	return select_matching(cur_path, step, node, subnode, functions, win, doc, position_range, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, no_enter);
8923							};
8924							var iter =
8925							{	result: is_last_step ? maybe_found_node : next_iter_found.result,
8926								next: is_last_step ? next_iter : function()
8927								{	if (next_iter_found = next_iter_found.next && next_iter_found.next())
8928									{	iter.result = next_iter_found.result;
8929										return iter;
8930									}
8931									return next_iter && next_iter();
8932								}
8933							};
8934							return iter;
8935						}
8936					}
8937				}
8938			}
8939	
8940			/**	The fast path: hand the selector to the browser, and let a throw (joyquery-only syntax:
8941				axes, `:from()`/`:limit()`, custom functions) fall back to the emulator. Runs in the debug
8942				flavor too - the emulator is the fallback for what the browser cannot parse, not a debug
8943				mode; see the comment in joyquery_ext.php for the history of that distinction.
8944			 **/
8945			function try_builtin(node, query, one_elem)
8946			{	try
8947				{	var result = one_elem ? node.querySelector(query) : node.querySelectorAll(query);
8948					if (one_elem)
8949					{	result = result ? [result] : [];
8950					}
8951	//console.log('Using builtin: '+query);
8952					return result;
8953				}
8954				catch (e)
8955				{
8956				}
8957			}
8958	
8959			function evaluate(path_obj_or_str, node, functions, override_settings)
8960			{	if (!functions)
8961				{	functions = {};
8962				}
8963				var path;
8964				var no_context_node = !node;
8965				if (no_context_node)
8966				{	node = document.documentElement || document.body;
8967				}
8968				var doc = node.nodeType==XML_DOCUMENT_NODE || node.nodeType==XML_HTML_DOCUMENT_NODE ? node : node.ownerDocument || document;
8969				var win = doc.defaultView || doc.parentWindow || window;
8970				var iter, builtin_result=null;
8971				var i = -1;
8972				var it = function(one_elem_enuogh)
8973				{	if (i == -1)
8974					{	var is_string = typeof(path_obj_or_str) != 'object';
8975						if (is_string && !(override_settings && override_settings.emulate!=null ? override_settings.emulate : SETTINGS.emulate))
8976						{	builtin_result = try_builtin(node, path_obj_or_str, one_elem_enuogh);
8977						}
8978						if (!builtin_result)
8979						{	var is_xml = !doc.body || (doc.contentType || '').indexOf('/xml')!=-1;
8980							path = is_string ? compile(path_obj_or_str, override_settings && override_settings.compiler_cache_max!=null ? override_settings.compiler_cache_max : SETTINGS.compiler_cache_max, no_context_node, node.firstElementChild!==undefined, node.hasAttribute, is_xml) : path_obj_or_str;
8981						}
8982						path_obj_or_str = null;
8983					}
8984					if (builtin_result)
8985					{	// using built-in
8986						return builtin_result[++i] || (node = functions = builtin_result = null);
8987					}
8988					else
8989					{	// emulating
8990						while (!iter)
8991						{	i++;
8992							if (!path[i])
8993							{	return (path = node = functions = null);
8994							}
8995							iter = select_matching(path[i], 0, node, null, functions, win, doc, 0, NaN, NaN, NaN, NaN, [], [], [], [], 0, false);
8996						}
8997						var elem = iter.result;
8998						iter = iter.next && iter.next();
8999						return elem;
9000					}
9001				};
9002				it.get = function(at, count)
9003				{	var want_scalar = at!=null && count==null;
9004					count = at==null ? 0x7FFFFFFF : count>0 ? count-0 : 1;
9005					at = at>0 ? at-0 : 0;
9006					var one_elem_enuogh = at==0 && want_scalar;
9007					var get_result = [];
9008					var j = -at;
9009					var elem;
9010					while (j<count && (elem=it(one_elem_enuogh)))
9011					{	if (builtin_result || get_result.indexOf(elem)==-1)
9012						{	get_result[get_result.length] = elem;
9013							j++;
9014						}
9015					}
9016					if (at > 0)
9017					{	get_result = get_result.slice(at);
9018					}
9019					return want_scalar ? get_result[0] : get_result;
9020				};
9021				return it;
9022			}
9023	
9024			evaluate.FUNCTIONS = FUNCTIONS;
9025			evaluate.SETTINGS = SETTINGS;
9026	
9027			return evaluate;
9028		}
9029
9030// joyquery_ext
9031function _new_elem_tmp_4(ns, joyquery)
9032	{	joyquery.FUNCTIONS.text_child_contains = function(str)
9033		{	for (var n=this.node.firstChild; n; n=n.nextSibling)
9034			{	if (n.nodeType==3 && n.data.indexOf(str) != -1) return true;
9035			}
9036		};
9037		/*	The debug flavor used to set `joyquery.SETTINGS.emulate = true` here, to suppress the
9038			(caught) exception that try_builtin() takes on joyquery-only selectors when DevTools is set
9039			to pause on caught exceptions. That made every selector run through the JS emulator - and
9040			since the dev host-nginx serves the -debug bundle for counter.personyze.com to make the
9041			tracker readable, every developer machine paid the emulator's price on every tracked page
9042			(measured: an idle panel tab at ~10% of a core, all of it selector re-walks; the release
9043			flavor answers the same selectors natively 25-40x faster). Debugging is also better served
9044			by the builtin path: it is what real visitors run, and the emulator differs from it in
9045			edge cases (uppercased names never match SVG, `:has()` is unsupported). Joyquery-only
9046			selectors are rare enough (6 containers fleet-wide) that the paused-exception noise is not
9047			worth that. Set `joyquery.SETTINGS.emulate = true` from the console if it bothers you. */
9048	}
9049
9050// TODO: templates dependancies. load files on switch-elem
9051// TODO: emulated CSS transitions - support Bezier timing funcs
9052// TODO: remove by_id_in
9053// TODO: ontplloading and ontplunloading: compatibility
9054// TODO: pair_funcs: compatibility
9055// TODO: remove report_tplload(n)
9056
9057// External compile-time settings:
9058// const NEW_ELEM_NDEBUG = ...;
9059// const NEW_ELEM_NS_NAME = ...;
9060
9061// Consts
9062const XML_ELEMENT_NODE = 1;
9063const XML_TEXT_NODE = 3;
9064const XML_CDATA_SECTION_NODE = 4;
9065const XML_PI_NODE = 7;
9066const XML_COMMENT_NODE = 8;
9067const XML_DOCUMENT_NODE = 9;
9068const XML_DOCUMENT_FRAG_NODE = 11;
9069const IS_MSIE = 'ActiveXObject' in window;
9070const IS_SAFARI = navigator.vendor?.indexOf('Apple') >= 0;
9071const IS_CHROME = window.chrome != null;
9072const IS_OPERA = window.opera != null;
9073const IS_FIREFOX = window.InstallTrigger != null;
9074const CSS_PREFIX = IS_MSIE ? 'Ms' : IS_SAFARI || IS_CHROME ? 'Webkit' : IS_OPERA ? 'O' : IS_FIREFOX ? 'Moz' : null;
9075const CSS_PREFIX_LC = CSS_PREFIX?.toLowerCase();
9076const CSS_DEFAULT_REPLACE = {Start: 'Left', End: 'Right'};
9077const RE_COMMA = /,/g;
9078const RE_SPACES = /\s+/g;
9079const RE_HAS_NONSPACE = /\S/;
9080const RE_NUMBER = /[+\-]?\b(?:\d+(?:\.\d+)?|\.\d+)/g;
9081const RE_THIS = /\bthis\b/;
9082const RE_NS = /\bns\b/;
9083const RE_FIRST_TAG = /^\s*<(\S+)/;
9084const RE_CSS_RULE = /([^;:\s]+)\s*:("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[^;]*)/g;
9085const RE_FROM_CAMEL_1 = /(\b[A-Z])/g;
9086const RE_FROM_CAMEL_2 = /([a-z])([A-Z])/g;
9087const RE_TO_CAMEL = /-([a-z])/gi;
9088const SPECIAL_EVENTS =
9089{	ontplready: 1,
9090	ontplunloading: 1,
9091	ontplloading: 1,
9092	ontplload: 1,
9093	ontplunload: 1,
9094	ontplreload: 1
9095};
9096const ELEMS_HAVE_OPTIONS =
9097{	SELECT: 1,
9098	OPTGROUP: 1,
9099	DATALIST: 1,
9100	MENU: 1,
9101	OL: 1,
9102	UL: 1,
9103	TABLE: 1,
9104	THEAD: 1,
9105	TBODY: 1,
9106	TFOOT: 1
9107};
9108const EVENTS_CTORS =
9109{	click: 'MouseEvent',
9110	dblclick: 'MouseEvent',
9111	mouseup: 'MouseEvent',
9112	mousedown: 'MouseEvent',
9113	mouseover: 'MouseEvent',
9114	mouseout: 'MouseEvent',
9115	mouseenter: 'MouseEvent',
9116	mouseleave: 'MouseEvent',
9117	keydown: 'KeyboardEvent',
9118	keyup: 'KeyboardEvent',
9119	keypress: 'KeyboardEvent',
9120	focus: 'FocusEvent',
9121	blur: 'FocusEvent',
9122	focusin: 'FocusEvent',
9123	focusout: 'FocusEvent'
9124};
9125
9126const default_display_by_tags_cache = {};
9127var scrollbar_width;
9128
9129if (!String.prototype.replaceAll)
9130{	try
9131	{	String.prototype.replaceAll = function(from, to)
9132		{	if (!(from instanceof RegExp))
9133			{	from = new RegExp((from+'').replace(/[\\^$|.*+?()[\]{}]/g, '\\$&'), 'g');
9134			}
9135			return this.replace(from, to);
9136		};
9137	}
9138	catch (e)
9139	{	error(e);
9140	}
9141}
9142
9143function do_nothing() {}
9144
9145function is_undefined(value)
9146{	return value === undefined;
9147}
9148
9149function assert(value)
9150{	if (!NEW_ELEM_NDEBUG)
9151	{	if (!value)
9152		{	error('Assertion failed', new Error().stack);
9153		}
9154	}
9155}
9156
9157/**	Get property of props, or get one of "data-name" or "name" attributes of elem.
9158	The first attribute found in this sequence will be returned.
9159	If "take" is true, will remove found attribute (but not prop).
9160	If requested property is in props, they get priority, but attribute will be still removed.
9161	Attribute that starts with "data-json-" will be JSON-decoded.
9162	**/
9163function get_data_attr(ns, elem, arg_name, default_value, take, props, parent_inher, for_dyn_options, options_props, options_no_keep_elems, options_by_id)
9164{	var value = props?.[arg_name];
9165	if (value !== undefined)
9166	{	if (arg_name=='options' && value && for_dyn_options?.options!==value) // for_dyn_options.options can contain already converted options from original tpl instance before _reapply_template()
9167		{	value = conv_options(ns, value);
9168		}
9169		if (!take) return value;
9170	}
9171	if (elem?.nodeType == XML_ELEMENT_NODE)
9172	{	let a_name, a_value=null, is_json=false;
9173		(	(a_value = elem.getAttribute((a_name = 'data-json-'+arg_name))) != null && (is_json = true) ||
9174			(a_value = elem.getAttribute((a_name = 'data-'+arg_name))) != null ||
9175			(a_value = elem.getAttribute((a_name = arg_name))) != null
9176		);
9177		if (value === undefined)
9178		{	if (a_value != null)
9179			{	if (arg_name.slice(0, 2)=='on' && typeof(elem[arg_name])=='function')
9180				{	value = elem[arg_name];
9181				}
9182				else if (!is_json)
9183				{	value = a_value;
9184				}
9185				else if (a_value !== '')
9186				{	try
9187					{	value = JSON.parse(a_value);
9188					}
9189					catch (e)
9190					{	// Values built through :json:html substitution can arrive double-encoded
9191						// (&quot; survives the browser's own attribute decode). Try one extra
9192						// entity-decode pass before giving up.
9193						try
9194						{	value = JSON.parse(a_value.replace(/&(?:quot|#0*34|#x0*22);/gi, '"').replace(/&(?:apos|#0*39|#x0*27);/gi, "'").replace(/&lt;/gi, '<').replace(/&gt;/gi, '>').replace(/&amp;/gi, '&'));
9195						}
9196						catch (e2)
9197						{	// Not NDEBUG-gated: a malformed data-json-* attribute is an authoring
9198							// error that otherwise fails silently (the component renders empty).
9199							window.console && console.error('Personyze: invalid JSON in "'+a_name+'" attribute', elem, e);
9200						}
9201					}
9202				}
9203			}
9204		}
9205		if (take && a_value!=null)
9206		{	elem.removeAttribute(a_name);
9207		}
9208	}
9209	if (value === undefined)
9210	{	value = parent_inher?.get_arg(arg_name);
9211		if (value===undefined && elem)
9212		{	if (arg_name == 'options')
9213			{	value = get_options(ns, elem, for_dyn_options, options_props, options_no_keep_elems, options_by_id);
9214			}
9215			else
9216			{	value = elem[arg_name];
9217			}
9218		}
9219	}
9220	return value!==undefined ? value : default_value;
9221}
9222
9223function get_options(ns, elem, for_dyn_options, options_props, options_no_keep_elems, options_by_id)
9224{	if (elem?.nodeName in ELEMS_HAVE_OPTIONS)
9225	{	let options = [];
9226		if (for_dyn_options)
9227		{	for_dyn_options.options = options;
9228			var dyn_options = (for_dyn_options.dyn_options = {length: 0});
9229			var dyn_options_info = (for_dyn_options.dyn_options_info = []);
9230		}
9231		get_elem_options(ns, options, elem, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id);
9232		return options;
9233	}
9234}
9235
9236class RepeatOption
9237{	constructor(elem, options, dyn_options, i)
9238	{	this.elem = elem;
9239		this.options = options;
9240		this.dyn_options = dyn_options;
9241		this.i = i|0;
9242	}
9243
9244	get_option_index()
9245	{	var {dyn_options} = this;
9246		var option_i = 0;
9247		for (let i=0, i_end=this.i; i<i_end; i++)
9248		{	if (dyn_options[i]?.index_of_child)
9249			{	option_i += dyn_options[i].length - 1;
9250			}
9251			option_i++;
9252		}
9253		return option_i;
9254	}
9255}
9256
9257/**	If callback returns undefined, will continue.
9258	If callback returns false, will continue, but not traverse inside current element.
9259	If callback returns a DOMNode (but not the current element itself), will replace this element with the returned node, and continue.
9260	If callback returns elem itself, or a value other than undefined, false and DOMNode, will stop traversing, and return it.
9261	**/
9262function traverse(elem, e_parent_node, callback)
9263{	var e_next_sibling = elem.nextSibling;
9264	var repl_elem = callback(elem);
9265	if (repl_elem !== undefined)
9266	{	if (!repl_elem?.cloneNode || repl_elem==elem)
9267		{	if (repl_elem === false)
9268			{	return; // don't traverse inside
9269			}
9270			return repl_elem;
9271		}
9272		if (e_parent_node)
9273		{	e_parent_node.insertBefore(repl_elem, e_next_sibling);
9274			if (elem.parentNode == e_parent_node)
9275			{	e_parent_node.removeChild(elem);
9276			}
9277		}
9278		return;
9279	}
9280	for (let subelem=elem.firstElementChild; subelem;) // firstElementChild support is present since MSIE9
9281	{	let next = subelem.nextElementSibling;
9282		let result = traverse(subelem, elem, callback);
9283		if (result !== undefined)
9284		{	return result;
9285		}
9286		subelem = next;
9287	}
9288}
9289
9290function get_elem_options(ns, options, elem, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id)
9291{	var node_name = elem.nodeName;
9292	if (node_name == 'TABLE')
9293	{	for (let e=elem.firstElementChild; e; e=e.nextElementSibling)
9294		{	let n_name = e.nodeName;
9295			if (n_name=='TBODY' || n_name=='THEAD' || n_name=='TFOOT')
9296			{	get_elem_options(ns, options, e, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id);
9297			}
9298		}
9299	}
9300	else
9301	{	let i = 0;
9302		for (let e=elem.firstElementChild; e; e=e.nextElementSibling)
9303		{	let for_dyn_options = dyn_options ? {} : null;
9304			let option = get_elem_option(ns, e, for_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id);
9305			if (dyn_options && e.classList.contains('$repeat-elem'))
9306			{	dyn_options_info[dyn_options_info.length] = new RepeatOption(e, options, dyn_options, i);
9307				dyn_options[i] = option;
9308				dyn_options.length = i + 1;
9309			}
9310			else
9311			{	options[options.length] = option;
9312			}
9313			if (dyn_options && for_dyn_options.options)
9314			{	if (!dyn_options[i])
9315				{	dyn_options[i] = {};
9316				}
9317				add_props_shallow(dyn_options[i], for_dyn_options);
9318				dyn_options.length = i + 1;
9319			}
9320			i++;
9321		}
9322	}
9323}
9324function get_elem_option(ns, elem, for_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id)
9325{	var option = get_attributes(ns, {}, elem, false, false, true);
9326	if (options_props)
9327	{	if (option.id)
9328		{	// id attribute was not removed yet
9329			var option_props = options_props[option.id];
9330		}
9331		else if (options_by_id)
9332		{	// look in options_by_id
9333			for (let p of Object.keys(options_props))
9334			{	if (options_by_id[p] == elem)
9335				{	option_props = options_props[p];
9336					break;
9337				}
9338			}
9339		}
9340	}
9341	if (option_props)
9342	{	add_props_shallow(option, option_props);
9343	}
9344	var node_name = elem.nodeName;
9345	if (node_name == 'OPTION')
9346	{	option.text = elem.text;
9347		option.value = elem.value;
9348		option.selected = elem.selected;
9349	}
9350	else if (node_name == 'OPTGROUP')
9351	{	option.text = option.label;
9352		delete option.label;
9353		option.options = [];
9354		let sub_dyn_options = {};
9355		get_elem_options(ns, option.options, elem, sub_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id);
9356		if ('length' in sub_dyn_options)
9357		{	for_dyn_options.options = sub_dyn_options;
9358		}
9359	}
9360	else
9361	{	let is_tr = node_name == 'TR';
9362		let text_content = is_tr ? null : '';
9363		let child_nodes = [];
9364		let n_options_variants = 0;
9365		for (let e2=elem.firstChild, next; e2; e2=next)
9366		{	next = e2.nextSibling;
9367			let node_type = e2.nodeType;
9368			if (node_type == XML_ELEMENT_NODE)
9369			{	traverse
9370				(	e2,
9371					elem,
9372					subelem =>
9373					{	if (get_data_attr(ns, subelem, 'OPTIONS')) // capital letters, so will not be recognized as magic 'options'
9374						{	let sub_options = [];
9375							let sub_dyn_options = {};
9376							get_elem_options(ns, sub_options, subelem, sub_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id);
9377							option[n_options_variants==0 ? 'options' : 'options_'+n_options_variants] = sub_options;
9378							if (!options_no_keep_elems)
9379							{	option[n_options_variants==0 ? 'options_elem' : 'options_elem_'+n_options_variants] = subelem;
9380							}
9381							else
9382							{	subelem.parentNode.removeChild(subelem);
9383							}
9384							if ('length' in sub_dyn_options)
9385							{	for_dyn_options[n_options_variants==0 ? 'options' : 'options_'+n_options_variants] = sub_dyn_options;
9386							}
9387							n_options_variants++;
9388							return false; // no traverse inside
9389						}
9390						else
9391						{	let class_list = subelem.classList;
9392							for (let i=0, i_end=class_list.length; i<i_end; i++)
9393							{	let cl = class_list[i];
9394								if (cl.charAt(0) == '$')
9395								{	cl = cl.slice(1);
9396									if (typeof(ns.templates[cl]) == 'function')
9397									{	let tpl_details = ns.templates[cl]();
9398										if ('options' in tpl_details.arg_names_assoc)
9399										{	return false; // no traverse inside
9400										}
9401									}
9402								}
9403							}
9404						}
9405					}
9406				);
9407				if (e2.parentNode) // if options_no_keep_elems i can unlink nodes
9408				{	text_content = null;
9409					child_nodes[child_nodes.length] = e2;
9410				}
9411			}
9412			else if ((node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE) && !is_tr)
9413			{	if (text_content != null)
9414				{	text_content += e2.data;
9415				}
9416				if (child_nodes.length>0 || e2.data?.trim())
9417				{	let prev_to_append = options_no_keep_elems && child_nodes[child_nodes.length - 1];
9418					if (prev_to_append?.nodeType == node_type)
9419					{	prev_to_append.data += e2.data;
9420					}
9421					else
9422					{	child_nodes[child_nodes.length] = e2;
9423					}
9424				}
9425			}
9426		}
9427		if (text_content != null)
9428		{	option.text = text_content.trim() || option.text || '';
9429		}
9430		else
9431		{	if (options_no_keep_elems)
9432			{	let e2 = child_nodes[child_nodes.length - 1];
9433				let node_type = e2.nodeType;
9434				if (node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE)
9435				{	e2.data = str_trim_right(e2.data);
9436					if (e2.length == 0)
9437					{	child_nodes.length--;
9438					}
9439				}
9440			}
9441			option.childNodes = child_nodes;
9442		}
9443	}
9444	return option;
9445}
9446function conv_options(ns, options)
9447{	var conved = get_options(ns, options);
9448	if (!conved)
9449	{	let parent_node = options[0]?.parentNode;
9450		if (parent_node)
9451		{	conved = get_options(ns, parent_node);
9452		}
9453	}
9454	if (!conved)
9455	{	for (let i=0, i_end=options.length; i<i_end; i++)
9456		{	let option = options[i];
9457			let no_conv = false;
9458			let is_obj=true, text, value, child_nodes=null, suboptions=null;
9459			if (typeof(option) != 'object')
9460			{	is_obj = false;
9461				text = option+'';
9462				value = text;
9463			}
9464			else
9465			{	text = option.text!=null ? option.text : option[0];
9466				value = option.value!=null ? option.value : option[1];
9467				child_nodes = option.childNodes;
9468				no_conv = (text==null || text==option.text) && (value==null || value==option.value);
9469				if (text == null)
9470				{	if (!child_nodes && value!=null)
9471					{	text = value+'';
9472						no_conv = false;
9473					}
9474				}
9475				else if (child_nodes != null)
9476				{	child_nodes = null;
9477					no_conv = false;
9478				}
9479				if (option.options)
9480				{	suboptions = conv_options(ns, option.options)
9481					if (option.options !== suboptions)
9482					{	no_conv = false;
9483					}
9484				}
9485			}
9486			if (no_conv)
9487			{	if (conved)
9488				{	conved[i] = option;
9489				}
9490			}
9491			else
9492			{	let c = {};
9493				if (text != null) c.text = text;
9494				if (value != null) c.value = value;
9495				if (is_obj)
9496				{	if (child_nodes != null) c.childNodes = child_nodes;
9497					if (suboptions != null) c.options = suboptions;
9498					for (let j in option)
9499					{	if (!(j in c)) c[j] = option[j];
9500					}
9501				}
9502				if (conved == null)
9503				{	conved = [];
9504					for (let j=0; j<i; j++)
9505					{	conved[j] = options[j];
9506					}
9507				}
9508				conved[i] = c;
9509			}
9510		}
9511	}
9512	return conved || options;
9513}
9514function insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem)
9515{	return e_parent_node.insertBefore(repl_elem, e_previous_sibling ? e_previous_sibling.nextSibling : e_parent_node.firstChild);
9516}
9517function has_playing_parent(elem)
9518{	while (elem)
9519	{	let ptr = elem._ne__playing;
9520		if (ptr) return ptr;
9521		elem = elem.parentNode;
9522	}
9523}
9524function replace_elem(ns, win_doc, onready_queue, elem, e_parent_node, e_previous_sibling, repl_elem, oninited, transit, ontransitionend)
9525{	function when_complete()
9526	{	if (!done) // when_complete is called through ontransitionend, so can be called twice if user makes several transitions
9527		{	done = true;
9528			if (e_parent_node)
9529			{	if (e_parent_node._ne__playing == ptr)
9530				{	delete e_parent_node._ne__playing;
9531				}
9532				else
9533				{	var playing_ptr = has_playing_parent(e_parent_node);
9534				}
9535				if (elem!=repl_elem && elem?.parentNode==e_parent_node && (!playing_ptr || playing_ptr.elem!=elem))
9536				{	e_parent_node.removeChild(elem);
9537				}
9538				a_elem?.parentNode?.removeChild(a_elem);
9539				if (repl_elem)
9540				{	repl_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem);
9541				}
9542			}
9543			if (ontransitionend) ontransitionend();
9544		}
9545	}
9546	var a_elem = null;
9547	var done = false;
9548	var ptr = {elem, end: when_complete, transit};
9549	var want_template = e_parent_node && transit?.template;
9550	if (want_template && typeof(ns.templates[want_template])!='function')
9551	{	log('No such template in current namespace (requested as animator) - '+want_template);
9552		want_template = null;
9553	}
9554	var playing_ptr;
9555	if (!want_template || !repl_elem && !elem || (playing_ptr = has_playing_parent(e_parent_node)))
9556	{	if (playing_ptr)
9557		{	if (playing_ptr.transit.stop)
9558			{	playing_ptr.transit.stop();
9559			}
9560			playing_ptr.end();
9561		}
9562		if (e_parent_node && repl_elem && elem && repl_elem!=elem)
9563		{	repl_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem);
9564		}
9565		pcall(oninited, repl_elem);
9566		when_complete();
9567	}
9568	else
9569	{	e_parent_node._ne__playing = ptr;
9570		ontransitionend = pair_funcs(transit.ontransitionend, ontransitionend);
9571		transit.ontransitionend = when_complete;
9572		a_elem = win_doc.doc.createElement('div');
9573		a_elem.id = 'elem';
9574		a_elem.className = '$'+transit.template;
9575		a_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, a_elem);
9576		a_elem = new_elem_3
9577		(	ns,
9578			a_elem,
9579			null,
9580			{	elem:
9581				{	current: elem,
9582					next: repl_elem,
9583					transit
9584				}
9585			},
9586			{},
9587			win_doc,
9588			onready_queue
9589		);
9590		pcall(oninited, repl_elem);
9591	}
9592	return repl_elem;
9593}
9594function elem_get_ontpl_attrs(elem, ns, by_id, remove_ids, ontpl_attrs)
9595{	var ontpl_attrs_special = null;
9596	for (let i=elem.attributes.length-1; i>=0; i--)
9597	{	let attr = elem.attributes.item(i);
9598		let name = attr.name;
9599		if
9600		(	name.slice(0, 2)=='on' ||
9601			name.slice(0, 10)=='data-ontpl' && (name=name.slice(5)) ||
9602			name.slice(0, 15)=='data-json-ontpl' && (name=name.slice(10))
9603		)
9604		{	if (name.slice(0, 5) == 'ontpl')
9605			{	let value = get_data_attr(ns, elem, name, null, remove_ids);
9606				if (value)
9607				{	let func = str_to_func(value, ns, by_id);
9608					if (name in SPECIAL_EVENTS)
9609					{	if (!ontpl_attrs_special) ontpl_attrs_special = {};
9610						ontpl_attrs_special[name] = func;
9611					}
9612					else
9613					{	ontpl_attrs[name] = func;
9614					}
9615				}
9616			}
9617			else if (name == attr.name)
9618			{	try
9619				{	if (typeof(elem[name]) == 'function') // in Chrome 60.0.3112.78: typeof(elem[name]) throws error if event elem[name] is incorrect code
9620					{	let code = attr.value+'';
9621						if (code.indexOf('by_id')!=-1 || code.indexOf('ns')!=-1 && RE_NS.test(code))
9622						{	elem[name] = str_to_func(code, ns, by_id);
9623						}
9624					}
9625				}
9626				catch (e)
9627				{	error(e);
9628				}
9629			}
9630		}
9631	}
9632	return ontpl_attrs_special;
9633}
9634function hook_ontplloading(parent_ontplloading, get_elem, props, for_parent_props_has_ontplloading, inher)
9635{	var ontplloading, ontplunloading, ontplload, ontplunload, ontplreload;
9636	function set_props(props)
9637	{	ontplloading = props.ontplloading;
9638		ontplunloading = props.ontplunloading;
9639		ontplload = props.ontplload;
9640		ontplunload = props.ontplunload;
9641		ontplreload = props.ontplreload;
9642	}
9643	if (props)
9644	{	set_props(props);
9645	}
9646	if (!ontplload && !ontplunload && !ontplreload && !ontplloading && !ontplunloading && !inher)
9647	{	return parent_ontplloading;
9648	}
9649	var n_tpls_to_load = -1;
9650	var n_tpls_to_load_parent = 0;
9651	var parent_props_has_ontplloading = for_parent_props_has_ontplloading.value;
9652	if (ontplloading || ontplunloading)
9653	{	for_parent_props_has_ontplloading.value = true;
9654	}
9655	return function(is_unload, event_obj, update_props, onready_queue)
9656	{	if (update_props)
9657		{	// Call with (null, null, props) to update props. Then must call with (false, null) to report initial load.
9658			// Call with (null, event_obj, props, onready_queue) to update props and report unload. Then must call with (false, event_obj) to report initial load.
9659			set_props(update_props);
9660			n_tpls_to_load--;
9661			if (event_obj)
9662			{	n_tpls_to_load_parent--;
9663				if (parent_ontplloading && (parent_props_has_ontplloading || n_tpls_to_load_parent==-1))
9664				{	onready_queue.add_onready(parent_ontplloading, this, [true, event_obj]); // load
9665				}
9666			}
9667		}
9668		else
9669		{	if (event_obj)
9670			{	if (is_unload)
9671				{	if (ontplunloading)
9672					{	pcall(ontplunloading, get_elem(), [event_obj]);
9673					}
9674				}
9675				else
9676				{	if (ontplloading)
9677					{	pcall(ontplloading, get_elem(), [event_obj]);
9678					}
9679				}
9680			}
9681			if (is_unload)
9682			{	if (n_tpls_to_load-- == 0)
9683				{	if (ontplunload)
9684					{	pcall(ontplunload, get_elem());
9685					}
9686				}
9687			}
9688			else
9689			{	if (++n_tpls_to_load == 0)
9690				{	var func = ontplload;
9691					ontplload = ontplreload; // override before calling because call may cause unload+load
9692					pcall(func, get_elem());
9693				}
9694			}
9695			if (event_obj) // called with event_obj==null to report that main tpl is loaded (initial n_tpls_to_load is -1)
9696			{	if (inher)
9697				{	inher.subst_event_target(event_obj, []);
9698				}
9699				n_tpls_to_load_parent += is_unload ? -1 : +1;
9700				if (parent_ontplloading && (parent_props_has_ontplloading || is_unload && n_tpls_to_load_parent==-1 || !is_unload && n_tpls_to_load_parent==0))
9701				{	parent_ontplloading(is_unload, event_obj);
9702				}
9703			}
9704		}
9705	};
9706}
9707function hook_ontplevent(ns, parent_ontplevent, get_elem, props, ontpl_attrs, inher, debug_tpl_name, debug_elem_id)
9708{	function set_props(props)
9709	{	if (props)
9710		{	for (let p in props)
9711			{	if (p.slice(0, 5)=='ontpl' && !(p in SPECIAL_EVENTS))
9712				{	let func = props[p];
9713					if (!NEW_ELEM_NDEBUG && ns.EventsDebugger && func)
9714					{	let orig_func = func;
9715						func = function()
9716						{	var result = ns.EventsDebugger.log_before(debug_tpl_name || '', debug_elem_id || '', p, get_elem());
9717							try
9718							{	return orig_func.apply(this, arguments);
9719							}
9720							finally
9721							{	ns.EventsDebugger.log_after(result);
9722							}
9723						};
9724					}
9725					var func_name = p.slice(5);
9726					event_funcs[func_name] = pair_funcs
9727					(	event_funcs[func_name],
9728						func,
9729						is_undefined
9730					);
9731					has_hooks = true;
9732				}
9733			}
9734		}
9735	}
9736	var event_funcs = {}
9737	var has_hooks = false;
9738	set_props(ontpl_attrs);
9739	set_props(props);
9740	return !has_hooks && !inher ? parent_ontplevent : function(args, update_props_and_inher)
9741	{	if (update_props_and_inher) // call with (null, props) to update props
9742		{	event_funcs = {};
9743			set_props(update_props_and_inher.props);
9744			inher = update_props_and_inher.inher;
9745		}
9746		else
9747		{	let event_obj = args[0];
9748			let {type} = event_obj;
9749			if (type in event_funcs)
9750			{	let stop_propagation_value = pcall(event_funcs[type], get_elem(), args);
9751				if (stop_propagation_value !== undefined)
9752				{	return stop_propagation_value;
9753				}
9754			}
9755			if (inher)
9756			{	let stop_propagation_value = inher.subst_event_target(event_obj, args);
9757				if (stop_propagation_value !== undefined)
9758				{	return stop_propagation_value;
9759				}
9760			}
9761			if (parent_ontplevent)
9762			{	return parent_ontplevent(args);
9763			}
9764			else if (type == 'error')
9765			{	error(args[1]);
9766			}
9767		}
9768	};
9769}
9770function ontplevent_filter(parent_ontplevent, get_elem, props, reports, inher)
9771{	if (!parent_ontplevent || !reports)
9772	{	return parent_ontplevent;
9773	}
9774	var event_funcs = {};
9775	if (props)
9776	{	for (let p in props)
9777		{	if (p.slice(0, 5)=='ontpl' && !(p in SPECIAL_EVENTS))
9778			{	var func_name = p.slice(5);
9779				event_funcs[func_name] = props[p];
9780			}
9781		}
9782	}
9783	return function(args)
9784	{	let event_obj = args[0];
9785		if (reports.indexOf(event_obj.type) != -1)
9786		{	if (!inher || inher.to_wrap_elem!=event_obj.target)
9787			{	let {type} = event_obj;
9788				if (type in event_funcs)
9789				{	return pcall(event_funcs[type], get_elem(), args);
9790				}
9791				return;
9792			}
9793		}
9794		return parent_ontplevent(args);
9795	};
9796}
9797
9798class OnReadyQ
9799{	constructor()
9800	{	this.onready = [];
9801		this.onload = [];
9802		this.level = 1;
9803		this.for_next = null;
9804	}
9805
9806	add_onready(func, owner, args)
9807	{	if (this.level == 0)
9808		{	func.apply(owner, args || []);
9809		}
9810		else
9811		{	let {onready} = this;
9812			onready[onready.length] = {func, owner, args};
9813		}
9814	}
9815
9816	add_onload(func)
9817	{	if (this.level == 0)
9818		{	func();
9819		}
9820		else
9821		{	let {onload} = this;
9822			onload[onload.length] = func;
9823		}
9824	}
9825
9826	enter()
9827	{	this.level++;
9828	}
9829
9830	leave()
9831	{	if (--this.level == 0)
9832		{	let {onready, onload} = this;
9833			this.onready = [];
9834			this.onload = [];
9835			for (let i=0, i_end=onready.length; i<i_end; i++)
9836			{	let obj = onready[i];
9837				obj.func.apply(obj.owner, obj.args || []);
9838			}
9839			for (let i=0, i_end=onload.length; i<i_end; i++)
9840			{	onload[i]();
9841			}
9842		}
9843	}
9844}
9845
9846function new_elem_3(ns, node_name, parent_node, props, by_id, win_doc, onready_queue, is_tpl_name, by_id_parent_use, parent_ontplloading, props_has_ontplloading, parent_ontplevent, inher, for_root_id)
9847{	var empty = {};
9848	var ret_elem;
9849	var ontplloading = parent_ontplloading;
9850	var ontplevent = parent_ontplevent;
9851	if (!is_tpl_name)
9852	{	function get_elem()
9853		{	return ret_elem;
9854		}
9855		var for_props_has_ontplloading = {value: props_has_ontplloading};
9856		ontplloading = hook_ontplloading(parent_ontplloading, get_elem, props, for_props_has_ontplloading);
9857		ontplevent = hook_ontplevent(ns, parent_ontplevent, get_elem, props);
9858		props_has_ontplloading = for_props_has_ontplloading.value;
9859	}
9860	function new_elem_traverse(elem, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, for_update_elem, parent_for_dyn_options)
9861	{	var e_parent_node = elem.parentNode;
9862		var e_previous_sibling = elem.previousSibling;
9863		var id = elem.id;
9864		if (id && typeof(id)!='string') // remember: a form (HTMLFormElement) can contain input named "id", so typeof(id) will be 'object'
9865		{	id = elem.getAttribute('id');
9866		}
9867		var ap = id && props && !(id in empty) ? props[id] : null; // props for this element
9868		var repl_elem = null;
9869		if (ap?.cloneNode)
9870		{	repl_elem = ap;
9871			ap = null;
9872		}
9873		//
9874		var ontpl_attrs = {};
9875		var ontpl_attrs_special = elem_get_ontpl_attrs(elem, ns, by_id, remove_ids, ontpl_attrs);
9876		// is template?
9877		var tpl_name_main = null;
9878		var tpl_names_secondary = null;
9879		var tpl_details = null;
9880		var tpl_elem = null;
9881		var new_tpl_step_2 = null;
9882		var for_dyn_options = null;
9883		var tpl_ontplloading = null;
9884		var tpl_ontplloading_2 = null;
9885		var tpl_ontplloading_3 = null;
9886		var tpl_ontplevent = null;
9887		var tpl_ontpleventchildren = null;
9888		var tpl_ontpleventinner = null;
9889		var tpl_by_id_in = by_id_in;
9890		var tpl_onnewids = onnewids;
9891		var is_repeat_elem = false;
9892		var is_switch_elem = false;
9893		var tpl_inher = inher && inher.wrap_id==id ? inher : null;
9894		var class_name = elem.className;
9895		if (class_name)
9896		{	if (typeof(class_name) != 'string')
9897			{	class_name = elem.getAttribute('class') || ''; // in Chrome class_name can be SVGAnimatedString
9898			}
9899			let pos = class_name.indexOf('$');
9900			if (pos != -1)
9901			{	let class_list = elem.classList; // classList support is present since MSIE10
9902				let new_class_name = '';
9903				for (let i=0, c; c=class_list[i]; i++)
9904				{	if (c.charAt(0)=='$' && c.length>1)
9905					{	let c_tpl_name = c.slice(1);
9906						if (c_tpl_name == 'strip-text-nodes')
9907						{	strip_text_nodes(elem);
9908						}
9909						else if (c_tpl_name == 'repeat-elem')
9910						{	is_repeat_elem = true;
9911						}
9912						else if (c_tpl_name == 'switch-elem')
9913						{	is_switch_elem = true;
9914						}
9915						else if (typeof(ns.templates[c_tpl_name]) == 'function')
9916						{	let c_tpl_details = ns.templates[c_tpl_name]();
9917							if (c_tpl_details.html != null)
9918							{	if (!tpl_name_main)
9919								{	tpl_name_main = c_tpl_name;
9920									tpl_details = c_tpl_details;
9921								}
9922								else
9923								{	warn(`Templates "${tpl_name_main}" and "${c_tpl_name}" cannot come together`, elem, class_name);
9924								}
9925							}
9926							else
9927							{	if (!tpl_names_secondary)
9928								{	tpl_names_secondary = [c_tpl_name];
9929								}
9930								else
9931								{	tpl_names_secondary[tpl_names_secondary.length] = c_tpl_name;
9932								}
9933								if (!tpl_details)
9934								{	tpl_details = c_tpl_details;
9935								}
9936							}
9937							if (!ap) ap = {}; // don't use "empty" because this object can be modified in _reapply_template()
9938						}
9939						else
9940						{	warn(`Template "${c_tpl_name}" not found in current namespace`, elem, class_name);
9941							if (new_class_name)
9942							{	new_class_name += ' ';
9943							}
9944							new_class_name += c;
9945						}
9946					}
9947					else
9948					{	if (new_class_name)
9949						{	new_class_name += ' ';
9950						}
9951						new_class_name += c;
9952					}
9953				}
9954				if (!new_class_name)
9955				{	elem.removeAttribute('class');
9956				}
9957				else
9958				{	elem.setAttribute('class', new_class_name); // note, that Chrome 87.0.4280.88 throws error if i try to elem.className = ..., if the elem is an SVG element
9959				}
9960				if (is_repeat_elem && is_switch_elem)
9961				{	warn('$repeat-elem and $switch-elem cannot come together', elem, class_name);
9962					is_switch_elem = false;
9963				}
9964				if (is_switch_elem && tpl_name_main)
9965				{	warn('$switch-elem cannot come together with another template that replaces the elem', elem, class_name);
9966					tpl_name_main = null;
9967				}
9968			}
9969		}
9970		if (!tpl_name_main && tpl_names_secondary)
9971		{	tpl_name_main = tpl_names_secondary.shift();
9972			if (tpl_names_secondary.length == 0)
9973			{	tpl_names_secondary = null;
9974			}
9975		}
9976		if (id && (remove_ids || is_repeat_elem || ap && ap.id===null))
9977		{	elem.removeAttribute('id');
9978		}
9979		if (tpl_name_main) // if is template
9980		{	tpl_by_id_in = {};
9981			for_dyn_options =
9982			{	options: null,
9983				dyn_options: null,
9984				dyn_options_info: null
9985			};
9986			tpl_elem = elem;
9987			new_tpl_step_2 = tpl_details.inst(for_dyn_options, tpl_elem, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props);
9988			tpl_onnewids = function()
9989			{	add_props_shallow(by_id_in, tpl_by_id_in);
9990				onnewids?.();
9991			};
9992		}
9993		function get_repl_elem()
9994		{	return repl_elem || elem;
9995		}
9996		var for_props_has_ontplloading = {value: props_has_ontplloading};
9997		tpl_ontplloading = hook_ontplloading
9998		(	ontplloading,
9999			get_repl_elem,
10000			ontpl_attrs_special,
10001			for_props_has_ontplloading
10002		);
10003		tpl_ontplloading_2 = hook_ontplloading
10004		(	tpl_ontplloading,
10005			get_repl_elem,
10006			ap,
10007			for_props_has_ontplloading
10008		);
10009		tpl_ontplloading_3 = !tpl_details ? tpl_ontplloading_2 : hook_ontplloading
10010		(	tpl_ontplloading_2,
10011			get_repl_elem,
10012			tpl_details.props,
10013			for_props_has_ontplloading,
10014			tpl_details.inher
10015		);
10016		tpl_ontplevent = hook_ontplevent
10017		(	ns,
10018			ontplevent,
10019			get_repl_elem,
10020			ap,
10021			ontpl_attrs,
10022			undefined,
10023			NEW_ELEM_NDEBUG ? undefined : is_tpl_name,
10024			NEW_ELEM_NDEBUG ? undefined : id
10025		);
10026		tpl_ontpleventchildren = !tpl_details ? tpl_ontplevent : hook_ontplevent
10027		(	ns,
10028			tpl_ontplevent,
10029			get_repl_elem,
10030			tpl_details.props,
10031			null,
10032			tpl_details.inher,
10033			NEW_ELEM_NDEBUG ? undefined : tpl_name_main,
10034			NEW_ELEM_NDEBUG ? undefined : '{}'
10035		);
10036		tpl_ontpleventinner = !tpl_details || tpl_details.html==null ? tpl_ontpleventchildren : ontplevent_filter
10037		(	tpl_ontpleventchildren,
10038			get_repl_elem,
10039			tpl_details.props,
10040			tpl_details.reports,
10041			tpl_details.inher
10042		);
10043		props_has_ontplloading = for_props_has_ontplloading.value;
10044		//
10045		if (is_repeat_elem)
10046		{	if (!e_parent_node)
10047			{	warn("$repeat-elem: element must be attached to parent");
10048			}
10049			else
10050			{	// repeat elem
10051				let case_props_decl = get_data_attr(ns, elem, 'case', null, true, ap);
10052				function splice(index, n_remove, add, case_props)
10053				{	// normalize args
10054					index |= 0;
10055					if (index >= 0)
10056					{	n_remove |= 0;
10057						let add_is_array = typeof(add) == 'object';
10058						let n_add = (add_is_array ? add.length : add)|0;
10059						if (index > this.length)
10060						{	index = this.length;
10061						}
10062						// find node at index
10063						let node = this._get_node_at_index(index);
10064						let {repeat_option} = this;
10065						let repeat_option_i = !repeat_option ? -1 : repeat_option.get_option_index() + index;
10066						//
10067						onready_queue.enter();
10068						// remove
10069						if (n_remove > 0)
10070						{	if (index+n_remove > this.length)
10071							{	n_remove = this.length - index;
10072							}
10073							Array.prototype.splice.call(this, index, n_remove);
10074							repeat_option?.options.splice(repeat_option_i, n_remove);
10075							while (n_remove-->0 && node)
10076							{	let e = node.nextSibling;
10077								this._e_parent_node.removeChild(node);
10078								node = e;
10079								while (node && node._ne__repeat_elem!==this._repeat_elem_id)
10080								{	node = node.nextSibling;
10081								}
10082							}
10083						}
10084						// add
10085						for (let j=0; j<n_add; j++)
10086						{	if (add_is_array)
10087							{	case_props = add[j];
10088							}
10089							let by_id_2 = {parent: by_id};
10090							let e = this.repeat_elem.cloneNode(true);
10091							e._ne__repeat_elem = this._repeat_elem_id;
10092							if (tpl_ontplevent)
10093							{	if (!case_props) case_props = {};
10094								var event_obj = new NewElemEvent('beforecase', e, 'repeat-elem');
10095								tpl_ontplevent([event_obj, index, case_props]); // let modify case_props
10096							}
10097							this._e_parent_node.insertBefore(e, node);
10098							Array.prototype.splice.call(this, index, 0, {elem: e, by_id: by_id_2});
10099							if (repeat_option)
10100							{	let for_dyn_options_options = {};
10101								let sub_dyn_options_info = [];
10102								var for_dyn_options = this._repeat_option_has_suboptions ? {options: null, dyn_options: {length: 0}, dyn_options_info: sub_dyn_options_info} : null;
10103								let option = get_elem_option(ns, e, for_dyn_options_options, sub_dyn_options_info);
10104								if (for_dyn_options_options.options)
10105								{	option._dyn_options = for_dyn_options.dyn_options = for_dyn_options_options.options;
10106									for (let k=1, option_options; option_options = for_dyn_options_options['options_'+k]; k++)
10107									{	option['_dyn_options_'+k] = for_dyn_options['dyn_options_'+k] = option_options;
10108									}
10109								}
10110								repeat_option.options.splice(repeat_option_i++, 0, option);
10111							}
10112							let r_props = clone_obj(ap); // clone deep, so one row will not modify another's data
10113							r_props = case_to_tpls_props(case_props_decl, case_props, r_props); // modify
10114							new_elem_traverse
10115							(	e,
10116								r_props,
10117								by_id_2,
10118								by_id_in,
10119								remove_ids,
10120								!id ? onnewids : function()
10121								{	by_id_export_from_repeated_child(id, by_id, by_id_2);
10122									onnewids?.();
10123								},
10124								ontplloading,
10125								props_has_ontplloading,
10126								ontplevent,
10127								null,
10128								for_dyn_options
10129							);
10130							if (id)
10131							{	by_id_export_from_repeated_child(id, by_id, by_id_2);
10132								onnewids?.();
10133							}
10134							if (ap)
10135							{	add_props(win_doc, ns, e, ap, 0, by_id_2, id, onready_queue); // exclude ids
10136							}
10137							index++;
10138							if (tpl_ontplevent)
10139							{	event_obj.type = 'case';
10140								onready_queue.add_onready(tpl_ontplevent, this, [[event_obj, index, case_props, true, true]]);
10141							}
10142						}
10143						//
10144						if (tpl_ontplevent)
10145						{	event_obj = new NewElemEvent('resize', this, 'repeat-elem');
10146							onready_queue.add_onready(tpl_ontplevent, this, [[event_obj]]);
10147						}
10148						onready_queue.leave();
10149					}
10150				}
10151				let obj = new RepeatElem(elem, e_parent_node, e_previous_sibling, splice);
10152				e_parent_node.removeChild(elem);
10153				let dyn_options_info = parent_for_dyn_options?.dyn_options_info;
10154				if (dyn_options_info)
10155				{	for (let i=0, i_end=dyn_options_info.length; i<i_end; i++)
10156					{	let o = dyn_options_info[i];
10157						if (o.elem == elem)
10158						{	// found
10159							obj.repeat_option = o;
10160							obj._repeat_option_has_suboptions = !!o.dyn_options[o.i].options;
10161							add_props(win_doc, ns, obj, ap, 0, obj);
10162							add_props(win_doc, ns, obj, o.dyn_options[o.i], 0, obj);
10163							o.dyn_options[o.i] = obj;
10164							break;
10165						}
10166					}
10167				}
10168				elem = obj;
10169			}
10170		}
10171		else if (is_switch_elem)
10172		{	// the switch
10173			let sw_is_initing = true;
10174			let sw_initial_case = get_data_attr(ns, elem, 'case', null, true, ap);
10175			let sw_cases_props_decl = get_data_attr(ns, elem, 'cases', null, true, ap);
10176			let sw_hash = get_data_attr(ns, elem, 'hash', null, true, ap);
10177			let sw_map = {};
10178			let sw_case_names = [];
10179			let sw_cur_case_name = null;
10180			let sw_cur_case_props = {};
10181			let sw_hash_cases = null;
10182			let sw_state_handler = ns.SwitchElemStateHandler;
10183			let sw_state = null;
10184			let want_reswitch_name = undefined;
10185			let want_reswitch_props = undefined;
10186			let want_reswitch_force_reapply = undefined;
10187			let want_reswitch_transit = undefined;
10188			let want_reswitch_ontplload = undefined;
10189			function sw_init()
10190			{	var e;
10191				while ((e = elem.firstChild))
10192				{	if (e.nodeType == XML_ELEMENT_NODE)
10193					{	let case_name = get_data_attr(ns, e, 'case', null, true);
10194						if (case_name == null)
10195						{	error('No case in element inside $switch-elem', e);
10196						}
10197						else
10198						{	let c_hash = get_data_attr(ns, e, 'hash', null, true);
10199							let c_prepared = !!get_data_attr(ns, e, 'prepared', false, true);
10200							if (sw_hash!=null && c_hash==null)
10201							{	c_hash = case_name;
10202							}
10203							c_hash = (sw_hash || '') + (c_hash || '');
10204							if (c_hash)
10205							{	// add to sw_hash_cases
10206								if (sw_state_handler)
10207								{	let case_props_decl = sw_cases_props_decl?.[case_name];
10208									if (!sw_hash_cases)
10209									{	sw_hash_cases = [];
10210									}
10211									sw_hash_cases[sw_hash_cases.length] =
10212									{	case_name: case_name,
10213										hash: c_hash,
10214										hash_format: case_props_decl ? case_props_decl.hash : null
10215									};
10216								}
10217							}
10218							sw_map[case_name] = {elem: e, inited: c_prepared, hash: c_hash};
10219							if (c_prepared)
10220							{	sw_map[case_name].elem = e = new_elem_traverse(e, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, sw_map[case_name], parent_for_dyn_options);
10221								sw_map[case_name].elem._ne__switch_elem = elem;
10222							}
10223							sw_case_names.push(case_name);
10224						}
10225					}
10226					if (e.parentNode == elem) // new_elem_traverse could change
10227					{	elem.removeChild(e);
10228					}
10229				}
10230				// state
10231				if (sw_hash_cases)
10232				{	sw_state = sw_state_handler.reg
10233					(	sw_hash_cases,
10234						(new_case_name, new_case_props) =>
10235						{	if (sw_is_initing)
10236							{	sw_initial_case = null;
10237							}
10238							onready_queue.add_onready(sw_set_case, null, [new_case_name, new_case_props, false, null, null, true]);
10239						}
10240					);
10241				}
10242				if (sw_initial_case != null)
10243				{	// set initial case (before ontplready)
10244					if (sw_set_case(sw_initial_case, ap) !== undefined)
10245					{	if (sw_state)
10246						{	sw_is_initing = true; // fuse
10247							sw_state.set(sw_cur_case_name, sw_cur_case_props);
10248							sw_is_initing = false;
10249						}
10250					}
10251				}
10252				// add props (including ontplready)
10253				if (ap != null)
10254				{	add_props(win_doc, ns, elem, ap, 0, {'case': 1, cases: 1, hash: 1}, id, onready_queue);
10255				}
10256				sw_is_initing = false;
10257			}
10258			function sw_set_case(case_name, case_props, force_reapply, transit, ontplload, no_update_hash)
10259			{	if (case_name!=null && (!sw_map[case_name] || (case_name in {})))
10260				{	throw new Error('No such case in $switch-elem: '+case_name);
10261				}
10262				var need_init = case_name!=null && !sw_map[case_name].inited;
10263				if (!sw_is_initing && (tpl_ontplevent || elem.ontplbeforecase || elem.ontplcase)) // tplbeforecase will not fire for initial case (but tplcase will)
10264				{	if (!case_props) case_props = {};
10265					if (!force_reapply && case_name!=null && case_name==sw_cur_case_name)
10266					{	sw_cur_case_props = clone_obj(sw_cur_case_props, 1);
10267					}
10268					if (elem.ontplbeforecase)
10269					{	var result = elem.ontplbeforecase(new NewElemEvent('beforecase', elem, 'switch-elem'), case_name, case_props, need_init);
10270						if (!result && result!==undefined) return result;
10271					}
10272					if (tpl_ontplevent)
10273					{	result = tpl_ontplevent([new NewElemEvent('beforecase', elem, 'switch-elem'), case_name, case_props, need_init]); // let modify case_props
10274						if (!result && result!==undefined) return result;
10275					}
10276				}
10277				if (force_reapply || case_name!=sw_cur_case_name || !equals(case_props || {}, sw_cur_case_props || {}))
10278				{	let save_cur_case_name = sw_cur_case_name;
10279					let save_case_name = case_name;
10280					if (save_cur_case_name!=null && !sw_map[save_cur_case_name].elem)
10281					{	sw_map[save_cur_case_name].elem = elem.firstChild;
10282					}
10283					if (need_init)
10284					{	sw_map[save_case_name].inited = true;
10285					}
10286					sw_cur_case_name = case_name;
10287					sw_cur_case_props = case_props || {};
10288					onready_queue.enter(); // align ontplready/ontplload calls in valid order
10289					let autoplay = transit && !('play' in transit);
10290					let transit_set_end = transit_set_begin(transit);
10291					if (!need_init)
10292					{	if (case_props)
10293						{	// reapply templates
10294							let r_props = case_to_tpls_props(sw_cases_props_decl?.[case_name], case_props);
10295							if (r_props)
10296							{	add_props(win_doc, ns, by_id, r_props, force_reapply ? 2 : 1, null, null, onready_queue);
10297							}
10298						}
10299					}
10300					else
10301					{	props = case_to_tpls_props(sw_cases_props_decl?.[case_name], case_props, props); // merge props
10302						sw_map[save_case_name].elem = new_elem_traverse(sw_map[save_case_name].elem, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, sw_map[save_case_name], parent_for_dyn_options);
10303						sw_map[save_case_name].elem._ne__switch_elem = elem;
10304						onnewids?.();
10305					}
10306					if (tpl_ontplevent || elem.ontplcase)
10307					{	let args = [new NewElemEvent('case', elem, 'switch-elem'), case_name, case_props, need_init];
10308						if (elem.ontplcase)
10309						{	onready_queue.add_onready(elem.ontplcase, elem, args);
10310						}
10311						if (tpl_ontplevent)
10312						{	onready_queue.add_onready(tpl_ontplevent, elem, [args]);
10313						}
10314					}
10315					replace_elem
10316					(	ns,
10317						win_doc,
10318						onready_queue,
10319						save_cur_case_name==null ? null : sw_map[save_cur_case_name].elem,
10320						elem,
10321						null,
10322						save_case_name==null ? null : sw_map[save_case_name].elem,
10323						ontplload,
10324						transit,
10325						() =>
10326						{	elem.ontplcasetransitionend?.(new NewElemEvent('casetransitionend', elem, 'switch-elem'));
10327							tpl_ontplevent?.([new NewElemEvent('casetransitionend', elem, 'switch-elem')]);
10328						}
10329					);
10330					onready_queue.leave();
10331					if (autoplay)
10332					{	deferred(transit_set_end, 60); // give to template time to load data (reduces flickering)
10333					}
10334					if (!no_update_hash)
10335					{	sw_state?.set(sw_cur_case_name, sw_cur_case_props);
10336					}
10337				}
10338				else
10339				{	transit_set_begin(transit)();
10340					if (ontplload) ontplload.call(elem);
10341				}
10342				if (want_reswitch_name !== undefined)
10343				{	case_name = want_reswitch_name;
10344					case_props = want_reswitch_props;
10345					force_reapply = want_reswitch_force_reapply;
10346					transit = want_reswitch_transit;
10347					ontplload = want_reswitch_ontplload
10348					want_reswitch_name = want_reswitch_props = want_reswitch_force_reapply = want_reswitch_transit = want_reswitch_ontplload = undefined;
10349					sw_set_case(case_name, case_props, force_reapply, transit, ontplload);
10350				}
10351			}
10352			elem._get_cases = function()
10353			{	return sw_case_names;
10354			};
10355			elem._get_case = function()
10356			{	return sw_cur_case_name;
10357			};
10358			elem._set_case = function(case_name, case_props, force_reapply, transit, ontplload)
10359			{	sw_set_case(case_name, case_props, force_reapply, transit, ontplload);
10360			};
10361			elem._reapply_template = function(tpl_props, set_new, only_if_diff, transit, ontplload)
10362			{	if (tpl_props)
10363				{	var case_name = tpl_props?.['case'];
10364					if (case_name != null)
10365					{	if ((!sw_map[case_name] || (case_name in {})))
10366						{	throw new Error('No such case in $switch-elem: '+case_name);
10367						}
10368					}
10369					else
10370					{	case_name = sw_cur_case_name;
10371					}
10372					var case_props_decl = sw_cases_props_decl?.[case_name];
10373					sw_set_case(case_name, tpl_props, !only_if_diff, transit, ontplload);
10374					add_props(win_doc, ns, this, tpl_props, 0, case_props_decl, null, onready_queue, transit, true);
10375				}
10376				return this;
10377			};
10378			sw_init();
10379		}
10380		else
10381		{	// traverse recursively
10382			for (let e=elem.firstElementChild, e_next; e; e=e_next)
10383			{	e_next = e.nextElementSibling;
10384				new_elem_traverse(e, props, by_id, tpl_by_id_in, remove_ids, tpl_onnewids, tpl_ontplloading_3, props_has_ontplloading, tpl_ontpleventchildren, null, for_dyn_options ? for_dyn_options : parent_for_dyn_options);
10385			}
10386			//
10387			if (tpl_name_main) // if is template
10388			{	add_props_shallow(by_id_in, tpl_by_id_in);
10389				// apply template
10390				repl_elem = new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventinner);
10391				if (tpl_names_secondary)
10392				{	for (let i=0, i_end=tpl_names_secondary.length; i<i_end; i++)
10393					{	let c_tpl_details = ns.templates[tpl_names_secondary[i]]();
10394						let c_for_dyn_options =
10395						{	options: null,
10396							dyn_options: null,
10397							dyn_options_info: null
10398						};
10399						let c_new_tpl_step_2 = c_tpl_details.inst(c_for_dyn_options, repl_elem, id, ap, by_id, {}, by_id_parent_use, win_doc, onready_queue, null, props);
10400						c_new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventchildren);
10401					}
10402				}
10403			}
10404			else if (ap)
10405			{	// add props
10406				add_props(win_doc, ns, elem, ap, 0, null, id, onready_queue);
10407			}
10408			// replace elem?
10409			if (repl_elem!=null && repl_elem!=elem)
10410			{	elem = replace_elem(ns, win_doc, onready_queue, elem, e_parent_node, e_previous_sibling, repl_elem);
10411			}
10412		}
10413		// add to by_id
10414		if (id)
10415		{	if (by_id[id])
10416			{	log('Markup has duplicate id: '+id, by_id[id], elem);
10417			}
10418			by_id[id] = elem;
10419			if (by_id_in)
10420			{	by_id_in[id] = true;
10421			}
10422		}
10423		//
10424		var ontplready = is_repeat_elem || !ontpl_attrs_special ? null : ontpl_attrs_special.ontplready;
10425		if (ontplready)
10426		{	// ontplready legitimately fires again when the element is re-traversed (re-show,
10427			// lazy $switch-elem case init, template reapply). Stamp the element after the
10428			// first run, so handlers that must init once (e.g. DOM-appending code) can guard:
10429			// if (this.getAttribute('data-tpl-inited')) return;
10430			onready_queue.add_onready
10431			(	function()
10432				{	try
10433					{	return ontplready.apply(this, arguments);
10434					}
10435					finally
10436					{	this.setAttribute?.('data-tpl-inited', '1');
10437					}
10438				},
10439				elem
10440			);
10441		}
10442		if (tpl_ontplloading_3 != tpl_ontplloading_2)
10443		{	onready_queue.add_onload(tpl_ontplloading_3); // load
10444		}
10445		if (tpl_ontplloading_2 != tpl_ontplloading)
10446		{	onready_queue.add_onload(tpl_ontplloading_2); // load
10447		}
10448		if (tpl_ontplloading != ontplloading)
10449		{	onready_queue.add_onload(tpl_ontplloading); // load
10450		}
10451		//
10452		if (is_repeat_elem)
10453		{	elem.set_length(get_data_attr(ns, elem.repeat_elem, 'length', 0, true, ap));
10454		}
10455		else if (tpl_name_main) // if is template, add _clone_template() and _reapply_template() to tpl
10456		{	function get_tplname() // TODO: rename to _get_tplnames()
10457			{	return !tpl_name_main ? tpl_names_secondary.join(' ') : !tpl_names_secondary ? tpl_name_main : tpl_name_main+' '+tpl_names_secondary.join(' ');
10458			}
10459			function get_tplelem()
10460			{	return elem;
10461			}
10462			function get_tplprops()
10463			{	return ap;
10464			}
10465			function get_tplwrapped()
10466			{	var {inher} = tpl_details;
10467				return inher?.to_wrap_elem?._get_tplname();
10468			}
10469			function get_tplhtml()
10470			{	return tpl_details.html;
10471			}
10472			function get_tplfunc()
10473			{	return tpl_details.func;
10474			}
10475			function tpl_serialize()
10476			{	var attrs_str = serialize_attrs(ap, {options: 1}, ('$'+tpl_name_main+' '+tpl_details.class_name_for_serialize).trim());
10477				var options_html;
10478				var inner_html = tpl_details.html==null ? this.innerHTML : ap.options ? (options_html = '\n'+serialize_tpl_options(ap.options, '\t')) : '';
10479				var html = tpl_details.elem_for_serialize.outerHTML;
10480				var pos = html.match(/^<(?:[^"'>]+|"[^"]*"|'[^']*')*>/)[0].length - 1; // pos of '>' that terminates opening tag
10481				var tag_open = html.slice(0, pos);
10482				var rest = html.slice(pos + 1);
10483				if (options_html)
10484				{	pos = tag_open.indexOf(' ');
10485					tag_open = pos==-1 ? '<menu' : '<menu'+tag_open.slice(pos);
10486					pos = rest.lastIndexOf('<');
10487					rest = rest.slice(0, pos)+'</menu>';
10488				}
10489				return tag_open+attrs_str+'>'+inner_html+rest;
10490			}
10491			function clone_template(parent_node, ontplload)
10492			{	var hook = tpl_ontplloading;
10493				if (ontplload != null)
10494				{	var elem = this;
10495					hook = hook_ontplloading(null, () => elem, {ontplload}, {value: false});
10496				}
10497				onready_queue.enter();
10498				var e2 = tpl_details.html!=null ? tpl_elem : clone_node(this, null, null, null, true);
10499				var repl_elem = ns.templates[tpl_name_main]().inst(for_dyn_options, e2, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props)(false, hook);
10500				repl_elem._get_tplname = get_tplname;
10501				repl_elem._get_tplelem = get_tplelem;
10502				repl_elem._get_tplprops = get_tplprops;
10503				repl_elem._get_tplwrapped = get_tplwrapped;
10504				repl_elem._get_tplhtml = get_tplhtml;
10505				repl_elem._get_tplfunc = get_tplfunc;
10506				repl_elem._reapply_template = reapply_template;
10507				repl_elem._clone_template = clone_template;
10508				repl_elem._tpl_serialize = tpl_serialize;
10509				add_to_parent(repl_elem, parent_node);
10510				add_props(win_doc, ns, repl_elem, {attributes: this, style: this.style.cssText, className: this.className});
10511				hook?.();
10512				onready_queue.leave();
10513				return repl_elem;
10514			}
10515			let is_reapplying = false;
10516			let want_again = false;
10517			function reapply_template(tpl_props, set_new, only_if_diff, transit, ontplload)
10518			{	var no_action = false;
10519				if (tpl_props)
10520				{	var tpl_props_only, to_add, to_add_ontpl;
10521					if (only_if_diff)
10522					{	for (let a in tpl_props)
10523						{	let is_ontpl = a.slice(0, 5) == 'ontpl';
10524							let is_on_listeners = a.slice(0, 2)=='on' && typeof(tpl_props[a]?.func)=='function';
10525							if ((a in tpl_details.arg_names_assoc) && !is_ontpl && !is_on_listeners)
10526							{	if (!tpl_props_only) tpl_props_only = {};
10527								tpl_props_only[a] = tpl_props[a];
10528							}
10529							else
10530							{	if (!to_add) to_add = {};
10531								to_add[a] = tpl_props[a];
10532								if (is_ontpl)
10533								{	if (!to_add_ontpl) to_add_ontpl = {};
10534									to_add_ontpl[a] = tpl_props[a];
10535								}
10536							}
10537						}
10538					}
10539					if (set_new)
10540					{	if (only_if_diff && equals(ap || {}, tpl_props_only || {}))
10541						{	no_action = true;
10542						}
10543						else
10544						{	ap = typeof(tpl_props)=='object' ? tpl_props : null;
10545						}
10546					}
10547					else if (!only_if_diff)
10548					{	if (ap)
10549						{	add_props_shallow(ap, tpl_props);
10550						}
10551						else
10552						{	ap = tpl_props;
10553						}
10554					}
10555					else if (!tpl_props_only)
10556					{	no_action = true;
10557					}
10558					else
10559					{	// can use this._set_*()?
10560						let to_set = [];
10561						for (let a in tpl_props_only)
10562						{	if (typeof(this['_set_'+a]) == 'function')
10563							{	to_set[to_set.length] = a;
10564							}
10565							else if (!ap || !equals(ap[a], tpl_props_only[a]))
10566							{	to_set = null;
10567								break;
10568							}
10569						}
10570						if (to_set)
10571						{	if (to_set.length)
10572							{	if (!ap) ap = {};
10573								for (let a of to_set)
10574								{	this['_set_'+a](tpl_props[a]);
10575									ap[a] = tpl_props[a];
10576								}
10577							}
10578							no_action = true;
10579						}
10580						else
10581						{	if (ap)
10582							{	add_props_shallow(ap, tpl_props);
10583							}
10584							else
10585							{	ap = tpl_props;
10586							}
10587						}
10588					}
10589				}
10590				var transit_set_end = transit_set_begin(transit);
10591				if (no_action || is_reapplying)
10592				{	if (!no_action)
10593					{	want_again = true;
10594						log('Template "'+tpl_name_main+'" is already reapplying. I will reapply it again when it finishes.');
10595					}
10596					if (to_add)
10597					{	add_props(win_doc, ns, this, to_add, 0, null, null, onready_queue, transit, true);
10598					}
10599					transit_set_end();
10600					if (ontplload) ontplload.call(this, false); // ontplload(has_reapplied)
10601					return this;
10602				}
10603				is_reapplying = true;
10604				onready_queue.enter();
10605				var e_parent_node = this.parentNode;
10606				var e_previous_sibling = this.previousSibling;
10607				var elem = this;
10608				var new_tpl_step_2 = ns.templates[tpl_name_main]().inst(for_dyn_options, tpl_elem, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props);
10609				if (tpl_ontplloading_3 != tpl_ontplloading_2)
10610				{	var event_obj = new NewElemEvent('unloading', elem, tpl_name_main);
10611					tpl_ontplloading_3(null, event_obj, tpl_details.props, onready_queue); // load
10612				}
10613				if (tpl_ontpleventchildren != tpl_ontplevent)
10614				{	tpl_ontpleventchildren(null, tpl_details);
10615				}
10616				repl_elem = new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventchildren, transit);
10617				repl_elem._get_tplname = get_tplname;
10618				repl_elem._get_tplelem = get_tplelem;
10619				repl_elem._get_tplprops = get_tplprops;
10620				repl_elem._get_tplwrapped = get_tplwrapped;
10621				repl_elem._get_tplhtml = get_tplhtml;
10622				repl_elem._get_tplfunc = get_tplfunc;
10623				repl_elem._reapply_template = reapply_template;
10624				repl_elem._clone_template = clone_template;
10625				repl_elem._tpl_serialize = tpl_serialize;
10626				if (to_add_ontpl)
10627				{	add_props(win_doc, ns, repl_elem, to_add_ontpl, 0, null, null, onready_queue, null, true);
10628				}
10629				if (id) by_id[id] = repl_elem;
10630				if (for_update_elem) for_update_elem.elem = repl_elem;
10631				repl_elem = replace_elem
10632				(	ns,
10633					win_doc,
10634					onready_queue,
10635					this,
10636					e_parent_node,
10637					e_previous_sibling,
10638					repl_elem,
10639					function()
10640					{	is_reapplying = false;
10641						ontplload?.call(this, true); // ontplload(has_reapplied)
10642						if (want_again)
10643						{	want_again = false;
10644							reapply_template();
10645						}
10646					},
10647					transit
10648				);
10649				if (tpl_inher)
10650				{	tpl_inher.to_wrap_elem = repl_elem;
10651					tpl_inher.apply_props();
10652				}
10653				if (tpl_ontplloading_3 != tpl_ontplloading_2)
10654				{	event_obj.type = 'loading';
10655					event_obj.target = repl_elem;
10656					onready_queue.add_onready(tpl_ontplloading_3, this, [false, event_obj]); // load
10657				}
10658				transit_set_end();
10659				onready_queue.leave();
10660				return repl_elem;
10661			}
10662			elem._get_tplname = get_tplname;
10663			elem._get_tplelem = get_tplelem;
10664			elem._get_tplprops = get_tplprops;
10665			elem._get_tplwrapped = get_tplwrapped;
10666			elem._get_tplhtml = get_tplhtml;
10667			elem._get_tplfunc = get_tplfunc;
10668			elem._reapply_template = reapply_template;
10669			elem._clone_template = clone_template;
10670			(repl_elem ?? elem)._tpl_serialize = tpl_serialize;
10671		}
10672		return elem;
10673	}
10674	if (node_name.cloneNode)
10675	{	if (parent_node)
10676		{	add_to_parent(node_name, parent_node);
10677		}
10678		if (node_name.nodeType == XML_ELEMENT_NODE)
10679		{	if (for_root_id)
10680			{	for_root_id.id = node_name.getAttribute('id');
10681			}
10682			ret_elem = new_elem_traverse(node_name, props, by_id, null, false, null, ontplloading, props_has_ontplloading, ontplevent);
10683		}
10684		else
10685		{	ret_elem = node_name;
10686		}
10687	}
10688	else
10689	{	ret_elem = win_doc.doc.createElement('div');
10690		add_props(win_doc, ns, ret_elem, {childNodes: node_name});
10691		elem_trim(ret_elem);
10692		let children = ret_elem.childNodes;
10693		let n_children = children.length;
10694		if (for_root_id && n_children==1)
10695		{	for_root_id.id = ret_elem.firstChild.getAttribute('id');
10696		}
10697		if (parent_node)
10698		{	children = add_to_parent(children, parent_node) || children;
10699			ret_elem = null;
10700		}
10701		for (let i=0; i<n_children; i++)
10702		{	let e = children[i];
10703			if (e.nodeType == XML_ELEMENT_NODE)
10704			{	e = new_elem_traverse(e, props, by_id, null, !props.no_remove_ids, null, ontplloading, props_has_ontplloading, ontplevent);
10705			}
10706			if (i==0 && n_children==1)
10707			{	ret_elem = e;
10708			}
10709		}
10710	}
10711	if (props.ontplready)
10712	{	onready_queue.add_onready(props.ontplready, ret_elem);
10713	}
10714	if (ontplloading && ontplloading!=parent_ontplloading)
10715	{	onready_queue.add_onready(ontplloading, ret_elem);
10716	}
10717	return ret_elem;
10718}
10719function declare_template(name, wrap_id, html, arg_names, func)
10720{	var ns = this;
10721	if (!ns.templates)
10722	{	ns.templates = {};
10723	}
10724	ns.templates[name] = function()
10725	{	// arguments
10726		if (!arg_names)
10727		{	const RE_TPL_ARGS_TOKENIZER = /[^,(){}\[\]'"`\/]+|'(?:[^'\\]*)(?:\\?[^'\\]*)*'|"(?:[^"\\]*)(?:\\?[^"\\]*)*"|`(?:[^`\\]*)(?:\\?[^`\\]*)*`|\/\*[\S\s]*?\*\/|\/\/[^\r\n]*|./g;
10728			const RE_TPL_ARGS_COMMENTS = /\/\*[\S\s]*?\*\/|\/\/[^\r\n]*/g;
10729			arg_names = [];
10730			let level = 0;
10731			let from = 0;
10732			function add(offset, source)
10733			{	var arg_name = source.slice(from, offset);
10734				arg_name = arg_name.replace(RE_TPL_ARGS_COMMENTS, '');
10735				arg_name = arg_name.trim();
10736				arg_name = arg_name.charAt(0)=='_' ? arg_name.slice(1) : arg_name;
10737				var eq = arg_name.indexOf('=');
10738				if (eq != -1)
10739				{	arg_name = arg_name.slice(0, eq).trim();
10740				}
10741				arg_names[arg_names.length] = arg_name;
10742				from = offset + 1;
10743			}
10744			(func+'').replace
10745			(	RE_TPL_ARGS_TOKENIZER,
10746				(token, offset, source) =>
10747				{	if (from != -1)
10748					{	if (token=='(' || token=='{' || token=='[')
10749						{	if (level++ == 0)
10750							{	from = offset + 1;
10751							}
10752						}
10753						else if (token==')' || token=='}' || token==']')
10754						{	if (--level == 0)
10755							{	add(offset, source);
10756								from = -1;
10757							}
10758						}
10759						else if (level==1 && token==',')
10760						{	add(offset, source);
10761						}
10762					}
10763					return '';
10764				}
10765			);
10766		}
10767		var arg_i_ns = -1;
10768		var arg_i_by_id = -1;
10769		var arg_i_by_id_parent = -1;
10770		var arg_i_by_id_in = -1;
10771		var arg_i_elem = -1;
10772		var arg_i_window = -1;
10773		var arg_i_document = -1;
10774		var arg_i_id = -1;
10775		var arg_i_report_tplload = -1;
10776		var arg_i_report_tplunload = -1;
10777		var arg_names_assoc = {};
10778		var reports = null;
10779		for (let i=0, i_end=arg_names.length; i<i_end; i++)
10780		{	let arg_name = arg_names[i];
10781			if (arg_name == 'ns')
10782			{	arg_i_ns = i;
10783			}
10784			else if (arg_name == 'by_id')
10785			{	arg_i_by_id = i;
10786			}
10787			else if (arg_name == 'by_id_parent')
10788			{	arg_i_by_id_parent = i;
10789			}
10790			else if (arg_name == 'by_id_in')
10791			{	arg_i_by_id_in = i;
10792			}
10793			else if (arg_name == 'elem')
10794			{	arg_i_elem = i;
10795			}
10796			else if (arg_name == 'window')
10797			{	arg_i_window = i;
10798			}
10799			else if (arg_name == 'document')
10800			{	arg_i_document = i;
10801			}
10802			else if (arg_name == 'id')
10803			{	arg_i_id = i;
10804			}
10805			else if (arg_name.slice(0, 10) == 'report_tpl')
10806			{	let onwhat = arg_name.slice(10);
10807				if (onwhat=='ready' || onwhat=='reload')
10808				{	error('$'+name+': '+arg_name+' is invalid');
10809					arg_names[i - 1] = '';
10810					continue;
10811				}
10812				arg_name = arg_name.slice(10);
10813				if (onwhat == 'load')
10814				{	arg_i_report_tplload = i;
10815				}
10816				else if (onwhat == 'unload')
10817				{	arg_i_report_tplunload = i;
10818				}
10819				else if (onwhat != 'error')
10820				{	if (!reports)
10821					{	reports = [];
10822					}
10823					reports[reports.length] = arg_name;
10824				}
10825				arg_name = 'ontpl'+arg_name;
10826			}
10827			arg_names_assoc[arg_name] = true;
10828		}
10829		// tpl_details
10830		const tpl_details =
10831		{	arg_names_assoc,
10832			reports,
10833			html,
10834			func,
10835			elem_for_serialize: null,
10836			class_name_for_serialize: null,
10837			props: null,
10838			has_by_id_in: false,
10839			inher: null,
10840			inst(for_dyn_options, elem, id, props, by_id_parent, by_id_in, by_id_parent_use, win_doc, onready_queue, parent_inher, parent_props)
10841			{	var args = [];
10842				var by_id = by_id_parent ? {parent: by_id_parent} : {};
10843				var n_to_load = 0;
10844				var debug_timer = null;
10845				var elem2;
10846				var by_id_parent_is_outer = get_data_attr(ns, elem, 'by_id_parent', undefined, true, props) !== undefined;
10847				var ontplloading = null;
10848				var ontplevent = null;
10849				function loading_initial(is_unload, n)
10850				{	var prev_n_to_load = n_to_load;
10851					n = parseInt(n);
10852					n_to_load += is_unload ? -(isNaN(n) ? 1 : n|0) : isNaN(n) ? -n_to_load : n|0;
10853					if (prev_n_to_load==0 && n_to_load<0 || prev_n_to_load<0 && n_to_load==0)
10854					{	onready_queue.add_onready
10855						(	() =>
10856							{	if (ontplloading)
10857								{	var event_obj = new NewElemEvent(is_unload ? 'unloading' : 'loading', elem2, name);
10858									ontplloading(is_unload, event_obj);
10859								}
10860							}
10861						);
10862						if (!NEW_ELEM_NDEBUG)
10863						{	if (debug_timer)
10864							{	debug_timer();
10865								debug_timer = null;
10866							}
10867							if (is_unload)
10868							{	debug_timer = deferred
10869								(	() =>
10870									{	log('Template $'+name+' is loading for too long', elem2);
10871									},
10872									16000
10873								);
10874							}
10875						}
10876					}
10877				}
10878				var loading = loading_initial;
10879				function get_arg(arg_name)
10880				{	var value = get_data_attr(ns, elem, arg_name, undefined, true, props, parent_inher, for_dyn_options, parent_props, html!=null, by_id_parent);
10881					if (value !== undefined)
10882					{	props[arg_name] = value; // if from attribute - copy to props, so can _reapply_template()
10883						/*if (html!=null && arg_name=='options')
10884						{	elem_empty(elem); // was converted to options
10885						}*/
10886					}
10887					else if (arg_name == 'dyn_options')
10888					{	value = get_data_attr(ns, elem, 'options', undefined, true, props, parent_inher, for_dyn_options, parent_props, html!=null, by_id_parent);
10889						if (value !== undefined)
10890						{	props.options = value; // if from attribute - copy to props, so can _reapply_template()
10891							props.dyn_options = value = for_dyn_options.dyn_options || {length: 0};
10892							//elem_empty(elem); // was converted to options
10893						}
10894					}
10895					return value;
10896				}
10897				for (var i=0, i_end=arg_names.length; i<i_end; i++)
10898				{	let arg_name = arg_names[i];
10899					let value;
10900					if (i == arg_i_ns)
10901					{	value = ns;
10902					}
10903					else if (i == arg_i_by_id)
10904					{	value = by_id;
10905					}
10906					else if (i == arg_i_by_id_parent)
10907					{	if (!by_id_parent_is_outer)
10908						{	value = by_id_parent;
10909						}
10910						else
10911						{	value = by_id_parent_use || by_id_parent;
10912							value = value?.parent || {};
10913						}
10914					}
10915					else if (i == arg_i_by_id_in)
10916					{	value = by_id_in || {};
10917					}
10918					else if (i == arg_i_elem)
10919					{	value = elem;
10920					}
10921					else if (i == arg_i_id)
10922					{	value = id;
10923					}
10924					else if (i == arg_i_window)
10925					{	value = win_doc.win;
10926					}
10927					else if (i == arg_i_document)
10928					{	value = win_doc.doc;
10929					}
10930					else if (i == arg_i_report_tplload)
10931					{	loading(true);
10932						value = pair_funcs(value, n => {loading(false, n)});
10933					}
10934					else if (i == arg_i_report_tplunload)
10935					{	value = pair_funcs(value, () => {loading(true)});
10936					}
10937					else if (arg_name.slice(0, 10) == 'report_tpl')
10938					{	let event_type = arg_name.slice(10);
10939						let func_name = 'ontpl'+event_type;
10940						value = pair_funcs
10941						(	value,
10942							function()
10943							{	if (!elem2 || (!ontplevent && !elem2[func_name]))
10944								{	if (event_type == 'error')
10945									{	error(arguments[0]);
10946									}
10947									return;
10948								}
10949								var event_obj = new NewElemEvent(event_type, elem2, name);
10950								if (arguments[0] instanceof NewElemEvent) // assume: proxied like {ontplchange: report_tplchange}
10951								{	var args = [...arguments];
10952									args[0] = event_obj;
10953								}
10954								else
10955								{	args = [event_obj, ...arguments];
10956								}
10957								if (elem2[func_name])
10958								{	var stop_propagation_value = pcall(elem2[func_name], elem2, args);
10959									if (stop_propagation_value !== undefined)
10960									{	return stop_propagation_value;
10961									}
10962								}
10963								if (ontplevent)
10964								{	return ontplevent.call(this, args);
10965								}
10966							}
10967						);
10968					}
10969					else
10970					{	value = get_arg(arg_name);
10971					}
10972					args[i] = value;
10973				}
10974				var inher = null;
10975				if (wrap_id)
10976				{	// Want find template with id "wrap_id". It will be passed to parent_inher of the found template to be filled 
10976in.
10977					inher = new Inher(parent_inher, name, wrap_id, arg_names_assoc, get_arg);
10978				}
10979				if (!tpl_details.elem_for_serialize)
10980				{	let elem_for_serialize = elem.cloneNode(html!=null && !('options' in arg_names_assoc));
10981					tpl_details.elem_for_serialize = elem_for_serialize;
10982					tpl_details.class_name_for_serialize = elem_for_serialize.className; // TODO: return, don't put to tpl_details
10983					elem_for_serialize.removeAttribute('class');
10984				}
10985				var new_elem_props = func?.apply(ns, args) || {};
10986				tpl_details.props = new_elem_props;
10987				tpl_details.inher = inher;
10988				// Completed step 1. Returning function to call to do step 2.
10989				return function(props_has_ontplloading, set_ontplloading, set_ontplevent, set_ontpleventinner, transit)
10990				{	ontplloading = set_ontplloading;
10991					ontplevent = set_ontplevent;
10992					if (NEW_ELEM_NDEBUG)
10993					{	if (!ontplloading)
10994						{	loading = do_nothing;
10995						}
10996					}
10997					var use_html = html==null ? elem : typeof(html)=='function' ? pcall(html, ns, [new_elem_props?.preprocess || {}], null, 'Error in preprocessing code: ')+'' : html+'';
10998					// html to elem
10999					var for_root_id = {id: null};
11000					elem2 = new_elem_3(ns, use_html, null, new_elem_props, by_id, win_doc, onready_queue, name, by_id_parent_is_outer ? by_id_parent_use || by_id_parent : by_id, set_ontplloading, props_has_ontplloading, set_ontpleventinner, inher, for_root_id);
11001					var root_props = for_root_id.id && new_elem_props[for_root_id.id]; // props of the root elem
11002					if (parent_inher)
11003					{	parent_inher.to_wrap_elem = elem2;
11004						parent_inher.to_wrap_root_props = {}; // template found
11005						if (inher?.to_wrap_root_props)
11006						{	// copy from inher
11007							for (let p in inher.to_wrap_root_props)
11008							{	parent_inher.to_wrap_root_props[p] = true;
11009							}
11010						}
11011						for (let p in arg_names_assoc)
11012						{	parent_inher.arg_names_assoc[p] = true;
11013						}
11014					}
11015					if (inher)
11016					{	if (!inher.to_wrap_elem)
11017						{	error(`Template "${name}" requested to wrap element with id "${wrap_id}", but it's not found in source HTML, or it doesn't have a $class`);
11018						}
11019						else
11020						{	inher.wrapper_elem = elem2;
11021							inher.apply_props();
11022						}
11023					}
11024					if (root_props && parent_inher)
11025					{	for (let p in root_props)
11026						{	if (typeof(root_props[p])=='function' && p.slice(0, 2)!='on')
11027							{	parent_inher.to_wrap_root_props[p] = true;
11028							}
11029						}
11030					}
11031					add_props(win_doc, ns, elem2, props, 0, arg_names_assoc, null, onready_queue, transit);
11032					add_attributes(elem2, elem, {'class': 1}, false, true);
11033					var cl_from = elem.classList;
11034					if (cl_from.length)
11035					{	let cl_to = elem2.classList;
11036						for (let i=0, i_end=cl_from.length; i<i_end; i++)
11037						{	cl_to.add(cl_from.item(i));
11038						}
11039					}
11040					return elem2;
11041				};
11042			}
11043		};
11044		// replace with shorter function
11045		this[name] = function()
11046		{	return tpl_details;
11047		};
11048		return tpl_details;
11049	};
11050}
11051
11052class Inher
11053{	constructor(parent_inher, wrapper_name, wrap_id, arg_names_assoc, get_arg)
11054	{	this.parent_inher = parent_inher;
11055		this.wrapper_name = wrapper_name;
11056		this.wrap_id = wrap_id;
11057		this.arg_names_assoc = arg_names_assoc; // want add keys to this object
11058		this.get_arg = get_arg;
11059		this.to_wrap_elem = null
11060		this.to_wrap_root_props = null;
11061		this.wrapper_elem = null;
11062	}
11063
11064	apply_props()
11065	{	var {wrapper_elem, to_wrap_elem} = this;
11066		var pp = this.to_wrap_root_props;
11067		if (wrapper_elem!=to_wrap_elem && pp)
11068		{	// proxy functions from to_wrap_elem to wrapper_elem
11069			for (let prop in pp)
11070			{	if (wrapper_elem[prop] == null)
11071				{	let func_src = to_wrap_elem[prop] + '';
11072					if (func_src.indexOf('function')!=-1 && !RE_THIS.test(func_src))
11073					{	wrapper_elem[prop] = to_wrap_elem[prop];
11074					}
11075					else
11076					{	wrapper_elem[prop] = function()
11077						{	return to_wrap_elem[prop].apply(to_wrap_elem, arguments);
11078						};
11079					}
11080				}
11081			}
11082		}
11083	}
11084
11085	subst_event_target(event_obj, args)
11086	{	if (this.to_wrap_elem == event_obj.target)
11087		{	if (this.wrapper_elem)
11088			{	event_obj.target = this.wrapper_elem;
11089			}
11090			event_obj.target_name = this.wrapper_name;
11091			var func_name = 'ontpl'+event_obj.type;
11092			if (typeof(event_obj.target[func_name]) == 'function')
11093			{	return pcall(event_obj.target[func_name], event_obj.target, args);
11094			}
11095		}
11096	}
11097}
11098
11099class RepeatElem
11100{	constructor(repeat_elem, e_parent_node, e_previous_sibling, splice)
11101	{	this.length = 0;
11102		this.repeat_elem = repeat_elem;
11103		this.splice = splice;
11104		this.repeat_option = undefined;
11105		this._e_parent_node = e_parent_node;
11106		this._e_previous_sibling = e_previous_sibling;
11107		this._repeat_elem_id = {};
11108		this._repeat_option_has_suboptions = false;
11109	}
11110
11111	*[Symbol?.iterator]()
11112	{	for (let i=0, i_end=this.length; i<i_end; i++)
11113		{	yield this[i];
11114		}
11115	}
11116
11117	_get_node_at_index(index)
11118	{	var node = this._e_previous_sibling ? this._e_previous_sibling.nextSibling : this._e_parent_node.firstChild;
11119		for (var i=0; i<index && node;)
11120		{	if (node._ne__repeat_elem === this._repeat_elem_id)
11121			{	i++;
11122			}
11123			node = node.nextSibling;
11124		}
11125		if (index < this.length)
11126		{	while (node && node._ne__repeat_elem!==this._repeat_elem_id)
11127			{	node = node.nextSibling;
11128			}
11129		}
11130		return node;
11131	}
11132
11133	swap(from_index, to_index)
11134	{	if (from_index>=0 && to_index>=0 && from_index!=to_index)
11135		{	var from_node = this._get_node_at_index(from_index);
11136			var to_node = this._get_node_at_index(to_index);
11137			if (from_node && to_node && from_node!=to_node)
11138			{	var from = this[from_index];
11139				var to = this[to_index];
11140				Array.prototype.splice.call(this, from_index, 1, to);
11141				Array.prototype.splice.call(this, to_index, 1, from);
11142				var from_node_prev = from_node.previousSibling;
11143				this._e_parent_node.insertBefore(from_node, to_node);
11144				this._e_parent_node.insertBefore(to_node, from_node_prev.nextSibling);
11145			}
11146		}
11147	}
11148
11149	set_length(new_length, case_props)
11150	{	if (new_length > this.length)
11151		{	this.splice(this.length, 0, new_length-this.length, case_props);
11152		}
11153		else if (new_length < this.length)
11154		{	this.splice(new_length, this.length-new_length, 0, case_props);
11155		}
11156	}
11157
11158	index_of_child(child)
11159	{	var prev = null;
11160		while (child && child!=this._e_parent_node)
11161		{	prev = child;
11162			child = child.parentNode;
11163		}
11164		if (!child || !prev)
11165		{	return -1;
11166		}
11167		for (var i=this.length-1; i>=0 && this[i].elem!=prev; i--);
11168		return i; // -1 if not found
11169	}
11170}
11171
11172function add_options(ns, elem, options)
11173{	var node_name = elem?.nodeName;
11174	if (node_name=='MENU' || node_name=='OL' || node_name=='UL')
11175	{	add_menu_options(ns, elem, options, node_name);
11176	}
11177	else if (node_name=='SELECT' || node_name=='DATALIST')
11178	{	add_select_options(ns, elem, options);
11179	}
11180	else
11181	{	return false;
11182	}
11183	return true;
11184}
11185function add_menu_options(ns, elem, options, node_name)
11186{	for (let i=0, i_end=options.length; i<i_end; i++)
11187	{	let option = options[i];
11188		let li = new_elem.call(ns, 'li', elem, {innerHTML: option.childNodes || [option.text]});
11189		if (option.options)
11190		{	let submenu = new_elem.call(ns, node_name, li);
11191			submenu.setAttribute('data-options', '1');
11192			add_menu_options(ns, submenu, option.options, node_name);
11193		}
11194	}
11195}
11196function add_select_options(ns, elem, options)
11197{	function new_option(o)
11198	{	if (typeof(o) != 'object')
11199		{	return new Option(o, o);
11200		}
11201		if (Array.isArray(o))
11202		{	return new Option(o[0], o[1]!=null ? o[1] : o[0]);
11203		}
11204		var text = o.text!=null ? o.text : o[0];
11205		var value = o.value!=null ? o.value : o[1];
11206		if (text==null && o.childNodes)
11207		{	text = '';
11208			for (let i=0, i_end=o.childNodes.length; i<i_end; i++)
11209			{	let node = o.childNodes[i];
11210				text += node.nodeType==XML_TEXT_NODE ? node.data : node.textContent!=null ? node.textContent : node.innerText;
11211			}
11212		}
11213		var o2 = new Option(text!=null ? text : value, value!=null ? value : text);
11214		if (o.selected)
11215		{	o2.selected = true;
11216		}
11217		return o2;
11218	}
11219	for (let i=0, i_end=options.length; i<i_end; i++)
11220	{	let option = options[i];
11221		if (option.options)
11222		{	let submenu = new_elem.call(ns, 'optgroup', elem, {label: new_option(option).text});
11223			add_select_options(ns, submenu, option.options);
11224		}
11225		else
11226		{	elem.appendChild(new_option(option));
11227		}
11228	}
11229}
11230function htmlspecialchars(text)
11231{	return (''+text).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
11232}
11233function css_unit_to_px(value_with_unit, inside_elem, style_prop)
11234{	if (value_with_unit == null)
11235	{	return undefined;
11236	}
11237	if (typeof(value_with_unit) == 'number')
11238	{	return value_with_unit;
11239	}
11240	value_with_unit = (value_with_unit+'').trim();
11241	var c_last = value_with_unit.charAt(value_with_unit.length - 1);
11242	if (c_last=='x' && value_with_unit.charAt(value_with_unit.length-2)=='p' || '0123456789.'.indexOf(c_last)!=-1)
11243	{	return parseFloat(value_with_unit);
11244	}
11245	var is_height = style_prop=='height' || style_prop=='top' || style_prop=='bottom';
11246	var body = (inside_elem?.ownerDocument || document).body;
11247	if (c_last == '%')
11248	{	value_with_unit = parseFloat(value_with_unit);
11249		let top_elem = inside_elem?.parentNode || body;
11250		let top_elem_style = get_comp_style(top_elem);
11251		let border_box = is_height ? top_elem.offsetHeight : top_elem.offsetWidth;
11252		let border = is_height ? parseFloat(top_elem_style.borderTopWidth) + parseFloat(top_elem_style.borderBottomWidth) : parseFloat(top_elem_style.borderLeftWidth) + parseFloat(top_elem_style.borderRightWidth);
11253		let padding = is_height ? parseFloat(top_elem_style.paddingTop) + parseFloat(top_elem_style.paddingBottom) : parseFloat(top_elem_style.paddingLeft) + parseFloat(top_elem_style.paddingRight);
11254		return (border_box - (border || 0) - (padding || 0)) * value_with_unit / 100;
11255	}
11256	var top_elem = inside_elem || multipurpose_div;
11257	if (inside_elem)
11258	{	if (!pcall(inside_elem.appendChild, inside_elem, [multipurpose_div]))
11259		{	return undefined;
11260		}
11261		// In order to determine dimensions i need that all parent elems will not be "display:none".
11262		// I will save original values for style.display ("none" may come from CSS) to var "modified", and set default "display" value for each given tag name.
11263		// If the top element is not document.body (i.e. the elem is detached from document), i will temporarily connect the elem to document.body.
11264		var modified = [];
11265		for (let e=inside_elem; e && e!=body; e=e.parentNode)
11266		{	top_elem = e;
11267			if (!e.offsetWidth && !e.offsetHeight && get_comp_style(e, 'display')=='none')
11268			{	modified.push({e:e, display:e.style.display});
11269				let tag = e.nodeName;
11270				if (!(tag in default_display_by_tags_cache))
11271				{	let temp_elem = new_elem(tag, body);
11272					default_display_by_tags_cache[tag] = get_comp_style(temp_elem, 'display', '');
11273					body.removeChild(temp_elem);
11274				}
11275				e.style.display = default_display_by_tags_cache[tag];
11276			}
11277		}
11278	}
11279	try
11280	{	multipurpose_div.style.width = is_height ? '1px' : value_with_unit;
11281		multipurpose_div.style.height = is_height ? value_with_unit: '1px';
11282		if (top_elem.parentNode == body)
11283		{	var px = is_height ? multipurpose_div.offsetHeight : multipurpose_div.offsetWidth;
11284		}
11285		else
11286		{	body.appendChild(top_elem);
11287			px = is_height ? multipurpose_div.offsetHeight : multipurpose_div.offsetWidth;
11288			body.removeChild(top_elem);
11289		}
11290	}
11291	catch (e)
11292	{	error(e);
11293	}
11294	if (inside_elem)
11295	{	inside_elem.removeChild(multipurpose_div);
11296	}
11297	if (modified)
11298	{	for (let i=0, i_end=modified.length; i<i_end; i++)
11299		{	let m = modified[i];
11300			m.e.style.display = m.display;
11301		}
11302	}
11303	return px - 0; // undefined -> NaN
11304}
11305function with_css_unit(value, default_value)
11306{	if (typeof(value) == 'number')
11307	{	value += 'px';
11308	}
11309	else if (value != null)
11310	{	value += '';
11311		if (!/[^\d\.]/.test(value))
11312		{	value += 'px';
11313		}
11314	}
11315	else
11316	{	value = default_value;
11317	}
11318	return value;
11319}
11320function get_v_scrollbar_width()
11321{	if (scrollbar_width == null)
11322	{	let outer = new_elem('div', document.body, {style: 'position:absolute; top:0; left:0; visibility:hidden; width:50px; height:50px; overflow:hidden'});
11323		let inner = new_elem('p', outer, {style: 'width:100%; height:100px'});
11324		let w1 = inner.offsetWidth;
11325		outer.style.overflow = 'scroll';
11326		let w2 = inner.offsetWidth;
11327		if (w1 == w2) w2 = outer.clientWidth;
11328		document.body.removeChild(outer);
11329		scrollbar_width = w1 - w2;
11330	}
11331	return scrollbar_width;
11332}
11333function str_trim_right(str)
11334{	return str.trimEnd ? str.trimEnd() : str.trimRight ? str.trimRight() : str.replace(/\s+$/, '');
11335}
11336function common_prefix(str_1, str_2)
11337{	var to = Math.min(str_1.length, str_2.length);
11338	var from = 0;
11339	while (from < to)
11340	{	let mid = from + ((to - from + 1) >> 1);
11341		if (str_1.slice(0, mid) == str_2.slice(0, mid))
11342		{	from = mid;
11343		}
11344		else
11345		{	to = mid - 1;
11346		}
11347	}
11348	return from;
11349}
11350function log()
11351{	if (!NEW_ELEM_NDEBUG)
11352	{	window.console && console.log.apply(console, arguments);
11353	}
11354}
11355function error()
11356{	if (!NEW_ELEM_NDEBUG)
11357	{	window.console && console.error.apply(console, arguments);
11358	}
11359}
11360function warn()
11361{	if (!NEW_ELEM_NDEBUG)
11362	{	window.console && console.warn.apply(console, arguments);
11363	}
11364}
11365function info()
11366{	if (!NEW_ELEM_NDEBUG)
11367	{	window.console && console.info.apply(console, arguments);
11368	}
11369}
11370class SetProp
11371{	constructor(elem, prop, is_style, value)
11372	{	this.elem = elem;
11373		this.prop = prop;
11374		this.is_style = is_style;
11375		this.value = value;
11376	}
11377
11378	apply_value(value)
11379	{	if (value == undefined)
11380		{	value = this.value;
11381		}
11382		if (!this.is_style)
11383		{	this.elem[this.prop] = value;
11384		}
11385		else if (this.prop.slice(0, 2) == '--')
11386		{	this.elem.style.setProperty(this.prop, value);
11387		}
11388		else
11389		{	this.elem.style[this.prop] = value;
11390		}
11391	}
11392
11393	stop_cur_transit() // if my elem has transition running, stop it
11394	{	var {elem, prop} = this;
11395		var cur_transit = elem._ne__transit?.[prop];
11396		if (cur_transit)
11397		{	cur_transit.stop(prop); // stop current animation if any
11398		}
11399	}
11400}
11401class SetPropFromTo extends SetProp
11402{	constructor(set_prop)
11403	{	super(set_prop.elem, set_prop.prop, set_prop.is_style, set_prop.value);
11404		this.at_step = null; // null - all steps, 0 - first, 1 - last
11405		this.format = set_prop.value;
11406		this.from_ns = null;
11407		this.to_ns = null;
11408		var that = this;
11409		set_from_to();
11410		function set_from_to()
11411		{	var p = that.prop;
11412			var elem = that.elem;
11413			that.at_step = null;
11414			var from=null, to=that.value;
11415			var cur_transit = elem._ne__transit?.[p];
11416			if (!that.is_style)
11417			{	from = elem[p];
11418			}
11419			else
11420			{	if (p == 'display')
11421				{	that.at_step = to=='none' ? 1 : 0; // defer hiding to the end of animation, but apply show immediately
11422					if (cur_transit) cur_transit.stop(p, true);
11423					return;
11424				}
11425				else if (p == 'visibility')
11426				{	that.at_step = to=='hidden' ? 1 : 0;
11427					return;
11428				}
11429				else
11430				{	var e_style = elem.style;
11431					from = e_style[p];
11432					if (from && (!cur_transit || !cur_transit.is_native_style) && try_set_from_to(from, to))
11433					{	return;
11434					}
11435					var e_comp_style = get_comp_style(elem);
11436					from = e_comp_style?.[p];
11437					if (!from)
11438					{	that.at_step = 0;
11439						if (is_node_linked(elem))
11440						{	log("Couldn't determine current style for "+p+" (needed for animation)", elem);
11441						}
11442						return;
11443					}
11444					if (cur_transit) cur_transit.stop(p);
11445					e_style[p] = to; // set new value to read it back computed
11446					to = e_comp_style[p];
11447					e_style[p] = from; // back to current
11448					if (typeof(from)=='string' && from.charAt(3)=='(' && typeof(to)=='string' && to.charAt(4)=='(' && from.slice(0, 3)=='rgb' && to.slice(0, 4)=='rgba')
11449					{	if (from.charAt(from.length - 1) == ')') from = 'rgba'+from.slice(3, -1)+', 1)';
11450					}
11451					else if (typeof(to)=='string' && to.charAt(3)=='(' && typeof(from)=='string' && from.charAt(4)=='(' && to.slice(0, 3)=='rgb' && from.slice(0, 4)=='rgba')
11452					{	if (to.charAt(to.length - 1) == ')') to = 'rgba'+to.slice(3, -1)+', 1)';
11453					}
11454				}
11455			}
11456			if (!try_set_from_to(from, to))
11457			{	that.at_step = 0;
11458				/*if (!NEW_ELEM_NDEBUG)
11459				{	if (from!=to && (/\b\d/.test(from) || /\b\d/.test(to)))
11460					{	log('props_transit for '+p+': "from" and "to" points are not similar or no numbers to animate: "'+from+'" and "'+to+'"', elem);
11461					}
11462				}*/
11463			}
11464		}
11465		function try_set_from_to(from, to)
11466		{	if (from!=null && from!=to)
11467			{	let from_ns = [];
11468				let to_ns = [];
11469				let from2 = (from+'').replace(RE_COMMA, ' , ').replace(RE_SPACES, ' ').replace(RE_NUMBER, n => {from_ns.push(parseFloat(n)); return '??'});
11470				let to2   = (to  +'').replace(RE_COMMA, ' , ').replace(RE_SPACES, ' ').replace(RE_NUMBER, n => {  to_ns.push(parseFloat(n)); return '??'});
11471				if (from_ns.length==to_ns.length && from_ns.length>0)
11472				{	let format = from2.split('??');
11473					let eq = from2 == to2;
11474					if (!eq)
11475					{	var to_format = to2.split('??');
11476						for (var i=0, i_end=format.length; i<i_end; i++)
11477						{	if (format[i+1])
11478							{	if (from_ns[i] == 0) // 0px is the same as 0em
11479								{	format[i+1] = to_format[i+1];
11480								}
11481								else if (to_ns[i] == 0)
11482								{	to_format[i+1] = format[i+1];
11483								}
11484							}
11485							if (format[i] != to_format[i])
11486							{	break;
11487							}
11488						}
11489						eq = i == i_end;
11490					}
11491					if (eq)
11492					{	that.format = format;
11493						that.from_ns = from_ns;
11494						that.to_ns = to_ns;
11495						that.at_step = null;
11496						return true;
11497					}
11498				}
11499			}
11500			that.at_step = 0;
11501			that.format = that.value;
11502			that.from_ns = null;
11503			that.to_ns = null;
11504			return false;
11505		}
11506	}
11507
11508	apply_value_for_step(n, timing_function_f, transit_only)
11509	{	if (this.at_step == null)
11510		{	var {from_ns, to_ns} = this;
11511			var text = '';
11512			for (var i=0, i_end=from_ns.length; i<i_end; i++)
11513			{	var v = n>=1 ? to_ns[i] : from_ns[i] + (to_ns[i] - from_ns[i]) * timing_function_f(n);
11514				if (v>1 || v<-1)
11515				{	v |= 0;
11516				}
11517				text += this.format[i] + v;
11518			}
11519			if (this.format[i_end])
11520			{	text += this.format[i_end];
11521			}
11522			this.apply_value(text);
11523		}
11524		else if (!transit_only && this.at_step == (n>=1 ? 1 : n))
11525		{	this.apply_value(this.format);
11526		}
11527	}
11528
11529	set_ne_state(stop, is_native_style)
11530	{	if (this.at_step==null || this.at_step==1)
11531		{	var {elem} = this;
11532			if (elem._ne__transit == null)
11533			{	elem._ne__transit = {};
11534			}
11535			else if ((!this.is_style || !is_native_style) && elem._ne__transit[this.prop] && elem._ne__transit[this.prop].stop!=stop)
11536			{	elem._ne__transit[this.prop].stop(this.prop, true); // stop previous transition
11537			}
11538			elem._ne__transit[this.prop] =
11539			{	stop,
11540				//is_style: this.is_style,
11541				is_native_style
11542			};
11543		}
11544	}
11545
11546	clear_ne_state(stop)
11547	{	var {elem, prop} = this;
11548		if (elem._ne__transit?.[prop]?.stop === stop)
11549		{	delete elem._ne__transit[prop];
11550		}
11551	}
11552}
11553function style_from_camel_case(str)
11554{	return str.replace(RE_FROM_CAMEL_1, '-$1').replace(RE_FROM_CAMEL_2, '$1-$2');
11555}
11556function style_to_camel_case(str)
11557{	if (str.charAt(0)=='-' && str.charAt(1)=='-')
11558	{	return str;
11559	}
11560	return str.replace(RE_TO_CAMEL, function(a, m) {return m.toUpperCase()});
11561}
11562function css_text_to_obj(text)
11563{	var obj = {};
11564	text.replace
11565	(	RE_CSS_RULE,
11566		(all, name, value) =>
11567		{	obj[style_to_camel_case(name)] = value.trim();
11568		}
11569	);
11570	return obj;
11571}
11572function props_transit(set_props, ontransitionend, ontransitionend_2, duration, fps, timing_function, emulate)
11573{	duration = duration>0 ? duration : 0.3; // secs
11574	fps = fps>0 && fps<=24 ? fps : 20;
11575	if (!timing_function)
11576	{	timing_function = 'ease-in';
11577	}
11578	var timing_function_f = null;
11579	switch (timing_function)
11580	{	default: timing_function_f = x => x*x; // default is exponent
11581	}
11582	for (let i=0, i_end=set_props.length; i<i_end; i++)
11583	{	if (set_props[i].is_style && set_props[i].prop=='display' && set_props[i].value!='none')
11584		{	set_props[i].stop_cur_transit();
11585			set_props[i].apply_value(); // apply immediately, so will be able to compute style
11586		}
11587	}
11588	var for_transit = [];
11589	var first_transit = null;
11590	for (let i=0, i_end=set_props.length; i<i_end; i++)
11591	{	let set_prop_from_to = new SetPropFromTo(set_props[i]);
11592		if (set_prop_from_to.at_step == null)
11593		{	first_transit = set_prop_from_to;
11594			if (!set_prop_from_to.is_style)
11595			{	emulate = true;
11596			}
11597		}
11598		for_transit.push(set_prop_from_to);
11599	}
11600	var transit_name, event_name;
11601	if (first_transit && !emulate)
11602	{	let e_style = first_transit.elem.style;
11603		if
11604		(	CSS_PREFIX && (transit_name=CSS_PREFIX+'Transition') && e_style[transit_name]!=null && (event_name=CSS_PREFIX_LC+'TransitionEnd') ||
11605			(transit_name='transition') && e_style[transit_name]!=null && (event_name='transitionend')
11606		)
11607		{	// ok, have native support
11608		}
11609		else
11610		{	emulate = true;
11611		}
11612	}
11613	function set_transit_style(for_transit)
11614	{	var prev_elem = null;
11615		for (var i=0, i_end=for_transit.length; i<i_end; i++)
11616		{	var elem = for_transit[i].elem;
11617			if (elem != prev_elem)
11618			{	var all = [];
11619				for (var p in elem._ne__transit)
11620				{	if (elem._ne__transit[p].is_native_style && p!='display' && p!='visibility')
11621					{	all.push(p+' '+duration+'s '+timing_function);
11622					}
11623				}
11624				elem.style[transit_name] = style_from_camel_case(all.join(', '));
11625				prev_elem = elem;
11626			}
11627		}
11628	}
11629	var call_ontransitionend = pair_funcs(ontransitionend, ontransitionend_2);
11630	// stop
11631	function stop(prop, for_reapply)
11632	{	var has_for_transit = false;
11633		for (var i=0, i_end=for_transit.length; i<i_end; i++)
11634		{	if (for_transit[i].prop == prop)
11635			{	if (!for_reapply)
11636				{	for_transit[i].clear_ne_state(stop);
11637					if (!emulate) set_transit_style(for_transit); // clear "transition" style
11638				}
11639				for_transit.splice(i--, 1);
11640				i_end--;
11641			}
11642			else if (for_transit[i].at_step == null)
11643			{	has_for_transit = true;
11644			}
11645		}
11646		if (!has_for_transit)
11647		{	for (var i=0; i<i_end; i++)
11648			{	for_transit[i].apply_value_for_step(1, timing_function_f);
11649			}
11650			for_transit.splice(0, i_end);
11651			if (call_ontransitionend) call_ontransitionend();
11652		}
11653	}
11654	// step
11655	var step = null;
11656	if (!first_transit)
11657	{	for (let i=0, i_end=for_transit.length; i<i_end; i++)
11658		{	for_transit[i].stop_cur_transit();
11659			for_transit[i].apply_value();
11660		}
11661		if (call_ontransitionend)
11662		{	deferred(call_ontransitionend);
11663		}
11664		return;
11665	}
11666	if (!emulate)
11667	{	// native
11668		step = function()
11669		{	var done = false;
11670			function func()
11671			{	if (!done)
11672				{	done = true;
11673					remove();
11674					for (let i=0, i_end=for_transit.length; i<i_end; i++)
11675					{	let set_prop = for_transit[i];
11676						set_prop.clear_ne_state(stop);
11677						set_transit_style(for_transit); // clear "transition" style
11678						if (!set_prop.elem._ne__transit || !set_prop.elem._ne__transit[set_prop.prop])
11679						{	set_prop.apply_value(); // set final value
11680						}
11681					}
11682					if (call_ontransitionend)
11683					{	call_ontransitionend();
11684					}
11685				}
11686			};
11687			var remove = add_event(null, first_transit.elem, event_name, func);
11688			deferred
11689			(	() =>
11690				{	set_transit_style(for_transit);
11691					deferred
11692					(	() =>
11693						{	for (let i=0, i_end=for_transit.length; i<i_end; i++)
11694							{	for_transit[i].apply_value_for_step(1, null, true);
11695							}
11696							deferred(func, duration*1000+20); // ensure ontransitionend call
11697						},
11698						navigator.userAgent.indexOf('Gecko/')!=-1 ? 80 : 0 // Gecko doesn't play animation if "transition" style had been set too recently
11699					);
11700				}
11701			);
11702		};
11703	}
11704	else
11705	{	// emulated
11706		let n = 0;
11707		let inc = 1 / (duration * fps);
11708		step = function()
11709		{	n += inc;
11710			for (var i=0, i_end=for_transit.length; i<i_end; i++)
11711			{	for_transit[i].apply_value_for_step(n, timing_function_f);
11712			}
11713			if (n >= 1)
11714			{	for (var i=0; i<i_end; i++)
11715				{	for_transit[i].clear_ne_state(stop);
11716				}
11717				if (call_ontransitionend) call_ontransitionend();
11718			}
11719			else
11720			{	deferred(step, 1000/fps); // 1sec / fps
11721			}
11722		};
11723	}
11724	// set state to elem._ne__transit
11725	for (let i=0, i_end=for_transit.length; i<i_end; i++)
11726	{	for_transit[i].set_ne_state(stop, !emulate);
11727	}
11728	step(); // run first step
11729	return stop;
11730}
11731function WinDoc(props, node)
11732{	this.doc = props?.document || props?.window?.document || node?.ownerDocument || document;
11733	this.win = props?.window || this.doc.defaultView || this.doc.parentWindow || window;
11734}
11735function get_comp_style(elem, prop, default_value)
11736{	var comp_style;
11737	if (elem?.nodeType === XML_ELEMENT_NODE)
11738	{	comp_style = elem.currentStyle;
11739		if (!comp_style)
11740		{	let doc = elem.ownerDocument;
11741			let win = doc?.defaultView || window;
11742			if (win.getComputedStyle)
11743			{	try
11744				{	comp_style = win.getComputedStyle(elem, null);
11745				}
11746				catch (e)
11747				{	error(e);
11748				}
11749			}
11750		}
11751	}
11752	return !prop ? comp_style || default_value || {} : comp_style?.[prop]!=null ? comp_style[prop] : default_value;
11753}
11754function add_event(win_doc, elem, onwhat, callback, capture, traditional)
11755{	// set callback2 to instrumented callback (based on http://www.quirksmode.org/js/events_properties.html)
11756	var callback2;
11757	if (IS_SAFARI)
11758	{	callback2 = function(e)
11759		{	if (e?.target?.nodeType == XML_TEXT_NODE)
11760			{	e.target = e.target.parentNode;
11761			}
11762			return callback.call(this, e);
11763		};
11764	}
11765	else
11766	{	callback2 = callback;
11767	}
11768	// add event listener
11769	if (traditional)
11770	{	elem[onwhat] = callback2;
11771	}
11772	else
11773	{	if (onwhat.slice(0, 2) == 'on')
11774		{	onwhat = onwhat.slice(2);
11775		}
11776		elem.addEventListener(onwhat, callback2, !!capture);
11777		return function()
11778		{	elem.removeEventListener(onwhat, callback2, !!capture);
11779		};
11780	}
11781}
11782function add_style(elem, style, set_props, idx)
11783{	if (!elem || !style) return;
11784	if (set_props && !idx)
11785	{	idx = {};
11786	}
11787	if (!elem.cloneNode)
11788	{	if (Array.isArray(elem))
11789		{	for (let e of elem)
11790			{	add_style(e, style, set_props, idx);
11791			}
11792		}
11793		return;
11794	}
11795	if (typeof(style) == 'string')
11796	{	style = css_text_to_obj(style);
11797	}
11798	if (Array.isArray(style))
11799	{	for (let s of style)
11800		{	add_style(elem, s, set_props, idx);
11801		}
11802	}
11803	else if (typeof(style) == 'object')
11804	{	if (style.length != null)
11805		{	// assume: elem.style object
11806			if (style != elem.style)
11807			{	for (let i=0, i_end=style.length; i<i_end; i++)
11808				{	add_style_prop(elem, style[i], style[style[i]], set_props, idx);
11809				}
11810			}
11811		}
11812		else
11813		{	for (let name of Object.keys(style))
11814			{	add_style_prop(elem, name, style[name], set_props, idx);
11815			}
11816		}
11817	}
11818}
11819function add_style_prop(elem, name, value, set_props, idx)
11820{	var prefixed = prefix_css_prop(name) || name;
11821	if (!set_props)
11822	{	elem._ne__transit?.[prefixed]?.stop(prefixed); // stop current animation if any
11823		let is_var = prefixed.slice(0, 2) == '--';
11824		if (!Array.isArray(value))
11825		{	try
11826			{	if (is_var)
11827				{	elem.style.setProperty(prefixed, value);
11828				}
11829				else
11830				{	elem.style[prefixed] = value;
11831				}
11832			}
11833			catch (e)
11834			{	error(e);
11835			}
11836		}
11837		else
11838		{	for (let i=0, i_end=value.length; i<i_end; i++)
11839			{	try
11840				{	if (is_var)
11841					{	elem.style.setProperty(prefixed, value[i]);
11842					}
11843					else
11844					{	elem.style[prefixed] = value[i];
11845					}
11846				}
11847				catch (e)
11848				{	error(e);
11849				}
11850			}
11851		}
11852	}
11853	else if (prefixed in idx)
11854	{	set_props[idx[prefixed]] = new SetProp(elem, prefixed, true, value);
11855	}
11856	else
11857	{	idx[prefixed] = set_props.length;
11858		set_props.push(new SetProp(elem, prefixed, true, value));
11859	}
11860}
11861function prefix_css_prop(prop)
11862{	if (CSS_PREFIX)
11863	{	let prefixed = CSS_PREFIX+prop.charAt(0).toUpperCase()+prop.slice(1);
11864		if (multipurpose_div?.style[prefixed] !== undefined)
11865		{	return prefixed;
11866		}
11867	}
11868	for (let from of Object.keys(CSS_DEFAULT_REPLACE))
11869	{	prop = prop.replace(from, CSS_DEFAULT_REPLACE[from]);
11870	}
11871	if (multipurpose_div?.style[prop] !== undefined)
11872	{	return prop;
11873	}
11874	return null;
11875}
11876function attr_add_to_hash(ns, elem, attrs, name, value, no_data, no_regular, process_data)
11877{	if (!(name in attrs))
11878	{	var prefix_len = name.slice(0, 5)!='data-' ? 0 : name.slice(5, 10)=='json-' || name.slice(5, 10)=='eval-' ? 10 : 5;
11878 // 'eval-' is needed for compatibility with old code
11879		if (prefix_len==0 ? no_regular : no_data)
11880		{	return;
11881		}
11882		if (process_data)
11883		{	if (prefix_len == 10)
11884			{	try
11885				{	value = JSON.parse(value);
11886				}
11887				catch (e)
11888				{	value = undefined;
11889					error(e);
11890				}
11891			}
11892			name = name.slice(prefix_len);
11893		}
11894		attrs[name] = value;
11895	}
11896}
11897/**	@param {Object} ns
11898	@param {Object} object to populate (usually empty). This object will be returned.
11899	@param {DOMElement} elem
11900	@param {bool} no_data
11901	@param {bool} no_regular
11902	@param {bool} process_data - unprefix data attributes, and JSON-decode if 'data-json-'
11903	**/
11904function get_attributes(ns, attrs, elem, no_data, no_regular, process_data)
11905{	for (let a=elem.attributes.length-1; a>=0; a--)
11906	{	let attr = elem.attributes.item(a);
11907		attr_add_to_hash(ns, elem, attrs, attr.name, attr.value, no_data, no_regular, process_data);
11908	}
11909	return attrs;
11910}
11911function add_attributes(elem, attrs, attrs_exclude, remove_with_data, merge_style)
11912{	if (attrs.nodeType == XML_ELEMENT_NODE)
11913	{	attrs = get_attributes(null, {}, attrs); // to copy without 'data-' apply get_attributes() by yourself
11914	}
11915	for (let a in attrs)
11916	{	if (!(a in attrs_exclude))
11917		{	try
11918			{	let value = attrs[a];
11919				if (value !== undefined)
11920				{	if (value === null)
11921					{	elem.removeAttribute(a);
11922						if (remove_with_data)
11923						{	elem.removeAttribute('data-'+a);
11924							elem.removeAttribute('data-json-'+a);
11925						}
11926					}
11927					else if (a=='style' && (merge_style || typeof(value)=='object'))
11928					{	add_style(elem, value);
11929					}
11930					else
11931					{	elem.setAttribute(a, value);
11932					}
11933				}
11934			}
11935			catch (e)
11936			{	error(e);
11937			}
11938		}
11939	}
11940}
11941/* ---------------------------------- TILL HERE --------------------------------*/
11942function is_loaded(win_doc)
11943{	let {doc} = win_doc;
11944	try
11945	{	// on some sites it's seen that "doc.readyState" is intentionally overridden to prevent detecting that document is loaded, but the original getter is still available in Document.prototype
11946		var readyState = Object.getOwnPropertyDescriptor(Document.prototype, 'readyState').get.call(doc);
11947	}
11948	catch
11949	{	readyState = doc.readyState;
11950	}
11951	return ('(uninitialized|loading)').indexOf(readyState) == -1;
11952}
11953function transit_set_begin(transit, ontransitionend)
11954{	var autoplay = ontransitionend || do_nothing;
11955	if (transit)
11956	{	autoplay = do_nothing;
11957		if (ontransitionend)
11958		{	transit.ontransitionend = pair_funcs(transit.ontransitionend, ontransitionend);
11959		}
11960		if (!('play' in transit))
11961		{	autoplay = function(callback)
11962			{	transit.play(callback);
11963				delete transit.play;
11964			};
11965		}
11966		if (!transit.play)
11967		{	var set_props = [];
11968			transit.play = function(callback_or_get_var)
11969			{	if (callback_or_get_var === true)
11970				{	return set_props;
11971				}
11972				pcall(props_transit, null, [set_props, transit.ontransitionend, callback_or_get_var, transit.duration, transit.fps, transit.timing_function, transit.emulate]);
11973			};
11974		}
11975	}
11976	return autoplay;
11977}
11978/**	@param {WinDoc} win_doc
11979	@param {Object} ns
11980	@param {DOMElement} elem
11981	@param {Object} props - Can contain magic props: "attributes", "onready".
11982	@param {number} reapply_level - if 0 - no reapply, 1 - yes, 2 - force.
11983	@param {Object} props_exclude - Only keys matter. E.g. to ignore props.size, props_exclude can be {size:true}.
11984	@param {string} elem_id - for internal use from new_elem().
11985	@param {Object} onready_queue - for internal use from new_elem().
11986	@param {Object} transit - for internal use from new_elem().
11987	@return {any} - If "elem" hasn't changed, returns undefined. Otherwise returns the changed "elem".
11988 **/
11989function add_props(win_doc, ns, elem, props, reapply_level, props_exclude, elem_id, onready_queue, transit, with_ontplevents, stack)
11990{	reapply_level |= 0;
11991	var has_changes = false;
11992	if (!elem || (!elem.cloneNode && elem.window!=elem)) // "elem" is not a DOMNode or is a Window
11993	{	if (typeof(props)!='object' || props==null || props.cloneNode) // if "props" is scalar or a Node
11994		{	if (!equals_shallow(elem, props))
11995			{	elem = props;
11996				has_changes = true;
11997			}
11998		}
11999		else // "props" is object, but not a Node
12000		{	if (typeof(elem)!='object' || elem==null)
12001			{	elem = Array.isArray(props) ? [] : {};
12002				has_changes = true;
12003			}
12004			if (!stack)
12005			{	stack = [elem];
12006			}
12007			let tos = stack.length;
12008			if (Array.isArray(props))
12009			{	if (elem.length != props.length)
12010				{	has_changes = true;
12011					if (elem.length > props.length)
12012					{	elem.length = props.length;
12013					}
12014				}
12015				for (let p=0, p_end=props.length; p<p_end; p++)
12016				{	let elem_p = elem[p];
12017					if (typeof(elem_p)!='object' || elem_p==null)
12018					{	let props_p = props[p];
12019						if (!has_changes)
12020						{	has_changes = !equals_shallow(elem_p, props_p);
12021						}
12022						elem[p] = props_p;
12023					}
12024					else if (stack.indexOf(elem_p) == -1)
12025					{	stack[tos] = elem_p;
12026						let result = add_props(win_doc, ns, elem_p, props[p], reapply_level, null, elem_id, onready_queue, transit, with_ontplevents, stack); // props_exclude excludes only on top level
12027						if (result !== undefined)
12028						{	elem[p] = result;
12029							has_changes = true;
12030						}
12031					}
12032				}
12033			}
12034			else
12035			{	if (!props_exclude) props_exclude = {}; // by default exclude Object.prototype
12036				for (let p in props)
12037				{	if (!(p in props_exclude))
12038					{	let elem_p = elem[p];
12039						if (typeof(elem_p)!='object' || elem_p==null)
12040						{	let props_p = props[p];
12041							if (!has_changes)
12042							{	has_changes = !equals_shallow(elem_p, props_p);
12043							}
12044							elem[p] = props_p;
12045						}
12046						else if (stack.indexOf(elem_p) == -1)
12047						{	stack[tos] = elem_p;
12048							let result = add_props(win_doc, ns, elem_p, props[p], reapply_level, null, elem_id, onready_queue, transit, with_ontplevents, stack); // props_exclude excludes only on top level
12049							if (result !== undefined)
12050							{	elem[p] = result;
12051								has_changes = true;
12052							}
12053						}
12054					}
12055				}
12056			}
12057			if (stack.length > tos)
12058			{	stack.pop();
12059			}
12060		}
12061	}
12062	else if (elem._reapply_template && reapply_level)
12063	{	elem = elem._reapply_template(props, false, reapply_level<2, transit);
12064	}
12065	else if (props?.cloneNode)
12066	{	if (!equals_shallow(elem, props))
12067		{	elem = props;
12068			has_changes = true;
12069		}
12070	}
12071	else if (typeof(props)=='object' && props!=null)
12072	{	if (!props_exclude) props_exclude = {}; // by default exclude Object.prototype
12073		let set_props = !transit ? null : transit.play?.(true) || [];
12074		for (let p in props)
12075		{	if (!(p in props_exclude) && p!='window' && p!='document' && p!='no_remove_ids')
12076			{	let value = props[p];
12077				if (value != null)
12078				{	has_changes = true;
12079					if (p==='options' && value && value.length!=null && add_options(ns, elem, value))
12080					{	continue;
12081					}
12082					if (p==='innerHTML' || p==='childNodes')
12083					{	let e;
12084						if (p === 'innerHTML')
12085						{	elem_empty(elem);
12086						}
12087						if (value.cloneNode)
12088						{	elem.appendChild(value);
12089						}
12090						else if (typeof(value)!='object' && !(IS_SAFARI && typeof(value)=='function' && typeof(value.item)=='function')) // on Safari 5.1.7 typeof(document.body.childNodes)=='function'
12091						{	value = value.toString();
12092							let level = 0;
12093							let pos = value.indexOf('>');
12094							if (pos != -1)
12095							{	let match = value.slice(0, pos).match(RE_FIRST_TAG);
12096								if (match)
12097								{	switch (match[1].toLowerCase())
12098									{	case 'th':
12099										case 'td':
12100											value = '<tr>'+value+'</tr>';
12101											level++;
12102										case 'tr':
12103											value = '<tbody>'+value+'</tbody>';
12104											level++;
12105										case 'caption':
12106										case 'colgroup':
12107										case 'thead':
12108										case 'tbody':
12109										case 'tfoot':
12110											value = '<table>'+value+'</table>';
12111											level++;
12112									}
12113								}
12114							}
12115							if (level==0 && p==='innerHTML')
12116							{	elem.innerHTML = value;
12117							}
12118							else
12119							{	let div = win_doc.doc.createElement('div');
12120								div.innerHTML = value;
12121								while (level-- > 0)
12122								{	e = get_root_child(div);
12123									if (!e) break;
12124									div = e;
12125								}
12126								let fc;
12127								while ((fc = div.firstChild))
12128								{	elem.appendChild(fc);
12129								}
12130							}
12131						}
12132						else // assume: value is array-like object
12133						{	let ins_before = null; // next sibling
12134							for (let i=value.length-1; i>=0; i--) // safe to run in reverse order, so even if "value" is a NodeList or a DocumentFragment that unlinks it's children while i move them to another parent, value[i] will always exist
12135							{	e = value[i];
12136								if (e!=null && e!=='')
12137								{	if (!e.cloneNode)
12138									{	e = win_doc.doc.createTextNode(e);
12139									}
12140									elem.insertBefore(e, ins_before);
12141									ins_before = e;
12141 // now e can != value[i] if value[i] was unlinked
12142								}
12143							}
12144						}
12145					}
12146					else if (p === 'style')
12147					{	add_style(elem, value, set_props);
12148					}
12149					else if (p === 'attributes')
12150					{	add_attributes(elem, value, {}, true);
12151					}
12152					else if (p==='className' && value=='')
12153					{	elem.className = '';
12154						elem.removeAttribute('class');
12155					}
12156					else
12157					{	if (p.slice(0, 2) == 'on')
12158						{	if (typeof(value) == 'string')
12159							{	value = str_to_func(value, ns);
12160							}
12161							if (typeof(value) == 'function')
12162							{	if (p.slice(0, 5) == 'ontpl')
12163								{	if (p === 'ontplready')
12164									{	if (elem!=win_doc.doc || is_loaded(win_doc))
12165										{	if (onready_queue)
12166											{	onready_queue.add_onready(value, elem);
12167											}
12168											else
12169											{	value.call(elem);
12170											}
12171										}
12172										else
12173										{	let func_elem = elem;
12174											function call_func()
12175											{	if (value != null)
12176												{	value.call(func_elem);
12177													value = null;
12178													func_elem = null;
12179												}
12180											}
12181											let reg;
12182											if (win_doc.doc.addEventListener || win_doc.win.addEventListener && win_doc.doc.attachEvent)
12183											{	reg = add_event(win_doc, win_doc.doc, 'DOMContentLoaded', () => {reg(); call_func()}, false);
12184											}
12185											else
12186											{	(reg = () => {is_loaded(win_doc) ? call_func() : deferred(reg, 400)})();
12187											}
12188											let prev_onload = win_doc.win.onload;
12189											win_doc.win.onload = function()
12190											{	if (prev_onload)
12191												{	prev_onload.call(this);
12192												}
12193												call_func();
12194												win_doc.win.onload = null;
12195											};
12196										}
12197										continue;
12198									}
12199									if (!with_ontplevents) continue;
12200								}
12201								add_event(win_doc, elem, p, value, false, true);
12202								continue;
12203							}
12204							else if (typeof(value.func) == 'function')
12205							{	(	function(elem, p, func, capture, listeners, till_node_linked)
12206									{	var func_to_add = func;
12207										if (till_node_linked)
12208										{	func_to_add = function(event)
12209											{	if (till_node_linked && !is_node_linked(till_node_linked, true))
12210												{	remove_full();
12211													remove = do_nothing;
12212													return;
12213												}
12214												return func.call(this, event);
12215											};
12216										}
12217										var remove = add_event(win_doc, elem, p, func_to_add, capture);
12218										var remove_full = remove;
12219										if (listeners)
12220										{	var my_listeners;
12221											if (!elem_id)
12222											{	my_listeners = listeners;
12223											}
12224											else
12225											{	if (!listeners[elem_id])
12226												{	listeners[elem_id] = {};
12227												}
12228												my_listeners = listeners[elem_id];
12229											}
12230											if (my_listeners[p])
12231											{	my_listeners[p].remove();
12232											}
12233											my_listeners[p] =
12234											{	remove: function()
12235												{	remove_full();
12236													remove = do_nothing;
12237												},
12238												add: function()
12239												{	remove_full();
12240													remove = add_event(win_doc, elem, p, func_to_add, capture);
12241												}
12242											};
12243										}
12244									}
12245								)(elem, p, value.func, value.capture, value.listeners, value.till_node_linked);
12246								continue;
12247							}
12248						}
12249						if (!set_props || elem[p]==value) // assign even if equal
12250						{	if (p.charAt(0) == '_')
12251							{	elem[p] = value;
12252							}
12253							else if (p in elem)
12254							{	try
12255								{	elem._ne__transit?.[p]?.stop(p); // stop current animation if any
12256									elem[p] = value;
12257									if (elem[p]!=value && p!='value')
12258									{	elem.setAttribute(p, value);
12259									}
12260								}
12261								catch (e)
12262								{	try {elem.setAttribute(p, value)} catch (e) {}
12263								}
12264							}
12265						}
12266						else
12267						{	set_props.push(new SetProp(elem, p, false, value));
12268						}
12269					}
12270				}
12271			}
12272		}
12273	}
12274	if (has_changes)
12275	{	return elem;
12276	}
12277}
12278/**	@return viewport dimensions
12279	**/
12280function get_vp_dim(win)
12281{	var doc = win.document;
12282	var body = doc.body;
12283	var html = doc.documentElement;
12284	var de = doc.compatMode == 'BackCompat' ? body : html; // scrolling/viewport element
12285	return {
12286		left: body?.scrollLeft || win.pageXOffset || win.scrollX || html?.scrollLeft || 0,
12287		top: body?.scrollTop || win.pageYOffset || win.scrollY || html?.scrollTop || 0,
12288		width: de?.clientWidth || win.innerWidth || 0,
12289		height: de?.clientHeight || win.innerHeight || 0 // body.clientHeight can be affected by styles on <body>, win.innerHeight includes scrollbar and is not affected by zooming
12290	};
12291}
12292function getBoundingClientRect(win, elem)
12293{	if (elem.nodeType == XML_TEXT_NODE)
12294	{	if (win.document.createRange)
12295		{	let range = win.document.createRange();
12296			if (range.selectNodeContents && range.getBoundingClientRect)
12297			{	range.selectNodeContents(elem);
12298				return range.getBoundingClientRect(); // can be null if elem is invisible
12299			}
12300		}
12301	}
12302	else if (win.getComputedStyle(elem).display == 'contents')
12303	{	let left, right, top, bottom;
12304		for (const child of elem.childNodes)
12305		{	let rect = getBoundingClientRect(win, child);
12306			if (rect && rect.width>0 && rect.height>0)
12307			{	if (left == null || rect.left < left) left = rect.left;
12308				if (right == null || rect.right > right) right = rect.right;
12309				if (top == null || rect.top < top) top = rect.top;
12310				if (bottom == null || rect.bottom > bottom) bottom = rect.bottom;
12311			}
12312		}
12313		if (left != null)
12314		{	return {left, right, top, bottom, width: right-left, height: bottom-top};
12315		}
12316	}
12317	else
12318	{	return elem.getBoundingClientRect?.();
12319	}
12320}
12321function get_dim(elem, box_sizing)
12322{	if (elem.window==elem && elem.document) // if is "window" object
12323	{	return get_vp_dim(elem);
12324	}
12325	var doc = elem.ownerDocument || document;
12326	var body = doc.body;
12327	var client_rect = getBoundingClientRect(doc.defaultView || window, elem);
12328	var is_text_node = elem.nodeType == XML_TEXT_NODE;
12329	if (client_rect)
12330	{	var vp_dim = get_vp_dim(doc.defaultView || window);
12331		var left = client_rect.left + vp_dim.left;
12332		var top = client_rect.top + vp_dim.top;
12333		var width = client_rect.width;
12334		var height = client_rect.height;
12335	}
12336	else
12337	{	if (is_text_node)
12338		{	return {left: 0, top: 0, width: 0, height: 0};
12339		}
12340		var e = elem;
12341		var e_topmost = e;
12342		left = e.offsetLeft;
12343		top = e.offsetTop;
12344		if (get_comp_style(e, 'position', 'static') == 'static')
12345		{	var offset_parent = e.offsetParent;
12346			while ((e = e.parentNode) && (e_topmost = e)!==body)
12347			{	if (e == offset_parent)
12348				{	left += e.offsetLeft;
12349					top += e.offsetTop;
12350					if (get_comp_style(e, 'position', 'static') != 'static') break;
12351					offset_parent = e.offsetParent;
12352				}
12353				left -= e.scrollLeft || 0;
12354				top -= e.scrollTop || 0;
12355			}
12356		}
12357		if (e && e!==body)
12358		{	while ((e = e.offsetParent) && (e_topmost = e)!==body)
12359			{	left += e.offsetLeft;
12360				top += e.offsetTop;
12361			}
12362		}
12363		if (e_topmost !== body)
12364		{	e = e_topmost;
12365			var s = get_comp_style(e);
12366			if (s.position == 'fixed')
12367			{	if (!e.offsetLeft && !e.offsetTop)
12368				{	var s_left = parseFloat(s.left);
12369					var s_top = parseFloat(s.top);
12370					var vp_dim;
12371					if (isNaN(s_left))
12372					{	var s_right = parseFloat(s.right);
12373						if (!isNaN(s_right))
12374						{	vp_dim = get_vp_dim(doc.defaultView || window);
12375							s_left = vp_dim.width - s_right - e.offsetWidth;
12376						}
12377					}
12378					if (isNaN(s_top))
12379					{	var s_bottom = parseFloat(s.bottom);
12380						if (!isNaN(s_bottom))
12381						{	vp_dim = vp_dim || get_vp_dim(doc.defaultView || window);
12382							s_top = vp_dim.height - s_bottom - e.offsetHeight;
12383						}
12384					}
12385					if (!isNaN(s_left)) left += s_left + (parseFloat(s.marginLeft) || 0); // offsetLeft and offsetTop don't count margin
12386					if (!isNaN(s_top)) top += s_top + (parseFloat(s.marginTop) || 0);
12387				}
12388				left += body.scrollLeft || doc.documentElement?.scrollLeft || 0;
12389				top += body.scrollTop || doc.documentElement?.scrollTop || 0;
12390			}
12391		}
12392		width = elem.offsetWidth;
12393		height = elem.offsetHeight;
12394	}
12395	// zoom
12396	for (e=elem; e && e!==body; e=e.parentNode)
12397	{	var zoom = parseFloat(get_comp_style(e, 'zoom'));
12398		if (zoom > 0)
12399		{	if (IS_MSIE) zoom /= 100;
12400			left *= zoom;
12401			top *= zoom;
12402			width *= zoom;
12403			height *= zoom;
12404		}
12405	}
12406	// box_sizing
12407	if (!is_text_node && (box_sizing=='padding-box' || box_sizing=='content-box'))
12408	{	var s = get_comp_style(elem);
12409		// left, width
12410		var add = parseFloat(s.borderLeftWidth);
12411		left += add || 0;
12412		add += parseFloat(s.borderRightWidth);
12413		width -= add || 0;
12414		// top, height
12415		add = parseFloat(s.borderTopWidth);
12416		top += add || 0;
12417		add += parseFloat(s.borderBottomWidth);
12418		height -= add || 0;
12419		if (box_sizing == 'content-box')
12420		{	add = parseFloat(s.paddingLeft);
12421			left += add || 0;
12422			add += parseFloat(s.paddingRight);
12423			width -= add || 0;
12424			// top, height
12425			add = parseFloat(s.paddingTop);
12426			top += add || 0;
12427			add += parseFloat(s.paddingBottom);
12428			height -= add || 0;
12429		}
12430	}
12431	return {left: left, top: top, width: width, height: height};
12432}
12433function get_set_dim_style(ns, elem, dim, box_sizing)
12434{	var style = {};
12435	var top = parseFloat(dim.top);
12436	var bottom = parseFloat(dim.bottom);
12437	var left = parseFloat(dim.left);
12438	var right = parseFloat(dim.right);
12439	var width = parseFloat(dim.width);
12440	var height = parseFloat(dim.height);
12441	var s = get_comp_style(elem);
12442	if (!box_sizing)
12443	{	box_sizing = 'border-box';
12444	}
12445	var target_box_sizing = s.boxSizing || 'content-box';
12446	if (!isNaN(top) || !isNaN(bottom) || !isNaN(left) || !isNaN(right))
12447	{	if (box_sizing=='padding-box' || box_sizing=='content-box')
12448		{	top -= parseFloat(s.borderTopWidth) || 0;
12449			bottom -= parseFloat(s.borderBottomWidth) || 0;
12450			left -= parseFloat(s.borderLeftWidth) || 0;
12451			right -= parseFloat(s.borderRightWidth) || 0;
12452			if (box_sizing == 'content-box')
12453			{	top -= parseFloat(s.paddingTop) || 0;
12454				bottom -= parseFloat(s.paddingBottom) || 0;
12455				left -= parseFloat(s.paddingLeft) || 0;
12456				right -= parseFloat(s.paddingRight) || 0;
12457			}
12458		}
12459		var e = elem;
12460		var body = (e.ownerDocument || document).body;
12461		var vp_dim = null;
12462		while ((e = e.parentNode) && e!==body)
12463		{	var cs = get_comp_style(e);
12464			if (cs.position && cs.position!='static' || cs.perspective && cs.perspective!='none')
12465			{	var dim = get_dim(e);
12466				top -= dim.top - e.scrollTop;
12467				left -= dim.left - e.scrollLeft;
12468				if (!isNaN(bottom) || !isNaN(right))
12469				{	if (!vp_dim)
12470					{	vp_dim = get_dim(new WinDoc(elem).win);
12471					}
12472					bottom += (dim.top + dim.height) - (vp_dim.height);
12473					right += (dim.left + dim.width) - (vp_dim.width); // right: not tested
12474				}
12475				break;
12476			}
12477		}
12478		if (!isNaN(top))
12479		{	style.top = top + 'px';
12480			style.bottom = 'auto';
12481		}
12482		else if (!isNaN(bottom))
12483		{	style.top = 'auto';
12484			style.bottom = bottom + 'px';
12485		}
12486		if (!isNaN(left))
12487		{	style.left = left + 'px';
12488			style.right = 'auto';
12489		}
12490		else if (!isNaN(right))
12491		{	style.left = 'auto';
12492			style.right = right + 'px';
12493		}
12494	}
12495	if (!isNaN(width) || !isNaN(height))
12496	{	if (target_box_sizing != box_sizing)
12497		{	var padding_height = (parseFloat(s.paddingTop) + parseFloat(s.paddingBottom)) || 0;
12498			var padding_width = (parseFloat(s.paddingLeft) + parseFloat(s.paddingRight)) || 0;
12499			var border_height = (parseFloat(s.borderTopWidth) + parseFloat(s.borderBottomWidth)) || 0;
12500			var border_width = (parseFloat(s.borderLeftWidth) + parseFloat(s.borderRightWidth)) || 0;
12501			if (box_sizing=='border-box' || box_sizing=='padding-box')
12502			{	height -= padding_height;
12503				width -= padding_width;
12504				if (box_sizing == 'border-box')
12505				{	height -= border_height;
12506					width -= border_width;
12507				}
12508			}
12509			if (target_box_sizing != 'content-box')
12510			{	height += padding_height;
12511				width += padding_width;
12512				if (target_box_sizing != 'padding-box')
12513				{	height += border_height;
12514					width += border_width;
12515				}
12516			}
12517		}
12518		if (width < 0) width = 0;
12519		if (height < 0) height = 0;
12520		if (!isNaN(width)) style.width = width+'px';
12521		if (!isNaN(height)) style.height = height+'px';
12522	}
12523	return style;
12524}
12525function set_dim(ns, elem, dim, transit, box_sizing)
12526{	if (elem.window==elem && elem.document) // if is "window" object
12527	{	var doc = elem.document;
12528		var {body} = doc;
12529		var html = doc.documentElement;
12530		if (body && html)
12531		{	var props = {};
12532			if (dim.top != null) props.scrollTop = dim.top;
12533			if (dim.left != null) props.scrollLeft = dim.left;
12534			var transit_set_end = transit_set_begin(transit);
12535			if (!html.scrollTop)
12536			{	add_props(new WinDoc(elem), ns, body, props, 0, null, null, null, transit);
12537			}
12538			if (!body.scrollTop)
12539			{	add_props(new WinDoc(elem), ns, html, props, 0, null, null, null, transit);
12540			}
12541			transit_set_end();
12542		}
12543		return;
12544	}
12545	var style = get_set_dim_style(ns, elem, dim, box_sizing);
12546	new NewElem(ns, elem).add_style(style, transit);
12547}
12548function scroll_into_view(ns, elem, transit, scroll_into_view_options)
12549{	if (!transit)
12550	{	elem.scrollIntoView();
12551	}
12552	else
12553	{	var to_end = scroll_into_view_options ? scroll_into_view_options.block=='end' : false;
12554		var win_doc = new WinDoc(elem);
12555		var e = elem;
12556		var left = e.offsetLeft;
12557		var top = e.offsetTop;
12558		var width = e.offsetWidth;
12559		var height = e.offsetHeight;
12560		var body = document.body;
12561		var p, offset;
12562		var transit_set_end = transit_set_begin(transit);
12563		while ((p = e.offsetParent))
12564		{	var client_width = p.clientWidth;
12565			var client_height = p!=body ? p.clientHeight : Math.min(document.documentElement.clientHeight, window.innerHeight);
12566			if (client_width<p.scrollWidth-1 && ((offset=left-p.scrollLeft)<0 || (offset>0 && width>client_width) || (offset=left+width-p.scrollLeft-client_width)>0))
12567			{	add_props(win_doc, ns, p, {scrollLeft: p.scrollLeft+offset}, 0, null, null, null, transit);
12568			}
12569			if (client_height<p.scrollHeight-1 && ((offset=top-p.scrollTop)<0 || (offset>0 && height>client_height && !to_end) || (offset=top+height-p.scrollTop-client_height)>0))
12570			{	add_props(win_doc, ns, p, {scrollTop: p.scrollTop+offset}, 0, null, null, null, transit);
12571			}
12572			e = p;
12573			left += e.offsetLeft;
12574			top += e.offsetTop;
12575		}
12576		transit_set_end();
12577	}
12578}
12579var deferred_q = null;
12580var deferred_q_for = {};
12581function deferred(func, miliseconds)
12582{	if (func)
12583	{	miliseconds |= 0;
12584		if (miliseconds <= 0)
12585		{	var q = deferred_q;
12586			if (!q)
12587			{	q = deferred_q = [];
12588				setTimeout
12589				(	function()
12590					{	deferred_q = null;
12591						for (var i=0, i_end=q.length; i<i_end; i++)
12592						{	pcall(q[i]);
12593						}
12594					},
12595					0
12596				);
12597			}
12598		}
12599		else
12600		{	var q = deferred_q_for[miliseconds];
12601			if (!q)
12602			{	q = deferred_q_for[miliseconds] = [];
12603				deferred
12604				(	function()
12605					{	delete deferred_q_for[miliseconds];
12606					}
12607				);
12608				setTimeout
12609				(	function()
12610					{	for (var i=0, i_end=q.length; i<i_end; i++)
12611						{	pcall(q[i]);
12612						}
12613					},
12614					miliseconds
12615				);
12616			}
12617		}
12618		var i = q.length;
12619		q[i] = func;
12620		return function()
12621		{	q[i] = null;
12622		};
12623	}
12624	else
12625	{	return do_nothing;
12626	}
12627}
12628function has_something(who, what_name, what_value)
12629{	if (Array.isArray(who))
12630	{	for (var i=0, i_end=who.length; i<i_end; i++)
12631		{	var n = has_something(who[i], what_name, what_value);
12632			if (n) return n;
12633		}
12634	}
12635	else if (Array.isArray(what_value))
12636	{	for (var i=0, i_end=what_value.length; i<i_end; i++)
12637		{	var n = has_something(who, what_name, what_value[i]);
12638			if (n) return n;
12639		}
12640	}
12641	else if (what_name == 'parentNode')
12642	{	for (var p=who.parentNode||who.host, n=1; p; p=p.parentNode||p.host, n++)
12643		{	if (p == what_value) return n;
12644		}
12645	}
12646	else
12647	{	for (var p=who[what_name], n=1; p; p=p[what_name], n++)
12648		{	if (p == what_value) return n;
12649		}
12650	}
12651	return 0;
12652}
12653function select_child_elems(elem, tag_names)
12654{	if (!tag_names)
12655	{	return elem?.children;
12656	}
12657	var children = [];
12658	tag_names = ','+(tag_names.join ? tag_names.join(',') : tag_names.replace(RE_SPACES, '')).toUpperCase()+',';
12659	for (var n=elem && elem.firstElementChild; n; n=n.nextElementSibling)
12660	{	if (tag_names.indexOf(','+n.nodeName+',') != -1)
12661		{	children.push(n);
12662		}
12663	}
12664	return children;
12665}
12666function select_parent_elem(elem, tag_names)
12667{	tag_names = ','+(tag_names.join ? tag_names.join(',') : tag_names).toUpperCase()+',';
12668	elem = elem?.parentNode;
12669	while (elem && tag_names.indexOf(','+elem.nodeName+',')==-1)
12670	{	elem = elem.parentNode;
12671	}
12672	return elem;
12673}
12674function elem_trim(elem)
12675{	while (elem.firstChild?.nodeType==XML_COMMENT_NODE || elem.firstChild?.nodeType==XML_TEXT_NODE && elem.firstChild.data.trim()==='')
12676	{	elem.removeChild(elem.firstChild);
12677	}
12678	while (elem.lastChild?.nodeType==XML_COMMENT_NODE || elem.lastChild?.nodeType==XML_TEXT_NODE && elem.lastChild.data.trim()==='')
12679	{	elem.removeChild(elem.lastChild);
12680	}
12681}
12682function elem_empty(elem)
12683{	var e;
12684	while ((e = elem.firstChild)) elem.removeChild(e);
12685}
12686function strip_text_nodes(elem)
12687{	var e, e2, i=0;
12688	var child_nodes = elem.childNodes;
12689	while ((e = child_nodes[i]))
12690	{	if (e.nodeType == XML_TEXT_NODE)
12691		{	var e2;
12692			while ((e2 = child_nodes[i+1]) && e2.nodeType==XML_TEXT_NODE)
12693			{	e.data += e2.data;
12694				elem.removeChild(e2);
12695			}
12696			var data = e.data.trim();
12697			if (data)
12698			{	e.data = data;
12699			}
12700			else
12701			{	elem.removeChild(e);
12702				i--;
12703			}
12704		}
12705		i++;
12706	}
12707}
12708function get_root_child(elem)
12709{	var root_elem = elem.firstChild;
12710	while (root_elem?.nodeType == XML_TEXT_NODE)
12711	{	if (RE_HAS_NONSPACE.test(root_elem.data)) return null;
12712		root_elem = root_elem.nextSibling;
12713	}
12714	if (!root_elem || root_elem.nodeType!=XML_ELEMENT_NODE)
12715	{	return null;
12716	}
12717	var after_root_elem = root_elem.nextSibling;
12718	while (after_root_elem?.nodeType == XML_TEXT_NODE)
12719	{	if (RE_HAS_NONSPACE.test(after_root_elem.data)) return null;
12720		after_root_elem = after_root_elem.nextSibling;
12721	}
12722	return after_root_elem ? null : root_elem;
12723}
12724function pcall(func, owner, args, errors, err_msg_prefix)
12725{	if (func)
12726	{	try
12727		{	return func.apply(owner || window, args);
12728		}
12729		catch (e)
12730		{	if (err_msg_prefix)
12731			{	error(err_msg_prefix, e);
12732			}
12733			else
12734			{	error(e);
12735			}
12736			if (errors) errors.push(e);
12737		}
12738	}
12739}
12740function add_to_parent(elem, parent_node, allow_circular)
12741{	if (parent_node)
12742	{	let pn = parent_node.cloneNode && parent_node;
12743		let ps, ns;
12744		if (!pn)
12745		{	ps = parent_node.previousSibling;
12746			ns = parent_node.nextSibling;
12747			pn = parent_node.parentNode || ps?.parentNode || ns?.parentNode;
12748			ns = ns || ps?.nextSibling;
12749		}
12750		if (pn)
12751		{	if (elem.cloneNode)
12752			{	if (allow_circular && has_something(pn, 'parentNode', elem))
12753				{	var elem_pn = elem.parentNode;
12754					var elem_ns = elem.nextSibling;
12755					pn.parentNode.removeChild(pn);
12756				}
12757				pn.insertBefore(elem, ns || null);
12758				if (elem_pn)
12759				{	elem_pn.insertBefore(pn, elem_ns);
12760				}
12761				return elem;
12762			}
12763			let result = Array.prototype.slice.call(elem);
12764			for (var i=0, i_end=result.length; i<i_end; i++)
12765			{	pn.insertBefore(result[i], ns || null);
12766			}
12767			return result;
12768		}
12769	}
12770}
12771function clone_node(node, parent_node, ontplunload, ontplload, no_templates_on_toplevel)
12772{	if (node._clone_template && !no_templates_on_toplevel)
12773	{	if (ontplunload) ontplunload();
12774		return node._clone_template(parent_node, ontplload);
12775	}
12776	var clone = node.cloneNode(false);
12777	if (node.nodeType == XML_ELEMENT_NODE)
12778	{	for (var n=node.firstChild; n; n=n.nextSibling)
12779		{	clone.appendChild(clone_node(n, null, ontplunload, ontplload));
12780		}
12781	}
12782	if (parent_node)
12783	{	add_to_parent(clone, parent_node);
12784	}
12785	return clone;
12786}
12787function is_node_linked(node, respect_switch_elem)
12788{	while (node)
12789	{	if (node.nodeType == XML_DOCUMENT_NODE)
12790		{	return true;
12791		}
12792		node = node.parentNode || node.host || respect_switch_elem && node._ne__switch_elem;
12793	}
12794}
12795function case_props_fill_values(case_props, prop_names, case_props_taken)
12796{	if (typeof(prop_names) == 'function')
12797	{	return prop_names(case_props);
12798	}
12799	else if (typeof(prop_names) != 'object')
12800	{	let value = case_props[prop_names];
12801		if (case_props_taken.hasOwnProperty(prop_names))
12802		{	value = clone_obj(value);
12803		}
12804		else
12805		{	case_props_taken[prop_names] = true;
12806		}
12807		return value;
12808	}
12809	else if (Array.isArray(prop_names))
12810	{	var obj = [];
12811		for (let p=0, p_end=prop_names.length; p<p_end; p++)
12812		{	obj[p] = case_props_fill_values(case_props, prop_names[p], case_props_taken);
12813		}
12814	}
12815	else
12816	{	var obj = {};
12817		for (let p of Object.getOwnPropertyNames(prop_names))
12818		{	obj[p] = case_props_fill_values(case_props, prop_names[p], case_props_taken);
12819		}
12820	}
12821	return obj;
12822}
12823function case_to_tpls_props(case_props_decl, case_props, add_to_props)
12824{	if (case_props_decl && case_props)
12825	{	for (let t of Object.getOwnPropertyNames(case_props_decl))
12826		{	if (t != 'hash')
12827			{	let tpl_props = case_props_decl[t];
12828				for (let p of Object.getOwnPropertyNames(tpl_props))
12829				{	let prop_name = tpl_props[p];
12830					let prop_value = case_props_fill_values(case_props, prop_name, {});
12831					if (prop_value !== undefined)
12832					{	if (!add_to_props) add_to_props = {};
12833						if (!add_to_props[t]) add_to_props[t] = {};
12834						add_to_props[t][p] = prop_value;
12835					}
12836				}
12837			}
12838		}
12839	}
12840	return add_to_props;
12841}
12842function dispatch_event(elem, event_hash, plus_call_func)
12843{	var win_doc = new WinDoc(null, elem);
12844	var win = win_doc.win;
12845	var doc = win_doc.doc;
12846	if (event_hash.bubbles === undefined)
12847	{	event_hash.bubbles = true;
12848	}
12849	var type = event_hash.type + '';
12850	if (type.slice(0, 2) == 'on')
12851	{	type = type.slice(2);
12852	}
12853	if (win.MouseEvent)
12854	{	try
12855		{	var event_obj = new win[EVENTS_CTORS[type] || 'Event'](type, event_hash);
12856			if (event_obj.type != type)
12857			{	event_obj = null;
12858			}
12859		}
12860		catch (e)
12861		{
12862		}
12863	}
12864	if (!event_obj)
12865	{	if (doc.createEvent)
12866		{	event_obj = doc.createEvent("HTMLEvents");
12867			event_obj.initEvent(type, true, true);
12868		}
12869		else if (doc.createEventObject)
12870		{	event_obj = doc.createEventObject();
12871			event_obj.eventType = type;
12872		}
12873	}
12874	for (var p in event_hash)
12875	{	try
12876		{	if (p != 'type')
12877			{	event_obj[p] = event_hash[p];
12878			}
12879		}
12880		catch (e)
12881		{	// ok
12882		}
12883	}
12884	try
12885	{	if (plus_call_func && plus_call_func.call(elem, event_obj)===false)
12886		{	return false;
12887		}
12888		if (elem.dispatchEvent)
12889		{	return elem.dispatchEvent(event_obj);
12890		}
12891		return elem.fireEvent(event_hash.type, event_obj);
12892	}
12893	catch (e)
12894	{	error(e);
12895	}
12896}
12897function by_id_find_get_deep(child, repeat, parents, i, id, arr)
12898{	if (i == -1)
12899	{	var elem = repeat[id];
12900		if (elem)
12901		{	for (var r_id in repeat)
12902			{	if (repeat[r_id] == child) // eventually found the right by_id scope
12903				{	return elem;
12904				}
12905			}
12906		}
12907		arr.push(elem);
12908	}
12909	else
12910	{	var r = repeat[parents[i]];
12911		for (var j=0, j_len=r.length; j<j_len; j++)
12912		{	var elem = by_id_find_get_deep(child, r[j].by_id, parents, i-1, id, arr);
12913			if (elem) return elem;
12914		}
12915	}
12916}
12917function by_id_find_add(all, found)
12918{	if (Array.isArray(found))
12919	{	for (let f of found)
12920		{	by_id_find_add(all, f);
12921		}
12922	}
12923	else
12924	{	all.push(found);
12925	}
12926}
12927function by_id_find_id(by_id, id, found)
12928{	for (var i in by_id)
12929	{	var elem = by_id[i];
12930		if (i == id)
12931		{	if (elem)
12932			{	found.push(elem);
12933				return; // no need to add other elements with the same id, if there are such ones in inner scope
12934			}
12935		}
12936		else
12937		{	if (elem?.index_of_child && !elem.cloneNode)
12938			{	for (var j=0, j_end=elem.length; j<j_end; j++)
12939				{	by_id_find_id(elem[j].by_id, id, found);
12940				}
12941			}
12942		}
12943	}
12944}
12945function by_id_find(child, id)
12946{	var elem = this[id];
12947	if (elem) return elem;
12948	var parents = this._parent_names?.[id];
12949	var arr = [];
12950	if (!parents)
12951	{	by_id_find_id(this, id, arr);
12952	}
12953	else
12954	{	var repeat = this;
12955		for (var i=parents.length-1; i>=0; i--)
12956		{	var r = repeat[parents[i]];
12957			var index = r.index_of_child(child);
12958			if (index == -1) break;
12959			repeat = r[index].by_id;
12960		}
12961		if (i == -1) return repeat[id];
12962		elem = by_id_find_get_deep(child, repeat, parents, i, id, arr);
12963		if (elem) return elem;
12964	}
12965	return arr;
12966}
12967function by_id_find_all(child, names)
12968{	var all = [];
12969	for (var i=0, il=names.length; i<il; i++)
12970	{	var found = this._find(child, names[i]);
12971		if (found) by_id_find_add(all, found);
12972	}
12973	return all;
12974}
12975function by_id_export_from_repeated_child(id, parent_by_id, by_id_child)
12976{	if (!parent_by_id._parent_names) parent_by_id._parent_names = {};
12977	var by_id_parent_names = parent_by_id._parent_names;
12978	var ignore = {_find:1, _find_all:1, _parent_names:1, parent:1};
12979	for (var n in by_id_child)
12980	{	if (!(n in by_id_parent_names) && !(n in ignore))
12981		{	by_id_parent_names[n] = [id];
12982		}
12983	}
12984	var by_id_2_parent_names = by_id_child._parent_names;
12985	if (by_id_2_parent_names)
12986	{	for (var n in by_id_2_parent_names)
12987		{	if (!(n in by_id_parent_names))
12988			{	by_id_parent_names[n] = [].concat(by_id_2_parent_names[n]);
12989				by_id_parent_names[n].push(id);
12990			}
12991		}
12992	}
12993	if (!parent_by_id._find)
12994	{	parent_by_id._find = by_id_find;
12995		parent_by_id._find_all = by_id_find_all;
12996	}
12997}
12998function str_to_func(code, ns, by_id)
12999{	if (!code) return null;
13000	if (typeof(code) == 'function') return code;
13001	var func = new Function('event', 'ns', 'by_id', 'arguments', code);
13002	return function(event)
13003	{	return func.call(this, event, ns, by_id, arguments);
13004	};
13005}
13006function pair_funcs(func_1, func_2, conditional_caller)
13007{	if (!func_2)
13008	{	return func_1;
13009	}
13010	else if (!func_1)
13011	{	return func_2;
13012	}
13013	else
13014	{	return function()
13015		{	var errors = [];
13016			var result = pcall(func_1, this, arguments, errors);
13017			if (!conditional_caller || conditional_caller.call(this, result, errors[0]))
13018			{	result = func_2.apply(this, arguments);
13019			}
13020			if (errors.length) throw errors[0];
13021			return result;
13022		};
13023	}
13024}
13025function add_props_shallow(obj, obj_2)
13026{	if (typeof(obj)=='object' && typeof(obj_2)=='object' && obj!=null && obj_2!=null)
13027	{	Object.assign(obj, obj_2);
13028	}
13029}
13030function NewElemEvent(type, target, target_name)
13031{	this.type = type;
13032	this.target = target;
13033	this.target_name = target_name;
13034}
13035function serialize_attrs(props, exclude, cls)
13036{	var attrs = '';
13037	var attrs_no_data_prefix = {style: 1};
13038	var ONW = /^on[a-z]/i;
13039	for (var p in props)
13040	{	var value = props[p];
13041		if (!(p in exclude) && value!==undefined && typeof(value)!='function')
13042		{	if (p=='class' || p=='className')
13043			{	cls = cls ? cls+' '+value : value;
13044			}
13045			else if (typeof(value) != 'object')
13046			{	attrs += ((p in attrs_no_data_prefix) ? ' ' : ' data-')+htmlspecialchars(p)+'="'+htmlspecialchars(value===false ? '0' : value===true ? '1' : value)+'"';
13047			}
13048			else if (p == 'style')
13049			{	var sexclude = {length: 1}; // plus Object.prototype
13050				var style = '';
13051				for (var sp in value)
13052				{	var svalue = value[sp];
13053					if (!(sp in sexclude) && isNaN(parseInt(sp)) && (svalue && typeof(svalue)!='function' || svalue===0))
13054					{	style += '; '+style_from_camel_case(sp)+':'+svalue;
13055					}
13056				}
13057				if (style)
13058				{	attrs += ' style="'+htmlspecialchars(style.slice(2))+'"';
13059				}
13060			}
13061			else if (value!==null || !ONW.test(p))
13062			{	attrs += ' data-json-'+htmlspecialchars(p)+'="'+htmlspecialchars(JSON.stringify(value))+'"';
13063			}
13064		}
13065	}
13066	if (cls)
13067	{	attrs = ' class="'+htmlspecialchars(cls)+'"'+attrs;
13068	}
13069	return attrs;
13070}
13071function serialize_tpl_options(options, indent)
13072{	var str = '';
13073	for (var i=0, i_end=options.length; i<i_end; i++)
13074	{	var option = options[i];
13075		str += indent+'<li'+serialize_attrs(option, {options:1, text:1, childNodes:1})+'>';
13076		if (option.options)
13077		{	str += '\n'+indent+'\t';
13078		}
13079		var child_nodes = option.childNodes;
13080		if (!child_nodes)
13081		{	str += htmlspecialchars(option.text);
13082		}
13083		else
13084		{	for (var j=0, j_end=child_nodes.length; j<j_end; j++)
13085			{	var n = child_nodes[j];
13086				var node_type = n.nodeType;
13087				if (node_type == XML_ELEMENT_NODE)
13088				{	str += n.outerHTML;
13089				}
13090				else if (node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE)
13091				{	str += htmlspecialchars(n.data);
13092				}
13093			}
13094		}
13095		if (option.options)
13096		{	str += '\n'+indent+'\t<menu data-options="1">\n'+serialize_tpl_options(option.options, indent+'\t\t')+indent+'\t</menu>\n'+indent;
13097		}
13098		str += '</li>\n';
13099	}
13100	return str;
13101}
13102/**	Use `createElement` method from `Document.prototype`, because some sites override `document.createElement` to reject creating `<script>` elements so enforcing privacy consent
13103 **/
13104function documentCreateElement(doc, tag_name)
13105{	return doc.constructor.prototype.createElement.call(doc, tag_name);
13106}
13107function new_elem(node_name, parent_node, props, by_id)
13108{	if (typeof(parent_node) == 'function')
13109	{	return traverse(node_name, node_name.parentNode, parent_node);
13110	}
13111	var pos = typeof(node_name)!='string' ? -2 : node_name.indexOf('<');
13112	if (pos==-2 && parent_node===undefined && props===undefined && by_id===un
13112defined)
13113	{	// Wrapper around elem/object
13114		return new NewElem(this, node_name);
13115	}
13116	var win_doc = new WinDoc
13117	(	props,
13118		node_name.cloneNode ? node_name : parent_node && (parent_node.cloneNode ? parent_node : parent_node.previousSibling || parent_node.nextSibling)
13119	);
13120	if (pos == -1)
13121	{	// Create element
13122		var {doc} = win_doc;
13123		var elem = documentCreateElement(doc, node_name || 'strike');
13124		if (!node_name)
13125		{	elem.style.cssText = 'text-decoration:none; display:inline; position:static; z-index:auto; float:none; clear:none; visibility:visible; border:none; color:black; background:none; width:auto; height:auto; top:auto; right:auto; bottom:auto; left:auto; margin:0; padding:0; text-align:left';
13126		}
13127		add_to_parent(elem, parent_node);
13128		add_props(win_doc, this, elem, props);
13129	}
13130	else
13131	{	// Create/parse DOM tree
13132		let onready_queue = new OnReadyQ;
13133		elem = new_elem_3(this, node_name, parent_node, props || {}, by_id || {}, win_doc, onready_queue);
13134		onready_queue.leave();
13135	}
13136	return elem;
13137}
13138function CallBalancer(initial, onminustozero, oninc, onzerotominus)
13139{	onminustozero = str_to_func(onminustozero);
13140	let balance = initial|0;
13141	let is_ready = false;
13142	let inc = function()
13143	{	oninc?.apply(this, arguments);
13144		if (++balance == 0)
13145		{	is_ready = true;
13146			return onminustozero?.call(this);
13147		}
13148	};
13149	this.dec = function()
13150	{	if (balance-- == 0)
13151		{	onzerotominus?.call(this);
13152		}
13153		return inc;
13154	};
13155	this.inc = inc;
13156	this.ready = function()
13157	{	return new Promise
13158		(	y =>
13159			{	if (is_ready)
13160				{	y();
13161				}
13162				else
13163				{	onminustozero = pair_funcs(y, onminustozero);
13164				}
13165			}
13166		);
13167	};
13168}
13169/**	@param {Object} props_for_win_doc - Window/Document specification. This can be a "window" object, or another object that contains {window:...} or {document:...}, or both.
13170 **/
13171function get_selection_ranges(props_for_win_doc)
13172{	var win_doc = new WinDoc(props_for_win_doc);
13173	var sel = win_doc.win.getSelection();
13174	if (!sel.anchorNode)
13175	{	return [
13176			{	startContainer: win_doc.doc.body,
13177				startOffset: 0,
13178				endContainer: win_doc.doc.body,
13179				endOffset: 0,
13180				collapsed: true
13181			}
13182		];
13183	}
13184	if (!sel.getRangeAt)
13185	{	return [
13186			{	startContainer: sel.anchorNode || sel.baseNode,
13187				startOffset: sel.anchorOffset!=null ? sel.anchorOffset : sel.baseOffset,
13188				endContainer: sel.focusNode || sel.extentNode,
13189				endOffset: sel.focusOffset!=null ? sel.focusOffset : sel.extentOffset,
13190				collapsed: sel.isCollapsed
13191			}
13192		]
13193	}
13194	var ranges = [];
13195	for (var i=0, i_end=sel.rangeCount; i<i_end; i++)
13196	{	var range = sel.getRangeAt(i);
13197		ranges[i] =
13198		{	startContainer: range.startContainer,
13199			startOffset: range.startOffset,
13200			endContainer: range.endContainer,
13201			endOffset: range.endOffset,
13202			collapsed: range.collapsed
13203		};
13204	}
13205	return ranges;
13206}
13207function set_selection_ranges(ranges)
13208{	var last_underlying_range = null;
13209	if (ranges)
13210	{	var win_doc = new WinDoc(null, ranges[0].startContainer || ranges[0].endContainer);
13211		var cur_sel = win_doc.win.getSelection();
13212		if (cur_sel)
13213		{	cur_sel.removeAllRanges();
13214			for (var i=0, i_end=ranges.length; i<i_end; i++)
13215			{	last_underlying_range = new_range(ranges[i]);
13216				cur_sel.addRange(last_underlying_range);
13217			}
13218		}
13219	}
13220	return last_underlying_range;
13221}
13222function new_range(range)
13223{	var startContainer = range.startContainer || range.endContainer;
13224	var startOffset = range.startContainer ? range.startOffset : range.endOffset;
13225	var endContainer = range.endContainer || range.startContainer;
13226	var endOffset = range.endContainer ? range.endOffset : range.startOffset;
13227	var win_doc = new WinDoc(null, startContainer || endContainer);
13228	var underlying_range = win_doc.doc.createRange();
13229	underlying_range.setStart(startContainer, startOffset);
13230	underlying_range.setEnd(endContainer, endOffset);
13231	return underlying_range;
13232}
13233function clone_obj(obj, max_levels, clone_nodes)
13234{	max_levels |= 0;
13235	if (max_levels == 0)
13236	{	max_levels = 0x7FFFFFFF;
13237	}
13238	if (window.Map)
13239	{	return sub(obj, max_levels, clone_nodes, new Map);
13240	}
13241	else
13242	{	return sub_array(obj, max_levels, clone_nodes, [], []);
13243	}
13244
13245	function sub(obj, max_levels, clone_nodes, cloned_map)
13246	{	if (max_levels==0 || typeof(obj)!='object' || obj==null)
13247		{	return obj;
13248		}
13249		if (obj.cloneNode)
13250		{	return clone_nodes ? obj.cloneNode(true) : obj;
13251		}
13252		let v = cloned_map.get(obj);
13253		if (v !== undefined)
13254		{	return v;
13255		}
13256		let done = false;
13257		try
13258		{	var cobj = new obj.constructor;
13259		}
13260		catch (e)
13261		{	cobj = {};
13262			done = clone_style(cobj, obj);
13263		}
13264		cloned_map.set(obj, cobj);
13265		if (!done)
13266		{	for (let key of Object.getOwnPropertyNames(obj))
13267			{	let value = obj[key];
13268				cobj[key] = !value || value.window==value ? value : sub(value, max_levels-1, clone_nodes, cloned_map);
13269			}
13270		}
13271		return cobj;
13272	}
13273
13274	function sub_array(obj, max_levels, clone_nodes, parents, parent_clones)
13275	{	if (max_levels==0 || typeof(obj)!='object' || obj==null)
13276		{	return obj;
13277		}
13278		if (obj.cloneNode)
13279		{	return clone_nodes ? obj.cloneNode(true) : obj;
13280		}
13281		let pos = parents.indexOf(obj);
13282		if (pos != -1) // if circular reference
13283		{	return parent_clones[pos];
13284		}
13285		let done = false;
13286		try
13287		{	var cobj = new obj.constructor;
13288		}
13289		catch (e)
13290		{	cobj = {};
13291			done = clone_style(cobj, obj);
13292		}
13293		parents[parents.length] = obj;
13294		parent_clones[parent_clones.length] = cobj;
13295		if (!done)
13296		{	for (let key of Object.getOwnPropertyNames(obj))
13297			{	let value = obj[key];
13298				cobj[key] = !value || value.window==value ? value : sub_array(value, max_levels-1, clone_nodes, parents, parent_clones);
13299			}
13300		}
13301		parents.length--;
13302		parent_clones.length--;
13303		return cobj;
13304	}
13305
13306	function clone_style(cobj, obj)
13307	{	if ('cssText' in obj && 'listStyleImage' in obj && typeof(obj.length)=='number')
13308		{	for (let i=0, i_end=obj.length; i<i_end; i++)
13309			{	cobj[obj[i]] = obj[obj[i]];
13310			}
13311			return true;
13312		}
13313		return false;
13314	}
13315}
13316function equals(a, b, locales, options)
13317{	return subequals(a, b, locales, options, false, false, [], []);
13318
13319	function subequals(a, b, locales, options, a_circular, b_circular, a_parents, b_parents)
13320	{	if (equals_shallow(a, b, locales, options))
13321		{	return true;
13322		}
13323		// compare functions by reference only (already compared above)
13324		// compare nonobjects in regular way (already compared above)
13325		// compare objects by reference (already compared above), and recursively (see below)
13326		if (typeof(a)=='object' && typeof(b)=='object' && a!=null && b!=null)
13327		{	// compare objects recursively
13328			let a_arr = Array.isArray(a);
13329			if (a_arr || Array.isArray(b)) // if any of them is array
13330			{	if (!a_arr || !Array.isArray(b) || a.length!==b.length) // unless both are arrays of equal length
13331				{	return false;
13332				}
13333			}
13334			else if (a.cloneNode || b.cloneNode)
13335			{	return false;
13336			}
13337			if (!a_circular) a_circular = push_parent(a_parents, a);
13338			if (!b_circular) b_circular = push_parent(b_parents, b);
13339			if (!a_circular || !b_circular)
13340			{	if (a_arr)
13341				{	for (let i=a.length-1; i>=0; i--)
13342					{	if (!subequals(a[i], b[i], locales, options, a_circular, b_circular, a_parents, b_parents))
13343						{	return false;
13344						}
13345					}
13346				}
13347				else
13348				{	for (let i in a)
13349					{	if (a[i]!=null || b[i]!=null) // if any of them is not null/undefined/missing
13350						{	if (!subequals(a[i], b[i], locales, options, a_circular, b_circular, a_parents, b_parents))
13351							{	return false;
13352							}
13353						}
13354					}
13355					for (let i in b)
13356					{	if (b[i]!=null && !(i in a))
13357						{	return false;
13358						}
13359					}
13360				}
13361				if (!a_circular) a_parents.pop();
13362				if (!b_circular) b_parents.pop();
13363			}
13364			return true;
13365		}
13366		return false;
13367	}
13368
13369	function push_parent(parents, obj)
13370	{	parents.push(obj);
13371		if (parents.length%4 == 0) // check for circular references each 4th level
13372		{	for (let i=parents.length-4, i_end=i+4; i<i_end; i++)
13373			{	for (let j=0; j<i; j++)
13374				{	if (parents[j] === parents[i])
13375					{	parents.pop();
13376						return true; // inside circular
13377					}
13378				}
13379			}
13380		}
13381		return false;
13382	}
13383}
13384function equals_shallow(a, b, locales, options)
13385{	if (typeof(a) == 'string')
13386	{	return a.localeCompare(b, locales, options) == 0;
13387	}
13388	if (typeof(b) == 'string')
13389	{	return b.localeCompare(a, locales, options) == 0;
13390	}
13391	if (a == b)
13392	{	return true;
13393	}
13394	if (typeof(a)=='number' && typeof(b)=='number' && isNaN(a) && isNaN(b))
13395	{	return true;
13396	}
13397	return false;
13398}
13399
13400class NewElem
13401{	constructor(ns, elem)
13402	{	this.ns = ns;
13403		this.elem = elem;
13404	}
13405
13406	add_props(props, transit)
13407	{	var transit_set_end = transit_set_begin(transit);
13408		var win_doc = new WinDoc(props, this.elem);
13409		var result = add_props(win_doc, this.ns, this.elem, props, 1, null, null, null, transit);
13410		var n_changes = 0;
13411		if (result !== undefined)
13412		{	this.elem = result;
13413			n_changes = 1;
13414		}
13415		transit_set_end();
13416		return {elem: this.elem, n_changes};
13417	}
13418
13419	add_style(style, transit)
13420	{	if (!transit)
13421		{	add_style(this.elem, style);
13422		}
13423		else
13424		{	var transit_set_end = transit_set_begin(transit);
13425			var win_doc = new WinDoc(null, this.elem);
13426			add_props(win_doc, this.ns, this.elem, {style}, 0, null, null, null, transit);
13427			transit_set_end();
13428		}
13429	}
13430
13431	style(prop, default_value)
13432	{	return get_comp_style(this.elem, prop, default_value);
13433	}
13434
13435	get_data_attr(arg_name, default_value, props)
13436	{	return get_data_attr(this.ns, this.elem, arg_name, default_value, false, props);
13437	}
13438
13439	take_data_attr(arg_name, default_value, props)
13440	{	return get_data_attr(this.ns, this.elem, arg_name, default_value, true, props);
13441	}
13442
13443	get_attributes(no_data, no_regular, process_data)
13444	{	return get_attributes(this.ns, {}, this.elem, no_data, no_regular, process_data);
13445	}
13446
13447	dispatch_event(event_hash, plus_call_func)
13448	{	return dispatch_event(this.elem, event_hash, plus_call_func);
13449	}
13450
13451	dim(box_sizing)
13452	{	return get_dim(this.elem, box_sizing);
13453	}
13454
13455	set_dim(dim, transit, box_sizing)
13456	{	set_dim(this.ns, this.elem, dim, transit, box_sizing);
13457	}
13458
13459	get_set_dim_style(dim, box_sizing)
13460	{	return get_set_dim_style(this.ns, this.elem, dim, box_sizing);
13461	}
13462
13463	scroll_into_view(transit, scroll_into_view_options)
13464	{	scroll_into_view(this.ns, this.elem, transit, scroll_into_view_options);
13465	}
13466
13467	css_unit_to_px(value_with_unit, style_prop)
13468	{	return css_unit_to_px(value_with_unit, this.elem, style_prop);
13469	}
13470
13471	has_parent(elem)
13472	{	return !this.elem || !elem ? 0 : has_something(this.elem, 'parentNode', elem);
13473	}
13474
13475	has_sibling(elem)
13476	{	return !this.elem || !elem ? 0 : -has_something(this.elem, 'previousSibling', elem) || has_something(this.elem, 'nextSibling', elem);
13477	}
13478	select_child_elems(tag_names)
13479	{	return select_child_elems(this.elem, tag_names);
13480	}
13481
13482	select_parent_elem(tag_name)
13483	{	return select_parent_elem(this.elem, tag_name);
13484	}
13485
13486	elem_trim()
13487	{	elem_trim(this.elem);
13488	}
13489
13490	elem_empty()
13491	{	elem_empty(this.elem);
13492	}
13493
13494	strip_text_nodes()
13495	{	strip_text_nodes(this.elem);
13496	}
13497
13498	get_root_child()
13499	{	return get_root_child(this.elem);
13500	}
13501
13502	/**	Can add a DOMNode or an array (or a magic collection) of DOMNode to a parent element.
13503		Can be used in place of appendChild() and insertBefore().
13504		Works in cases when appendChild() throws "The new child element contains the parent".
13505		@param {any} - Parent can be a DOMElement, or an object like {previousSibling: node}. In the latter case, after adding, the previous sibling of the added child will be the specified node. The object can contain one or several of: previousSibling, nextSibling, parentNode.
13506		@return the child. If it was a magic collection, it will be converted to array. Will return null if parent node is not specified.
13507	 **/
13508	add_to_parent(parent_node)
13509	{	return add_to_parent(this.elem, parent_node, true);
13510	}
13511
13512	is_node_linked(respect_switch_elem)
13513	{	return is_node_linked(this.elem, respect_switch_elem);
13514	}
13515
13516	clone_node(parent_node, ontplload)
13517	{	if (!ontplload)
13518		{	return clone_node(this.elem, parent_node);
13519		}
13520		var call_balancer = new CallBalancer
13521		(	-1,
13522			function()
13523			{	ontplload.call(node);
13524			}
13525		);
13526		var node = clone_node(this.elem, parent_node, null, call_balancer.dec());
13527		call_balancer.inc();
13528		return node;
13529	}
13530}
13531
13532var multipurpose_div = new_elem('div', null, {style: 'left:0;top:0;position:absolute;visibility:hidden'});
13533
13534function new_elem_bootstrap_to(target)
13535{	target.IS_MSIE = IS_MSIE;
13536	target.IS_SAFARI = IS_SAFARI;
13537	target.IS_OPERA = IS_OPERA;
13538	target.IS_CHROME = IS_CHROME;
13539	target.IS_FIREFOX = IS_FIREFOX;
13540	target.new_elem_bootstrap_to = new_elem_bootstrap_to;
13541	target.new_elem = new_elem;
13542	target.log = log;
13543	target.error = error;
13544	target.warn = warn;
13545	target.info = info;
13546	target.declare_template = declare_template;
13547	target.common_prefix = common_prefix;
13548	target.clone_obj = clone_obj;
13549	target.equals = equals;
13550	target.pcall = pcall;
13551	target.pair_funcs = pair_funcs;
13552	target.CallBalancer = CallBalancer;
13553	target.transit_set_begin = transit_set_begin;
13554	target.with_css_unit = with_css_unit;
13555	target.get_v_scrollbar_width = get_v_scrollbar_width;
13556	target.get_selection_ranges = get_selection_ranges;
13557	target.set_selection_ranges = set_selection_ranges;
13558	target.new_range = new_range;
13559	target.css_text_to_obj = css_text_to_obj;
13560	target.deferred = deferred;
13561	if (!target.templates)
13562	{	target.templates = {};
13563	}
13564	if (Array.isArray(target.template_sources))
13565	{	for (let {name, wrap_id, html, arg_names, func} of target.template_sources)
13566		{	target.declare_template(name, wrap_id, html, arg_names, func);
13567		}
13568		delete target.template_sources;
13569	}
13570}
13571new_elem_bootstrap_to(window[NEW_ELEM_NS_NAME]);NS['declare_template']
13572(	'pager_popup',
13573	null,
13574	null,
13575	['ns', 'elem'],
13576	function(ns, elem)
13577	{	let {children} = elem;
13578		let find = ['previous', 'next', 'preview'];
13579		let found = {};
13580		for (let i=0, i_end=children.length; i<i_end; i++)
13581		{	let e = children[i];
13582			if (find.indexOf(e.dataset.type) != -1)
13583			{	found[e.dataset.type] = children[i];
13584				e.removeAttribute('data-type');
13585			}
13586		}
13587		let {previous, next, preview} = found;
13588		elem.textContent = '';
13589		if (!previous || !next || !preview)
13590		{	throw new Error('Invalid HTML structure');
13591		}
13592		let previous_item = preview.children[0];
13593		let next_item = preview.children[1];
13594		preview.textContent = '';
13595		let popup_by_id = {p: previous, n: next};
13596		let need_preprocess = true;
13597		function copy_a_href(item, arrow)
13598		{	let arrow_a = arrow.nodeName=='A' ? arrow : arrow.getElementsByTagName('a')[0];
13599			if (arrow_a)
13600			{	let aa = item.getElementsByTagName('a');
13601				for (let i=0; i<aa.length; i++)
13602				{	let a = aa[i];
13603					if (a.href && a.href.slice(0, 4).toLowerCase()=='http')
13604					{	arrow_a.href = a.href;
13605					}
13606				}
13607			}
13608		}
13609		if (NEW_ELEM_NS_NAME != 'stgui')
13610		{	if (previous_item)
13611			{	document.body.appendChild(previous);
13612				copy_a_href(previous_item, previous);
13613			}
13614			if (next)
13615			{	document.body.appendChild(next);
13616				copy_a_href(next_item, next);
13617			}
13618		}
13619		else
13620		{	previous.style.position = 'absolute';
13621			next.style.position = 'absolute';
13622			elem.style.minHeight = '300px';
13623			elem.appendChild(previous);
13624			elem.appendChild(next);
13625			preview.addEventListener('click', event => {event.stopPropagation(); event.preventDefault()}, true);
13626		}
13627		preview.id = 'd';
13628		preview.className = '$popup';
13629		ns.new_elem
13630		(	preview,
13631			document.body,
13632			{	d:
13633				{	id: null,
13634					trigger_id: 'p,n',
13635					trigger_onmouseover: true,
13636					design: 'hint',
13637					ontplbeforeshow(event, case_props, near_node)
13638					{	preview.textContent = '';
13639						let item = near_node==previous ? previous_item : next_item;
13640						if (!item)
13641						{	return false;
13642						}
13643						preview.appendChild(item);
13644						if (need_preprocess)
13645						{	need_preprocess = false;
13646							ns.new_elem(preview, null);
13647						}
13648					}
13649				}
13650			},
13651			popup_by_id
13652		);
13653	}
13654);
13655
13656// popup_global_parent
13657function _new_elem_tmp_5(ns)
13658	{	const ESCAPE = 27;
13659
13660		let global_parent = null;
13661		let listeners = {};
13662		let pp = {};
13663		let pp_names = [];
13664		let dummies = {};
13665
13666		function has_parent(e, parent, level)
13667		{	if (!level)
13668			{	level = 0;
13669			}
13670			while (e)
13671			{	if (e == parent) return true;
13672				if (level<10 && typeof(e._get_near_node)=='function')
13673				{	if (has_parent(e._get_near_node(), parent, level+1))
13674					{	return true;
13675					}
13676				}
13677				e = e.parentNode;
13678			}
13679		}
13680
13681		function install(document, no_supress_css_where, tracker_params)
13682		{	let {body} = document;
13683			if (!global_parent)
13684			{	global_parent = ns.new_elem
13685				(	'<div style="position:static; height:auto; width:auto; background-color:transparent; border:none"></div>',
13686					body
13687				);
13688				ns.new_elem(window).add_props
13689				(	{	onkeydown:
13690						{	func(event)
13691							{	if (event.keyCode == ESCAPE)
13692								{	for (let i=pp_names.length-1; i>=0; i--)
13693									{	let pp_elem = pp[pp_names[i]];
13694										let {children} = pp_elem;
13695										for (let i=children.length-1; i>=0; i--)
13696										{	let c = children[i];
13697											if (typeof(c._hide)=='function' && c._get_is_modal?.())
13698											{	c._hide();
13699												event.stopPropagation();
13700												event.preventDefault();
13701											}
13702										}
13703									}
13704								}
13705							},
13706							listeners
13707						}
13708					}
13709				);
13710			}
13711			if (global_parent.nextSibling)
13712			{	body.appendChild(global_parent); // move to end, so the highest possible z-index will be higher than the highest possible z-index of other elements
13713			}
13714			let hash = !no_supress_css_where ? '' : ns.strhash(no_supress_css_where).toString(36);
13715			if (!pp[hash])
13716			{	let p_by_id = {};
13717				if (NEW_ELEM_NS_NAME == '_S_T')
13718				{	var data_not = !no_supress_css_where || no_supress_css_where=='*' ? '' : ` data-not="${ns.htmlspecialchars(no_supress_css_where)}"`;
13719				}
13720				else
13721				{	var data_not = !no_supress_css_where || no_supress_css_where=='*' ? ` data-not="${ns.htmlspecialchars(CONF__USER_AGENT_CSS_NOT)}"` : ` data-not="${ns.htmlspecialchars(no_supress_css_where)}"`;
13722				}
13723				ns.new_elem
13724				(	'<div id="id">' +
13725					(	NEW_ELEM_NS_NAME=='_S_T' && tracker_params ?
13726							'<div id="pp"></div>'
13727						: NEW_ELEM_NS_NAME == '_S_T' ? // for compatibility with old actions that didn't pass tracker_params
13728							(no_supress_css_where=='*' ? '' : '<style class="$user_agent_css S-3T S-4T S-5T S-6T S-7T S-8T" parent_selector="#az2tr8'+hash+'"'+data_not+'></style>') +
13729							'<div id="pp" class="S-3T S-4T S-5T S-6T S-7T S-8T S-9T"></div>'
13730						:
13731							(no_supress_css_where=='*' ? '' : `<style class="$user_agent_css ${CONF__PARTIAL_BOOTSTRAP_CLASSES_WEAK}" parent_selector="#az2tr8${hash}"${data_not}></style>`) +
13732							'<style class="$style '+CONF__PARTIAL_BOOTSTRAP_CLASSES+'" parent_selector="#az2tr8'+hash+'"></style>' +
13733							'<style class="$partial_bootstrap_css '+CONF__PARTIAL_BOOTSTRAP_CLASSES+'" parent_selector="#az2tr8'+hash+'"></style>' +
13734							'<div id="pp" class="'+CONF__PARTIAL_BOOTSTRAP_CLASSES+'"></div>'
13735					) +
13736					'</div>',
13737					global_parent,
13738					{	id:
13739						{	id: 'az2tr8'+hash,
13740							style: no_supress_css_where=='*' || NEW_ELEM_NS_NAME=='_S_T' && tracker_params ? 'position:static' : 'position:static; font-style:normal; font-weight:normal; font-variant:normal; text-decoration:none; text-align:left; text-align:start; line-height:normal; visibility:visible; white-space:normal; cursor:default'
13741						},
13742						pp:
13743						{	style: 'position:static'
13744						}
13745					},
13746					p_by_id
13747				);
13748				if (NEW_ELEM_NS_NAME=='_S_T' && tracker_params)
13749				{	ns.uacss.supress_for_elem(p_by_id.pp, tracker_params.action_id, tracker_params.with_uacss);
13750				}
13751				pp[hash] = p_by_id.pp;
13752				pp_names[pp_names.length] = hash;
13753			}
13754			return pp[hash];
13755		}
13756
13757		function uninstall(force)
13758		{	for (let i=pp_names.length-1; i>=0; i--)
13759			{	let pp_elem = pp[pp_names[i]];
13760				let {children} = pp_elem;
13761				if (force || !children.length)
13762				{	let p = pp_elem.parentNode;
13763					if (p)
13764					{	let p_p = p.parentNode;
13765						if (p_p)
13766						{	p_p.removeChild(p);
13767						}
13768					}
13769					delete pp[pp_names[i]];
13770					pp_names.splice(i, 1);
13771				}
13772			}
13773			if (global_parent && (force || !pp_names.length))
13774			{	let p = global_parent.parentNode;
13775				if (p)
13776				{	p.removeChild(global_parent);
13777				}
13778				global_parent = null;
13779				listeners.onkeydown.remove();
13780				listeners.onkeydown = null;
13781			}
13782		}
13783
13784		function close_popups_opened_inside_this_popup(elem)
13785		{	for (let name of Object.keys(pp))
13786			{	for (let popup of pp[name].children)
13787				{	let near_node = popup._get_near_node?.();
13788					if (near_node)
13789					{	while (near_node)
13790						{	if (near_node._by_id?.root == elem)
13791							{	popup._hide_by_trigger();
13792								break;
13793							}
13794							near_node = near_node.parentNode;
13795						}
13796					}
13797				}
13798			}
13799		}
13800
13801		var popup_global_parent =
13802		{	_add_elem(document, elem, no_supress_css_where, tracker_params)
13803			{	var pp_elem = install(document, no_supress_css_where, tracker_params);
13804				pp_elem.appendChild(elem);
13805				if (elem._get_name)
13806				{	delete dummies[elem._get_name()];
13807				}
13808			},
13809
13810			_remove_elem(document, elem)
13811			{	var p = elem.parentNode;
13812				for (var i=pp_names.length-1; i>=0; i--)
13813				{	var pp_elem = pp[pp_names[i]];
13814					if (p == pp_elem)
13815					{	p.removeChild(elem);
13816					}
13817				}
13818				uninstall();
13819			},
13820
13821			_close_popups_opened_inside_this_popup : close_popups_opened_inside_this_popup,
13822
13823			_get_by_name(name)
13824			{	var elems = [];
13825				for (var i=pp_names.length-1; i>=0; i--)
13826				{	var pp_elem = pp[pp_names[i]];
13827					for (var p=pp_elem.firstChild; p; p=p.nextSibling)
13828					{	if (p._get_name && p._get_name()==name)
13829						{	elems[elems.length] = p;
13830						}
13831					}
13832				}
13833				if (dummies.hasOwnProperty(name))
13834				{	elems[elems.length] = dummies[name];
13835				}
13836				return elems;
13837			},
13838
13839			_add_dummy(name, elem)
13840			{	dummies[name] = elem;
13841			},
13842
13843			has_parent
13844		};
13845
13846		return popup_global_parent;
13847	}
13848
13849NS['declare_template']
13850(	'popup',
13851	null,
13852	'<div id="root" class="$switch-elem"> '+
13853		'<div data-case="on" data-prepared="1" id="container"> '+
13854			'<div id="topbar" style="display:flex; justify-content:space-between; align-items:flex-start; width:100%; margin:0; padding:0; border-bottom:1px solid #eef3fb"> '+
13855				'<div id="title" style="padding:0.6em; font-size:130%; font-weight:600"></div> '+
13856				'<div style="flex-grow:1000; text-align:end; padding:1em"> '+
13857					'<a id="button_close" href="javascript:"> '+
13858						'&times; '+
13859					'</a> '+
13860				'</div> '+
13861			'</div> '+
13862			'<div id="elem"></div> '+
13863		'</div> '+
13864	'</div>',
13865	['ns', 'by_id', 'by_id_parent', 'window', 'document', 'elem', 'style', 'name', 'replace_current', 'side_preference', 'with_stretch', 'prefer_overlay', 'near_node_id', 'scroll_to_near_node', 'is_modal', 'trigger_id', 'parent_is_trigger', 'trigger_transit', 'trigger_onmouseover', 'trigger_onfocus', 'hide_onclick_outside', 'position_number', 'value', 'design', 'title', 'title_id', 'arrow_width', 'arrow_height', 'no_supress_css_where', 'tracker_params', 'topbar_style', 'with_backdrop_blur', 'button_close_style', 'report_tplchange', 'report_tplbeforeshow', 'report_tplbeforehide', 'report_tplshow', 'report_tplhide', 'report_tplupdateposition', 'report_tplbeforeupdateposition'],
13866	function(ns, by_id, by_id_parent, window, document, elem, style, name, replace_current, side_preference, with_stretch, prefer_overlay, near_node_id, scroll_to_near_node, is_modal, trigger_id, parent_is_trigger, trigger_transit, trigger_onmouseover, trigger_onfocus, hide_onclick_outside, position_number, value, design, title, title_id, arrow_width, arrow_height, no_supress_css_where, tracker_params, topbar_style, with_backdrop_blur, button_close_style, report_tplchange, report_tplbeforeshow, report_tplbeforehide, report_tplshow, report_tplhide, report_tplupdateposition, report_tplbeforeupdateposition)
13867	{	const HIDE_DELAY = 200;
13868		position_number |= 0;
13869		if (!elem._listeners)
13870		{	elem._listeners = {};
13871		}
13872		if (!elem._listeners_trigger)
13873		{	elem._listeners_trigger = [];
13874		}
13875		if (!elem._listeners_imgs)
13876		{	elem._listeners_imgs = [];
13877		}
13878		var at_x_side = side_preference=='right' || side_preference=='left' || side_preference=='x' || side_preference=='start' || side_preference=='end';
13879		arrow_width = arrow_width-0 || (at_x_side ? 12 : 8);
13880		arrow_height = arrow_height-0 || (at_x_side ? 8 : 12);
13881		var near_node = null;
13882		var trigger_by_id = by_id_parent;
13883		var triggers = [];
13884		var listeners = elem._listeners;
13885		var listeners_trigger = elem._listeners_trigger;
13886		var listeners_imgs = elem._listeners_imgs;
13887		var is_over = false;
13888		var want_remove = false;
13889		var cancel_deferred_hide = null;
13890		var want_detach_root = true;
13891		var glass = null;
13892		var chained_popup = null;
13893		var chained_show_wrapper = null;
13894		var side = '';
13895		var style_width_height_known = false;
13896		var style_width = null;
13897		var style_height = null;
13898		var hint_prev_side = null;
13899		var border_width = null;
13900		var border_color = null;
13901		var background_color = null;
13902		var hint_arrow_1_elem = null;
13903		var hint_arrow_2_elem = null;
13904		var flexible_elem = null;
13905		style = ns.new_elem('div', null, {style}).style;
13906		var is_position_absolute = style.position == 'absolute';
13907		var elem_parent = null;
13908		var sw_elem = null;
13909		var design_finished = false;
13910		var is_loaded = false;
13911		var onhide_callbacks = [];
13912		var cur_onhide = null;
13913		var over_trigger = null;
13914		var with_arrow = design=='hint';
13915		const RE_DIG = /\d/;
13916		const ARROW_STYLES =
13917		{	top:    'border-width:${HEIGHT}px ${WIDTH}px 0 ${WIDTH}px;  border-color:${COLOR} transparent; top:auto; right:auto; bottom:-${Y}px; left:auto',
13918			right:  'border-width:${HEIGHT}px ${WIDTH}px ${HEIGHT}px 0; border-color:transparent ${COLOR}; top:auto; right:auto; bottom:auto; left:-${X}px',
13919			bottom: 'border-width:0 ${WIDTH}px ${HEIGHT}px ${WIDTH}px;  border-color:${COLOR} transparent; top:-${Y}px; right:auto; bottom:auto; left:auto',
13920			left:   'border-width:${HEIGHT}px 0 ${HEIGHT}px ${WIDTH}px; border-color:transparent ${COLOR}; top:auto; right:-${X}px; bottom:auto; left:auto'
13921		};
13922		const DIALOG_BOTTOMBAR_STYLE = 'box-sizing:border-box; border-collapse:collapse; width:100%; border-top:1px solid #ECECEC; box-shadow:0 1px 0 #FFF inset; background-color:#F7F7F7; padding:1em; text-align:right; text-align:end';
13923		if (elem._by_id)
13924		{	elem._by_id.root._hide();
13925		}
13926		elem._by_id = by_id;
13927		function finish_design()
13928		{	if (!design_finished)
13929			{	design_finished = true;
13930				var root = elem;
13931				if (sw_elem)
13932				{	root = sw_elem.firstElementChild;
13933				}
13934				var placements = get_placements(root);
13935				if (placements.top || placements.bottom)
13936				{	elem.style.padding = by_id.container.style.padding || '1em';
13937					by_id.container.style.padding = '';
13938				}
13939				if (placements.top)
13940				{	let for_button_close = by_id.button_close.parentNode;
13941					for_button_close.parentNode.insertBefore(placements.top, for_button_close);
13942					placements.top.style.flexGrow = for_button_close.style.flexGrow;
13943					for_button_close.style.flexGrow = '';
13944				}
13945				else if (!title && !title_id)
13946				{	var pn = by_id.topbar.parentNode;
13947					if (pn)
13948					{	pn.removeChild(by_id.topbar);
13949					}
13950					by_id.topbar = null;
13951				}
13952				let container_style = ns.new_elem(by_id.container).style();
13953				if (placements.bottom)
13954				{	placements.bottom.style.cssText = DIALOG_BOTTOMBAR_STYLE+';'+placements.bottom.style.cssText;
13955					placements.bottom.style.borderBottomLeftRadius = container_style.borderBottomLeftRadius;
13956					placements.bottom.style.borderBottomRightRadius = container_style.borderBottomRightRadius;
13957					elem.parentNode.insertBefore(placements.bottom, elem.nextChild || null);
13958					by_id.bottombar = placements.bottom;
13959				}
13960				if (by_id.topbar)
13961				{	by_id.topbar.style.borderTopLeftRadius = container_style.borderTopLeftRadius;
13962					by_id.topbar.style.borderTopRightRadius = container_style.borderTopRightRadius;
13963				}
13964			}
13965		}
13966		function get_placements(elem)
13967		{	var placements = {};
13968			if (elem)
13969			{	for (var e=elem.firstChild; e; e=next)
13970				{	var next = e.nextSibling;
13971					var placement = e.getAttribute && e.getAttribute('data-placement');
13972					if (placement)
13973					{	placements[placement] = e;
13974					}
13975				}
13976			}
13977			return placements;
13978		}
13979		function subst_params(str, params)
13980		{	return str.replace(/\$\{(\w+)\}/g, (m, $1) => params[$1]);
13981		}
13982		function get_is_showing()
13983		{	return by_id.root._get_case()!=null;
13984		}
13985		function get_xy_for_position(vp_dim, width, height, is_absolute)
13986		{	var n = position_number || 5;
13987			var top = (is_absolute ? vp_dim.top : 0) +
13988			(	n==1 || n==2 || n==3 ? 0
13989				:
13990				n==4 || n==5 || n==6 ? (vp_dim.height - height)/2
13991				:
13992				vp_dim.height - height
13993			);
13994			var left = (is_absolute ? vp_dim.left : 0) +
13995			(	n==1 || n==4 || n==7 ? 0
13996				:
13997				n==2 || n==5 || n==8 ? (vp_dim.width - width)/2
13998				:
13999				vp_dim.width - width
14000			);
14001			var xy = {};
14002			if (!is_absolute && top==vp_dim.height-height)
14003			{	xy.bottom = style.bottom || 0;
14004			}
14005			else if (top == 0)
14006			{	xy.top = style.top || 0;
14007			}
14008			else
14009			{	xy.top = top;
14010			}
14011			if (!is_absolute && left==vp_dim.width-width)
14012			{	xy.right = style.right || 0;
14013				if (vp_dim.width == width)
14014				{	xy.left = style.left || 0;
14015				}
14016			}
14017			else if (left == 0)
14018			{	xy.left = style.left || 0;
14019			}
14020			else
14021			{	xy.left = left;
14022			}
14023			return xy;
14024		}
14025		function remove_listener(name)
14026		{	if (listeners[name])
14027			{	listeners[name].remove();
14028				listeners[name] = null;
14029			}
14030		}
14031		function remove_listeners_imgs()
14032		{	while (listeners_imgs.length)
14033			{	listeners_imgs.pop().remove();
14034			}
14035		}
14036		function do_hide_by_trigger()
14037		{	by_id.root._hide(ns.clone_obj(trigger_transit));
14038		}
14039		function get_defined_near_node()
14040		{	return !near_node_id ? null : near_node_id.cloneNode ? near_node_id : by_id_parent[near_node_id];
14041		}
14042		function ontrigger(event)
14043		{	if (trigger_onmouseover || event.which==1)
14044			{	if (!(this._get_disabled?.() ?? false))
14045				{	if (!ns.popup_global_parent.has_parent(event.target, by_id.container))
14046					{	let currently_showing = null;
14047						if (!trigger_onmouseover || !name || !(currently_showing = ns.popup_global_parent._get_by_name(name)[0]))
14048						{	by_id.root._switch(trigger_onmouseover ? true : null, ns.clone_obj(trigger_transit), get_defined_near_node() || this);
14049							event.stopPropagation();
14050						}
14051						else if (currently_showing && currently_showing!==by_id.root)
14052						{	over_trigger?._remove_onhide(cur_onhide);
14053							over_trigger = currently_showing;
14054							cur_onhide = () =>
14055							{	if (over_trigger)
14056								{	ontrigger.call(this, event);
14057								}
14058							};
14059							over_trigger._add_onhide(cur_onhide);
14060						}
14061					}
14062				}
14063			}
14064		}
14065		function ontriggeroff(event)
14066		{	if (!cancel_deferred_hide)
14067			{	if (event.type=='click' || trigger_onmouseover)
14068				{	let {target} = event;
14069					if (event.isTrusted!==false || target==document || target==document.body) // some jQuery scripts generate clicks on those elements
14070					{	if (target==by_id.root || triggers.indexOf(target)!=-1 || ns.popup_global_parent.has_parent(target, by_id.root) || !ns.new_elem(target).is_node_linked()) // in Chrome target may be unlinked if in onclick handler that has already been worked, the target's parent has been unlinked
14071						{	return;
14072						}
14073						let {top, right, bottom, left} = by_id.root.getBoundingClientRect();
14074						for (let trigger of triggers)
14075						{	if (ns.popup_global_parent.has_parent(target, trigger))
14076							{	return;
14077							}
14078							if (trigger_onmouseover)
14079							{	let trigger_rect = trigger.getBoundingClientRect();
14080								if (trigger_rect.top < top)
14081								{	top = trigger_rect.top;
14082								}
14083								if (trigger_rect.right > right)
14084								{	right = trigger_rect.right;
14085								}
14086								if (trigger_rect.bottom > bottom)
14087								{	bottom = trigger_rect.bottom;
14088								}
14089								if (trigger_rect.left < left)
14090								{	left = trigger_rect.left;
14091								}
14092							}
14093						}
14094						let hide_delay = trigger_onmouseover && event.pageX>left && event.pageX<right && event.pageY>top && event.pageY<bottom ? HIDE_DELAY : 0;
14095						cancel_deferred_hide = ns.deferred(do_hide_by_trigger, hide_delay);
14096					}
14097				}
14098			}
14099		}
14100		function find_trigger()
14101		{	// remove all triggers and listeners
14102			triggers.length = 0;
14103			for (let listeners of listeners_trigger)
14104			{	for (let name of Object.keys(listeners))
14105				{	listeners[name]?.remove?.();
14106				}
14107			}
14108			listeners_trigger.length = 0;
14109			//
14110			if (parent_is_trigger)
14111			{	let trigger = elem_parent;
14112				var n = parent_is_trigger;
14113				while (--n > 0 && trigger.parentNode)
14114				{	trigger = trigger.parentNode;
14115				}
14116				triggers[0] = trigger;
14117			}
14118			if (trigger_id)
14119			{	if (trigger_id.cloneNode)
14120				{	triggers[triggers.length] = trigger_id;
14121				}
14122				else
14123				{	for (let tid of (trigger_id+'').split(','))
14124					{	tid = tid.trim();
14125						if (tid)
14126						{	let trigger = trigger_by_id[tid];
14127							if (trigger)
14128							{	triggers[triggers.length] = trigger;
14129							}
14130						}
14131					}
14132				}
14133			}
14134			for (let trigger of triggers)
14135			{	let listeners = {};
14136				listeners_trigger[listeners_trigger.length] = listeners;
14137				ns.new_elem(trigger).add_props
14138				(	trigger_onfocus ?
14139					{	onfocus:
14140						{	func()
14141							{	by_id.root._show(ns.clone_obj(trigger_transit), get_defined_near_node() || this);
14142								this.focus();
14143							},
14144							listeners
14145						},
14146						onblur:
14147						{	func()
14148							{	if (!is_over)
14149								{	do_hide_by_trigger();
14150								}
14151							},
14152							listeners
14153						}
14154					}
14155					:
14156					{	onkeydown:
14157						{	func(event)
14158							{	/*if (event.keyCode == 13) // ENTER
14159								{	by_id.root._switch(null, ns.clone_obj(trigger_transit), get_defined_near_node() || this);
14160									event.stopPropagation();
14161								}
14162								else if (event.keyCode == 32) // SPACE
14163								{	by_id.root._show(ns.clone_obj(trigger_transit), this);
14164									event.stopPropagation();
14165								}
14166								else*/ if (event.keyCode == 27) // ESCAPE
14167								{	if (by_id.root._get_is_showing())
14168									{	do_hide_by_trigger();
14169										event.stopPropagation();
14170									}
14171								}
14172							},
14173							listeners
14174						},
14175						onclick: trigger_onmouseover ? null :
14176						{	func: ontrigger,
14177							listeners
14178						},
14179						onmouseenter: !trigger_onmouseover ? null :
14180						{	func: ontrigger,
14181							listeners
14182						},
14183						onmouseleave: !trigger_onmouseover ? null :
14184						{	func()
14185							{	if (over_trigger)
14186								{	over_trigger._remove_onhide(cur_onhide);
14187									over_trigger = null;
14188									cur_onhide = null;
14189								}
14190							},
14191							listeners
14192						}
14193					}
14194				);
14195			}
14196		}
14197		function is_no_dim(e, dim_name)
14198		{	var dim_value = e.style[dim_name];
14199			return !dim_value || dim_value=='auto' || dim_value=='100%';
14200		}
14201		function is_block(e)
14202		{	var display = ns.new_elem(e).style('display');
14203			return display=='block' || display=='table' || display=='table-row-group' || display=='table-row' || display=='table-cell' || display=='table-header-group' || display=='table-footer-group';
14204		}
14205		function is_overflow_scroll(e, overflow_name)
14206		{	var overflow = ns.new_elem(e).style(overflow_name);
14207			return overflow=='auto' || overflow=='scroll' || overflow=='overlay';
14208		}
14209		function get_scrollable_descendant(e, dim_name, overflow_name)
14210		{	if (is_overflow_scroll(e, overflow_name))
14211			{	return e;
14212			}
14213			if (!is_no_dim(e, dim_name) || !is_block(e))
14214			{	return null;
14215			}
14216			var {children} = e;
14217			if (children.length != 1)
14218			{	return null;
14219			}
14220			return get_scrollable_descendant(children[0], dim_name, overflow_name);
14221		}
14222		class FlexibleElem
14223		{	constructor(elem)
14224			{	var flexible_elem = null;
14225				var flexible_width_elem = undefined;
14226				var flexible_height_elem = undefined;
14227				if (is_no_dim(elem, 'width') && is_no_dim(elem, 'height'))
14228				{	var e = elem;
14229					while (true)
14230					{	var {children} = e;
14231						if (children.length != 1) break;
14232						var child = children[0];
14233						if (!is_no_dim(child, 'width') || !is_no_dim(child, 'height') || !is_block(child))
14234						{	break;
14235						}
14236						e = child;
14237					}
14238					var width = 0;
14239					var height = 0;
14240					var w_siblings = [];
14241					var h_siblings = [];
14242					for (var i=0, i_end=children.length; i<i_end; i++)
14243					{	var child = children[i];
14244						var scrollable_descendant_x = get_scrollable_descendant(child, 'width', 'overflow-x');
14245						var scrollable_descendant_y = get_scrollable_descendant(child, 'height', 'overflow-y');
14246						if (scrollable_descendant_x)
14247						{	if (flexible_width_elem === undefined)
14248							{	flexible_width_elem = scrollable_descendant_x;
14249							}
14250							else
14251							{	flexible_width_elem = null;
14252							}
14253						}
14254						else
14255						{	w_siblings[w_siblings.length] = child;
14256						}
14257						if (scrollable_descendant_y)
14258						{	if (flexible_height_elem === undefined)
14259							{	flexible_height_elem = scrollable_descendant_y;
14260							}
14261							else
14262							{	flexible_height_elem = null;
14263							}
14264						}
14265						else
14266						{	h_siblings[h_siblings.length] = child;
14267						}
14268						width += child.offsetWidth;
14269						height += child.offsetHeight;
14270					}
14271					if (width > elem.offsetWidth)
14272					{	flexible_width_elem = null;
14273					}
14274					if (height > elem.offsetHeight)
14275					{	flexible_height_elem = null;
14276					}
14277					flexible_elem = flexible_width_elem || flexible_height_elem;
14278					if (flexible_elem)
14279					{	if (flexible_width_elem && flexible_height_elem && flexible_width_elem!=flexible_height_elem)
14280						{	flexible_elem = null;
14281						}
14282					}
14283				}
14284				this.elem = flexible_elem || null;
14285				this.siblings = !flexible_elem ? null : flexible_width_elem ? w_siblings : h_siblings;
14286				this.f_width = flexible_elem && !!flexible_width_elem;
14287				this.f_height = flexible_elem && !!flexible_height_elem;
14288			}
14289			get_siblings_width()
14290			{	var width = 0;
14291				var siblings = this.siblings;
14292				for (var i=0, i_end=siblings.length; i<i_end; i++)
14293				{	width += siblings[i].offsetWidth;
14294				}
14295				return width;
14296			}
14297			get_siblings_height()
14298			{	var height = 0;
14299				var siblings = this.siblings;
14300				for (var i=0, i_end=siblings.length; i<i_end; i++)
14301				{	height += siblings[i].offsetHeight;
14302				}
14303				return height;
14304			}
14305		}
14306		function update_position(transit, new_near_node, new_side_preference, new_prefer_overlay)
14307		{	if (new_near_node)
14308			{	near_node = new_near_node;
14309			}
14310			if (near_node && !near_node.cloneNode && !(near_node.left!=null && near_node.top!=null))
14311			{	near_node = null;
14312			}
14313			if (!near_node)
14314			{	near_node = get_defined_near_node();
14315			}
14316			if (new_side_preference != null)
14317			{	side_preference = new_side_preference;
14318			}
14319			if (new_prefer_overlay != null)
14320			{	prefer_overlay = new_prefer_overlay;
14321			}
14322			if (!get_is_showing())
14323			{	return;
14324			}
14325			if (!flexible_elem)
14326			{	flexible_elem = new FlexibleElem(elem);
14327			}
14328			if (flexible_elem.f_width)
14329			{	var flexible_elem_width = flexible_elem.elem.scrollWidth;
14330			}
14331			if (flexible_elem.f_height)
14332			{	var flexible_elem_height = flexible_elem.elem.scrollHeight;
14333			}
14334			if (flexible_elem.f_width)
14335			{	flexible_elem.elem.style.width = flexible_elem_width+'px';
14336			}
14337			if (flexible_elem.f_height)
14338			{	flexible_elem.elem.style.height = flexible_elem_height+'px';
14339			}
14340			var container = by_id.container;
14341			var container_style = container.style;
14342			var vp_dim = ns.new_elem(window).dim();
14343			var dim = near_node?.cloneNode ? ns.new_elem(near_node).dim() : {left: near_node?.left || 0, top: near_node?.top || 0, width: near_node?.width || 0, height: near_node?.height || 0};
14344			var extended_bounds_x = false;
14345			var extended_bounds_y = false;
14346			var to_top = side_preference == 'top'; // Otherwise bottom
14347			var to_left = side_preference == 'left'; // to right if 'right', otherwise depends on direction
14348			if (side_preference!='left' && side_preference!='right')
14349			{	var direction = ns.new_elem(near_node?.cloneNode && near_node || document.body).style('direction');
14350				if (direction == 'rtl')
14351				{	to_left = side_preference == 'end';
14352				}
14353				else
14354				{	to_left = side_preference != 'end';
14355				}
14356			}
14357			var comp_style_d = ns.new_elem(container).style();
14358			// create arrow
14359			var add_arrow_width = 0;
14360			var add_arrow_height = 0;
14361			if (near_node && with_arrow)
14362			{	if (border_width == null)
14363				{	border_width = parseFloat(comp_style_d.borderTopWidth);
14364					border_color = comp_style_d.borderTopColor;
14365					background_color = comp_style_d.backgroundColor;
14366				}
14367				if (!hint_arrow_1_elem)
14368				{	hint_arrow_1_elem = ns.new_elem('div', container, {style: 'position:absolute; border-style:solid; width:0; height:0; z-index:'+0x7FFFFFFF});
14369					hint_arrow_2_elem = ns.new_elem('div', container, {style: 'position:absolute; border-style:solid; width:0; height:0; z-index:'+0x7FFFFFFF});
14370				}
14371				if (at_x_side) add_arrow_width = arrow_width;
14372				if (!at_x_side) add_arrow_height = arrow_height;
14373			}
14374			// defined width and height?
14375			if (!style_width_height_known)
14376			{	style_width_height_known = true;
14377				style_width = container_style.width;
14378				style_height = container_style.height;
14379				if (!style_width || style_width=='auto') style_width = null;
14380				if (!style_height || style_height=='auto') style_height = null;
14381			}
14382			var orig_left = comp_style_d.left;
14383			var orig_right = comp_style_d.right;
14384			var orig_top = comp_style_d.top;
14385			var orig_bottom = comp_style_d.bottom;
14386			var orig_width = comp_style_d.width;
14387			var orig_height = comp_style_d.height;
14388			// let occupy space
14389			container_style.width = style_width || 'auto';
14390			container_style.height = style_height || 'auto';
14391			container_style.left = vp_dim.left+'px';
14392			container_style.top = '0px';
14393			container_style.right = 'auto';
14394			container_style.bottom = 'auto';
14395			if (!RE_DIG.test(container_style.maxWidth))
14396			{	container_style.maxWidth = '100%';
14397			}
14398			if (!RE_DIG.test(container_style.maxHeight))
14399			{	container_style.maxHeight = '100%';
14400			}
14401			container_style.position = 'absolute';
14402			elem.style.overflow = 'visible';
14403			elem.style.width = style_width=='100%' ? '100%' : 'auto';
14404			elem.style.height = style_height=='100%' ? '100%' : 'auto';
14405			elem.style.boxSizing = 'border-box';
14406			report_tplbeforeupdateposition();
14407			var popup_dim = {};
14408			// width, left or right
14409			var client_width = vp_dim.width;
14410			var width = style_width && style_width.indexOf('%')!=-1 ? Math.round(client_width*parseFloat(style_width)/100) : is_modal && container.offsetWidth==client_width-ns.get_v_scrollbar_width() ? client_width : container.offsetWidth;
14411			var limited_width = width;
14412			if (near_node && !position_number)
14413			{	while (true)
14414				{	var t_width = width + add_arrow_width;
14415					var left = at_x_side ? dim.left+dim.width : !with_arrow ? dim.left : dim.left+dim.width/2-t_width/2;
14416					var right = at_x_side ? dim.left : !with_arrow ? dim.left+dim.width : dim.left+dim.width/2+t_width/2;
14417					left = Math.min(Math.max(left, vp_dim.left), vp_dim.left+client_width);
14418					right = Math.max(Math.min(right, vp_dim.left+client_width), vp_dim.left);
14419					var overflow_l = (left+t_width) - (vp_dim.left+client_width);
14420					var overflow_r = vp_dim.left - (right-t_width);
14421					var set_left = at_x_side ? !to_left : to_left;
14422					if (set_left && (overflow_l>0 ? overflow_l>overflow_r : left+t_width>client_width) || !set_left && overflow_r>0 && overflow_r>overflow_l) // if left is set to > client_width, browser behavior is to prefer minimum possible content width
14423					{	set_left = !set_left;
14424						to_left = !to_left;
14425					}
14426					var overflow_x = set_left ? overflow_l : overflow_r;
14427					if (overflow_x > 0)
14428					{	if (prefer_overlay || !at_x_side) // i will extend bounds if 1) prefer_overlay, 2) this will not cause overlaying the near_node
14429						{	if (t_width <= client_width)
14430							{	left = vp_dim.left + Math.floor((client_width - t_width) / 2);
14431							}
14432							else
14433							{	limited_width = client_width - add_arrow_width;
14434								left = vp_dim.left;
14435							}
14436							set_left = true;
14437							extended_bounds_x = true;
14438							at_x_side = false;
14439						}
14440						else
14441						{	limited_width = width - overflow_x;
14442							if (at_x_side)
14443							{	limited_width = width;
14444								at_x_side = false;
14445								add_arrow_height = add_arrow_width;
14446								add_arrow_width = 0;
14447								continue;
14448							}
14449						}
14450					}
14451					else if (!at_x_side && with_stretch && width<dim.width && left==dim.left && right==dim.left+dim.width)
14452					{	limited_width = dim.width; // stretch width to fit near_node
14453					}
14454					if (set_left)
14455					{	left += add_arrow_width;
14456						if (!at_x_side && with_arrow && left+arrow_width>dim.left)
14457						{	left = Math.max(dim.left-arrow_width, vp_dim.left);
14458						}
14459						popup_dim.left = left;
14460						popup_dim.right = 'auto';
14461					}
14462					else
14463					{	left = right - t_width;
14464						right -= add_arrow_width;
14465						if (!at_x_side && with_arrow && right-arrow_width<dim.left+dim.width)
14466						{	right = Math.min(dim.left+dim.width+arrow_width, vp_dim.left+client_width);
14467							left = right - t_width;
14468						}
14469						var body_position = ns.new_elem(document.body).style().position;
14470						right = (!body_position || body_position=='static' ? client_width : document.body.offsetWidth) - right;
14471						popup_dim.right = right;
14472						popup_dim.left = 'auto';
14473					}
14474					break;
14475				}
14476			}
14477			else if (width > client_width)
14478			{	limited_width = client_width;
14479			}
14480			// apply the width now to make content flow vertically. this will change the height which i will examine below
14481			var force_scroll = false; // set when a branch below deliberately asks for scrolling that the limited_* values don't express
14482			if (limited_width!=width || flexible_elem.f_width)
14483			{	ns.new_elem(container).add_style({width: limited_width+'px'}); // add_style will also stop current animation
14484				elem.style.width = '100%';
14485				if (flexible_elem.f_width)
14486				{	flexible_elem.elem.style.width = (client_width - flexible_elem.get_siblings_width())+'px';
14487					if (flexible_elem.elem.scrollWidth > limited_width)
14488					{	flexible_elem.elem.style.width = limited_width+'px';
14489						elem.style.overflowX = 'auto';
14490						force_scroll = true;
14491					}
14492				}
14493				else
14494				{	//elem.style.overflowX = 'auto';
14495				}
14496				popup_dim.width = limited_width;
14497			}
14498			else if (style_width && style_width.indexOf('%')==-1)
14499			{	popup_dim.width = style_width;
14500			}
14501			// height, top or bottom
14502			var client_height = vp_dim.height;
14503			var height = style_height && style_height.indexOf('%')!=-1 ? Math.round(client_height*parseFloat(style_height)/100) : container.offsetHeight;
14504			var limited_height = height;
14505			if (near_node && !position_number)
14506			{	var t_height = height + add_arrow_height;
14507				var top = !at_x_side ? dim.top+dim.height : !with_arrow ? dim.top : dim.top+dim.height/2-t_height/2;
14508				var bottom = !at_x_side ? dim.top : !with_arrow ? dim.top+dim.height : dim.top+dim.height/2+t_height/2;
14509				top = Math.min(Math.max(top, vp_dim.top), vp_dim.top+client_height);
14510				bottom = Math.max(Math.min(bottom, vp_dim.top+client_height), vp_dim.top);
14511				var overflow_t = (top+t_height) - (vp_dim.top+client_height);
14512				var overflow_b = vp_dim.top - (bottom-t_height);
14513				var set_top = at_x_side ? to_top : !to_top;
14514				if (set_top && overflow_t>0 && overflow_t>overflow_b || !set_top && overflow_b>0 && overflow_b>overflow_t)
14515				{	set_top = !set_top;
14516					to_top = !to_top;
14517				}
14518				var overflow_y = set_top ? overflow_t : overflow_b;
14519				if (overflow_y > 0)
14520				{	if (prefer_overlay || at_x_side) // i will extend bounds if 1) prefer_overlay, 2) this will not cause overlaying the near_node
14521					{	if (t_height <= client_height)
14522						{	top = vp_dim.top + Math.floor((client_height - t_height) / 2);
14523						}
14524						else
14525						{	limited_height = client_height - add_arrow_height;
14526						}
14527						set_top = true;
14528						extended_bounds_y = true;
14529					}
14530					else
14531					{	limited_height = height - overflow_y;
14532					}
14533				}
14534				if (set_top)
14535				{	top += add_arrow_height;
14536					popup_dim.top = top;
14537					popup_dim.bottom = 'auto';
14538				}
14539				else
14540				{	top = bottom - t_height;
14541					bottom -= add_arrow_height;
14542					var body_position = body_position || ns.new_elem(document.body).style().position;
14543					bottom = (!body_position || body_position=='static' ? client_height : document.body.offsetHeight) - bottom;
14544					popup_dim.bottom = bottom;
14545					popup_dim.top = 'auto';
14546				}
14547			}
14548			else if (height > client_height)
14549			{	limited_height = client_height;
14550			}
14551			//
14552			if (limited_height < height)
14553			{	var wrapper_height = (by_id.topbar?.offsetHeight | 0) + (by_id.bottombar?.offsetHeight | 0);
14554				if (wrapper_height)
14555				{	elem.style.cssText += 'height:100%; height:calc(100% - '+wrapper_height+'px); box-sizing:border-box';
14556				}
14557				else
14558				{	elem.style.height = '100%';
14559				}
14560				if (flexible_elem.f_height)
14561				{	var h = client_height - flexible_elem.get_siblings_height();
14562					flexible_elem.elem.style.height = h+'px';
14563					h -= elem.offsetHeight - client_height;
14564					flexible_elem.elem.style.height = h+'px';
14565				}
14566				else
14567				{	//elem.style.overflowY = 'auto';
14568				}
14569				popup_dim.height = limited_height;
14570			}
14571			//
14572			/*	Scroll only when the popup actually had to be CONSTRAINED — which is what the
14573				two `limited_*` values above mean. This element is sized to its content, so
14574				when the design is a single card the box ends up exactly the card's size, and
14575				`overflow:auto` then clips the card's own box-shadow at a square edge: on a
14576				rounded card that reads as hard grey corners, the shadow surviving only where
14577				the corner curves away. A popup that fits has nothing to scroll and loses
14578				nothing by letting the shadow paint. The layout re-runs on resize, so one that
14579				stops fitting gets its scrolling back.
14580			 */
14581			elem.style.overflow = limited_height < height || limited_width < width || force_scroll ? 'auto' : 'visible';
14582			elem.style.flexGrow = '100000';
14583			elem.style.flexShrink = '100000';
14584			//
14585			var ontransitionend_reset_width = false;
14586			var ontransitionend_reset_height = false;
14587			if (!near_node || position_number)
14588			{	var xy = get_xy_for_position(vp_dim, limited_width, limited_height, is_position_absolute);
14589				for (var i in xy)
14590				{	popup_dim[i] = xy[i];
14591				}
14592			}
14593			else
14594			{	var n;
14595				if (transit && popup_dim.width==null && (n = parseInt(style_width)) && style_width.indexOf('px')==-1 || n<width)
14596				{	popup_dim.width = width;
14597					ontransitionend_reset_width = true;
14598				}
14599				if (transit && popup_dim.height==null && (n = parseInt(style_height)) && style_height.indexOf('px')==-1 || n<height)
14600				{	popup_dim.height = height;
14601					ontransitionend_reset_height = true;
14602				}
14603			}
14604			// side
14605			side = at_x_side ? (extended_bounds_x ? '' : to_left ? 'left' : 'right') : (extended_bounds_y ? '' : to_top ? 'top' : 'bottom');
14606			// arrow?
14607			if (near_node && with_arrow && side)
14608			{	if (side != hint_prev_side)
14609				{	ns.new_elem(hint_arrow_1_elem).add_style
14610					(	subst_params
14611						(	ARROW_STYLES[side],
14612							{	WIDTH: arrow_width+border_width,
14613								HEIGHT: arrow_height+border_width,
14614								COLOR: border_color,
14615								X: arrow_width+border_width,
14616								Y: arrow_height+border_width
14617							}
14618						)
14619					);
14620					ns.new_elem(hint_arrow_2_elem).add_style
14621					(	subst_params
14622						(	ARROW_STYLES[side],
14623							{	WIDTH: arrow_width,
14624								HEIGHT: arrow_height,
14625								COLOR: background_color,
14626								X: arrow_width-1,
14627								Y: arrow_height-1
14628							}
14629						)
14630					);
14631					hint_prev_side = side;
14632				}
14633				if (!at_x_side)
14634				{	var arrow_x = Math.max(0, Math.min(dim.width/2 - arrow_width - border_width + (dim.left - left), limited_width-arrow_width*2-border_width*4));
14635					hint_arrow_1_elem.style.left = arrow_x+'px';
14636					hint_arrow_2_elem.style.left = (arrow_x + border_width)+'px';
14637				}
14638				else
14639				{	var arrow_y = Math.max(0, Math.min(dim.height/2 - arrow_height - border_width + (dim.top - top), limited_height-arrow_height*2-border_width*4));
14640					hint_arrow_1_elem.style.top = arrow_y+'px';
14641					hint_arrow_2_elem.style.top = (arrow_y + border_width)+'px';
14642				}
14643			}
14644			else
14645			{	if (hint_arrow_1_elem)
14646				{	container.removeChild(hint_arrow_1_elem);
14647					container.removeChild(hint_arrow_2_elem);
14648					hint_prev_side = null;
14649					hint_arrow_1_elem = null;
14650					hint_arrow_2_elem = null;
14651				}
14652			}
14653			// set
14654			if (transit)
14655			{	/*if (container_style.width.indexOf('%') == -1)
14656				{	container_style.width = style_width=='100%' ? '100%' : 'auto';
14657				}
14658				if (container_style.height.indexOf('%') == -1)
14659				{	container_style.height = 'auto';
14660				}*/
14661				if (popup_dim.width)
14662				{	container_style.width = orig_width;
14663				}
14664				if (popup_dim.height)
14665				{	container_style.height = orig_height;
14666				}
14667				container_style.left = popup_dim.right==null ? orig_left : 'auto';
14668				container_style.right = popup_dim.right!=null ? orig_right : 'auto';
14669				container_style.top = popup_dim.bottom==null ? orig_top : 'auto';
14670				container_style.bottom = popup_dim.bottom!=null ? orig_bottom : 'auto';
14671			}
14672			container_style.position = near_node && !position_number || is_position_absolute ? 'absolute' : 'fixed';
14673			var transit_set_end = ns.transit_set_begin
14674			(	transit,
14675				() =>
14676				{	if (ontransitionend_reset_width)
14677					{	container_style.width = 'auto';
14678					}
14679					if (ontransitionend_reset_height)
14680					{	container_style.height = 'auto';
14681					}
14682					var overflow_x = elem.scrollWidth-elem.offsetWidth;
14683					if (overflow_x>0 && overflow_x<=ns.get_v_scrollbar_width() && elem.style.overflow!='visible')
14684					{	// only when this box is already a scroll container: setting overflow-x on a
14685						// visible box would force the other axis to auto and start clipping the shadow
14686						elem.style.overflowX = 'hidden';
14687					}
14688					report_tplupdateposition();
14689				}
14690			);
14691			ns.new_elem(container).set_dim(popup_dim, transit, 'border-box');
14692			transit_set_end();
14693		}
14694		function set_case_transit(case_name, transit, to_null_first)
14695		{	if (transit)
14696			{	// set hard bounds during animation
14697				var container = by_id.container;
14698				var container_style = container.style;
14699				var elem_style = elem.style;
14700				var container_comp_style = ns.new_elem(container).style();
14701				var elem_comp_style = ns.new_elem(elem).style();
14702				var container_set_width = container_comp_style.width;
14703				var container_set_height = container_comp_style.height;
14704				var elem_set_width = elem_comp_style.width;
14705				var elem_set_height = elem_comp_style.height;
14706				var cur_w = container_style.width;
14707				var cur_h = container_style.height;
14708				var cur_elem_w = elem_style.width;
14709				var cur_elem_h = elem_style.height;
14710				var cur_overflow_x = elem_style.overflowX;
14711				var cur_overflow_y = elem_style.overflowY;
14712				container_style.width = container_set_width;
14713				container_style.height = container_set_height;
14714				elem_style.overflowX = 'visible';
14715				elem_style.overflowY = 'visible';
14716				elem_style.width = elem_set_width;
14717				elem_style.height = elem_set_height;
14718				transit.ontransitionend = ns.pair_funcs
14719				(	transit.ontransitionend,
14720					() =>
14721					{	ns.deferred // let animator finish
14722						(	() =>
14723							{	container_style.width = cur_w;
14724								container_style.height = cur_h;
14725								elem_style.overflowX = cur_overflow_x;
14726								elem_style.overflowY = cur_overflow_y;
14727								elem_style.width = cur_elem_w;
14728								elem_style.height = cur_elem_h;
14729							}
14730						);
14731					}
14732				);
14733			}
14734			if (to_null_first)
14735			{	by_id.root._set_case(null);
14736			}
14737			by_id.root._set_case(case_name, null, false, transit);
14738		}
14739		function show_glass(ondone)
14740		{	var e = ns.new_elem('div', null, {style});
14741			glass = ns.new_elem
14742			(	'div',
14743				null,
14744				{	style:
14745					{	position: 'fixed',
14746						top: 0,
14747						left: 0,
14748						width: '100%',
14749						height: '100%',
14750						backgroundColor: with_backdrop_blur ? 'rgba(0,0,0,0)' : 'black',
14751						backdropFilter: with_backdrop_blur ? 'blur(2px)' : '',
14752						opacity: with_backdrop_blur ? '' : '0',
14753						zIndex: e.style.zIndex || 0x7FFFFFFF
14754					},
14755					_get_name()
14756					{	return 'G L A S S';
14757					},
14758					_chain(popup, show_wrapper, is_high_priority)
14759					{	by_id.root._chain(popup, show_wrapper, is_high_priority);
14760					},
14761					_hide(transit)
14762					{	by_id.root._hide(transit);
14763					}
14764				}
14765			);
14766			if (innerHeight<document.body.offsetHeight && !ns.IS_SAFARI) // Safari stucks if adding a `<style>` that mentions `html {}`
14767			{	glass.innerHTML = `<style>html {position:fixed; overflow-y:scroll; width:100%; left:${-window.scrollX}px; top:${-window.scrollY}px}</style>`;
14768				glass._x = window.scrollX;
14769				glass._y = window.scrollY;
14770			}
14771			ns.new_elem(glass).add_style
14772			(	with_backdrop_blur ? {backgroundColor: 'rgba(0,0,0,.73)'} : {opacity: 0.7},
14773				{	duration: 0.25,
14774					ontransitionend: ondone
14775				}
14776			);
14777			ns.popup_global_parent._add_elem(document, glass, no_supress_css_where, tracker_params); // pass no_supress_css_where, so glass will use the same div as popup
14778			return glass;
14779		}
14780		function hide_glass()
14781		{	if (glass)
14782			{	var cur_glass = glass;
14783				glass = null;
14784				cur_glass.style.backdropFilter = '';
14785				ns.new_elem(cur_glass).add_style
14786				(	with_backdrop_blur ? {backgroundColor: 'rgba(0,0,0,0)'} : {opacity: 0},
14787					{	duration: 0.25,
14788						ontransitionend()
14789						{	ns.popup_global_parent._remove_elem(document, cur_glass);
14790							if (cur_glass._x != null)
14791							{	window.scrollTo(cur_glass._x, cur_glass._y);
14792							}
14793						}
14794					}
14795				);
14796				cur_glass._get_name = () => {};
14797			}
14798		}
14799		var update_position_infreq = ns.infrequent_func(update_position, 100, true);
14800		var title_elem;
14801		return {
14802			ontplready()
14803			{	title_elem = title_id && by_id_parent[title_id];
14804				if (title_elem)
14805				{	by_id.title.appendChild(title_elem);
14806				}
14807				elem_parent = by_id.root.parentNode;
14808				by_id.root._ne__switch_elem = elem_parent; // for new_elem.is_node_linked
14809				remove_listener('onresize');
14810				remove_listener('onscroll');
14811				remove_listener('onclick');
14812				remove_listener('onmousedown');
14813				remove_listener('onmousemove');
14814				remove_listeners_imgs();
14815				find_trigger();
14816				var elem_root = ns.new_elem(elem).get_root_child();
14817				if (elem_root?._get_cases)
14818				{	sw_elem = elem_root;
14819				}
14820				// Before detaching the popup element from it's parent, let other templates to see it attached during parsing.
14821				// This is especially needed to read attributes like data-js dedicated to $list.
14822				(window.queueMicrotask || ns.deferred)
14823				(	() =>
14824					{	if (want_detach_root)
14825						{	want_detach_root = false;
14826							var {root} = by_id;
14827							root.parentNode?.removeChild(root);
14828							if (value)
14829							{	root._show();
14830							}
14831						}
14832					}
14833				);
14834				// hack styles of parent template
14835				if (by_id_parent.root?.className.indexOf(CONF__PARTIAL_BOOTSTRAP_CLASSES)>=0)
14836				{	this.className = by_id_parent.root.className;
14837				}
14838			},
14839			ontplload()
14840			{	is_loaded = true;
14841				update_position_infreq();
14842			},
14843			ontplunload()
14844			{	is_loaded = false;
14845			},
14846			ontplreload()
14847			{	is_loaded = true;
14848				update_position_infreq();
14849			},
14850			ontplcasetransitionend(event)
14851			{	if (event.target != this) // not my $switch-elem
14852				{	if (is_loaded)
14853					{	update_position_infreq(by_id.root && by_id.root.offsetWidth*by_id.root.offsetHeight ? {} : null);
14854					}
14855				}
14856				return false;
14857			},
14858			container:
14859			{	style:
14860				[	with_arrow ?
14861					{	backgroundColor: 'white',
14862						border: 'solid #CCC 1px',
14863						boxShadow: '0 0 5px rgba(0, 0, 0, 0.3)',
14864						borderRadius: '5px',
14865						padding: '0.5em'
14866					}
14867					: design=='dialog' ?
14868					{	backgroundColor: 'white',
14869						//border: '1px solid #E5ECF6',
14870						boxShadow: '-5px 0 30px rgba(0,0,0,0.3)',
14871						borderRadius: '1em',
14872						overflow: 'hidden'
14873					}
14874					: design=='menu' ?
14875					{	backgroundColor: 'white',
14876						border: '0.0625em solid #E5E7EB',
14877						boxShadow: '0 0.375em 1.5em rgba(16,24,40,.06)',
14878						borderRadius: '1em',
14879						overflow: 'hidden'
14880					}
14881					: null,
14882					'z-index:'+0x7FFFFFFF,
14883					style,
14884					position_number>=1 && position_number<=4 || position_number==7 ? {borderTopLeftRadius: 0} : null,
14885					position_number>=1 && position_number<=3 || position_number==6 || position_number==9 ? {borderTopRightRadius: 0} : null,
14886					position_number==3 || position_number>=6 ? {borderBottomRightRadius: 0} : null,
14887					position_number==1 || position_number==4 || position_number>=7 ? {borderBottomLeftRadius: 0} : null,
14888					'display:flex; flex-direction:column; position:relative; box-sizing:border-box'
14889				],
14890				onmouseenter()
14891				{	is_over = true;
14892					if (cancel_deferred_hide)
14893					{	cancel_deferred_hide();
14894						cancel_deferred_hide = null;
14895					}
14896				},
14897				onmouseleave()
14898				{	is_over = false;
14899				}
14900			},
14901			elem,
14902			topbar:
14903			{	style: topbar_style
14904			},
14905			title:
14906			{	innerHTML: title
14907			},
14908			root:
14909			{	_get_name()
14910				{	return name;
14911				},
14912				_set_title(new_title)
14913				{	title = new_title;
14914					by_id.title.innerHTML = title;
14915					if (title_elem)
14916					{	by_id.title.appendChild(title_elem);
14917					}
14918				},
14919				_update_position()
14920				{	if (is_loaded)
14921					{	update_position_infreq();
14922					}
14923				},
14924				_show(transit, new_near_node, new_side_preference, new_prefer_overlay, case_props, force_reapply, new_replace_current)
14925				{	want_detach_root = false;
14926					if (cancel_deferred_hide)
14927					{	cancel_deferred_hide();
14928						cancel_deferred_hide = null;
14929					}
14930					if (is_modal && !transit)
14931					{	transit = {template: 'animator_fade'};
14932					}
14933					if (new_replace_current != null)
14934					{	replace_current = new_replace_current;
14935					}
14936					var root = by_id.root;
14937					if (!get_is_showing())
14938					{	if (!case_props)
14939						{	case_props = {};
14940						}
14941						if (report_tplbeforeshow(case_props, new_near_node || near_node) === false)
14942						{	return false;
14943						}
14944						var already = name && ns.popup_global_parent._get_by_name(name)[0] || position_number && (glass || ns.popup_global_parent._get_by_name('G L A S S')[0]);
14945						if (already && already!=root)
14946						{	if (replace_current)
14947							{	already._hide(transit);
14948							}
14949							else if (already._chain)
14950							{	already._chain
14951								(	root,
14952									() =>
14953									{	root._show(transit, new_near_node, new_side_preference, new_prefer_overlay, case_props, force_reapply);
14954									},
14955									is_modal
14956								);
14957								return true;
14958							}
14959						}
14960						if (sw_elem)
14961						{	sw_elem._set_case(sw_elem._get_cases()[0], case_props, force_reapply);
14962						}
14963						function do_show()
14964						{	want_remove = false;
14965							ns.popup_global_parent._add_elem(document, root, no_supress_css_where, tracker_params);
14966							root._set_case('on');
14967							finish_design();
14968							ns.new_elem
14969							(	root,
14970								subelem =>
14971								{	if (subelem.nodeName == 'IMG')
14972									{	if (subelem.complete===false || subelem.naturalWidth===0)
14973										{	var listeners = {};
14974											ns.new_elem(subelem).add_props
14975											(	{	onload:
14976													{	func()
14977														{	update_position_infreq(); // no first argument
14978														},
14979														listeners,
14980														till_node_linked: root
14981													}
14982												}
14983											);
14984											listeners_imgs.push(listeners.onload);
14985										}
14986									}
14987								}
14988							);
14989							update_position_infreq.immediate_call(null, new_near_node, new_side_preference, new_prefer_overlay);
14990							if (transit)
14991							{	set_case_transit('on', transit, true);
14992							}
14993							var reg_onclick_outside = !trigger_onmouseover && (hide_onclick_outside || (triggers.length || position_number) && hide_onclick_outside==null);
14994							var reg_onmouseover_outside = triggers.length && trigger_onmouseover;
14995							if (reg_onclick_outside || reg_onmouseover_outside)
14996							{	var mousedown_time = 0;
14997								setTimeout
14998								(	() =>
14999									{	ns.new_elem(document.body).add_props
15000										(	{	onclick: !reg_onclick_outside ? null :
15001												{	func(event)
15002													{	if (event.which==1 && mousedown_time+300>event.timeStamp)
15003														{	ontriggeroff(event);
15004														}
15005													},
15006													listeners,
15007													till_node_linked: root
15008												},
15009												onmousedown: !reg_onclick_outside ? null :
15010												{	func(event)
15011													{	mousedown_time = event.timeStamp;
15012													},
15013													listeners,
15014													till_node_linked: root
15015												},
15016												onmousemove: !reg_onmouseover_outside ? null :
15017												{	func: ontriggeroff,
15018													listeners,
15019													till_node_linked: root
15020												}
15021											}
15022										);
15023									},
15024									100
15025								);
15026							}
15027							ns.new_elem(window).add_props
15028							(	{	onresize:
15029									{	func()
15030										{	update_position_infreq();
15031										},
15032										listeners,
15033										till_node_linked: root
15034									}
15035								}
15036							);
15037							report_tplchange();
15038							report_tplshow();
15039						}
15040						var scroll_dim = null;
15041						if (scroll_to_near_node)
15042						{	var node = new_near_node || near_node;
15043							if (node)
15044							{	var dim = node.cloneNode ? ns.new_elem(node).dim() : node;
15045								var vp_dim = ns.new_elem(window).dim();
15046								if (dim.left<vp_dim.left || dim.left+dim.width>vp_dim.left+vp_dim.width)
15047								{	if (!scroll_dim) scroll_dim = {};
15048									scroll_dim.left = dim.left - Math.max((vp_dim.width - dim.width)/2, 0);
15049								}
15050								if (dim.top<vp_dim.top || dim.top+dim.height>vp_dim.top+vp_dim.height)
15051								{	if (!scroll_dim) scroll_dim = {};
15052									scroll_dim.top = dim.top - Math.max((vp_dim.height - dim.height)/2, 0);
15053								}
15054							}
15055						}
15056						if (name && (scroll_dim || is_modal))
15057						{	ns.popup_global_parent._add_dummy(name, root);
15058						}
15059						if (scroll_dim)
15060						{	ns.new_elem(window).set_dim(scroll_dim, {ontransitionend: do_show});
15061						}
15062						else if (is_modal)
15063						{	show_glass();
15064							do_show();
15065						}
15066						else
15067						{	do_show();
15068						}
15069					}
15070					else
15071					{	update_position_infreq.immediate_call(null, new_near_node, new_side_preference, new_prefer_overlay);
15072					}
15073					return true;
15074				},
15075				_set_dim(new_dim, transit)
15076				{	var transit_set_end = ns.transit_set_begin(transit, report_tplupdateposition);
15077					ns.new_elem(by_id.container).set_dim(new_dim, transit, 'border-box');
15078					transit_set_end();
15079				},
15080				_hide_by_trigger()
15081				{	this._hide(trigger_transit);
15082				},
15083				_hide(transit)
15084				{	if (cancel_deferred_hide)
15085					{	cancel_deferred_hide();
15086						cancel_deferred_hide = null;
15087					}
15088					if (want_detach_root)
15089					{	want_detach_root = false;
15090						by_id.root.parentNode.removeChild(by_id.root);
15091					}
15092					if (get_is_showing())
15093					{	if (report_tplbeforehide() === false)
15094						{	return;
15095						}
15096						if (is_modal && !transit)
15097						{	transit = {template: 'animator_fade'};
15098						}
15099						remove_listener('onresize');
15100						remove_listener('onscroll');
15101						remove_listener('onclick');
15102						remove_listener('onmousedown');
15103						remove_listener('onmousemove');
15104						remove_listeners_imgs();
15105						let {root} = by_id;
15106						function do_hide()
15107						{	ns.popup_global_parent._remove_elem(document, root);
15108							if (chained_popup)
15109							{	if (chained_show_wrapper)
15110								{	chained_show_wrapper();
15111									chained_show_wrapper = null;
15112								}
15113								else
15114								{	chained_popup._show();
15115								}
15116								chained_popup = null;
15117							}
15118							for (let c of onhide_callbacks)
15119							{	c();
15120							}
15121							onhide_callbacks.length = 0;
15122						}
15123						hide_glass();
15124						ns.popup_global_parent._close_popups_opened_inside_this_popup(root);
15125						if (!transit)
15126						{	root._set_case(null);
15127							do_hide();
15128						}
15129						else
15130						{	want_remove = true;
15131							transit.ontransitionend = ns.pair_funcs
15132							(	transit.ontransitionend,
15133								() =>
15134								{	if (want_remove && root.parentNode)
15135									{	do_hide();
15136									}
15137								}
15138							);
15139							set_case_transit(null, transit);
15140						}
15141						report_tplchange();
15142						report_tplhide();
15143					}
15144					else if (transit && transit.ontransitionend)
15145					{	transit.ontransitionend();
15146					}
15147				},
15148				_switch(new_value, transit, new_near_node, new_side_preference, new_prefer_overlay)
15149				{	var {root} = by_id;
15150					if (new_value == null)
15151					{	new_value = !get_is_showing();
15152					}
15153					if (new_value)
15154					{	root._show(transit, new_near_node, new_side_preference, new_prefer_overlay);
15155					}
15156					else
15157					{	root._hide(transit);
15158					}
15159				},
15160				_chain(popup, show_wrapper, is_high_priority)
15161				{	if (!chained_popup)
15162					{	chained_popup = popup;
15163						chained_show_wrapper = show_wrapper;
15164					}
15165					else if (chained_popup != popup)
15166					{	if (is_high_priority)
15167						{	popup._chain(chained_popup, chained_show_wrapper);
15168							chained_popup = popup;
15169							chained_show_wrapper = show_wrapper;
15170						}
15171						else
15172						{	chained_popup._chain(popup, show_wrapper);
15173						}
15174					}
15175				},
15176				_add_onhide(callback)
15177				{	this._remove_onhide(callback);
15178					onhide_callbacks.push(callback);
15179				},
15180				_remove_onhide(callback)
15181				{	let i = onhide_callbacks.indexOf(callback);
15182					if (i != -1)
15183					{	onhide_callbacks.splice(i, 1);
15184					}
15185				},
15186				_get_near_node()
15187				{	return near_node;
15188				},
15189				_set_near_node(new_near_node)
15190				{	near_node_id = new_near_node;
15191					near_node = get_defined_near_node();
15192				},
15193				_set_trigger_id(new_trigger_id, new_trigger_by_id)
15194				{	trigger_id = new_trigger_id;
15195					if (new_trigger_by_id != null)
15196					{	trigger_by_id = new_trigger_by_id;
15197					}
15198					find_trigger();
15199				},
15200				_set_parent_is_trigger(new_parent_is_trigger)
15201				{	parent_is_trigger = new_parent_is_trigger;
15202					find_trigger();
15203				},
15204				_get_trigger_elems()
15205				{	return triggers;
15206				},
15207				_get_is_showing: get_is_showing,
15208				_get_value: get_is_showing,
15209				_set_value(new_value)
15210				{	this._switch(new_value);
15211				},
15212				_get_side()
15213				{	// at which side of near_node the popup is currently showing?
15214					// can be: 'left', 'right', 'top', 'bottom', '' (empty string if overlaying).
15215					return side;
15216				},
15217				_set_content(content)
15218				{	ns.new_elem(by_id.container).add_props({innerHTML: content});
15219					hint_prev_side = null;
15220					hint_arrow_1_elem = null;
15221					hint_arrow_2_elem = null;
15222				},
15223				_get_elem()
15224				{	return elem;
15225				},
15226				_get_side_preference()
15227				{	return side_preference;
15228				},
15229				_set_side_preference(new_side_preference)
15230				{	side_preference = new_side_preference;
15231				},
15232				_get_is_modal()
15233				{	return is_modal;
15234				}
15235			},
15236			button_close:
15237			{	style: ['color:#6B7A90; font-size:190%; text-decoration:none; line-height:1', button_close_style],
15238				onclick()
15239				{	by_id.root._hide_by_trigger();
15240				}
15241			}
15242		};
15243	}
15244);
15245
15246NS['declare_template']
15247(	'popup_minimizeable',
15248	null,
15249	null,
15250	['ns', 'elem', 'value', 'remember_state', 'rotate_trigger', 'position_number'],
15251	function(ns, elem, value, remember_state, rotate_trigger, position_number)
15252	{	value = !!value;
15253		var trigger = elem.firstElementChild;
15254		if (trigger && trigger.dataset.isTrigger)
15255		{	let content = ns.new_elem('div', null, {childNodes: elem.childNodes});
15256			elem.appendChild(trigger);
15257			elem.appendChild(content);
15258			ns.new_elem(elem).add_style('direction:ltr; display:flex; margin:0; padding:0; visibility:hidden');
15259			ns.new_elem(trigger).add_style('margin:auto; cursor:pointer');
15260			trigger.onclick = sw;
15261			elem._switch = function(new_value)
15262			{	if (new_value == null || value != !!new_value)
15263				{	sw();
15264				}
15265			};
15266			let animate_prop = '';
15267			let animate_from_prop = '';
15268			let freeze_prop = '';
15269			let freeze_from_prop = '';
15270			let is_inited = false;
15271			/*	position_number:
15272				1  2  3
15273				4  5  6
15274				7  8  9
15275			*/
15276			let session_key = null;
15277			if (window.IntersectionObserver)
15278			{	let observer = new IntersectionObserver
15279				(	records =>
15280					{	if (records[records.length - 1].intersectionRatio > 0)
15281						{	observer.disconnect();
15282							begin();
15283						}
15284					},
15285					{threshold: 0.0001}
15286				);
15287				observer.observe(elem);
15288			}
15289			else
15290			{	begin();
15291			}
15292			function begin()
15293			{	init();
15294				if (value)
15295				{	// initially on
15296					value = false;
15297					sw(); // switch to true
15298				}
15299			}
15300			function init()
15301			{	if (!is_inited)
15302				{	is_inited = true;
15303					// init
15304					let action = ns.get_owner_action && ns.get_owner_action(elem);
15305					let action_id = (action && action.action_id)|0;
15306					if (action_id && remember_state && window.sessionStorage)
15307					{	session_key = '_S_T.popup_state_'+action_id;
15308						let state = sessionStorage[session_key]|0;
15309						if (state)
15310						{	value = state > 0;
15311						}
15312					}
15313					//
15314					if (!position_number)
15315					{	let dim = elem.getBoundingClientRect();
15316						let horiz = dim.left - (window.innerWidth - dim.right); // center 0;  left: dim.width-innerWidth;  right: innerWidth-dim.width
15317						let vert = dim.top - (window.innerHeight - dim.bottom); // center: 0;  top: dim.height-innerHeight;  bottom: innerHeight-dim.height
15318						let w = (window.innerWidth - dim.width)/2;
15319						let h = (window.innerHeight - dim.height)/2;
15320						position_number = (vert<-h ? 1 : vert<=h ? 4 : 7) + (horiz>w ? 2 : horiz>=-w ? 1 : 0);
15321					}
15322					let rotate = '';
15323					if (position_number==1 || position_number==4 || position_number==7)
15324					{	animate_prop = 'marginLeft';
15325						animate_from_prop = 'offsetWidth';
15326						freeze_prop = 'height';
15327						freeze_from_prop = 'offsetHeight';
15328						rotate = 'transform:rotate(90deg); transform-origin:0 100%; margin-top:-50%';
15329					}
15330					else if (position_number==3 || position_number==6 || position_number==9)
15331					{	animate_prop = 'marginRight';
15332						animate_from_prop = 'offsetWidth';
15333						freeze_prop = 'height';
15334						freeze_from_prop = 'offsetHeight';
15335						rotate = 'transform:rotate(-90deg); transform-origin:100% 100%; margin-top:-50%';
15336					}
15337					else if (position_number == 2)
15338					{	animate_prop = 'marginTop';
15339						animate_from_prop = 'offsetHeight';
15340						freeze_prop = 'width';
15341						freeze_from_prop = 'offsetWidth';
15342					}
15343					else if (position_number == 8)
15344					{	animate_prop = 'marginBottom';
15345						animate_from_prop = 'offsetHeight';
15346						freeze_prop = 'width';
15347						freeze_from_prop = 'offsetWidth';
15348					}
15349					if (rotate_trigger && rotate)
15350					{	let wrapper = ns.new_elem('div', null, {childNodes: trigger.childNodes, style: rotate});
15351						trigger.appendChild(wrapper);
15352					}
15353					elem.style.flexDirection = position_number==2 || position_number==5 || position_number==8 ? 'column' : 'row';
15354					elem.insertBefore(content, position_number==1 || position_number==2 || position_number==4 || position_number==7 ? trigger : null);
15355					elem.style[freeze_prop] = elem[freeze_from_prop]+'px';
15356					elem.style.visibility = 'visible';
15357					content.style.display = 'none';
15358				}
15359			}
15360			function sw(event)
15361			{	var no_animation = !event;
15362				init();
15363				value = !value;
15364				if (session_key)
15365				{	sessionStorage[session_key] = value ? 1 : -1;
15366				}
15367				if (value)
15368				{	// switch on
15369					content.style.display = '';
15370					elem.style[freeze_prop] = '';
15371					content.style[animate_prop] = -content[animate_from_prop]+'px';
15372					ns.new_elem(content).add_style({[animate_prop]: '0px'}, no_animation ? null : {});
15373				}
15374				else
15375				{	// switch off
15376					elem.style[freeze_prop] = elem[freeze_from_prop]+'px';
15377					ns.new_elem(content).add_style({[animate_prop]: -content[animate_from_prop]+'px', display: 'none'}, no_animation ? null : {});
15378				}
15379				// call data-onminimize
15380				let elems = elem.querySelectorAll('*[data-onminimize]');
15381				for (let i=0; i<elems.length; i++)
15382				{	_S_T.pcall(new Function('value', elems[i].dataset.onminimize), elems[i], [value]);
15383				}
15384			}
15385		}
15386	}
15387);
15388
15389NS['declare_template']
15390(	'round_x',
15391	null,
15392	'<div id="root" onmouseenter="this.style.backgroundColor=this.style.outlineColor" onmouseleave="this.style.backgroundColor=this.style.textDecorationColor"><div></div></div>',
15393	['ns', 'style'],
15394	function(ns, style)
15395	{	const OUTER_STYLE =
15396		{	display: 'inline-block',
15397			verticalAlign: 'text-bottom',
15398			width: '2em',
15399			height: '2em',
15400			boxSizing: 'border-box',
15401			borderRadius: '50%',
15402			border: 'solid 0px',
15403			boxShadow: 'rgba(255, 255, 255, 0.2) 0px 1px 0px inset, rgba(0, 0, 0, 0.2) 0px 1px 2px',
15404			padding: '4px',
15405			backgroundColor: 'black',
15406			textDecorationColor: 'black', // i use this to store original backgroundColor
15407			outlineColor: '#FF6666', // i use this for hover color
15408			outlineWidth: '10px', // i use this for line thickness, unit doesn't matter, means % (outlineWidth doesn't accept %)
15409			color: 'white',
15410			cursor: 'pointer',
15411			transition: 'all 0.3s ease'
15412		};
15413		const INNER_STYLE =
15414		{	display: 'block',
15415			width: '100%',
15416			height: '100%',
15417			transition: 'all 0.3s ease 0s',
15418			borderRadius: '50%',
15419			backgroundImage: 'linear-gradient(-45deg, transparent 0%, transparent 45%, currentcolor 45%, currentcolor 55%, transparent 55%, transparent 100%), linear-gradient(45deg, transparent 0%, transparent 45%, currentcolor 45%, currentcolor 55%, transparent 55%, transparent 100%)'
15420		};
15421		return {
15422			root:
15423			{	ontplready()
15424				{	this._set_style(style);
15425				},
15426				_set_style(new_style)
15427				{	var {firstChild, style} = this;
15428					ns.new_elem(this).add_style([OUTER_STYLE, new_style]);
15429					ns.new_elem(firstChild).add_style(INNER_STYLE);
15430					if (style.outlineWidth)
15431					{	let t = Math.round(parseFloat(style.outlineWidth)/2);
15432						firstChild.style.backgroundImage = firstChild.style.backgroundImage.replace(/45%/g, (50-t)+1+'%').replace(/55%/g, (50+t)+1+'%'); // +1 seems to fix rendering defects
15433					}
15434					style.height = style.width;
15435					style.textDecorationColor = style.backgroundColor;
15436					firstChild.style.borderRadius = style.borderRadius;
15437				}
15438			}
15439		};
15440	}
15441);
15442
15443// uacss
15444function _new_elem_tmp_6(ns)
15445	{	const RE_CSS_TOKENIZER = /\s+|[\w\x80-\xFF\-]+|'(?:[^\\']*\\[\S\s])*[^\\']*'|"(?:[^\\"]*\\[\S\s])*[^\\"]*"|\S/gi;
15446		const XML_ELEMENT_NODE = 1;
15447
15448		var uacss = []; // uacss[0]: suppress all css, uacss[1]: supress except <a>, uacss[2]: supress except a, button, input, textarea, select
15449		var uacss_all = []; // uacss_all[0], uacss_all[1], uacss_all[2]: arrays of selectors
15450
15451		function supress_for_elem(elem, action_id, with_uacss, style_root_elem)
15452		{	let with_uacss_level = !with_uacss ? -1 : with_uacss=='-a' ? 1 : with_uacss=='-b' ? 2 : 0;
15453			let mutation_observer = null;
15454			if (action_id>0 && with_uacss_level!=-1)
15455			{	var uacss_id = '_'+action_id.toString(36)+'y';
15456				var uacss_selector = '#'+uacss_id+'.S-3T.S-4T.S-5T.S-6T.S-7T.S-8T.S-9T';
15457				var uacss_selector_weak = '#'+uacss_id+'.S-3T.S-4T.S-5T.S-6T.S-7T.S-8T';
15458				use_uacss(uacss_selector_weak, with_uacss_level);
15459			}
15460			if (uacss_id)
15461			{	if (elem.nodeType==XML_ELEMENT_NODE && elem.nodeName!='STYLE' && elem.nodeName!='SCRIPT')
15462				{	elem.setAttribute('id', uacss_id);
15463					elem.className = (!elem.className ? '' : elem.className+' ') + 'S-3T S-4T S-5T S-6T S-7T S-8T S-9T';
15464					ns.new_elem(elem).add_style('font-style:normal; font-weight:normal; font-variant:normal; text-decoration:none; text-align:left; text-align:start; line-height:normal; visibility:visible; white-space:normal; cursor:default');
15465					conv_style_elems();
15466					if (window.MutationObserver)
15467					{	mutation_observer = new MutationObserver(conv_style_elems);
15468						mutation_observer.observe
15469						(	elem,
15470							{	childList: true,
15471								subtree: true
15472							}
15473						);
15474					}
15475					function conv_style_elems()
15476					{	// convert all <style> tags
15477						let styles = elem.getElementsByTagName('style');
15478						for (let i=0, i_end=styles.length; i<i_end; i++)
15479						{	let style = styles[i];
15480							let css = style.textContent || '';
15481							if (css.indexOf(uacss_selector) == -1)
15482							{	let new_css = conv_css_for_action(css, uacss_selector);
15483								if (new_css && style.textContent!=new_css) // to avoid infinite loop
15484								{	style.textContent = new_css;
15485								}
15486							}
15487						}
15488					}
15489				}
15490			}
15491			return revert_for_elem;
15492			function revert_for_elem()
15493			{	if (mutation_observer)
15494				{	mutation_observer.disconnect();
15495					mutation_observer = null;
15496				}
15497				let u = uacss_all[with_uacss_level];
15498				if (u)
15499				{	let i = u.indexOf(uacss_selector_weak);
15500					if (i != -1)
15501					{	u.splice(i, 1);
15502					}
15503				}
15504			}
15505			function use_uacss(selector, level)
15506			{	level |= 0;
15507				let uacss_all_i = uacss_all[level] || [];
15508				if (selector && uacss_all_i.indexOf(selector)==-1)
15509				{	uacss_all_i.push(selector);
15510					uacss_all[level] = uacss_all_i;
15511					let uacss_i = uacss[level];
15512					uacss_i?.parentNode?.removeChild(uacss_i);
15513					uacss[level] = ns.new_elem('<style class="$user_agent_css" data-selector="'+uacss_all_i.join(',')+(level==1 ? '" data-not="a' : level==2 ? '" data-not="a,button,select,input,textarea' : '')+'"></style>', style_root_elem || document.getElementsByTagName('head')[0]);
15514				}
15515			}
15516		}
15517
15518		function rollback()
15519		{	for (var i=0; i<3; i++)
15520			{	let uacss_i = uacss[i];
15521				uacss_i?.parentNode?.removeChild(uacss_i);
15522			}
15523			uacss = [];
15524			uacss_all = [];
15525		}
15526
15527		function conv_css_for_action(css, inject_root_selector)
15528		{	inject_root_selector += ' ';
15529			RE_CSS_TOKENIZER.lastIndex = 0;
15530			let in_at_block = 0;
15531			let in_selector = false;
15532			let in_block = false;
15533			let level = 0;
15534			let new_css = '';
15535			let new_css_i = 0;
15536			let token;
15537			while ((token = RE_CSS_TOKENIZER.exec(css)?.[0]))
15538			{	if (token == '@')
15539				{	if (!in_block)
15540					{	let at_rule = RE_CSS_TOKENIZER.exec(css)?.[0];
15541						if (!at_rule)
15542						{	break;
15543						}
15544						while ((token = RE_CSS_TOKENIZER.exec(css)?.[0]))
15545						{	if (token == ';')
15546							{	break;
15547							}
15548							if (token == '{')
15549							{	if (at_rule=='media' || at_rule=='layer' || at_rule=='supports')
15550								{	in_at_block++;
15551								}
15552								else
15553								{	while ((token = RE_CSS_TOKENIZER.exec(css)?.[0]) && token!='}');
15554								}
15555								break;
15556							}
15557						}
15558						if (!token)
15559						{	break;
15560						}
15561					}
15562				}
15563				else if (token == '{')
15564				{	if (in_block)
15565					{	return '';
15566					}
15567					in_block = true;
15568					in_selector = false;
15569				}
15570				else if (token == '}')
15571				{	if (in_block)
15572					{	in_block = false;
15573						level = 0;
15574					}
15575					else if (in_at_block > 0)
15576					{	in_at_block--;
15577					}
15578					else
15579					{	return '';
15580					}
15581				}
15582				else if (!in_block && !in_selector && token.trim())
15583				{	if (token.length!=4 || token.toLowerCase()!='html')
15584					{	inject(inject_root_selector, token.length);
15585					}
15586					in_selector = true;
15587				}
15588				else if (!in_block && in_selector && level==0 && token==',')
15589				{	in_selector = false;
15590				}
15591				else if (in_selector && (token=='(' || token=='['))
15592				{	level++;
15593				}
15594				else if (in_selector && (token==')' || token==']'))
15595				{	level--;
15596				}
15597			}
15598			new_css += css.slice(new_css_i);
15599			return new_css;
15600			function inject(what, scroll_back)
15601			{	let i = RE_CSS_TOKENIZER.lastIndex - scroll_back;
15602				new_css += css.slice(new_css_i, i);
15603				new_css_i = i;
15604				new_css += what;
15605			}
15606		}
15607
15608		return {
15609			supress_for_elem,
15610			rollback
15611		};
15612	}
15613
15614// htmlspecialchars
15615function _new_elem_tmp_7(ns)
15616	{	return function(text)
15617		{	return (''+text).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
15618		};
15619	}
15620
15621// load_scripts
15622function _new_elem_tmp_8(ns)
15623	{	var load_script_loading = {};
15624
15625		function load_script(src, checker, ondone)
15626		{	let checker_func =
15627				typeof(checker)=='function' ? checker :
15628				checker && function()
15629				{	let obj = checker[0];
15630					for (let i=1; obj && i<checker.length; i++)
15631					{	obj = obj[checker[i]];
15632					}
15633					return obj;
15634				}
15635			;
15636			function call_ondone(event)
15637			{	let handlers = load_script_loading[src];
15638				if (handlers)
15639				{	let result = checker_func ? checker_func() : event?.type!='error';
15640					for (let i=0; i<handlers.length; i++)
15641					{	handlers[i](src, result);
15642					}
15643					delete load_script_loading[src];
15644					script?.parentNode.removeChild(script);
15645					script = null;
15646				}
15647			}
15648			let i = 0;
15649			function wait()
15650			{	!checker_func || checker_func() ? call_ondone() : i++<100 ? ns.deferred(wait, 600) : call_ondone();
15651			}
15652			let loading = load_script_loading[src];
15653			if (loading)
15654			{	loading.push(ondone);
15655			}
15656			else
15657			{	load_script_loading[src] = [ondone];
15658				if (checker_func?.())
15659				{	call_ondone();
15660				}
15661				else
15662				{	let load_src = src.slice(0, 2)=='//' ? location.protocol+src : src; // sometimes wrong <base> tag is used
15663					var script = ns.new_elem('script', null, {src: load_src, async: true});
15664					if ('onload' in script)
15665					{	script.onload = function()
15666						{	i = 95; // wait (100-95)*600 = 3 sec
15667							ns.deferred(wait, 10); // 1) i assume that wait() will trigger call_ondone() immediately 2) timeout is needed at least for Chrome
15668						};
15669					}
15670					else if ('onreadystatechange' in script)
15671					{	script.onreadystatechange = function()
15672						{	if (script.readyState == 'complete')
15673							{	call_ondone();
15674							}
15675							else if (script.readyState == 'loaded')
15676							{	i = 90; // wait (100-90)*600 = 6 sec
15677								ns.deferred(wait, checker_func ? 20 : 600);
15678							}
15679						};
15680					}
15681					else
15682					{	ns.deferred(wait, checker_func ? 20 : 2000);
15683					}
15684					script.onerror = call_ondone;
15685					(document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script);
15686				}
15687			}
15688		}
15689
15690		return function(sources_checkers, onload, onerror)
15691		{	let sources_checkers_arr = sources_checkers.join ? sources_checkers : [sources_checkers];
15692			let cur = 0;
15693			let failed_srcs = [];
15694			let call_balancer = new ns.CallBalancer
15695			(	-1,
15696				() =>
15697				{	if (failed_srcs.length == 0)
15698					{	if (++cur < sources_checkers_arr.length)
15699						{	call_balancer.dec();
15700							next();
15701						}
15702						else
15703						{	ns.pcall(onload, window);
15704						}
15705					}
15706					else
15707					{	let e = new Error(`load_scripts() failed for ${failed_srcs.length} sources: ${failed_srcs.join(', ')}`);
15708						if (!onerror && !onload)
15709						{	ns.error(e);
15710						}
15711						ns.pcall(onerror || onload, window, [e]);
15712					}
15713				},
15714				(src, result) =>
15715				{	if (!result) failed_srcs.push(src);
15716				}
15717			);
15718			function next()
15719			{	let sc = sources_checkers_arr[cur];
15720				for (let src in sc)
15721				{	if (sc.hasOwnProperty(src))
15722					{	load_script(src, sc[src], call_balancer.dec());
15723					}
15724				}
15725				call_balancer.inc(null, true);
15726			}
15727			next();
15728		};
15729	}
15730
15731// Location
15732function _new_elem_tmp_9(ns)
15733	{	const RE_PROTOCOL = /[^\w\-\+]:/;
15734		const RE_URL_PARAMS = /([^=&]+)(?:=([^&]*))?/g;
15735
15736		return class Location
15737		{	constructor(href, in_context_of_location)
15738			{	if (typeof(href) == 'object')
15739				{	in_context_of_location = href;
15740					href = '';
15741				}
15742				else if (in_context_of_location)
15743				{	if (typeof(in_context_of_location) != 'object')
15744					{	in_context_of_location = new Location(in_context_of_location);
15745					}
15746				}
15747				else
15748				{	in_context_of_location = location;
15749				}
15750				this.protocol = in_context_of_location.protocol;
15751				this.host = in_context_of_location.host;
15752				this.username = in_context_of_location.username;
15753				this.password = in_context_of_location.password;
15754				this.hostname = in_context_of_location.hostname;
15755				this.port = in_context_of_location.port;
15756				this.pathname = in_context_of_location.pathname;
15757				this.search = in_context_of_location.search;
15758				this.hash = in_context_of_location.hash;
15759				var protocol_set = false;
15760				href = href==null ? '' : (href+'').trim();
15761				if (href)
15762				{	let len_p1 = href.length + 1;
15763					let from = 0;
15764					let c = href.charAt(0);
15765					let no_pathname_slash = false;
15766					if (c!='/' && c!='?' && c!='#')
15767					{	let pos = href.indexOf(':');
15768						if (pos!=-1 && pos<=32 && !RE_PROTOCOL.test(href))
15769						{	if (href.substr(pos+1, 2) != '//')
15770							{	no_pathname_slash = true;
15771							}
15772							pos += no_pathname_slash ? 1 : 3;
15773							this.protocol = href.slice(0, pos);
15774							from = pos - 2; // later will +2
15775							protocol_set = true;
15776						}
15777						else
15778						{	let pathname = this.pathname;
15779							let pathname_to = pathname.length;
15780							// example: ./path/path?query#hash or ../path/path?query#hash or path/path?query#hash
15781							if (c == '.')
15782							{	// example: ./path/path?query#hash or ../path/path?query#hash
15783								while (pathname_to > 0)
15784								{	if (href.substr(from, 2) == './')
15785									{	from += 2;
15786									}
15787									else if (href.substr(from, 3) == '../')
15788									{	from += 3;
15789										let pos = pathname.lastIndexOf('/', pathname_to-1);
15790										pathname_to = pos==-1 ? 0 : pos;
15791										while (pathname_to>0 && pathname.charAt(pathname_to-1)=='/')
15792										{	pathname_to--;
15793										}
15794									}
15795									else
15796									{	break;
15797									}
15798									while (href.charAt(from) == '/')
15799									{	from++;
15800									}
15801								}
15802							}
15803							href = pathname.slice(0, pathname.lastIndexOf('/', pathname_to-1)) + '/' + href.slice(from);
15804							from = 0;
15805							len_p1 = href.length + 1;
15806							c = '/'; // assume: href.charAt(0) == '/'
15807						}
15808					}
15809					if (protocol_set || c=='/')
15810					{	// example: //path/path?query#hash or /path/path?query#hash
15811						if (protocol_set || href.charAt(from+1)=='/')
15812						{	let pos = Math.min(href.indexOf('/', from+2)+1 || len_p1, href.indexOf('?', from+2)+1 || len_p1, href.indexOf('#', from+2)+1 || len_p1) - 1;
15813							let host = href.slice(from+2, pos);
15814							let username = '';
15815							let password = '';
15816							let hostname = host;
15817							let port = '';
15818							let a_pos = hostname.lastIndexOf('@');
15819							if (a_pos != -1)
15820							{	username = hostname.slice(0, a_pos);
15821								hostname = hostname.slice(a_pos + 1);
15822								a_pos = username.indexOf(':');
15823								if (a_pos != -1)
15824								{	password = username.slice(a_pos + 1);
15825									username = username.slice(0, a_pos);
15826								}
15827							}
15828							a_pos = hostname.lastIndexOf(':');
15829							if (a_pos != -1)
15830							{	port = hostname.slice(a_pos + 1);
15831								hostname = hostname.slice(0, a_pos);
15832							}
15833							this.host = host;
15834							this.username = username;
15835							this.password = password;
15836							this.hostname = hostname;
15837							this.port = port;
15838							from = href.charAt(pos)=='/' ? pos+1 : pos;
15839						}
15840						else
15841						{	from++;
15842						}
15843						let pos = Math.min(href.indexOf('?', from)+1 || len_p1, href.indexOf('#', from)+1 || len_p1) - 1;
15844						let pathname = href.slice(from, pos);
15845						this.pathname = no_pathname_slash ? pathname : '/'+pathname;
15846						from = pos;
15847						c = href.charAt(from);
15848					}
15849					if (c == '?')
15850					{	// example: ?query#hash
15851						let pos = href.indexOf('#', from+1);
15852						this.search = pos==-1 ? (from==len_p1-2 ? '' : href.slice(from)) : (from+1==pos ? '' : href.slice(from, pos));
15853						this.hash = pos==-1 || pos==len_p1-2 ? '' : href.slice(pos);
15854					}
15855					else if (c == '#')
15856					{	// example: #hash
15857						this.hash = from==len_p1-2 ? '' : href.slice(from);
15858					}
15859					else
15860					{	this.search = '';
15861						this.hash = '';
15862					}
15863				}
15864				if (!protocol_set && in_context_of_location===location)
15865				{	this.protocol += '//';
15866				}
15867				this.get_href(); // update href
15868			}
15869
15870			get_href()
15871			{	this.href = this.protocol + this.host + this.pathname + this.search + this.hash;
15872				return this.href;
15873			}
15874
15875			toString()
15876			{	return this.get_href();
15877			}
15878
15879			params(new_value, set_new_value)
15880			{	var params = {};
15881				var new_str = '';
15882				var url_changed = false;
15883				this.search.slice(1).replace
15884				(	RE_URL_PARAMS,
15885					function(a, k_enc, v_enc)
15886					{	var k = decodeURIComponent(k_enc);
15887						var v = decodeURIComponent(v_enc);
15888						if (new_value?.[k]!==undefined && new_value[k]!=v)
15889						{	url_changed = true; // param is to be modified/removed
15890							if (new_value[k] != null)
15891							{	params[k] = new_value[k];
15892								new_str += '&' + k_enc + '=' + encodeURIComponent(new_value[k]);
15893							}
15894						}
15895						else if (!new_value || !set_new_value || new_value[k]===undefined)
15896						{	params[k] = v;
15897							if (new_value)
15898							{	new_str += '&' + k_enc + '=' + v_enc;
15899							}
15900						}
15901						else
15902						{	url_changed = true; // param is to be removed
15903						}
15904					}
15905				);
15906				if (new_value)
15907				{	for (var new_k in new_value)
15908					{	if (!(new_k in params) && new_value[new_k]!=null)
15909						{	params[new_k] = new_value[new_k];
15910							url_changed = true; // param is to be added
15911							new_str += '&' + encodeURIComponent(new_k) + '=' + encodeURIComponent(new_value[new_k]);
15912						}
15913					}
15914				}
15915				if (url_changed)
15916				{	this.search = !new_str ? '' : '?'+new_str.slice(1);
15917					this.get_href(); // update href
15918				}
15919				return params;
15920			}
15921		};
15922	}
15923
15924// sleep
15925function _new_elem_tmp_10(ns)
15926	{	return function(msec)
15927		{	return new Promise(resolve => {ns.deferred(resolve, msec|0)});
15928		};
15929	}
15930
15931// random_id
15932function _new_elem_tmp_11(ns)
15933	{	return function(id_len)
15934		{	const ABC = 'abcdefghijklmnopqrstuvwxyz0123456789';
15935			id_len = id_len|0 || 4;
15936			let id = '';
15937			for (let j=0; j<id_len; j++)
15938			{	id += ABC.charAt(Math.floor(Math.random() * (j==0 ? 26 : 26+10)));
15939			}
15940			return id;
15941		};
15942	}
15943
15944// infrequent_func
15945function _new_elem_tmp_12(ns)
15946	{	return function(func, period, less)
15947		{	var cancel, owner, args, result;
15948			var is_cancelled = false;
15949			period = period|0;
15950			function call()
15951			{	cancel = null;
15952				if (args)
15953				{	if (!is_cancelled)
15954					{	result = ns.pcall(func, owner, args);
15955					}
15956					is_cancelled = false;
15957					owner = null;
15958					args = null;
15959					if (period > 0)
15960					{	cancel = ns.deferred(call, period);
15961					}
15962				}
15963			}
15964			var infreq = function()
15965			{	is_cancelled = false;
15966				owner = this;
15967				args = arguments;
15968				if (less && period>0)
15969				{	if (cancel) cancel();
15970				}
15971				if (cancel==null && period>0)
15972				{	call();
15973				}
15974				else if (less || period<=0)
15975				{	cancel = ns.deferred(call, period);
15976				}
15977				return result;
15978			};
15979			infreq.cancel = function()
15980			{	is_cancelled = true;
15981			};
15982			infreq.immediate_call = function()
15983			{	is_cancelled = false;
15984				owner = this;
15985				args = arguments;
15986				if (cancel) cancel();
15987				call();
15988				return result;
15989			};
15990			infreq.flush = function()
15991			{	if (cancel != null)
15992				{	call();
15993				}
15994			};
15995			infreq.set_period = function(new_period)
15996			{	period = new_period|0;
15997			};
15998			return infreq;
15999		};
16000	}
16001
16002// ftime
16003function _new_elem_tmp_13(ns)
16004	{	let ftime_value = null;
16005
16006		function get_ftime()
16007		{	if (!ftime_value)
16008			{	var date = new Date(1999, 0, 3); // Sun
16009				var locale = navigator.language;
16010				var weekdays = [];
16011				var weekdays_abbr = [];
16012				var months = [];
16013				var months_abbr = [];
16014				var with_ampm = date.toLocaleTimeString(locale, {hour: 'numeric'}) == date.toLocaleTimeString(locale, {hour: 'numeric', hour12: true});
16015				var time_format = with_ampm ? '%I:%M:%S %p' : '%H:%M:%S';
16016				var time_format_short = with_ampm ? '%I:%M %p' : '%H:%M';
16017				// weekdays
16018				for (var i=0; i<7; i++)
16019				{	date.setDate(3+i);
16020					weekdays[i] = date.toLocaleDateString(locale, {weekday: 'long'});
16021					weekdays_abbr[i] = date.toLocaleDateString(locale, {weekday: 'short'});
16022				}
16023				// months
16024				for (var i=0; i<12; i++)
16025				{	date.setMonth(i);
16026					months[i] = date.toLocaleDateString(locale, {month: 'long'});
16027					months_abbr[i] = date.toLocaleDateString(locale, {month: 'short'});
16028				}
16029				// date_format
16030				date.setDate(3); // 1999-12-03
16031				var date_str = date.toLocaleDateString('en', {day: 'numeric', month: 'short', year: '2-digit'});
16032				var day_str = date.toLocaleDateString('en', {day: 'numeric'});
16033				var month_str = date.toLocaleDateString('en', {month: 'short'});
16034				var year_str = date.toLocaleDateString('en', {year: '2-digit'});
16035				var date_format = date_str.replace(day_str, '%d').replace(month_str, '%b').replace(year_str, '%y');
16036				// first_weekday
16037				var first_weekday = navigator.language=='he' ? 0 : navigator.language=='ar' ? 6 : 1;
16038				//
16039				ftime_value = {weekdays, weekdays_abbr, months, months_abbr, with_ampm, time_format, time_format_short, date_format, first_weekday};
16040			}
16041			return ftime_value;
16042		}
16043
16044		function lpad(v, n_digits, fill_char)
16045		{	fill_char = fill_char ? ''+fill_char : '0000';
16046			while (fill_char.length < n_digits)
16047			{	fill_char += fill_char;
16048			}
16049			v = ''+v;
16050			return fill_char.slice(0, n_digits-v.length) + v;
16051		}
16052
16053		function cond_zeropad(value, yes)
16054		{	return yes ? lpad(value, 2) : value;
16055		}
16056
16057		function strftime(fmt, time, is_gm)
16058		{	var locale = navigator.language;
16059			var date = time==null || time==='' ? new Date() : (time instanceof Date) ? time : new Date(time*1000);
16060			var time_zone = is_gm ? 'GMT' : undefined;
16061			return (fmt || '%c').replace
16062			(	/%(0?)(.)/g,
16063				function(all, zero, f)
16064				{	if (f == 'j')
16065					{	var d = new Date(date.getTime());
16066						d.setMonth(0);
16067						d.setDate(1);
16068						return (date.getTime() - d.getTime()) / (24*60*60*1000);
16069					}
16070					if (f == 'Z')
16071					{	if (is_gm) return "GMT+0000";
16072						var tz = date.getTimezoneOffset();
16073						var atz = Math.abs(tz);
16074						var m = atz % 60;
16075						var h = (atz-m) / 60;
16076						var gmt = tz<=0 ? 'GMT+' : 'GMT-';
16077						return gmt + lpad(h, 2) + lpad(m, 2);
16078					}
16079					return f=='A' ? date.toLocaleDateString(locale, {timeZone: time_zone, weekday: 'long'}) :
16080						f=='a' ? date.toLocaleDateString(locale, {timeZone: time_zone, weekday: 'short'}) :
16081						f=='B' ? date.toLocaleDateString(locale, {timeZone: time_zone, month: 'long'}) :
16082						f=='b' ? date.toLocaleDateString(locale, {timeZone: time_zone, month: 'short'}) :
16083						f=='D' ? date.toLocaleDateString(locale, {timeZone: time_zone, day: 'numeric', month: 'short', year: '2-digit'}) :
16084						f=='x' ? date.toLocaleDateString(locale, {timeZone: time_zone, day: 'numeric', month: 'short'}) :
16085						f=='T' || f=='r' || f=='X' ? date.toLocaleTimeString(locale, {timeZone: time_zone}) :
16086						f=='R' ? date.toLocaleTimeString(locale, {timeZone: time_zone, hour: 'numeric', minute: 'numeric'}) :
16087						f=='c' ? strftime('%D %T', date, is_gm) :
16088						f=='d' ? cond_zeropad(is_gm ? date.getUTCDate() : date.getDate(), zero) :
16089						f=='H' ? cond_zeropad(is_gm ? date.getUTCHours() : date.getHours(), zero) :
16090						f=='I' ? cond_zeropad(((is_gm ? date.getUTCHours() : date.getHours()) % 12) || 12, zero) :
16091						f=='m' ? cond_zeropad((is_gm ? date.getUTCMonth() : date.getMonth())+1, zero) :
16092						f=='M' ? cond_zeropad(is_gm ? date.getUTCMinutes() : date.getMinutes(), zero) :
16093						f=='p' ? ((is_gm ? date.getUTCHours() : date.getHours())<12 ? 'AM' : 'PM') :
16094						f=='S' ? cond_zeropad(is_gm ? date.getUTCSeconds() : date.getSeconds(), zero) :
16095						f=='y' ? (is_gm ? date.getUTCFullYear() : date.getYear()) % 100 :
16096						f=='Y' ? (is_gm ? date.getUTCFullYear() : date.getFullYear()) :
16097						f;
16098				}
16099			);
16100		}
16101
16102		function gmstrftime(fmt, time)
16103		{	return strftime(fmt, time, true);
16104		}
16105
16106		const obj =
16107		{	get_ftime,
16108			strftime,
16109			gmstrftime,
16110		};
16111
16112		return obj;
16113	}
16114
16115// get_cookie
16116function _new_elem_tmp_14(ns)
16117	{	return function(name)
16118		{	var value;
16119			document.cookie.replace
16120			(	/(?:^|;)\s*([^;=]+)=([^;]+)/g,
16121				function(a, n, v)
16122				{	if (value===undefined && decodeURIComponent(n)==name)
16123					{	value = decodeURIComponent(v);
16124					}
16125				}
16126			);
16127			return value;
16128		};
16129	}
16130
16131// strhash
16132function _new_elem_tmp_15(ns)
16133	{	return function(str)
16134		{	const HASHWORDBITS = 32;
16135			var sum = 0;
16136			var shift_by = 8;
16137			for (let i=0, i_end=str.length; i<i_end; i++)
16138			{	if (i == HASHWORDBITS/8)
16139				{	shift_by = 7;
16140				}
16141				let c = (str.charCodeAt(i) ^ i) & 0xFF;
16142				let high = sum & (0xFF << (HASHWORDBITS - shift_by));
16143				sum ^= high; // zero high 8 or 7 bits
16144				sum = (sum << shift_by) ^ (high >> (HASHWORDBITS - shift_by)) ^ c;
16145			}
16146			return sum;
16147		};
16148	}
16149
16150if (!('ElemsAnnotator' in NS)) NS['ElemsAnnotator'] = _new_elem_tmp_0.call(NS, NS);
16151if (!('inject_code' in NS)) NS['inject_code'] = _new_elem_tmp_2.call(NS, NS);
16152if (!('joyquery' in NS)) NS['joyquery'] = _new_elem_tmp_3.call(NS, NS);
16153if (!('popup_global_parent' in NS)) NS['popup_global_parent'] = _new_elem_tmp_5.call(NS, NS);
16154if (!('uacss' in NS)) NS['uacss'] = _new_elem_tmp_6.call(NS, NS);
16155if (!('htmlspecialchars' in NS)) NS['htmlspecialchars'] = _new_elem_tmp_7.call(NS, NS);
16156if (!('load_scripts' in NS)) NS['load_scripts'] = _new_elem_tmp_8.call(NS, NS);
16157if (!('Location' in NS)) NS['Location'] = _new_elem_tmp_9.call(NS, NS);
16158if (!('sleep' in NS)) NS['sleep'] = _new_elem_tmp_10.call(NS, NS);
16159if (!('random_id' in NS)) NS['random_id'] = _new_elem_tmp_11.call(NS, NS);
16160if (!('infrequent_func' in NS)) NS['infrequent_func'] = _new_elem_tmp_12.call(NS, NS);
16161if (!('ftime' in NS)) NS['ftime'] = _new_elem_tmp_13.call(NS, NS);
16162if (!('get_cookie' in NS)) NS['get_cookie'] = _new_elem_tmp_14.call(NS, NS);
16163if (!('strhash' in NS)) NS['strhash'] = _new_elem_tmp_15.call(NS, NS);
16164if (!('_s_t_loaded' in NS)) NS['_s_t_loaded'] = _new_elem_tmp_1.call(NS, NS, NS["infrequent_func"], NS["ftime"], NS["new_elem"]);
16165if (!('joyquery_ext' in NS)) NS['joyquery_ext'] = _new_elem_tmp_4.call(NS, NS, NS["joyquery"]);
16166
16167})(); // end ns

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.