PageSourceSearch

https://toveedo.com/sign_in

html toveedo.com collected 2026-10-02 15:13:13 UTC 217,475 bytes, 6,680 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <title>Toveedo</title>
5    <meta charset="utf-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7      <link
8  rel="icon"
9  href="https://toveedo.com/images/favicontypes/662/lxdymwvws1srejfy94nq_t.png"
10>
11<link
12  rel="apple-touch-icon"
13  sizes="180x180"
14  href="https://toveedo.com/images/favicontypes/662/lxdymwvws1srejfy94nq_t.png"
15>
16
17    <meta name="csrf-param" content="authenticity_token" />
18<meta name="csrf-token" content="Oj6IAwuIWZfWdeMWA9qLV5uqAiV7kMpHvjtmN22_pbbDUh-0YmcJpZcbsoT5gXbKVgPx5lCHQ_ohcbX9xqh5zw" />
19    <link rel="stylesheet" href="https://assets-gke.uscreencdn.com/vite/assets/tailwind-CeQmPIAS.css" media="all" />
20    <link rel="stylesheet" href="https://assets-gke.uscreencdn.com/vite/assets/vue_storefront-AgU0tiPx.css" media="all" />
21
22    
22<script src="https://assets-gke.uscreencdn.com/vite/assets/web-components-BnSq9iDb.js" crossorigin="anonymous" type="module"></script>
22<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/preload-helper-s3Hva0Xc.js" as="script" crossorigin="anonymous">
23    
23<script src="https://assets-gke.uscreencdn.com/vite/assets/storefront_hotwire-BZ9GwY5K.js" crossorigin="anonymous" type="module"></script>
23<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/preload-helper-s3Hva0Xc.js" as="script" crossorigin="anonymous">
24<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/excludeTurboTrainBroadcastReactEvents-Ch9QDwMS.js" as="script" crossorigin="anonymous">
25<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/VideoTracker-DKtiOlZs.js" as="script" crossorigin="anonymous">
26<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/turbo.es2017-esm-CbrezRZI.js" as="script" crossorigin="anonymous">
27<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/_commonjsHelpers-BosuxZz1.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="https://assets-gke.uscreencdn.com/vite/assets/storefront_hotwire-DtHu4oK3.css" media="screen" />
28
29    <!-- Google Analytics -->
30<!-- End Google Analytics -->
31
32    
32<script>
33      window.apiUrl = "https://api.uscreencdn.com"
34      window.apiOrigin = "torahtreasure"
35    </script>
35
36      
36<script>
37        window.hotwireCatalog = true
38      </script>
38
39    <!-- Feature: Toveedo global Uscreen head customizations. Tag: catalog arrows, header/footer sync, profile helpers, player watermark. -->
40<style>
41  [data-program-video-target="shareCtaContainer"] .flex.flex-col.items-center.relative.gap-4.w-full.h-full.justify-center.px-4 {
42    display: none;
43  }
44
45  :root {
46    --card-shadow-height: 0px;
47  }
48
49  #catalog_filters > div {
50    background-color: #41d0ff47;
51    position: relative;
52  }
53  #catalog_filters > div:before,
54  #catalog_filters > div:after {
55    content: '';
56    position: absolute;
57    background-color: #41d0ff47;
58    width: 100%;
59    height: 1rem;
60  }
61  #catalog_filters > div:before {
62    top: -1rem;
63  }
64  #catalog_filters > div:after {
65    bottom: -1rem;
66  }
67  #add_to_calendar {
68    display: none;
69  }
70  [data-test="filters-list"] ds-select {
71    display: flex;
72    justify-content: center;
73    align-items: center;
74    gap: .5rem;
75  }
76  [data-test="filters-list"] ds-select::after {
77    content: "";
78    display: block;
79    width: 32px;
80    height: 32px;
81    background: #41d0ff url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16.3049 14.9248C17.367 13.5671 18 11.8575 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C11.8399 18 13.5348 17.3788 14.8864 16.3348L18.2965 19.7107C18.689 20.0992 19.3222 20.096 19.7107 19.7036C20.0993 19.3111 20.0961 18.6779 19.7036 18.2894L16.3049 14.9248ZM16 10C16 13.3137 13.3137 16 10 16C6.68629 16 4 13.3137 4 10C4 6.68629 6.68629 4 10 4C13.3137 4 16 6.68629 16 10Z' fill='%23fff'/%3E%3C/svg%3E")
82      center / 24px 24px no-repeat;
83    border-radius: 4px;
84  }
85  [data-custom="content-card"] {
86    // zoom: 1.25;
87  }
88  .category-group ds-icon[name="chevron-right"],
89  .category-group ds-icon[name="chevron-left"] {
90    visibility: visible !important;
91    opacity: 0;
92  }
93  .category-group .catalog-row-overflow {
94    overflow: visible;
95  }
96  .category-group [slot="next-button"] {
97    background: url('https://unode1.s3.amazonaws.com/assets%2F%2Fassets%2F15576%2F%2Fp8sbaktgr1eya4y9z9bv_arrow_new-next-1.1778771546.svg') 0% 62% no-repeat !important;
98    background-size: 23px !important;
99    position: relative;
100  }
101  .category-group [slot="next-button"]:before {
102    content: "";
103    display: block;
104    position: absolute;
105    bottom: 0;
106    right: 0;
107    width: 200%;
108    height: var(--card-shadow-height);
109    background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 77%);
110    z-index: -1;
111  }
112  .category-group [slot="prev-button"] {
113    background: url('https://unode1.s3.amazonaws.com/assets%2F%2Fassets%2F15576%2F%2Ftz649pbtlwmfy0ougiyg_arrow_new-prev-1.1778771539.svg') 60% 62% no-repeat !important;
114    background-size: 23px  !important;
115    display: block !important;
116  }
117  .category-group [slot="prev-button"]:before {
118    content: "";
119    display: block;
120    position: absolute;
121    bottom: 0;
122    left: 0;
123    width: 200%;
124    height: var(--card-shadow-height);
125    background: linear-gradient(270deg, rgba(0, 0, 0, 0) 33%, rgba(0, 0, 0, 1) 92%);
126    z-index: -1;
127  }
128  .catalog-constrained .catalog-area, .catalog-constrained .catalog-filter-preloader, .catalog-constrained .catalog-max-width {
129    max-width: none;
130  }
131  .toveedo-uscreen-header-dropdown {
132    display: none !important;
133  }
134  #website-links a.text-ds-default {
135    color: #fff !important;
136  }
137  #website-links {
138    margin-right: 0;
139  }
140  .w-full.border-b.border-ds-default.bg-ds-main > .flex.w-max.min-w-full {
141    display: none;
142  }
143  [data-controller="themeless-featured-slider"] > ds-swiper + div {
144    zoom: 2.5;
145    overflow: visible;
146  }
147  [data-controller="themeless-featured-slider"] .aspect-\[19\/8\] {
148    aspect-ratio: 19 / 6;
149  }
150  [data-controller="themeless-featured-slider"]
151    [data-themeless-featured-slider-target="paginationItem"]
152    button {
153    position: relative;
154  }
155  [data-controller="themeless-featured-slider"]
156    [data-themeless-featured-slider-target="paginationItem"]
157    button:before {
158    content: "";
159    width: 120%;
160    height: 40px;
161    top: -20px;
162    left: 10%;
163    position: absolute;
164    display: block;
165  }
166</style>
167
168<!-- Feature: Sync Toveedo content-card image height to --card-shadow-height for catalog arrow fades. Tag: Uscreen content-card sizing, ResizeObserver, Turbo-safe lifecycle. -->
169<script>
170  (function () {
171    var SWIPER_CARD_IMAGE_SELECTOR =
172      'swiper-slide:not(.hidden) [data-custom="content-card"] .card-image-container img';
173    var CARD_IMAGE_SELECTOR =
174      '[data-custom="content-card"] .card-image-container img';
175    var CSS_VAR_NAME = "--card-shadow-height";
176    var currentImage = null;
177    var imageLoadHandler = null;
178    var resizeObserver = null;
179    var mutationObserver = null;
180    var syncFrame = 0;
181    var lastHeight = "";
182
183    function findSampleImage() {
184      return (
185        document.querySelector(SWIPER_CARD_IMAGE_SELECTOR) ||
186        document.querySelector(CARD_IMAGE_SELECTOR)
187      );
188    }
189
190    function setShadowHeight(image) {
191      if (!image || !image.isConnected) return false;
192
193      var height = image.getBoundingClientRect().height;
194      if (!(height > 0)) return false;
195
196      var nextHeight = Math.round(height * 100) / 100 + "px";
197      if (nextHeight !== lastHeight) {
198        document.documentElement.style.setProperty(CSS_VAR_NAME, nextHeight);
199        lastHeight = nextHeight;
200      }
201
202      return true;
203    }
204
205    function disconnectImageObserver() {
206      if (currentImage && imageLoadHandler) {
207        currentImage.removeEventListener("load", imageLoadHandler);
208      }
209
210      if (resizeObserver) {
211        resizeObserver.disconnect();
212        resizeObserver = null;
213      }
214
215      currentImage = null;
216      imageLoadHandler = null;
217    }
218
219    function watchImage(image) {
220      if (!image || image === currentImage) return;
221
222      disconnectImageObserver();
223      currentImage = image;
224      imageLoadHandler = scheduleSync;
225      currentImage.addEventListener("load", imageLoadHandler);
226
227      if ("ResizeObserver" in window) {
228        resizeObserver = new ResizeObserver(scheduleSync);
229        resizeObserver.observe(currentImage);
230
231        var container = currentImage.closest(".card-image-container");
232        if (container) {
233          resizeObserver.observe(container);
234        }
235      }
236    }
237
238    function syncCardShadowHeight() {
239      var image = currentImage;
240
241      if (!image || !image.isConnected || !setShadowHeight(image)) {
242        image = findSampleImage();
243        if (!image) return;
244
245        watchImage(image);
246        setShadowHeight(image);
247      }
248    }
249
250    function scheduleSync() {
251      if (syncFrame) return;
252
253      syncFrame = window.requestAnimationFrame(function () {
254        syncFrame = 0;
255        syncCardShadowHeight();
256      });
257    }
258
259    function startDomObserver() {
260      if (mutationObserver || !document.documentElement) return;
261
262      mutationObserver = new MutationObserver(scheduleSync);
263      mutationObserver.observe(document.documentElement, {
264        childList: true,
265        subtree: true,
266        attributes: true,
267        attributeFilter: ["class"],
268      });
269    }
270
271    function initCardShadowHeightSync() {
272      startDomObserver();
273      scheduleSync();
274    }
275
276    document.addEventListener("turbo:load", scheduleSync);
277    document.addEventListener("turbo:render", scheduleSync);
278    document.addEventListener("turbo:frame-load", scheduleSync);
279    window.addEventListener("pageshow", scheduleSync);
280    window.addEventListener("resize", scheduleSync);
281
282    if (document.readyState === "loading") {
283      document.addEventListener("DOMContentLoaded", initCardShadowHeightSync, {
284        once: true,
285      });
286    } else {
287      initCardShadowHeightSync();
288    }
289  })();
290</script>
290
291<link rel="profile" href="https://gmpg.org/xfn/11" />
292<link
293  rel="apple-touch-icon"
294  sizes="180x180"
295  href="https://uscreen-customizations.b-cdn.net/toveedo/brand/toveedo-browser-icon.png"
296/>
297<link
298  rel="icon"
299  type="image/png"
300  sizes="32x32"
301  href="https://uscreen-customizations.b-cdn.net/toveedo/brand/toveedo-browser-icon.png"
302/>
303<link
304  rel="icon"
305  type="image/png"
306  sizes="16x16"
307  href="https://uscreen-customizations.b-cdn.net/toveedo/brand/toveedo-browser-icon.png"
308/>
309<link
310  rel="shortcut icon"
311  type="image/png"
312  href="https://uscreen-customizations.b-cdn.net/toveedo/brand/toveedo-browser-icon.png"
313/>
314<link
315  rel="manifest"
316  href="https://s3.amazonaws.com/unode1/assets/556/rDcArYznT8a4NHzQNSYy_site.webmanifest"
317/>
318<link
319  rel="mask-icon"
320  href="https://s3.amazonaws.com/unode1/assets/556/dQ4wKg5RZW8mDKlz3FD5_safari-pinned-tab.svg"
321  color="#5bbad5"
322/>
323<meta name="msapplication-TileColor" content="#ffffff" />
324<meta name="theme-color" content="#ffffff" />
325
326<script>
327  (function () {
328    var SESSION_ENDPOINT = "https://toveedo.com/api/sessions";
329    var MIN_SYNC_INTERVAL_MS = 60 * 1000;
330    var lastSyncStartedAt = 0;
331
332    function broadcastMemberState(state, syncedAt) {
333      var detail = {
334        state: state,
335        loggedIn: state === "logged_in",
336        syncedAt: syncedAt,
337      };
338
339      window.__TOVEEDO_MEMBER_STATE__ = detail;
340
341      if (typeof window.CustomEvent === "function") {
342        window.dispatchEvent(
343          new CustomEvent("toveedo:member-state-sync", { detail: detail }),
344        );
345      }
346    }
347
348    function persistMemberState(state) {
349      var syncedAt = String(Date.now());
350      broadcastMemberState(state, syncedAt);
351    }
352
353    function syncMemberState() {
354      var now = Date.now();
355
356      if (now - lastSyncStartedAt < MIN_SYNC_INTERVAL_MS) {
357        return;
358      }
359
360      lastSyncStartedAt = now;
361
362      window
363        .fetch(SESSION_ENDPOINT, {
364          credentials: "include",
365          headers: { Accept: "application/json" },
366          cache: "no-store",
367        })
368        .then(function (response) {
369          if (!response.ok) {
370            throw new Error(
371              "Session sync failed with status " + response.status,
372            );
373          }
374
375          return response.json();
376        })
377        .then(function (payload) {
378          persistMemberState(
379            payload && payload.logged === true ? "logged_in" : "guest",
380          );
381        })
382        .catch(function () {
383          if (!window.__TOVEEDO_MEMBER_STATE__) {
384            persistMemberState("guest");
385          }
386        });
387    }
388
389    syncMemberState();
390    window.addEventListener("focus", syncMemberState);
391    window.addEventListener("pageshow", syncMemberState);
392    document.addEventListener("visibilitychange", function () {
393      if (document.visibilityState === "visible") {
394        syncMemberState();
395      }
396    });
397    window.setInterval(syncMemberState, 5 * 60 * 1000);
398  })();
399</script>
399
400
401<script>
402  (function () {
403    var HEADER_API_URL = "https://home.toveedo.com/api/header-menu";
404    var HEADER_HOME_URL = "https://toveedo.com";
405    var HEADER_TARGET_SELECTOR = "#website-header #website-links";
406    var HEADER_MOBILE_TARGET_SELECTOR =
407      '[data-themeless--header-target="menuNavigation"]';
408    var HEADER_LOGO_SELECTOR = "#website-header #store-logo img";
409    var HEADER_LOGO_LINK_SELECTOR = "#website-header #store-logo";
410    var HEADER_LOGO_URL =
411      "https://uscreen-customizations.b-cdn.net/toveedo/home_hero_logo_e9cc7d78af.svg";
412    var HEADER_MARKER = "data-toveedo-uscreen-header-dropdown";
413    var HEADER_MOBILE_MARKER = "data-toveedo-uscreen-header-mobile-menu";
414    var HEADER_STYLE_MARKER = "data-toveedo-uscreen-header-dropdown-style";
415    var HEADER_SIGNATURE_ATTR = "data-toveedo-uscreen-header-signature";
416    var headerPromise = null;
417    var syncQueued = false;
418    var observer = null;
419    var documentBound = false;
420
421    function ensureHeaderStyles() {
422      if (document.head.querySelector("style[" + HEADER_STYLE_MARKER + "]")) {
423        return;
424      }
425
426      var style = document.createElement("style");
427      style.setAttribute(HEADER_STYLE_MARKER, "true");
428      style.textContent =
429        ".toveedo-uscreen-header-dropdown{position:relative;display:flex;align-items:center;height:100%;margin-right:1.25rem;flex:none;}" +
430        ".toveedo-uscreen-header-dropdown__toggle{appearance:none;border:0;background:transparent;padding:0;color:#fff;font-family:'Open Sans',sans-serif;font-size:1rem;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;gap:.65rem;line-height:1;}" +
431        ".toveedo-uscreen-header-dropdown__toggle svg{display:block;transition:transform .2s ease;}" +
432        ".toveedo-uscreen-header-dropdown.is-open .toveedo-uscreen-header-dropdown__toggle svg{transform:rotate(180deg);}" +
433        ".toveedo-uscreen-header-dropdown__menu{position:absolute;top:calc(100% + .9rem);right:-1rem;z-index:150;min-width:11.5rem;padding:.6rem 0;border:1px solid rgba(255,255,255,.3);border-radius:.9rem;background:#41d0ff;box-shadow:0 18px 40px rgba(0,0,0,.16);}" +
434        ".toveedo-uscreen-header-dropdown__link{display:block;padding:.7rem 1rem;color:#fff !important;font-family:'Open Sans',sans-serif;font-size:.98rem;font-weight:700;line-height:1.35;text-decoration:none;transition:background-color .18s ease;white-space:nowrap;}" +
435        ".toveedo-uscreen-header-dropdown__link:hover,.toveedo-uscreen-header-dropdown__link:focus-visible{background:rgba(255,255,255,.1);}" +
436        ".toveedo-uscreen-header-mobile-menu{padding:0 1rem .5rem;border-bottom:1px solid rgba(255,255,255,.18);margin-bottom:.35rem;}" +
437        ".toveedo-uscreen-header-mobile-menu__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.1rem;}" +
438        ".toveedo-uscreen-header-mobile-menu__link{display:block;padding:.55rem 0;color:inherit !important;font-family:'Open Sans',sans-serif;font-size:1rem;font-weight:600;line-height:1.25;text-decoration:none;}" +
439        ".toveedo-uscreen-header-mobile-menu__link:hover,.toveedo-uscreen-header-mobile-menu__link:focus-visible{text-decoration:underline;text-underline-offset:2px;}" +
440        "@media (max-width:767px){.toveedo-uscreen-header-dropdown{display:none !important;}}";
441
442      document.head.appendChild(style);
443    }
444
445    function escapeHtml(value) {
446      return String(value || "")
447        .replace(/&/g, "&amp;")
448        .replace(/</g, "&lt;")
449        .replace(/>/g, "&gt;")
450        .replace(/"/g, "&quot;")
451        .replace(/'/g, "&#39;");
452    }
453
454    function normalizeHeaderUrl(url) {
455      if (!url) return "#";
456      if (/^(?:https?:)?\/\//i.test(url) || url.charAt(0) === "#") return url;
457      if (url.charAt(0) === "/") return HEADER_HOME_URL + url;
458      return HEADER_HOME_URL + "/" + url.replace(/^\/+/, "");
459    }
460
461    function fetchHeaderPayload() {
462      if (!headerPromise) {
463        headerPromise = window
464          .fetch(HEADER_API_URL, {
465            mode: "cors",
466            credentials: "omit",
467            headers: { Accept: "application/json" },
468            cache: "no-store",
469          })
470          .then(function (response) {
471            if (!response.ok) {
472              throw new Error(
473                "Header sync failed with status " + response.status,
474              );
475            }
476
477            return response.json();
478          })
479          .catch(function (error) {
480            headerPromise = null;
481            throw error;
482          });
483      }
484
485      return headerPromise;
486    }
487
488    function getHeaderItems(payload) {
489      return (
490        (Array.isArray(payload && payload.popupItems) && payload.popupItems) ||
491        (Array.isArray(payload && payload.items) && payload.items) ||
492        []
493      );
494    }
495
496    function getHeaderSignature(items) {
497      return JSON.stringify(
498        items.map(function (item) {
499          return {
500            label: item && item.label,
501            url: item && item.url,
502            openInNewTab: Boolean(item && item.openInNewTab),
503          };
504        }),
505      );
506    }
507
508    function loggedInMember() {
509      var memberState =
510        window.__TOVEEDO_MEMBER_STATE__ &&
511        window.__TOVEEDO_MEMBER_STATE__.state;
512      if (memberState === "logged_in") return true;
513      if (memberState === "guest") return false;
514
515      return false;
516    }
517
518    function buildHeaderMarkup(payload) {
519      var items = getHeaderItems(payload);
520      var signature = escapeHtml(getHeaderSignature(items));
521
522      var linksHtml = items
523        .map(function (item) {
524          var target =
525            item && item.openInNewTab
526              ? ' target="_blank" rel="noopener noreferrer"'
527              : "";
528
529          return (
530            '<a class="toveedo-uscreen-header-dropdown__link" href="' +
531            normalizeHeaderUrl(item && item.url) +
532            '"' +
533            target +
534            ">" +
535            escapeHtml(item && item.label) +
536            "</a>"
537          );
538        })
539        .join("");
540
541      return (
542        '<div class="toveedo-uscreen-header-dropdown" ' +
543        HEADER_MARKER +
544        '="true" ' +
545        HEADER_SIGNATURE_ATTR +
546        '="' +
547        signature +
548        '">' +
549        '<button class="toveedo-uscreen-header-dropdown__toggle" type="button" aria-expanded="false" aria-haspopup="true">' +
550        "<span>Menu</span>" +
551        '<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
552        '<path d="M5 5L0 0H10L5 5Z" fill="currentColor" />' +
553        "</svg>" +
554        "</button>" +
555        '<div class="toveedo-uscreen-header-dropdown__menu" hidden>' +
556        linksHtml +
557        "</div>" +
558        "</div>"
559      );
560    }
561
562    function buildMobileHeaderMarkup(payload) {
563      var items = getHeaderItems(payload);
564      var signature = escapeHtml(getHeaderSignature(items));
565
566      var linksHtml = items
567        .map(function (item) {
568          var target =
569            item && item.openInNewTab
570              ? ' target="_blank" rel="noopener noreferrer"'
571              : "";
572
573          return (
574            '<li><a class="toveedo-uscreen-header-mobile-menu__link" href="' +
575            normalizeHeaderUrl(item && item.url) +
576            '"' +
577            target +
578            ">" +
579            escapeHtml(item && item.label) +
580            "</a></li>"
581          );
582        })
583        .join("");
584
585      return (
586        '<nav class="toveedo-uscreen-header-mobile-menu" ' +
587        HEADER_MOBILE_MARKER +
588        '="true" ' +
589        HEADER_SIGNATURE_ATTR +
590        '="' +
591        signature +
592        '">' +
593        '<ul class="toveedo-uscreen-header-mobile-menu__list">' +
594        linksHtml +
595        "</ul>" +
596        "</nav>"
597      );
598    }
599
600    function closeHeaderDropdown(dropdownRoot) {
601      if (!dropdownRoot) return;
602
603      var toggle = dropdownRoot.querySelector(
604        ".toveedo-uscreen-header-dropdown__toggle",
605      );
606      var menu = dropdownRoot.querySelector(
607        ".toveedo-uscreen-header-dropdown__menu",
608      );
609
610      if (!toggle || !menu) return;
611
612      dropdownRoot.classList.remove("is-open");
613      toggle.setAttribute("aria-expanded", "false");
614      menu.hidden = true;
615    }
616
617    function openHeaderDropdown(dropdownRoot) {
618      if (!dropdownRoot) return;
619
620      var toggle = dropdownRoot.querySelector(
621        ".toveedo-uscreen-header-dropdown__toggle",
622      );
623      var menu = dropdownRoot.querySelector(
624        ".toveedo-uscreen-header-dropdown__menu",
625      );
626
627      if (!toggle || !menu) return;
628
629      dropdownRoot.classList.add("is-open");
630      toggle.setAttribute("aria-expanded", "true");
631      menu.hidden = false;
632    }
633
634    function bindHeaderDocumentEvents() {
635      if (documentBound) return;
636
637      document.addEventListener("click", function (event) {
638        Array.from(
639          document.querySelectorAll("[" + HEADER_MARKER + "]"),
640        ).forEach(function (dropdownRoot) {
641          if (!dropdownRoot.contains(event.target)) {
642            closeHeaderDropdown(dropdownRoot);
643          }
644        });
645      });
646
647      document.addEventListener("keydown", function (event) {
648        if (event.key !== "Escape") return;
649
650        Array.from(
651          document.querySelectorAll("[" + HEADER_MARKER + "]"),
652        ).forEach(function (dropdownRoot) {
653          closeHeaderDropdown(dropdownRoot);
654        });
655      });
656
657      documentBound = true;
658    }
659
660    function bindHeaderDropdown(dropdownRoot) {
661      if (
662        !dropdownRoot ||
663        dropdownRoot.dataset.toveedoDropdownBound === "true"
664      ) {
665        return;
666      }
667
668      var toggle = dropdownRoot.querySelector(
669        ".toveedo-uscreen-header-dropdown__toggle",
670      );
671      var menu = dropdownRoot.querySelector(
672        ".toveedo-uscreen-header-dropdown__menu",
673      );
674
675      if (!toggle || !menu) return;
676
677      toggle.addEventListener("click", function (event) {
678        event.preventDefault();
679        event.stopPropagation();
680
681        if (menu.hidden) {
682          Array.from(
683            document.querySelectorAll("[" + HEADER_MARKER + "]"),
684          ).forEach(function (otherDropdownRoot) {
685            if (otherDropdownRoot !== dropdownRoot) {
686              closeHeaderDropdown(otherDropdownRoot);
687            }
688          });
689          openHeaderDropdown(dropdownRoot);
690        } else {
691          closeHeaderDropdown(dropdownRoot);
692        }
693      });
694
695      dropdownRoot.dataset.toveedoDropdownBound = "true";
696      closeHeaderDropdown(dropdownRoot);
697    }
698
699    function syncHeaderLogo() {
700      var headerLogo = document.querySelector(HEADER_LOGO_SELECTOR);
701      var headerLogoLink = document.querySelector(HEADER_LOGO_LINK_SELECTOR);
702      if (!headerLogo) return;
703
704      if (headerLogo.getAttribute("src") !== HEADER_LOGO_URL) {
705        headerLogo.setAttribute("src", HEADER_LOGO_URL);
706      }
707
708      if (headerLogo.hasAttribute("srcset")) {
709        headerLogo.removeAttribute("srcset");
710      }
711
712      if (headerLogoLink) {
713        if (!headerLogoLink.dataset.toveedoDefaultHref) {
714          headerLogoLink.dataset.toveedoDefaultHref =
715            headerLogoLink.getAttribute("href") || "/";
716        }
717
718        headerLogoLink.setAttribute(
719          "href",
720          loggedInMember()
721            ? "/catalog"
722            : headerLogoLink.dataset.toveedoDefaultHref,
723        );
724      }
725    }
726
727    function syncHeaderDropdown() {
728      var target = document.querySelector(HEADER_TARGET_SELECTOR);
729      var mobileTarget = document.querySelector(HEADER_MOBILE_TARGET_SELECTOR);
730      syncHeaderLogo();
731      if (
732        (!target || !target.parentNode) &&
733        (!mobileTarget || !mobileTarget.parentNode)
734      ) {
735        return;
736      }
737
738      ensureHeaderStyles();
739
740      fetchHeaderPayload()
741        .then(function (payload) {
742          var existingDropdown = document.querySelector(
743            "[" + HEADER_MARKER + "]",
744          );
745          var markup = buildHeaderMarkup(payload);
746          var existingMobileMenu = document.querySelector(
747            "[" + HEADER_MOBILE_MARKER + "]",
748          );
749          var mobileMarkup = buildMobileHeaderMarkup(payload);
750          var nextSignature = getHeaderSignature(getHeaderItems(payload));
751
752          if (target && target.parentNode) {
753            if (existingDropdown) {
754              if (existingDropdown.previousElementSibling !== target) {
755                existingDropdown.remove();
756                target.insertAdjacentHTML("afterend", markup);
757              } else if (
758                existingDropdown.getAttribute(HEADER_SIGNATURE_ATTR) !==
759                nextSignature
760              ) {
761                existingDropdown.outerHTML = markup;
762              }
763            } else {
764              target.insertAdjacentHTML("afterend", markup);
765            }
766          }
767
768          if (mobileTarget && mobileTarget.parentNode) {
769            if (existingMobileMenu) {
770              if (existingMobileMenu.nextElementSibling !== mobileTarget) {
771                existingMobileMenu.remove();
772                mobileTarget.insertAdjacentHTML("beforebegin", mobileMarkup);
773              } else if (
774                existingMobileMenu.getAttribute(HEADER_SIGNATURE_ATTR) !==
775                nextSignature
776              ) {
777                existingMobileMenu.outerHTML = mobileMarkup;
778              }
779            } else {
780              mobileTarget.insertAdjacentHTML("beforebegin", mobileMarkup);
781            }
782          }
783
784          bindHeaderDocumentEvents();
785          bindHeaderDropdown(document.querySelector("[" + HEADER_MARKER + "]"));
786        })
787        .catch(function (error) {
788          console.error(error);
789        });
790    }
791
792    function scheduleHeaderSync() {
793      if (syncQueued) return;
794
795      syncQueued = true;
796      window.requestAnimationFrame(function () {
797        syncQueued = false;
798        syncHeaderDropdown();
799      });
800    }
801
802    function startHeaderObserver() {
803      if (observer || !document.documentElement) return;
804
805      observer = new MutationObserver(function () {
806        scheduleHeaderSync();
807      });
808
809      observer.observe(document.documentElement, {
810        childList: true,
811        subtree: true,
812      });
813    }
814
815    function initHeaderSync() {
816      startHeaderObserver();
817      scheduleHeaderSync();
818    }
819
820    document.addEventListener("turbo:load", scheduleHeaderSync);
821    document.addEventListener("turbo:render", scheduleHeaderSync);
822    document.addEventListener("turbo:frame-load", scheduleHeaderSync);
823    window.addEventListener("pageshow", scheduleHeaderSync);
824    window.addEventListener("toveedo:member-state-sync", scheduleHeaderSync);
825
826    if (document.readyState === "loading") {
827      document.addEventListener("DOMContentLoaded", initHeaderSync, {
828        once: true,
829      });
830    } else {
831      initHeaderSync();
832    }
833  })();
834</script>
834
835
836<script>
837  (function () {
838    var FOOTER_API_URL = "https://home.toveedo.com/api/footer-menu";
839    var FOOTER_HOME_URL = "https://toveedo.com";
840    var FOOTER_LOGO_URL =
841      "https://uscreen-customizations.b-cdn.net/toveedo/toveedo_footer_logo_8d654d52cc.svg";
842    var FOOTER_BACKGROUND_URL =
843      "https://uscreen-customizations.b-cdn.net/toveedo/large_toveedo_footer_wave_background_ff898d5227.png";
844    var FOOTER_MARKER = "data-toveedo-uscreen-footer";
845    var FOOTER_STYLE_MARKER = "data-toveedo-uscreen-footer-style";
846    var nativeFooterSelector = "footer.bg-ds-default";
847    var footerPromise = null;
848    var syncQueued = false;
849    var observer = null;
850
851    function ensureFooterStyles() {
852      if (document.head.querySelector("style[" + FOOTER_STYLE_MARKER + "]")) {
853        return;
854      }
855
856      var style = document.createElement("style");
857      style.setAttribute(FOOTER_STYLE_MARKER, "true");
858      style.textContent =
859        ".toveedo-uscreen-footer{margin-top:4.75rem;background:" +
860        "url('" +
861        FOOTER_BACKGROUND_URL +
862        "') no-repeat top center,linear-gradient(to bottom,#fff 0%,#fff 100%);" +
863        "background-size:cover;color:#fff;opacity:1;position:relative;z-index:2;}" +
864        ".toveedo-uscreen-footer__inner{max-width:1120px;margin:0 auto;padding:clamp(8rem,18vw,22rem) 1.5rem 4.4rem;text-align:center;}" +
865        ".toveedo-uscreen-footer__logo-link{display:inline-flex;align-items:center;justify-content:center;text-decoration:none;}" +
866        ".toveedo-uscreen-footer__logo-image{display:block;width:auto;height:auto;max-width:min(13rem,60vw);}" +
867        ".toveedo-uscreen-footer__nav{display:flex;justify-content:center;flex-wrap:wrap;gap:.95rem 1.6rem;margin-top:2rem;}" +
868        ".toveedo-uscreen-footer__nav-link{color:#fff !important;font-size:.95rem;font-weight:700;line-height:1.35;text-transform:uppercase;text-decoration:none;transition:opacity 180ms ease;}" +
869        ".toveedo-uscreen-footer__nav-link:hover,.toveedo-uscreen-footer__nav-link:focus-visible{opacity:.88;}" +
870        ".toveedo-uscreen-footer__copyright{margin:1.35rem 0 0;color:rgba(255,255,255,.92);font-size:.78rem;text-transform:uppercase;}" +
871        "footer[data-toveedo-native-footer-hidden='true']{display:none !important;}" +
872        "@media (max-width:767px){.toveedo-uscreen-footer__inner{padding-top:6rem;padding-bottom:3rem;}.toveedo-uscreen-footer__nav{flex-direction:column;gap:.65rem;}}";
873
874      document.head.appendChild(style);
875    }
876
877    function normalizeFooterUrl(url) {
878      if (!url) return "#";
879      if (/^(?:https?:)?\/\//i.test(url) || url.charAt(0) === "#") return url;
880      if (url.charAt(0) === "/") return FOOTER_HOME_URL + url;
881      return FOOTER_HOME_URL + "/" + url.replace(/^\/+/, "");
882    }
883
884    function fetchFooterPayload() {
885      if (!footerPromise) {
886        footerPromise = window
887          .fetch(FOOTER_API_URL, {
888            mode: "cors",
889            credentials: "omit",
890            headers: { Accept: "application/json" },
891            cache: "no-store",
892          })
893          .then(function (response) {
894            if (!response.ok) {
895              throw new Error(
896                "Footer sync failed with status " + response.status,
897              );
898            }
899
900            return response.json();
901          })
902          .catch(function (error) {
903            footerPromise = null;
904            throw error;
905          });
906      }
907
908      return footerPromise;
909    }
910
911    function buildFooterMarkup(payload) {
912      var links = Array.isArray(payload && payload.items) ? payload.items : [];
913      var copyrightText =
914        (payload && payload.copyrightText) || "© 2026 All Rights Reserved.";
915
916      var navHtml = links
917        .map(function (item) {
918          var target =
919            item && item.openInNewTab
920              ? ' target="_blank" rel="noopener noreferrer"'
921              : "";
922          return (
923            '<a class="toveedo-uscreen-footer__nav-link" href="' +
924            normalizeFooterUrl(item && item.url) +
925            '"' +
926            target +
927            ">" +
928            String((item && item.label) || "")
929              .replace(/&/g, "&amp;")
930              .replace(/</g, "&lt;")
931              .replace(/>/g, "&gt;") +
932            "</a>"
933          );
934        })
935        .join("");
936
937      return (
938        '<footer class="toveedo-uscreen-footer" ' +
939        FOOTER_MARKER +
940        '="true">' +
941        '<div class="toveedo-uscreen-footer__inner">' +
942        '<a class="toveedo-uscreen-footer__logo-link" href="' +
943        FOOTER_HOME_URL +
944        '/">' +
945        '<img class="toveedo-uscreen-footer__logo-image" src="' +
946        FOOTER_LOGO_URL +
947        '" alt="Toveedo" loading="lazy" decoding="async" />' +
948        "</a>" +
949        '<nav class="toveedo-uscreen-footer__nav" aria-label="Footer">' +
950        navHtml +
951        "</nav>" +
952        '<p class="toveedo-uscreen-footer__copyright">' +
953        String(copyrightText)
954          .replace(/&/g, "&amp;")
955          .replace(/</g, "&lt;")
956          .replace(/>/g, "&gt;") +
957        "</p>" +
958        "</div>" +
959        "</footer>"
960      );
961    }
962
963    function syncFooter() {
964      var nativeFooter = document.querySelector(nativeFooterSelector);
965      if (!nativeFooter || !nativeFooter.parentNode) return;
966
967      ensureFooterStyles();
968
969      fetchFooterPayload()
970        .then(function (payload) {
971          var existingFooter = document.querySelector(
972            "[" + FOOTER_MARKER + "]",
973          );
974          var markup = buildFooterMarkup(payload);
975
976          if (existingFooter) {
977            if (existingFooter.nextElementSibling !== nativeFooter) {
978              existingFooter.remove();
979              nativeFooter.insertAdjacentHTML("beforebegin", markup);
980            }
981          } else {
982            nativeFooter.insertAdjacentHTML("beforebegin", markup);
983          }
984
985          nativeFooter.setAttribute(
986            "data-toveedo-native-footer-hidden",
987            "true",
988          );
989          nativeFooter.setAttribute("aria-hidden", "true");
990        })
991        .catch(function (error) {
992          console.error(error);
993        });
994    }
995
996    function scheduleFooterSync() {
997      if (syncQueued) return;
998
999      syncQueued = true;
1000      window.requestAnimationFrame(function () {
1001        syncQueued = false;
1002        syncFooter();
1003      });
1004    }
1005
1006    function startFooterObserver() {
1007      if (observer || !document.documentElement) return;
1008
1009      observer = new MutationObserver(function () {
1010        scheduleFooterSync();
1011      });
1012
1013      observer.observe(document.documentElement, {
1014        childList: true,
1015        subtree: true,
1016      });
1017    }
1018
1019    function initFooterSync() {
1020      startFooterObserver();
1021      scheduleFooterSync();
1022    }
1023
1024    document.addEventListener("turbo:load", scheduleFooterSync);
1025    document.addEventListener("turbo:render", scheduleFooterSync);
1026    document.addEventListener("turbo:frame-load", scheduleFooterSync);
1027    window.addEventListener("pageshow", scheduleFooterSync);
1028
1029    if (document.readyState === "loading") {
1030      document.addEventListener("DOMContentLoaded", initFooterSync, {
1031        once: true,
1032      });
1033    } else {
1034      initFooterSync();
1035    }
1036  })();
1037</script>
1037
1038
1039<!--Facebook Pixel Code-->
1040<script>
1041  !(function (f, b, e, v, n, t, s) {
1042    if (f.fbq) return;
1043    n = f.fbq = function () {
1044      n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
1045    };
1046    if (!f._fbq) f._fbq = n;
1047    n.push = n;
1048    n.loaded = !0;
1049    n.version = "2.0";
1050    n.queue = [];
1051    t = b.createElement(e);
1052    t.async = !0;
1053    t.src = v;
1054    s = b.getElementsByTagName(e)[0];
1055    s.parentNode.insertBefore(t, s);
1056  })(
1057    window,
1058    document,
1059    "script",
1060    "https://connect.facebook.net/en_US/fbevents.js",
1061  );
1062  fbq("init", "628633714435515");
1063  fbq("track", "PageView");
1064</script>
1064
1065<noscript
1066  ><img
1067    height="1"
1068    width="1"
1069    style="display: none"
1070    src="https://www.facebook.com/tr?id=628633714435515&ev=PageView&noscript=1" /></noscript
1071><!--End Facebook Pixel Code-->
1072
1073<link rel="preconnect" href="https://fonts.googleapis.com" />
1074<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1075<link
1076  href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Quicksand:[email protected]&display=swap"
1077  rel="stylesheet"
1078/>
1079
1080<style>
1081  [data-themeless--header-target="menuNavigation"]
1082    a[href="https://toveedo.com/playlists"] {
1083    display: none;
1084  }
1085  [data-themeless--header-target="navigationLinks"]
1086    a[href="https://toveedo.com/playlists"]
1087    > span {
1088    border: 1px solid #fff;
1089    padding: 0.75rem 0.75rem;
1090    border-radius: 4px;
1091    background-color: rgba(255, 255, 255, 0.1);
1092    transform: background-color 0.3s;
1093  }
1094  [data-themeless--header-target="navigationLinks"]
1095    a[href="https://toveedo.com/playlists"]:hover
1096    > span {
1097    background-color: rgba(255, 255, 255, 0.3);
1098  }
1099  #main-menu > a + div {
1100    display: block;
1101  }
1102  #store-logo > img {
1103    max-height: 5rem;
1104    max-width: 14.5rem;
1105  }
1106  #website-header.h-20 {
1107    height: 5.6rem;
1108  }
1109  .themeless-featured-slide {
1110    opacity: 0;
1111  }
1112  [data-test="themeless-featured-slider"] .text-gray-900,
1113  [data-test="themeless-featured-slider"] .text-gray-700 {
1114    color: #fff !important;
1115  }
1116  [data-type="home"] {
1117    display: none;
1118  }
1119  swiper-slide.hidden {
1120    display: none;
1121  }
1122  #website-header {
1123    background-color: var(--primary);
1124  }
1125  .category-title {
1126    color: var(--primary) !important;
1127    font-weight: 600;
1128  }
1129  #website-header .text-primary {
1130    color: #fff !important;
1131  }
1132  .catalog-top-slider {
1133    background: #5ed1ff;
1134    position: relative;
1135    padding-left: 0%;
1136    padding-right: 0%;
1137  }
1138  .catalog-top-slider--relative {
1139    position: relative;
1140  }
1141  .catalog-top-slider--slide a {
1142    display: block;
1143    position: relative;
1144  }
1145  .catalog-top-slider--slide a > div {
1146    position: absolute;
1147    top: -20px;
1148    left: 0;
1149    width: 100%;
1150    height: 100%;
1151    z-index: 2;
1152    padding: 20px;
1153    text-align: center;
1154    display: flex;
1155    justify-content: center;
1156    align-items: center;
1157    line-height: 1.4;
1158    opacity: 0;
1159    transition:
1160      top 0.3s,
1161      opacity 0.3s;
1162    color: #fff !important;
1163  }
1164  .catalog-top-slider--slide a:before {
1165    content: "";
1166    position: absolute;
1167    top: 10px;
1168    left: 10px;
1169    width: calc(100% - 20px);
1170    height: calc(100% - 20px);
1171    background-color: rgba(0, 0, 0, 0.6);
1172    border-radius: 50%;
1173    z-index: 1;
1174    opacity: 0;
1175    transition: opacity 0.3s;
1176  }
1177  .catalog-top-slider--slide a:hover > div {
1178    top: 0;
1179    opacity: 1;
1180  }
1181  .catalog-top-slider--slide a:hover:before {
1182    opacity: 1;
1183  }
1184
1185  .catalog-top-slider--slide a {
1186    max-width: 150px;
1187    padding: 10px;
1188  }
1189  .catalog-top-slider--slide a img {
1190    border-radius: 50%;
1191  }
1192
1193  .catalog-top-slider--inner {
1194    max-height: 147px;
1195    overflow: hidden;
1196    opacity: 0;
1197    transition: opacity 0.3s;
1198  }
1199
1200  .catalog-top-slider--inner.slick-initialized {
1201    opacity: 1;
1202    overflow: visible;
1203  }
1204
1205  .catalog-top-slider--inner {
1206    padding-left: 100px;
1207    padding-right: 100px;
1208  }
1209  .catalog-top-slider--arrow {
1210    position: absolute;
1211    top: 0;
1212    cursor: pointer;
1213    width: 100px;
1214    height: 100%;
1215    display: flex;
1216    justify-content: center;
1217    align-items: center;
1218    background-color: #2bc2ff;
1219  }
1220  .catalog-top-slider--arrow img {
1221    max-width: 30px;
1222  }
1223  .catalog-top-slider--arrow.catalog-top-slider--arrow-left {
1224    left: 0;
1225  }
1226  .catalog-top-slider--arrow.catalog-top-slider--arrow-right {
1227    right: 0;
1228  }
1229
1230  .catalog-top-slider--arrow.slick-disabled {
1231    cursor: default;
1232    opacity: 0.4;
1233  }
1234
1235  @media only screen and (min-width: 991px) and (max-width: 1199px) {
1236  }
1237  @media only screen and (min-width: 768px) and (max-width: 992px) {
1238    .catalog-top-slider--arrow {
1239      width: 60px;
1240    }
1241    .catalog-top-slider--inner {
1242      padding-left: 60px;
1243      padding-right: 60px;
1244    }
1245    .catalog-top-slider--arrow img {
1246      max-width: 20px;
1247    }
1248  }
1249  @media only screen and (max-width: 767px) {
1250    .catalog-top-slider--arrow {
1251      width: 30px;
1252    }
1253    .catalog-top-slider--inner {
1254      padding-left: 60px;
1255      padding-right: 60px;
1256    }
1257    .catalog-top-slider--arrow img {
1258      max-width: 10px;
1259    }
1260  }
1261
1262  #page [slot="next-button"] {
1263    left: 97%;
1264    //background: rgba(0, 0, 0, 0.5)
1265      url("https://s3.us-east-1.amazonaws.com/unode1/assets%2Fassets%2F556%2Fright-arrow-shadow3x.1740639245.png")
1266      65% 56% no-repeat !important;
1267    background: rgba(0, 0, 0, 0.5)
1268      url("https://s3.us-east-1.amazonaws.com/unode1/assets%2Fassets%2F15576%2Farrow-right-new.1742208383.svg")
1269      65% 56% no-repeat !important;
1270    background-size: 28px !important;
1271    display: block !important;
1272    height: calc(100% - 22.2px);
1273    bottom: 0;
1274  }
1275  #page .ui-featured-category .swiper-button-next {
1276    left: 96%;
1277  }
1278  #page [slot="prev-button"] {
1279    right: 97%;
1280    //background: rgba(0, 0, 0, 0.5)
1281      url("https://s3.us-east-1.amazonaws.com/unode1/assets%2Fassets%2F556%2Fleft-arrow-shadow3x.1740639253.png")
1282      35% 56% no-repeat !important;
1283    background: rgba(0, 0, 0, 0.5)
1284      url("https://s3.us-east-1.amazonaws.com/unode1/assets%2Fassets%2F15576%2Farrow-left-new.1742208485.svg")
1285      35% 56% no-repeat !important;
1286    background-size: 28px !important;
1287    display: block !important;
1288    display: block !important;
1289    height: calc(100% - 22.2px);
1290    bottom: 0;
1291  }
1292
1293  .content-row [slot="next-button"] {
1294    left: auto !important;
1295    top: -21px;
1296  }
1297  .content-row [slot="prev-button"] {
1298    right: auto !important;
1299    top: -21px;
1300  }
1301  .card-title {
1302    min-height: 40px;
1303  }
1304  .content-row #continue_watching_row [slot="next-button"],
1305  .content-row #continue_watching_row [slot="prev-button"] {
1306    top: -22px;
1307  }
1308  #page [slot="next-button"] ds-icon,
1309  #page [slot="prev-button"] ds-icon {
1310    opacity: 0;
1311  }
1312
1313  .content-row .overflow-hidden {
1314    overflow: visible;
1315  }
1316
1317  .swiper-button-next svg,
1318  .swiper-button-prev svg {
1319    zoom: 2;
1320    stroke: #fff !important;
1321    opacity: 0;
1322  }
1323  .swiper-button-next svg {
1324    position: relative;
1325    left: -5px;
1326  }
1327  .swiper-button-prev svg {
1328    position: relative;
1329    right: -5px;
1330  }
1331</style>
1332
1333<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
1333
1334
1335<script>
1336  var videos = null;
1337  var blockVideoScanStarted = false;
1338
1339  let parseBlockedVideos = (value) => {
1340    try {
1341      let parsed = JSON.parse(value || "[]");
1342      return Array.isArray(parsed) ? parsed : [];
1343    } catch (error) {
1344      return [];
1345    }
1346  };
1347
1348  let detectTemplate = () => {
1349    if (!Array.isArray(videos)) return;
1350
1351    let programTitle = document.querySelector(
1352      'h1[data-area="program-title"]:not(.touched)',
1353    );
1354    if (programTitle) {
1355      videos.forEach((video) => {
1356        if (video == programTitle.innerText.trim()) {
1357          location.replace("/catalog");
1358        }
1359      });
1360      programTitle.classList.add("touched");
1361    }
1362
1363    let cardTitles = document.querySelectorAll(".card-title:not(.touched)");
1364    if (cardTitles.length) {
1365      cardTitles.forEach((cardTitle) => {
1366        cardTitle.classList.add("touched");
1367        videos.forEach((video) => {
1368          if (cardTitle.innerText.trim() === video) {
1369            cardTitle.closest("swiper-slide").classList.add("hidden");
1370          }
1371        });
1372      });
1373    }
1374    setTimeout(() => detectTemplate(), 500);
1375  };
1376
1377  let startBlockedVideoScan = () => {
1378    if (blockVideoScanStarted) return;
1379
1380    blockVideoScanStarted = true;
1381    detectTemplate();
1382  };
1383
1384  axios
1385    .get("/api/sessions", { headers: { Accept: "application/json" } })
1386    .then((response) => {
1387      let session = response.data;
1388      if (!session.logged) return;
1389
1390      axios
1391        .get("/api/users/me", { headers: { Accept: "application/json" } })
1392        .then((userResponse) => {
1393          videos = parseBlockedVideos(userResponse.data.field_1);
1394          startBlockedVideoScan();
1395        });
1396    });
1397</script>
1397
1398
1399<script>
1400  const CUSTOM_CATALOG = false;
1401</script>
vendor: 1 bytes, line 1401
1401
1402<script>
1403  var url = new URL(location.href);
1404  var search = url.search;
1405
1406  let newInfinityScript = () => {
1407    url = new URL(location.href);
1408
1409    if (url.pathname === "/catalog/search") {
1410      let content = document.querySelector("#catalog_content");
1411      if (content) {
1412        if (search !== url.search) {
1413          search = url.search;
1414          setTimeout(() => {
1415            let y =
1416              document.querySelector("#catalog_content").getBoundingClientRect()
1417                .top + window.scrollY;
1418            window.scroll({
1419              top: y,
1420              behavior: "smooth",
1421            });
1422            console.log("search != url.search");
1423          }, 500);
1424        } else {
1425          // console.log("search == url.search")
1426        }
1427      }
1428    }
1429
1430    if (location.href.includes("/catalog")) {
1431      let catalogWrapper = document.querySelector(
1432        ".catalog-area:not(.embeded-touched)",
1433      );
1434      if (catalogWrapper) {
1435        catalogWrapper.classList.add("embeded-touched");
1436        if (CUSTOM_CATALOG) {
1437          catalogWrapper.insertAdjacentHTML(
1438            "afterBegin",
1439            `
1440          <div class="content-row catalog-embeded-content">
1441            <turbo-frame id="custom_catalog" src="/pages/custom-catalog-page"></turbo-frame>
1442          </div>
1443        `,
1444          );
1445        }
1446      }
1447    }
1448
1449    setTimeout(() => newInfinityScript(), 500);
1450  };
1451  newInfinityScript();
1452</script>
1452
1453
1454<script>
1455  (function () {
1456    var FEATURED_BANNER_ROOT_SELECTOR =
1457      '[data-test="themeless-featured-slider"] [data-controller="themeless-featured-slider"]';
1458    var FEATURED_BANNER_SLIDER_SELECTOR =
1459      '[data-test="themeless-featured-slider"] ds-swiper';
1460    var FEATURED_BANNER_STYLE_MARKER =
1461      "data-toveedo-featured-banner-arrow-style";
1462    var FEATURED_BANNER_NAV_ATTR = "data-toveedo-featured-banner-nav";
1463    var FEATURED_BANNER_ARROW_ATTR = "data-toveedo-featured-banner-arrow";
1464    var syncQueued = false;
1465    var observer = null;
1466
1467    function ensureFeaturedBannerArrowStyles() {
1468      if (
1469        document.head.querySelector(
1470          "style[" + FEATURED_BANNER_STYLE_MARKER + "]",
1471        )
1472      ) {
1473        return;
1474      }
1475
1476      var style = document.createElement("style");
1477      style.setAttribute(FEATURED_BANNER_STYLE_MARKER, "true");
1478      style.textContent =
1479        ".toveedo-featured-banner-nav{position:absolute;inset:0;z-index:20;pointer-events:none;}" +
1480        ".toveedo-featured-banner-arrow{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;width:clamp(2.75rem,4vw,3.5rem);height:clamp(2.75rem,4vw,3.5rem);padding:0;border:1px solid rgba(255,255,255,.72);border-radius:999px;background:rgba(14,29,46,.34);color:#fff;cursor:pointer;pointer-events:auto;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:0 18px 36px rgba(0,0,0,.22);transition:background-color .18s ease,border-color .18s ease,transform .18s ease,opacity .18s ease;}" +
1481        ".toveedo-featured-banner-arrow:hover,.toveedo-featured-banner-arrow:focus-visible{background:rgba(14,29,46,.56);border-color:rgba(255,255,255,.92);transform:translateY(-50%) scale(1.04);}" +
1482        ".toveedo-featured-banner-arrow:focus-visible{outline:2px solid rgba(255,255,255,.9);outline-offset:2px;}" +
1483        ".toveedo-featured-banner-arrow--prev{left:clamp(.75rem,1.8vw,1.5rem);}" +
1484        ".toveedo-featured-banner-arrow--next{right:clamp(.75rem,1.8vw,1.5rem);}" +
1485        ".toveedo-featured-banner-arrow svg{display:block;width:clamp(1rem,1.8vw,1.35rem);height:clamp(1rem,1.8vw,1.35rem);}" +
1486        ".toveedo-featured-banner-nav[hidden]{display:none !important;}" +
1487        "@media (max-width:767px){.toveedo-featured-banner-arrow{width:2.65rem;height:2.65rem;box-shadow:0 12px 24px rgba(0,0,0,.18);}.toveedo-featured-banner-arrow--prev{left:.55rem;}.toveedo-featured-banner-arrow--next{right:.55rem;}}";
1488
1489      document.head.appendChild(style);
1490    }
1491
1492    function getFeaturedBannerRoot() {
1493      return document.querySelector(FEATURED_BANNER_ROOT_SELECTOR);
1494    }
1495
1496    function getFeaturedBannerSlider(root) {
1497      if (root) {
1498        return root.querySelector("ds-swiper");
1499      }
1500
1501      return document.querySelector(FEATURED_BANNER_SLIDER_SELECTOR);
1502    }
1503
1504    function getFeaturedSlideCount(slider) {
1505      if (!slider) return 0;
1506      if (slider.slides && typeof slider.slides.length === "number") {
1507        return slider.slides.length;
1508      }
1509
1510      return slider.querySelectorAll("swiper-slide").length;
1511    }
1512
1513    function moveFeaturedSlider(slider, direction) {
1514      if (!slider) return;
1515
1516      var methodName = direction === "prev" ? "slidePrev" : "slideNext";
1517      if (typeof slider[methodName] === "function") {
1518        slider[methodName]();
1519        return;
1520      }
1521
1522      if (slider._swiper && typeof slider._swiper[methodName] === "function") {
1523        slider._swiper[methodName]();
1524        return;
1525      }
1526
1527      var shadowButton =
1528        slider.shadowRoot &&
1529        slider.shadowRoot.querySelector(
1530          direction === "prev" ? ".swiper-button-prev" : ".swiper-button-next",
1531        );
1532      if (shadowButton) {
1533        shadowButton.click();
1534      }
1535    }
1536
1537    function createFeaturedBannerArrow(direction, slider) {
1538      var button = document.createElement("button");
1539      button.type = "button";
1540      button.className =
1541        "toveedo-featured-banner-arrow toveedo-featured-banner-arrow--" +
1542        direction;
1543      button.setAttribute(FEATURED_BANNER_ARROW_ATTR, direction);
1544      button.setAttribute(
1545        "aria-label",
1546        direction === "prev"
1547          ? "Previous featured slide"
1548          : "Next featured slide",
1549      );
1550      button.innerHTML =
1551        direction === "prev"
1552          ? '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M14.5 5L7.5 12L14.5 19" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>'
1553          : '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M9.5 5L16.5 12L9.5 19" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
1554      button.addEventListener("click", function (event) {
1555        event.preventDefault();
1556        event.stopPropagation();
1557        moveFeaturedSlider(slider, direction);
1558      });
1559
1560      return button;
1561    }
1562
1563    function syncFeaturedBannerArrows() {
1564      var root = getFeaturedBannerRoot();
1565      var slider = getFeaturedBannerSlider(root);
1566      if (!root || !slider) return;
1567
1568      ensureFeaturedBannerArrowStyles();
1569
1570      if (window.getComputedStyle(root).position === "static") {
1571        root.style.position = "relative";
1572      }
1573
1574      var nav = root.querySelector("[" + FEATURED_BANNER_NAV_ATTR + "]");
1575      if (!nav || nav.__toveedoFeaturedBannerSlider !== slider) {
1576        if (nav) {
1577          nav.remove();
1578        }
1579
1580        nav = document.createElement("div");
1581        nav.className = "toveedo-featured-banner-nav";
1582        nav.setAttribute(FEATURED_BANNER_NAV_ATTR, "true");
1583        nav.__toveedoFeaturedBannerSlider = slider;
1584        root.appendChild(nav);
1585      } else if (nav.parentNode !== root) {
1586        nav.remove();
1587        root.appendChild(nav);
1588      }
1589
1590      ["prev", "next"].forEach(function (direction) {
1591        if (
1592          !nav.querySelector(
1593            "[" + FEATURED_BANNER_ARROW_ATTR + '="' + direction + '"]',
1594          )
1595        ) {
1596          nav.appendChild(createFeaturedBannerArrow(direction, slider));
1597        }
1598      });
1599
1600      nav.hidden = getFeaturedSlideCount(slider) < 2;
1601    }
1602
1603    function scheduleFeaturedBannerSync() {
1604      if (syncQueued) return;
1605
1606      syncQueued = true;
1607      window.requestAnimationFrame(function () {
1608        syncQueued = false;
1609        syncFeaturedBannerArrows();
1610      });
1611    }
1612
1613    function startFeaturedBannerObserver() {
1614      if (observer || !document.documentElement) return;
1615
1616      observer = new MutationObserver(function () {
1617        scheduleFeaturedBannerSync();
1618      });
1619
1620      observer.observe(document.documentElement, {
1621        childList: true,
1622        subtree: true,
1623      });
1624    }
1625
1626    function initFeaturedBannerArrows() {
1627      startFeaturedBannerObserver();
1628      scheduleFeaturedBannerSync();
1629    }
1630
1631    document.addEventListener("turbo:load", scheduleFeaturedBannerSync);
1632    document.addEventListener("turbo:render", scheduleFeaturedBannerSync);
1633    document.addEventListener("turbo:frame-load", scheduleFeaturedBannerSync);
1634    window.addEventListener("pageshow", scheduleFeaturedBannerSync);
1635    window.addEventListener("resize", scheduleFeaturedBannerSync);
1636
1637    if (document.readyState === "loading") {
1638      document.addEventListener("DOMContentLoaded", initFeaturedBannerArrows, {
1639        once: true,
1640      });
1641    } else {
1642      initFeaturedBannerArrows();
1643    }
1644  })();
1645</script>
1645
1646
1647<!-- Feature: Toveedo profile tweaks, community redirect, and RCP player watermark overlay. Tag: Uscreen themeless profile tab, RCP category guard, shadow DOM player watermark, turbo-safe lifecycle. -->
1648
1649<style>
1650  #profile_form .mb-5 + .mb-5 + .mb-5,
1651  form[data-test="form-customer-info"]
1652    .mb-6.flex.flex-col
1653    + div
1654    > .mb-6.flex.flex-col:nth-of-type(2) {
1655    display: none;
1656  }
1657</style>
1658
1659<script>
1660  (function () {
1661    const API_HEADERS = { headers: { Accept: "application/json" } };
1662    const PROFILE_TAB_URL = "/pages/block-videos-page";
1663    const PROFILE_TAB_LABEL = "Video Filters";
1664    const RCP_CATEGORY_LABEL = "RCP";
1665    const WATERMARK_SELECTOR = "[data-toveedo-rcp-watermark]";
1666
1667    const state = {
1668      sessionPromise: null,
1669      userPromise: null,
1670      syncFrame: 0,
1671      domObserver: null,
1672      destroyWatermark: null,
1673      watermarkSignature: "",
1674    };
1675
1676    function normalizeText(value) {
1677      return (value || "").replace(/\s+/g, " ").trim().toUpperCase();
1678    }
1679
1680    function isProgramRoute() {
1681      return /^\/programs\//.test(window.location.pathname);
1682    }
1683
1684    function isCommunityRoute() {
1685      return window.location.pathname.indexOf("/community") === 0;
1686    }
1687
1688    function scheduleSync() {
1689      if (state.syncFrame) return;
1690
1691      state.syncFrame = window.requestAnimationFrame(function () {
1692        state.syncFrame = 0;
1693        syncFeatures();
1694      });
1695    }
1696
1697    function fetchJson(url) {
1698      if (window.axios && typeof window.axios.get === "function") {
1699        return window.axios.get(url, API_HEADERS).then(function (response) {
1700          return response.data;
1701        });
1702      }
1703
1704      return window
1705        .fetch(url, {
1706          credentials: "same-origin",
1707          headers: { Accept: "application/json" },
1708        })
1709        .then(function (response) {
1710          if (!response.ok) {
1711            throw new Error("Request failed: " + response.status);
1712          }
1713
1714          return response.json();
1715        });
1716    }
1717
1718    function hasAuthenticatedSession(session) {
1719      return Boolean(
1720        session &&
1721        (session.logged_in === true ||
1722          session.loggedIn === true ||
1723          session.user ||
1724          session.current_user ||
1725          session.user_id ||
1726          session.id ||
1727          session.email),
1728      );
1729    }
1730
1731    function getSessionUserId(session) {
1732      if (!session) return "";
1733      return (
1734        session.user_id ||
1735        session.userId ||
1736        (session.user && session.user.id) ||
1737        (session.current_user && session.current_user.id) ||
1738        session.id ||
1739        ""
1740      );
1741    }
1742
1743    function getSessionEmail(session) {
1744      if (!session) return "";
1745      return (
1746        session.email ||
1747        (session.user && session.user.email) ||
1748        (session.current_user && session.current_user.email) ||
1749        ""
1750      );
1751    }
1752
1753    function getUserId(user) {
1754      if (!user) return "";
1755      return user.id || user.user_id || user.userId || "";
1756    }
1757
1758    function getUserEmail(user) {
1759      if (!user) return "";
1760      return user.email || "";
1761    }
1762
1763    function getSession() {
1764      if (!state.sessionPromise) {
1765        state.sessionPromise = fetchJson("/api/sessions").catch(function () {
1766          return null;
1767        });
1768      }
1769
1770      return state.sessionPromise;
1771    }
1772
1773    function getCurrentUser() {
1774      if (!state.userPromise) {
1775        state.userPromise = getSession()
1776          .then(function (session) {
1777            if (!hasAuthenticatedSession(session)) return null;
1778            return fetchJson("/api/users/me").catch(function () {
1779              return null;
1780            });
1781          })
1782          .catch(function () {
1783            return null;
1784          });
1785      }
1786
1787      return state.userPromise;
1788    }
1789
1790    function resetIdentityCache() {
1791      state.sessionPromise = null;
1792      state.userPromise = null;
1793    }
1794
1795    function ensureProfileTab() {
1796      if (window.location.pathname.indexOf("/account/profile") === -1) return;
1797
1798      const tablist = document.querySelector('ul[role="tablist"]');
1799      if (!tablist) return;
1800
1801      const existingTab = tablist.querySelector("[data-toveedo-profile-tab]");
1802      if (existingTab) return;
1803
1804      const tabItem = document.createElement("li");
1805      tabItem.className = "text-center";
1806      tabItem.setAttribute("role", "presentation");
1807
1808      const link = document.createElement("a");
1809      link.href = PROFILE_TAB_URL;
1810      link.textContent = PROFILE_TAB_LABEL;
1811      link.setAttribute("role", "tab");
1812      link.setAttribute("data-toveedo-profile-tab", "true");
1813      link.style.marginBottom = "-1px";
1814      link.className =
1815        "block font-medium leading-tight py-5 cursor-pointer mr-8 text-gray-500 dark:text-gray-600";
1816
1817      tabItem.appendChild(link);
1818      tablist.appendChild(tabItem);
1819    }
1820
1821    function redirectCommunityPage() {
1822      if (!isCommunityRoute()) return false;
1823
1824      window.location.replace("/catalog");
1825      return true;
1826    }
1827
1828    function getProgramPlayer() {
1829      return document.querySelector("video-player");
1830    }
1831
1832    function hasRcpCategory() {
1833      return Array.from(
1834        document.querySelectorAll(
1835          '[data-area="program-categories"] a, [data-area="program-categories"] [data-category-item]',
1836        ),
1837      ).some(function (element) {
1838        return normalizeText(element.textContent) === RCP_CATEGORY_LABEL;
1839      });
1840    }
1841
1842    function getPlayerUserId(player) {
1843      if (!player) return "";
1844
1845      return (
1846        player.getAttribute("data-user-id") ||
1847        (player.dataset && player.dataset.userId) ||
1848        ""
1849      );
1850    }
1851
1852    function getWatermarkTarget(player) {
1853      if (!player) return null;
1854      return player.shadowRoot || player;
1855    }
1856
1857    function destroyWatermark() {
1858      state.watermarkSignature = "";
1859
1860      if (typeof state.destroyWatermark === "function") {
1861        state.destroyWatermark();
1862      }
1863
1864      state.destroyWatermark = null;
1865    }
1866
1867    function createWatermark(player, label) {
1868      const target = getWatermarkTarget(player);
1869      if (!target) return null;
1870
1871      const existing = target.querySelector
1872        ? target.querySelector(WATERMARK_SELECTOR)
1873        : null;
1874      if (existing) existing.remove();
1875
1876      const overlay = document.createElement("div");
1877      overlay.setAttribute("data-toveedo-rcp-watermark", "true");
1878      overlay.style.position = "absolute";
1879      overlay.style.inset = "0";
1880      overlay.style.zIndex = "35";
1881      overlay.style.pointerEvents = "none";
1882      overlay.style.overflow = "hidden";
1883      overlay.style.userSelect = "none";
1884
1885      const badge = document.createElement("div");
1886      badge.textContent = label;
1887      badge.style.position = "absolute";
1888      badge.style.top = "0";
1889      badge.style.left = "0";
1890      badge.style.boxSizing = "border-box";
1891      badge.style.maxWidth = "min(72%, 420px)";
1892      badge.style.padding = "0";
1893      badge.style.background = "transparent";
1894      badge.style.color = "rgba(255, 255, 255, 0.14)";
1895      badge.style.fontFamily = "inherit";
1896      badge.style.fontSize = "12px";
1897      badge.style.fontWeight = "700";
1898      badge.style.lineHeight = "1.35";
1899      badge.style.letterSpacing = "0.08em";
1900      badge.style.textTransform = "uppercase";
1901      badge.style.textAlign = "center";
1902      badge.style.whiteSpace = "normal";
1903      badge.style.wordBreak = "break-word";
1904      badge.style.textShadow = "none";
1905      badge.style.backdropFilter = "none";
1906      badge.style.WebkitBackdropFilter = "none";
1907      badge.style.opacity = "1";
1908      badge.style.transform = "translate(18px, 18px)";
1909
1910      overlay.appendChild(badge);
1911
1912      const originalInlinePosition = player.style.position;
1913      const shouldResetPosition =
1914        window.getComputedStyle(player).position === "static";
1915
1916      if (shouldResetPosition) {
1917        player.style.position = "relative";
1918      }
1919
1920      target.appendChild(overlay);
1921
1922      let destroyed = false;
1923      let moveTimer = 0;
1924      let moveFrame = 0;
1925      let resizeObserver = null;
1926
1927      function moveWatermark() {
1928        if (destroyed || !overlay.isConnected) return;
1929
1930        const width = player.clientWidth;
1931        const height = player.clientHeight;
1932        if (!width || !height) return;
1933
1934        const badgeWidth = badge.offsetWidth || 180;
1935        const badgeHeight = badge.offsetHeight || 42;
1936        const marginX = Math.max(16, Math.round(width * 0.06));
1937        const marginY = Math.max(16, Math.round(height * 0.1));
1938        const bottomClearance = Math.max(56, Math.round(height * 0.16));
1939
1940        const minX = marginX;
1941        const maxX = Math.max(minX, width - badgeWidth - marginX);
1942        const minY = marginY;
1943        const maxY = Math.max(minY, height - badgeHeight - bottomClearance);
1944
1945        const x =
1946          maxX <= minX
1947            ? Math.max(0, (width - badgeWidth) / 2)
1948            : minX + Math.random() * (maxX - minX);
1949        const y =
1950          maxY <= minY
1951            ? Math.max(0, (height - badgeHeight) / 2)
1952            : minY + Math.random() * (maxY - minY);
1953
1954        badge.style.transform =
1955          "translate(" + Math.round(x) + "px, " + Math.round(y) + "px)";
1956      }
1957
1958      function scheduleMove() {
1959        if (destroyed) return;
1960        if (moveFrame) {
1961          window.cancelAnimationFrame(moveFrame);
1962        }
1963
1964        moveFrame = window.requestAnimationFrame(function () {
1965          moveFrame = 0;
1966          moveWatermark();
1967        });
1968      }
1969
1970      if ("ResizeObserver" in window) {
1971        resizeObserver = new ResizeObserver(function () {
1972          scheduleMove();
1973        });
1974        resizeObserver.observe(player);
1975      }
1976
1977      scheduleMove();
1978      moveTimer = window.setInterval(moveWatermark, 20000);
1979
1980      return function () {
1981        destroyed = true;
1982
1983        if (moveFrame) {
1984          window.cancelAnimationFrame(moveFrame);
1985          moveFrame = 0;
1986        }
1987
1988        if (moveTimer) {
1989          window.clearInterval(moveTimer);
1990          moveTimer = 0;
1991        }
1992
1993        if (resizeObserver) {
1994          resizeObserver.disconnect();
1995          resizeObserver = null;
1996        }
1997
1998        overlay.remove();
1999
2000        if (shouldResetPosition) {
2001          player.style.position = originalInlinePosition || "";
2002        }
2003      };
2004    }
2005
2006    function resolveWatermarkLabel(player) {
2007      return getSession().then(function (session) {
2008        let email = getSessionEmail(session);
2009        let userId = getPlayerUserId(player) || getSessionUserId(session);
2010
2011        const needsUserRequest = !email || !userId;
2012
2013        if (!needsUserRequest) {
2014          return [email, userId].filter(Boolean).join(" / ");
2015        }
2016
2017        return getCurrentUser().then(function (user) {
2018          email = email || getUserEmail(user);
2019          userId = userId || getUserId(user);
2020          return [email, userId].filter(Boolean).join(" / ");
2021        });
2022      });
2023    }
2024
2025    function syncWatermark() {
2026      if (!isProgramRoute()) {
2027        destroyWatermark();
2028        return Promise.resolve();
2029      }
2030
2031      const routeToken = window.location.pathname;
2032      const categories = document.querySelector(
2033        '[data-area="program-categories"]',
2034      );
2035      const player = getProgramPlayer();
2036
2037      if (!categories || !player) {
2038        destroyWatermark();
2039        return Promise.resolve();
2040      }
2041
2042      if (!hasRcpCategory()) {
2043        destroyWatermark();
2044        return Promise.resolve();
2045      }
2046
2047      return resolveWatermarkLabel(player)
2048        .then(function (label) {
2049          if (window.location.pathname !== routeToken || !player.isConnected) {
2050            return;
2051          }
2052
2053          if (!label) {
2054            destroyWatermark();
2055            return;
2056          }
2057
2058          const signature = window.location.pathname + "::" + label;
2059          const target = getWatermarkTarget(player);
2060          const existing =
2061            target && target.querySelector
2062              ? target.querySelector(WATERMARK_SELECTOR)
2063              : null;
2064
2065          if (state.watermarkSignature === signature && existing) return;
2066
2067          destroyWatermark();
2068          state.destroyWatermark = createWatermark(player, label);
2069          state.watermarkSignature = state.destroyWatermark ? signature : "";
2070        })
2071        .catch(function () {
2072          destroyWatermark();
2073        });
2074    }
2075
2076    function syncFeatures() {
2077      if (redirectCommunityPage()) return;
2078
2079      ensureProfileTab();
2080      syncWatermark();
2081    }
2082
2083    function startObserver() {
2084      if (state.domObserver || !document.documentElement) return;
2085
2086      state.domObserver = new MutationObserver(function () {
2087        scheduleSync();
2088      });
2089
2090      state.domObserver.observe(document.documentElement, {
2091        childList: true,
2092        subtree: true,
2093      });
2094    }
2095
2096    function init() {
2097      startObserver();
2098      scheduleSync();
2099
2100      let headerLogo =
2101        "https://uscreen-customizations.b-cdn.net/toveedo/home_hero_logo_e9cc7d78af.svg";
2102      let headerLogoElement = document.querySelector(
2103        "#website-header .logo-area img",
2104      );
2105      if (headerLogoElement) {
2106        headerLogoElement.src = headerLogo;
2107      }
2108    }
2109
2110    document.addEventListener("turbo:load", function () {
2111      resetIdentityCache();
2112      scheduleSync();
2113    });
2114    document.addEventListener("turbo:render", scheduleSync);
2115    document.addEventListener("turbo:frame-load", scheduleSync);
2116    window.addEventListener("pageshow", function () {
2117      resetIdentityCache();
2118      scheduleSync();
2119    });
2120    window.addEventListener("focus", function () {
2121      resetIdentityCache();
2122      scheduleSync();
2123    });
2124
2125    if (document.readyState === "loading") {
2126      document.addEventListener("DOMContentLoaded", init, { once: true });
2127    } else {
2128      init();
2129    }
2130  })();
2131</script>
2131
2132<!-- Feature: Toveedo parental controls. Tag: field_3 hiding, test-user gate, family profile chooser, first-run setup, checkout PIN gate. -->
2133
2134<style>
2135  ds-input[name="form[field_3]"],
2136  ds-input#form_field_3,
2137  input[name="form[field_3]"],
2138  textarea[name="form[field_3]"],
2139  select[name="form[field_3]"],
2140  [name="user[field_3]"],
2141  #form_field_3,
2142  #user_field_3,
2143  [for="form_field_3"],
2144  [for="user_field_3"],
2145  .toveedo-parental-field-hidden,
2146  [data-toveedo-parental-hidden="1"] {
2147    display: none !important;
2148  }
2149
2150  .toveedo-pc-backdrop {
2151    position: fixed;
2152    inset: 0;
2153    z-index: 2147483000;
2154    display: flex;
2155    align-items: center;
2156    justify-content: center;
2157    padding: 24px;
2158    background: rgba(2, 6, 23, 0.78);
2159    backdrop-filter: blur(10px);
2160  }
2161
2162  .toveedo-pc-modal {
2163    width: min(790px, 100%);
2164    max-height: min(86vh, 780px);
2165    overflow: auto;
2166    border: 1px solid rgba(148, 163, 184, 0.32);
2167    border-radius: 14px;
2168    background: #070c18;
2169    color: #f8fafc;
2170    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.4);
2171  }
2172
2173  .toveedo-pc-modal--setup {
2174    width: min(620px, 100%);
2175  }
2176
2177  .toveedo-pc-modal--pin {
2178    width: min(620px, 100%);
2179  }
2180
2181  .toveedo-pc-modal--blocked {
2182    width: min(620px, 100%);
2183  }
2184
2185  .toveedo-pc-modal--blocked .toveedo-pc-modal__body {
2186    justify-items: center;
2187    text-align: center;
2188  }
2189
2190  .toveedo-pc-modal--blocked .toveedo-pc-actions {
2191    justify-content: center;
2192  }
2193
2194  .toveedo-pc-modal__body {
2195    padding: 36px;
2196  }
2197
2198  .toveedo-pc-modal h2,
2199  .toveedo-pc-modal h3,
2200  .toveedo-pc-modal p {
2201    margin: 0;
2202  }
2203
2204  .toveedo-pc-modal h2 {
2205    font-size: 30px;
2206    line-height: 1.15;
2207    letter-spacing: 0;
2208    text-align: center;
2209  }
2210
2211  .toveedo-pc-modal p {
2212    color: #aab7ca;
2213    line-height: 1.5;
2214  }
2215
2216  .toveedo-pc-stack {
2217    display: grid;
2218    gap: 18px;
2219  }
2220
2221  .toveedo-pc-profile-grid {
2222    display: grid;
2223    grid-template-columns: repeat(3, minmax(120px, 1fr));
2224    gap: 24px 34px;
2225    max-width: 650px;
2226    margin: 0 auto;
2227  }
2228
2229  .toveedo-pc-manage-list {
2230    display: grid;
2231    gap: 12px;
2232  }
2233
2234  .toveedo-pc-manage-row {
2235    display: flex;
2236    align-items: center;
2237    gap: 14px;
2238    padding: 12px;
2239    border: 1px solid rgba(148, 163, 184, 0.24);
2240    border-radius: 12px;
2241    background: rgba(15, 23, 42, 0.72);
2242  }
2243
2244  .toveedo-pc-manage-row__body {
2245    min-width: 0;
2246    flex: 1;
2247  }
2248
2249  .toveedo-pc-manage-row strong {
2250    display: block;
2251    color: #ffffff;
2252    font-size: 16px;
2253  }
2254
2255  .toveedo-pc-section {
2256    display: grid;
2257    gap: 14px;
2258    padding: 18px;
2259    border: 1px solid rgba(148, 163, 184, 0.24);
2260    border-radius: 12px;
2261    background: rgba(15, 23, 42, 0.58);
2262  }
2263
2264  .toveedo-pc-section h3 {
2265    margin: 0;
2266    color: #ffffff;
2267    font-size: 20px;
2268    line-height: 1.2;
2269  }
2270
2271  .toveedo-pc-section-grid {
2272    display: grid;
2273    grid-template-columns: repeat(2, minmax(0, 1fr));
2274    gap: 12px;
2275  }
2276
2277  .toveedo-pc-section--restrictions {
2278    gap: 18px;
2279  }
2280
2281  .toveedo-pc-section--restrictions > h3 {
2282    font-size: 24px;
2283  }
2284
2285  .toveedo-pc-restriction-card {
2286    display: grid;
2287    gap: 14px;
2288    padding: 16px;
2289    border: 1px solid rgba(148, 163, 184, 0.22);
2290    border-radius: 12px;
2291    background: linear-gradient(
2292      180deg,
2293      rgba(15, 23, 42, 0.82),
2294      rgba(8, 13, 26, 0.72)
2295    );
2296  }
2297
2298  .toveedo-pc-restriction-card__head {
2299    display: flex;
2300    align-items: center;
2301    justify-content: space-between;
2302    gap: 12px;
2303  }
2304
2305  .toveedo-pc-restriction-card h4 {
2306    margin: 0;
2307    color: #ffffff;
2308    font-size: 19px;
2309    line-height: 1.2;
2310  }
2311
2312  .toveedo-pc-restriction-count {
2313    display: inline-flex;
2314    align-items: center;
2315    justify-content: center;
2316    min-width: 34px;
2317    min-height: 28px;
2318    padding: 0 10px;
2319    border-radius: 999px;
2320    background: rgba(56, 189, 248, 0.14);
2321    color: #7dd3fc;
2322    font-size: 12px;
2323    font-weight: 900;
2324  }
2325
2326  .toveedo-pc-filter-grid {
2327    display: grid;
2328    grid-template-columns: repeat(2, minmax(0, 1fr));
2329    gap: 10px 14px;
2330  }
2331
2332  .toveedo-pc-filter-grid--single {
2333    grid-template-columns: 1fr;
2334  }
2335
2336  .toveedo-pc-filter-row {
2337    appearance: none;
2338    display: flex;
2339    width: 100%;
2340    min-height: 58px;
2341    align-items: center;
2342    gap: 12px;
2343    padding: 10px 12px;
2344    border: 1px solid transparent;
2345    border-radius: 10px;
2346    background: rgba(15, 23, 42, 0.54);
2347    color: #f8fafc;
2348    cursor: pointer;
2349    font: inherit;
2350    text-align: left;
2351    transition:
2352      border-color 160ms ease,
2353      background-color 160ms ease,
2354      transform 160ms ease;
2355  }
2356
2357  .toveedo-pc-filter-row:hover,
2358  .toveedo-pc-filter-row:focus-visible {
2359    border-color: rgba(56, 189, 248, 0.34);
2360    outline: none;
2361    transform: translateY(-1px);
2362  }
2363
2364  .toveedo-pc-filter-row.is-blocked {
2365    border-color: rgba(244, 63, 94, 0.32);
2366    background: rgba(127, 29, 29, 0.28);
2367  }
2368
2369  .toveedo-pc-filter-icon {
2370    display: grid;
2371    width: 34px;
2372    height: 34px;
2373    flex: none;
2374    place-items: center;
2375    border-radius: 9px;
2376    background: #38bdf8;
2377    color: #06121f;
2378    font-size: 12px;
2379    font-weight: 900;
2380    letter-spacing: 0;
2381  }
2382
2383  .toveedo-pc-filter-row.is-blocked .toveedo-pc-filter-icon {
2384    background: #fb7185;
2385    color: #190b12;
2386  }
2387
2388  .toveedo-pc-filter-name {
2389    min-width: 0;
2390    flex: 1;
2391    overflow: hidden;
2392    color: #ffffff;
2393    font-weight: 800;
2394    text-overflow: ellipsis;
2395    white-space: nowrap;
2396  }
2397
2398  .toveedo-pc-rule-status {
2399    display: inline-flex;
2400    min-width: 70px;
2401    min-height: 30px;
2402    align-items: center;
2403    justify-content: center;
2404    border-radius: 999px;
2405    background: rgba(34, 197, 94, 0.14);
2406    color: #bbf7d0;
2407    font-size: 12px;
2408    font-weight: 900;
2409  }
2410
2411  .toveedo-pc-filter-row.is-blocked .toveedo-pc-rule-status {
2412    background: rgba(251, 113, 133, 0.16);
2413    color: #fecdd3;
2414  }
2415
2416  .toveedo-pc-video-combobox {
2417    position: relative;
2418    display: grid;
2419    gap: 10px;
2420  }
2421
2422  .toveedo-pc-video-search {
2423    padding-right: 42px;
2424  }
2425
2426  .toveedo-pc-video-results {
2427    position: absolute;
2428    top: calc(100% + 8px);
2429    left: 0;
2430    right: 0;
2431    z-index: 12;
2432    display: grid;
2433    max-height: 312px;
2434    gap: 6px;
2435    overflow: auto;
2436    padding: 8px;
2437    border: 1px solid rgba(148, 163, 184, 0.28);
2438    border-radius: 12px;
2439    background: #050b16;
2440    box-shadow: 0 20px 42px rgba(0, 0, 0, 0.38);
2441  }
2442
2443  .toveedo-pc-video-results[hidden] {
2444    display: none !important;
2445  }
2446
2447  .toveedo-pc-video-selected {
2448    display: grid;
2449    max-height: 230px;
2450    gap: 8px;
2451    overflow: auto;
2452    padding-right: 4px;
2453  }
2454
2455  .toveedo-pc-filter-empty {
2456    padding: 12px 2px;
2457    color: #91a2bb;
2458    font-size: 13px;
2459    font-weight: 700;
2460  }
2461
2462  .toveedo-pc-hidden-rules {
2463    display: none !important;
2464  }
2465
2466  .toveedo-pc-profile-card {
2467    min-height: 134px;
2468    border: 1px solid transparent;
2469    border-radius: 12px;
2470    background: transparent;
2471    color: inherit;
2472    cursor: pointer;
2473    text-align: center;
2474    display: grid;
2475    justify-items: center;
2476    align-content: start;
2477    padding: 8px;
2478    transition: transform 160ms ease;
2479  }
2480
2481  .toveedo-pc-profile-card:hover,
2482  .toveedo-pc-profile-card:focus-visible {
2483    outline: none;
2484    transform: translateY(-1px);
2485  }
2486
2487  .toveedo-pc-profile-card:hover .toveedo-pc-avatar,
2488  .toveedo-pc-profile-card:focus-visible .toveedo-pc-avatar {
2489    border-color: #38bdf8;
2490    box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.16);
2491  }
2492
2493  .toveedo-pc-profile-card--add .toveedo-pc-avatar {
2494    border: 2px dashed rgba(148, 163, 184, 0.42);
2495    background: transparent;
2496    color: #cbd5e1;
2497    font-size: 38px;
2498  }
2499
2500  .toveedo-pc-avatar {
2501    display: grid;
2502    width: 74px;
2503    height: 74px;
2504    place-items: center;
2505    margin: 0 auto 12px;
2506    border: 2px solid transparent;
2507    border-radius: 14px;
2508    background: #18243a;
2509    font-size: 30px;
2510    font-weight: 900;
2511    line-height: 1;
2512    box-sizing: border-box;
2513    transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
2514  }
2515
2516  .toveedo-pc-profile-card strong {
2517    display: block;
2518    color: #ffffff;
2519    font-size: 15px;
2520  }
2521
2522  .toveedo-pc-profile-meta {
2523    display: block;
2524    margin-top: 4px;
2525    color: #91a2bb;
2526    font-size: 12px;
2527  }
2528
2529  .toveedo-pc-actions {
2530    display: flex;
2531    flex-wrap: wrap;
2532    gap: 10px;
2533    align-items: center;
2534  }
2535
2536  .toveedo-pc-actions--stack {
2537    flex-direction: column;
2538    justify-content: center;
2539  }
2540
2541  .toveedo-pc-button {
2542    display: inline-flex;
2543    align-items: center;
2544    justify-content: center;
2545    min-height: 44px;
2546    padding: 0 18px;
2547    border: 1px solid rgba(148, 163, 184, 0.26);
2548    border-radius: 10px;
2549    background: #101a2d;
2550    color: #f8fafc;
2551    cursor: pointer;
2552    font: inherit;
2553    font-weight: 700;
2554    text-decoration: none;
2555  }
2556
2557  .toveedo-pc-close {
2558    position: absolute;
2559    top: 16px;
2560    right: 18px;
2561    width: 38px;
2562    height: 38px;
2563    border: 0;
2564    background: transparent;
2565    color: #cbd5e1;
2566    cursor: pointer;
2567    font: inherit;
2568    font-size: 30px;
2569    line-height: 1;
2570  }
2571
2572  .toveedo-pc-form-grid {
2573    display: grid;
2574    grid-template-columns: repeat(2, minmax(0, 1fr));
2575    gap: 12px;
2576  }
2577
2578  .toveedo-pc-setup-grid {
2579    display: grid;
2580    grid-template-columns: 1fr;
2581    gap: 18px;
2582    width: min(520px, 100%);
2583    margin: 0 auto;
2584  }
2585
2586  .toveedo-pc-field {
2587    display: grid;
2588    gap: 6px;
2589  }
2590
2591  .toveedo-pc-field span {
2592    color: #aab7ca;
2593    font-size: 13px;
2594    font-weight: 700;
2595  }
2596
2597  .toveedo-pc-select {
2598    width: 100%;
2599    min-height: 46px;
2600    box-sizing: border-box;
2601    border: 1px solid rgba(148, 163, 184, 0.28);
2602    border-radius: 10px;
2603    background: #050b16;
2604    color: #f8fafc;
2605    font: inherit;
2606    padding: 0 14px;
2607  }
2608
2609  .toveedo-pc-pin-avatar {
2610    display: grid;
2611    width: 72px;
2612    height: 72px;
2613    place-items: center;
2614    justify-self: center;
2615    border-radius: 16px;
2616    background: #18243a;
2617    color: #f8fafc;
2618    font-size: 30px;
2619    font-weight: 900;
2620    line-height: 1;
2621  }
2622
2623  .toveedo-pc-pin-field {
2624    justify-items: center;
2625  }
2626
2627  .toveedo-pc-setup-grid .toveedo-pc-pin-field {
2628    justify-items: stretch;
2629  }
2630
2631  .toveedo-pc-setup-grid .toveedo-pc-pin-grid {
2632    width: 100%;
2633    grid-template-columns: repeat(4, minmax(0, 1fr));
2634  }
2635
2636  .toveedo-pc-setup-grid .toveedo-pc-pin-digit {
2637    width: 100%;
2638  }
2639
2640  .toveedo-pc-sr-only {
2641    position: absolute;
2642    width: 1px;
2643    height: 1px;
2644    padding: 0;
2645    margin: -1px;
2646    overflow: hidden;
2647    clip: rect(0, 0, 0, 0);
2648    white-space: nowrap;
2649    border: 0;
2650  }
2651
2652  .toveedo-pc-pin-grid {
2653    display: grid;
2654    grid-template-columns: repeat(4, 58px);
2655    overflow: hidden;
2656    border: 1px solid rgba(148, 163, 184, 0.3);
2657    border-radius: 12px;
2658    background: #050b16;
2659  }
2660
2661  .toveedo-pc-pin-digit {
2662    width: 58px;
2663    height: 58px;
2664    box-sizing: border-box;
2665    border: 0;
2666    border-left: 1px solid rgba(148, 163, 184, 0.24);
2667    background: transparent;
2668    color: #f8fafc;
2669    font: inherit;
2670    font-size: 24px;
2671    font-weight: 800;
2672    line-height: 1;
2673    text-align: center;
2674  }
2675
2676  .toveedo-pc-pin-digit:first-child {
2677    border-left: 0;
2678  }
2679
2680  .toveedo-pc-pin-digit:focus {
2681    outline: 2px solid #38bdf8;
2682    outline-offset: -2px;
2683    background: rgba(56, 189, 248, 0.08);
2684  }
2685
2686  .toveedo-pc-button--primary {
2687    border-color: transparent;
2688    background: #38bdf8;
2689    color: #06121f;
2690  }
2691
2692  .toveedo-pc-button--unlock {
2693    width: min(390px, 100%);
2694    border-color: transparent;
2695    background: linear-gradient(90deg, #38bdf8 0%, #a855f7 100%);
2696    color: #06121f;
2697  }
2698
2699  .toveedo-pc-button--ghost {
2700    background: transparent;
2701  }
2702
2703  .toveedo-pc-link {
2704    display: inline-flex;
2705    align-items: center;
2706    justify-content: center;
2707    border: 0;
2708    background: transparent;
2709    color: #cbd5e1;
2710    cursor: pointer;
2711    font: inherit;
2712    font-weight: 700;
2713    line-height: 1.35;
2714    text-decoration: none;
2715  }
2716
2717  .toveedo-pc-link--row {
2718    flex-basis: 100%;
2719  }
2720
2721  .toveedo-pc-actions--stack .toveedo-pc-link--row {
2722    flex-basis: auto;
2723  }
2724
2725  .toveedo-pc-link:hover,
2726  .toveedo-pc-link:focus-visible {
2727    color: #ffffff;
2728    text-decoration: underline;
2729    text-underline-offset: 3px;
2730    outline: none;
2731  }
2732
2733  .toveedo-pc-button--danger {
2734    border-color: rgba(251, 113, 133, 0.42);
2735    background: rgba(127, 29, 29, 0.22);
2736    color: #fecdd3;
2737  }
2738
2739  .toveedo-pc-input {
2740    width: 100%;
2741    min-height: 46px;
2742    box-sizing: border-box;
2743    border: 1px solid rgba(148, 163, 184, 0.28);
2744    border-radius: 10px;
2745    background: #050b16;
2746    color: #f8fafc;
2747    font: inherit;
2748    padding: 0 14px;
2749  }
2750
2751  .toveedo-pc-textarea {
2752    min-height: 92px;
2753    padding: 12px 14px;
2754    resize: vertical;
2755  }
2756
2757  .toveedo-pc-checkbox {
2758    display: flex;
2759    align-items: center;
2760    gap: 10px;
2761    color: #f8fafc;
2762    font-weight: 800;
2763  }
2764
2765  .toveedo-pc-checkbox input {
2766    width: 18px;
2767    height: 18px;
2768    accent-color: #38bdf8;
2769  }
2770
2771  .toveedo-pc-error {
2772    display: none;
2773    color: #fb7185;
2774    font-weight: 700;
2775  }
2776
2777  .toveedo-pc-error.is-visible {
2778    display: block;
2779  }
2780
2781  .toveedo-pc-switcher {
2782    position: relative;
2783    z-index: 2147482500;
2784    display: inline-flex;
2785    align-items: center;
2786    gap: 8px;
2787    min-height: 38px;
2788    max-width: 230px;
2789    border: 1px solid rgba(148, 163, 184, 0.26);
2790    border-radius: 999px;
2791    background: rgba(8, 17, 31, 0.9);
2792    color: #f8fafc;
2793    cursor: pointer;
2794    font: inherit;
2795    font-weight: 800;
2796    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.22);
2797    padding: 6px 12px 6px 7px;
2798    margin-left: 12px;
2799    flex: none;
2800    pointer-events: auto;
2801  }
2802
2803  .toveedo-pc-switcher::before {
2804    content: "";
2805    position: absolute;
2806    inset: -8px;
2807    border-radius: inherit;
2808  }
2809
2810  .toveedo-pc-switcher > * {
2811    pointer-events: none;
2812  }
2813
2814  .toveedo-pc-trigger-avatar {
2815    display: grid;
2816    width: 26px;
2817    height: 26px;
2818    place-items: center;
2819    border-radius: 999px;
2820    background: #38bdf8;
2821    color: #06121f;
2822    font-size: 13px;
2823    font-weight: 900;
2824  }
2825
2826  .toveedo-pc-trigger-name {
2827    overflow: hidden;
2828    text-overflow: ellipsis;
2829    white-space: nowrap;
2830  }
2831
2832  @media (max-width: 640px) {
2833    .toveedo-pc-backdrop {
2834      align-items: stretch;
2835      padding: 12px;
2836    }
2837
2838    .toveedo-pc-modal {
2839      max-height: calc(100vh - 24px);
2840    }
2841
2842    .toveedo-pc-modal__body {
2843      padding: 22px;
2844    }
2845
2846    .toveedo-pc-form-grid {
2847      grid-template-columns: 1fr;
2848    }
2849
2850    .toveedo-pc-section-grid {
2851      grid-template-columns: 1fr;
2852    }
2853
2854    .toveedo-pc-filter-grid {
2855      grid-template-columns: 1fr;
2856    }
2857
2858    .toveedo-pc-filter-row {
2859      min-height: 54px;
2860    }
2861
2862    .toveedo-pc-rule-status {
2863      min-width: 62px;
2864    }
2865
2866    .toveedo-pc-manage-row {
2867      align-items: flex-start;
2868      flex-direction: column;
2869    }
2870
2871    .toveedo-pc-setup-grid {
2872      grid-template-columns: 1fr;
2873    }
2874
2875    .toveedo-pc-trigger-name {
2876      display: none;
2877    }
2878
2879    .toveedo-pc-profile-grid {
2880      grid-template-columns: repeat(2, minmax(110px, 1fr));
2881      gap: 18px;
2882    }
2883
2884    .toveedo-pc-pin-grid {
2885      grid-template-columns: repeat(4, minmax(44px, 56px));
2886    }
2887
2888    .toveedo-pc-pin-digit {
2889      width: 100%;
2890    }
2891  }
2892</style>
2893
2894<script>
2895  (function () {
2896    var TEST_USER_EMAIL = "[email protected]";
2897    var FIELD_KEY = "field_3";
2898    var FORM_FIELD_NAME = "form[field_3]";
2899    var LEGACY_STORAGE_PREFIX = "toveedo_parental_";
2900    var ACTIVE_PROFILE_STORAGE_KEY = "toveedo_pc_active_profile_v1";
2901    var ACCOUNT_CACHE_STORAGE_KEY = "toveedo_pc_account_cache_v1:";
2902    var ACCOUNT_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
2903    var CATALOG_MANIFEST_ENDPOINT =
2904      "https://home.toveedo.com/api/parental-control/catalog-manifest";
2905    var CATALOG_MANIFEST_CACHE_KEY = "toveedo_pc_catalog_manifest_v1";
2906    var CATALOG_MANIFEST_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
2907    var CATALOG_MANIFEST_REVALIDATE_INTERVAL_MS = 5 * 60 * 1000;
2908    var BLOCKED_CATEGORY_STYLE_ID = "toveedo-pc-blocked-category-styles";
2909    var MODAL_ID = "toveedo-parental-control-modal";
2910    var SWITCHER_ID = "toveedo-parental-profile-switcher";
2911    var SCHEMA_VERSION = 1;
2912
2913    var state = {
2914      syncFrame: 0,
2915      sessionPromise: null,
2916      userPromise: null,
2917      contentPromises: {},
2918      observer: null,
2919      timeTicker: null,
2920      timeProfileId: "",
2921      lastUsageTickAt: 0,
2922      switcherAccount: null,
2923      switcherEmail: "",
2924      activeSelection: { email: "", profileId: "" },
2925      catalogManifest: null,
2926      catalogManifestPromise: null,
2927      catalogManifestLastFetchedAt: 0,
2928      catalogConfirmations: {},
2929      purchaseUnlocks: {},
2930      usageSeconds: {}
2931    };
2932
2933    var AGE_FILTERS = [
2934      { value: "all", minAge: 0 },
2935      { value: "1-5", minAge: 5 },
2936      { value: "3-6", minAge: 6 },
2937      { value: "6+", minAge: 6 },
2938      { value: "9+", minAge: 9 },
2939      { value: "12+", minAge: 12 }
2940    ];
2941
2942    // Replace this static manifest with the custom DB payload when the endpoint is ready.
2943    var PARENTAL_CATALOG_MANIFEST = {
2944      channels: [
2945        {
2946          id: "toveedo",
2947          label: "Toveedo",
2948          icon: "TV",
2949          videos: [
2950            { id: "welcome-to-toveedo", title: "Welcome to Toveedo" },
2951            { id: "toveedo-clubhouse", title: "Toveedo Clubhouse" },
2952            { id: "toveedo-remix", title: "Toveedo Remix" },
2953            { id: "toveedo-tablet", title: "Toveedo Tablet" }
2954          ]
2955        },
2956        {
2957          id: "chofetz-chaim",
2958          label: "Chofetz Chaim",
2959          icon: "CC",
2960          videos: [
2961            { id: "1001-voices", title: "1001 Voices" },
2962            { id: "agent-chesed", title: "Agent Chesed" },
2963            { id: "a-tree-full-of-mitzvos", title: "A Tree Full of Mitzvos - Story Time" }
2964          ]
2965        },
2966        {
2967          id: "torah-live",
2968          label: "Torah Live",
2969          icon: "TL",
2970          videos: [
2971            { id: "ahavas-hashem", title: "Ahavas Hashem - Torah Live" },
2972            { id: "asher-yaatzar", title: "Asher Yaatzar - Torah Live" },
2973            { id: "bishul-akum", title: "Bishul Akum - Torah Live" },
2974            { id: "learning-torah", title: "Learning Torah - Torah Live" }
2975          ]
2976        },
2977        {
2978          id: "chabad-kids",
2979          label: "Chabad.org/kids",
2980          icon: "CK",
2981          videos: [
2982            { id: "captain-sinai-pilot", title: "Captain Sinai Ep. 1 - Pilot" },
2983            { id: "captain-sinai-fly-away", title: "Captain Sinai Ep. 2 - Fly Away" },
2984            { id: "captain-sinai-trailer", title: "Captain Sinai - Trailer" }
2985          ]
2986        },
2987        {
2988          id: "malkali",
2989          label: "Malkali",
2990          icon: "MK",
2991          videos: [
2992            { id: "a-brivala", title: "A Brivala - Yiddish" },
2993            { id: "a-minute-tzi-shpet", title: "A Minute Tzi Shpet - Yiddish" },
2994            { id: "chodesh-nisson", title: "Chodesh Nisson In Terkvitz - Yiddish" }
2995          ]
2996        },
2997        {
2998          id: "meir-kids",
2999          label: "Meir Kids",
3000          icon: "ME",
3001          videos: [
3002            { id: "kivi-tuki-adventures", title: "Kivi & Tuki - Adventures Begin" },
3003            { id: "kivi-tuki-good-middos", title: "Kivi & Tuki - Good Middos" },
3004            { id: "derech-emunah", title: "Derech Emunah - Meir Green #TYH" }
3005          ]
3006        },
3007        {
3008          id: "shazak",
3009          label: "SHAZAK",
3010          icon: "SZ",
3011          videos: [
3012            { id: "boruch-learns-pesach", title: "Boruch learns about Pesac
3012h - Kunda" },
3013            { id: "boruch-learns-shabbos", title: "Boruch learns about Shabbos - kunda" },
3014            { id: "mitzvah-boulevard-aleph-beis", title: "Mitzvah Boulevard - Aleph Beis (Final)" }
3015          ]
3016        },
3017        {
3018          id: "hidabroot",
3019          label: "Hidabroot",
3020          icon: "HD",
3021          videos: [
3022            { id: "fish-explain-tashlich", title: "Fish explain Tashlich" },
3023            { id: "judges-judgement", title: "Judges & Judgement" },
3024            { id: "prophecies-revealed", title: "Prophecies Revealed - Torah Live" }
3025          ]
3026        },
3027        {
3028          id: "shezoli",
3029          label: "Shezoli",
3030          icon: "SH",
3031          videos: [
3032            { id: "lets-draw", title: "Let's Draw" },
3033            { id: "flower-vase-art", title: "Flower Vase Art" },
3034            { id: "fun-decorations", title: "Fun Decorations" }
3035          ]
3036        },
3037        {
3038          id: "beis-rivkah",
3039          label: "Beis Rivkah",
3040          icon: "BR",
3041          videos: [
3042            { id: "camp-bnos-yisrael-trailer", title: "Camp Bnos Yisrael #1 Trailer" },
3043            { id: "camp-bnos-yisrael-together", title: "Camp Bnos Yisrael - Together As One" },
3044            { id: "bat-mitzvah-girl", title: "Bat Mitzvah Girl - Esther Press" }
3045          ]
3046        },
3047        {
3048          id: "jem",
3049          label: "JEM",
3050          icon: "JM",
3051          videos: [
3052            { id: "the-beis-hamikdash", title: "The Beis Hamikdash - Sara Blau" },
3053            { id: "sefer-torah", title: "Sefer Torah - Torah Live" },
3054            { id: "yerushalayim", title: "Yerushalayim - Torah Live" }
3055          ]
3056        }
3057      ],
3058      categories: [
3059        { id: "education", label: "Education" },
3060        { id: "torah-study", label: "Torah Study" },
3061        { id: "music", label: "Music" },
3062        { id: "family", label: "Family" },
3063        { id: "lectures", label: "Lectures" },
3064        { id: "documentaries", label: "Documentaries" },
3065        { id: "kids-content", label: "Kids Content" }
3066      ]
3067    };
3068
3069    var PARENTAL_VIDEO_TITLES = [
3070   "Micha Gamerman - Yomim Noraim",
3071    "Micha Gamerman - Sukkos",
3072    "Micha Gamerman - Shavuos",
3073    "Micha Gamerman - Chanukah Medley",
3074    "Micha Gamerman - Purim",
3075    "Micha Gamerman - Pesach",
3076    "Dudi Knopfler – Ki B’simcha",
3077    "Ari Goldwag - Hashem Loves You",
3078    "Ari Goldwag - Shir Ushvacha - ft. Sheves Chaverim",
3079    "Ari Goldwag - Chanukah Lights",
3080    "Ari Goldwag - Am Echad",
3081    "Ari Goldwag - Today is the Day",
3082    "Ari Goldwag - Lo Nafsik Lirkod",
3083    "Agent Emes #1 - The Fish Head",
3084    "Agent Emes #5 - and the Happy Chanukah",
3085    "Agent Emes #7 - Hard to Forget Pt. 2",
3086    "Agent Emes #6 - Hard to Forget Pt. 1",
3087    "Agent Emes #10 - vs. The Karrupter",
3088    "Dudi Knopfler – Soreiku",
3089    "coming - bonus",
3090    "Bella Brocha goes to a Wedding - (For boys)",
3091    "Trailer_2.mp4",
3092    "Bella Brocha & The Talent Show - (For boys)",
3093    "Bella Brocha goes to a Wedding  - with Kol Isha",
3094    "Trailer2.mp4",
3095    "Bella Brocha & The Talent Show - with Kol Isha",
3096    "babyshark.mp4 - 192057",
3097    "Michoel Pruzansky - Shootin’ For The Moon",
3098    "Micha Gamerman - Rabim",
3099    "YBC - Yivonim - no volume",
3100    "Uncle Moishy & the Mitzvah Men! #8",
3101    "Welcome to Toveedo (Original)",
3102    "On a Roll - Finger Puppets",
3103    "Bella Brocha and The Twins - with Kol Isha",
3104    "Tricks - Can Grenade",
3105    "Toveedo - 4",
3106    "Toveedo - 2",
3107    "Toveedo - 3",
3108    "Toveedo - 1",
3109    "Dovid Pearlman - We Can't Thank You Enough",
3110    "COMING SOON: Kivi & Tuki - Trailer",
3111    "Ari Goldwag - Zeh Keili",
3112    "Ari Goldwag - For the Light",
3113    "Ari Goldwag - Ashira LaHashem",
3114    "Ari Goldwag - Smile",
3115    "Ari Goldwag- Ahalela",
3116    "Ari Goldwag - Rak Hu",
3117    "Ari Goldwag - Hafachta",
3118    "Yoey Muchnik - Light Light Light",
3119    "6 Hour Play",
3120    "Play All - Torah Town",
3121    "“I Believe in Me” (kol isha)",
3122    "Rogers Park - No Place Like Home",
3123    "Mimmy & Simmy - Sharing a Sunshine Umbrella (for women and girls)",
3124    "Eli Scheller - Mashtuka",
3125    "Eli Scheller - Just Be B'Simcha",
3126    "Shmueli Ungar - Halailah Hazeh",
3127    "Uri Davidi- Echad Ani Yodea",
3128    "Rogers Park - My Friendship Circle",
3129    "Rogers Park - Yaaleh",
3130    "Fräulein",
3131    "Benny Friedman - Hold On Tight - ft. Baruch Levine",
3132    "Interin Bild - Yiddish",
3133    "Interin Shpiel - Yiddish",
3134    "Thankful - Talent Unite",
3135    "The Shepselach - Dress Up Contest",
3136    "Dr. Wildshaar - Gragger",
3137    "The Shepselach - Good Night Shepselach",
3138    "Dr. Wildshaar - Pesach",
3139    "The Kumzits - Chanale",
3140    "Yossi & Leah’s Ranch - Horse Prepping",
3141    "The Runaways (9th of Av only)",
3142    "Team Teretz #3 - Premiering on Tuesday 9AM iy”h",
3143    "Rabbi B - Mezuzah",
3144    "Yaakov Shwekey - Baruch Hashem It’S Shabbos",
3145    "Team Teretz #4 - Trailer",
3146    "Living Lessons - Masmedei Hasiyum",
3147    "Living Lessons - Hashgacha Pratis",
3148    "Living Lessons - Chessed",
3149    "Living Lessons - Pesach",
3150    "Living Lessons - Mesiras Nefesh",
3151    "Living Lessons - Shabbos",
3152    "Living Lessons - Rosh Hashana",
3153    "Tomp3.Cc - מנדי פיאמנטה  כוחות Mendy Piamenta  Kochot_1080p",
3154    "World of Wonder- Farm Animals",
3155    "Toveedo By Kids - ROUND 2",
3156    "Where is Gayil? - CGC",
3157    "Vikar - Franciska ft. Maayan Davis",
3158    "Maoz Tzur - Franciska ft. Maayan Davis",
3159    "Yoey Muchnik - Adeh Adeh",
3160    "Benny Friedman - Rebbi It's All From You",
3161    "Mendy Piamenta - Kochot",
3162    "Dovid Pearlman - Greatest Gift",
3163    "Rogers Park - Tumb",
3164    "Shmueli Ungar - Yiten Lecha",
3165    "8th Day - Shliach  #TYH",
3166    "B'korov Mamash - Avraham Fried #TYH",
3167    "Trailer - Elegant Ella - Snow Much Fun",
3168    "Punchkees - Rambam",
3169    "Punchkees - Yaakov's Dream",
3170    "Punchkees - Secret Signs",
3171    "The Nochum Show Feat. Chaim & Tzvi",
3172    "Punchkees - 17 Tammuz",
3173    "Punchkees - Korach",
3174    "Dr. Shnitzel - Trailer",
3175    "Pisom - Trailer",
3176    "CGC 2 - Trailer (old",
3177    "Mish and Mush Trailer",
3178    "Mr. Hossenfepher - Mission Possible - Trailer",
3179    "Torah Tots Promo Video",
3180    "Where Did All These Leaves Come From - Mish & Mush",
3181    "Let's All Run - Mish & Mush - Sefardi",
3182    "Shalom - Torah Live",
3183    "Kippah - Torah Live",
3184    "Jewish Revenge 6 old",
3185    "New Live Stream",
3186    "Tantz It - Advanced",
3187    "Chaptzem - Purim Pirates - pt.1",
3188    "Sheyafutzu - Naftali Kempeh #TYH",
3189    "Avi Ilson - Eretz Israel",
3190    "Joey Newcomb - One Note Niggun",
3191    "Uri Davidi - The Connection Is You",
3192    "Untied - Trailer: Coming Motzoi Shabbos",
3193    "The Secret Room - Neteilas Yadayim",
3194    "Flower Pops",
3195    "Chaptzem - Purim Pirates - pt. 2",
3196    "Twists and Tales - Racheli's First Day",
3197    "The Shepselach - Savta Liba-a-a Comes to Visit",
3198    "Avrohom Leffler",
3199    "Sputz Kids",
3200    "Esti",
3201    "Yaakov",
3202    "Rivkin Kids",
3203    "Meira Gross",
3204    "Alone Family",
3205    "Bilvavi - Elena Tal",
3206    "Unity -  Shaindy Plotzker",
3207    "Dr Shnitzel - Solar Energy #2",
3208    "Word Power - \"Enjoy\"",
3209    "The Attic - Trailer",
3210    "Mimmy & Simmy 2 - The Grass Isn't Greener (for women and girls)",
3211    "ESL - Week 2",
3212    "ESL - Week 6",
3213    "ESL - Week 4",
3214    "ESL - Week 1",
3215    "ESL - Week 3",
3216    "ESL - Week 5",
3217    "Word Power - \"Derech Eretz\"",
3218    "Finish Line #TYH Boys",
3219    "8th Day - Get It Done! Ft. Tzvi Silberstein",
3220    "Ari Goldwag - Yisroel Love Prayer - Ft. Refael Klein",
3221    "Tour Benny Friemdan - London",
3222    "Dovid Pearlman - Nachamu - Acapella",
3223    "Dovid Lowy- Don'T Hide From Me (Acoustic Cover)",
3224    "Dovid Lowy - Yom Yom",
3225    "Dudi Knopfler - Ain Od Milvado -  ft. Shira Choir",
3226    "Eli Scheller - Kol Hamelamed",
3227    "Joey Newcomb - Freilach All The Time - ft. Benny Friedman",
3228    "Joey Newcomb - Slabodka Niggun - ft. Naftali Kempeh",
3229    "Pirchei Shomayim Choir - Anim Zemiros",
3230    "Uri Davidi - Toda Raba",
3231    "Mechel Trailer Coming Soon",
3232    "Word Power - \"Sorry\"",
3233    "Word Power - \"Honesty\"",
3234    "The Secret Room - Respect Challenge",
3235    "Word Power - \"Sincerity\"",
3236    "Toveedo Teaser",
3237    "תורה שטעטל - צו געשמיידיק",
3238    "תורה שטעטל - נייַ טראָק",
3239    "תורה שטעטל - אכרייעס",
3240    "תורה שטעטל - געדולד",
3241    "תורה שטעטל - זיכערקייַט ערשטער",
3242    "The Mystery of Chaviv - Ep 7. Joining Forces",
3243    "The Mystery of Chaviv - Ep 6. He's Ready",
3244    "Ropogos - Ep. 4 Trailer",
3245    "Word Power - Please",
3246    "Midos Power Kids - Efraim the Lazy",
3247    "Midos Power Kids - Avi the Volcano Boy",
3248    "When Zaidy Was Young 1 - Kunda",
3249    "Midos Power Kids - Yitzchak Wants It All",
3250    "The Shepselach - Momo's New Friends",
3251    "Word Power - Solidarity",
3252    "Midos Power Kids - Rosa the Fearful",
3253    "Juggling Made Easy!",
3254    "Eliyahu Juggling Final 99098",
3255    "Pocket-size Pets",
3256    "Eliyahu Animals Final 7905",
3257    "Brushbox - Cupcake",
3258    "Brushbox - Fish Tank",
3259    "Incredible Science - Balloons",
3260    "Incredible Science - Fire",
3261    "Incredible Science - Liquid Nitrogen",
3262    "Sign Language with Rabbi B - Part 1",
3263    "Sign Language with Rabbi B - Part 2",
3264    "Dr. Sensy",
3265    "Shloima & Friends",
3266    "Llama Food Craft",
3267    "Toveedo Clubhouse - Chanukah Special",
3268    "Bentzi Marcus - Chai",
3269    "Chaptzem Theme Song",
3270    "The Bal Shem Tov & Eliyahu Hanavi",
3271    "Seven/Eight - Afikoman #TYH",
3272    "I Love You Hashem - Joey Newcomb ft. Afikoman #TYH",
3273    "Tried & True CGC 3 - Trailer",
3274    "Tried and True -  CGC 3",
3275    "Woh 3 Trailer",
3276    "Woh Safari Adventure trailer",
3277    "Wonders Of Hashem Under The Sea Trailer",
3278    "Shepselach Theme Song",
3279    "Pisom Trailer",
3280    "The Secret Room - The Blame Game",
3281    "Opposite_day Trailer",
3282    "Pet_penny Trailer",
3283    "Shofar_calling Trailer",
3284    "Double_mitzvah Trailer",
3285    "No_voice Trailer",
3286    "Copy_cups Trailer",
3287    "Stunt Matzah Trailer",
3288    "First_day Trailer",
3289    "Fan-Tastic Trailer",
3290    "Sukkah Blunders Trailer",
3291    "Midnight Watch Teaser- Coming Soon!",
3292    "Pintelach Teaser- Coming Soon!",
3293    "Chaptzem Teaser- Coming Soon",
3294    "Dr. Shnitzel - Liquid Nitrogen - Part 1",
3295    "Word Power - Achdus",
3296    "Midnight Watch - Episode 1",
3297    "Chaptzem - Wild Ride - pt. 1",
3298    "The Shepselach - The Great Pacifier Hunt",
3299    "Rabbi B - A Fresh Start for Nature",
3300    "Word Power - Imagination",
3301    "Word Power - Nature",
3302    "Chaptzem - Wild Ride - pt. 2",
3303    "Billionaire  #TYH Boys",
3304    "Chaptzem - Wild Ride - pt. 3",
3305    "Word Power - Humility",
3306    "Toveedo by Kids #5 Promo",
3307    "Chaptzem - Wild Ride - pt. 4",
3308    "Hero Clip",
3309    "The Mystery of Chaviv - Ep 8. A Radical Change",
3310    "The Mystery of Chaviv - Ep 9. The Quartet",
3311    "The Mystery of Chaviv - Ep 10. Moachnet",
3312    "Ayin Tova - Mirel Bennet",
3313    "Chaptzem - Wild Ride - pt. 5",
3314    "Judeo - Kol Isha",
3315    "Rabbi B - The Four Seasons",
3316    "Word Power - Self Esteem",
3317    "The Shepselach - Shaina's Pink Nose",
3318    "Word Power - I Love You",
3319    "Word Power - Recognition",
3320    "Good Morning Rooster - Modeh Ani",
3321    "Midnight Watch - Episode 2",
3322    "V’Nahapoch Hu - Afikoman Ft. Brother Sochi #TYH",
3323    "Chaptzem Purim - Yiddish",
3324    "Word Power - S2e01 - Strength",
3325    "Adar Dance",
3326    "This Is Your Moment - Kol Isha",
3327    "Billionaire",
3328    "Ein Kelokeinu",
3329    "I'm Grateful",
3330    "Ah-Yay - Kol Isha",
3331    "Nothing Holding You Back",
3332    "B’Yachad",
3333    "Magic with Shaya",
3334    "Shirei Netzach",
3335    "Pintelach 3 - Yomim Tovim",
3336    "Ropogos- Behind the Scenes of Season 1",
3337    "Funtastic Circus with Bello",
3338    "Word Power - Gratitude",
3339    "1,2,3 - Leah Avruch",
3340    "Dr. Shnitzel - Liquid Nitrogen - Part 2",
3341    "Midnight Watch - Episode 3",
3342    "Midnight Watch - Episode 4",
3343    "The Secret Room - Honesty",
3344    "Captain Sinai Ep. 6 - Cutting the Plishti's Down to Size",
3345    "The Shepselach - The Cozy Fort Adventure",
3346    "New Studio Live Stream 59998",
3347    "Impressions (for women and girls)",
3348    "The Fifth Kos- Coming Soon!",
3349    "Judeo- Coming Soon!",
3350    "Impressions by Rachel's Place- Coming Soon!",
3351    "Ropogos Season 2 - Trailer",
3352    "Mission To Pisom",
3353    "The Fifth Kos",
3354    "Hashem's Army #TYH Boys",
3355    "Pesach Medley - Afikoman",
3356    "Ropogos - S2 Ep 1. Vacation Mode",
3357    "Fritz Loses His Wits - Reb Nachman's Stories",
3358    "Rabbi B - Learning Torah pt. 1",
3359    "Word Power - Lashon Harah",
3360    "Schnorgelovich - Keilim Mikvah",
3361    "Secret Room - Someone Else's Turn",
3362    "Who I Am - Devorah Schwartz",
3363    "The Formula - Part 1/3 (For ages 12+)",
3364    "The Formula- Trailer",
3365    "Dr. Shnitzel - Fire",
3366    "Schnorgelovich - Shiluach Hakan",
3367    "Ropogos - S2 Ep 2. Erev Shabbos Mode",
3368    "Numbers - Shiras Hayam",
3369    "Everything's Amazing - Shiras Hayam",
3370    "I Know You're There - Shiras Hayam",
3371    "Better Together - Shiras_hayam ft. Tehila Erlich",
3372    "The Formula - Part 3/3 (For ages 12+)",
3373    "The Formula - Part 2/3 (For ages 12+)",
3374    "Rabbi B - Chodesh Sivan",
3375    "Shine My Light",
3376    "Jungle - Mendy Worch # TYH Boys",
3377    "I Wanna Be Like Zusha (Acapella)",
3378    "Ahavas Yisroel - Mendy Worch & Yossi Green #TYH",
3379    "Ropogos - S2 Ep 3. The Show Must Go On",
3380    "The Shepselach - Leftover Seeds",
3381    "Fraidy Katz - Screaming Ant Mystery Pt. 1",
3382    "Fraidy Katz - Screaming Ant Mystery Pt. 2",
3383    "Midnight Watch - Episode 5",
3384    "Secret Room - Games with Cookies",
3385    "The Nochum Show - Everything Is A Lesson",
3386    "Midbar Menny - Parshas Balak",
3387    "Midbar Menny - Parshas Pinchas",
3388    "Midbar Menny - Parshas Matos-Maasei",
3389    "Midbar Menny - Parshas Devorim",
3390    "Midbar Menny - Parshas Va'eschanan",
3391    "Midbar Menny - Parshas Eikev",
3392    "Ropogos - S2 Ep 4. No Sleep, No Papers",
3393    "Toveedo by Kids #6 Promo",
3394    "Backyard Treasure (For ages 10+)",
3395    "Rabbi B - Tammuz And Summer",
3396    "The Shepselach - Shmooberry Smoothies",
3397    "Dr Shnitzel - Helmet Safety",
3398    "Keeper Of The Keilim - Trailer",
3399    "The Shepselach - Shmoo Day School",
3400    "Corn Salad - Toveedo By Kids",
3401    "Glitter Art - Toveedo By Kids",
3402    "Hashovas Avaida  - Toveedo By Kids",
3403    "How To Juggle - Toveedo By Kids",
3404    "Cinnamon Buns - Toveedo By Kids",
3405    "Lemonade - Toveedo By Kids",
3406    "Magic Ink - Toveedo By Kids",
3407    "The Plan - Toveedo By Kids",
3408    "The Miracle Dirt",
3409    "Agent Emes 2.0 - Toveedo by Kids",
3410    "The Keeper Of The Keilim",
3411    "Midos Power Kids - Ita the Presumptuous",
3412    "Midnight Watch - Episode 6 (For ages 10+)",
3413    "Midnight Watch - Episode 7 (For ages 10+)",
3414    "Secret Room - Kind Words",
3415    "Ropogos - S2 Ep 5. Pressure Flight",
3416    "The Secret Room - A Bowl of Blueberry Cream",
3417    "The Secret Room - The Great Restoration",
3418    "Never Lose Hope - Reb Nachman's Stories",
3419    "No Shtus! #TYH - Avraham Fried",
3420    "Word Power - S1e10 - Bravery",
3421    "Word Power - S2e04 - Responsibility",
3422    "Torah Town - Race Day",
3423    "Hands Off -  CGC 4",
3424    "Ropogos- Behind the Scenes of Season 2",
3425    "The Shepselach - Feel Better Momo!",
3426    "Schnorgelovich - Hashovas Aveida",
3427    "Bernoulli's Principle with Dr Shnitzel",
3428    "Midnight Watch - Episode 8",
3429    "Coming Soon: The Go's",
3430    "Chaptzem Identity Shaimos - Part 1",
3431    "Chaptzem Identity Shaimos - Part 2",
3432    "Chaptzem Identity Shaimos - Part 3",
3433    "Chaptzem Identity Shaimos - Part 4",
3434    "GO Theme Song",
3435    "The Shepselach - Gavi's Birthday",
3436    "Secret-Room-Ep39_beware-Virus",
3437    "Secret-Room-Ep41_the-Seed-Of-Kindness",
3438    "Secret-Room-Ep38_sorry",
3439    "Unit 2 For Kids",
3440    "Unit 1 For Kids",
3441    "Unit 3 For Kids",
3442    "The Gos - Ep. 1",
3443    "Echoes_of_faith (2160p)",
3444    "Anash_ep_1_4k (2160p)",
3445    "Anonymous Benefactor (recommended ages 10+)",
3446  ];
3447
3448    function normalizedString(value) {
3449      return String(value == null ? "" : value).replace(/\s+/g, " ").trim();
3450    }
3451
3452    function normalizeArray(value) {
3453      return Array.isArray(value) ? value : [];
3454    }
3455
3456    function normalizeManifestCategory(category) {
3457      var label = normalizedString(
3458        category && (category.label || category.title || category.name),
3459      );
3460      var id =
3461        category && category.id != null
3462          ? String(category.id)
3463          : normalizeText(label);
3464
3465      if (!label || !id) return null;
3466
3467      return {
3468        id: id,
3469        title: normalizedString((category && category.title) || label),
3470        label: label,
3471        permalink: normalizedString(category && category.permalink),
3472        public: !(category && category.public === false),
3473        legacyId:
3474          (category && (category.legacyId || category.legacy_id)) || null,
3475        contentCount:
3476          Number(category && (category.contentCount || category.content_count)) ||
3477          0,
3478        icon: (category && category.icon) || "CA",
3479      };
3480    }
3481
3482    function normalizeManifestVideo(video) {
3483      var title = normalizedString(
3484        video && (video.title || video.label || video.name),
3485      );
3486      var id =
3487        video && video.id != null ? String(video.id) : normalizeText(title);
3488      var categoryLabels = normalizeArray(video && video.categoryLabels)
3489        .concat(normalizeArray(video && video.category_labels))
3490        .map(normalizedString)
3491        .filter(Boolean);
3492
3493      if (!title || !id) return null;
3494
3495      return {
3496        id: id,
3497        title: title,
3498        label: title,
3499        permalink: normalizedString(video && video.permalink),
3500        contentType:
3501          (video && (video.contentType || video.content_type)) || "video",
3502        categoryIds: normalizeArray(video && video.categoryIds)
3503          .concat(normalizeArray(video && video.category_ids))
3504          .map(function (item) {
3505            return String(item == null ? "" : item).trim();
3506          })
3507          .filter(Boolean),
3508        categoryLabels: categoryLabels,
3509        releaseStage:
3510          (video && (video.releaseStage || video.release_stage)) || "",
3511        publishedAt:
3512          video && (video.publishedAt || video.published_at)
3513            ? video.publishedAt || video.published_at
3514            : null,
3515        icon: (video && video.icon) || "VI",
3516        searchText: normalizeText(
3517          [title, video && video.permalink, categoryLabels.join(" ")].join(" "),
3518        ),
3519      };
3520    }
3521
3522    function normalizeParentalCatalogManifest(payload) {
3523      var fallback = PARENTAL_CATALOG_MANIFEST || {};
3524      var source = payload || {};
3525      var categories = normalizeArray(source.categories)
3526        .map(normalizeManifestCategory)
3527        .filter(Boolean);
3528      var videos = normalizeArray(source.videos)
3529        .map(normalizeManifestVideo)
3530        .filter(Boolean);
3531
3532      if (!categories.length) {
3533        categories = normalizeArray(fallback.categories)
3534          .map(normalizeManifestCategory)
3535          .filter(Boolean);
3536      }
3537
3538      return {
3539        schemaVersion: source.schemaVersion || SCHEMA_VERSION,
3540        source: source.source || "static-fallback",
3541        syncedAt: source.syncedAt || "",
3542        totalContents: Number(source.totalContents || videos.length) || 0,
3543        categories: categories,
3544        videos: videos,
3545        channels: normalizeArray(fallback.channels),
3546      };
3547    }
3548
3549    function getCachedParentalCatalogManifest() {
3550      var cached = readStorageJson(CATALOG_MANIFEST_CACHE_KEY);
3551      var savedAt = Number(cached && cached.savedAt);
3552
3553      if (!cached || !cached.manifest || !savedAt) return null;
3554
3555      if (Date.now() - savedAt > CATALOG_MANIFEST_CACHE_MAX_AGE_MS) {
3556        removeStorageItem(CATALOG_MANIFEST_CACHE_KEY);
3557        return null;
3558      }
3559
3560      return normalizeParentalCatalogManifest(cached.manifest);
3561    }
3562
3563    function cacheParentalCatalogManifest(manifest) {
3564      if (!manifest || !manifest.categories || !manifest.videos) return;
3565
3566      writeStorageJson(CATALOG_MANIFEST_CACHE_KEY, {
3567        savedAt: Date.now(),
3568        manifest: manifest,
3569      });
3570    }
3571
3572    function getParentalCatalogManifest() {
3573      var cachedManifest = state.catalogManifest || getCachedParentalCatalogManifest();
3574
3575      if (cachedManifest) {
3576        state.catalogManifest = cachedManifest;
3577        return cachedManifest;
3578      }
3579
3580      return normalizeParentalCatalogManifest(PARENTAL_CATALOG_MANIFEST);
3581    }
3582
3583    function loadParentalCatalogManifest(forceRefresh) {
3584      if (state.catalogManifestPromise) return state.catalogManifestPromise;
3585
3586      state.catalogManifest = getParentalCatalogManifest();
3587
3588      if (
3589        !forceRefresh &&
3590        state.catalogManifestLastFetchedAt &&
3591        Date.now() - state.catalogManifestLastFetchedAt <
3592          CATALOG_MANIFEST_REVALIDATE_INTERVAL_MS
3593      ) {
3594        return Promise.resolve(state.catalogManifest);
3595      }
3596
3597      state.catalogManifestLastFetchedAt = Date.now();
3598
3599      state.catalogManifestPromise = window
3600        .fetch(CATALOG_MANIFEST_ENDPOINT, {
3601          mode: "cors",
3602          credentials: "omit",
3603          headers: { Accept: "application/json" },
3604          cache: "no-store",
3605        })
3606        .then(function (response) {
3607          if (!response.ok) {
3608            throw new Error(
3609              "Catalog manifest failed with status " + response.status,
3610            );
3611          }
3612
3613          return response.json();
3614        })
3615        .then(function (payload) {
3616          var manifest = normalizeParentalCatalogManifest(payload);
3617
3618          if (manifest.categories.length || manifest.videos.length) {
3619            state.catalogManifest = manifest;
3620            cacheParentalCatalogManifest(manifest);
3621          }
3622
3623          return state.catalogManifest;
3624        })
3625        .catch(function () {
3626          return getParentalCatalogManifest();
3627        })
3628        .then(function (manifest) {
3629          state.catalogManifestPromise = null;
3630          return manifest;
3631        });
3632
3633      return state.catalogManifestPromise;
3634    }
3635
3636    function findManifestVideo(value) {
3637      var normalized = normalizeText(value);
3638      if (!normalized) return null;
3639
3640      return normalizeArray(getParentalCatalogManifest().videos).find(function (video) {
3641        return (
3642          normalizeText(video.id) === normalized ||
3643          normalizeText(video.title) === normalized ||
3644          normalizeText(video.permalink) === normalized
3645        );
3646      });
3647    }
3648
3649    function findManifestCategory(value) {
3650      var normalized = normalizeText(value);
3651      if (!normalized) return null;
3652
3653      return normalizeArray(getParentalCatalogManifest().categories).find(function (category) {
3654        return (
3655          normalizeText(category.id) === normalized ||
3656          normalizeText(category.label) === normalized ||
3657          normalizeText(category.title) === normalized ||
3658          normalizeText(category.permalink) === normalized
3659        );
3660      });
3661    }
3662
3663    function normalizeEmail(value) {
3664      return String(value || "").trim().toLowerCase();
3665    }
3666
3667    function normalizeText(value) {
3668      return String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
3669    }
3670
3671    function htmlEscape(value) {
3672      return String(value == null ? "" : value)
3673        .replace(/&/g, "&amp;")
3674        .replace(/</g, "&lt;")
3675        .replace(/>/g, "&gt;")
3676        .replace(/"/g, "&quot;")
3677        .replace(/'/g, "&#039;");
3678    }
3679
3680    function clearLegacyParentalStorage() {
3681      var storages = [];
3682
3683      try {
3684        storages.push(window.localStorage);
3685      } catch (error) {}
3686
3687      try {
3688        storages.push(window.sessionStorage);
3689      } catch (error) {}
3690
3691      storages.forEach(function (storage) {
3692        try {
3693          if (!storage) return;
3694
3695          for (var index = storage.length - 1; index >= 0; index -= 1) {
3696            var key = storage.key(index);
3697            if (key && key.indexOf(LEGACY_STORAGE_PREFIX) === 0) {
3698              storage.removeItem(key);
3699            }
3700          }
3701        } catch (error) {}
3702      });
3703    }
3704
3705    function readStorageJson(key) {
3706      try {
3707        var raw = window.localStorage && window.localStorage.getItem(key);
3708        return raw ? JSON.parse(raw) : null;
3709      } catch (error) {
3710        return null;
3711      }
3712    }
3713
3714    function writeStorageJson(key, value) {
3715      try {
3716        if (window.localStorage) {
3717          window.localStorage.setItem(key, JSON.stringify(value));
3718        }
3719      } catch (error) {}
3720    }
3721
3722    function removeStorageItem(key) {
3723      try {
3724        if (window.localStorage) {
3725          window.localStorage.removeItem(key);
3726        }
3727      } catch (error) {}
3728    }
3729
3730    function emptyAccount() {
3731      return {
3732        schemaVersion: SCHEMA_VERSION,
3733        source: "toveedo-parental-control-v1",
3734        updatedAt: "",
3735        parentPinHash: "",
3736        profiles: []
3737      };
3738    }
3739
3740    function emptyProfile(type) {
3741      return normalizeProfile({
3742        id: "profile_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 7),
3743        name: type === "parent" ? "Parent" : "Kid",
3744        type: type || "kid",
3745        birthday: "",
3746        pinHash: "",
3747        rules: {
3748          blockedVideos: [],
3749          blockedChannels: [],
3750          blockedCategories: [],
3751          maxAgeRating: "all",
3752          timeLimits: {
3753            enabled: false,
3754            allowedStart: "08:00",
3755            allowedEnd: "20:00",
3756            dailyMinutes: 120
3757          }
3758        }
3759      });
3760    }
3761
3762    function normalizeProfile(profile) {
3763      profile.rules = profile.rules || {};
3764      profile.rules.blockedVideos = Array.isArray(profile.rules.blockedVideos)
3765        ? profile.rules.blockedVideos
3766        : [];
3767      profile.rules.blockedChannels = Array.isArray(profile.rules.blockedChannels)
3768        ? profile.rules.blockedChannels
3769        : [];
3770      profile.rules.blockedCategories = Array.isArray(profile.rules.blockedCategories)
3771        ? profile.rules.blockedCategories
3772        : [];
3773      profile.rules.maxAgeRating = profile.rules.maxAgeRating || "all";
3774      profile.rules.timeLimits = Object.assign(
3775        {
3776          enabled: false,
3777          allowedStart: "08:00",
3778          allowedEnd: "20:00",
3779          dailyMinutes: 120
3780        },
3781        profile.rules.timeLimits || {}
3782      );
3783      return profile;
3784    }
3785
3786    function parseAccount(rawValue) {
3787      if (!rawValue) return emptyAccount();
3788      try {
3789        var parsed = typeof rawValue === "string" ? JSON.parse(rawValue) : rawValue;
3790        if (!parsed || typeof parsed !== "object") return emptyAccount();
3791        parsed.profiles = Array.isArray(parsed.profiles) ? parsed.profiles : [];
3792        parsed.profiles.forEach(normalizeProfile);
3793        return Object.assign(emptyAccount(), parsed);
3794      } catch (error) {
3795        return emptyAccount();
3796      }
3797    }
3798
3799    function hideField3() {
3800      var selectors = [
3801        'ds-input[name="form[field_3]"]',
3802        "ds-input#form_field_3",
3803        'input[name="form[field_3]"]',
3804        'textarea[name="form[field_3]"]',
3805        'select[name="form[field_3]"]',
3806        '[name="user[field_3]"]',
3807        "#form_field_3",
3808        "#user_field_3",
3809        '[for="form_field_3"]',
3810        '[for="user_field_3"]'
3811      ];
3812
3813      document.querySelectorAll(selectors.join(",")).forEach(function (field) {
3814        var wrapper =
3815          field.closest(".w-full, .mb-6.flex.flex-col, .mb-5, .form-group, .field, [data-test]") ||
3816          field.parentElement ||
3817          field;
3818        wrapper.classList.add("toveedo-parental-field-hidden");
3819      });
3820
3821      document.querySelectorAll("p, label, span").forEach(function (node) {
3822        if (normalizeText(node.textContent) !== "parental control") return;
3823        var wrapper =
3824          node.closest(".w-full, .mb-6.flex.flex-col, .mb-5, .form-group, .field, [data-test]") ||
3825          node.parentElement ||
3826          node;
3827        wrapper.classList.add("toveedo-parental-field-hidden");
3828      });
3829    }
3830
3831    function fetchJson(url) {
3832      if (window.axios && typeof window.axios.get === "function") {
3833        return window.axios.get(url, { headers: { Accept: "application/json" } }).then(function (response) {
3834          return response.data;
3835        });
3836      }
3837
3838      return fetch(url, {
3839        credentials: "same-origin",
3840        headers: { Accept: "application/json" }
3841      }).then(function (response) {
3842        if (!response.ok) throw new Error("Request failed: " + response.status);
3843        return response.json();
3844      });
3845    }
3846
3847    function putUserField(value) {
3848      var formData = new FormData();
3849      formData.append(FORM_FIELD_NAME, value);
3850
3851      if (window.axios && typeof window.axios.put === "function") {
3852        return window.axios.put("/api/users", formData).then(function (response) {
3853          return response.data;
3854        });
3855      }
3856
3857      return fetch("/api/users", {
3858        method: "PUT",
3859        credentials: "same-origin",
3860        body: formData
3861      }).then(function (response) {
3862        if (!response.ok) throw new Error("Save failed: " + response.status);
3863        return response.text();
3864      });
3865    }
3866
3867    function hasSession(session) {
3868      return Boolean(
3869        session &&
3870          (session.logged === true ||
3871            session.logged_in === true ||
3872            session.loggedIn === true ||
3873            session.user ||
3874            session.current_user ||
3875            session.email ||
3876            session.user_id ||
3877            session.id)
3878      );
3879    }
3880
3881    function accountCacheKey(email) {
3882      return ACCOUNT_CACHE_STORAGE_KEY + normalizeEmail(email || TEST_USER_EMAIL);
3883    }
3884
3885    function cacheAccount(email, account) {
3886      if (!account || !account.profiles) return;
3887
3888      writeStorageJson(accountCacheKey(email), {
3889        savedAt: Date.now(),
3890        account: account
3891      });
3892    }
3893
3894    function getCachedAccount(email) {
3895      var cached = readStorageJson(accountCacheKey(email));
3896      var savedAt = Number(cached && cached.savedAt);
3897      if (!cached || !cached.account) return null;
3898
3899      if (!savedAt || Date.now() - savedAt > ACCOUNT_CACHE_MAX_AGE_MS) {
3900        removeStorageItem(accountCacheKey(email));
3901        return null;
3902      }
3903
3904      return parseAccount(cached.account);
3905    }
3906
3907    function clearAccountCache(email) {
3908      var normalizedEmail = normalizeEmail(email || TEST_USER_EMAIL);
3909      var storedSelection = readStorageJson(ACTIVE_PROFILE_STORAGE_KEY);
3910
3911      removeStorageItem(accountCacheKey(normalizedEmail));
3912
3913      if (
3914        !storedSelection ||
3915        normalizeEmail(storedSelection.email) === normalizedEmail
3916      ) {
3917        removeStorageItem(ACTIVE_PROFILE_STORAGE_KEY);
3918      }
3919
3920      if (normalizeEmail(state.activeSelection.email) === normalizedEmail) {
3921        state.activeSelection = {
3922          email: normalizedEmail,
3923          profileId: ""
3924        };
3925      }
3926    }
3927
3928    function accountScore(account) {
3929      if (!account) return 0;
3930      return (account.parentPinHash ? 100 : 0) + ((account.profiles || []).length || 0);
3931    }
3932
3933    function accountUpdatedAt(account) {
3934      var time = Date.parse((account && account.updatedAt) || "");
3935      return Number.isNaN(time) ? 0 : time;
3936    }
3937
3938    function chooseAccount(remoteAccount, cachedAccount) {
3939      var remoteScore = accountScore(remoteAccount);
3940      var cachedScore = accountScore(cachedAccount);
3941      var remoteUpdatedAt = accountUpdatedAt(remoteAccount);
3942      var cachedUpdatedAt = accountUpdatedAt(cachedAccount);
3943
3944      if (!remoteScore) return cachedScore ? cachedAccount : emptyAccount();
3945      if (!cachedScore) return remoteAccount;
3946
3947      if (
3948        remoteUpdatedAt &&
3949        cachedUpdatedAt &&
3950        remoteUpdatedAt !== cachedUpdatedAt
3951      ) {
3952        return remoteUpdatedAt > cachedUpdatedAt ? remoteAccount : cachedAccount;
3953      }
3954
3955      if (cachedScore > remoteScore) return cachedAccount;
3956      if (remoteScore > cachedScore) return remoteAccount;
3957
3958      return remoteAccount;
3959    }
3960
3961    function saveAccount(email, account) {
3962      account.updatedAt = new Date().toISOString();
3963      return putUserField(JSON.stringify(account)).then(function () {
3964        cacheAccount(email, account);
3965        return account;
3966      });
3967    }
3968
3969    function getSession() {
3970      if (!state.sessionPromise) {
3971        state.sessionPromise = fetchJson("/api/sessions").catch(function () {
3972          return null;
3973        });
3974      }
3975      return state.sessionPromise;
3976    }
3977
3978    function getCurrentUser() {
3979      if (!state.userPromise) {
3980        state.userPromise = getSession()
3981          .then(function (session) {
3982            if (!hasSession(session)) return null;
3983            return fetchJson("/api/users/me").catch(function () {
3984              return session || null;
3985            });
3986          })
3987          .catch(function () {
3988            return null;
3989          });
3990      }
3991      return state.userPromise;
3992    }
3993
3994    function getUserEmail(user) {
3995      return normalizeEmail(
3996        user &&
3997          (user.email ||
3998            (user.user && user.user.email) ||
3999            (user.current_user && user.current_user.email))
4000      );
4001    }
4002
4003    function getUserField(user, key) {
4004      var containers = [
4005        user,
4006        user && user.user,
4007        user && user.current_user,
4008        user && user.fields,
4009        user && user.custom_fields,
4010        user && user.customFields
4011      ];
4012
4013      for (var index = 0; index < containers.length; index += 1) {
4014        if (
4015          containers[index] &&
4016          Object.prototype.hasOwnProperty.call(containers[index], 
4016key)
4017        ) {
4018          return containers[index][key];
4019        }
4020      }
4021
4022      return undefined;
4023    }
4024
4025    function isEnabledUser(user) {
4026      return getUserEmail(user) === TEST_USER_EMAIL;
4027    }
4028
4029    function getAccountForUser(user, email) {
4030      var remoteAccountValue = getUserField(user, FIELD_KEY);
4031      var remoteFieldWasCleared =
4032        remoteAccountValue === null ||
4033        (typeof remoteAccountValue === "string" &&
4034          remoteAccountValue.trim() === "");
4035
4036      if (remoteFieldWasCleared) {
4037        clearAccountCache(email);
4038        return emptyAccount();
4039      }
4040
4041      var remoteAccount = parseAccount(remoteAccountValue);
4042      var cachedAccount = getCachedAccount(email);
4043      var account = chooseAccount(remoteAccount, cachedAccount);
4044
4045      if (accountScore(account)) {
4046        cacheAccount(email, account);
4047      }
4048
4049      return account;
4050    }
4051
4052    function getActiveSelection(email) {
4053      var normalizedEmail = normalizeEmail(email);
4054      var storedSelection = null;
4055
4056      if (normalizeEmail(state.activeSelection.email) === normalizedEmail) {
4057        return state.activeSelection.profileId || "";
4058      }
4059
4060      storedSelection = readStorageJson(ACTIVE_PROFILE_STORAGE_KEY);
4061      if (
4062        !storedSelection ||
4063        normalizeEmail(storedSelection.email) !== normalizedEmail
4064      ) {
4065        return "";
4066      }
4067
4068      state.activeSelection = {
4069        email: normalizedEmail,
4070        profileId: storedSelection.profileId || ""
4071      };
4072      return state.activeSelection.profileId || "";
4073    }
4074
4075    function setActiveSelection(email, profileId) {
4076      var normalizedEmail = normalizeEmail(email);
4077      state.activeSelection = {
4078        email: normalizedEmail,
4079        profileId: profileId || ""
4080      };
4081
4082      if (profileId) {
4083        writeStorageJson(ACTIVE_PROFILE_STORAGE_KEY, {
4084          email: normalizedEmail,
4085          profileId: profileId,
4086          selectedAt: new Date().toISOString()
4087        });
4088      } else {
4089        removeStorageItem(ACTIVE_PROFILE_STORAGE_KEY);
4090      }
4091    }
4092
4093    function getActiveProfile(account, email) {
4094      var profileId = getActiveSelection(email);
4095      return (account.profiles || []).find(function (profile) {
4096        return profile.id === profileId;
4097      });
4098    }
4099
4100    function isCatalogRoute() {
4101      return window.location.pathname.indexOf("/catalog") === 0;
4102    }
4103
4104    function isParentalModalSuppressedRoute() {
4105      return /^\/(?:join|checkout|account)(?:\/|$)/.test(window.location.pathname);
4106    }
4107
4108    function dismissParentalModal() {
4109      var existing = document.getElementById(MODAL_ID);
4110      if (existing) existing.remove();
4111    }
4112
4113    function catalogConfirmKey(email) {
4114      return normalizeEmail(email);
4115    }
4116
4117    function markCatalogConfirmed(email) {
4118      state.catalogConfirmations[catalogConfirmKey(email)] = true;
4119    }
4120
4121    function shouldConfirmOnCatalog(email) {
4122      return (
4123        isCatalogRoute() &&
4124        !getActiveSelection(email) &&
4125        state.catalogConfirmations[catalogConfirmKey(email)] !== true
4126      );
4127    }
4128
4129    function hashPin(pin) {
4130      var value = String(pin || "").trim();
4131      if (!value) return Promise.resolve("");
4132
4133      if (window.crypto && window.crypto.subtle && window.TextEncoder) {
4134        return window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)).then(function (buffer) {
4135          return (
4136            "sha256:" +
4137            Array.from(new Uint8Array(buffer))
4138              .map(function (byte) {
4139                return byte.toString(16).padStart(2, "0");
4140              })
4141              .join("")
4142          );
4143        });
4144      }
4145
4146      return Promise.resolve("plain-fallback:" + btoa(value));
4147    }
4148
4149    function verifyPin(pin, hash) {
4150      return hashPin(pin).then(function (candidate) {
4151        return Boolean(candidate && hash && candidate === hash);
4152      });
4153    }
4154
4155    function profileLabel(profile) {
4156      return profile && profile.name ? profile.name : "Profile";
4157    }
4158
4159    function profileAvatar(profile) {
4160      var label = profileLabel(profile);
4161      if (profile && profile.type === "kid") return "K";
4162      return label ? label.charAt(0).toUpperCase() : "P";
4163    }
4164
4165    function pinBoxesHtml(id, label) {
4166      var safeLabel = label || "PIN";
4167      var labelHtml = label
4168        ? "<span>" + htmlEscape(label) + "</span>"
4169        : '<span class="toveedo-pc-sr-only">' + htmlEscape(safeLabel) + "</span>";
4170      var boxes = [0, 1, 2, 3]
4171        .map(function (index) {
4172          return (
4173            '<input class="toveedo-pc-pin-digit" data-tpc-pin-digit type="password" inputmode="numeric" maxlength="1" autocomplete="off" aria-label="' +
4174            htmlEscape(safeLabel + " digit " + (index + 1)) +
4175            '">'
4176          );
4177        })
4178        .join("");
4179
4180      return (
4181        '<label class="toveedo-pc-field toveedo-pc-pin-field">' +
4182        labelHtml +
4183        '<input class="toveedo-pc-pin-value" id="' +
4184        htmlEscape(id) +
4185        '" type="hidden" value="">' +
4186        '<div class="toveedo-pc-pin-grid" data-tpc-pin-group data-tpc-pin-target="' +
4187        htmlEscape(id) +
4188        '">' +
4189        boxes +
4190        "</div></label>"
4191      );
4192    }
4193
4194    function findPinGroup(id) {
4195      var groups = document.querySelectorAll("[data-tpc-pin-group]");
4196      for (var index = 0; index < groups.length; index += 1) {
4197        if (groups[index].getAttribute("data-tpc-pin-target") === id) {
4198          return groups[index];
4199        }
4200      }
4201
4202      return null;
4203    }
4204
4205    function syncPinGroup(group) {
4206      if (!group) return "";
4207
4208      var value = Array.from(group.querySelectorAll("[data-tpc-pin-digit]"))
4209        .map(function (input) {
4210          var digit = String(input.value || "").replace(/\D/g, "").slice(-1);
4211          input.value = digit;
4212          return digit;
4213        })
4214        .join("");
4215      var target = document.getElementById(group.getAttribute("data-tpc-pin-target"));
4216      if (target) target.value = value;
4217      return value;
4218    }
4219
4220    function setPinGroupValue(group, value) {
4221      if (!group) return;
4222
4223      var digits = String(value || "").replace(/\D/g, "").slice(0, 4).split("");
4224      Array.from(group.querySelectorAll("[data-tpc-pin-digit]")).forEac
4224h(function (input, index) {
4225        input.value = digits[index] || "";
4226      });
4227      syncPinGroup(group);
4228    }
4229
4230    function getPinValue(id) {
4231      var group = findPinGroup(id);
4232      if (group) return syncPinGroup(group);
4233
4234      var input = document.getElementById(id);
4235      return input ? String(input.value || "").replace(/\D/g, "").slice(0, 4) : "";
4236    }
4237
4238    function focusPinInput(id) {
4239      var group = findPinGroup(id);
4240      var input = group && group.querySelector("[data-tpc-pin-digit]");
4241      if (input) input.focus();
4242    }
4243
4244    function bindPinEntry(root) {
4245      if (!root || root.dataset.toveedoPinBound === "true") return;
4246
4247      root.dataset.toveedoPinBound = "true";
4248
4249      root.addEventListener("input", function (event) {
4250        var input = event.target.closest("[data-tpc-pin-digit]");
4251        if (!input) return;
4252
4253        var group = input.closest("[data-tpc-pin-group]");
4254        var digits = String(input.value || "").replace(/\D/g, "");
4255        var inputs = Array.from(group.querySelectorAll("[data-tpc-pin-digit]"));
4256        var currentIndex = inputs.indexOf(input);
4257
4258        if (digits.length > 1) {
4259          setPinGroupValue(group, digits);
4260          var nextEmpty = inputs.find(function (item) {
4261            return !item.value;
4262          });
4263          (nextEmpty || inputs[inputs.length - 1]).focus();
4264          return;
4265        }
4266
4267        input.value = digits;
4268        syncPinGroup(group);
4269
4270        if (digits && currentIndex < inputs.length - 1) {
4271          inputs[currentIndex + 1].focus();
4272        }
4273      });
4274
4275      root.addEventListener("keydown", function (event) {
4276        var input = event.target.closest("[data-tpc-pin-digit]");
4277        if (!input) return;
4278
4279        var group = input.closest("[data-tpc-pin-group]");
4280        var inputs = Array.from(group.querySelectorAll("[data-tpc-pin-digit]"));
4281        var currentIndex = inputs.indexOf(input);
4282
4283        if (event.key === "Enter") {
4284          var modal = group.closest(".toveedo-pc-modal");
4285          var actionButton =
4286            modal &&
4287            modal.querySelector(
4288              "[data-tpc-unlock], [data-tpc-parent-manage], [data-tpc-setup], [data-tpc-parent-unlock]",
4289            );
4290
4291          if (actionButton) {
4292            event.preventDefault();
4293            syncPinGroup(group);
4294            actionButton.click();
4295          }
4296          return;
4297        }
4298
4299        if (event.key === "Backspace" && !input.value && currentIndex > 0) {
4300          inputs[currentIndex - 1].focus();
4301          return;
4302        }
4303
4304        if (event.key === "ArrowLeft" && currentIndex > 0) {
4305          event.preventDefault();
4306          inputs[currentIndex - 1].focus();
4307          return;
4308        }
4309
4310        if (event.key === "ArrowRight" && currentIndex < inputs.length - 1) {
4311          event.preventDefault();
4312          inputs[currentIndex + 1].focus();
4313        }
4314      });
4315
4316      root.addEventListener("paste", function (event) {
4317        var input = event.target.closest("[data-tpc-pin-digit]");
4318        if (!input) return;
4319
4320        var clipboard = event.clipboardData || window.clipboardData;
4321        var pasted = clipboard ? clipboard.getData("text") : "";
4322        var digits = String(pasted || "").replace(/\D/g, "").slice(0, 4);
4323        if (!digits) return;
4324
4325        event.preventDefault();
4326
4327        var group = input.closest("[data-tpc-pin-group]");
4328        setPinGroupValue(group, digits);
4329
4330        var inputs = Array.from(group.querySelectorAll("[data-tpc-pin-digit]"));
4331        var nextEmpty = inputs.find(function (item) {
4332          return !item.value;
4333        });
4334        (nextEmpty || inputs[inputs.length - 1]).focus();
4335      });
4336    }
4337
4338    function openProfileChooser(account, email, options) {
4339      options = options || {};
4340      if (isParentalModalSuppressedRoute()) {
4341        dismissParentalModal();
4342        return;
4343      }
4344
4345      var existing = document.getElementById(MODAL_ID);
4346      if (existing && !options.force) return;
4347      if (existing) existing.remove();
4348      if (
4349        getActiveProfile(account, email) &&
4350        !options.force &&
4351        !options.catalogConfirm &&
4352        account.parentPinHash &&
4353        account.profiles.length
4354      ) {
4355        return;
4356      }
4357
4358      var backdrop = document.createElement("div");
4359      backdrop.id = MODAL_ID;
4360      backdrop.className = "toveedo-pc-backdrop";
4361      document.body.appendChild(backdrop);
4362      var workingAccount = account;
4363
4364      function closeModal() {
4365        if (options.catalogConfirm) markCatalogConfirmed(email);
4366        backdrop.remove();
4367        ensureSwitcher(workingAccount, email);
4368        scheduleSync();
4369      }
4370
4371      function frameHtml(title, bodyHtml, canClose, modalClass, beforeTitleHtml) {
4372        return (
4373          '<div class="toveedo-pc-modal ' +
4374          htmlEscape(modalClass || "") +
4375          '" role="dialog" aria-modal="true" aria-label="' +
4376          htmlEscape(title) +
4377          '" style="position:relative">' +
4378          (canClose ? '<button class="toveedo-pc-close" type="button" data-tpc-close aria-label="Close">x</button>' : "") +
4379          '<div class="toveedo-pc-modal__body toveedo-pc-stack">' +
4380          (beforeTitleHtml || "") +
4381          "<h2>" +
4382          htmlEscape(title) +
4383          "</h2>" +
4384          bodyHtml +
4385          "</div></div>"
4386        );
4387      }
4388
4389      function inputField(label, inputHtml) {
4390        return '<label class="toveedo-pc-field"><span>' + htmlEscape(label) + "</span>" + inputHtml + "</label>";
4391      }
4392
4393      function selectedHtml(value, selectedValue) {
4394        return String(value) === String(selectedValue) ? " selected" : "";
4395      }
4396
4397      function checkedHtml(value) {
4398        return value ? " checked" : "";
4399      }
4400
4401      function ruleItemsToLines(items) {
4402        return (Array.isArray(items) ? items : [])
4403          .map(function (item) {
4404            return String(
4405              typeof item === "string"
4406                ? item
4407                : (item && (item.title || item.label || item.value || item.id)) || "",
4408            ).trim();
4409          })
4410          .filter(Boolean)
4411          .join("\n");
4412      }
4413
4414      function textLinesToRuleItems(value, key) {
4415        var seen = {};
4416        return String(value || "")
4417          .split(/\n+/)
4418          .map(function (line) {
4419            return line.replace(/\s+/g, " ").trim();
4420          })
4421          .filter(function (line) {
4422            var normalized = normalizeText(line);
4423            if (!normalized || seen[normalized]) return false;
4424            seen[normalized] = true;
4425            return true;
4426          })
4427          .map(function (line) {
4428            var manifestItem =
4429              key === "title"
4430                ? findVideoFilterOption(line)
4431                : findCategoryFilterOption(line);
4432            var item = manifestItem
4433              ? Object.assign({}, manifestItem)
4434              : { label: line };
4435
4436            if (item.searchText) delete item.searchText;
4437            if (item.icon) delete item.icon;
4438            item[key] = item[key] || line;
4439            return item;
4440          });
4441      }
4442
4443      function ageRatingOptionsHtml(selectedValue) {
4444        return AGE_FILTERS.map(function (item) {
4445          return (
4446            '<option value="' +
4447            htmlEscape(item.value) +
4448            '"' +
4449            selectedHtml(item.value, selectedValue) +
4450            ">" +
4451            htmlEscape(item.value === "all" ? "All ages" : item.value) +
4452            "</option>"
4453          );
4454        }).join("");
4455      }
4456
4457      function activeProfileCanManage() {
4458        var activeProfile = getActiveProfile(workingAccount, email);
4459        return Boolean(activeProfile && activeProfile.type === "parent");
4460      }
4461
4462      function findWorkingProfile(profileId) {
4463        return workingAccount.profiles.find(function (profile) {
4464          return profile.id === profileId;
4465        });
4466      }
4467
4468      function ruleItemText(item) {
4469        return String(
4470          typeof item === "string"
4471            ? item
4472            : (item && (item.title || item.label || item.value || item.id)) || "",
4473        ).trim();
4474      }
4475
4476      function ruleListHasValue(items, value) {
4477        var normalized = normalizeText(value);
4478        return (Array.isArray(items) ? items : []).some(function (item) {
4479          if (typeof item === "string") {
4480            return normalizeText(item) === normalized;
4481          }
4482
4483          return [
4484            item && item.id,
4485            item && item.value,
4486            item && item.permalink,
4487            item && item.title,
4488            item && item.label,
4489          ].some(function (candidate) {
4490            return normalizeText(candidate) === normalized;
4491          });
4492        });
4493      }
4494
4495      function hiddenRuleTextarea(id, value) {
4496        return (
4497          '<textarea class="toveedo-pc-hidden-rules" id="' +
4498          htmlEscape(id) +
4499          '">' +
4500          htmlEscape(value) +
4501          "</textarea>"
4502        );
4503      }
4504
4505      function getRuleLines(id) {
4506        var field = document.getElementById(id);
4507        var seen = {};
4508        if (!field) return [];
4509
4510        return String(field.value || "")
4511          .split(/\n+/)
4512          .map(function (line) {
4513            return line.replace(/\s+/g, " ").trim();
4514          })
4515          .filter(function (line) {
4516            var normalized = normalizeText(line);
4517            if (!normalized || seen[normalized]) return false;
4518            seen[normalized] = true;
4519            return true;
4520          });
4521      }
4522
4523      function setRuleLines(id, lines) {
4524        var field = document.getElementById(id);
4525        if (!field) return;
4526
4527        field.value = lines.join("\n");
4528      }
4529
4530      function filterIconLabel(label, icon) {
4531        var fallback = String(label || "")
4532          .replace(/[^a-z0-9]/gi, "")
4533          .slice(0, 2)
4534          .toUpperCase();
4535        return icon || fallback || "FL";
4536      }
4537
4538      function restrictionRowHtml(targetId, value, label, icon, isBlocked) {
4539        return (
4540          '<button class="toveedo-pc-filter-row' +
4541          (isBlocked ? " is-blocked" : "") +
4542          '" type="button" data-tpc-filter-row data-tpc-toggle-rule data-tpc-target="' +
4543          htmlEscape(targetId) +
4544          '" data-tpc-value="' +
4545          htmlEscape(value) +
4546          '" aria-pressed="' +
4547          (isBlocked ? "true" : "false") +
4548          '">' +
4549          '<span class="toveedo-pc-filter-icon">' +
4550          htmlEscape(filterIconLabel(label, icon)) +
4551          "</span>" +
4552          '<span class="toveedo-pc-filter-name">' +
4553          htmlEscape(label) +
4554          "</span>" +
4555          '<span class="toveedo-pc-rule-status" data-tpc-rule-status>' +
4556          htmlEscape(isBlocked ? "Hidden" : "Shown") +
4557          "</span>" +
4558          "</button>"
4559        );
4560      }
4561
4562      function buildRestrictionCard(title, count, innerHtml) {
4563        return (
4564          '<div class="toveedo-pc-restriction-card">' +
4565          '<div class="toveedo-pc-restriction-card__head">' +
4566          "<h4>" +
4567          htmlEscape(title) +
4568          "</h4>" +
4569          '<span class="toveedo-pc-restriction-count">' +
4570          htmlEscape(count) +
4571          "</span>" +
4572          "</div>" +
4573          innerHtml +
4574          "</div>"
4575        );
4576      }
4577
4578      function getVideoFilterOptions() {
4579        var seen = {};
4580        var manifest = getParentalCatalogManifest();
4581        var channels = manifest.channels || [];
4582        var manifestVideos = manifest.videos || [];
4583        var options = [];
4584
4585        function addVideo(video, icon) {
4586          var label = String(
4587            typeof video === "string"
4588              ? video
4589              : video && (video.title || video.label || video.name),
4590          )
4591            .replace(/\s+/g, " ")
4592            .trim();
4593          var id =
4594            typeof video === "string" || !(video && video.id != null)
4595              ? ""
4596              : String(video.id);
4597          var key = id ? "id:" + id : "title:" + normalizeText(label);
4598          if (!key || seen[key]) return;
4599          seen[key] = true;
4600          options.push({
4601            id: id,
4602            title: label,
4603            label: label,
4604            permalink:
4605              typeof video === "string" ? "" : String((video && video.permalink) || ""),
4606            categoryIds:
4607              typeof video === "string" ? [] : normalizeArray(video && video.categoryIds),
4608            categoryLabels:
4609              typeof video === "string"
4610                ? []
4611                : normalizeArray(video && video.categoryLabels),
4612            contentType:
4613              typeof video === "string" ? "video" : video && video.contentType,
4614            icon: icon || (video && video.icon) || "VI",
4615            searchText: normalizeText(
4616              [
4617                label,
4618                typeof video === "string" ? "" : video && video.permalink,
4619                typeof video === "string"
4620                  ? ""
4621                  : normalizeArray(video && video.categoryLabels).join(" "),
4622              ].join(" "),
4623            )
4624          });
4625        }
4626
4627        if (manifestVideos.length) {
4628          manifestVideos.forEach(function (video) {
4629            addVideo(video, "VI");
4630          });
4631        } else {
4632          (PARENTAL_VIDEO_TITLES || []).forEach(function (title) {
4633            addVideo(title, "VI");
4634          });
4635
4636          channels.forEach(function (channel) {
4637            (channel.videos || []).forEach(function (video) {
4638              addVideo(video, channel.icon || "VI");
4639            });
4640          });
4641        }
4642
4643        return options;
4644      }
4645
4646      function findVideoFilterOption(value) {
4647        var normalized = normalizeText(value);
4648        if (!normalized) return null;
4649
4650        return getVideoFilterOptions().find(function (video) {
4651          return (
4652            normalizeText(video.id) === normalized ||
4653            normalizeText(video.title) === normalized ||
4654            normalizeText(video.permalink) === normalized
4655          );
4656        });
4657      }
4658
4659      function getCategoryFilterOptions() {
4660        return (getParentalCatalogManifest().categories || [])
4661          .map(function (category) {
4662            var option = normalizeManifestCategory(category);
4663            if (!option) return null;
4664
4665            option.searchText = normalizeText(
4666              [
4667                option.label,
4668                option.title,
4669                option.permalink,
4670                option.id,
4671              ].join(" "),
4672            );
4673
4674            return option;
4675          })
4676          .filter(Boolean);
4677      }
4678
4679      function findCategoryFilterOption(value) {
4680        var normalized = normalizeText(value);
4681        if (!normalized) return null;
4682
4683        return getCategoryFilterOptions().find(function (category) {
4684          return (
4685            normalizeText(category.id) === normalized ||
4686            normalizeText(category.label) === normalized ||
4687            normalizeText(category.title) === normalized ||
4688            normalizeText(category.permalink) === normalized
4689          );
4690        });
4691      }
4692
4693      function videoSearchMatches(option, query) {
4694        var terms = normalizeText(query).split(" ").filter(Boolean);
4695        if (!terms.length) return true;
4696
4697        return terms.every(function (term) {
4698          return option.searchText.indexOf(term) !== -1;
4699        });
4700      }
4701
4702      function categorySearchMatches(option, query) {
4703        var terms = normalizeText(query).split(" ").filter(Boolean);
4704        if (!terms.length) return true;
4705
4706        return terms.every(function (term) {
4707          return option.searchText.indexOf(term) !== -1;
4708        });
4709      }
4710
4711      function videoRowsHtml(options, blockedLines) {
4712        return options
4713          .map(function (video) {
4714            return restrictionRowHtml(
4715              "toveedo-pc-edit-blocked-videos",
4716              video.title,
4717              video.title,
4718              video.icon,
4719              blockedLines.indexOf(normalizeText(video.title)) !== -1,
4720            );
4721          })
4722          .join("");
4723      }
4724
4725      function selectedVideoRowsHtml(lines) {
4726        if (!lines.length) {
4727          return '<div class="toveedo-pc-filter-empty">No hidden videos selected.</div>';
4728        }
4729
4730        return lines
4731          .map(function (title) {
4732            return restrictionRowHtml(
4733              "toveedo-pc-edit-blocked-videos",
4734              title,
4735              title,
4736              "VI",
4737              true,
4738            );
4739          })
4740          .join("");
4741      }
4742
4743      function categoryRowsHtml(options, blockedLines) {
4744        return options
4745          .map(function (category) {
4746            var isBlocked =
4747              blockedLines.indexOf(normalizeText(category.label)) !== -1 ||
4748              blockedLines.indexOf(normalizeText(category.id)) !== -1;
4749
4750            return restrictionRowHtml(
4751              "toveedo-pc-edit-blocked-categories",
4752              category.label,
4753              category.label,
4754              category.icon || "CA",
4755              isBlocked,
4756            );
4757          })
4758          .join("");
4759      }
4760
4761      function selectedCategoryRowsHtml(lines) {
4762        if (!lines.length) {
4763          return '<div class="toveedo-pc-filter-empty">No hidden categories selected.</div>';
4764        }
4765
4766        return lines
4767          .map(function (line) {
4768            var category = findCategoryFilterOption(line);
4769            var label = (category && category.label) || line;
4770
4771            return restrictionRowHtml(
4772              "toveedo-pc-edit-blocked-categories",
4773              label,
4774              label,
4775              (category && category.icon) || "CA",
4776              true,
4777            );
4778          })
4779          .join("");
4780      }
4781
4782      function renderVideoSearchResults(query) {
4783        var results = backdrop.querySelector("[data-tpc-video-results]");
4784        var input = backdrop.querySelector("[data-tpc-video-search]");
4785        if (!results || !input) return;
4786
4787        var blockedLines = getRuleLines("toveedo-pc-edit-blocked-videos").map(
4788          normalizeText,
4789        );
4790        var matches = getVideoFilterOptions()
4791          .filter(function (video) {
4792            return videoSearchMatches(video, query);
4793          })
4794          .slice(0, 20);
4795
4796        results.innerHTML = matches.length
4797          ? videoRowsHtml(matches, blockedLines)
4798          : '<div class="toveedo-pc-filter-empty">No matching videos.</div>';
4799        results.hidden = false;
4800        input.setAttribute("aria-expanded", "true");
4801      }
4802
4803      function refreshSelectedVideoRows() {
4804        var selected = backdrop.querySelector("[data-tpc-selected-videos]");
4805        if (!selected) return;
4806
4807        selected.innerHTML = selectedVideoRowsHtml(
4808          getRuleLines("toveedo-pc-edit-blocked-videos"),
4809        );
4810      }
4811
4812      function refreshVideoFilterUi(query) {
4813        var input = backdrop.querySelector("[data-tpc-video-search]");
4814        var results = backdrop.querySelector("[data-tpc-video-results]");
4815        refreshSelectedVideoRows();
4816
4817        if (
4818          input &&
4819          results &&
4820          (!results.hidden || document.activeElement === input)
4821        ) {
4822          renderVideoSearchResults(query || input.value || "");
4823        }
4824      }
4825
4826      function renderCategorySearchResults(query) {
4827        var results = backdrop.querySelector("[data-tpc-category-results]");
4828        var input = backdrop.querySelector("[data-tpc-category-search]");
4829        if (!results || !input) return;
4830
4831        var blockedLines = getRuleLines("toveedo-pc-edit-blocked-categories").map(
4832          normalizeText,
4833        );
4834        var matches = getCategoryFilterOptions()
4835          .filter(function (category) {
4836            return categorySearchMatches(category, query);
4837          })
4838          .slice(0, 20);
4839
4840        results.innerHTML = matches.length
4841          ? categoryRowsHtml(matches, blockedLines)
4842          : '<div class="toveedo-pc-filter-empty">No matching categories.</div>';
4843        results.hidden = false;
4844        input.setAttribute("aria-expanded", "true");
4845      }
4846
4847      function refreshSelectedCategoryRows() {
4848        var selected = backdrop.querySelector("[data-tpc-selected-categories]");
4849        if (!selected) return;
4850
4851        selected.innerHTML = selectedCategoryRowsHtml(
4852          getRuleLines("toveedo-pc-edit-blocked-categories"),
4853        );
4854      }
4855
4856      function refreshCategoryFilterUi(query) {
4857        var input = backdrop.querySelector("[data-tpc-category-search]");
4858        var results = backdrop.querySelector("[data-tpc-category-results]");
4859        refreshSelectedCategoryRows();
4860
4861        if (
4862          input &&
4863          results &&
4864          (!results.hidden || document.activeElement === input)
4865        ) {
4866          renderCategorySearchResults(query || input.value || "");
4867        }
4868      }
4869
4870      function closeVideoSearchResults() {
4871        var results = backdrop.querySelector("[data-tpc-video-results]");
4872        var input = backdrop.querySelector("[data-tpc-video-search]");
4873        if (results) results.hidden = true;
4874        if (input) input.setAttribute("aria-expanded", "false");
4875      }
4876
4877      function closeCategorySearchResults() {
4878        var results = backdrop.querySelector("[data-tpc-category-results]");
4879        var input = backdrop.querySelector("[data-tpc-category-search]");
4880        if (results) results.hidden = true;
4881        if (input) input.setAttribute("aria-expanded", "false");
4882      }
4883
4884      function buildVideoFiltersHtml(rules) {
4885        var videos = getVideoFilterOptions();
4886        var selectedLines = ruleItemsToLines(rules.blockedVideos)
4887          .split(/\n+/)
4888          .map(function (line) {
4889            return line.trim();
4890          })
4891          .filter(Boolean);
4892
4893        return buildRestrictionCard(
4894          "Video Filters",
4895          String(videos.length),
4896          '<div class="toveedo-pc-video-combobox" data-tpc-video-combobox>' +
4897            '<input class="toveedo-pc-input toveedo-pc-video-search" data-tpc-video-search type="search" autocomplete="off" placeholder="Search video titles" aria-label="Search video titles" aria-expanded="false">' +
4898            '<div class="toveedo-pc-video-results" data-tpc-video-results hidden></div>' +
4899            "</div>" +
4900            '<div class="toveedo-pc-video-selected" data-tpc-selected-videos>' +
4901            selectedVideoRowsHtml(selectedLines) +
4902            "</div>",
4903        );
4904      }
4905
4906      function buildCategoryFiltersHtml(rules) {
4907        var categories = getCategoryFilterOptions();
4908        var selectedLines = ruleItemsToLines(rules.blockedCategories)
4909          .split(/\n+/)
4910          .map(function (line) {
4911            return line.trim();
4912          })
4913          .filter(Boolean);
4914
4915        return buildRestrictionCard(
4916          "Categories",
4917          String(categories.length),
4918          '<div class="toveedo-pc-video-combobox" data-tpc-category-combobox>' +
4919            '<input class="toveedo-pc-input toveedo-pc-video-search" data-tpc-category-search type="search" autocomplete="off" placeholder="Search categories" aria-label="Search categories" aria-expanded="false">' +
4920            '<div class="toveedo-pc-video-results" data-tpc-category-results hidden></div>' +
4921            "</div>" +
4922            '<div class="toveedo-pc-video-selected" data-tpc-selected-categories>' +
4923            selectedCategoryRowsHtml(selectedLines) +
4924            "</div>",
4925        );
4926      }
4927
4928      function buildContentRestrictionsHtml(rules) {
4929        return (
4930          '<div class="toveedo-pc-section toveedo-pc-section--restrictions">' +
4931          "<h3>Content Restrictions</h3>" +
4932          hiddenRuleTextarea(
4933            "toveedo-pc-edit-blocked-videos",
4934            ruleItemsToLines(rules.blockedVideos),
4935          ) +
4936          hiddenRuleTextarea(
4937            "toveedo-pc-edit-blocked-categories",
4938            ruleItemsToLines(rules.blockedCategories),
4939          ) +
4940          buildVideoFiltersHtml(rules) +
4941          buildCategoryFiltersHtml(rules) +
4942          "</div>"
4943        );
4944      }
4945
4946      function setRestrictionRowState(row, isBlocked) {
4947        var status = row && row.querySelector("[data-tpc-rule-status]");
4948        if (!row) return;
4949
4950        row.classList.toggle("is-blocked", isBlocked);
4951        row.setAttribute("aria-pressed", isBlocked ? "true" : "false");
4952        if (status) status.textContent = isBlocked ? "Hidden" : "Shown";
4953      }
4954
4955      function optionMatchesRuleLine(option, line) {
4956        if (!option) return false;
4957
4958        return [
4959          option.id,
4960          option.value,
4961          option.permalink,
4962          option.title,
4963          option.label,
4964        ].some(function (candidate) {
4965          return normalizeText(candidate) === normalizeText(line);
4966        });
4967      }
4968
4969      function ruleLineMatchesValue(line, value, targetId) {
4970        if (normalizeText(line) === normalizeText(value)) return true;
4971
4972        if (targetId === "toveedo-pc-edit-blocked-videos") {
4973          return (
4974            optionMatchesRuleLine(findVideoFilterOption(line), value) ||
4975            optionMatchesRuleLine(findVideoFilterOption(value), line)
4976          );
4977        }
4978
4979        if (targetId === "toveedo-pc-edit-blocked-categories") {
4980          return (
4981            optionMatchesRuleLine(findCategoryFilterOption(line), value) ||
4982            optionMatchesRuleLine(findCategoryFilterOption(value), line)
4983          );
4984        }
4985
4986        return false;
4987      }
4988
4989      function toggleRestrictionRule(row) {
4990        var targetId = row.getAttribute("data-tpc-target");
4991        var value = row.getAttribute("data-tpc-value") || "";
4992        var lines = getRuleLines(targetId);
4993        var existingIndex = -1;
4994
4995        lines.some(function (line, index) {
4996          if (!ruleLineMatchesValue(line, value, targetId)) return false;
4997          existingIndex = index;
4998          return true;
4999        });
5000
5001        if (existingIndex >= 0) {
5002          lines.splice(existingIndex, 1);
5003          setRuleLines(targetId, lines);
5004          setRestrictionRowState(row, false);
5005          return;
5006        }
5007
5008        lines.push(value);
5009        setRuleLines(targetId, lines);
5010        setRestrictionRowState(row, true);
5011      }
5012
5013      function renderSetup(errorMessage) {
5014        var body =
5015          '<p style="text-align:center">Create the parent profile and PIN first. You can add family members in the next step.</p>' +
5016          '<div class="toveedo-pc-setup-grid">' +
5017          inputField("Parent name", '<input class="toveedo-pc-input" id="toveedo-pc-setup-name" value="Parent">') +
5018          pinBoxesHtml("toveedo-pc-setup-pin", "Parent PIN") +
5019          pinBoxesHtml("toveedo-pc-setup-pin-confirm", "Confirm PIN") +
5020          "</div>" +
5021          '<p class="toveedo-pc-error ' +
5022          (errorMessage ? "is-visible" : "") +
5023          '">' +
5024          htmlEscape(errorMessage || "") +
5025          "</p>" +
5026          '<div class="toveedo-pc-actions" style="justify-content:center">' +
5027          '<button class="toveedo-pc-button toveedo-pc-button--primary" data-tpc-setup>Create Parent Profile</button>' +
5028          "</div>";
5029        backdrop.innerHTML = frameHtml("Set up profiles", body, false, "toveedo-pc-modal--setup");
5030        focusPinInput("toveedo-pc-setup-pin");
5031      }
5032
5033      function renderChoose(errorMessage) {
5034        if (!workingAccount.parentPinHash || !workingAccount.profiles.length) {
5035          renderSetup(errorMessage);
5036          return;
5037        }
5038
5039        var canClose = Boolean(getActiveProfile(workingAccount, email));
5040        var canManage = activeProfileCanManage();
5041        var addProfileCard = canManage
5042          ? '<button class="toveedo-pc-profile-card toveedo-pc-profile-card--add" data-tpc-add-profile>' +
5043            '<span class="toveedo-pc-avatar">+</span><strong>Add Profile</strong><span class="toveedo-pc-profile-meta">Parent only</span></button>'
5044          : "";
5045        var body =
5046          '<div class="toveedo-pc-profile-grid">' +
5047          workingAccount.profiles
5048            .map(function (profile) {
5049              return (
5050                '<button class="toveedo-pc-profile-card" data-tpc-profile="' +
5051                htmlEscape(profile.id) +
5052                '">' +
5053                '<span class="toveedo-pc-avatar">' +
5054                htmlEscape(profileAvatar(profile)) +
5055                "</span>" +
5056                "<strong>" +
5057                htmlEscape(profileLabel(profile)) +
5058                '</strong><span class="toveedo-pc-profile-meta">' +
5059                htmlEscape(profile.type === "parent" ? "Parent" : "Kid") +
5060                "</span></button>"
5061              );
5062            })
5063            .join("") +
5064          addProfileCard +
5065          "</div>" +
5066          '<p class="toveedo-pc-error ' +
5067          (errorMessage ? "is-visible" : "") +
5068          '">' +
5069          htmlEscape(errorMessage || "") +
5070          "</p>" +
5071          '<div class="toveedo-pc-actions" style="justify-content:center">' +
5072          '<button class="toveedo-pc-button" type="button" data-tpc-manage-profiles>Manage Profiles</button>' +
5073          (canManage
5074            ? '<button class="toveedo-pc-button" type="button" data-tpc-add-profile>Add Profile</button>'
5075            : "") +
5076          "</div>" +
5077          "";
5078        backdrop.innerHTML = frameHtml("Who's watching?", body, canClose);
5079      }
5080
5081      function renderPin(profile, errorMessage) {
5082        var avatarHtml =
5083          '<div class="toveedo-pc-pin-avatar">' +
5084          htmlEscape(profileAvatar(profile)) +
5085          "</div>";
5086        var body =
5087          '<p style="text-align:center">Please enter your 4-digit profile PIN to continue</p>' +
5088          pinBoxesHtml("toveedo-pc-profile-pin", "") +
5089          '<p class="toveedo-pc-error ' +
5090          (errorMessage ? "is-visible" : "") +
5091          '">' +
5092          htmlEscape(errorMessage || "") +
5093          "</p>" +
5094          '<div class="toveedo-pc-actions toveedo-pc-actions--stack">' +
5095          '<button class="toveedo-pc-button toveedo-pc-button--unlock" data-tpc-unlock="' +
5096          htmlEscape(profile.id) +
5097          '">Unlock Profile</button>' +
5098          '<a class="toveedo-pc-link toveedo-pc-link--row" href="#profiles" data-tpc-back>&larr; Back to profiles</a>' +
5099          "</div>";
5100        backdrop.innerHTML = frameHtml(
5101          "Enter PIN for " + profileLabel(profile),
5102          body,
5103          Boolean(getActiveProfile(workingAccount, email)),
5104          "toveedo-pc-modal--pin",
5105          avatarHtml
5106        );
5107        focusPinInput("toveedo-pc-profile-pin");
5108      }
5109
5110      function renderParentGate(errorMessage, nextAction) {
5111        nextAction = nextAction || "add";
5112        var body =
5113          '<p style="text-align:center">Enter the 4-digit parent PIN to add or manage family profiles.</p>' +
5114          pinBoxesHtml("toveedo-pc-parent-manage-pin", "") +
5115          '<p class="toveedo-pc-error ' +
5116          (errorMessage ? "is-visible" : "") +
5117          '">' +
5118          htmlEscape(errorMessage || "") +
5119          "</p>" +
5120          '<div class="toveedo-pc-actions toveedo-pc-actions--stack">' +
5121          '<button class="toveedo-pc-button toveedo-pc-button--unlock" data-tpc-parent-manage="' +
5122          htmlEscape(nextAction) +
5123          '">Continue</button>' +
5124          '<a class="toveedo-pc-link toveedo-pc-link--row" href="#profiles" data-tpc-back>&larr; Back to profiles</a>' +
5125          "</div>";
5126        backdrop.innerHTML = frameHtml("Parent PIN", body, false, "toveedo-pc-modal--pin");
5127        focusPinInput("toveedo-pc-parent-manage-pin");
5128      }
5129
5130      function renderManageProfiles(message, isError) {
5131        var canClose = Boolean(getActiveProfile(workingAccount, email));
5132        var body =
5133          '<div class="toveedo-pc-manage-list">' +
5134          workingAccount.profiles
5135            .map(function (profile) {
5136              return (
5137                '<div class="toveedo-pc-manage-row">' +
5138                '<span class="toveedo-pc-avatar" style="margin:0">' +
5139                htmlEscape(profileAvatar(profile)) +
5140                "</span>" +
5141                '<div class="toveedo-pc-manage-row__body"><strong>' +
5142                htmlEscape(profileLabel(profile)) +
5143                '</strong><span class="toveedo-pc-profile-meta">' +
5144                htmlEscape(profile.type === "parent" ? "Parent" : "Kid") +
5145                "</span></div>" +
5146                '<button class="toveedo-pc-button" type="button" data-tpc-edit-profile="' +
5147                htmlEscape(profile.id) +
5148                '">Edit Settings</button>' +
5149                "</div>"
5150              );
5151            })
5152            .join("") +
5153          "</div>" +
5154          '<p class="toveedo-pc-error ' +
5155          (message && isError ? "is-visible" : "") +
5156          '">' +
5157          htmlEscape(isError ? message : "") +
5158          "</p>" +
5159          (message && !isError ? '<p style="text-align:center;color:#86efac;font-weight:700">' + htmlEscape(message) + "</p>" : "") +
5160          '<div class="toveedo-pc-actions" style="justify-content:center">' +
5161          (activeProfileCanManage()
5162            ? '<button class="toveedo-pc-button toveedo-pc-button--primary" data-tpc-add-profile>Add Profile</button>'
5163            : "") +
5164          '<a class="toveedo-pc-link toveedo-pc-link--row" href="#profiles" data-tpc-back>&larr; Back to profiles</a>' +
5165          "</div>";
5166        backdrop.innerHTML = frameHtml("Manage Profiles", body, canClose);
5167      }
5168
5169      function renderProfileEditor(profileId, message, isError) {
5170        var profile = findWorkingProfile(profileId);
5171        if (!profile) {
5172          renderManageProfiles("Profile no longer exists.", true);
5173          return;
5174        }
5175
5176        normalizeProfile(profile);
5177
5178        var rules = profile.rules || {};
5179        var limits = rules.timeLimits || {};
5180        var canClose = Boolean(getActiveProfile(workingAccount, email));
5181        var avatarHtml =
5182          '<div class="toveedo-pc-pin-avatar">' +
5183          htmlEscape(profileAvatar(profile)) +
5184          "</div>";
5185        var body =
5186          '<div class="toveedo-pc-actions">' +
5187          '<button class="toveedo-pc-button toveedo-pc-button--ghost" t
5187ype="button" data-tpc-manage-profiles>Back to Manage</button>' +
5188          "</div>" +
5189          '<div class="toveedo-pc-section">' +
5190          "<h3>Profile Information</h3>" +
5191          '<div class="toveedo-pc-section-grid">' +
5192          inputField(
5193            "Name",
5194            '<input class="toveedo-pc-input" id="toveedo-pc-edit-name" value="' +
5195              htmlEscape(profile.name || "") +
5196              '">',
5197          ) +
5198          inputField(
5199            "Birthday",
5200            '<input class="toveedo-pc-input" id="toveedo-pc-edit-birthday" type="date" value="' +
5201              htmlEscape(profile.birthday || "") +
5202              '">',
5203          ) +
5204          inputField(
5205            "Profile type",
5206            '<select class="toveedo-pc-select" id="toveedo-pc-edit-type">' +
5207              '<option value="kid"' +
5208              selectedHtml("kid", profile.type || "kid") +
5209              ">Kid</option>" +
5210              '<option value="parent"' +
5211              selectedHtml("parent", profile.type || "kid") +
5212              ">Parent</option>" +
5213              "</select>",
5214          ) +
5215          inputField(
5216            "Max age rating",
5217            '<select class="toveedo-pc-select" id="toveedo-pc-edit-age-rating">' +
5218              ageRatingOptionsHtml(rules.maxAgeRating || "all") +
5219              "</select>",
5220          ) +
5221          "</div>" +
5222          '<div class="toveedo-pc-actions">' +
5223          '<button class="toveedo-pc-button toveedo-pc-button--danger" type="button" data-tpc-delete-profile="' +
5224          htmlEscape(profile.id) +
5225          '">Delete Profile</button>' +
5226          "</div>" +
5227          "</div>" +
5228          '<div class="toveedo-pc-section">' +
5229          "<h3>Profile PIN</h3>" +
5230          pinBoxesHtml("toveedo-pc-edit-pin", "New PIN") +
5231          "</div>" +
5232          buildContentRestrictionsHtml(rules) +
5233          '<div class="toveedo-pc-section">' +
5234          "<h3>Time Limits</h3>" +
5235          '<label class="toveedo-pc-checkbox"><input id="toveedo-pc-edit-time-enabled" type="checkbox"' +
5236          checkedHtml(limits.enabled) +
5237          ">Enable time limits</label>" +
5238          '<div class="toveedo-pc-section-grid">' +
5239          inputField(
5240            "Allowed start",
5241            '<input class="toveedo-pc-input" id="toveedo-pc-edit-time-start" type="time" value="' +
5242              htmlEscape(limits.allowedStart || "08:00") +
5243              '">',
5244          ) +
5245          inputField(
5246            "Allowed end",
5247            '<input class="toveedo-pc-input" id="toveedo-pc-edit-time-end" type="time" value="' +
5248              htmlEscape(limits.allowedEnd || "20:00") +
5249              '">',
5250          ) +
5251          inputField(
5252            "Daily minutes",
5253            '<input class="toveedo-pc-input" id="toveedo-pc-edit-daily-minutes" type="number" min="0" step="15" value="' +
5254              htmlEscape(limits.dailyMinutes || 120) +
5255              '">',
5256          ) +
5257          "</div></div>" +
5258          '<p class="toveedo-pc-error ' +
5259          (message && isError ? "is-visible" : "") +
5260          '">' +
5261          htmlEscape(isError ? message : "") +
5262          "</p>" +
5263          (message && !isError ? '<p style="text-align:center;color:#86efac;font-weight:700">' + htmlEscape(message) + "</p>" : "") +
5264          '<div class="toveedo-pc-actions" style="justify-content:center">' +
5265          '<button class="toveedo-pc-button toveedo-pc-button--primary" type="button" data-tpc-save-profile="' +
5266          htmlEscape(profile.id) +
5267          '">Save Settings</button>' +
5268          '<button class="toveedo-pc-button" type="button" data-tpc-manage-profiles>Back to Manage</button>' +
5269          "</div>";
5270
5271        backdrop.innerHTML = frameHtml(
5272          profileLabel(profile) + " Settings",
5273          body,
5274          canClose,
5275          "",
5276          avatarHtml,
5277        );
5278        focusPinInput("toveedo-pc-edit-pin");
5279      }
5280
5281      function renderFreshProfileEditor(profileId) {
5282        return loadParentalCatalogManifest(true).then(function () {
5283          renderProfileEditor(profileId, "", false);
5284        });
5285      }
5286
5287      function renderAddProfile(message, isError) {
5288        var body =
5289          '<p style="text-align:center">Create another profile for this account.</p>' +
5290          '<div class="toveedo-pc-form-grid">' +
5291          inputField("Name", '<input class="toveedo-pc-input" id="toveedo-pc-new-name" placeholder="Sarah">') +
5292          inputField(
5293            "Profile type",
5294            '<select class="toveedo-pc-select" id="toveedo-pc-new-type"><option value="kid">Kid</option><option value="parent">Parent</option></select>'
5295          ) +
5296          inputField("Birthday", '<input class="toveedo-pc-input" id="toveedo-pc-new-birthday" type="date">') +
5297          pinBoxesHtml("toveedo-pc-new-pin", "Profile PIN") +
5298          "</div>" +
5299          '<p class="toveedo-pc-error ' +
5300          (message && isError ? "is-visible" : "") +
5301          '">' +
5302          htmlEscape(isError ? message : "") +
5303          "</p>" +
5304          (message && !isError ? '<p style="text-align:center;color:#86efac;font-weight:700">' + htmlEscape(message) + "</p>" : "") +
5305          '<div class="toveedo-pc-actions" style="justify-content:center">' +
5306          '<button class="toveedo-pc-button toveedo-pc-button--primary" data-tpc-save-new-profile>Add Profile</button>' +
5307          '<button class="toveedo-pc-button toveedo-pc-button--ghost" data-tpc-finish>Finish</button>' +
5308          '<a class="toveedo-pc-link toveedo-pc-link--row" href="#profiles" data-tpc-back>&larr; Back to profiles</a>' +
5309          "</div>";
5310        backdrop.innerHTML = frameHtml("Add Profile", body, Boolean(getActiveProfile(workingAccount, email)));
5311      }
5312
5313      renderChoose("");
5314      bindPinEntry(backdrop);
5315
5316      backdrop.addEventListener("input", function (event) {
5317        var videoSearch = event.target.closest("[data-tpc-video-search]");
5318        var categorySearch = event.target.closest("[data-tpc-category-search]");
5319
5320        if (videoSearch) {
5321          renderVideoSearchResults(videoSearch.value);
5322          return;
5323        }
5324
5325        if (categorySearch) {
5326          renderCategorySearchResults(categorySearch.value);
5327        }
5328      });
5329
5330      backdrop.addEventListener("focusin", function (event) {
5331        var videoSearch = event.target.closest("[data-tpc-video-search]");
5332        var categorySearch = event.target.closest("[data-tpc-category-search]");
5333
5334        if (videoSearch) {
5335          renderVideoSearchResults(videoSearch.value);
5336          return;
5337        }
5338
5339        if (categorySearch) {
5340          renderCategorySearchResults(categorySearch.value);
5341        }
5342      });
5343
5344      backdrop.addEventListener("keydown", function (event) {
5345        var videoSearch = event.target.closest("[data-tpc-video-search]");
5346        var categorySearch = event.target.closest("[data-tpc-category-search]");
5347        var firstResult = null;
5348        if (!videoSearch && !categorySearch) return;
5349
5350        if (event.key === "Escape") {
5351          if (videoSearch) closeVideoSearchResults();
5352          if (categorySearch) closeCategorySearchResults();
5353          return;
5354        }
5355
5356        if (event.key !== "Enter") return;
5357
5358        firstResult = backdrop.querySelector(
5359          videoSearch
5360            ? "[data-tpc-video-results] [data-tpc-toggle-rule]"
5361            : "[data-tpc-category-results] [data-tpc-toggle-rule]",
5362        );
5363        if (!firstResult) return;
5364
5365        event.preventDefault();
5366        firstResult.click();
5367      });
5368
5369      backdrop.addEventListener("click", function (event) {
5370        var closeButton = event.target.closest("[data-tpc-close]");
5371        if (closeButton) {
5372          closeModal();
5373          return;
5374        }
5375
5376        var finishButton = event.target.closest("[data-tpc-finish]");
5377        if (finishButton) {
5378          closeModal();
5379          return;
5380        }
5381
5382        var backButton = event.target.closest("[data-tpc-back]");
5383        if (backButton) {
5384          event.preventDefault();
5385          renderChoose("");
5386          return;
5387        }
5388
5389        var restrictionRow = event.target.closest("[data-tpc-toggle-rule]");
5390        if (restrictionRow) {
5391          var isVideoRule =
5392            restrictionRow.getAttribute("data-tpc-target") ===
5393            "toveedo-pc-edit-blocked-videos";
5394          var isCategoryRule =
5395            restrictionRow.getAttribute("data-tpc-target") ===
5396            "toveedo-pc-edit-blocked-categories";
5397          event.preventDefault();
5398          toggleRestrictionRule(restrictionRow);
5399          if (isVideoRule) {
5400            refreshVideoFilterUi();
5401          }
5402          if (isCategoryRule) {
5403            refreshCategoryFilterUi();
5404          }
5405          return;
5406        }
5407
5408        if (!event.target.closest("[data-tpc-video-combobox]")) {
5409          closeVideoSearchResults();
5410        }
5411
5412        if (!event.target.closest("[data-tpc-category-combobox]")) {
5413          closeCategorySearchResults();
5414        }
5415
5416        var profileButton = event.target.closest("[data-tpc-profile]");
5417        if (profileButton) {
5418          var profileId = profileButton.getAttribute("data-tpc-profile");
5419          var profile = workingAccount.profiles.find(function (item) {
5420            return item.id === profileId;
5421          });
5422          if (!profile) return;
5423          if (profile.pinHash) {
5424            renderPin(profile, "");
5425            return;
5426          }
5427          setActiveSelection(email, profile.id);
5428          closeModal();
5429          return;
5430        }
5431
5432        if (event.target.closest("[data-tpc-add-profile]")) {
5433          var activeProfile = getActiveProfile(workingAccount, email);
5434          if (activeProfile && activeProfile.type === "parent") {
5435            renderAddProfile("");
5436          } else {
5437            renderChoose("Switch to a parent profile to add profiles.");
5438          }
5439          return;
5440        }
5441
5442        if (event.target.closest("[data-tpc-manage-profiles]")) {
5443          if (activeProfileCanManage()) {
5444            renderManageProfiles("");
5445          } else {
5446            renderParentGate("", "manage");
5447          }
5448          return;
5449        }
5450
5451        var editProfileButton = event.target.closest("[data-tpc-edit-profile]");
5452        if (editProfileButton) {
5453          if (activeProfileCanManage()) {
5454            renderFreshProfileEditor(
5455              editProfileButton.getAttribute("data-tpc-edit-profile"),
5456            );
5457          } else {
5458            renderParentGate(
5459              "",
5460              "edit:" + editProfileButton.getAttribute("data-tpc-edit-profile"),
5461            );
5462          }
5463          return;
5464        }
5465
5466        var unlockButton = event.target.closest("[data-tpc-unlock]");
5467        if (unlockButton) {
5468          var unlockProfile = workingAccount.profiles.find(function (item) {
5469            return item.id === unlockButton.getAttribute("data-tpc-unlock");
5470          });
5471          verifyPin(getPinValue("toveedo-pc-profile-pin"), unlockProfile && unlockProfile.pinHash).then(function (ok) {
5472            if (!ok) {
5473              renderPin(unlockProfile, "Incorrect PIN.");
5474              return;
5475            }
5476            setActiveSelection(email, unlockProfile.id);
5477            closeModal();
5478          });
5479          return;
5480        }
5481
5482        var parentManageButton = event.target.closest("[data-tpc-parent-manage]");
5483        if (parentManageButton) {
5484          var parentManageAction =
5485            parentManageButton.getAttribute("data-tpc-parent-manage") || "add";
5486          verifyPin(getPinValue("toveedo-pc-parent-manage-pin"), workingAccount.parentPinHash).then(function (ok) {
5487            if (!ok) {
5488              renderParentGate("Incorrect parent PIN.", parentManageAction);
5489              return;
5490            }
5491
5492            if (parentManageAction === "manage") {
5493              renderManageProfiles("");
5494              return;
5495            }
5496
5497            if (parentManageAction.indexOf("edit:") === 0) {
5498              renderFreshProfileEditor(parentManageAction.slice(5));
5499              return;
5500            }
5501
5502            renderAddProfile("");
5503          });
5504          return;
5505        }
5506
5507        if (event.target.closest("[data-tpc-setup]")) {
5508          var parentName = (document.getElementById("toveedo-pc-setup-name") || {}).value || "Parent";
5509          var pin = getPinValue("toveedo-pc-setup-pin");
5510          var confirmPin = getPinValue("toveedo-pc-setup-pin-confirm");
5511          if (pin.length !== 4) {
5512            renderSetup("Use a 4-digit parent PIN.");
5513            return;
5514          }
5515          if (pin !== confirmPin) {
5516            renderSetup("The PIN confirmation does not match.");
5517            return;
5518          }
5519          hashPin(pin).then(function (pinHash) {
5520            workingAccount = emptyAccount();
5521            workingAccount.parentPinHash = pinHash;
5522            var parent = emptyProfile("parent");
5523            parent.name = parentName;
5524            parent.pinHash = pinHash;
5525            workingAccount.profiles.push(parent);
5526            setActiveSelection(email, parent.id);
5527            return saveAccount(email, workingAccount)
5528              .then(function () {
5529                renderAddProfile("Parent profile created. Add family members now.", false);
5530              })
5531              .catch(function (error) {
5532                renderSetup(error.message || "Could not save parent profile.");
5533              });
5534          });
5535          return;
5536        }
5537
5538        var deleteProfileButton = event.target.closest("[data-tpc-delete-profile]");
5539        if (deleteProfileButton) {
5540          var deleteProfileId = deleteProfileButton.getAttribute("data-tpc-delete-profile");
5541          var deleteProfile = findWorkingProfile(deleteProfileId);
5542          if (!deleteProfile) {
5543            renderManageProfiles("Profile no longer exists.", true);
5544            return;
5545          }
5546
5547          if (workingAccount.profiles.length < 2) {
5548            renderProfileEditor(deleteProfileId, "Keep at least one profile on this account.", true);
5549            return;
5550          }
5551
5552          if (
5553            deleteProfile.type === "parent" &&
5554            workingAccount.profiles.filter(function (profile) {
5555              return profile.type === "parent" && profile.id !== deleteProfileId;
5556            }).length < 1
5557          ) {
5558            renderProfileEditor(deleteProfileId, "Keep at least one parent profile.", true);
5559            return;
5560          }
5561
5562          if (
5563            !window.confirm(
5564              "Delete " + profileLabel(deleteProfile) + "? This cannot be undone.",
5565            )
5566          ) {
5567            return;
5568          }
5569
5570          workingAccount.profiles = workingAccount.profiles.filter(function (profile) {
5571            return profile.id !== deleteProfileId;
5572          });
5573
5574          if (getActiveSelection(email) === deleteProfileId) {
5575            setActiveSelection(email, workingAccount.profiles[0].id);
5576          }
5577
5578          saveAccount(email, workingAccount)
5579            .then(function () {
5580              ensureSwitcher(workingAccount, email);
5581              renderManageProfiles("Profile deleted.", false);
5582            })
5583            .catch(function (error) {
5584              workingAccount.profiles.push(deleteProfile);
5585              renderProfileEditor(deleteProfileId, error.message || "Could not delete profile.", true);
5586            });
5587          return;
5588        }
5589
5590        var saveProfileButton = event.target.closest("[data-tpc-save-profile]");
5591        if (saveProfileButton) {
5592          var editProfileId = saveProfileButton.getAttribute("data-tpc-save-profile");
5593          var editProfile = findWorkingProfile(editProfileId);
5594          if (!editProfile) {
5595            renderManageProfiles("Profile no longer exists.", true);
5596            return;
5597          }
5598
5599          var editNameInput = document.getElementById("toveedo-pc-edit-name");
5600          var editBirthdayInput = document.getElementById("toveedo-pc-edit-birthday");
5601          var editTypeInput = document.getElementById("toveedo-pc-edit-type");
5602          var editAgeRatingInput = document.getElementById("toveedo-pc-edit-age-rating");
5603          var editTimeEnabledInput = document.getElementById("toveedo-pc-edit-time-enabled");
5604          var editTimeStartInput = document.getElementById("toveedo-pc-edit-time-start");
5605          var editTimeEndInput = document.getElementById("toveedo-pc-edit-time-end");
5606          var editDailyMinutesInput = document.getElementById("toveedo-pc-edit-daily-minutes");
5607          var editName = (editNameInput ? editNameInput.value : "").trim();
5608          var editType = editTypeInput ? editTypeInput.value : editProfile.type || "kid";
5609          var editPin = getPinValue("toveedo-pc-edit-pin");
5610
5611          if (!editName) {
5612            renderProfileEditor(editProfileId, "Enter a profile name.", true);
5613            return;
5614          }
5615
5616          if (editPin && editPin.length !== 4) {
5617            renderProfileEditor(editProfileId, "Use a 4-digit profile PIN.", true);
5618            return;
5619          }
5620
5621          if (editType === "kid" && !editProfile.pinHash && !editPin) {
5622            renderProfileEditor(editProfileId, "Set a 4-digit PIN for kid profiles.", true);
5623            return;
5624          }
5625
5626          if (
5627            editProfile.type === "parent" &&
5628            editType !== "parent" &&
5629            workingAccount.profiles.filter(function (profile) {
5630              return profile.type === "parent" && profile.id !== editProfileId;
5631            }).length < 1
5632          ) {
5633            renderProfileEditor(editProfileId, "Keep at least one parent profile.", true);
5634            return;
5635          }
5636
5637          function persistEditedProfile(pinHash) {
5638            editProfile.name = editName;
5639            editProfile.type = editType;
5640            editProfile.birthday = editBirthdayInput ? editBirthdayInput.value : "";
5641            editProfile.rules = editProfile.rules || {};
5642            editProfile.rules.maxAgeRating = editAgeRatingInput
5643              ? editAgeRatingInput.value
5644              : "all";
5645            editProfile.rules.blockedVideos = textLinesToRuleItems(
5646              (document.getElementById("toveedo-pc-edit-blocked-videos") || {}).value,
5647              "title",
5648            );
5649            editProfile.rules.blockedChannels = [];
5650            editProfile.rules.blockedCategories = textLinesToRuleItems(
5651              (document.getElementById("toveedo-pc-edit-blocked-categories") || {}).value,
5652              "label",
5653            );
5654            editProfile.rules.timeLimits = {
5655              enabled: Boolean(editTimeEnabledInput && editTimeEnabledInput.checked),
5656              allowedStart: editTimeStartInput ? editTimeStartInput.value || "08:00" : "08:00",
5657              allowedEnd: editTimeEndInput ? editTimeEndInput.value || "20:00" : "20:00",
5658              dailyMinutes: Math.max(
5659                0,
5660                Number(editDailyMinutesInput ? editDailyMinutesInput.value : 120) || 0,
5661              ),
5662            };
5663
5664            if (pinHash) {
5665              editProfile.pinHash = pinHash;
5666              if (editProfile.type === "parent") {
5667                workingAccount.parentPinHash = pinHash;
5668              }
5669            }
5670
5671            normalizeProfile(editProfile);
5672
5673            return saveAccount(email, workingAccount)
5674              .then(function () {
5675                ensureSwitcher(workingAccount, email);
5676                renderProfileEditor(editProfileId, "Settings saved.", false);
5677              })
5678              .catch(function (error) {
5679                renderProfileEditor(editProfileId, error.message || "Could not save profile.", true);
5680              });
5681          }
5682
5683          if (editPin) {
5684            hashPin(editPin).then(persistEditedProfile);
5685          } else {
5686            persistEditedProfile("");
5687          }
5688          return;
5689        }
5690
5691        if (event.target.closest("[data-tpc-save-new-profile]")) {
5692          var nameInput = document.getElementById("toveedo-pc-new-name");
5693          var typeInput = document.getElementById("toveedo-pc-new-type");
5694          var birthdayInput = document.getElementById("toveedo-pc-new-birthday");
5695          var newPin = getPinValue("toveedo-pc-new-pin");
5696          var name = (nameInput ? nameInput.value : "").trim();
5697          if (!name) {
5698            renderAddProfile("Enter a profile name.", true);
5699            return;
5700          }
5701          if (newPin.length !== 4) {
5702            renderAddProfile("Use a 4-digit profile PIN.", true);
5703            return;
5704          }
5705          var nextProfile = emptyProfile(typeInput ? typeInput.value : "kid");
5706          nextProfile.name = name;
5707          nextProfile.birthday = birthdayInput ? birthdayInput.value : "";
5708          hashPin(newPin).then(function (pinHash) {
5709            if (pinHash) nextProfile.pinHash = pinHash;
5710            workingAccount.profiles.push(nextProfile);
5711            saveAccount(email, workingAccount)
5712              .then(function () {
5713                renderChoose("");
5714              })
5715              .catch(function (error) {
5716                workingAccount.profiles = workingAccount.profiles.filter(function (profile) {
5717                  return profile.id !== nextProfile.id;
5718                });
5719                renderAddProfile(error.message || "Could not save profile.", true);
5720              });
5721          });
5722        }
5723      });
5724    }
5725
5726    function ensureSwitcher(account, email) {
5727      if (!account) return;
5728      var header = document.querySelector("#website-header");
5729      var host =
5730        header &&
5731        (header.querySelector(".relative.flex") ||
5732          header.querySelector('[data-themeless--header-target="menuNavigation"]') ||
5733          header);
5734      if (!host) return;
5735
5736      state.switcherAccount = account;
5737      state.switcherEmail = email;
5738
5739      var active = getActiveProfile(account, email);
5740      var switcher = document.getElementById(SWITCHER_ID);
5741      if (!switcher) {
5742        switcher = document.createElement("button");
5743        switcher.id = SWITCHER_ID;
5744        switcher.type = "button";
5745        switcher.className = "toveedo-pc-switcher";
5746      }
5747      if (switcher.parentElement !== host) host.appendChild(switcher);
5748      switcher.onclick = function () {
5749        openProfileChooser(account, email, { force: true });
5750      };
5751      var label = active ? profileLabel(active) : account.parentPinHash ? "Choose profile" : "Set up profiles";
5752      switcher.innerHTML =
5753        '<span class="toveedo-pc-trigger-avatar">' +
5754        htmlEscape(active ? profileAvatar(active) : "P") +
5755        '</span><span class="toveedo-pc-trigger-name">' +
5756        htmlEscape(label) +
5757        "</span>";
5758    }
5759
5760    function handleSwitcherClick(event) {
5761      var switcher = document.getElementById(SWITCHER_ID);
5762      if (!switcher) return;
5763
5764      var rect = switcher.getBoundingClientRect();
5765      var padding = 8;
5766      var insideSwitcher =
5767        event.clientX >= rect.left - padding &&
5768        event.clientX <= rect.right + padding &&
5769        event.clientY >= rect.top - padding &&
5770        event.clientY <= rect.bottom + padding;
5771
5772      if (!insideSwitcher) return;
5773
5774      var account = state.switcherAccount;
5775      var email = state.switcherEmail || TEST_USER_EMAIL;
5776      if (!account) return;
5777
5778      event.preventDefault();
5779      event.stopImmediatePropagation();
5780      openProfileChooser(account, email, { force: true });
5781    }
5782
5783    function cssAttributeValue(value) {
5784      return String(value == null ? "" : value)
5785        .replace(/\\/g, "\\\\")
5786        .replace(/"/g, '\\"');
5787    }
5788
5789    function syncBlockedCategoryStyles(profile) {
5790      var style = document.getElementById(BLOCKED_CATEGORY_STYLE_ID);
5791      var ids = [];
5792
5793      if (profile && profile.type !== "parent") {
5794        ids = blockedIndex(profile).categoryIds.filter(Boolean);
5795      }
5796
5797      if (!ids.length) {
5798        if (style) style.remove();
5799        return;
5800      }
5801
5802      if (!style) {
5803        style = document.createElement("style");
5804        style.id = BLOCKED_CATEGORY_STYLE_ID;
5805        document.head.appendChild(style);
5806      }
5807
5808      style.textContent = ids
5809        .map(function (id) {
5810          var escapedId = cssAttributeValue(id);
5811
5812          return (
5813            '.category-group[data-category-id="' +
5814            escapedId +
5815            '"],' +
5816            '#category_id ds-select-option[name="' +
5817            escapedId +
5818            '"],' +
5819            'ds-select[name="category_id"] ds-select-option[name="' +
5820            escapedId +
5821            '"],' +
5822            '#category_id ds-select-option[value="' +
5823            escapedId +
5824            '"],' +
5825            'ds-select[name="category_id"] ds-select-option[value="' +
5826            escapedId +
5827            '"],' +
5828            '#category_id ds-select-option[data-value="' +
5829            escapedId +
5830            '"],' +
5831            'ds-select[name="category_id"] ds-select-option[data-value="' +
5832            escapedId +
5833            '"]{display:none!important;}'
5834          );
5835        })
5836        .join("\n");
5837    }
5838
5839    function ensureFamilyTab() {
5840      if (window.location.pathname.indexOf("/account") !== 0) return;
5841      if (document.querySelector('[data-toveedo-parental-tab="1"]')) return;
5842
5843      var tabHost =
5844        document.querySelector("#profile_form") ||
5845        document.querySelector('[href="/pages/block-videos-page"]') ||
5846        document.querySelector("main") ||
5847        document.body;
5848      var anchorParent = tabHost.closest("div") || tabHost.parentElement || document.body;
5849      var link = document.createElement("button");
5850      link.type = "button";
5851      link.textContent = "Family Profiles";
5852      link.setAttribute("data-toveedo-parental-tab", "1");
5853      link.style.cssText =
5854        "display:inline-flex;align-items:center;min-height:38px;padding:0 14px;margin:8px 8px 8px 0;border-radius:8px;background:#38bdf8;color:#06121f;font-weight:800;text-decoration:none;";
5855      link.addEventListener("click", openCurrentFamilyModal);
5856      anchorParent.insertBefore(link, anchorParent.firstChild);
5857    }
5858
5859    function blockedIndex(profile) {
5860      var rules = (profile && profile.rules) || {};
5861      var items = [].concat(rules.blockedVideos || []);
5862      var categories = rules.blockedCategories || [];
5863      var categoryEntries = categories.map(function (item) {
5864        var lookupValue =
5865          typeof item === "string"
5866            ? item
5867            : item &&
5868              (item.id || item.value || item.permalink || item.label || item.title);
5869
5870        return {
5871          item: item,
5872          manifestCategory: findManifestCategory(lookupValue),
5873        };
5874      });
5875
5876      return {
5877        titles: items.map(function (item) {
5878          return normalizeText(
5879            typeof item === "string" ? item : item && (item.title || item.label),
5880          );
5881        }),
5882        ids: items.map(function (item) {
5883          if (typeof item === "string") return "";
5884          return String(item.id || item.value || "");
5885        }),
5886        permalinks: items.map(function (item) {
5887          if (typeof item === "string") return "";
5888          return normalizeText(item.permalink || "");
5889        }),
5890        categoryIds: categoryEntries.map(function (entry) {
5891          var item = entry.item;
5892          if (typeof item !== "string" && item && (item.id || item.value)) {
5893            return String(item.id || item.value || "");
5894          }
5895
5896          return entry.manifestCategory ? String(entry.manifestCategory.id || "") : "";
5897        }),
5898        categoryLabels: categoryEntries
5899          .map(function (entry) {
5900            var item = entry.item;
5901            var manifestCategory = entry.manifestCategory;
5902            return [
5903              typeof item === "string"
5904                ? item
5905                : item && (item.label || item.title),
5906              item && item.permalink,
5907              manifestCategory && manifestCategory.label,
5908              manifestCategory && manifestCategory.title,
5909              manifestCategory && manifestCategory.permalink,
5910            ];
5911          })
5912          .reduce(function (result, values) {
5913            values.forEach(function (value) {
5914              var normalized = normalizeText(value);
5915              if (normalized) result.push(normalized);
5916            });
5917            return result;
5918          }, [])
5919      };
5920    }
5921
5922    function textHasBlockedTitle(text, index) {
5923      var normalized = normalizeText(text);
5924      return index.titles.some(function (title) {
5925        return title && (normalized === title || normalized.indexOf(title) !== -1);
5926      });
5927    }
5928
5929    function signatureIsBlocked(signature, index) {
5930      var normalized = normalizeText(signature);
5931      return (
5932        index.ids.some(function (id) {
5933          return id && signature.indexOf(id) !== -1;
5934        }) ||
5935        index.permalinks.some(function (permalink) {
5936          return permalink && normalized.indexOf(permalink) !== -1;
5937        }) ||
5938        index.categoryIds.some(function (id) {
5939          return id && signature.indexOf("category_id=" + id) !== -1;
5940        }) ||
5941        index.categoryLabels.some(function (label) {
5942          return label && normalized.indexOf(label) !== -1;
5943        })
5944      );
5945    }
5946
5947    function hideBlockedCards(profile) {
5948      if (!profile || profile.type === "parent") return;
5949
5950      var index = blockedIndex(profile);
5951      var selector = [
5952        '[data-custom="content-card"]',
5953        "swiper-slide[data-card]",
5954        ".card-title",
5955        'a[href*="/programs/"]',
5956        'a[href*="category_id="]'
5957      ].join(",");
5958
5959      document.querySelectorAll(selector).forEach(function (node) {
5960        var card =
5961          node.closest("swiper-slide, [data-custom='content-card'], article, li, .card, .content-card") ||
5962          node;
5963        if (!card || card.getAttribute("data-toveedo-parental-hidden") === "1") return;
5964
5965        var href = node.href || (node.querySelector && node.querySelector("a[href]") ? node.querySelector("a[href]").href : "");
5966        var dataCard = card.getAttribute("data-card") || node.getAttribute("data-card") || "";
5967        var text = node.textContent || card.textContent || "";
5968        var signature = [href, dataCard, text].join(" ");
5969
5970        if (signatureIsBlocked(signature, index) || textHasBlockedTitle(text, index)) {
5971          card.setAttribute("data-toveedo-parental-hidden", "1");
5972        }
5973      });
5974
5975      index.categoryIds.forEach(function (id) {
5976        if (!id) return;
5977        document.querySelectorAll('[data-category-id="' + id.replace(/"/g, "") + '"]').forEach(function (node) {
5978          node.setAttribute("data-toveedo-parental-hidden", "1");
5979        });
5980      });
5981    }
5982
5983    function isProgramRoute() {
5984      return /^\/programs\/[^/]+/.test(window.location.pathname);
5985    }
5986
5987    function currentProgramSlug() {
5988      var match = window.location.pathname.match(/^\/programs\/([^/]+)/);
5989      return match ? decodeURIComponent(match[1]) : "";
5990    }
5991
5992    function fetchCurrentProgram(slug) {
5993      if (!slug) return Promise.resolve(null);
5994      if (!state.contentPromises[slug]) {
5995        state.contentPromises[slug] = fetchJson("/api/contents/" + encodeURIComponent(slug)).catch(function () {
5996          return null;
5997        });
5998      }
5999      return state.contentPromises[slug];
6000    }
6001
6002    function ageFromRating(value) {
6003      var rating = AGE_FILTERS.find(function (item) {
6004        return item.value === value;
6005      });
6006      return rating ? rating.minAge : 0;
6007    }
6008
6009    function extractMinAge(value) {
6010      var text = normalizeText(value);
6011      var match = text.match(/(?:ages?\s*)?(\d{1,2})\s*\+/);
6012      return match ? Number(match[1]) : 0;
6013    }
6014
6015    function profileAge(profile) {
6016      if (!profile || !profile.birthday) return null;
6017      var birthday = new Date(profile.birthday + "T00:00:00");
6018      if (Number.isNaN(birthday.getTime())) return null;
6019      var now = new Date();
6020      var age = now.getFullYear() - birthday.getFullYear();
6021      var beforeBirthday =
6022        now.getMonth() < birthday.getMonth() ||
6023        (now.getMonth() === birthday.getMonth() && now.getDate() < birthday.getDate());
6024      return beforeBirthday ? age - 1 : age;
6025    }
6026
6027    function detailIsBlocked(detail, profile) {
6028      var index = blockedIndex(profile);
6029      var slug = currentProgramSlug();
6030      var manifestVideo = findManifestVideo(slug);
6031      var titleNode = document.querySelector('h1[data-area="program-title"], h1');
6032      var title =
6033        (detail && (detail.title || detail.name)) ||
6034        (manifestVideo && manifestVideo.title) ||
6035        (titleNode ? titleNode.textContent : "");
6036      var id =
6037        (detail && (detail.id || detail.content_id || detail.contentId)) ||
6038        (manifestVideo && manifestVideo.id);
6039      var categories =
6040        (detail && (detail.categories || detail.category || detail.tags)) ||
6041        (manifestVideo &&
6042          (manifestVideo.categoryLabels || []).concat(
6043            manifestVideo.categoryIds || [],
6044          )) ||
6045        [];
6046      var categoryText = Array.isArray(categories)
6047        ? categories
6048            .map(function (item) {
6049              return typeof item === "string" ? item : item && (item.title || item.name || item.label || item.id);
6050            })
6051            .join(" ")
6052        : String(categories || "");
6053      var age = Math.max(
6054        extractMinAge(title),
6055        extractMinAge(categoryText),
6056        extractMinAge(detail && JSON.stringify(detail))
6057      );
6058      var signature = [
6059        slug,
6060        title,
6061        id || "",
6062        manifestVideo && manifestVideo.permalink,
6063        categoryText,
6064      ].join(" ");
6065
6066      if (signatureIsBlocked(signature, index) || textHasBlockedTitle(title, index)) return true;
6067
6068      if (age) {
6069        var childAge = profileAge(profile);
6070        var ratingCap = ageFromRating(profile.rules && profile.rules.maxAgeRating);
6071        if (childAge != null && childAge < age) return true;
6072        if (ratingCap && age > ratingCap) return true;
6073      }
6074
6075      return false;
6076    }
6077
6078    function pauseMedia() {
6079      document.querySelectorAll("video, audio").forEach(function (media) {
6080        if (media && typeof media.pause === "function") media.pause();
6081      });
6082    }
6083
6084    function showBlockedModal(message) {
6085      if (isParentalModalSuppressedRoute()) {
6086        dismissParentalModal();
6087        return;
6088      }
6089
6090      pauseMedia();
6091      var existing = document.getElementById(MODAL_ID);
6092      if (existing) existing.remove();
6093      var backdrop = document.createElement("div");
6094      backdrop.id = MODAL_ID;
6095      backdrop.className = "toveedo-pc-backdrop";
6096      backdrop.innerHTML =
6097        '<div class="toveedo-pc-modal toveedo-pc-modal--blocked" role="dialog" aria-modal="true" aria-label="Content blocked">' +
6098        '<div class="toveedo-pc-modal__body toveedo-pc-stack">' +
6099        "<div><h2>Content unavailable</h2><p>" +
6100        htmlEscape(message || "This profile is not allowed to view this content.") +
6101        "</p></div>" +
6102        '<div class="toveedo-pc-actions">' +
6103        '<button class="toveedo-pc-button toveedo-pc-button--primary" data-tpc-switch>Switch profile</button>' +
6104        '<a class="toveedo-pc-button" href="/catalog">Back to catalog</a>' +
6105        "</div></div></div>";
6106      document.body.appendChild(backdrop);
6107      backdrop.addEventListener("click", function (event) {
6108        if (!event.target.closest("[data-tpc-switch]")) return;
6109        if (state.switcherAccount) {
6110          openProfileChooser(
6111            state.switcherAccount,
6112            state.switcherEmail || TEST_USER_EMAIL,
6113            { force: true }
6114          );
6115        }
6116      });
6117    }
6118
6119    function guardProgramRoute(profile) {
6120      if (!profile || profile.type === "parent" || !isProgramRoute()) return;
6121      if (detailIsBlocked(null, profile)) {
6122        showBlockedModal("This item is blocked for the selected profile.");
6123        return;
6124      }
6125      var slug = currentProgramSlug();
6126      fetchCurrentProgram(slug).then(function (detail) {
6127        if (detail && detailIsBlocked(detail, profile)) {
6128          showBlockedModal("This item is blocked for the selected profile.");
6129        }
6130      });
6131    }
6132
6133    function minutesFromTime(value) {
6134      var parts = String(value || "00:00").split(":");
6135      return Number(parts[0] || 0) * 60 + Number(parts[1] || 0);
6136    }
6137
6138    function nowMinutes() {
6139      var now = new Date();
6140      return now.getHours() * 60 + now.getMinutes();
6141    }
6142
6143    function isInsideTimeWindow(start, end) {
6144      var current = nowMinutes();
6145      var startMinutes = minutesFromTime(start || "00:00");
6146      var endMinutes = minutesFromTime(end || "23:59");
6147      if (startMinutes <= endMinutes) return current >= startMinutes && current <= endMinutes;
6148      return current >= startMinutes || current <= endMinutes;
6149    }
6150
6151    function usageKey(profile) {
6152      var today = new Date();
6153      var month = String(today.getMonth() + 1).padStart(2, "0");
6154      var day = String(today.getDate()).padStart(2, "0");
6155      return profile.id + ":" + today.getFullYear() + "-" + month + "-" + day;
6156    }
6157
6158    function getUsageSeconds(profile) {
6159      return Number(state.usageSeconds[usageKey(profile)] || 0);
6160    }
6161
6162    function addUsageSeconds(profile, seconds) {
6163      state.usageSeconds[usageKey(profile)] = getUsageSeconds(profile) + seconds;
6164    }
6165
6166    function findPlayingMedia() {
6167      var media = Array.from(document.querySelectorAll("video, audio")).find(function (item) {
6168        return item && !item.paused && !item.ended;
6169      });
6170      if (media) return media;
6171      var players = document.querySelectorAll("video-player, uscreen-video-player");
6172      for (var index = 0; index < players.length; index += 1) {
6173        var root = players[index].shadowRoot;
6174        var nested = root && root.querySelector("video");
6175        if (nested && !nested.paused && !nested.ended) return nested;
6176      }
6177      return null;
6178    }
6179
6180    function timeLimitMessage(profile) {
6181      var limits = profile.rules && profile.rules.timeLimits;
6182      if (!limits || !limits.enabled) return "";
6183      if (!isInsideTimeWindow(limits.allowedStart, limits.allowedEnd)) {
6184        return "This profile is outside its allowed viewing hours.";
6185      }
6186      var allowance = Number(limits.dailyMinutes || 0) * 60;
6187      if (allowance > 0 && getUsageSeconds(profile) >= allowance) {
6188        return "This profile reached its daily viewing limit.";
6189      }
6190      return "";
6191    }
6192
6193    function trackTimeLimits(profile) {
6194      if (state.timeProfileId === (profile && profile.id)) return;
6195      if (state.timeTicker) clearInterval(state.timeTicker);
6196      state.timeTicker = null;
6197      state.timeProfileId = profile && profile.id ? profile.id : "";
6198      state.lastUsageTickAt = Date.now();
6199
6200      if (!profile || profile.type === "parent" || !(profile.rules && profile.rules.timeLimits && profile.rules.timeLimits.enabled)) return;
6201
6202      state.timeTicker = setInterval(function () {
6203        var message = timeLimitMessage(profile);
6204        if (message) {
6205          showBlockedModal(message);
6206          return;
6207        }
6208
6209        if (!findPlayingMedia()) {
6210          state.lastUsageTickAt = Date.now();
6211          return;
6212        }
6213
6214        var now = Date.now();
6215        var elapsed = Math.min(30, Math.max(0, Math.round((now - state.lastUsageTickAt) / 1000)));
6216        state.lastUsageTickAt = now;
6217        if (elapsed) addUsageSeconds(profile, elapsed);
6218
6219        message = timeLimitMessage(profile);
6220        if (message) showBlockedModal(message);
6221      }, 15000);
6222
6223      var initialMessage = timeLimitMessage(profile);
6224      if (initialMessage && isProgramRoute()) showBlockedModal(initialMessage);
6225    }
6226
6227    function isPurchaseRoute() {
6228      return /\/checkout|\/orders|\/complete_order|\/payments|\/gift/.test(window.location.pathname);
6229    }
6230
6231    function purchaseUnlockKey() {
6232      return window.location.pathname + window.location.search;
6233    }
6234
6235    function showPurchaseGate(account) {
6236      if (isParentalModalSuppressedRoute()) {
6237        dismissParentalModal();
6238        return;
6239      }
6240      if (!account.parentPinHash) return;
6241
6242      var existing = document.getElementById(MODAL_ID);
6243      if (existing) return;
6244
6245      var backdrop = document.createElement("div");
6246      backdrop.id = MODAL_ID;
6247      backdrop.className = "toveedo-pc-backdrop";
6248      backdrop.innerHTML =
6249        '<div class="toveedo-pc-modal toveedo-pc-modal--pin" role="dialog" aria-modal="true" aria-label="Parent PIN required">' +
6250        '<div class="toveedo-pc-modal__body toveedo-pc-stack">' +
6251        "<div><h2>Parent PIN required</h2><p>Enter the parent PIN to continue checkout or make a purchase.</p></div>" +
6252        pinBoxesHtml("toveedo-pc-parent-pin", "") +
6253        '<p class="toveedo-pc-error" id="toveedo-pc-parent-pin-error">Incorrect parent PIN.</p>' +
6254        '<div class="toveedo-pc-actions" style="justify-content:center">' +
6255        '<button class="toveedo-pc-button toveedo-pc-button--unlock" data-tpc-parent-unlock>Continue</button>' +
6256        '<a class="toveedo-pc-button" href="/catalog">Back to catalog</a>' +
6257        "</div></div></div>";
6258      document.body.appendChild(backdrop);
6259      bindPinEntry(backdrop);
6260      focusPinInput("toveedo-pc-parent-pin");
6261      backdrop.addEventListener("click", function (event) {
6262        if (!event.target.closest("[data-tpc-parent-unlock]")) return;
6263        verifyPin(getPinValue("toveedo-pc-parent-pin"), account.parentPinHash).then(function (ok) {
6264          if (!ok) {
6265            var error = document.getElementById("toveedo-pc-parent-pin-error");
6266            if (error) error.classList.add("is-visible");
6267            return;
6268          }
6269          state.purchaseUnlocks[purchaseUnlockKey()] = true;
6270          backdrop.remove();
6271        });
6272      });
6273    }
6274
6275    function enforcePurchaseProtection(account, profile) {
6276      if (isParentalModalSuppressedRoute()) return;
6277      if (!profile || profile.type !== "kid") return;
6278      if (!isPurchaseRoute()) return;
6279      if (state.purchaseUnlocks[purchaseUnlockKey()] === true) return;
6280      showPurchaseGate(account);
6281    }
6282
6283    function isPurchaseTrigger(target) {
6284      if (!target || !target.closest) return false;
6285      return Boolean(
6286        target.closest('a[href*="/checkout"], a[href*="/orders"], a[href*="/gift"]') ||
6287          (isPurchaseRoute() &&
6288            target.closest('button[type="submit"], input[type="submit"], #submit-button, ds-button#submit-button'))
6289      );
6290    }
6291
6292    function handlePurchaseClick(event) {
6293      if (isParentalModalSuppressedRoute()) return;
6294      if (!isPurchaseTrigger(event.target)) return;
6295
6296      var account = state.switcherAccount;
6297      var email = state.switcherEmail || TEST_USER_EMAIL;
6298      var active = account && getActiveProfile(account, email);
6299      if (!active || active.type !== "kid" || state.purchaseUnlocks[purchaseUnlockKey()] === true) return;
6300      event.preventDefault();
6301      event.stopImmediatePropagation();
6302      showPurchaseGate(account);
6303    }
6304
6305    function applyRules(account, email) {
6306      if (!account) return;
6307      ensureFamilyTab();
6308      ensureSwitcher(account, email);
6309      if (isParentalModalSuppressedRoute()) {
6310        dismissParentalModal();
6311        syncBlockedCategoryStyles(null);
6312        trackTimeLimits(null);
6313        return;
6314      }
6315
6316      if (!account.parentPinHash || !account.profiles.length) {
6317        syncBlockedCategoryStyles(null);
6318        openProfileChooser(account, email);
6319        return;
6320      }
6321
6322      openProfileChooser(
6323        account,
6324        email,
6325        shouldConfirmOnCatalog(email) ? { catalogConfirm: true } : {}
6326      );
6327
6328      var active = getActiveProfile(account, email);
6329      if (!active) return;
6330      syncBlockedCategoryStyles(active);
6331      hideBlockedCards(active);
6332      guardProgramRoute(active);
6333      enforcePurchaseProtection(account, active);
6334      trackTimeLimits(active);
6335    }
6336
6337    function openCurrentFamilyModal() {
6338      getCurrentUser().then(function (user) {
6339        if (!isEnabledUser(user)) return;
6340        var account = getAccountForUser(user, TEST_USER_EMAIL);
6341        ensureFamilyTab();
6342        ensureSwitcher(account, TEST_USER_EMAIL);
6343        openProfileChooser(account, TEST_USER_EMAIL, { force: true });
6344      });
6345    }
6346
6347    function scheduleSync() {
6348      hideField3();
6349      if (state.syncFrame) return;
6350      state.syncFrame = window.requestAnimationFrame(function () {
6351        state.syncFrame = 0;
6352        syncFeatures();
6353      });
6354    }
6355
6356    function syncFeatures() {
6357      hideField3();
6358      getCurrentUser().then(function (user) {
6359        if (isEnabledUser(user)) {
6360          loadParentalCatalogManifest().then(function () {
6361            var account = getAccountForUser(user, TEST_USER_EMAIL);
6362            applyRules(account, TEST_USER_EMAIL);
6363          });
6364          return;
6365        }
6366
6367        if (user && getUserEmail(user)) return;
6368      });
6369    }
6370
6371    function resetIdentityCache() {
6372      state.sessionPromise = null;
6373      state.userPromise = null;
6374    }
6375
6376    function startObserver() {
6377      if (state.observer || !document.documentElement) return;
6378      state.observer = new MutationObserver(function () {
6379        hideField3();
6380        scheduleSync();
6381      });
6382      state.observer.observe(document.documentElement, { childList: true, subtree: true });
6383    }
6384
6385    function init() {
6386      clearLegacyParentalStorage();
6387      hideField3();
6388      startObserver();
6389      loadParentalCatalogManifest().then(scheduleSync);
6390      scheduleSync();
6391    }
6392
6393    document.addEventListener("click", handlePurchaseClick, true);
6394    document.addEventListener("click", handleSwitcherClick, true);
6395    document.addEventListener("toveedo:open-parental-control", openCurrentFamilyModal);
6396    document.addEventListener("turbo:load", function () {
6397      resetIdentityCache();
6398      scheduleSync();
6399    });
6400    document.addEventListener("turbo:render", scheduleSync);
6401    document.addEventListener("turbo:frame-load", scheduleSync);
6402    window.addEventListener("pageshow", function () {
6403      resetIdentityCache();
6404      scheduleSync();
6405    });
6406
6407    if (document.readyState === "loading") {
6408      document.addEventListener("DOMContentLoaded", init, { once: true });
6409    } else {
6410      init();
6411    }
6412  })();
6413</script>
6413
6414<!-- 
6414<script id="ze-snippet" src="https://static.zdassets.com/ekr/snippet.js?key=998791a4-73b2-4f32-973f-e1b10c144bc6"> </script>
6414 -->
6415
6416<!-- REFERSION TRACKING: BEGIN -->
6417<!-- 
6417<script src="//www.refersion.com/tracker/v3/pub_c7986d812f548f3be371.js"></script>
vendor: 1 bytes, line 6417
6417
6418<script>_refersion();</script>
6418 -->
6419<!-- REFERSION TRACKING: END -->
6420
6421<!-- <style>
6422  /* temp fix attempt center logo in reduce churn page */
6423  body > div.px-2.md\:px-0.root > div.logo-area > div {
6424    justify-content: center;
6425  }
6426</style> -->
6427
6428
6429
6430    
6430<script>
6431      var sentryDsn = 'https://[email protected]/4509910289350656'
6432      var sentryStorefrontActive = true
6433      var sentryStorefrontSampleRate = 1.0
6434      var railsEnv = 'production'
6435      var analyticsHost = 'https://stats.uscreen.io'
6436      var isUscreenUserImpersonated = false
6437      var uscreenStoreId = 556
6438      var analyticsConfig = {"debug": false, "mode": "batch","maxIntervalMs": 20000, "maxEventsInStorage": 10, "allowedEvents": ["abort", "emptied", "ended", "error", "interruptbegin","interruptend", "loadstart", "pause", "canplay","play", "progress", "stalled", "waiting", "seeking", "seeked"],"alwaysClearSession": true,"maxSendingAttempts": 5}
6439    </script>
6439
6440    
6440<script>
6441//<![CDATA[
6442window.gon={};gon.global={"uppy_companion_url":"/uppy","assets_cdn_url":"https://alpha.uscreencdn.com","mixpanel_token":"44e8d04b49529b306bd4a1c79afcc56e"};gon.env="production";gon.session=false;gon.currency="USD";gon.country="US";gon.locale="en";gon.title="Toveedo";gon.terms_url="https://toveedo.com/pages/terms";gon.stripe="pk_live_DImPqz7QOOyx70XCA9DSifxb";gon.stripe_api_version="2024-12-18.acacia";gon.captcha=false;gon.has_gift_card_enabled=true;gon.feature_flags=["themeless","themeless_catalog_visual_fixes","ai_storefront_related_videos","network_spam_detection","calendar_collection_scheduling","checkout_donations","hotwire_catalog","rich_user_profile","passwordless_sign_up","rudolph_subscription_plans","content_moderation","automations","external_checkout","custom_google_cast_receiver","hubspot_hand_raise","email_capture","refer_to_uscreen","billing_sales_tax_on_fees","developer_apps_ui","users_mcp_permissions","apps_update_requests","account_creation_required","banner_with_live_stream_overage","paypal_beta","client_emails_footer","community_notifications_v1","new_video_get_access_page","real_time_apple_notifications","real_time_amazon_notifications","real_time_android_notifications","force_shortened_checkout","new_subscription_flow","pause_subscriptions","stripe_subscriptions","stripe_tax_collection","dm_attachments_in_app","live_chat_with_react","ai_video_recommendations_waitlist","mcp_waitlist","ai_video_recommendations","mobile_featured_category_thumbnail_style","audiences","prompt_app_updates","ai_storefront_related_video_collections","email_topics","headless_api","videos_bulk_all_matching","webhook_auto_disable","ai_storefront_schema_free_recommendations","launch_dashboard_growth_ab_test"];gon.referral_program_tab_enabled=true;gon.recaptcha_v3_pub_key="6LenX-EZAAAAAAITxgGIXKB3S2OVKsM5r5tg0wKL";gon.store_id=556;gon.whitelabeling_enabled=true;gon.writing_direction="ltr";gon.primary_color="#41d0ffff";gon.color_scheme="light";gon.banner_image="https://alpha.uscreencdn.com/1200xnull/v6sGsvQJT8GE30L68fDZ_Launc
6442h%20Screen.jpg";gon.logo="https://alpha.uscreencdn.com/nullx100/images/logotypes/662/null_2fk3gmtm2vrzkxuqp7burq_toveedo-blue.png";gon.integrations={"facebook_pixel":true,"google_tag_manager":false,"google_analytics":true,"refersion":true};
6443//]]>
6444</script>
6444
6445      <title>Toveedo</title>
6446<meta name="description" content="">
6447<!-- Twitter Card data -->
6448<meta name="twitter:card" content="summary" />
6449<meta name="twitter:title" content="Toveedo" />
6450<meta name="twitter:description" content="" />
6451<meta name="twitter:image" content="https://alpha.uscreencdn.com/450xnull/images/logotypes/662/null_2fk3gmtm2vrzkxuqp7burq_toveedo-blue.png?auto=webp" />
6452<!-- Open Graph data -->
6453<meta property="og:title" content="Toveedo" >
6454<meta property="og:type" content="website" />
6455<meta property="og:url" content="https://toveedo.com/sign_in" >
6456<meta property="og:image" content="https://alpha.uscreencdn.com/1110xnull/images/logotypes/662/null_2fk3gmtm2vrzkxuqp7burq_toveedo-blue.png?auto=webp" >
6457<meta property="og:image:width" content="740" >
6458<meta property="og:image:height" content="420" >
6459<meta property="og:description" content="" >
6460<meta property="og:site_name" content="Toveedo" />
6461<link rel="canonical" href="https://toveedo.com/sign_in" />
6462<script type = 'application/ld+json' >
6463  {
6464    "@context": "https://schema.org",
6465    "@type": "Organization",
6466    "url": "https://toveedo.com",
6467    "@id": "https://toveedo.com/#organization",
6468    "name": "Toveedo",
6469    "logo": "https://alpha.uscreencdn.com/450xnull/images/logotypes/662/null_2fk3gmtm2vrzkxuqp7burq_toveedo-blue.png?auto=webp"
6470  }
6471</script>
6471
6472
6473
6474  </head>
6475  <body
6476    class="light themeless group/body"
6477    dir="ltr"
6478  >
6479    <style>
6480      html {
6481        height: 100%;
6482      }
6483      body {
6484          height: 100%;
6485          min-height: 100vh;
6486      }
6487      a {
6488        color: #41d0ffff;
6489      }
6490      .active-color {
6491        color: #41d0ffff !important;
6492      }
6493      .active-background-color {
6494        background-color: #41d0ffff !important;
6495      }
6496      .active-fill {
6497        fill: #41d0ffff !important;
6498      }
6499      .active-border-color {
6500        border-color: #41d0ffff !important;
6501      }
6502      .link, .link:hover, .link:active, .link:focus, .link:visited {
6503        color: #41d0ffff !important;
6504        text-decoration: none;
6505      }
6506
6507      .stripe-card-element .StripeElement {
6508        border-radius: 4px;
6509        background-color: #ffffff;
6510        border: solid 2px #d6dde5;
6511        padding: 10px 15px;
6512      }
6513
6514      .stripe-card-element .field-with-error .StripeElement {
6515        border: solid 2px #ff2322;
6516      }
6517
6518      .stripe-card-element .StripeElement--focus {
6519        border-color: #41d0ffff !important;
6520      }
6521      .stripe-card-element .StripeElement--invalid {
6522        border-color: #ff2322 !important;
6523      }
6524      .active {
6525        fill: #41d0ffff !important;
6526      }
6527      input[type=text]:active, input[type=password]:active, input[type=text]:focus, input[type=password]:focus {
6528        border-color: #41d0ffff !important;
6529      }
6530      .open {
6531        border-color: #41d0ffff !important;
6532      }
6533      .options {
6534        border-color: #41d0ffff !important;
6535      }
6536      .logo-url {
6537        text-decoration: none;
6538      }
6539      
6540    </style>
6541    <style>
6542    :root {
6543        --primary: #41d0ffff;
6544        --primary-rgb: 65, 208, 255;
6545        --contrast-text-color: #FFFFFF;
6546        --contrast-to-primary-color: #fff;
6547    }
6548</style>
6549<style id="custom-theme-styles" media="all">
6550    body.light {
6551        --main-color-h: 0;
6552        --main-color-s: 100;
6553        --main-color-l: 100;
6554        --ds-bg-main: hsl(0, 100%, 100%);
6555
6556        --ds-bg-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 96%);
6557        --ds-bg-inset: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 91%);
6558        --ds-bg-overlay: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 98%);
6559        --ds-bg-subtle: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 91%);
6560
6561        --ds-border-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 85%);
6562        --ds-fg-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 8%);
6563        --ds-fg-muted: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 25%);
6564        --ds-fg-subtle: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 50%);
6565    }
6566
6567    body.dark {
6568        --main-color-h: 0;
6569        --main-color-s: 100;
6570        --main-color-l: 0;
6571        --ds-bg-main: hsl(0, 100%, 0%);
6572
6573        --ds-bg-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 10%);
6574        --ds-bg-overlay: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 2%);
6575        --ds-bg-inset: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 6%);
6576        --ds-bg-subtle: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 15%);
6577
6578        --ds-border-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 20%);
6579        --ds-fg-default: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 98%);
6580        --ds-fg-muted: hsl(var(--main-color-h), calc(var(--main-color-s) * 1%), 70%);
6581        --ds-fg-subtle: hsl(var(--main-color-h), clamp(7%, calc(calc(var(--main-color-s) - 10) * 1%), 100%), 60%);
6582    }
6583
6584    body.dark,
6585    body.light {
6586        --main-color-h: 0;
6587        --main-color-s: 0;
6588        --main-color-l: 100;
6589        --ds-bg-main: hsl(0, 0%, 100%);
6590    }
6591</style>
vendor: 92 bytes, lines 6591-6595
6591
6592
6593    
6594    
6595    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
6596<div class="flex flex-col min-h-screen">
6597  <header class="bg-ds-main">
6598  <div class="container px-5 flex py-4 mx-auto my-0 items-center justify-between max-w-7xl">
6599    <div data-test="branding">
6600  <div class="flex max-w-full overflow-hidden is-centered justify-center">
6601    <div class="flex justify-center max-w-3xl max-h-12">
6602      <a href="/" class="flex justify-center">
6603          <img
6604            src="https://alpha.uscreencdn.com/images/logotypes/662/null_2fk3gmtm2vrzkxuqp7burq_toveedo-blue.png"
6605            alt="Toveedo"
6606            class="object-contain max-w-full max-h-full"
6607          >
6608</a>    </div>
6609  </div>
6610</div>
6611
6612  </div>
6613</header>
6614  <div class="w-full flex-1 bg-ds-subtle text-ds-default">
6615    <div class="flex flex-col md:items-center">
6616      <div class="w-full md:max-w-lg lg:w-[28rem] mt-12 md:mt-32 mb-4" data-test="form-container">
6617          <turbo-frame id="sign_in_form">
6618  <div id="flash_messages">
6619</div>
6620
6621
6622  <div class="p-4 md:p-6 md:border md:rounded bg-ds-default mb-4">
6623    <form data-turbo-frame="sign_in_form" action="/sessions/passwordless_token_email" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="bR4Y8RlsjR6pzgUL_yybYmHN6G66otvgsnR1zZmM2-lEKQFdAVUG1oC16y0QQGw7cSD8lvtenxebvRHl7Pt3Sg" autocomplete="off" />
6624
6625      <h1 class="mb-6 text-ds-large-semi-bold text-ds-default">Welcome back!</h1>
6626
6627      <div class="w-full mb-6">
6628        <div>
6629          <div class="flex-initial mb-2 flex rtl:mr-0 text-ds-muted">
6630            <label>Email</label>
6631          </div>
6632        </div>
6633        <div class="w-full">
6634          <ds-input id="user_email" placeholder="Enter your email" autofocus="autofocus" type="email" not-disable-real-input="true" data-sign-in-target="email"><input name="user[email]" type="email" placeholder="Enter your email" autocomplete="email" slot="input" autofocus="autofocus"></input></ds-input>
6635        </div>
6636      </div>
6637
6638
6639      <ds-button type="submit"
6640                variant="primary"
6641                class="w-full"
6642                data-controller="helpers--button-submitting-state"
6643                data-sign-in-target="submitButton">
6644        Send me a sign in link
6645      </ds-button>
6646</form>
6647    <div class='my-4 text-ds-tiny-semi-bold text-center text-ds-muted'>
6648      OR
6649    </div>
6650
6651    <ds-button id='password-form-btn' variant='regular' class='mb-2 w-full' onclick="goToPasswordForm()" data-redirect=/sign_in?passwordless=false>Sign in with password</ds-button>
6652
6653    <div class="flex flex-col gap-y-2" data-turbo="false">
6654
6655</div>
6656
6657  </div>
6658
6659  <div class="flex flex-col md:items-center">
6660    
6661  </div>
6662  
6662<script type="text/javascript">
6663    function goToPasswordForm() {
6664      const newHref = this.document.getElementById('password-form-btn').dataset.redirect;
6665      const inputEmail = this.document.getElementById('user_email').value;
6666      window.location.href = newHref + '&email=' + inputEmail;
6667    }
6668  </script>
6668
6669</turbo-frame>
6670      </div>
6671    </div>
6672  </div>
6673</div>
6674
6675      
6675<script src="https://js.stripe.com/v3/"></script>
6675
6676    
6677    
6677<script src="https://assets-gke.uscreencdn.com/vite/assets/vue_storefront-DTkOeMtS.js" crossorigin="anonymous" type="module"></script>
6677<link rel="modulepreload" href="https://assets-gke.uscreencdn.com/vite/assets/preload-helper-s3Hva0Xc.js" as="script" crossorigin="anonymous">
6678    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
6679  </body>
6680</html>

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.