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, "&") 448 .replace(/</g, "<") 449 .replace(/>/g, ">") 450 .replace(/"/g, """) 451 .replace(/'/g, "'"); 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, "&") 930 .replace(/</g, "<") 931 .replace(/>/g, ">") + 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, "&") 955 .replace(/</g, "<") 956 .replace(/>/g, ">") + 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, "&") 3674 .replace(/</g, "<") 3675 .replace(/>/g, ">") 3676 .replace(/"/g, """) 3677 .replace(/'/g, "'"); 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>← 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>← 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>← 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>← 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.