1 2 3 4 5<!DOCTYPE html> 6<html lang="ca"> 7<head> 8 9 10 11 <base hx-preserve="true" href="/"/> 12 13 14 15 <meta hx-preserve="true" charset="UTF-8"> 16 <meta hx-preserve="true" http-equiv="X-UA-Compatible" content="IE=edge"> 17 <meta hx-preserve="true" content="width=device-width, initial-scale=1.0" name="viewport"> 18 19 20 21 <meta hx-preserve="true" name="author" content="EnaCast"> 22 <meta hx-preserve="true" name="fragment" content="!"> 23 24 <meta name="apple-mobile-web-app-capable" content="yes"> 25 <meta name="mobile-web-app-capable" content="yes"> 26 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> 27 <meta name="apple-mobile-web-app-title" content="Rà dio Sant Vicenç"> 28 <meta name="application-name" content="Rà dio Sant Vicenç"> 29 30 31 <title>Rà dio Sant Vicenç</title> 32 <meta property="og:title" content="Rà dio Sant Vicenç"/> 33 <meta property="og:description" content="Rà dio Sant Vicenç"/> 34 35 36 <meta hx-preserve="true" name="theme-color" content="None"> 37 38 <link hx-preserve="true" rel="manifest" href="manifest.webmanifest" /> 39 40 <link hx-preserve="true" rel="apple-touch-icon" href="/radiosantvicenc/apple-touch-icon.png"> 41 42 43 44 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-32.png" sizes="32x32"> 45 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-76.png" sizes="76x76"> 46 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-57.png" sizes="57x57"> 47 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-96.png" sizes="96x96"> 48 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-128.png" sizes="128x128"> 49 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-192.png" sizes="192x192"> 50 <link hx-preserve="true" rel="icon" href="/radiosantvicenc/favicon-228.png" sizes="228x228"> 51 52 53 <link hx-preserve="true" rel="shortcut icon" sizes="196x196" href="/radiosantvicenc/favicon-196.png"> 54 55 56 <link hx-preserve="true" rel="apple-touch-icon" href="/radiosantvicenc/favicon-120.png" sizes="120x120"> 57 <link hx-preserve="true" rel="apple-touch-icon" href="/radiosantvicenc/favicon-152.png" sizes="152x152"> 58 <link hx-preserve="true" rel="apple-touch-icon" href="/radiosantvicenc/favicon-180.png" sizes="180x180"> 59 60 <link hx-preserve="true" href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet"> 61 62 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.min.css" crossorigin=""> 63 64 <link hx-preserve="true" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" 65 integrity="sha256-rByPlHULObEjJ6XQxW/flG2r+22R5dKiAoef+aXWfik=" crossorigin="anonymous"/> 66 <link hx-preserve="true" rel="stylesheet" 67 href="https://cdnjs.cloudflare.com/ajax/libs/jQuery-ui-Slider-Pips/1.11.4/jquery-ui-slider-pips.min.css" 68 integrity="sha256-sKlemxcbJhCtL3BoS3JA7MIORA4bizjl1VpSgpBI4r8=" crossorigin="anonymous"/> 69 70 <link hx-preserve="true" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.css" 71 integrity="sha512-yxWNfGm+7EK+hqP2CMJ13hsUNCQfHmOuCuLmOq2+uv/AVQtFAjlAJO8bHzpYGQnBghULqnPuY8NEr7f5exR3Qw==" 72 crossorigin="anonymous"/> 73 <link hx-preserve="true" rel="stylesheet" 74 href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.min.css" 75 integrity="sha512-Rck8F2HFBjAQpszOB9Qy+NVLeIy4vUOMB7xrp46edxB3KXs2RxXRguHfrJqNK+vJ+CkfvcGqAKMJTyWYBiBsGA==" 76 crossorigin="anonymous"/> 77 78 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/nprogress.min.css"> 79 80 81 82 83 <link hx-preserve="true" rel="stylesheet" 84 href="/generated_css/3/radiosantvicenc.css"> 85 86 87 88 <link hx-preserve="true" rel="stylesheet" 89 href="https://enacast.com/static/themes/enacast-nx-ui/fontawesome.min.css"> 90 <link hx-preserve="true" rel="stylesheet" href="https://enacast.com/static/frontend_v2/third-party/climacons/climacons-font.css"> 91 <link hx-preserve="true" rel="stylesheet" href="https://enacast.com/static/frontend_v2/weather.css"> 92 93 <link hx-preserve="true" rel="stylesheet" href="https://enacast.com/static/frontend_v2/player/player-nx-ui.css"> 94 95 96 97 98</head> 99 100<body hx-ext="head-support"> 101 102 103 104 <!--<!– Global site tag (gtag.js) - Google Analytics –>--> 105<!--
vendor: 63 bytes, line 105
105<script async src="https://www.googletagmanager.com/gtag/js?id=
105G-TE405X0472
vendor: 11 bytes, line 105
105"></script>
105--> 106<!--
106<script>--> 107<!-- window.dataLayer = window.dataLayer || [];--> 108<!-- function gtag(){dataLayer.push(arguments);}--> 109<!-- gtag('js', new Date());--> 110 111<!-- gtag('config', 'G-TE405X0472');--> 112<!--</script>
112--> 113
114<script defer data-domain="enacast.com" src="https://plausible.io/js/plausible.js"></script>
114 115 116<!-- Matomo EnaCast -->
117<script> 118 var _paq = window._paq = window._paq || []; 119 /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 120 121 _paq.push(["disableCookies"]); 122 _paq.push(['trackPageView']); 123 _paq.push(['enableLinkTracking']); 124 (function() { 125 var u="//web-analytics.enacast.com/"; 126 _paq.push(['setTrackerUrl', u+'matomo.php']); 127 _paq.push(['setSiteId', '10']); 128 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 129 g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); 130 })(); 131</script>
131 132<!-- End Matomo Code --> 133 134<!-- Global site tag (gtag.js) - Google Analytics -->
vendor: 64 bytes, lines 134-135
134 135<script async src="https://www.googletagmanager.com/gtag/js?id=
135UA-68940270-23
vendor: 12 bytes, line 135
135"></script>
136<script> 137
vendor: 134 bytes, lines 137-141
137window.dataLayer = window.dataLayer || []; 138 function gtag(){dataLayer.push(arguments);} 139 gtag('js', new Date()); 140 141 gtag('config', '
141UA-31833407-5
vendor: 11 bytes, lines 141-144
141'); 142 143 144
145</script>
145 146 147 148 149 150 151 152 153 154 155 156<div class="wrapper"> 157 158 159 <div class="toast" data-delay="4000" style="position: absolute; top: 25px; right: 25px;"> 160 <div class="toast-header"> 161 <img src="/radiosantvicenc/favicon-32.png" class="rounded mr-2" alt=""> 162 <strong class="mr-auto">Resuming podcast</strong> 163 164 <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> 165 <span aria-hidden="true">×</span> 166 </button> 167 </div> 168 </div> 169 170 171 172 173 174 175 176 177 178 179<div id="header_with_cover" 180 hx-boost="true" 181 hx-target="#hx-content-div" 182 hx-push-url="true" 183 hx-swap="innerHTML show:window:top" 184 class="nav_header back-img-directive" 185 style="background-image: url(''); background-position: center; transition: background-image 400ms ease-in-out; background-size: cover;"> 186 <nav x-data="{ isNavCollapsed: true }" class="navbar navbar-expand-lg navbar-light bg-white"> 187 <a class="navbar-brand" href="."> 188 <img id="header_radio_logo" 189 src="https://ik.imagekit.io/7ftrkrun31/enacast/logos/2015/10/13/logo_santvi_W6x5uzw.png?tr=w-512,h-None,fo-auto" style="height: 50px" class="d-inline-block align-top pl-sm-2 pl-md-5"> 190 </a> 191 <button x-on:click="isNavCollapsed = !isNavCollapsed" class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" 192 aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> 193 <span class="navbar-toggler-icon"></span> 194 </button> 195 196 <div preload="mouseover" :class="{ 'collapse': isNavCollapsed }" class="collapse navbar-collapse flex-grow-1 text-right" id="navbarNav"> 197 <ul class="navbar-nav ml-auto flex-nowrap"> 198 <!-- Global Search Button --> 199 <li class="enacast-nav-item pr-4" id="global_search_button"> 200 <a x-on:click="$dispatch('open-search-modal')" class="nav-link" href="javascript:void(0);"> 201 <i class="fas fa-search"></i> <span class="d-none d-md-inline">Cercar</span> 202 </a> 203 </li> 204 205 <li class="enacast-nav-item active pr-4" id="menu_item_home"> 206 <a x-on:click='$("#navbarNav").collapse("hide");' class="nav-link" href=".">Inici</a> 207 </li> 208 209 210 <li class="enacast-nav-item pr-4" id="menu_item_programs"> 211 <a x-on:click='$("#navbarNav").collapse("hide");' class="nav-link" href="programs"><span>Programes</span></a> 212 </li> 213 214 215 216 <li class="enacast-nav-item pr-4" id="menu_item_albums"> 217 <a x-on:click='$("#navbarNav").collapse("hide");' class="nav-link" href="albums"><span>Fotografies</span></a> 218 </li> 219 220 221 222 <li class="enacast-nav-item pr-4" id="menu_item_schedules"> 223 <a x-on:click='$("#navbarNav").collapse("hide");' class="nav-link" href="schedules" ><span>Programació</span></a> 224 </li> 225 226 227 228 229 230 231 <li id="custom_menu_423" 232 class="enacast-nav-item dropdown show pr-4 "> 233 <a class="dropdown-toggle nav-link" style="cursor: pointer;" role="button" id="dropdownMenuLink423" 234 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 235 Món educatiu 236 </a> 237 238 <div class="dropdown-menu" aria-labelledby="dropdownMenuLink423"> 239 240 241 <a x-on:click='$("#navbarNav").collapse("hide");'class="dropdown-item " 242 href="moneducatiu">Aula Dial 243 </a> 244 245 246 </div> 247 </li> 248 249 <li id="custom_menu_451" 250 class="enacast-nav-item dropdown show pr-4 "> 251 <a class="dropdown-toggle nav-link" style="cursor: pointer;" role="button" id="dropdownMenuLink451" 252 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 253 Qui som 254 </a> 255 256 <div class="dropdown-menu" aria-labelledby="dropdownMenuLink451"> 257 258 259 <a x-on:click='$("#navbarNav").collapse("hide");'class="dropdown-item " 260 href="page/qui-som">Història 261 </a> 262 263 264 </div> 265 </li> 266 267 268 </ul> 269 </div> 270 </nav> 271</div> 272 273 274 275 276 277 <div id="hx-content-div" 278 hx-boost="true" 279 hx-target="#hx-content-div" 280 hx-push-url="true" 281 hx-swap="innerHTML show:window:top" 282 class="container pt-4 px-2 mb-5 pb-5" class="content"> 283 284 285 286 287<div class="row" style="margin-top: -60px;"> 288 <div class="col-md-8 col-12 bg-neutral_100 mx-auto p-5"> 289 290 <div class="px-5"> 291 292 293 294 <div class="row d-flex justify-content-between mt-5"> 295 <div> 296 <h2>On trobar-nos</h2> 297 298 <p class="text-black"> 299 Ca l'Aragall<br> c/ Barcelona 16-18 primer pis 08620<br> Sant Vicenç dels Horts 300 </p> 301 </div> 302 <div id="conctact_mapid" style="width: 240px; height: 200px;"></div> 303 </div> 304 305 306 307 <div class="row mt-5"> 308 <div> 309 <h2>Contacta amb nosaltres</h2> 310 311 <p class="text-black"> 312 313 <a href="tel:936769377" 314 class="text-black">Telf. 936769377</a><br> 315 316 317 <a href="mailto:" class="text-black" target="_blank"></a><br> 318 319 </p> 320 </div> 321 </div> 322 323 324 <div class="row mt-5"> 325 326 <a class="mr-3" 327 target="_blank" 328 href="https://www.facebook.com/radiosantvicenc"> 329 <i class="icon-bg-grey pull-right fab fa-facebook facebook-icon-bg rounded-x icon-bg-grey fa-3x" 330 title="Contacte a Facebook"></i> 331 </a> 332 333 334 <a class="mr-3" 335 target="_blank" 336 href="https://twitter.com/radiosvh902"> 337 <i class="icon-bg-grey pull-right fab fa-twitter twitter-icon-bg rounded-x icon-bg-grey fa-3x" 338 title="Contacte a Twitter"></i> 339 </a> 340 341 342 </div> 343 344 345 </div> 346 </div> 347</div> 348
349<script> 350 window.addEventListener('load', function() { 351 let success = window.location.search.includes("success=1"); 352 console.log(success); 353 console.log(window.location.search); 354 if (success) { 355 Swal.fire("Missatge enviat correctament"); 356 } 357 }); 358 359 const lat = radio.latitude; 360 const lon = radio.longitude; 361 362 let mymap = L.map('conctact_mapid', { 363 zoomControl: false, 364 doubleClickZoom: false, 365 }).setView([lat, lon], 15); 366 367 L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=' + mapbox_public_key, { 368 attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', 369 maxZoom: 17, 370 id: 'mapbox/streets-v11', 371 tileSize: 512, 372 zoomOffset: -1, 373 accessToken: mapbox_public_key 374 }).addTo(mymap); 375 376 let marker = L.marker([lat, lon]).addTo(mymap); 377 378 function onMapClick(e) { 379 window.open("https://www.google.com/maps/search/?api=1&query=" + lat.toString() + "," + lon.toString(), "_blank") 380 } 381 382 mymap.on('click', onMapClick); 383 384 385 386 387 388</script>
388 389 390 391 392 </div> 393 394 395 396 <footer class="footeroom fixed-bottom"> 397 398 399<div > 400 401 402 403 404 405 406 407<div x-data class="p-0 px-md-2 container"> 408 409 <div class="enacast-player bg-primary d-flex rounded" style="height: 85px; align-items: center;"> 410 <div class="mx-2 mx-md-4"> 411 <img class="rounded" style="width: 45px; height: 45px;" 412 alt="Now playing logo" 413 :src="$store.player.now_playing_image"> 414 </div> 415 416 <div @click="$store.player.playBack(10)" 417 style="background: none;" 418 class="player-button mr-2" 419 x-show="$store.player.showFwdBackBtn"> 420 <i class="fas fa-undo" title="-10s"></i> 421 </div> 422 <div @click="$store.player.play()" 423 class="player-button mr-2" 424 x-show="$store.player.showPlayBtn"> 425 <i class="fas fa-play"></i> 426 </div> 427 <div @click="$store.player.pause()" 428 class="player-button mr-2" x-show="$store.player.showPauseBtn"> 429 <i class="fas fa-pause"></i> 430 </div> 431 <div @click="$store.player.playForward(30)" 432 style="background: none;" 433 class="player-button mr-2" 434 x-show="$store.player.showFwdBackBtn"> 435 <i class="fas fa-undo" style="transform: scaleX(-1); filter: flipH(); float: none;" title="+30s"></i> 436 </div> 437 438 <div @click="$store.player.stop()" 439 class="player-button mr-2" 440 x-show="$store.player.showStopBtn"> 441 <i class="fas fa-stop"></i> 442 </div> 443 444 <div id="enacast_now_playing_info" class="ml-1 ml-md-3 col-md-4 mw-0"> 445 <div class="d-flex flex-column"> 446 <span x-text="$store.player.now_playing_title" class="one-line-text" 447 style="font-size: 0.8125rem; font-weight: bold; color: white; display: inline-block; margin-left: -10px;"></span> 448 449 <span class="" x-show="$store.player.player_status == 'playing_live'" style="margin-left: -10px;"> 450 Directe <i class="fas fa-stream icon-sm rounded-x color-default" 451 style="transform: rotate(-90deg)"></i><i 452 class="fas fa-stream icon-sm rounded-x color-default" style="transform: rotate(90deg)"></i> 453 </span> 454 455 </div> 456 457 458 <div class="pb-1" 459 x-show="$store.player.player_status == 'playing_podcast' || $store.player.player_status == 'paused' || $store.player.current_schedule != undefined"> 460 <div id="slider_nx" class=""></div> 461 <div class="d-flex justify-content-between"> 462 <span 463 style="font-size: 0.8125rem; font-weight: bold; color: white; display: inline-block; margin-left: -10px;" 464 x-text="$store.player.elapsed_time"></span> 465 <span 466 style="font-size: 0.8125rem; font-weight: bold; color: white; display: inline-block; margin-left: -10px;" 467 x-text="$store.player.remaining_time"></span> 468 </div> 469 </div> 470 </div> 471 472 <div class="mx-3 d-none d-md-block" x-show="$store.player.now_playing_podcast_id"> 473 474 <social-share-buttons podcast="now_playing_podcast_id" inverted="true"></social-share-buttons> 475 </div> 476 477 478 <div class="ml-auto p-2 d-none d-lg-block"> 479 <span style="font-size: 0.5rem; opacity: 0.75;">Powered by</span> 480 <a href="https://enacast.com"> 481 <img src="https://enacast.com/static/enacast_logos/enacast_negative.png" style="height: 15px; margin-bottom: 4px;"></a> 482 </div> 483 484 485 <div class="d-none"> 486 <audio id="enacast-player"> 487 <source src="" type="audio/mpeg"> 488 </audio> 489 </div> 490 </div> 491</div> 492 493</div> 494 495 </footer> 496 497 498 499</div> 500 501<!-- Root element of PhotoSwipe. Must have class pswp. --> 502<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"> 503 504 <!-- Background of PhotoSwipe. 505 It's a separate element as animating opacity is faster than rgba(). --> 506 <div class="pswp__bg"></div> 507 508 <!-- Slides wrapper with overflow:hidden. --> 509 <div class="pswp__scroll-wrap"> 510 511 <!-- Container that holds slides. 512 PhotoSwipe keeps only 3 of them in the DOM to save memory. 513 Don't modify these 3 pswp__item elements, data is added later on. --> 514 <div class="pswp__container"> 515 <div class="pswp__item"></div> 516 <div class="pswp__item"></div> 517 <div class="pswp__item"></div> 518 </div> 519 520 <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. --> 521 <div class="pswp__ui pswp__ui--hidden"> 522 523 <div class="pswp__top-bar"> 524 525 <!-- Controls are self-explanatory. Order can be changed. --> 526 527 <div class="pswp__counter"></div> 528 529 <button class="pswp__button pswp__button--close" title="Close (Esc)"></button> 530 531 <button class="pswp__button pswp__button--share" title="Share"></button> 532 533 <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button> 534 535 <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button> 536 537 <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR --> 538 <!-- element will get class pswp__preloader--active when preloader is running --> 539 <div class="pswp__preloader"> 540 <div class="pswp__preloader__icn"> 541 <div class="pswp__preloader__cut"> 542 <div class="pswp__preloader__donut"></div> 543 </div> 544 </div> 545 </div> 546 </div> 547 548 <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"> 549 <div class="pswp__share-tooltip"></div> 550 </div> 551 552 <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"> 553 </button> 554 555 <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"> 556 </button> 557 558 <div class="pswp__caption"> 559 <div class="pswp__caption__center"></div> 560 </div> 561 562 </div> 563 564 </div> 565 566</div> 567 568 569 570<div id="global-search-modal" x-data="globalSearchComponent" @open-search-modal.window="openModal" class="modal fade" tabindex="-1" aria-hidden="true"> 571 <div class="modal-dialog modal-lg modal-dialog-scrollable" style="max-width: 70%;"> 572 <div class="modal-content "> 573 <div class="modal-body " style="max-height: 80vh; overflow-y: auto;"> 574 <div class="form-group"> 575 <div class="input-group mb-3"> 576 <input x-model="search_input" @input.debounce.500ms="fetchSearchResults" type="text" class="form-control form-control-lg" 577 placeholder="Search..." autofocus x-ref="searchInput"> 578 </div> 579 </div> 580 581 <!-- Tabs --> 582 <ul class="nav nav-tabs " id="searchTabs" role="tablist"> 583 <li class="nav-item" role="presentation"> 584 <a class="nav-link active" id="all-tab" data-toggle="tab" href="#all" role="tab" 585 aria-controls="all" aria-selected="true" @click="activeTab = 'all'"> 586 Tots <span class="badge badge-light" x-text="total_results"></span> 587 </a> 588 </li> 589 <li class="nav-item" role="presentation"> 590 <a class="nav-link " id="podcasts-tab" data-toggle="tab" href="#podcasts" role="tab" 591 aria-controls="podcasts" aria-selected="false" @click="activeTab = 'podcasts'"> 592 Podcasts <span class="badge badge-light" x-text="podcasts_count"></span> 593 </a> 594 </li> 595 <li class="nav-item" role="presentation"> 596 <a class="nav-link " id="moments-tab" data-toggle="tab" href="#moments" role="tab" 597 aria-controls="moments" aria-selected="false" @click="activeTab = 'moments'"> 598 Moments <span class="badge badge-light" x-text="moments_count"></span> 599 </a> 600 </li> 601 <li class="nav-item" role="presentation"> 602 <a class="nav-link " id="news-tab" data-toggle="tab" href="#news" role="tab" 603 aria-controls="news" aria-selected="false" @click="activeTab = 'news'"> 604 NotÃcies <span class="badge badge-light" x-text="news_count"></span> 605 </a> 606 </li> 607 </ul> 608 609 <!-- Tab contents --> 610 <div class="tab-content pt-3" id="searchTabsContent"> 611 <!-- Loading indicator --> 612 <div x-show="isLoading" class="text-center py-5"> 613 <div class="spinner-border text-primary" role="status">
614 <span class="sr-only">Loading...</span> 615 </div> 616 </div> 617 618 <!-- No results message --> 619 <div x-show="!isLoading && search_input.length >= 3 && total_results === 0" class="text-center py-5"> 620 <p class="text-muted">No results found for "<span x-text="search_input"></span>"</p> 621 </div> 622 623 <!-- All Results Tab --> 624 <div class="tab-pane fade show active" id="all" role="tabpanel" aria-labelledby="all-tab" 625 x-show="!isLoading && total_results > 0"> 626 <div class="search-results"> 627 628 <!-- Podcasts --> 629 <div x-show="podcasts_results.length > 0" class="result-section mb-4"> 630 <h4>Podcasts</h4> 631 <div class="row"> 632 <template x-for="podcast in podcasts_results.slice(0, 6)"> 633 <div class="col-12 col-md-6 col-xl-4 mb-3"> 634 <div class="p-2 h-100 border rounded"> 635 <div class="d-flex overflow-hidden"> 636 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 637 <img class="img-fluid rounded" 638 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 639 x-bind:src="podcast.document.thumbnail || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 640 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 641 alt=""> 642 </div> 643 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 644 <h5 class="mt-0 mb-1 text-truncate" style="max-width: 100%;"> 645 <a :href="window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID" 646 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 647 class="text-decoration-none text-dark"> 648 <span x-text="podcast.document.podcastTitle"></span> 649 </a> 650 </h5> 651 <p class="small mb-1 text-truncate" style="max-width: 100%;"> 652 <span x-html="podcast.highlights[0]?.snippet || podcast.document.transcription?.substring(0, 100) + '...'" class="d-block" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"></span> 653 </p> 654 <p class="small text-muted mb-1"> 655 <i class="far fa-calendar-alt"></i> 656 <span x-text="formatDate(podcast.document.recorded_ts)"></span> 657 </p> 658 <div class="mt-auto"> 659 <a @click="$store.player.play_podcast(podcast.document)" href="javascript:void(0);" class="btn btn-sm btn-primary me-1"> 660 <i class="fas fa-play text-white"></i> Play 661 </a> 662 <a :href="window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID" 663 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 664 class="btn btn-sm"> 665 <i class="fas fa-info-circle"></i> Details 666 </a> 667 </div> 668 </div> 669 </div> 670 </div> 671 </div> 672 </template> 673 </div> 674 <div x-show="podcasts_results.length > 6" class="text-right mt-2"> 675 <a href="#podcasts" data-toggle="tab" @click="activeTab = 'podcasts'" class="">View all podcasts »</a> 676 </div> 677 </div> 678 679 <!-- Moments --> 680 <div x-show="moments_results.length > 0" class="result-section mb-4"> 681 <h4>Moments</h4> 682 <div class="row"> 683 <template x-for="moment in moments_results.slice(0, 6)"> 684 <div class="col-12 col-md-6 col-xl-4 mb-3"> 685 <div class="p-2 h-100 border rounded"> 686 <div class="d-flex overflow-hidden"> 687 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 688 <img class="img-fluid rounded" 689 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 690 x-bind:src="moment.document.thumbnail || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 691 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 692 alt=""> 693 </div> 694 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 695 <h5 class="mt-0 mb-1 text-truncate" style="max-width: 100%;"> 696 <a :href="window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID" 697 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 698 class="text-decoration-none text-dark">
699 <span x-text="moment.document.podcastTitle"></span> 700 </a> 701 </h5> 702 <p class="small mb-1 text-truncate" style="max-width: 100%;"> 703 <span x-html="moment.highlights[0]?.snippet || moment.document.transcription?.substring(0, 100) + '...'" class="d-block" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"></span> 704 </p> 705 <p class="small text-muted mb-1"> 706 <i class="far fa-calendar-alt"></i> 707 <span x-text="formatDate(moment.document.recorded_ts)"></span> 708 </p> 709 <div class="mt-auto"> 710 <a @click="$store.player.play_podcast_at_time(moment.document, moment.document.start)" href="javascript:void(0);" class="btn btn-sm btn-primary me-1"> 711 <i class="fas fa-play text-white"></i> Play 712 </a> 713 <a :href="window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID" 714 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 715 class="btn btn-sm"> 716 <i class="fas fa-info-circle"></i> Details 717 </a> 718 </div> 719 </div> 720 </div> 721 </div> 722 </div> 723 </template> 724 </div> 725 <div x-show="moments_results.length > 6" class="text-right mt-2"> 726 <a href="#moments" data-toggle="tab" @click="activeTab = 'moments'" class="">View all moments »</a> 727 </div> 728 </div> 729 730 <!-- News --> 731 <div x-show="news_results.length > 0" class="result-section mb-4"> 732 <h4>NotÃcies</h4> 733 <div class="row"> 734 <template x-for="news in news_results.slice(0, 6)"> 735 <div class="col-12 col-md-6 col-xl-4 mb-3"> 736 <div class="p-2 h-100 border rounded"> 737 <div class="d-flex overflow-hidden"> 738 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 739 <img class="img-fluid rounded" 740 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 741 x-bind:src="news.document.main_image || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 742 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 743 alt=""> 744 </div> 745 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 746 <h5 class="mt-0 mb-1" x-text="news.document.headline"></h5> 747 <div class="small mb-2 text-truncate" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"> 748 <span x-html="news.highlights[0]?.snippet || news.document.summary"></span> 749 </div> 750 <p class="small text-muted mb-1"> 751 <i class="far fa-calendar-alt"></i> 752 <span x-text="formatDate(news.document.datetime_to_publish_ts)"></span> 753 </p> 754 <div class="mt-auto"> 755 <a :href="window.radioPrefix + 'news/' + news.document.slug" 756 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'news/' + news.document.slug, {target: '#hx-content-div', pushURL: true})" 757 class="btn btn-sm btn-primary">Llegir més</a> 758 </div> 759 </div> 760 </div> 761 </div> 762 </div> 763 </template> 764 </div> 765 <div x-show="news_results.length > 6" class="text-right mt-2"> 766 <a href="#news" data-toggle="tab" @click="activeTab = 'news'" class="">View all news »</a> 767 </div> 768 </div> 769 </div> 770 </div> 771 772 773 <!-- Podcasts Tab --> 774 <div class="tab-pane fade" id="podcasts" role="tabpanel" aria-labelledby="podcasts-tab" 775 x-show="!isLoading && podcasts_results.length > 0"> 776 <div class="row"> 777 <template x-for="podcast in podcasts_results"> 778 <div class="col-12 col-md-6 col-xl-4 mb-3"> 779 <div class="p-2 h-100 border rounded"> 780 <div class="d-flex overflow-hidden"> 781 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 782 <img class="img-fluid rounded" 783 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 784 x-bind:src="podcast.document.thumbnail || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 785 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 786 alt=""> 787 </div> 788 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 789 <h5 class="mt-0 mb-1 text-truncate" style="max-width: 100%;"> 790 <a :href="window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID" 791 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 792 class="text-decoration-none text-dark">
793 <span x-text="podcast.document.podcastTitle"></span> 794 </a> 795 </h5> 796 <p class="small mb-1 text-truncate" style="max-width: 100%;"> 797 <span x-html="podcast.highlights[0]?.snippet || podcast.document.transcription?.substring(0, 100) + '...'" class="d-block" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"></span> 798 </p> 799 <p class="small text-muted mb-1"> 800 <i class="far fa-calendar-alt"></i> 801 <span x-text="formatDate(podcast.document.recorded_ts)"></span> 802 </p> 803 <div class="mt-auto"> 804 <a @click="$store.player.play_podcast(podcast.document)" href="javascript:void(0);" class="btn btn-sm btn-primary me-1"> 805 <i class="fas fa-play text-white"></i> Play 806 </a> 807 <a :href="window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID" 808 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + podcast.document.programCodename + '/' + podcast.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 809 class="btn btn-sm"> 810 <i class="fas fa-info-circle"></i> Details 811 </a> 812 </div> 813 </div> 814 </div> 815 </div> 816 </div> 817 </template> 818 </div> 819 </div> 820 821 <!-- Moments Tab --> 822 <div class="tab-pane fade" id="moments" role="tabpanel" aria-labelledby="moments-tab" 823 x-show="!isLoading && moments_results.length > 0"> 824 <div class="row"> 825 <template x-for="moment in moments_results"> 826 <div class="col-12 col-md-6 col-xl-4 mb-3"> 827 <div class="p-2 h-100 border rounded"> 828 <div class="d-flex overflow-hidden"> 829 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 830 <img class="img-fluid rounded" 831 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 832 x-bind:src="moment.document.thumbnail || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 833 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 834 alt=""> 835 </div> 836 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 837 <h5 class="mt-0 mb-1 text-truncate" style="max-width: 100%;"> 838 <a :href="window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID" 839 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 840 class="text-decoration-none text-dark"> 841 <span x-text="moment.document.podcastTitle"></span> 842 </a> 843 </h5> 844 <p class="small mb-1 text-truncate" style="max-width: 100%;"> 845 <span x-html="moment.highlights[0]?.snippet || moment.document.transcription?.substring(0, 100) + '...'" class="d-block" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"></span> 846 </p> 847 <p class="small text-muted mb-1"> 848 <i class="far fa-calendar-alt"></i>
849 <span x-text="formatDate(moment.document.recorded_ts)"></span> 850 </p> 851 <div class="mt-auto"> 852 <a @click="$store.player.play_podcast_at_time(moment.document, moment.document.start)" href="javascript:void(0);" class="btn btn-sm btn-primary me-1"> 853 <i class="fas fa-play text-white"></i> Play 854 </a> 855 <a :href="window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID" 856 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'programs/' + moment.document.programCodename + '/' + moment.document.podcastPublicID, {target: '#hx-content-div', pushURL: true})" 857 class="btn btn-sm"> 858 <i class="fas fa-info-circle"></i> Details 859 </a> 860 </div> 861 </div> 862 </div> 863 </div> 864 </div> 865 </template> 866 </div> 867 </div> 868 869 <!-- News Tab --> 870 <div class="tab-pane fade" id="news" role="tabpanel" aria-labelledby="news-tab" 871 x-show="!isLoading && news_results.length > 0"> 872 <div class="row"> 873 <template x-for="news in news_results"> 874 <div class="col-12 col-md-6 col-xl-4 mb-3"> 875 <div class="p-2 h-100 border rounded"> 876 <div class="d-flex overflow-hidden"> 877 <div class="flex-shrink-0" style="width: 80px; height: 80px;"> 878 <img class="img-fluid rounded" 879 style="margin-right: 1rem; width: 80px; height: 80px; object-fit: cover;" 880 x-bind:src="news.document.main_image || 'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto'" 881 :onerror="'this.src=\\'https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto\\''" 882 alt=""> 883 </div> 884 <div class="flex-grow-1 overflow-hidden ml-3 ms-3 d-flex flex-column"> 885 <h5 class="mt-0 mb-1 text-truncate" style="max-width: 100%;" x-text="news.document.headline"></h5> 886 <div class="small mb-2 text-truncate" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;"> 887 <span x-html="news.highlights[0]?.snippet || news.document.summary"></span> 888 </div> 889 <p class="small text-muted mb-1"> 890 <i class="far fa-calendar-alt"></i> 891 <span x-text="formatDate(news.document.datetime_to_publish_ts)"></span> 892 </p> 893 <div class="mt-auto"> 894 <a :href="window.radioPrefix + 'news/' + news.document.slug" 895 @click.prevent="$('#global-search-modal').modal('hide'); htmx.ajax('GET', window.radioPrefix + 'news/' + news.document.slug, {target: '#hx-content-div', pushURL: true})" 896 class="btn btn-sm btn-primary">Llegir més</a> 897 </div> 898 </div> 899 </div> 900 </div> 901 </div> 902 </template> 903 </div> 904 </div> 905 906 </div> 907 908 <div class="search-meta text-muted small mt-3" x-show="search_input.length >= 3 && !isLoading"> 909 <span x-text="total_results"></span> results found in <span x-text="search_time_ms"></span>ms 910 </div> 911 </div> 912 </div> 913 </div> 914</div> 915
916<script> 917 document.addEventListener('alpine:init', () => { 918 Alpine.data('globalSearchComponent', () => ({ 919 search_input: '', 920 isLoading: false, 921 activeTab: 'all', 922 923 // Results containers 924 podcasts_results: [], 925 moments_results: [], 926 news_results: [], 927 928 // Counts 929 podcasts_count: 0, 930 moments_count: 0, 931 news_count: 0, 932 933 search_time_ms: 0, 934 935 openModal() { 936 $('#global-search-modal').modal('show'); 937 this.$nextTick(() => { 938 this.$refs.searchInput.focus(); 939 }); 940 }, 941 942 clearSearch() { 943 this.search_input = ''; 944 this.podcasts_results = []; 945 this.moments_results = []; 946 this.news_results = []; 947 this.podcasts_count = 0; 948 this.moments_count = 0; 949 this.news_count = 0; 950 }, 951 952 get total_results() { 953 return this.podcasts_count + this.moments_count + this.news_count; 954 }, 955 956 formatDate(ts) { 957 if (!ts) return ''; 958 // If it's a string, try to parse as ISO, else treat as timestamp (seconds) 959 let d = typeof ts === 'string' ? new Date(ts) : new Date(ts * 1000); 960 if (isNaN(d.getTime())) return ''; 961 return d.toLocaleDateString(); 962 }, 963 964 async fetchSearchResults() { 965 if (this.search_input.length < 3) { 966 this.clearSearch(); 967 return; 968 } 969 970 this.isLoading = true; 971 972 try { 973 // Initialize Typesense client if not already done 974 initTypesense(); 975 976 // Search parameters 977 const baseParams = { 978 'q': this.search_input, 979 'filter_by': 'radioCodename:radiosantvicenc', 980 'per_page': 30, 981 'page': 1 982 }; 983 984 // Run searches in parallel 985 const [podcastsResults, momentsResults, newsResults] = await Promise.all([ 986 // Podcasts search (full podcasts) 987 typesense_client.collections('enacast___podcasts').documents().search({ 988 ...baseParams, 989 'query_by': 'podcastTitle,podcastDescription', 990 'sort_by': 'recorded_ts:desc' 991 }), 992 993 // Moments search (podcast chunks/segments) 994 typesense_client.collections('enacast___podcasts-chunks').documents().search({ 995 ...baseParams, 996 'query_by': 'transcription', 997 'sort_by': 'recorded_ts:desc' 998 }), 999 1000 // News search 1001 typesense_client.collections('enacast___news').documents().search({ 1002 ...baseParams, 1003 'query_by': 'headline,body', 1004 'sort_by': 'datetime_to_publish_ts:desc' 1005 }) 1006 ]); 1007 1008 // Set results from the API response 1009 this.podcasts_results = podcastsResults.hits || []; 1010 this.podcasts_count = podcastsResults.found || 0; 1011 1012 this.moments_results = momentsResults.hits || []; 1013 this.moments_count = momentsResults.found || 0; 1014 1015 this.news_results = newsResults.hits || []; 1016 this.news_count = newsResults.found || 0; 1017 1018 // Use the highest search time for display 1019 this.search_time_ms = Math.max( 1020 podcastsResults.search_time_ms || 0, 1021 momentsResults.search_time_ms || 0, 1022 newsResults.search_time_ms || 0 1023 ); 1024 } catch (error) { 1025 console.error('Search error:', error); 1026 } finally { 1027 this.isLoading = false; 1028 } 1029 } 1030 })); 1031 1032 // Wait for Alpine to be ready so $store is available 1033 $('#global-search-modal').on('shown.bs.modal', function () { 1034 // Use Alpine's $data to get the component and focus the input 1035 let alpineComponent = Alpine.$data(document.getElementById('global-search-modal')); 1036 if (alpineComponent && alpineComponent.$refs && alpineComponent.$refs.searchInput) { 1037 alpineComponent.$refs.searchInput.focus(); 1038 } 1039 }); 1040 }); 1041 1042 document.addEventListener('keydown', function(e) { 1043 // Check for Ctrl+K (Windows/Linux) or Cmd+K (Mac) 1044 if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { 1045 e.preventDefault(); 1046 window.dispatchEvent(new CustomEvent('open-search-modal')); 1047 } 1048 }); 1049</script>
1049 1050
1051<script> 1052 $('#global-search-modal').on('shown.bs.modal', function () { 1053 $(this).find('input[type="text"]').trigger('focus'); 1054 }); 1055</script>
1055 1056 1057
1058<script>window.radioPrefix = '';</script>
1058 1059 1060 1061
1062<script 1063 src="https://code.jquery.com/jquery-3.4.1.min.js" 1064 integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" 1065 crossorigin="anonymous"></script>
vendor: 1 bytes, line 1065
1065
1066<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" 1067 integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" 1068 crossorigin="anonymous"></script>
1068 1069
1070<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" 1071 integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" 1072 crossorigin="anonymous"></script>
1072 1073 1074 1075
1076<script> 1077 const typesense_host = "typesense-hgs884w80gc8gc80ssk84coo"; 1078 const typesense_port = "8108"; 1079 const typesense_protocol = "http"; 1080 const typesense_search_api_key = "azX92P4d73j0FsXmiytDe3IcFYzsiOLv"; 1081 let typesense_client = null; 1082 1083 function initTypesense() { 1084 if (typesense_client) { 1085 return; 1086 } 1087 typesense_client = new Typesense.Client({ 1088 'nodes': [{ 1089 'host': typesense_host, 1090 'port': typesense_port, 1091 'protocol': typesense_protocol, 1092 }], 1093 'apiKey': typesense_search_api_key, 1094 'connectionTimeoutSeconds': 2 1095 }); 1096 } 1097</script>
1097 1098
1099<script> 1100 document.addEventListener('alpine:init', () => { 1101 const programs_names_list = []; 1102 1103 programs_names_list.push('El Sacacorchos'); 1104 1105 programs_names_list.push('Caña activa'); 1106 1107 programs_names_list.push('Informatiu - Sant Vicenç al dia'); 1108 1109 programs_names_list.push('Més que estrenes.'); 1110 1111 programs_names_list.push('Sal y pimienta'); 1112 1113 programs_names_list.push('Batibull'); 1114 1115 programs_names_list.push('La pirámide'); 1116 1117 programs_names_list.push('Hell Awaits Radio '); 1118 1119 programs_names_list.push('Años luz'); 1120 1121 programs_names_list.push('El Conte'); 1122 1123 programs_names_list.push('Moments clà ssics'); 1124 1125 programs_names_list.push('Fem esport'); 1126 1127 programs_names_list.push('Radio Games'); 1128 1129 programs_names_list.push('Jazz club de nit'); 1130 1131 programs_names_list.push('Collita pròpia'); 1132 1133 programs_names_list.push('Voltes al dial'); 1134 1135 programs_names_list.push('Vicentinades'); 1136 1137 programs_names_list.push('Barris'); 1138 1139 programs_names_list.push('Tomà quets i Pebrots'); 1140 1141 programs_names_list.push('A dues bandes'); 1142 1143 programs_names_list.push('Recerques'); 1144 1145 programs_names_list.push('Rock Parking Lot'); 1146 1147 programs_names_list.push('Heavy MPL. '); 1148 1149 programs_names_list.push('10 contra 1'); 1150 1151 programs_names_list.push('El jardà de la memòria'); 1152 1153 programs_names_list.push('Pasarela Shopper'); 1154 1155 programs_names_list.push('MKC (Més que cinema)'); 1156 1157 programs_names_list.push('Encuentros'); 1158 1159 programs_names_list.push('De bon rotllo'); 1160 1161 programs_names_list.push('A quatre bandes'); 1162 1163 programs_names_list.push('Mix de Trébol'); 1164 1165 programs_names_list.push('Ple municipal'); 1166 1167 programs_names_list.push('The Psychedelic Years'); 1168 1169 programs_names_list.push('2gether'); 1170 1171 programs_names_list.push('Rebel·lió a la granja'); 1172 1173 programs_names_list.push('Campanya de joguines (10 - 12 h)'); 1174 1175 programs_names_list.push('Campanya de joguines (12 - 14.30 h)'); 1176 1177 programs_names_list.push('Campanya de joguines (16 - 18 h)'); 1178 1179 programs_names_list.push('Campanya de joguines (18 - 20 h)'); 1180 1181 programs_names_list.push('Festa Major d'Hivern 2013 (part 1)'); 1182 1183 programs_names_list.push('Festa Major d'Hivern 2013 (part 2)'); 1184 1185 programs_names_list.push('Festa Major d'Hivern 2013 (part 3)'); 1186 1187 programs_names_list.push('Festa Major d'Hivern 2013 (part 4)'); 1188 1189 programs_names_list.push('Sábado sabadete '); 1190 1191 programs_names_list.push('Sábado sabadete (2ª part)'); 1192 1193 programs_names_list.push('Psicologia al dia'); 1194 1195 programs_names_list.push('Aula dial'); 1196 1197 programs_names_list.push('Festa Major d'Estiu 2013 - Pregó'); 1198 1199 programs_names_list.push('Tocat del bolet'); 1200 1201 programs_names_list.push('Klaatu Barada Nikto'); 1202 1203 programs_names_list.push('Un poble, una rotllana'); 1204 1205 programs_names_list.push('Lunáticos'); 1206 1207 programs_names_list.push('Teatre a la rà dio - s'aixeca el teló'); 1208 1209 programs_names_list.push('La nit del coaching'); 1210 1211 programs_names_list.push('Festa Major d'Hivern i Mostra'); 1212 1213 programs_names_list.push('FamÃlies i escola'); 1214 1215 programs_names_list.push('Fly's rock'); 1216 1217 programs_names_list.push('Sal y Pimienta The Luxe'); 1218 1219 programs_names_list.push('Tours i trucs'); 1220 1221 programs_names_list.push('Aquellos maravillosos 80'); 1222 1223 programs_names_list.push('Madinati'); 1224 1225 programs_names_list.push('A la Vieja Escuela'); 1226 1227 programs_names_list.push('Cap infant sense joguina'); 1228 1229 programs_names_list.push('Eleccions municipals'); 1230 1231 programs_names_list.push('MixTeen'); 1232 1233 programs_names_list.push('Fitness mental'); 1234 1235 programs_names_list.push('Las Noches en Vela'); 1236 1237 programs_names_list.push('Tutifruti'); 1238 1239 programs_names_list.push('Desfem'); 1240 1241 programs_names_list.push('Acción adolescente'); 1242 1243 programs_names_list.push('Audiència pública'); 1244 1245 programs_names_list.push('La República'); 1246 1247 programs_names_list.push('La coctelera'); 1248 1249 programs_names_list.push('Detrás del arte'); 1250 1251 programs_names_list.push('L'envelat'); 1252 1253 programs_names_list.push('Area 52'); 1254 1255 programs_names_list.push('Llargs i curts'); 1256 1257 programs_names_list.push('Torna-la a tocar'); 1258 1259 programs_names_list.push('Bona tarda poesia'); 1260 1261 programs_names_list.push('Dando la nota'); 1262 1263 programs_names_list.push('Som terra'); 1264 1265 programs_names_list.push('Sendes'); 1266 1267 programs_names_list.push('Moments d'òpera'); 1268 1269 programs_names_list.push('The Block'); 1270 1271 programs_names_list.push('Ciutat en Vers'); 1272 1273 programs_names_list.push('Gerónimo'); 1274 1275 programs_names_list.push('L'alcalde respon'); 1276 1277 programs_names_list.push('Espai Públic 2030'); 1278 1279 programs_names_list.push('Eureka!'); 1280 1281 programs_names_list.push('Col·lectius en xarxa'); 1282 1283 programs_names_list.push('Tinc una idea'); 1284 1285 programs_names_list.push('La meva playlist'); 1286 1287 programs_names_list.push('Moments d'opera'); 1288 1289 programs_names_list.push('Aula dial Curs'); 1290 1291 programs_names_list.push('momentsopera'); 1292 1293 programs_names_list.push('L'any que ens va canviar la vida'); 1294 1295 programs_names_list.push('Audiència Publica'); 1296 1297 programs_names_list.push('locucio_ajuntament'); 1298 1299 programs_names_list.push('El grup'); 1300 1301 programs_names_list.push('bellesa al aire'); 1302 1303 programs_names_list.push('Manifest'); 1304 1305 programs_names_list.push('Coblejant'); 1306 1307 programs_names_list.push('esplai-public'); 1308 1309 programs_names_list.push('Coses del directe'); 1310 1311 programs_names_list.push('Festival XS'); 1312 1313 programs_names_list.push('Arrels i ales'); 1314 1315 programs_names_list.push('Valors a l'alça'); 1316 1317 programs_names_list.push('Peluts'); 1318 1319 programs_names_list.push('Una de cada'); 1320 1321 programs_names_list.push('L'Eriçó i la Guineu'); 1322 1323 programs_names_list.push('NotÃcies en Xarxa'); 1324 1325 programs_names_list.push('En joc'); 1326 1327 programs_names_list.push('Dia Mundial de la Rà dio'); 1328 1329 programs_names_list.push('Eighty'); 1330 1331 programs_names_list.push('Joves en acció'); 1332 1333 programs_names_list.push('Onda ODS'); 1334 1335 programs_names_list.push('El dia abans del col·lapse'); 1336 1337 programs_names_list.push('¡No me digas!'); 1338 1339 programs_names_list.push('Tertulia deportiva'); 1340 1341 programs_names_list.push('Vicentines: les nostres referents'); 1342 1343 programs_names_list.push('Alerta alimentà ria'); 1344 1345 programs_names_list.push('Revolución Sexitosa'); 1346 1347 programs_names_list.push('Parlem de salut'); 1348 1349 programs_names_list.push('Dejando huella'); 1350 1351 programs_names_list.push('Rauxa'); 1352 1353 programs_names_list.push('Sintetitzades'); 1354 1355 programs_names_list.push('El Dia R'); 1356 1357 programs_names_list.push('La Timidez'); 1358 1359 programs_names_list.push('A les portes de Troia'); 1360 1361 programs_names_list.push('Veus i arrels'); 1362 1363 programs_names_list.push('Destruir la democrà cia'); 1364 1365 programs_names_list.push('Fes Ta Festa'); 1366 1367 programs_names_list.push('Obert x vacances'); 1368 1369 programs_names_list.push('El calaix'); 1370 1371 programs_names_list.push('L'hora del conte'); 1372 1373 programs_names_list.push('La caixa tonta'); 1374 1375 programs_names_list.push('Rambla, 54'); 1376 1377 programs_names_list.push('Això va en sèrie'); 1378 1379 programs_names_list.push('El món és digital'); 1380 1381 programs_names_list.push('Mosaic sonor'); 1382 1383 programs_names_list.push('La terra no és plana'); 1384 1385 programs_names_list.push('Gaudeix la festa'); 1386 1387 programs_names_list.push('Més enllà del prospecte'); 1388 1389 1390 const options = { 1391 includeScore: true, 1392 findAllMatches: true, 1393 }; 1394 1395let fuse; 1396Alpine.data('searchProgramsComponent', () => ({ 1397 search_input: '', 1398 results: [], 1399 selectOnAir: '1', 1400 1401 filterProgramsResults() { 1402 // console.log("filterProgramsResults"); 1403 // console.log(this.search_input); 1404 if (this.search_input.length < 3) { 1405 this.results = []; 1406 return; 1407 } 1408 if (!fuse) { 1409 // console.log("new Fuse"); 1410 // console.log(programs_names_list); 1411 fuse = new Fuse(programs_names_list, options); 1412 } 1413 this.results = fuse.search(this.search_input); 1414 // console.log(this.results); 1415 }, 1416 1417 show(program_name, onair) { 1418 let showOnAir = this.selectOnAir === '3'; 1419 if (this.selectOnAir === '1') { 1420 showOnAir = onair == 'True'; 1421 } 1422 if (this.selectOnAir === '2') { 1423 showOnAir = onair == 'False'; 1424 } 1425 1426 if (this.search_input.length < 3) { 1427 return showOnAir; 1428 } 1429 1430 const found = this.results.find(element => element.item === program_name && element.score < 0.5); 1431 1432 return found !== undefined && showOnAir; 1433 }, 1434})); 1435 1436 let mediaelementjs; 1437 let mediaelement; 1438 1439 1440Alpine.store('player', { 1441 now_playing_title: '', 1442 now_playing_image: '', 1443 now_playing_summary: '', 1444 player_status: 'stopped', 1445 now_playing_podcast_id: '', 1446 now_playing_marker: null, 1447 now_playing_markers: null, 1448 current_schedule: null, // current live schedule, NOT podcast 1449 timer: null, 1450 interval: null, 1451 elapsed_time: 0, 1452 remaining_time: 0, 1453 showPlayBtn: true, 1454 showPauseBtn: false, 1455 showStopBtn: false,
1456 showFwdBackBtn: false, 1457 interval_update_slider_non_playing_live: null, 1458 1459 clickedMarkerId: null, 1460 clickedMarkerAtMs: null, 1461 1462 slider_options_v4: null, 1463 slider_pips_options_v4: null, 1464 1465 init() { 1466 // 1467 this.init_slider(); 1468 1469 this.init_mp(); 1470 1471 if (!isEmbed || !podcast_embed) { 1472 this.update_now_playing(); 1473 } 1474 1475 if (isEmbed) { 1476 // console.log("isEmbed"); 1477 if (podcast_embed) 1478 console.log(podcast_embed); 1479 if (podcast_embed && !marker_embed) { 1480 this.set_podcast(podcast_embed); 1481 } 1482 if (marker_embed) { 1483 // this.playMarker(podcast_embed, marker_embed); 1484 this.set_podcast(podcast_embed, null, marker_embed['id']); 1485 } 1486 } 1487 }, 1488 init_slider() { 1489 this.slider_options_v4 = { 1490 min: 0, 1491 max: 100, 1492 value: 0, 1493 step: 1, 1494 range: "min", 1495 animate: "fast", // or "true" or "fast" 1496 stop: function (event, ui) { 1497 const per_one = ui.value / 100; 1498 const second = Alpine.store('player').getDuration() * per_one; 1499 Alpine.store('player').setCurrentTime(second); 1500 } 1501 }; 1502 1503 this.slider_pips_options_v4 = { 1504 first: "false", 1505 last: "false", 1506 rest: "false", 1507 labels: [], 1508 step: 1 1509 }; 1510 1511 $("#slider_nx").slider(this.slider_options_v4).slider("pips", this.slider_pips_options_v4); 1512 // clean "pips" 1513 $("#slider_nx span.ui-slider-label").each(function () { 1514 $(this).parent().remove(); 1515 }); 1516 }, 1517 1518 init_mp() { 1519 if (mediaelementjs) { 1520 return; 1521 } 1522 mediaelementjs = new MediaElementPlayer('enacast-player', { 1523 success: function (mediaElement, originalNode, instance) { 1524 // 1525 mediaelement = mediaElement; 1526 // 1527 // 1528 1529 mediaElement.addEventListener('timeupdate', function (e) { 1530 // 1531 // 1532 1533 // 1534 Alpine.store('player').update_podcast_current_percent_position_percent() 1535 1536 if (Alpine.store('player').interval_update_slider_non_playing_live === undefined) { 1537 clearInterval(this.interval_update_slider_non_playing_live); 1538 Alpine.store('player').interval_update_slider_non_playing_live = undefined; 1539 } 1540 1541 Alpine.store('player').updateSlider(); 1542 1543 // 1544 1545 1546 // TODO: save outside so we don't query all the time 1547 let slider_nx = $("#slider_nx"); 1548 1549 let podcast_id = this.now_playing_podcast_id; 1550 1551 // 1552 // 1553 1554 if (Alpine.store('player').now_playing_marker === null || Alpine.store('player').now_playing_marker === undefined) { 1555 // 1556 // 1557 // 1558 // 1559 if (Alpine.store('player').slider_pips_options_v4.labels.length === 0 && Alpine.store('player').now_playing_markers && Alpine.store('player').now_playing_markers.length > 0) { 1560 let labels = []; 1561 // 1562 for (let c in Alpine.store('player').now_playing_markers) { 1563 const m_percent = Math.round((Alpine.store('player').now_playing_markers[c].startsecond / Alpine.store('player').getDuration()) * 100); 1564 labels[m_percent] = Alpine.store('player').now_playing_markers[c].name; 1565 } 1566 Alpine.store('player').slider_pips_options_v4.labels = labels; 1567 // 1568 // console.log(Alpine.store('player').slider_pips_options_v4.labels); 1569 slider_nx.slider("pips", Alpine.store('player').slider_pips_options_v4); 1570 1571 // remove non-markers pips 1572 $("#slider_nx span.ui-slider-label").each(function () { 1573 if ($(this).text().length === 0) { 1574 $(this).parent().remove(); 1575 } else { 1576 // this is a marker, set alt, ant title, for tooltips 1577 const marker_title = $(this).text(); 1578 $(this).parent().prop("alt", marker_title); 1579 $(this).parent().prop("title", marker_title); 1580 // jQuery UI tooltips, TODO: we should run this on document also on every new page loaded? 1581 $(this).parent().tooltip(); 1582 } 1583 }); 1584 1585 } 1586 } 1587 1588 }, false); 1589 } 1590 }); 1591 }, 1592 1593 getCurrentSchedule: function () { 1594 return this.current_schedule; 1595 }, 1596 1597 updateSlider() { 1598 // 1599 let percent = this.getPercent(); 1600 if (percent === undefined) { 1601 return; 1602 } 1603 1604 // 1605 let slider_nx = $("#slider_nx"); 1606 1607 // 1608 // 1609 1610 // 1611 slider_nx.slider("value", percent); 1612 1613 if (this.getCurrentSchedule() !== undefined) { 1614 // Live 1615 slider_nx.slider("disable"); // this produces a more muted color than I like, but prevents "seeking" 1616 // remove handle 1617 $("#slider_nx span.ui-slider-handle").each(function () { 1618 $(this).addClass('d-none'); 1619 }); 1620 } else { 1621 // Podcast 1622 slider_nx.slider("enable"); // this produces a more muted color than I like, but prevents "seeking" 1623 $("#slider_nx span.ui-slider-handle").each(function () { 1624 $(this).removeClass('d-none'); 1625 }); 1626 } 1627 }, 1628 1629 getPercent() { 1630 let duration = this.getDuration(); 1631 //
1632 let currTime = this.getCurrentTime(); 1633 // 1634 if (this.now_playing_marker) { 1635 // A section without an end (docs/sections.md) runs to the next section, 1636 // else to the end of the episode: see markerEnd(). 1637 const end = this.markerEnd(this.now_playing_marker, this.now_playing_markers, duration); 1638 if (end !== null && this.getCurrentTime() > end) { 1639 this.stop(); 1640 } 1641 currTime = this.getCurrentTime() - this.now_playing_marker.startsecond; 1642 duration = (end !== null ? end : duration) - this.now_playing_marker.startsecond; 1643 1644 } 1645 return Math.round((currTime / duration) * 100); 1646 }, 1647 1648 setSrc(src_url) { 1649 // 1650 mediaelementjs.setSrc(src_url); 1651 }, 1652 1653 toggle_play_pause_podcast(podcast, pause_if_playing = true) { 1654 console.log("toggle_play_pause_podcast"); 1655 const urlParams = new URLSearchParams(window.location.search); 1656 const second = urlParams.get('s') || 0; 1657 if (pause_if_playing && this.is_playing() && podcast.id === this.getCurrentPodcastId()) { 1658 this.pause(); 1659 } else { 1660 this.play_podcast(podcast, second); 1661 } 1662 }, 1663 1664 // playPodcastWithConfirmation(podcast, second) { 1665 // console.log("playWithConfirmation"); 1666 // Swal.fire({ 1667 // title: 'Play podcast?', 1668 // // text: podcast.title, 1669 // icon: 'question', 1670 // showCancelButton: true, 1671 // confirmButtonText: 'Play', 1672 // cancelButtonText: 'Cancel', 1673 // confirmButtonColor: '#3085d6', 1674 // cancelButtonColor: '#d33' 1675 // }).then((result) => { 1676 // if (result.isConfirmed) { 1677 // console.log("playWithConfirmation confirmed", podcast, second); 1678 // this._play_podcast(podcast, second); 1679 // } 1680 // }); 1681 // }, 1682 1683 stop_timer() { 1684 if (this.timer) { 1685 clearInterval(this.timer); 1686 this.timer = null; 1687 } 1688 }, 1689 1690 pause() { 1691 // this.showPlay = true; 1692 this.showPlayBtn = true; 1693 this.showPauseBtn = false; 1694 mediaelement.pause(); 1695 try { 1696 this.stop_timer(); 1697 } catch (e) { 1698 1699 } 1700 this.player_status = "paused"; 1701 }, 1702 1703 is_playing() { 1704 return this.player_status === "playing_podcast" || this.player_status === "playing_live"; 1705 }, 1706 1707 getCurrentPodcastId() { 1708 return this.now_playing_podcast_id; 1709 }, 1710 1711 isPodcastPlaying(podcast_id) { 1712 if (this.player_status !== "playing_podcast") 1713 return false; 1714 return this.is_playing() && podcast_id === this.getCurrentPodcastId(); 1715 }, 1716 1717 playMarker(podcast, marker) { 1718 if (this.markerIsPlaying(marker)) { 1719 this.pause(); 1720 return; 1721 } 1722 1723 this.clickedMarkerId = marker['id']; 1724 // console.log("marker: ", marker['id']); 1725 this.clickedMarkerAtMs = new Date().getTime(); 1726 1727 if (!this.isPodcastPlaying(podcast['id'])) 1728 this.play_podcast(podcast, marker['startsecond']); 1729 else 1730 this.setCurrentTime(marker['startsecond']); 1731 1732 $("div.collapse").collapse('hide'); 1733 $("div#collapse_marker_" + marker['id']).collapse('show'); 1734 }, 1735 1736 playSearchTranscriptMoment(podcast, startsecond) { 1737 if (!this.isPodcastPlaying(podcast['id'])) 1738 this.play_podcast(podcast, startsecond); 1739 else 1740 this.setCurrentTime(startsecond); 1741 }, 1742 1743 play_podcast_at_time(podcast, secondsToPlay) { 1744 if (!this.isPodcastPlaying(podcast['id'])) 1745 this.play_podcast(podcast, secondsToPlay); 1746 else 1747 this.setCurrentTime(secondsToPlay); 1748 }, 1749 1750 markerIsPlaying(marker) { 1751 const marker_id = marker['id']; 1752 // console.log("marker_id: ", marker_id); 1753 // TODO: take into account end second on the future 1754 if (!this.isPodcastPlaying(this.now_playing_podcast_id)) 1755 return false; 1756 1757 // Reduce glitches (as getcurrent time has some lag): 1758 if (this.clickedMarkerId !== undefined) { 1759 // a marker have just been clicked, but it's not me 1760 if (this.clickedMarkerId !== marker_id && (new Date().getTime() - this.clickedMarkerAtMs) < 500) 1761 return false; 1762 // the marker has been clicked less than 500ms ago, so I'm playing 1763 if (this.clickedMarkerId === marker_id && (new Date().getTime() - this.clickedMarkerAtMs) < 500) 1764 return true; 1765 } 1766 1767 // This finds the first marker that matches the current time 1768 let start_seconds = [];
1769 let curr_time = this.getCurrentTime(); 1770 if (start_seconds.length === 0) { 1771 for (let i = 0; i < this.now_playing_markers.length; i++) { 1772 start_seconds.push(this.now_playing_markers[i].startsecond); 1773 } 1774 } 1775 1776 let largest = 0; 1777 1778 for (let i = 0; i <= start_seconds.length; i++) { 1779 if (start_seconds[i] > largest) { 1780 if (curr_time > start_seconds[i]) 1781 largest = start_seconds[i]; 1782 } 1783 } 1784 return largest === marker.startsecond; 1785 }, 1786 1787 toggle_play_pause() { 1788 if (this.player_status === "playing_podcast") { 1789 this.pause(); 1790 } else if (this.player_status === "paused") { 1791 this.play(); 1792 } else if (this.player_status === "playing_live") { 1793 this.stop(); 1794 } else if (this.player_status === "stopped") { 1795 this.play(); 1796 } 1797 }, 1798 1799 setTitle: function (title) { 1800 this.now_playing_title = title; 1801 }, 1802 1803 setSummary: function (summary) { 1804 this.now_playing_summary = summary; 1805 }, 1806 1807 setMarkers: function (markers) { 1808 this.slider_pips_options_v4.labels.length = 0; 1809 this.now_playing_markers = markers; 1810 // We don't have the podcast duration until we start playing, so we can't put the 1811 // "markers"/"pips" on the timeline until we have the duration 1812 }, 1813 1814 // Where a section stops, in seconds into the served audio. Same rule as the 1815 // backend's section_range() and the Astro site's markerPlaybackRange() 1816 // (docs/sections.md): the marker's own end when it is after its start, else 1817 // the start of the next section, else the episode's end (null while the 1818 // duration is unknown, so callers fall back to the audio's duration). 1819 markerEnd(marker, markers, duration) { 1820 const start = marker.startsecond || 0; 1821 if (typeof marker.endsecond === "number" && marker.endsecond > start) 1822 return marker.endsecond; 1823 let end = null; 1824 for (const m of markers || []) { 1825 const s = m.startsecond || 0; 1826 if (s > start && (end === null || s < end)) 1827 end = s; 1828 } 1829 if (end === null && duration > 0) 1830 end = duration; 1831 return end; 1832 }, 1833 1834 set_podcast: function (podcast, second = null, marker_id = null) { 1835 this.setSrc(podcast.audio); 1836 if (marker_id !== null) { 1837 const marker = podcast.markers.filter(obj => obj.id === marker_id)[0]; 1838 this.setTitle(marker.name + " (" + podcast.name + ")"); 1839 // TODO: limit play from start second to end second of the marker 1840 // (and also shown timers, that we don't have, yet) 1841 // TODO: 1842 // this.setDescription(marker.descr); 1843 second = marker.startsecond; 1844 1845 console.log("marker", marker); 1846 this.now_playing_image = marker.image; 1847 this.now_playing_marker = marker; 1848 this.now_playing_markers = podcast.markers; 1849 } else { 1850 this.setTitle(podcast.title); 1851 this.setSummary(podcast.summary); 1852 this.setMarkers(podcast.markers); 1853 } 1854 1855 // TODO: htmx 1856 // if (second === null) { 1857 // second = EnaCastLocalDataBase.get_podcast_current_position_secs(podcast.id); 1858 // if (second !== 0) { 1859 // $('.toast').toast('show'); 1860 // } 1861 // } 1862 1863 if (second) { 1864 // this is needed for safari, if we don't wait until it start playing it doesn't jump to the second 1865 let tmp_listener = mediaelement.addEventListener("playing", function tmp_func() { 1866 mediaelement.removeEventListener("playing", tmp_func); 1867 // this.setCurrentTime(second); // this is null here 1868 mediaelement.setCurrentTime(second); 1869 }); 1870 1871 } 1872 if (marker_id == null) { 1873 this.now_playing_image = podcast.program_logo_400x400; 1874 } 1875 this.now_playing_podcast = podcast; 1876 this.now_playing_podcast_id = podcast.id; 1877 1878 this.current_schedule = undefined; 1879 this.next_playing_program = undefined; 1880 this.next_playing_title = undefined; 1881 this.next_playing_summary = undefined; 1882 this.next_playing_image = undefined; 1883 }, 1884 1885 play_podcast(podcast, second = 0) { 1886 this._play_podcast(podcast, second); 1887 // canAutoplay.audio().then(({result}) => { 1888 // if (result === true) { 1889 // this._play_podcast(podcast, second); 1890 // } else { 1891 // this.playPodcastWithConfirmation(podcast, second); 1892 // } 1893 // }); 1894 }, 1895 1896 _play_podcast(podcast, second = 0) { 1897 // 1898 // 1899 // 1900 this.set_podcast(podcast, second); 1901 1902 // 1903 this.player_status = "playing_podcast"; 1904 // start interval to update time and remaining time 1905 1906 1907 // 1908 if (second !== undefined) { 1909 // this is needed for safari, if we don't wait until it start playing it doesn't jump to the second 1910 mediaelement.addEventListener("playing", function tmp_func() { 1911 mediaelement.removeEventListener("playing", tmp_func); 1912 mediaelement.setCurrentTime(second); 1913 }); 1914 } 1915 this.play(); 1916 }, 1917 1918 play() { 1919 // 1920 // 1921 // 1922 // 1923 // 1924 // 1925 // 1926 // 1927 // 1928 1929 // 1930 if (!this.now_playing_podcast_id) { 1931 this.setSrc("https://enacast.com/radiosantvicenc/streams/SD"); 1932 this.setLiveInfo(); 1933 } else { 1934 this.player_status = "playing_podcast"; 1935 this.timer = setInterval(() => { 1936 this.elapsed_time = this.getCurrentTimeHuman(); 1937 this.remaining_time = this.getRemainingTimeHuman(); 1938 }, 1000); 1939 } 1940 1941 this.showPlayBtn = false;
1942 this.showPauseBtn = this.player_status === "playing_podcast"; 1943 this.showStopBtn = true; 1944 this.showFwdBackBtn = this.player_status === "paused" || this.player_status === "playing_podcast"; 1945 1946 mediaelementjs.play(); 1947 1948 // 1949 1950 // 1951 // 1952 // 1953 // 1954 // 1955 // 1956 // 1957 // 1958 // 1959 }, 1960 1961 playBack: function (seconds) { 1962 mediaelementjs.setCurrentTime(mediaelementjs.currentTime - seconds); 1963 }, 1964 1965 playForward: function (seconds) { 1966 mediaelementjs.setCurrentTime(mediaelementjs.currentTime + seconds); 1967 }, 1968 1969 stop() { 1970 this.showPlayBtn = true; 1971 this.showPauseBtn = false; 1972 this.showStopBtn = false; 1973 this.showFwdBackBtn = false; 1974 1975 this.player_status = "stopped"; 1976 1977 try { 1978 this.stop_timer(); 1979 } catch (e) { 1980 } 1981 mediaelementjs.pause(); 1982 if (this.now_playing_marker) 1983 this.setCurrentTime(this.now_playing_marker.startsecond); 1984 else 1985 this.setCurrentTime(0); 1986 1987 if (!isEmbed) { 1988 mediaelementjs.setSrc("https://"); 1989 1990 this.setLiveInfo(); 1991 } 1992 }, 1993 1994 setCurrentTime(second) { 1995 mediaelement.setCurrentTime(second); 1996 }, 1997 1998 setLiveInfo: function () { 1999 this.player_status = "playing_live"; 2000 this.now_playing_podcast_id = ""; 2001 this.now_playing_podcast = undefined; 2002 // this.now_playing_title = radio.name; 2003 this.now_playing_summary = ""; 2004 this.now_playing_markers = ""; 2005 this.now_playing_marker = undefined; 2006 2007 // this.now_playing_image = radio.logo_square_256; 2008 2009 this.current_schedule = undefined; 2010 2011 this.next_playing_program = undefined; 2012 this.next_playing_title = undefined; 2013 this.next_playing_summary = undefined; 2014 this.next_playing_image = undefined; 2015 2016 this.update_now_playing(); 2017 }, 2018 2019 getDuration() { 2020 // 2021 // 2022 // 2023 // 2024 // 2025 // 2026 if (isNaN(mediaelement.duration)) { 2027 return 0; 2028 } 2029 return mediaelement.duration; 2030 }, 2031 2032 getCurrentTime: function () { 2033 if (this.current_schedule) { 2034 // Live 2035 let d = new Date(); 2036 let total_seconds = 0; 2037 total_seconds += (d.getHours() - this.current_schedule.start_time_local.split(":")[0]) * 3600; 2038 total_seconds += (d.getMinutes() - this.current_schedule.start_time_local.split(":")[1]) * 60; 2039 total_seconds += (d.getSeconds() - this.current_schedule.start_time_local.split(":")[2]); 2040 return total_seconds; 2041 } 2042 2043 if (!mediaelement) return; 2044 // Podcast 2045 return mediaelement.currentTime; 2046 }, 2047 2048 getCurrentTimeHuman: function () { 2049 const s = this.getCurrentTime(); 2050 if (s < 3600) 2051 return new Date(s * 1000).toISOString().substring(14, 19); 2052 else 2053 return new Date(s * 1000).toISOString().substring(11, 19); 2054 }, 2055 2056 getRemainingTimeHuman: function () { 2057 const s = this.getDuration() - this.getCurrentTime(); 2058 if (s < 3600) 2059 return "-" + new Date(s * 1000).toISOString().substring(14, 19); 2060 else 2061 return "-" + new Date(s * 1000).toISOString().substring(11, 19); 2062 }, 2063 2064 get_podcast_current_percent_position_percent(podcast_id) { 2065 if (typeof (Storage) !== "undefined") { 2066 const pos_list_dict = localStorage.getItem("current_percent_position_podcasts"); 2067 if (pos_list_dict) { 2068 const pos = JSON.parse(pos_list_dict)[podcast_id]; 2069 if (pos) { 2070 return pos; 2071 } 2072 } 2073 return 0; 2074 } else { 2075 // Sorry! No Web Storage support... 2076 } 2077 }, 2078 2079 update_podcast_current_percent_position_percent() { 2080 if (typeof (Storage) !== "undefined") { 2081 let pos_list_dict = localStorage.getItem("current_percent_position_podcasts"); 2082 if (pos_list_dict) { 2083 pos_list_dict = JSON.parse(pos_list_dict); 2084 } else { 2085 pos_list_dict = {}; 2086 } 2087 pos_list_dict[this.now_playing_podcast_id] = this.getPercent(); 2088 localStorage.setItem("current_percent_position_podcasts", JSON.stringify(pos_list_dict)); 2089 } else { 2090 // Sorry! No Web Storage support... 2091 } 2092 }, 2093 2094 update_now_playing() { 2095 let url = `/api/v3/now_playing/?page_size=100&radio=radiosantvicenc`; 2096 fetch(url) 2097 .then(response => response.json()) 2098 .then(data => { 2099 this.now_playing_title = data.current_program.name; 2100 this.now_playing_image = data.current_program.logo_square_400; 2101 2102 if (this.now_playing_title === undefined || this.now_playing_title === null || this.now_playing_title === "") { 2103 this.setTitle(radio.name); 2104 } 2105 2106 if (this.now_playing_image === undefined || this.now_playing_image === null || this.now_playing_image === "") { 2107 this.now_playing_image = radio.logo_square_256; 2108 } 2109 }); 2110 } 2111 2112}); 2113 2114 }); 2115</script>
2115 2116 2117 2118
2119<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js"></script>
vendor: 1 bytes, line 2119
2119
2120<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
vendor: 1 bytes, line 2120
2120
2121<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/can-autoplay.min.js" integrity="sha256-h/0BsGoRtodhD+FDU7WeqveJ0PM9gPDX++eE+M2eaZ4=" crossorigin="anonymous"></script>
2121 2122
2123<script type="text/javascript"> 2124 2125 2126 const enacast_template = "enacast-nx-htmx"; 2127 const radio = {"codename": "radiosantvicenc", "name": "R\u00e0dio Sant Vicen\u00e7", "language": "ca", "custom_domain": "www.radiosvh.info", "streams": [{"name": "56k standard stream", "bitrate": 64, "host": "streaming.enacast.com", "port": 8000, "mount_point": "stvicenc56.mp3", "url": "https://streaming.enacast.com:8000/stvicenc56.mp3", "ssl": false, "enacast_managed_stream": true}, {"name": "128k standard stream", "bitrate": 128, "host": "streaming.enacast.com", "port": 8000, "mount_point": "stvicenc128.mp3", "url": "https://streaming.enacast.com:8000/stvicenc128.mp3", "ssl": false, "enacast_managed_stream": true}], "contact_mail": "", "phone_number_whatsapp": "", "twitter": "https://twitter.com/radiosvh902", "facebook": "https://www.facebook.com/radiosantvicenc", "youtube": "http://www.youtube.com/user/radiosvh902/", "instagram": "https://www.instagram.com/radiosvh902/", "tiktok": "", "linkedin": "", "default_stream": "https://enacast.com/radiosantvicenc/streams/HD", "podcast_service": true, "has_news": false, "direccio": "Ca l'Aragall, c/ Barcelona 16-18 primer pis 08620, Sant Vicen\u00e7 dels Horts", "active": true, "latitude": 41.398456, "primary_color": "#FF8601", "dark_theme": false, "longitude": 2.007947, "send_stream_alert_enacast_admins": false, "send_stream_alert_radio_contacts": true, "logo_radio": "https://enacast.com/media/logos/2015/10/13/logo_santvi_W6x5uzw.png", "logo_radio_square": "https://enacast.com/media/logos/2022/02/11/icon_8YNoPw6.png", "logo_square_256": "https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-256,h-None,fo-auto", "logo_square_128": "https://ik.imagekit.io/7ftrkrun31/enacast/logos/2022/02/11/icon_8YNoPw6.png?tr=w-128,h-None,fo-auto", "time_zone": "Europe/Madrid", "android_app_id": "", "ios_app_id": "", "cover_big_landscape": "https://ik.imagekit.io/7ftrkrun31/enacast/radio_covers/2022/02/11/COVER.jpg?tr=w-1300,h-250,fo-auto", "cover_orig": "https://enacast.com/media/radio_covers/2022/02/11/COVER.jpg", "cover": "https://enacast.com/media/cache/a5/07/a50741e4c75069d377a24b2c0a4136e7.jpg", "now_playing_program_codename": "", "now_playing_program_id": null, "analytics_web_tracking_id": null, "matomo_internal_web_tracking_id": 10, "glossary": "R\u00e0dio Sant Vicen\u00e7: name of the radio\nR\u00e0dio Sant Vicen\u00e7: emissora municipal de r\u00e0dio p\u00fablica de Sant Vicen\u00e7 dels Horts\nSant Vicen\u00e7 dels Horts: municipi del Baix Llobregat on opera l'emissora\nAula dial: projecte educatiu de r\u00e0dio adre\u00e7at a escolars del municipi\nCa l'Aragall: edifici c\u00e8ntric que allotja la seu actual de la r\u00e0dio\nBaix Llobregat: comarca catalana a la qual pertany el municipi\nServei Educatiu del Baix Llobregat-6: servei de la Generalitat col\u00b7laborador del projecte Aula dial\nPremi el CAC a l'Escola: guard\u00f3 del Consell Audiovisual atorgat al projecte Aula dial\nPremis de Comunicaci\u00f3 Local: distinci\u00f3 de la Diputaci\u00f3 de Barcelona guanyada per la r\u00e0dio\nFesta Major d'Hivern: festivitat local en qu\u00e8 es va inaugurar oficialment l'emissora\nMostra Comercial i Agr\u00edcola: fira anual vicentina des d'on s'emet en directe\nLa Pepada: primer programa em\u00e8s per la r\u00e0dio l'any 1982\nPep Ramoneda: locutor hist\u00f2ric i un dels creadors del programa La Pepada\nPep Romero: co-presentador de l'espai fundacional d'entreteniment La Pepada\nPepi Meca: locutora hist\u00f2rica i co-conductora del programa inaugural La Pepada\nGolfes de l'Ajuntament: primer espai municipal on es van instal\u00b7lar els estudis\n90.2 FM: freq\u00fc\u00e8ncia del dial des d'on emet actualment l'emissora\n101.2 FM: primer dial de freq\u00fc\u00e8ncia utilitzat per R\u00e0dio Sant Vicen\u00e7\n103.9 FM: dial utilitzat per l'emissora municipal a partir de 1985\n90.9 FM: freq\u00fc\u00e8ncia utilitzada per la r\u00e0dio vicentina des de 1993\nSom un poble: programa cultural pioner condu\u00eft per les primeres col\u00b7laboradores locals\nPaquita Pahissa: pionera de l'emissora i conductora del programa Som un poble\nImmaculada Caralt: comunicadora local i co-conductora hist\u00f2rica de Som un poble\nRosa Maria Caralt: col\u00b7laboradora fundacional del programa cultural Som un poble\nLos Petetes de la radio: espai infantil pioner em\u00e8s en els primers anys\nEncarni Vela: locutora i directora del programa infantil Los Petetes de la radio\nPilar Capar\u00e0: col\u00b7laboradora hist\u00f2rica encarregada de la conducci\u00f3 de programes musicals\nArcadi Tuset: locutor de la primera \u00e8poca especialitzat en espais musicals\nJosep Anton Ramoneda: conductor de programes musicals en la fase inicial de la r\u00e0dio\nEmissores Municipals de Catalunya: associaci\u00f3 de r\u00e0dios a la qual es va incorporar el 1985\nAntics Salesians: edifici municipal que va allotjar els estudis entre 1990 i 2016\nInformatiu Comarcal: programa col\u00b7laboratiu entre emissores municipals del Baix Llobregat\
2127nPla Educatiu d'Entorn: marc comunitari de cooperaci\u00f3 educativa que inclou Aula dial\nRecerques: programa radiof\u00f2nic premiat sobre treballs de recerca de secund\u00e0ria\nPremis R\u00e0dio Associaci\u00f3: reconeixement d'\u00e0mbit catal\u00e0 atorgat al programa local Recerques\nCap infant sense joguina: campanya solid\u00e0ria anual de Nadal amb marat\u00f3 radiof\u00f2nica\nEureka!: programa de COM R\u00e0dio que va transmetre des de la vila\nCOM R\u00e0dio: xarxa radiof\u00f2nica que va participar en el 30\u00e8 aniversari local\nPla\u00e7a de la Vila: espai p\u00fablic c\u00e8ntric utilitzat per a transmissions radiof\u00f2niques exteriors\nCarrer Barcelona: carrer c\u00e8ntric on se situa la seu de Ca l'Aragall\nPrograma de Barris: espai informatiu especial dedicat a les associacions de ve\u00efns\nLa Xarxa de Comunicaci\u00f3 Local: plataforma col\u00b7laboradora en cobertures electorals i informatives\nBlocs electorals: quotes pol\u00edtiques de temps rebutjades per defensar criteris period\u00edstics professionals\nLOREG: llei org\u00e0nica reguladora del r\u00e8gim electoral a l'Estat espanyol\nTN de TV3: telenot\u00edcies que va destacar la cobertura electoral independent de la r\u00e0dio\nRegidoria d'Educaci\u00f3: \u00e0rea de l'Ajuntament impulsora de projectes escolars com Aula dial\nVicent\u00ed: gentilici referit a la ciutadania o entitats de Sant Vicen\u00e7 dels Horts\nControl de so: espai t\u00e8cnic equipat per a la mescla i emissi\u00f3 sonora\nGraella: planificaci\u00f3 peri\u00f2dica dels programes emesos per l'estaci\u00f3 de r\u00e0dio\nEstudi de gravaci\u00f3: sala insonoritzada i equipada per a la producci\u00f3 de continguts\nPle municipal: sessi\u00f3 plen\u00e0ria de l'Ajuntament que la r\u00e0dio transmet en directe", "podcast_summary_preference": "long", "default_title_template": "ai_or_default", "in_trial_period": false, "trial_start_date": null, "holidays": false, "on_holidays_today": false, "next_holiday_period": null, "pre_roll_intro": null, "show_chat": true, "allow_downloads": true, "allow_rss": true, "sound_archive_enabled": true, "sound_archive_default_view": "chronology", "module_videos_youtube": false, "module_ads": false, "titol_fora_programacio": "R\u00e0dio Sant Vicen\u00e7 90.2 fm", "descripcio_fora_programacio": "Esteu escoltant en directe R\u00e0dio Sant Vicen\u00e7 90.2 fm", "has_insights_pro": false, "custom_llm_context": "", "terminology": {}, "needs_onboarding": false, "web_url": "http://radio.santvidigital.cat/", "created_via": "admin"}; 2128 const default_cover = "https://enacast.com/media/frontend_config_covers/radiosantvicenc/2022/7/7/2022-7-7_1657194224..jpg"; 2129 2130 // const isDebug = true; 2131 const isDebug = false; 2132 2133 const isProd = true; 2134 const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); 2135 2136 const mapbox_public_key = "pk.eyJ1Ijoib3Jpb2xlbmFjYXN0IiwiYSI6ImNrZmNnOTRodjFmaDYyc3FzdGUxbmVocXkifQ.6BPyoMjBEfn-v48e_Mc3HA"; 2137 2138 let base_url = "https://enacast.com/" + radio.codename; 2139 if (radio.custom_domain != undefined && radio.custom_domain.length > 4) 2140 base_url = "https://" + radio.custom_domain; 2141 2142 const window_width = $(window).width(); 2143 const standalone = window.navigator.standalone, 2144 userAgent = window.navigator.userAgent.toLowerCase(), 2145 safari = /safari/.test(userAgent) && !isChrome, 2146 android = /android/.test(userAgent), 2147 ios = /iphone|ipod|ipad/.test(userAgent), 2148 mobile = android | ios, 2149 isEmbed = 0 === 1; 2150 moment.locale(radio.language); 2151 2152 // redirect to custom domain if radio has one 2153 if (radio.custom_domain !== undefined && window.location.hostname.indexOf(radio.custom_domain) === -1 && isProd) { 2154 let target_url = window.location.href; 2155 target_url = target_url.replace("www.", ""); 2156 const base_custom_domain = radio.custom_domain.replace("www.", ""); 2157 2158 // multiple possible urls to rewrite (order important to avoid issues): 2159 target_url = target_url.replace(base_custom_domain + "/" + radio.codename, radio.custom_domain); 2160 target_url = target_url.replace(base_custom_domain, radio.custom_domain); 2161 target_url = target_url.replace("enacast.com/" + radio.codename, radio.custom_domain); 2162 2163 // console.log("radio codename 0 to: " + radio.codename); 2164 // console.log("radio custom domain 0 to: " + radio.custom_domain); 2165 // console.log("redirecting 0 to: " + target_url); 2166 window.location = target_url; 2167 } 2168 2169 2170</script>
2170 2171 2172
2173<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
2173 2174
2175<script src="https://cdnjs.cloudflare.com/ajax/libs/mediaelement/4.2.16/mediaelement-and-player.min.js" 2176 integrity="sha256-RDugr3p+2CciPH+zwAjAK5/x1lG2SS6cJwN4sH2fYAg=" crossorigin="anonymous" defer></script>
2176 2177
2178<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" 2179 integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" 2180 crossorigin="anonymous"></script>
vendor: 1 bytes, line 2180
2180
2181<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-ui-Slider-Pips/1.11.4/jquery-ui-slider-pips.min.js" 2182 integrity="sha256-OL8tV6ONTtyNVwcLNx0HfV0RVvreW1rvBCu/Nv2zIxo=" crossorigin="anonymous"></script>
2182 2183
2184<script src="https://cdnjs.cloudflare.com/ajax/libs/headroom/0.11.0/headroom.min.js" 2185 integrity="sha256-AsUX4SJE1+yuDu5+mAVzJbuYNPHj/WroHuZ8Ir/CkE0=" crossorigin="anonymous"></script>
2185 2186
2187<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js" 2188 integrity="sha512-2R4VJGamBudpzC1NTaSkusXP7QkiUYvEKhpJAxeVCqLDsgW4OqtzorZGpulE3eEA7p++U0ZYmqBwO3m+R2hRjA==" 2189 crossorigin="anonymous" defer ></script>
vendor: 1 bytes, line 2189
2189
2190<script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js" 2191 integrity="sha512-SxO0cwfxj/QhgX1SgpmUr0U2l5304ezGVhc0AO2YwOQ/C8O67ynyTorMKGjVv1fJnPQgjdxRz6x70MY9r0sKtQ==" 2192 crossorigin="anonymous" defer></script>
2192 2193
2194<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.min.js" defer></script>
2194 2195 2196 2197
2198<script 2199 src="https://browser.sentry-cdn.com/7.34.0/bundle.tracing.min.js" 2200 integrity="sha384-3w0Bmtwehbv8MH8akckH78UJlX4muadjYSzwTQYYgOp4T96F/25itcN5r0oijRU+" 2201 crossorigin="anonymous"></script>
2201 2202
2203<script> 2204 Sentry.init({ 2205 dsn: "https://[email protected]/4504595370606592", 2206 2207 release: "EnaCastFrontend_PROD@63fdb072457bb2c9ce440be42c1f69b0f2d01e1b", 2208 integrations: [new Sentry.BrowserTracing()], 2209 tracesSampleRate: 0.0001, 2210 }); 2211</script>
2211 2212
2213<script> 2214 2215 navigator.serviceWorker.getRegistrations().then(function(registrations) { 2216 for(let registration of registrations) { 2217 registration.unregister(); 2218 } 2219 }); 2220 2221 2222 for (let key in localStorage){ 2223 if (key.startsWith("ngStorage-")){ 2224 localStorage.removeItem(key); 2225 } 2226 } 2227</script>
2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241
2242<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
vendor: 1 bytes, line 2242
2242
2243<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ext/debug.js"></script>
vendor: 1 bytes, line 2243
2243
2244<script src="https://cdn.jsdelivr.net/npm/[email protected]/preload.js"></script>
vendor: 1 bytes, line 2244
2244
2245<script src="https://cdn.jsdelivr.net/npm/[email protected]/head-support.js"></script>
2245 2246
2247<script defer src="https://unpkg.com/[email protected]"></script>
2247 2248 2249 2250
2251<script defer src="https://cdn.jsdelivr.net/npm/typesense@1/dist/typesense.min.js"></script>
vendor: 1 bytes, line 2251
2251
2252<script defer src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.basic.min.js"></script>
2252 2253 2254 2255
2256<script src="https://cdn.jsdelivr.net/npm/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
2256 2257 2258 2259 2260
2261<script src="https://cdn.jsdelivr.net/npm/[email protected]/nprogress.js"></script>
vendor: 1 bytes, line 2261
2261
2262<script> 2263 document.addEventListener('htmx:beforeRequest', function (event) { 2264 NProgress.start(); 2265 }); 2266 document.addEventListener('htmx:afterRequest', function (event) { 2267 NProgress.done(); 2268 }); 2269 2270</script>
2270 2271
2272<script defer src="https://enacast.com/static/social_shares.js"></script>
vendor: 1 bytes, line 2272
2272
2273<script defer src="https://enacast.com/static/third_party/clipboard/clipboard.js"></script>
2273 2274 2275 2276 2277 2278</body> 2279</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.