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