1<!DOCTYPE html> 2<html lang="pt-BR"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> 7 <title>Nossa Equipe - Rádio 93 FM â Rádio 93 FM</title> 8 <meta name="description" content="A primeira em Porto Feliz!"> 9 <meta name="keywords" content="radio, online, musica, streaming"> 10 <meta name="robots" content="index, follow"> 11 12 <link rel="canonical" href="https://radio93portofeliz.com.br/equipe"> 13 14 <!-- Open Graph (WhatsApp, Facebook, Telegram, LinkedIn, Discord, Teams, iMessage) --> 15 <meta property="og:type" content="website"> 16 <meta property="og:title" content="Nossa Equipe - Rádio 93 FM"> 17 <meta property="og:description" content="A primeira em Porto Feliz!"> 18 <meta property="og:url" content="https://radio93portofeliz.com.br/equipe"> 19 <meta property="og:site_name" content="Rádio 93 FM"> 20 <meta property="og:locale" content="pt_BR"> 21 <meta property="og:image" content="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 22 <meta property="og:image:secure_url" content="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 23 <meta property="og:image:alt" content="Nossa Equipe - Rádio 93 FM"> 24 <meta property="og:image:width" content="512"> 25 <meta property="og:image:height" content="512"> 26 <meta property="og:image:type" content="image/png"> 27 28 <!-- Twitter / X --> 29 <meta name="twitter:card" content="summary_large_image"> 30 <meta name="twitter:title" content="Nossa Equipe - Rádio 93 FM"> 31 <meta name="twitter:description" content="A primeira em Porto Feliz!"> 32 <meta name="twitter:image" content="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 33 <meta name="twitter:image:alt" content="Nossa Equipe - Rádio 93 FM"> 34 35 <!-- LCP Preload (Logo) com WebP fallback --> 36 <link rel="preload" as="image" href="https://radio93portofeliz.com.br/uploads/webp/media/6a05dc8e13c73_1778769038.webp"> 37 38 <link rel="stylesheet" href="https://radio93portofeliz.com.br/themes/neon/style.css?v=1790901861"> 39 40 41 <!-- Custom CSS (Admin) --> 42 43 <!-- External Fonts (Google Fonts) --> 44 <link rel="preconnect" href="https://fonts.googleapis.com"> 45 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 46 <link 47 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@400;500;700&family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,400&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,700;1,9..40,400&display=swap" 48 rel="stylesheet"> 49 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> 50 51 <!-- Lucide Icons (Ajuste 8 â Ãcones modernos) --> 52
52<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js" defer></script>
52 53 54 <!-- Global CSS â Seções e Cookie Banner (Ajustes 6 e 3) --> 55 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/section-header.css?v=1790901861"> 56 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/cookie-banner.css?v=1790901861"> 57 <!-- Global Slider CSS â carregado para todos os temas --> 58 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/slider.css?v=1784221298"> 59 <!-- Top Músicas: marquee de tÃtulo/artista. Depois do CSS de tema de 60 propósito â precisa vencer o `text-overflow: ellipsis` dos temas. --> 61 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/chart-marquee.css?v=1790785403"> 62 <!-- App Rádio 3.0: camada de segurança escopada em html.radio30-app. Carregada 63 sempre, porque a classe também é injetada pelo próprio app â cobre o HTML 64 servido sem a detecção no servidor (cache, service worker). --> 65 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/radio30-app.css?v=1790785403"> 66 67 <!-- Favicon --> 68 <link rel="icon" type="image/png" sizes="32x32" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 69 <link rel="shortcut icon" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 70 71 <!-- PWA Meta Tags --> 72 <link rel="manifest" href="/?manifest=1"> 73 <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> 74 <meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)"> 75 <meta name="theme-color" content="#ffffff"> 76 77 <meta name="mobile-web-app-capable" content="yes"> 78 <meta name="apple-mobile-web-app-capable" content="yes"> 79 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> 80 <meta name="apple-mobile-web-app-title" content="Rádio 93 FM"> 81 82 <!-- Apple Touch Icons â usa o Ãcone de Configurações Gerais; PWA como fallback --> 83 <link rel="apple-touch-icon" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 84 <link rel="apple-touch-icon" sizes="152x152" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 85 <link rel="apple-touch-icon" sizes="180x180" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 86 <link rel="apple-touch-icon" sizes="167x167" href="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"> 87 88
88<script> 89 const SITE_URL = "https://radio93portofeliz.com.br"; 90 const STREAM_URL = ""; /* carregado via /api/stream-config.php */ 91 const SITE_NAME = "Rádio 93 FM"; 92 const PLAYER_DEFAULT_IMAGE = "https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png"; 93</script>
93 94 95<!-- Font Awesome --> 96<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> 97<!-- Material Icons + Symbols (Stitch uses Material Symbols Outlined) --> 98<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet"> 99<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,[email protected],0..1&display=swap" rel="stylesheet"> 100<!-- Space Grotesk â Fonte principal do design Stitch NEON --> 101<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet"> 102 103<link rel="preconnect" href="https://fonts.googleapis.com"> 104<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 105<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Poppins:wght@300;400;500;600;700;800&family=Space+Grotesk:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> 106 <!-- Dynamic Theme CSS â carregado APÃS o head.php do tema para garantir que o admin sempre prevaleça --> 107 <style> 108 @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); 109 110 :root { 111 --primary-color: #ff9500; 112 --secondary-color: #234783; 113 --tertiary-color: #234783; 114 --accent-color: #ff00ea; 115 --primary-rgb: 255, 149, 0; 116 --secondary-rgb: 35, 71, 131; 117 --tertiary-rgb: 35, 71, 131; 118 --accent-rgb: 255, 0, 234; 119 /* Aliases semânticos */ 120 --color-primary: #ff9500; 121 --color-secondary: #234783; 122 --color-tertiary: #234783; 123 } 124 125 :root { 126 /* Light Theme - Background Colors */ 127 --bg-primary: #ffffff; 128 --bg-primary-rgb: 255, 255, 255; 129 --bg-secondary: #f5f5f7; 130 --bg-secondary-rgb: 245, 245, 247; 131 --bg-tertiary: #e9ecef; 132 --bg-card: #ffffff; 133 --bg-card-rgb: 255, 255, 255; 134 --bg-card-hover: #f8f9fa; 135 --bg-input: #ffffff; 136 137 /* === Header/Nav background (sincronizado com theme_color do PWA) === */ 138 --site-header-bg: #ffffff; 139 --header-bg: var(--site-header-bg); 140 141 --glass-bg: rgba(255, 255, 255, 0.95); 142 --glass-border: rgba(0, 0, 0, 0.08); 143 --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); 144 145 --text-primary: #1a1a2e; 146 --text-primary-rgb: 26, 26, 46; 147 --text-secondary: #4a4a5a; 148 --text-muted: #6c757d; 149 150 --border-color: rgba(0, 0, 0, 0.08); 151 --border-light: rgba(0, 0, 0, 0.05); 152 153 --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06); 154 --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08); 155 --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1); 156 --shadow-glow: 0 0 30px rgba(255, 149, 0, 0.12); 157 158 --gradient-dark: linear-gradient(180deg, #ffffff 0%, #f5f5f7 100%); 159 160 /* === WAVE Brutalism â Borda/Sombra (Light) === */ 161 --brut-border-color: #000000; 162 --brut-border-light: #333333; 163 --brut-shadow-color: #000000; 164 --brut-shadow-hover: #000000; 165 } 166 167 :root { 168 --nav-font-family: 'Inter', sans-serif; 169 --nav-font-size: 14px; 170 --nav-font-weight: normal; 171 --nav-font-style: normal; 172 --nav-color: #111111; 173 } 174 175 .nav, .nav-link, .header nav, .header .nav-link, nav a, 176 #mainNav a, #mainNav a.nav-link { 177 font-family: var(--nav-font-family); 178 font-size: var(--nav-font-size); 179 font-weight: var(--nav-font-weight); 180 font-style: var(--nav-font-style); 181 color: var(--nav-color); 182 } 183 184 :root { 185 --page-title-font-family: 'Inter', sans-serif;
186 --page-title-font-weight: bold; 187 --page-title-font-style: normal; 188 --page-title-color: #1a1a1a; 189 --page-title-font-size-base: 39px; 190 191 /* Aliases legados â mantém compatibilidade com head.php dos temas */ 192 --title-font-family: 'Inter', sans-serif; 193 --title-font-weight: bold; 194 --title-font-style: normal; 195 --title-color: #1a1a1a; 196 --title-font-size-base: 39px; 197 } 198 199 /* === FONTE DOS TÃTULOS DAS PÃGINAS (somente front-end) === */ 200 body h1, body h2, body h3, body h4, body h5, body h6, 201 body .section-title, body .page-title, body .pd-page-hero__title, 202 /* Single pages â tÃtulos principais de página */ 203 body .ns-title, 204 body .pr-title, 205 body .ep-title, 206 body .on-air-title, 207 body .page-hero__title { 208 font-family: var(--page-title-font-family) !important; 209 font-weight: var(--page-title-font-weight) !important; 210 font-style: var(--page-title-font-style) !important; 211 color: var(--page-title-color) !important; 212 } 213 body h1, body .ns-title, body .pr-title, body .ep-title, body .on-air-title, body .page-hero__title { font-size: var(--page-title-font-size-base) !important; } 214 body h2 { font-size: calc(var(--page-title-font-size-base) * 0.85) !important; } 215 body h3 { font-size: calc(var(--page-title-font-size-base) * 0.7) !important; } 216 body h4, body h5, body h6 { font-size: calc(var(--page-title-font-size-base) * 0.6) !important; } 217 218 :root { 219 --card-title-font-family: 'Inter', sans-serif; 220 --card-title-font-weight: bold; 221 --card-title-font-style: normal; 222 --card-title-color: #222222; 223 --card-title-font-size: 20px; 224 } 225 226 /* === FONTE DOS CARDS (home + páginas internas + single pages) (somente front-end) === */ 227 /* ââ NotÃcias ââ */ 228 body .hn-card__title, body .hn-hero__title, body .d5-news-card__title, 229 body .d5-news-featured__title, body .s-news-card__title, body .news-title, 230 /* ââ Sidebar de notÃcia single ââ */ 231 body .ns-sidebar-card-title, body .ns-sidebar-title, 232 body .pulse-sidebar-card-title, body .pulse-sidebar-title, 233 /* ââ Eventos ââ */ 234 body .d5-event-card__title, body .s-event-card__title, body .event-title, 235 /* ââ Promoções ââ */ 236 body .d5-promo-card__title, body .s-promo-card__title, body .promo-title, 237 /* ââ Podcasts / Episódios ââ */ 238 body .pod-card__title, body .sp-title, body .ep-sidebar-title, body .ep-more-title, body .podcast-title, 239 /* ââ Programação / Shows ââ */ 240 body .d5-prog-card__name, body .schedule-name, body .on-air-name, body .spsc-name, body .s-show-card__name, 241 /* ââ Equipe ââ */ 242 body .team-name, body .team-member-name, 243 /* ââ Listagens ââ */ 244 body .s-section-title, 245 /* ââ Genéricos ââ */ 246 body .card-title, body .font-display { 247 font-family: var(--card-title-font-family) !important; 248 font-weight: var(--card-title-font-weight) !important; 249 font-style: var(--card-title-font-style) !important; 250 color: var(--card-title-color) !important; 251 font-size: var(--card-title-font-size) !important; 252 } 253 254 :root { 255 --body-font-family: 'Poppins', sans-serif; 256 --body-font-weight: normal; 257 --body-font-style: normal; 258 --body-font-color: #333333; 259 --body-font-size: 15px; 260 } 261 262 /* ââ Parágrafos e listas genéricas (somente front-end) ââ */ 263 p:not(.font-preview), li { 264 font-family: var(--body-font-family) !important; 265 font-weight: var(--body-font-weight) !important; 266 font-style: var(--body-font-style) !important; 267 font-size: var(--body-font-size) !important; 268 color: var(--body-font-color) !important; 269 } 270 271 /* ââ Single de NotÃcia (ns-*) ââ */ 272 .ns-body-content, .ns-body-content p, .ns-body-content li, .ns-lead { 273 font-family: var(--body-font-family) !important; 274 font-weight: var(--body-font-weight) !important; 275 font-style: var(--body-font-style) !important; 276 font-size: var(--body-font-size) !important; 277 color: var(--body-font-color) !important; 278 } 279 280 /* ââ Single de Evento (ev-*) ââ */ 281 .ev-description, .ev-description p, .ev-description li { 282 font-family: var(--body-font-family) !important; 283 font-weight: var(--body-font-weight) !important; 284 font-style: var(--body-font-style) !important; 285 font-size: var(--body-font-size) !important; 286 color: var(--body-font-color) !important; 287 } 288 289 /* ââ Single de Promoção (pr-*) ââ */ 290 .pr-description, .pr-description p, .pr-description li, 291 .pr-rules-body, .pr-rules-body p, .pr-rules-body li { 292 font-family: var(--body-font-family) !important; 293 font-weight: var(--body-font-weight) !important; 294 font-style: var(--body-font-style) !important; 295 font-size: var(--body-font-size) !important; 296 color: var(--body-font-color) !important; 297 } 298 299 /* ââ Single de Podcast (ep-*) ââ */ 300 .ep-desc-content, .ep-desc-content p, .ep-desc-content li { 301 font-family: var(--body-font-family) !important; 302 font-weight: var(--body-font-weight) !important; 303 font-style: var(--body-font-style) !important; 304 font-size: var(--body-font-size) !important; 305 color: var(--body-font-color) !important; 306 } 307 308 /* ââ Page Builder / Páginas dinâmicas ââ */ 309 .bloco-texto, .bloco-texto p, .bloco-texto li, 310 .pd-compat-text, .pd-compat-rich, .pd-compat-rich p, .pd-compat-rich li, 311 .pd-page-content p, .pd-page-content li { 312 font-family: var(--body-font-family) !important; 313 font-weight: var(--body-font-weight) !important; 314 font-style: var(--body-font-style) !important; 315 font-size: var(--body-font-size) !important; 316 color: var(--body-font-color) !important; 317 } 318 319 /* ââ Cards e seções da Home ââ */ 320 .card-description, .card-text, .card-excerpt, 321 .news-excerpt, .news-description, 322 .event-description, .event-excerpt, 323 .team-description, .team-bio, 324 .promo-description, .promotion-description, 325 .podcast-description, .podcast-excerpt, 326 .section-description, .section-text, 327 .about-text, .about-description, 328 .hero-description, .hero-text, 329 .slider-description, .slide-text { 330 font-family: var(--body-font-family) !important; 331 font-weight: var(--body-font-weight) !important; 332 font-style: var(--body-font-style) !important; 333 font-size: var(--body-font-size) !important; 334 color: var(--body-font-color) !important; 335 } 336 337 :root { 338 --player-bg: linear-gradient(135deg, #ff9500 0%, #ff5900 100%); 339 --player-text-color: #ffffff; 340 } 341 342 /* ========================================================= 343 GLOBAL PLAYER CONTROLS (SOLID ROUND BUTTONS) 344 ========================================================= */ 345 /* Garante herança da cor base do texto no container */ 346 #player, .d5-player, .neon-player { 347 color: var(--player-text-color, #ffffff) !important; 348 } 349 350 /* Textos padronizados */ 351 #playerArtist, #playerTitle, #playerStatusText, .live-text, .neon-player__title, .neon-player__artist, 352 .d5-player-title, .d5-player-artist { 353 color: var(--player-text-color, #ffffff) !important; 354 -webkit-text-stroke: 0 !important; 355 text-shadow: none !important; 356 } 357 .neon-player__artist, .d5-player-artist { 358 opacity: 0.8 !important; 359 } 360 361 /* 362 BOTÃES SÃLIDOS (Exemplo Anexo) 363 O fundo do botão herda a Cor dos Elementos. 364 O Ãcone corta a cor, usando a cor de fundo original do player (ou escala de cinza/preta). 365 */ 366 #playBtn, #liveBtn, #videoBtn, #volumeBtn, 367 .d5-play-btn, .neon-player__play-btn, .neon-player__live-btn, .neon-player__vol-btn { 368 background-color: var(--player-text-color, #ffffff) !important; 369 border: none !important; 370 box-shadow: 0 4px 10px rgba(0,0,0,0.15) !important; 371 display: flex !important; 372 align-items: center !important; 373 justify-content: center !important; 374 border-radius: 50% !important; /* Sempre redondo */ 375 } 376 377 /* Ajuste de Preenchimento para garantir que todos os botões fiquem proporcionais */ 378 #liveBtn, #videoBtn, #volumeBtn, .neon-player__live-btn, .neon-player__vol-btn { 379 width: 36px !important; 380 height: 36px !important; 381 min-width: 36px !important; 382 } 383 #playBtn, .d5-play-btn, .neon-player__play-btn { 384 width: 48px !important; 385 height: 48px !important; 386 } 387 388 /* Ãcones dentro dos botões (Cor contrastante) */ 389 #playBtn i, #liveBtn i, #volumeBtn i, #videoBtn i, .d5-vol-icon, 390 #playBtn svg, #liveBtn svg, #volumeBtn svg, #videoBtn svg, 391 #svgPlay, #svgPause, 392 .d5-play-btn i, .d5-play-btn svg, .d5-vol-icon svg, 393 .neon-player__play-btn svg, .neon-player__controls button svg, .neon-player__vol-btn svg, 394 .neon-player__controls button i { 395 color: var(--player-bg, #222) !important; 396 fill: var(--player-bg, #222) !important; 397 filter: none !important; 398 -webkit-text-stroke: 0 !important; 399 text-shadow: none !important; 400 /* Adicionando um mixin de preto para garantir contraste na inversão */ 401 color: color-mix(in srgb, var(--player-bg, #222) 80%, black) !important; 402 fill: color-mix(in srgb, var(--player-bg, #222) 80%, black) !important; 403 } 404 /* Exceção para o spinner de loading preservar stroke e não ter fill */ 405 #svgLoading { 406 color: color-mix(in srgb, var(--player-bg, #222) 80%, black) !important; 407 fill: none !important; 408 } 409 410 /* Remove margens estranhas dos Ãcones centralizados que alguns SVGs tem por padrão */ 411 .neon-player__play-btn svg, .d5-play-btn i { 412 margin: 0 !important; 413 transform: translateX(2px); /* Ajuste visual do triângulo de play ficar centralizado visualmente */ 414 } 415 .neon-player__controls button svg { 416 transform: none; 417 } 418 419 /* Trilho do Slider de Volume */ 420 .d5-volume-slider, .neon-player__vol-slider { 421 background: color-mix(in srgb, var(--player-text-color, #ffffff) 20%, transparent) !important; 422 } 423 424 /* Controle de Volume: Destaque na bolinha (Thumb) combinando com o botão sólido */ 425 #volumeSlider::-webkit-slider-thumb, 426 .d5-volume-slider::-webkit-slider-thumb, 427 .neon-player__vol-slider::-webkit-slider-thumb { 428 background: var(--player-text-color, #ffffff) !important; 429 box-shadow: 0 2px 5px rgba(0,0,0,0.3) !important; 430 border: none !important; 431 } 432 #volumeSlider::-moz-range-thumb, 433 .d5-volume-slider::-moz-range-thumb, 434 .neon-player__vol-slider::-moz-range-thumb { 435 background: var(--player-text-color, #ffffff) !important; 436 box-shadow: 0 2px 5px rgba(0,0,0,0.3) !important; 437 border: none !important; 438 } 439 440 /* Elementos secundários (Badges informativos) */ 441 #playerArtwork, .d5-player-artwork { 442 background-color: color-mix(in srgb, var(--player-text-color, #ffffff) 10%, transparent) !important; 443 border: 1px solid color-mix(in srgb, var(--player-text-color, #ffffff) 15%, transparent) !important; 444 border-radius: 8px !important; 445 } 446 .live-badge { 447 background-color: color-mix(in srgb, var(--player-text-color, #ffffff) 15%, transparent) !important; 448 border: 1px solid color-mix(in srgb, var(--player-text-color, #ffffff) 30%, transparent) !important; 449 color: var(--player-text-color, #ffffff) !important; 450 } 451 .live-dot, .neon-player__live-dot { 452 background-color: var(--player-text-color, #ffffff) !important; 453 } 454 455 /* ========================================================= 456 PADRONIZAÃÃO TIPOGRÃFICA MOBILE (FIXED SIZES) 457 As definições abaixo forçam tamanhos fixos no mobile, 458 ignorando os tamanhos definidos pelo painel para desktop. 459 Escala baseada em boas práticas para UI Mobile. 460 ========================================================= */ 461 @media (max-width: 768px) { 462 :root { 463 --page-title-font-size-base: 24px !important; 464 --card-title-font-size: 18px !important; 465 --body-font-size: 14px !important; 466 --nav-font-size: 16px !important; 467 } 468 469 /* TÃtulo Principal (H1) */ 470 body h1, body .ns-title, body .pr-title, body .ep-title, body .on-air-title, body .page-hero__title { 471 font-size: 24px !important; 472 line-height: 1.4 !important; 473 } 474 475 /* TÃtulo Secundário (H2) */ 476 body h2, body .section-title { 477 font-size: 20px !important; 478 line-height: 1.4 !important; 479 } 480 481 /* TÃtulo Terciário (H3) / Cards Principais */ 482 body h3, 483 body .hn-card__title, body .hn-hero__title, body .d5-news-card__title, 484 body .d5-news-featured__title, body .s-news-card__title, body .news-title, 485 body .ns-sidebar-card-title, body .ns-sidebar-title, 486 body .pulse-sidebar-card-title, body .pulse-sidebar-title, 487 body .d5-event-card__title, body .s-event-card__title, body .event-title, 488 body .d5-promo-card__title, body .s-promo-card__title, body .promo-title, 489 body .pod-card__title, body .sp-title, body .ep-sidebar-title, body .ep-more-title, body .podcast-title, 490 body .d5-prog-card__name, body .schedule-name, body .on-air-name, body .spsc-name, body .s-show-card__name, 491 body .tea
491m-name, body .team-member-name, 492 body .s-section-title, 493 body .card-title, body .font-display, 494 body .neon-news-header__title { 495 font-size: 18px !important; 496 line-height: 1.4 !important; 497 } 498 499 /* SubtÃtulo (H4-H6 / Nav) */ 500 body h4, body h5, body h6, 501 body .nav, body .nav-link, body .header nav, body .header .nav-link, body nav a, 502 body #mainNav a, body #mainNav a.nav-link { 503 font-size: 16px !important; 504 line-height: 1.4 !important; 505 } 506 507 /* Corpo do Texto (Body) */ 508 body p:not(.font-preview), body li, 509 body .ns-body-content, body .ns-body-content p, body .ns-body-content li, body .ns-lead, 510 body .ev-description, body .ev-description p, body .ev-description li, 511 body .pr-description, body .pr-description p, body .pr-description li, 512 body .pr-rules-body, body .pr-rules-body p, body .pr-rules-body li, 513 body .ep-desc-content, body .ep-desc-content p, body .ep-desc-content li, 514 body .bloco-texto, body .bloco-texto p, body .bloco-texto li, 515 body .pd-compat-text, body .pd-compat-rich, body .pd-compat-rich p, body .pd-compat-rich li, 516 body .pd-page-content p, body .pd-page-content li { 517 font-size: 14px !important; 518 line-height: 1.6 !important; 519 } 520 521 /* Descrições / Texto Auxiliar */ 522 body .card-description, body .card-text, body .card-excerpt, 523 body .news-excerpt, body .news-description, 524 body .event-description, body .event-excerpt, 525 body .team-description, body .team-bio, 526 body .promo-description, body .promotion-description, 527 body .podcast-description, body .podcast-excerpt, 528 body .section-description, body .section-text, 529 body .about-text, body .about-description, 530 body .hero-description, body .hero-text, 531 body .slider-description, body .slide-text, 532 body .neon-slide__desc { 533 font-size: 12px !important; 534 line-height: 1.5 !important; 535 } 536
537 /* Legendas / Caption */ 538 body figcaption, body .caption, body .date-badge, body .d5-news-date, body .category-badge, 539 body .neon-news-header__label span, body .neon-news-header__label { 540 font-size: 11px !important; 541 line-height: 1.4 !important; 542 } 543 } 544 545/* === CONTROLE DE TAMANHO DO LOGOTIPO === */ 546@media (min-width: 769px) { 547 .site-logo img, .navbar-brand img, .navbar-logo img, header .logo img, nav .logo img, header img[src*='uploads/media'], header img[src*='uploads/logo'], header a[href='./'] > img, .wave-header__logo img, .pulse-header__logo img, .neon-header__logo img, .header-logo img { 548 width: 110px !important; 549 max-width: 100% !important; 550 height: auto !important; 551 object-fit: contain !important; 552 display: block !important; 553 transition: width 0.2s, height 0.2s !important; 554 } 555 header, .site-header, nav.navbar, .navbar, .navbar-inner, .header-inner, .wave-header, .pulse-header, .neon-header { 556 height: auto !important; 557 min-height: 65px !important; 558 padding-top: 16px !important; 559 padding-bottom: 16px !important; 560 display: flex !important; 561 align-items: center !important; 562 box-sizing: border-box !important; 563 } 564} 565@media (max-width: 768px) { 566 .site-logo img, .navbar-brand img, .navbar-logo img, header .logo img, nav .logo img, header img[src*='uploads/media'], header img[src*='uploads/logo'], header a[href='./'] > img, .wave-header__logo img, .pulse-header__logo img, .neon-header__logo img, .header-logo img, #mobileNav img[src*='uploads/media'], #mobileNav img[src*='uploads/logo'], #mobileNav a[href='./'] > img, [data-ag-drawer] img[src*='uploads/media'], [data-ag-drawer] img[src*='uploads/logo'], [data-ag-drawer] a[href='./'] > img, .wave-mobile__logo img, .pulse-mobile__logo img, .neon-mobile__logo img, .mobile-logo img { 567 width: 50px !important; 568 max-width: 100% !important; 569 height: auto !important; 570 object-fit: contain !important; 571 display: block !important; 572 transition: width 0.2s, height 0.2s !important; 573 } 574 header, .site-header, nav.navbar, .navbar, .navbar-inner, .header-inner, .wave-header, .pulse-header, .neon-header { 575 height: auto !important; 576 min-height: 39px !important; 577 padding-top: 12px !important; 578 padding-bottom: 12px !important; 579 } 580} 581.navbar-brand, .site-logo, nav .logo, .wave-header__logo, .pulse-header__logo, .neon-header__logo, .header-logo { 582 display: flex !important; 583 align-items: center !important; 584 overflow: visible !important; 585 flex-shrink: 0 !important; 586 height: auto !important; 587} 588 </style> 589 590 <!-- Google Analytics --> 591 592 <!-- Google Search Console --> 593 594 <!-- Código Personalizado â <head> (raw, escopo do tenant; ver /?page=integrations) --> 595 </head> 596 597<body class="theme-light"> 598 599 600 <!-- Código Personalizado â inÃcio do <body> (raw, escopo do tenant) --> 601 602 603<header class="neon-header" id="header"> 604 <div class="neon-header__inner"> 605 <!-- Logo --> 606 <a href="./" class="neon-header__logo"> 607 <img src="https://radio93portofeliz.com.br/uploads/media/6a05e3548b2fc_1778770772.png" 608 alt="Rádio 93 FM"> 609 </a> 610 611 <!-- Desktop Nav --> 612 <nav class="neon-nav" id="mainNav"> 613 <a href="https://radio93portofeliz.com.br/" class="neon-nav__link "> 614 InÃcio </a> 615 <a href="a-radio" class="neon-nav__link "> 616 A rádio </a> 617 <a href="https://www.youtube.com/radio93portofeliz" class="neon-nav__link "> 618 Estúdio </a> 619 <a href="equipe" class="neon-nav__link neon-nav__link--active"> 620 Equipe </a> 621 <a href="programacao" class="neon-nav__link "> 622 Programação </a> 623 <a href="promocoes" class="neon-nav__link "> 624 Promoções </a> 625 <a href="contato" class="neon-nav__link "> 626 Contato </a> 627 </nav> 628 629 <!-- Right Actions --> 630 <div class="neon-header__actions"> 631 <!-- Seletor de Praça (multi-praça; inerte com <2 praças) --> 632 633 <!-- On Air Widget --> 634 635 <!-- Search --> 636 <form action="https://radio93portofeliz.com.br/" method="GET" class="neon-search" id="neonDesktopSearchForm"> 637 <input type="hidden" name="page" value="busca"> 638 <button type="submit" class="neon-search__btn"> 639 <!-- Lucide: search --> 640 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg> 641 </button> 642 <input type="text" name="q" placeholder="Buscar..." class="neon-search__input" id="neonDesktopSearchInput" autocomplete="off"> 643 <div class="neon-search-spinner" style="display: none;"> 644 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
644ox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> 645 </div> 646 <!-- Resultado Desktop --> 647 <div class="neon-live-search-results" id="neonDesktopSearchDropdown"></div> 648 </form> 649 650 </div> 651 652 <!-- NEW: Mobile Actions (App Style Spotify) --> 653 <div class="neon-header__mobile-actions"> 654 <!-- Mobile Toggle (drawer) --> 655 <button type="button" class="neon-header__mobile-btn neon-header__mobile-action" id="neonMobileBtn" data-ag-drawer-toggle aria-label="Abrir menu" aria-expanded="false" aria-controls="neonMobile"> 656 <!-- Lucide: menu --> 657 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg> 658 </button> 659 <a href="https://wa.me/5515996090935" target="_blank" rel="noopener" class="neon-header__mobile-action header-icon-whatsapp" aria-label="WhatsApp" style="display: flex; align-items: center;"> 660 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" style="width: 22px; height: 22px; min-width: 22px; min-height: 22px;"> 661 <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/> 662 </svg> 663 </a> 664 <button type="button" class="neon-header__mobile-action" onclick="NeonMobileSearch.open()" aria-label="Buscar" style="display: flex; align-items: center;"> 665 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 22px; height: 22px; min-width: 22px; min-height: 22px;"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg> 666 </button> 667 </div> 668 </div> 669</header> 670 671<!-- Mobile Expandable Search --> 672<div class="neon-mobile-search-overlay" id="neonMobileSearchOverlay"> 673 <div class="neon-mobile-search-dialog"> 674 <div class="neon-mobile-search-topbar"> 675 <h3 class="neon-mobile-search-title">Pesquisar</h3> 676 <button type="button" class="neon-mobile-search-close" onclick="NeonMobileSearch.close()" aria-label="Fechar Busca"> 677 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> 678 </button> 679 </div> 680 <form action="https://radio93portofeliz.com.br/" method="GET" class="neon-mobile-search-form" id="neonMobileSearchForm"> 681 <input type="hidden" name="page" value="busca"> 682 <button type="submit" class="neon-mobile-search-submit"> 683 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg> 684 </button> 685 <input type="text" name="q" placeholder="O que você quer ouvir?" class="neon-mobile-search-input" id="neonMobileSearchInput" autocomplete="off"> 686 <div class="neon-search-spinner" id="neonMobileSearchSpinner" style="display: none;"> 687 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> 688 </div> 689 </form> 690 <!-- Resultado Mobile --> 691 <div class="neon-mobile-live-results" id="neonMobileSearchDropdown"></div> 692 </div> 693</div> 694 695<!-- Menu Mobile Drawer (partial compartilhado â markup+JS únicos) --> 696 697<!-- Backdrop / Overlay do Drawer --> 698<div class="neon-mobile-overlay" id="neonOverlay" data-ag-drawer-overlay></div> 699 700<!-- Drawer / Painel Lateral --> 701<aside class="neon-mobile" id="neonMobile" data-ag-drawer aria-hidden="true"> 702 <div class="neon-mobile__header"> 703 <div class="neon-mobile__logo"> 704 <img src="https://radio93portofeliz.com.br/uploads/media/6a05e3548b2fc_1778770772.png" 705 alt="Rádio 93 FM"> 706 </div> 707 <button type="button" class="neon-mobile__close" data-ag-drawer-close aria-label="Fechar menu"> 708 <!-- Lucide: x --> 709 <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> 710 </button> 711 </div> 712 713 714 715 <nav class="neon-mobile__nav"> 716 <a href="https://radio93portofeliz.com.br/" data-ag-drawer-link 717 class="neon-mobile__link "> 718 InÃcio <!-- Lucide: chevron-right --> 719 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
719ox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 720 </a> 721 <a href="a-radio" data-ag-drawer-link 722 class="neon-mobile__link "> 723 A rádio <!-- Lucide: chevron-right --> 724 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 725 </a> 726 <a href="https://www.youtube.com/radio93portofeliz" data-ag-drawer-link 727 class="neon-mobile__link "> 728 Estúdio <!-- Lucide: chevron-right --> 729 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 730 </a> 731 <a href="equipe" data-ag-drawer-link 732 class="neon-mobile__link neon-mobile__link--active"> 733 Equipe <!-- Lucide: chevron-right --> 734 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 735 </a> 736 <a href="programacao" data-ag-drawer-link 737 class="neon-mobile__link "> 738 Programação <!-- Lucide: chevron-right --> 739 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 740 </a> 741 <a href="promocoes" data-ag-drawer-link 742 class="neon-mobile__link "> 743 Promoções <!-- Lucide: chevron-right --> 744 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 745 </a> 746 <a href="contato" data-ag-drawer-link 747 class="neon-mobile__link "> 748 Contato <!-- Lucide: chevron-right --> 749 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> 750 </a> 751 </nav> 752 753 <div class="neon-mobile__footer"> 754 <p>Rádio 93 FM</p> 755 </div> 756</aside> 757
758<script> 759/* Controlador ÃNICO do drawer mobile (compartilhado por Pulse/Wave/Neon/Glow). 760 Como só um tema renderiza por página, há um único [data-ag-drawer]. */ 761(function () { 762 if (window.AgDrawer) return; 763 764 const panel = document.querySelector('[data-ag-drawer]'); 765 if (!panel) return; 766 const overlay = document.querySelector('[data-ag-drawer-overlay]'); 767 const toggles = document.querySelectorAll('[data-ag-drawer-toggle]'); 768 let lastFocus = null; 769 770 const AgDrawer = { 771 open() { 772 panel.classList.add('is-open'); 773 if (overlay) overlay.classList.add('is-open'); 774 document.body.style.overflow = 'hidden'; 775 panel.setAttribute('aria-hidden', 'false'); 776 toggles.forEach(t => t.setAttribute('aria-expanded', 'true')); 777 // Acessibilidade: guarda o foco anterior e move para o botão de fechar. 778 lastFocus = document.activeElement; 779 const closeBtn = panel.querySelector('[data-ag-drawer-close]'); 780 if (closeBtn) closeBtn.focus(); 781 }, 782 close() { 783 panel.classList.remove('is-open'); 784 if (overlay) overlay.classList.remove('is-open'); 785 document.body.style.overflow = ''; 786 panel.setAttribute('aria-hidden', 'true'); 787 toggles.forEach(t => t.setAttribute('aria-expanded', 'false')); 788 // Devolve o foco a quem abriu o drawer. 789 if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus(); 790 }, 791 toggle() { 792 panel.classList.contains('is-open') ? this.close() : this.open(); 793 } 794 }; 795 window.AgDrawer = AgDrawer; 796 797 toggles.forEach(t => { 798 t.setAttribute('aria-expanded', 'false'); 799 t.addEventListener('click', (e) => { e.preventDefault(); AgDrawer.toggle(); }); 800 }); 801 if (overlay) overlay.addEventListener('click', () => AgDrawer.close()); 802 panel.querySelectorAll('[data-ag-drawer-close]').forEach(b => b.addEventListener('click', () => AgDrawer.close())); 803 panel.querySelectorAll('[data-ag-drawer-link]').forEach(a => a.addEventListener('click', () => AgDrawer.close())); 804 document.addEventListener('keydown', (e) => { 805 if (e.key === 'Escape' && panel.classList.contains('is-open')) AgDrawer.close(); 806 }); 807})(); 808</script>
808 809 810<!-- Header spacer removido: #mainContent já tem padding-top:72px via style.css --> 811
812<script> 813// ââ Active nav state sync (JS) ââââââââââââââââââââââââââââââââââââââââââ 814function syncNeonNavActive() { 815 const raw = window.location.pathname.replace(/^\//, '').replace(/\/$/, ''); 816 const currentPage = raw || 'home'; 817 const normalizeSlug = (s) => s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9-]/g, ''); 818 const getSlug = (href) => { 819 try { 820 const url = new URL(href, window.location.origin); 821 const sp = url.searchParams.get('page'); 822 if (sp) return sp; 823 const p = url.pathname.replace(/^\//, '').replace(/\/$/, ''); 824 return p ? p.split('/').pop() : 'home'; 825 } catch(e) { return href.replace(/^\//, '').replace(/\/$/, '') || 'home'; } 826 }; 827 const pn = normalizeSlug(currentPage); 828 // Menu de Ãcones: o componente mantém o próprio sync (.is-active no <li> + 829 // aria-current no link). Menu de texto: a lista abaixo fica vazia quando o 830 // estilo de Ãcones está ativo, então as duas linhas convivem. 831 if (typeof window.agEicSyncActive === 'function') window.agEicSyncActive(); 832 document.querySelectorAll('.neon-nav__link').forEach(l => l.classList.toggle('neon-nav__link--active', normalizeSlug(getSlug(l.getAttribute('href')||'')) === pn)); 833 document.querySelectorAll('.neon-mobile__link').forEach(l => l.classList.toggle('neon-mobile__link--active', normalizeSlug(getSlug(l.getAttribute('href')||'')) === pn)); 834} 835syncNeonNavActive(); 836window.addEventListener('pageshow', (e) => { if (e.persisted) syncNeonNavActive(); }); 837window.addEventListener('popstate', syncNeonNavActive); 838// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 839 840// ESC fecha a busca mobile. O drawer é controlado pelo AgDrawer (partial compartilhado). 841document.addEventListener('keydown', e => { 842 if (e.key === 'Escape' && typeof NeonMobileSearch !== 'undefined') NeonMobileSearch.close(); 843}); 844 845const NeonMobileSearch = { 846 open() { 847 document.getElementById('neonMobileSearchOverlay').classList.add('is-active'); 848 document.body.style.overflow = 'hidden'; 849 setTimeout(() => { 850 const input = document.getElementById('neonMobileSearchInput'); 851 if(input) input.focus(); 852 }, 300); 853 }, 854 close() { 855 document.getElementById('neonMobileSearchOverlay').classList.remove('is-active'); 856 document.body.style.overflow = ''; 857 }, 858 toggle() { 859 document.getElementById('neonMobileSearchOverlay').classList.contains('is-active') ? this.close() : this.open(); 860 } 861}; 862</script>
862 863 <!-- Page Content --> 864 <div id="mainContent"> 865 866 867<style> 868/* ââââââââââââââââââââââââââââââââââââââââââ 869 PAGE HEADER â Padrão global de listagens 870 Usa CSS variables para herdar light/dark mode automaticamente 871 ââââââââââââââââââââââââââââââââââââââââââ */ 872.page-header { 873 padding: 32px 0 16px; 874 border-bottom: 1px solid var(--border-color); 875 margin-bottom: 24px; 876 max-width: 1200px; 877 margin-left: auto; 878 margin-right: auto; 879 padding-left: 40px; 880 padding-right: 40px; 881} 882 883.page-header .breadcrumb { 884 display: flex; 885 align-items: center; 886 gap: 6px; 887 font-size: 12px; 888 color: var(--text-muted); 889 margin-bottom: 16px; 890 opacity: 0.75; 891} 892 893/* "InÃcio" â apagado, página pai */ 894.page-header .breadcrumb a { 895 color: var(--text-muted); 896 text-decoration: none; 897 transition: color 0.15s; 898 opacity: 0.7; 899} 900 901.page-header .breadcrumb a:hover { 902 color: var(--text-secondary); 903 opacity: 1; 904} 905 906/* Separador "/" */ 907.page-header .breadcrumb .bc-sep { 908 color: var(--border-color); 909 font-size: 12px; 910 user-select: none; 911} 912 913/* Página atual â destaque sutil em âmbar */ 914.page-header .breadcrumb .bc-current { 915 color: var(--primary-color, #f5a623); 916 font-weight: 500; 917 opacity: 1; 918} 919 920.page-header h1 { 921 font-size: 1.6rem; 922 font-weight: 600; 923 color: var(--text-primary); 924 line-height: 1.2; 925 margin: 0; 926} 927 928.page-header .subtitle { 929 font-size: 0.95rem; 930 color: var(--text-secondary); 931 margin-top: 8px; 932 margin-bottom: 0; 933} 934 935/* Decoração lateral: barra única, levemente mais fina */ 936.page-header h1::before { 937 content: ''; 938 display: inline-block; 939 width: 3px; 940 height: 0.9em; 941 background: var(--primary-color); 942 border-radius: 2px; 943 margin-right: 12px; 944 vertical-align: middle; 945} 946 947@media (max-width: 768px) { 948 .page-header { 949 padding-left: 20px; 950 padding-right: 20px; 951 padding-top: 24px; 952 } 953 954 .page-header h1 { 955 font-size: 1.35rem; 956 } 957} 958</style> 959 960<div class="page-header"> 961 <nav class="breadcrumb" aria-label="Breadcrumb"> 962 <a href="https://radio93portofeliz.com.br/">InÃcio</a> 963 <span class="bc-sep" aria-hidden="true">/</span> 964 <span class="bc-current">Equipe</span> 965 </nav> 966 <h1>Equipe</h1> 967 </div> 968 969 970<div class="container"> 971 <div class="page-body"> 972 973 <div class="team-grid"> 974 <article class="team-card" 975 data-nome="93 FM" 976 data-cargo="93 FM" 977 data-descricao="A primeira em todo lugar!" 978 data-foto="https://radio93portofeliz.com.br/uploads/media/6a0cec6dbaece_1779231853.png" 979 data-facebook="https://www.facebook.com/radio93portofeliz/" 980 data-instagram="https://www.instagram.com/radio93portofeliz/" 981 data-twitter="" 982 data-tiktok=""> 983 <!-- Capa 1920Ã800 --> 984 <div class="team-cover"> 985 <img src="https://radio93portofeliz.com.br/uploads/media/6a0cec6dbaece_1779231853.png" 986 alt="93 FM"> 987 </div> 988 989 <!-- Informações --> 990 <div class="team-card__body"> 991 <h2 class="team-name">93 FM</h2> 992 993 <span class="team-role">93 FM</span> 994 995 <p class="team-bio"> 996 A primeira em todo lugar! </p> 997 998 <div class="team-social"> 999 <sp
999an><i class="fab fa-instagram"></i> Instagram</span> 1000 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1001 </div> 1002 </div> 1003 </article> 1004 <article class="team-card" 1005 data-nome="Adilson Giuli" 1006 data-cargo="Locutor" 1007 data-descricao="Adilson comanda todos os começos de manhã com muita alegria e música sertaneja, de segunda a sexta, das 05h à s 08h, além de ser no nosso narrador esportivo oficial." 1008 data-foto="https://radio93portofeliz.com.br/uploads/media/6a05ecb89c971_1778773176.png" 1009 data-facebook="" 1010 data-instagram="https://www.instagram.com/locutoradilsongiuli/" 1011 data-twitter="" 1012 data-tiktok=""> 1013 <!-- Capa 1920Ã800 --> 1014 <div class="team-cover"> 1015 <img src="https://radio93portofeliz.com.br/uploads/media/6a05ecb89c971_1778773176.png" 1016 alt="Adilson Giuli"> 1017 </div> 1018 1019 <!-- Informações --> 1020 <div class="team-card__body"> 1021 <h2 class="team-name">Adilson Giuli</h2> 1022 1023 <span class="team-role">Locutor</span> 1024 1025 <p class="team-bio"> 1026 Adilson comanda todos os começos de manhã com muita alegria e música sertaneja, de segunda a sexta,... </p> 1027 1028 <div class="team-social"> 1029 <span><i class="fab fa-instagram"></i> Instagram</span> 1030 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1031 </div> 1032 </div> 1033 </article> 1034 <article class="team-card" 1035 data-nome="Bruno Mendonça" 1036 data-cargo="Apresentador" 1037 data-descricao="O Bruno é integrante do Programa Corneteiros, de segunda à sexta, das 18h à s 19h!!!" 1038 data-foto="https://radio93portofeliz.com.br/uploads/media/6a078bcf74df9_1778879439.png" 1039 data-facebook="" 1040 data-instagram="https://www.instagram.com/brunomendonca.oficial/" 1041 data-twitter="" 1042 data-tiktok=""> 1043 <!-- Capa 1920Ã800 --> 1044 <div class="team-cover"> 1045 <img src="https://radio93portofeliz.com.br/uploads/media/6a078bcf74df9_1778879439.png" 1046 alt="Bruno Mendonça"> 1047 </div> 1048 1049 <!-- Informações --> 1050 <div class="team-card__body"> 1051 <h2 class="team-name">Bruno Mendonça</h2> 1052 1053 <span class="team-role">Apresentador</span> 1054 1055 <p class="team-bio"> 1056 O Bruno é integrante do Programa Corneteiros, de segunda à sexta, das 18h à s 19h!!! </p> 1057 1058 <div class="team-social"> 1059 <span><i class="fab fa-instagram"></i> Instagram</span> 1060 <sp
1060an><i class="fas fa-microphone-alt"></i> Locutor</span> 1061 </div> 1062 </div> 1063 </article> 1064 <article class="team-card" 1065 data-nome="Douglas Torres" 1066 data-cargo="Apresentador" 1067 data-descricao="Apresenta o programa Corneteiros, de segunda a sexta, das 18h à s 19h." 1068 data-foto="https://radio93portofeliz.com.br/uploads/media/6a14f99bd2c49_1779759515.png" 1069 data-facebook="" 1070 data-instagram="https://www.instagram.com/douglastorresradio/" 1071 data-twitter="" 1072 data-tiktok=""> 1073 <!-- Capa 1920Ã800 --> 1074 <div class="team-cover"> 1075 <img src="https://radio93portofeliz.com.br/uploads/media/6a14f99bd2c49_1779759515.png" 1076 alt="Douglas Torres"> 1077 </div> 1078 1079 <!-- Informações --> 1080 <div class="team-card__body"> 1081 <h2 class="team-name">Douglas Torres</h2> 1082 1083 <span class="team-role">Apresentador</span> 1084 1085 <p class="team-bio"> 1086 Apresenta o programa Corneteiros, de segunda a sexta, das 18h à s 19h. </p> 1087 1088 <div class="team-social"> 1089 <span><i class="fab fa-instagram"></i> Instagram</span> 1090 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1091 </div> 1092 </div> 1093 </article> 1094 <article class="team-card" 1095 data-nome="Isa Nascimento" 1096 data-cargo="Locutora" 1097 data-descricao="A Isa comanda as nossas tardes, de segunda a sexta, a partir das 13h até à s 18h, além de ser a produtora das fofocas da 93 FM." 1098 data-foto="https://radio93portofeliz.com.br/uploads/media/6a05f05b5b952_1778774107.png" 1099 data-facebook="" 1100 data-instagram="https://www.instagram.com/euaisanascimento/" 1101 data-twitter="" 1102 data-tiktok=""> 1103 <!-- Capa 1920Ã800 --> 1104 <div class="team-cover"> 1105 <img src="https://radio93portofeliz.com.br/uploads/media/6a05f05b5b952_1778774107.png" 1106 alt="Isa Nascimento"> 1107 </div> 1108 1109 <!-- Informações --> 1110 <div class="team-card__body"> 1111 <h2 class="team-name">Isa Nascimento</h2> 1112 1113 <span class="team-role">Locutora</span> 1114 1115 <p class="team-bio"> 1116 A Isa comanda as nossas tardes, de segunda a sexta, a partir das 13h até à s 18h, além de ser a... </p> 1117 1118 <div class="team-social"> 1119 <span><i class="fab fa-instagram"></i> Instagram</span> 1120 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1121 </div> 1122 </div> 1123 </article> 1124 <article class="team-card" 1125 data-nome="JB" 1126 data-cargo="Jornalista" 1127 data-descricao="O JB é jornalista e editor do Bom Dia 93, que vai ao ar de segunda a sexta, das 9h as 11h." 1128 data-foto="https://radio93portofeliz.com.br/uploads/media/6a05f0a88b485_1778774184.png" 1129 data-facebook="" 1130 data-instagram="https://www.instagram.com/euojb/" 1131 data-twitter="" 1132 data-tiktok=""> 1133 <!-- Capa 1920Ã800 --> 1134 <div class="team-cover"> 1135 <img src="https://radio93portofeliz.com.br/uploads/media/6a05f0a88b485_1778774184.png" 1136 alt="JB"> 1137 </div> 1138 1139 <!-- Informações --> 1140 <div class="team-card__body"> 1141 <h2 class="team-name">JB</h2> 1142
1143 <span class="team-role">Jornalista</span> 1144 1145 <p class="team-bio"> 1146 O JB é jornalista e editor do Bom Dia 93, que vai ao ar de segunda a sexta, das 9h as 11h. </p> 1147 1148 <div class="team-social"> 1149 <span><i class="fab fa-instagram"></i> Instagram</span> 1150 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1151 </div> 1152 </div> 1153 </article> 1154 <article class="team-card" 1155 data-nome="Luciano Veiga" 1156 data-cargo="Diretor ArtÃstico" 1157 data-descricao="O Luciano comanda o programa Corneteiros, de segunda a sexta, das 18h à s 19h, além de ser o nosso diretor artÃstico, cuidando de tudo que você ouve aqui na 93 FM." 1158 data-foto="https://radio93portofeliz.com.br/uploads/media/6a14fb21c308a_1779759905.png" 1159 data-facebook="" 1160 data-instagram="" 1161 data-twitter="" 1162 data-tiktok=""> 1163 <!-- Capa 1920Ã800 --> 1164 <div class="team-cover"> 1165 <img src="https://radio93portofeliz.com.br/uploads/media/6a14fb21c308a_1779759905.png" 1166 alt="Luciano Veiga"> 1167 </div> 1168 1169 <!-- Informações --> 1170 <div class="team-card__body"> 1171 <h2 class="team-name">Luciano Veiga</h2> 1172 1173 <span class="team-role">Diretor ArtÃstico</span> 1174 1175 <p class="team-bio"> 1176 O Luciano comanda o programa Corneteiros, de segunda a sexta, das 18h à s 19h, além de ser o nosso... </p> 1177 1178 <div class="team-social"> 1179 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1180 </div> 1181 </div> 1182 </article> 1183 <article class="team-card" 1184 data-nome="Ricardo Ribeiro" 1185 data-cargo="Apresentador" 1186 data-descricao="Ricardo traz as principais notÃcias da manhã no âBom Dia 93â, de segunda a sexta, das 9h à s 11h." 1187 data-foto="https://radio93portofeliz.com.br/uploads/media/6a05f12cf386c_1778774316.png" 1188 data-facebook="" 1189 data-instagram="https://www.instagram.com/ricardo_inacio_ribeiro/" 1190 data-twitter="" 1191 data-tiktok=""> 1192 <!-- Capa 1920Ã800 --> 1193 <div class="team-cover"> 1194 <img src="https://radio93portofeliz.com.br/uploads/media/6a05f12cf386c_1778774316.png" 1195 alt="Ricardo Ribeiro"> 1196 </div> 1197 1198 <!-- Informações --> 1199 <div class="team-card__body"> 1200 <h2 class="team-name">Ricardo Ribeiro</h2> 1201 1202 <span class="team-role">Apresentador</span> 1203 1204 <p class="team-bio"> 1205 Ricardo traz as principais notÃcias da manhã no âBom Dia 93â, de segunda a sexta, das 9h à s 11h. </p> 1206 1207 <div class="team-social"> 1208 <sp
1208an><i class="fab fa-instagram"></i> Instagram</span> 1209 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1210 </div> 1211 </div> 1212 </article> 1213 <article class="team-card" 1214 data-nome="Rodrigo Ribeiro" 1215 data-cargo="Locutor" 1216 data-descricao="Rodrigo comanda as nossas manhãs, de segunda a sexta-feira das 08h à s 13h e nos sábados das 07 à s 12h." 1217 data-foto="https://radio93portofeliz.com.br/uploads/media/6a05f165cd49f_1778774373.png" 1218 data-facebook="" 1219 data-instagram="https://www.instagram.com/rodrigoribeirolocutor/" 1220 data-twitter="" 1221 data-tiktok=""> 1222 <!-- Capa 1920Ã800 --> 1223 <div class="team-cover"> 1224 <img src="https://radio93portofeliz.com.br/uploads/media/6a05f165cd49f_1778774373.png" 1225 alt="Rodrigo Ribeiro"> 1226 </div> 1227 1228 <!-- Informações --> 1229 <div class="team-card__body"> 1230 <h2 class="team-name">Rodrigo Ribeiro</h2> 1231 1232 <span class="team-role">Locutor</span> 1233 1234 <p class="team-bio"> 1235 Rodrigo comanda as nossas manhãs, de segunda a sexta-feira das 08h à s 13h e nos sábados das 07 à s... </p> 1236 1237 <div class="team-social"> 1238 <span><i class="fab fa-instagram"></i> Instagram</span> 1239 <span><i class="fas fa-microphone-alt"></i> Locutor</span> 1240 </div> 1241 </div> 1242 </article> 1243 </div> 1244 1245 </div> 1246</div> 1247 1248<style> 1249 /* ============================= 1250 GRID DE EQUIPE 1251 ============================= */ 1252 .team-grid { 1253 display: grid; 1254 grid-template-columns: repeat(4, 1fr); 1255 gap: 24px; 1256 margin-top: 8px; 1257 } 1258 1259 @media (max-width: 1024px) { 1260 .team-grid { grid-template-columns: repeat(3, 1fr); } 1261 } 1262 1263 @media (max-width: 768px) { 1264 .team-grid { grid-template-columns: repeat(2, 1fr); gap: 16px; } 1265 } 1266 1267 @media (max-width: 480px) { 1268 .team-grid { grid-template-columns: 1fr; } 1269 } 1270 1271 /* ============================= 1272 CARD DE MEMBRO 1273 ============================= */ 1274 .team-card { 1275 background: var(--bg-card, var(--card-bg)); 1276 border-radius: 16px; 1277 overflow: hidden; 1278 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07); 1279 transition: transform 0.2s ease, box-shadow 0.2s ease; 1280 display: flex; 1281 flex-direction: column; 1282 } 1283 1284 .team-card:hover { 1285 transform: translateY(-4px); 1286 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); 1287 } 1288 1289 /* Capa 1920Ã800 (object-fit cover) */ 1290 .team-cover { 1291 width: 100%; 1292 aspect-ratio: 1920 / 800; 1293 overflow: hidden; 1294 background: var(--bg-tertiary); 1295 flex-shrink: 0; 1296 } 1297 1298 .team-cover img { 1299 width: 100%; 1300 height: 100%; 1301 object-fit: cover; 1302 object-position: center; 1303 display: block; 1304 } 1305 1306 .team-cover-placeholder { 1307 width: 100%; 1308 height: 100%; 1309 background: linear-gradient(135deg, var(--primary-color), var(--secondary-color, var(--primary-color))); 1310 display: flex; 1311 align-items: center; 1312 justify-content: center; 1313 } 1314 1315 .team-cover-placeholder i { 1316 font-size: 2.5rem; 1317 color: rgba(255, 255, 255, 0.6); 1318 } 1319 1320 /* Corpo do card */ 1321 .team-card__body { 1322 padding: 20px 16px; 1323 text-align: center; 1324 display: flex; 1325 flex-direction: column; 1326 align-items: center; 1327 flex: 1; 1328 } 1329 1330 /* Nome */ 1331 .team-name { 1332 font-size: 1rem; 1333 font-weight: 700; 1334 color: var(--text-primary); 1335 margin: 0 0 6px; 1336 line-height: 1.3; 1337 } 1338 1339 /* Cargo */ 1340 .team-role { 1341 font-size: 0.85rem; 1342 color: var(--cor-destaque, var(--primary-color)); 1343 font-weight: 600; 1344 margin-bottom: 10px; 1345 display: block; 1346 } 1347 1348 /* Bio */ 1349 .team-bio { 1350 font-size: 0.825rem; 1351 color: var(--text-secondary); 1352 line-height: 1.5; 1353 margin-bottom: 12px; 1354 } 1355 1356 /* Redes sociais */ 1357 .team-social { 1358 display: flex; 1359 gap: 12px; 1360 flex-wrap: wrap; 1361 justify-content: center; 1362 font-size: 0.75rem; 1363 color: var(--text-muted); 1364 margin-top: auto; 1365 } 1366</style>
1367<script src="https://radio93portofeliz.com.br/assets/js/team-module.js?v=1790901861"></script>
1367 </div> 1368 1369 <!-- Persistent Audio Player --> 1370 <!-- Theme: NEON â Glassmorphism + Neon Glow â Audio Player --> 1371<div id="player" class="neon-player"> 1372 <div class="neon-player__inner"> 1373 1374 <!-- ââââ GRUPO ESQUERDO: Artwork + Info + Controles + Volume ââââ --> 1375 <div class="neon-player__left-group"> 1376 1377 <!-- Artwork + Info --> 1378 <div class="neon-player__info"> 1379 <div class="neon-player__artwork" id="playerArtwork"> 1380 <img src="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png" 1381 alt="Album Art" 1382 onerror="this.src = PLAYER_DEFAULT_IMAGE || 'https://radio93portofeliz.com.br/assets/img/default-cover.png'"> 1383 </div> 1384 <div class="neon-player__meta"> 1385 <div class="neon-player__status" data-player-status> 1386 </div> 1387 <div class="neon-player__title" id="playerTitle">Carregando...</div> 1388 <div class="neon-player__artist" id="playerArtist">Rádio 93 FM</div> 1389 </div> 1390 </div> 1391 1392 <!-- NOVOS CONTROLES (NATIVOS) --> 1393 <div class="zmp-player-controls-wrapper"> 1394 1395 <!-- PLAY / PAUSE --> 1396 <button id="playBtn" class="zmp-btn zmp-play-size" title="Play/Pause"> 1397 <svg id="playIcon" class="zmp-svg zmp-show" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" style="max-width: 22px; max-height: 22px;"> 1398 <path fill="currentColor" d="M6 3.5a1.5 1.5 0 0 1 2.3-.8l11.5 8a1.5 1.5 0 0 1 0 2.6l-11.5 8A1.5 1.5 0 0 1 6 20.5v-17z"/> 1399 </svg> 1400 </button> 1401 1402 <!-- AO VIVO --> 1403 <button id="zmpLiveBtn" class="zmp-btn zmp-aux-size" title="Voltar ao Vivo" onclick="if(window.radioPlayer) window.radioPlayer.returnToLive()"> 1404 <svg class="zmp-svg zmp-show" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" style="max-width: 18px; max-height: 18px;"> 1405 <circle cx="12" cy="12" r="2.5" /> 1406 <path d="M7.74 16.26a6 6 0 0 1 0-8.52l1.42 1.42a4 4 0 0 0 0 5.68z" /> 1407 <path d="M16.26 16.26a6 6 0 0 0 0-8.52l-1.42 1.42a4 4 0 0 1 0 5.68z" /> 1408 <path d="M4.91 19.09a10 10 0 0 1 0-14.18l1.42 1.42a8 8 0 0 0 0 11.34z" /> 1409 <path d="M19.09 19.09a10 10 0 0 0 0-14.18l-1.42 1.42a8 8 0 0 1 0 11.34z" /> 1410 </svg> 1411 </button> 1412 1413 <!-- VOLUME --> 1414 <button id="volumeBtn" class="zmp-btn zmp-aux-size" title="Volume"> 1415 <svg id="volumeIcon" class="zmp-svg zmp-show" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"> 1416 <path fill="currentColor" d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/> 1417 </svg> 1418 </button> 1419 <input type="range" id="volumeSlider" class="zmp-volume-slider" min="0" max="100" value="90" /> 1420 1421 </div> <!-- /zmp-player-controls-wrapper --> 1422 1423 <!-- CSS BLINDADO DOS CONTROLES --> 1424 <style> 1425 .zmp-player-controls-wrapper { 1426 display: flex; 1427 align-items: center; 1428 gap: 16px; 1429 margin-left: 20px; 1430 } 1431 .zmp-btn { 1432 appearance: none !important; 1433 background: transparent !important; 1434 color: var(--player-text-color, #ffffff); 1435 border: none !important; 1436 border-radius: 50% !important; 1437 display: flex !important; 1438 align-items: center !important; 1439 justify-content: center !important; 1440 cursor: pointer; 1441 box-shadow: none !important; 1442 transition: opacity 0.2s ease, transform 0.15s ease; 1443 padding: 0 !important; 1444 } 1445 .zmp-btn:hover { 1446 opacity: 0.7; 1447 transform: scale(1.08); 1448 } 1449 .zmp-play-size { 1450 width: 48px !important; 1451 height: 48px !important; 1452 min-width: 48px !important; 1453 } 1454 .zmp-aux-size { 1455 width: 36px !important; 1456 height: 36px !important; 1457 min-width: 36px !important; 1458 } 1459 /* ââ BLINDAGEM MÃXIMA PARA SVG E DESIGN NEON ââ 1460 Garante que os controles respondam à "COR DOS ELEMENTOS" do painel, 1461 independentemente do fundo, restaurando o estilo Neon sem fills hardcoded. */ 1462 1463 /* 1. Cores e Fills dos Ãcones (SVGs e FontAwesome) */ 1464 html body #player #playBtn svg, 1465 html body #player #zmpLiveBtn svg, 1466 html body #player #volumeBtn svg, 1467 html body #player .zmp-svg, 1468 html body #player .zmp-btn i { 1469 display: block; 1470 color: var(--player-text-color, #ffffff) !important; 1471 fill: var(--player-text-color, #ffffff) !important; 1472 stroke: none !important; 1473 margin: 0 auto !important; 1474 pointer-events: none;
1475 width: 20px !important; 1476 height: auto !important; 1477 filter: none !important; 1478 } 1479 1480 /* Ajuste de tamanho especÃfico para Live e Volume */ 1481 html body #player #zmpLiveBtn svg, 1482 html body #player #volumeBtn svg, 1483 html body #player #volumeBtn i, 1484 html body #player #zmpLiveBtn i { 1485 width: 18px !important; 1486 } 1487 1488 /* Textos padronizados de AO VIVO */ 1489 html body #player .neon-player__live-text, 1490 html body #player .neon-watch-label { 1491 color: var(--player-text-color, #ffffff) !important; 1492 } 1493 html body #player .neon-player__live-dot { 1494 background: var(--player-text-color, #ffffff) !important; 1495 box-shadow: 0 0 6px color-mix(in srgb, var(--player-text-color, #ffffff) 60%, transparent) !important; 1496 } 1497 1498 html body #player #zmpIconLoad { 1499 fill: none !important; 1500 stroke: currentColor !important; 1501 } 1502 1503 /* 2. Formato e Glow dos Botões Principais (Restaurando o Neon) */ 1504 html body #player, html body .neon-player { 1505 background: var(--player-bg, rgba(20, 16, 33, 0.95)) !important; 1506 } 1507 1508 html body #player #playBtn, 1509 html body #player #zmpLiveBtn, 1510 html body #player #volumeBtn, 1511 html body #player .zmp-btn { 1512 background-color: transparent !important; 1513 box-shadow: none !important; 1514 display: flex; 1515 align-items: center; 1516 justify-content: center; 1517 cursor: pointer; 1518 transition: all 0.3s ease; 1519 border: none !important; /* Limpa bordas extras e heranças do themes/neon/style.css */ 1520 } 1521 1522 /* Play Button - Anel Neon Principal */ 1523 html body #player #playBtn { 1524 width: 48px !important; 1525 height: 48px !important; 1526 border-radius: 50% !important; 1527 border: 1.5px solid color-mix(in srgb, var(--player-text-color, #ffffff) 50%, transparent) !important; 1528 background: color-mix(in srgb, var(--player-text-color, #ffffff) 5%, transparent) !important; 1529 } 1530 html body #player #playBtn:hover { 1531 background: color-mix(in srgb, var(--player-text-color, #ffffff) 15%, transparent) !important; 1532 border-color: var(--player-text-color, #ffffff) !important; 1533 transform: scale(1.05); 1534 box-shadow: 0 0 15px color-mix(in srgb, var(--player-text-color, #ffffff) 30%, transparent) !important; 1535 } 1536 1537 /* Correção visual do play center gap (o triângulo engana o olho) */ 1538 html body #player #playBtn svg { 1539 transform: translateX(1.5px); 1540 } 1541 /* Quando está pausado, o SVG muda para pause/spinner, então removemos o shift */ 1542 html body #player #playBtn svg.animate-spin, 1543 html body #player #playBtn svg[class*="fa-pause"] { 1544 transform: translateX(0); 1545 } 1546 1547 /* Live/Volume Buttons - Anéis Menores */ 1548 html body #player #zmpLiveBtn, 1549 html body #player #volumeBtn { 1550 width: 36px !important; 1551 height: 36px !important; 1552 border-radius: 50% !important; 1553 border: 1.5px solid color-mix(in srgb, var(--player-text-color, #ffffff) 30%, transparent) !important; 1554 background: transparent !important; 1555 } 1556 html body #player #zmpLiveBtn:hover, 1557 html body #player #volumeBtn:hover { 1558 border-color: color-mix(in srgb, var(--player-text-color, #ffffff) 80%, transparent) !important; 1559 background: color-mix(in srgb, var(--player-text-color, #ffffff) 10%, transparent) !important; 1560 } 1561 1562 /* Slider de Volume */ 1563 .zmp-volume-slider { 1564 width: 80px; 1565 height: 4px; 1566 border-radius: 4px; 1567 appearance: none; 1568 background: color-mix(in srgb, var(--player-text-color, #ffffff) 20%, transparent); 1569 outline: none; 1570 cursor: pointer; 1571 } 1572 .zmp-volume-slider::-webkit-slider-thumb { 1573 appearance: none; 1574 width: 12px; 1575 height: 12px; 1576 border-radius: 50%; 1577 background: var(--player-text-color, #ffffff); 1578 cursor: pointer; 1579 box-shadow: 0 1px 4px rgba(0,0,0,0.3); 1580 } 1581 1582 /* Responsive Hiding se a tela for mto pequena pro volume (inclui celular em paisagem) */ 1583 @media (max-width: 1024px), (pointer: coarse) and (max-width: 1366px) { 1584 #zmpVolBtn, .zmp-volume-slider { display: none !important; }
1585 .zmp-player-controls-wrapper { margin-left: auto; gap: 12px; } 1586 } 1587 </style> 1588 1589 1590 </div><!-- /.neon-player__left-group --> 1591 1592 <!-- ââââ GRUPO DIREITO: Botão ASSISTIR AO VIVO ââââ --> 1593 <!-- Aparece apenas quando o widget de vÃdeo existe no DOM (JS controla) --> 1594 <div class="neon-player__video-btn-wrap" id="neonVideoWatchBtn" style="display:none;"> 1595 <button class="neon-player__watch-btn" id="btnAssistirAoVivo" title="Assistir transmissão ao vivo"> 1596 <span class="neon-watch-dot"></span> 1597 <span class="neon-watch-label">ASSISTIR AO VIVO</span> 1598 </button> 1599 </div> 1600 1601 </div><!-- /.neon-player__inner --> 1602 <div id="visualizer" style="display:none;"></div> 1603</div> 1604 1605<style> 1606/* ââââââââââââââââââââââââââââââââââââââââââââââââ 1607 NEON PLAYER â Layout: Left-group + Right-button 1608 ââââââââââââââââââââââââââââââââââââââââââââââââ */ 1609 1610/* O inner agora é: left-group flex-1 | btn-wrap flex-shrink-0 */ 1611.neon-player__inner { 1612 display : flex; 1613 align-items : center; 1614 justify-content: space-between; 1615 max-width : 1440px; 1616 margin : 0 auto; 1617 padding : 12px 32px; 1618 gap : 24px; 1619} 1620 1621/* Grupo esquerdo: agrupa artwork, controles e volume numa fileira */ 1622.neon-player__left-group { 1623 display : flex; 1624 align-items: center; 1625 gap : 24px; 1626 flex : 1; 1627 min-width : 0; 1628} 1629 1630/* Fix: info não cresce mais (ocupa apenas o que precisa) */ 1631.neon-player__info { 1632 display : flex; 1633 align-items: center; 1634 gap : 14px; 1635 min-width : 0; 1636 /* Remove o flex:1 antigo â agora o left-group controla o crescimento */ 1637} 1638 1639/* ââ Botão ASSISTIR AO VIVO (direita) ââ */ 1640.neon-player__video-btn-wrap { 1641 flex-shrink: 0; 1642 margin-left: auto; /* empurra para a extrema direita */ 1643} 1644 1645.neon-player__watch-btn { 1646 display : flex; 1647 align-items : center; 1648 gap : 8px; 1649 background : transparent; 1650 color : var(--player-text-color, #ffffff); 1651 border : 1px solid color-mix(in srgb, var(--player-text-color, #ffffff) 40%, transparent); 1652 font-size : 11px; 1653 font-weight : 800; 1654 letter-spacing : 0.8px; 1655 text-transform : uppercase; 1656 padding : 9px 20px; 1657 border-radius : 999px; 1658 cursor : pointer; 1659 white-space : nowrap; 1660 transition : all 0.2s ease; 1661 font-family : 'Space Grotesk', 'Inter', sans-serif; 1662 box-shadow : none; 1663} 1664 1665.neon-player__watch-btn:hover { 1666 background: color-mix(in srgb, var(--player-text-color, #ffffff) 10%, transparent); 1667 transform: scale(1.02); 1668} 1669 1670.neon-watch-dot { 1671 width : 7px; 1672 height : 7px; 1673 border-radius: 50%; 1674 background : var(--player-text-color, #ffffff); 1675 flex-shrink : 0; 1676 animation : neon-watch-pulse 1.5s ease-in-out infinite; 1677} 1678 1679@keyframes neon-watch-pulse { 1680 0%, 100% { opacity: 1; transform: scale(1); } 1681 50% { opacity: 0.3; transform: scale(0.85); } 1682} 1683 1684/* Estado: vÃdeo aberto â botão muda para "FECHAR VÃDEO" */ 1685.neon-player__watch-btn.is-open { 1686 background : color-mix(in srgb, var(--player-text-color, #ffffff) 15%, transparent); 1687 color : var(--player-text-color, #ffffff); 1688 border : 1px solid color-mix(in srgb, var(--player-text-color, #ffffff) 40%, transparent); 1689 box-shadow : none; 1690} 1691 1692.neon-player__watch-btn.is-open .neon-watch-dot { 1693 animation: none; 1694 opacity : 1; 1695} 1696 1697/* Mobile: ocultar botão de vÃdeo (não aplicável) â inclui celular em paisagem */ 1698@media (max-width: 1024px), (pointer: coarse) and (max-width: 1366px) { 1699 .neon-player__video-btn-wrap { display: none !important; } 1700 .neon-player__inner { padding: 10px 16px; gap: 12px; } 1701 .neon-player__left-group { gap: 12px; } 1702} 1703</style> 1704
1705<script> 1706(function() { 1707 function initWatchBtn() { 1708 var btn = document.getElementById('btnAssistirAoVivo'); 1709 var btnWrap = document.getElementById('neonVideoWatchBtn'); 1710 var widget = document.getElementById('vmpWidget'); 1711 var label = btn ? btn.querySelector('.neon-watch-label') : null; 1712 1713 // Só exibe o botão se o widget de vÃdeo existir no DOM 1714 if (!widget || !btn || !label) return; 1715 btnWrap.style.display = ''; 1716 1717 function syncLabel() { 1718 var isOpen = widget.style.display !== 'none'; 1719 btn.classList.toggle('is-open', isOpen); 1720 label.textContent = isOpen ? 'FECHAR VÃDEO' : 'ASSISTIR AO VIVO'; 1721 } 1722 1723 btn.addEventListener('click', function() { 1724 var isOpen = widget.style.display !== 'none'; 1725 widget.style.display = isOpen ? 'none' : 'block'; 1726 syncLabel(); 1727 }); 1728 1729 // Sincronizar quando o botão à interno do mini player for clicado 1730 var closeBtn = document.getElementById('vmpCloseBtn'); 1731 if (closeBtn) { 1732 closeBtn.addEventListener('click', function() { 1733 setTimeout(syncLabel, 50); 1734 }); 1735 } 1736 1737 syncLabel(); // estado inicial 1738 } 1739 1740 if (document.readyState === 'loading') { 1741 document.addEventListener('DOMContentLoaded', initWatchBtn); 1742 } else { 1743 setTimeout(initWatchBtn, 200); 1744 } 1745 1746})(); 1747</script>
1747 1748 <!-- Video Player Global: movido para dentro do #mainContent (topo) â Fix 3 --> 1749 <!-- O singleton define('VIDEO_PLAYER_GLOBAL_RENDERED') previne dupla renderização --> 1750 1751 1752<footer class="neon-footer"> 1753 <div class="neon-footer__accent-line"></div> 1754 <div class="neon-footer__inner"> 1755 1756 <!-- Col 1: Marca / About --> 1757 <div class="neon-footer__col"> 1758 <a href="home" class="neon-footer__logo"> 1759 <img src="https://radio93portofeliz.com.br/uploads/media/6a05e3548b2fc_1778770772.png" 1760 alt="Rádio 93 FM"> 1761 </a> 1762 <p class="neon-footer__site-name">Rádio 93 FM</p> 1763 <p class="neon-footer__desc">A primeira em Porto Feliz!</p> 1764 <div class="neon-footer__social"> 1765 <a href="https://www.instagram.com/RADIO93PORTOFELIZ" target="_blank" rel="noopener" class="neon-footer__social-btn" title="Instagram"> 1766 <i class="fab fa-instagram"></i> 1767 </a> 1768 <a href="https://www.facebook.com/radio93portofeliz" target="_blank" rel="noopener" class="neon-footer__social-btn" title="Facebook"> 1769 <i class="fab fa-facebook-f"></i> 1770 </a> 1771 <a href="https://www.youtube.com/radio93portofeliz" target="_blank" rel="noopener" class="neon-footer__social-btn" title="YouTube"> 1772 <i class="fab fa-youtube"></i> 1773 </a> 1774 <a href="https://play.google.com/store/apps/details?id=radio93portofeliz.uhost.br&hl=pt_BR" target="_blank" rel="noopener" class="neon-footer__social-btn" title="Android"> 1775 <i class="fab fa-android"></i> 1776 </a> 1777 <a href="https://apps.apple.com/br/app/r%C3%A1dio-93-porto-feliz/id6773632501" target="_blank" rel="noopener" class="neon-footer__social-btn" title="iOS"> 1778 <i class="fab fa-apple"></i> 1779 </a> 1780 <a href="https://wa.me/5515996090935" target="_blank" rel="noopener" class="neon-footer__social-btn" title="WhatsApp"> 1781 <i class="fab fa-whatsapp"></i> 1782 </a> 1783 </div> 1784 </div> 1785 1786 <!-- Col 2 e 3: Navegação (2 colunas) --> 1787 <div class="neon-footer__col neon-footer__col--nav"> 1788 <h4 class="neon-footer__heading">Navegação</h4> 1789 <ul class="neon-footer__links neon-footer__links--two-cols"> 1790 <li><a href="a-radio">A rádio</a></li> 1791 <li><a href="equipe">Equipe</a></li> 1792 <li><a href="programacao">Programação</a></li> 1793 <li><a href="promocoes">Promoções</a></li> 1794 <li><a href="contato">Contato</a></li> 1795 </ul> 1796 </div> 1797 1798 <!-- Col 4: Contato --> 1799 <div class="neon-footer__col"> 1800 <h4 class="neon-footer__heading">Contato</h4> 1801 <ul class="neon-footer__contact"> 1802 <li> 1803 <!-- WhatsApp (mantido FA Brands) --> 1804 <i class="fab fa-whatsapp"></i> 1805 <a href="https://wa.me/5515996090935" target="_blank" rel="noopener"> 1806 (15) 996090935 </a> 1807 </li> 1808 <li> 1809 <!-- Lucide: phone --> 1810 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.56 1.2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
1811 <span>(15) 3237-1955</span> 1812 </li> 1813 <li> 1814 <!-- Lucide: mail --> 1815 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg> 1816 <a href="mailto:[email protected]">[email protected]</a> 1817 </li> 1818 <li> 1819 <!-- Lucide: mail --> 1820 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg> 1821 <a href="mailto:[email protected]">[email protected]</a> 1822 </li> 1823 <li> 1824 <!-- Lucide: mail --> 1825 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg> 1826 <a href="mailto:[email protected]">[email protected]</a> 1827 </li> 1828 </ul> 1829 </div> 1830 </div> 1831 1832 <!-- Barra de Copyright --> 1833 <div class="neon-footer__bottom"> 1834 <div class="neon-footer__bottom-inner"> 1835 <div class="neon-footer__b-left"> 1836 <p class="neon-footer__copyright"> 1837 Todos os Direito Reservados - Rádio 93 FM · <a href="politica-de-privacidade" class="neon-footer__privacy-link">PolÃtica de Privacidade</a> 1838 </p> 1839 </div> 1840 <div class="neon-footer__b-right"> 1841 <a href="https://uhost.com.br/" target="_blank" rel="noopener" class="neon-footer__dev-link"> 1842 <img src="https://radio93portofeliz.com.br/uploads/media/6a1501d3687d1_1779761619.png" 1843 alt="Desenvolvido pela UHOST" 1844 class="neon-footer__dev-logo" 1845 onerror="this.closest('.neon-footer__dev-link').style.display='none'"> 1846 </a> 1847 </div> 1848 </div> 1849 </div> 1850</footer> 1851
1852<script> 1853document.addEventListener('DOMContentLoaded', () => { 1854 const handleShelves = () => { 1855 if (window.innerWidth > 768) { 1856 document.querySelectorAll('.shelf-dots').forEach(d => d.style.display = 'none'); 1857 return; 1858 } else { 1859 document.querySelectorAll('.shelf-dots').forEach(d => d.style.display = 'flex'); 1860 } 1861 1862 const shelves = document.querySelectorAll('.shelf-row'); 1863 shelves.forEach(shelf => { 1864 // Touch handler aplicado SEMPRE â independente de haver dots 1865 let _tx = 0, _tl = 0; 1866 shelf.addEventListener('touchstart', e => { 1867 _tx = e.touches[0].clientX; 1868 _tl = shelf.scrollLeft; 1869 }, { passive: true }); 1870 shelf.addEventListener('touchmove', e => { 1871 shelf.scrollLeft = _tl + (_tx - e.touches[0].clientX); 1872 }, { passive: true }); 1873 1874 // Encontra o container dos dots (pode estar fora do layout-wrapper, dentro do .container) 1875 const dotContainer = shelf.closest('.container')?.querySelector('.shelf-dots') ?? null; 1876 1877 if (!dotContainer) return; 1878 1879 const cards = Array.from(shelf.children); 1880 if (cards.length === 0) return; 1881 1882 // Evitar duplicações 1883 if (dotContainer.children.length === cards.length) return; 1884 1885 // Criar dots 1886 dotContainer.innerHTML = ''; 1887 cards.forEach((_, i) => { 1888 const dot = document.createElement('div'); 1889 dot.classList.add('dot'); 1890 if (i === 0) dot.classList.add('active'); 1891 dotContainer.appendChild(dot); 1892 }); 1893 1894 const dots = Array.from(dotContainer.children); 1895 1896 // Observer para atualizar estado ativo 1897 const observer = new IntersectionObserver((entries) => { 1898 entries.forEach(entry => { 1899 if (entry.isIntersecting) { 1900 const index = cards.indexOf(entry.target); 1901 if (index !== -1 && dots[index]) { 1902 dots.forEach(d => d.classList.remove('active')); 1903 dots[index].classList.add('active'); 1904 } 1905 } 1906 }); 1907 }, { 1908 root: shelf, 1909 threshold: 0.6 // pelo menos 60% visÃvel para ativar 1910 }); 1911 1912 cards.forEach(card => observer.observe(card)); 1913 }); 1914 }; 1915 1916 handleShelves(); 1917 window.addEventListener('resize', handleShelves); 1918}); 1919</script>
1919 1920 1921 1922 <!-- ===== WHATSAPP WIDGET (Global â todos os temas) ===== --> 1923 <!-- Oculto automaticamente se nenhum número estiver configurado --> 1924 <!-- Oculto em mobile via CSS (max-width: 768px) --> 1925 <link rel="stylesheet" href="https://radio93portofeliz.com.br/assets/css/whatsapp-widget.css?v=1779801036"> 1926
1926<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" defer></script>
1926 1927
1927<script src="https://radio93portofeliz.com.br/assets/js/whatsapp-widget.js" defer></script>
1927 1928 1929<!-- ===== WHATSAPP WIDGET (Global) ===== --> 1930 1931<!-- Botão Flutuante --> 1932<button id="wa-fab" 1933 aria-label="Fale conosco pelo WhatsApp" 1934 title="Fale conosco pelo WhatsApp"> 1935 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 1936 <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/> 1937 </svg> 1938</button> 1939 1940<!-- Overlay --> 1941<div id="wa-overlay" aria-hidden="true"></div> 1942 1943<!-- Painel lateral (Drawer) --> 1944<aside id="wa-panel" role="dialog" aria-modal="true" aria-label="WhatsApp"> 1945 1946 <!-- Cabeçalho --> 1947 <div id="wa-panel-header"> 1948 <p class="wa-ph-title"> 1949 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 1950 <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/> 1951 </svg> 1952 WhatsApp 1953 </p> 1954 <button id="wa-close-btn" aria-label="Fechar">✕</button> 1955 </div> 1956 1957 <!-- Corpo --> 1958 <div id="wa-panel-body"> 1959 1960 <p class="wa-body-title">Escaneie o QR Code e fale conosco pelo WhatsApp</p> 1961 1962 <!-- QR Code gerado via whatsapp-widget.js --> 1963 <div id="wa-qr-wrap" aria-label="QR Code WhatsApp"></div> 1964 1965 <p class="wa-body-hint"> 1966 Aponte a câmera do celular para o código acima 1967 </p> 1968 1969 </div> 1970</aside> 1971 1972<!-- Variáveis injetadas para o JS -->
1973<script> 1974var WA_NUMBER = "5515996090935"; 1975var WA_BTN_POS = "bottom-left"; 1976</script>
1976 1977 1978 <!-- Cookie Banner Global (Ajuste 3) --> 1979 <!-- ===== COOKIE BANNER â Bottom Sheet Nativo ===== --> 1980<div 1981 id="cookieBanner" 1982 class="cookie-banner" 1983 role="alertdialog" 1984 aria-live="polite" 1985 aria-label="Aviso de privacidade" 1986 aria-describedby="cookieBannerDesc" 1987 style="display:none;" 1988> 1989 <!-- Handle pill (drag indicator nativo) --> 1990 <div class="cookie-banner__handle" aria-hidden="true"></div> 1991 1992 <!-- Cabeçalho estilo app nativo --> 1993 <div class="cookie-banner__header"> 1994 <div class="cookie-banner__app-icon" aria-hidden="true"> 1995 <img src="https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png" alt="" width="46" height="46" loading="lazy"> 1996 </div> 1997 <div class="cookie-banner__app-info"> 1998 <p class="cookie-banner__app-name">Sua privacidade</p> 1999 <p class="cookie-banner__app-host">Rádio 93 FM · radio93portofeliz.com.br</p> 2000 </div> 2001 </div> 2002 2003 <!-- Separador --> 2004 <div class="cookie-banner__divider" aria-hidden="true"></div> 2005 2006 <!-- Texto explicativo --> 2007 <p class="cookie-banner__desc" id="cookieBannerDesc"> 2008 Usamos cookies para manter o app funcionando e melhorar sua experiência. Nenhum dado é vendido a terceiros. 2009 <a href="https://radio93portofeliz.com.br/?page=politica-de-privacidade" target="_blank" rel="noopener" class="cookie-banner__privacy-link">PolÃtica de privacidade</a> 2010 </p> 2011 2012 <!-- Ações empilhadas (estilo nativo) --> 2013 <div class="cookie-banner__actions"> 2014 <button 2015 id="cookieBannerAccept" 2016 class="cookie-banner__btn cookie-banner__btn--primary" 2017 type="button" 2018 aria-label="Aceitar todos os cookies" 2019 > 2020 Aceitar todos 2021 </button> 2022 <button 2023 id="cookieBannerDecline" 2024 class="cookie-banner__btn cookie-banner__btn--secondary" 2025 type="button" 2026 aria-label="Aceitar apenas cookies essenciais" 2027 > 2028 Apenas essenciais 2029 </button> 2030 </div> 2031</div> 2032
2033<script> 2034/* ââ Cookie Banner JS (IIFE) ââ */ 2035(function () { 2036 'use strict'; 2037 2038 var STORAGE_KEY = 'cookie_consent'; 2039 var banner = document.getElementById('cookieBanner'); 2040 if (!banner) return; 2041 2042 /* Se o usuário já escolheu, não mostrar novamente */ 2043 try { 2044 if (localStorage.getItem(STORAGE_KEY)) return; 2045 } catch (e) { 2046 return; 2047 } 2048 2049 /* ââ Animação de entrada (slide-up) ââ */ 2050 function showBanner() { 2051 banner.style.display = 'flex'; 2052 void banner.offsetHeight; 2053 banner.classList.add('cookie-banner--visible'); 2054 } 2055 2056 function hideBanner(callback) { 2057 banner.classList.remove('cookie-banner--visible'); 2058 banner.classList.add('cookie-banner--hiding'); 2059 setTimeout(function () { 2060 banner.style.display = 'none'; 2061 banner.classList.remove('cookie-banner--hiding'); 2062 if (typeof callback === 'function') callback(); 2063 }, 400); 2064 } 2065 2066 function saveChoice(choice) { 2067 try { localStorage.setItem(STORAGE_KEY, choice); } catch (e) {} 2068 } 2069 2070 /* ââ Mostrar após 1.2s ââ */ 2071 setTimeout(showBanner, 1200); 2072 2073 /* ââ Botão Aceitar todos ââ */ 2074 var acceptBtn = document.getElementById('cookieBannerAccept'); 2075 if (acceptBtn) { 2076 acceptBtn.addEventListener('click', function () { 2077 saveChoice('accepted'); 2078 hideBanner(function () { 2079 document.dispatchEvent(new CustomEvent('cookie:accepted')); 2080 }); 2081 }); 2082 } 2083 2084 /* ââ Botão Apenas essenciais ââ */ 2085 var declineBtn = document.getElementById('cookieBannerDecline'); 2086 if (declineBtn) { 2087 declineBtn.addEventListener('click', function () { 2088 saveChoice('essential'); 2089 hideBanner(function () { 2090 document.dispatchEvent(new CustomEvent('cookie:declined')); 2091 }); 2092 }); 2093 } 2094 2095 /* ââ Fechar com Escape ââ */ 2096 document.addEventListener('keydown', function (e) { 2097 if (e.key === 'Escape' && banner.classList.contains('cookie-banner--visible')) { 2098 saveChoice('essential'); 2099 hideBanner(); 2100 } 2101 }); 2102})(); 2103</script>
2103 2104 2105 <!-- Smart App Install Banner (global â CTA de loja, prompt PWA ou instruções iOS). 2106 Dentro do app Rádio 3.0 não faz sentido convidar a instalar o app. --> 2107 2108 <!-- Popup de Abertura (global â campanha cadastrada em /?page=banners). 2109 Sem campanha ativa não emite CSS, JS nem markup. --> 2110 2111 <!-- Menu Mobile Global (Apenas Mobile via CSS). Também traz o estilo de mini 2112 player e o player expandido â nada disso existe dentro do app Rádio 3.0. --> 2113 2114<nav class="mobile-bottom-nav"> 2115 <!-- Item 1: fixo (Página Inicial) --> 2116 <a class="nav-item" href="https://radio93portofeliz.com.br"> 2117 <div class="nav-icon-box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9.5z"/><path d="M9 21V12h6v9"/></svg></div> 2118 </a> 2119 2120 <!-- Itens 2-5: dinâmicos --> 2121 <a class="nav-item" href="/promocoes"> 2122 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 12 20 22 4 22 4 12"/><rect x="2" y="7" width="20" height="5"/><line x1="12" y1="22" x2="12" y2="7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/></svg> <span>Promoções</span> 2123 </a> 2124 <a class="nav-item" href="https://www.youtube.com/radio93portofeliz"> 2125 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg> <span>Estúdio</span> 2126 </a> 2127 <a class="nav-item" href="/programacao"> 2128 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg> <span>Programação</span> 2129 </a> 2130 <a class="nav-item" href="/contato"> 2131 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> <span>Contato</span> 2132 </a> 2133 </nav> 2134 2135<style> 2136.mobile-bottom-nav { 2137 display: none; 2138} 2139@media (max-width: 1024px), (pointer: coarse) and (max-width: 1366px) { 2140 .mobile-bottom-nav { 2141 position: fixed; 2142 bottom: 0; left: 0; right: 0; 2143 background: var(--bg-card, #ffffff); 2144 border-top: 1px solid var(--border-color, #e0e0e0); 2145 display: flex; 2146 justify-content: space-around; 2147 align-items: flex-start; 2148 padding: 12px 0 calc(12px + env(safe-area-inset-bottom)); 2149 z-index: 9999; 2150 } 2151} 2152 2153html[data-theme="dark"] .mobile-bottom-nav, 2154body.dark-mode .mobile-bottom-nav { 2155 background: var(--bg-card, #121214); 2156 border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.08)); 2157} 2158 2159.mobile-bottom-nav .nav-item { 2160 all: unset; 2161 display: flex !important; 2162 flex-direction: column !important; 2163 align-items: center !important; 2164 gap: 5px !important; 2165 cursor: pointer !important; 2166 padding: 4px 10px !important; 2167 text-decoration: none !important; 2168 box-sizing: border-box !important; 2169} 2170 2171.mobile-bottom-nav .nav-item svg { stroke: #9e9e9e; width: 24px; height: 24px; transition: all 0.2s; } 2172.mobile-bottom-nav .nav-item span { font-size: 11px; color: #9e9e9e; font-weight: 500; transition: all 0.2s; } 2173 2174/* Hover â feedback visual ao toque */ 2175.mobile-bottom-nav .nav-item:hover svg, 2176.mobile-bottom-nav .nav-item:focus svg { stroke: var(--primary-color, #1B1464); opacity: 0.7; } 2177.mobile-bottom-nav .nav-item:hover span, 2178.mobile-bottom-nav .nav-item:focus span { color: var(--primary-color, #1B1464); opacity: 0.7; } 2179 2180/* Ativo â página atual */ 2181.mobile-bottom-nav .nav-item.active svg { stroke: var(--primary-color, #1B1464); opacity: 1; } 2182.mobile-bottom-nav .nav-item.active span { color: var(--primary-color, #1B1464); font-weight: 500; opacity: 1; } 2183 2184.mobile-bottom-nav .nav-icon-box { 2185 width: 48px; height: 48px; 2186 border-radius: 14px; 2187 background: var(--primary-color, #1B1464); 2188 display: flex; align-items: center; justify-content: center; 2189 margin-top: -6px; /* Para alinhar opticamente a caixa maior com os outros Ãcones */ 2190 transition: all 0.2s; 2191} 2192 2193/* Ãcone box do item 1 quando ativo */ 2194.mobile-bottom-nav .nav-item.active .nav-icon-box { background: var(--primary-color, #1B1464); } 2195 2196/* Ãcone box do item 1 quando inativo */ 2197.mobile-bottom-nav .nav-item:not(.active) .nav-icon-box { background: var(--border-color, #e0e0e0); } 2198.mobile-bottom-nav .nav-item:not(.active) .nav-icon-box svg { stroke: #9e9e9e !important; opacity: 1; } 2199 2200/* Home icon should be white inside the active solid box */ 2201.mobile-bottom-nav .nav-item.active .nav-icon-box svg { stroke: #ffffff !important; } 2202 2203/* Prevent overrides from global player svg rule modifying this one if any */ 2204
2205/* Para não sobrepor o footer em sites mobile, adicione padding bottom global body via classe */ 2206@media (max-width: 1024px), (pointer: coarse) and (max-width: 1366px) { 2207 body { 2208 padding-bottom: calc(148px + env(safe-area-inset-bottom)) !important; 2209 } 2210 2211 /* ========================================= 2212 REESTILIZAÃÃO GLOBAL DO PLAYER MOBILE 2213 ESTILO DEEZER (PÃLULA FLUTUANTE LIMPA) 2214 ========================================= */ 2215 #player, .radio-player, .neon-player, .d5-player, .app-player, .player-container, .global-player { 2216 position: fixed !important; 2217 bottom: calc(85px + env(safe-area-inset-bottom)) !important; 2218 left: 12px !important; 2219 right: 12px !important; 2220 width: auto !important; 2221 height: 64px !important; 2222 min-height: 64px !important; 2223 max-height: 64px !important; 2224 box-sizing: border-box !important; 2225 align-items: center !important; 2226 overflow: hidden !important; /* garante que nada vaze para fora */ 2227 border-radius: 12px !important; 2228 background: var(--player-bg, #181818) !important; 2229 box-shadow: 0 8px 25px rgba(0,0,0,0.4) !important; 2230 border: 1px solid var(--border-color, rgba(255,255,255,0.06)) !important; 2231 padding: 0 12px !important; 2232 z-index: 9998 !important; 2233 } 2234 2235 /* Container global - forçar flex row */ 2236 #player > div, 2237 .neon-player__inner, 2238 .d5-player-inner { 2239 display: flex !important; 2240 flex-direction: row !important; 2241 align-items: center !important; 2242 justify-content: flex-start !important; 2243 padding: 0 !important; 2244 height: 100% !important; 2245 gap: 10px !important; 2246 width: 100% !important; 2247 border: none !important; 2248 } 2249 2250 /* Ocultar elementos desnecessários (Artwork e Status Badge agora são visÃveis) */ 2251 .neon-player__video-btn-wrap, 2252 .wave-player__video-btn-wrap, 2253 .pulse-player__video-btn-wrap, 2254 .glow-player__video-btn-wrap, 2255 .d5-volume-ctrl, 2256 .zmp-volume-slider, 2257 #volumeBtn, 2258 #liveBtn, 2259 #zmpLiveBtn, 2260 #videoBtn, 2261 .d5-play-ring, 2262 #visualizer, 2263 .neon-player__watch-btn, 2264 .wave-player__watch-btn, 2265 .pulse-player__watch-btn, 2266 .glow-player__watch-btn { 2267 display: none !important; 2268 } 2269 2270 /* ==== Ajuste: Alinhamento exato da Capa e Botão Play (Tamanho fixo 48x48) ==== 2271 Cada tema pode redefinir o tamanho da capa com especificidade maior 2272 (ex.: WAVE usa 52px no brutalism_override.css). O que importa aqui é que 2273 o slot NUNCA saia do fluxo e não mude de tamanho entre estados. */ 2274 .neon-player__artwork, 2275 .d5-player-artwork, 2276 .player-artwork, 2277 #playerArtwork, 2278 .wave-player__artwork, 2279 .pulse-player__artwork, 2280 .glow-player__artwork, 2281 .play-btn, 2282 .neon-player__play-btn, 2283 .d5-play-btn, 2284 #playBtn, 2285 .global-play-btn, 2286 .wave-player__play-btn { 2287 width: 48px !important; 2288 height: 48px !important; 2289 min-width: 48px !important; 2290 max-width: 48px !important; 2291 flex-shrink: 0 !important; 2292 align-self: center !important; 2293 display: flex !important; 2294 align-items: center !important; 2295 justify-content: center !important; 2296 box-sizing: border-box !important; 2297 margin: 0 !important; 2298 padding: 0 !important; 2299 } 2300 2301 .neon-player__artwork, 2302 .d5-player-artwork, 2303 .player-artwork, 2304 #playerArtwork, 2305 .wave-player__artwork, 2306 .pulse-player__artwork, 2307 .glow-player__artwork { 2308 border-radius: 6px !important; /* Capa quadrada com cantos suaves no mobile */ 2309 overflow: hidden !important; 2310 display: block !important; 2311 } 2312 2313 .neon-player__artwork img, 2314 .d5-player-artwork img, 2315 .player-artwork img, 2316 #playerArtwork img, 2317 .wave-player__artwork img, 2318 .pulse-player__artwork img, 2319 .glow-player__artwork img { 2320 width: 48px !important; 2321 height: 48px !important; 2322 object-fit: cover !important; 2323 display: block !important; 2324 } 2325 2326 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2327 ESQUELETO ÃNICO DO MINI PLAYER (NEON / WAVE / PULSE / GLOW) 2328 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2329 Regra de ouro: o layout NÃO depende do conteúdo. A ordem e a 2330 distribuição são as mesmas com ou sem metadados â só o texto e a 2331 imagem mudam. Ordem fixa: 2332 capa â badge â tÃtulo â artista â play (colado na direita) 2333 IMPORTANTE: usamos #player como prefixo para ter especificidade 2334 maior que as regras inline no @media do player.php. --- */ 2335 #player .neon-player__left-group, 2336 #player .wave-player__left-group, 2337 #player .pulse-player__left-group, 2338 #player .glow-player__left-group { 2339 display: flex !important; 2340 flex-direction: row !important; 2341 align-items: center !important; 2342 justify-content: flex-start !important; /* sobrescreve qualquer space-between herdado */ 2343 gap: 10px !important; 2344 flex: 1 1 0 !important; 2345 width: 100% !important; 2346 min-width: 0 !important; 2347 overflow: hidden !important; 2348 } 2349 2350 /* Bloco capa + texto: ocupa TODA a sobra, sempre. à esse flex:1 1 0 que 2351 impede o botão play de escorregar para o meio quando o texto é curto. */ 2352 #player .neon-player__info, 2353 #player .wave-player__info, 2354 #player .pulse-player__info, 2355 #player .glow-player__info { 2356 display: flex !important; 2357 flex-direction: row !important; 2358 align-items: center !important; 2359 justify-content: flex-start !important; 2360 flex: 1 1 0 !important; 2361 min-width: 0 !important; 2362 gap: 10px !important; 2363 order: 1 !important; /* capa + texto primeiro */ 2364 overflow: hidden !important; 2365 } 2366 2367 /* Coluna de texto: min-width:0 é obrigatório para o ellipsis funcionar 2368 dentro de um flex item (sem ele o texto empurra o container). */ 2369 #player .wave-player__meta, 2370 #player .neon-player__meta, 2371 #player .pulse-player__meta, 2372 #player .glow-player__meta, 2373 #player .d5-player-meta, 2374 #player .player-meta, 2375 #player .nmp-meta-wrap { 2376 display: flex !important; 2377 flex-direction: column !important; 2378 justify-content: center !important; 2379 gap: 0 !important; 2380 line-height: 1 !important; 2381 overflow: hidden !important; 2382 min-width: 0 !important; 2383 flex: 1 1 0 !important; 2384 } 2385 2386 /* Play ancorado na borda direita em TODOS os estados. 2387 margin-left:auto + order:2 = sempre o último, sempre colado à direita.
2388 (o margin-left:20px do player.php perde: 0,2,0 vs 0,1,0) */ 2389 #player .zmp-player-controls-wrapper { 2390 margin: 0 0 0 auto !important; 2391 padding: 0 !important; 2392 order: 2 !important; 2393 flex: 0 0 auto !important; 2394 gap: 0 !important; 2395 width: auto !important; 2396 } 2397 2398 /* ---- LÃGICA DEMO 05 ---- */ 2399 .d5-player-inner > div:nth-child(2) { 2400 order: -1 !important; /* Bloco do Play vai para a esquerda */ 2401 margin: 0 !important; 2402 margin-right: -4px !important; 2403 flex: 0 0 auto !important; 2404 width: auto !important; 2405 } 2406 .d5-player-left { 2407 order: 2 !important; /* Bloco de Info vai para a direita do Play */ 2408 flex: 1 !important; 2409 width: auto !important; 2410 gap: 0 !important; 2411 } 2412 .d5-player-inner > div:nth-child(3) { 2413 display: none !important; /* Esconde botões extras estendidos */ 2414 } 2415 .d5-player-meta { 2416 justify-content: center !important; 2417 min-height: 0 !important; 2418 gap: 0 !important; 2419 } 2420 2421 /* ---- TIPOGRAFIA COMUM (TITULO / ARTISTA) ---- 2422 As três linhas têm ALTURA RESERVADA (badge 10px, tÃtulo 16px, 2423 artista 12px). Linha vazia continua ocupando o mesmo espaço, então 2424 trocar de estado não causa reflow nem salto de altura. */ 2425 .player-status-badge, 2426 .neon-player__status, 2427 .wave-player__status, 2428 .pulse-player__status, 2429 .glow-player__status, 2430 .live-badge, 2431 .wave-player__live-text { 2432 display: block !important; 2433 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important; 2434 font-size: 10px !important; 2435 font-weight: 700 !important; 2436 text-transform: uppercase !important; 2437 letter-spacing: 0.08em !important; 2438 margin-bottom: 3px !important; 2439 line-height: 1 !important; 2440 height: 10px !important; 2441 flex: 0 0 10px !important; 2442 color: var(--player-text-color, #ffffff) !important; 2443 } 2444 2445 .neon-player__title, 2446 .d5-player-title, 2447 .wave-player__title, 2448 .pulse-player__title, 2449 .glow-player__title, 2450 .player-title { 2451 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important; 2452 font-size: 13px !important; 2453 font-weight: 800 !important; 2454 color: var(--player-text-color, #ffffff) !important; 2455 text-transform: uppercase !important; 2456 width: 100% !important; 2457 text-shadow: none !important; 2458 -webkit-text-stroke: 0 !important; 2459 line-height: 16px !important; 2460 height: 16px !important; 2461 flex: 0 0 16px !important; 2462 display: block; /* sem !important: o JS do marquee precisa poder ocultar */ 2463 padding: 0 !important; 2464 margin: 0 0 2px 0 !important; 2465 white-space: nowrap !important; 2466 overflow: hidden !important; 2467 text-overflow: ellipsis !important; 2468 } 2469 2470 /* Oculta os elementos originais quando o marquee está ativo â usa !important 2471 para sobrescrever regras de tema (ex: wave-player__title tem display:block !important) */ 2472 #player .nmp-src-hidden { 2473 display: none !important; 2474 } 2475 2476 .neon-player__artist, 2477 .d5-player-artist, 2478 .wave-player__artist, 2479 .pulse-player__artist, 2480 .glow-player__artist, 2481 .player-artist { 2482 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important; 2483 font-size: 11px !important; 2484 font-weight: 400 !important; 2485 color: var(--player-text-color, #ffffff) !important; 2486 opacity: 0.8 !important; 2487 text-transform: uppercase !important; 2488 white-space: nowrap !important; 2489 overflow: hidden !important; 2490 text-overflow: ellipsis !important; 2491 width: 100% !important; 2492 line-height: 12px !important; 2493 height: 12px !important; 2494 flex: 0 0 12px !important; 2495 display: block; 2496 padding: 0 !important; 2497 margin: 0 !important; 2498 } 2499 2500 /* ---- AJUSTE 3: ANIMAÃÃO MARQUEE ---- */ 2501 --marquee-duration: 10s; 2502 2503 /* Wrapper de texto do mini player mobile. 2504 Nenhum tema gera .nmp-meta-wrap hoje; mantido para compatibilidade. 2505 order:1 para acompanhar o esqueleto (capa+texto antes do play). */ 2506 .nmp-meta-wrap { 2507 flex: 1 1 0 !important; 2508 min-width: 0 !important; 2509 overflow: hidden !important; 2510 display: flex !important; 2511 flex-direction: column !important; 2512 justify-content: center !important; 2513 gap: 0 !important; 2514 line-height: 1 !important; 2515 order: 1 !important; 2516 } 2517 2518 /* O antigo bloco .nmp-fallback (logo + nome do site) foi removido: ele 2519 trocava a subárvore inteira do mini player quando não havia metadados, 2520 com markup, tamanhos e ordem próprios â a causa do layout embaralhado. 2521 Agora o esqueleto é sempre o mesmo e só o texto muda. */ 2522 2523 /* Linha de texto com suporte a marquee. 2524 O <span> só vira inline-block quando o marquee está ativo. Enquanto for 2525 inline, o text-overflow do pai consegue aplicar o ellipsis â um filho 2526 inline-block seria cortado a seco, sem reticências. */ 2527 .mini-player-text { 2528 white-space: nowrap !important; 2529 overflow: hidden !important; 2530 text-overflow: ellipsis !important; 2531 display: block !important; 2532 width: 100% !important; 2533 position: relative !important; 2534 } 2535 .mini-player-text span { 2536 display: inline; 2537 white-space: nowrap !important; /* Impede que o texto duplicado (marquee) quebre linha */ 2538 padding-right: 0; 2539 } 2540 /* Estado rolando: o span passa a conter DUAS cópias idênticas (.nmp-run), 2541 cada uma com o intervalo já embutido no próprio padding-right. Assim 2542 metade da largura total é exatamente uma cópia + intervalo, e o 2543 translateX(-50%) emenda o fim no começo sem salto. Se o intervalo 2544 ficasse solto entre as cópias (ou no padding do span), 50% cairia no 2545 meio do vão e a volta daria um pulo visÃvel. */ 2546 .mini-player-text span.scrolling { 2547 display: inline-block; 2548 padding-right: 0; 2549 will-change: transform; 2550 animation: nmp-marquee-scroll 12s linear infinite; /* duração real vem do JS */ 2551 } 2552 .mini-player-text span.scrolling .nmp-run { 2553 display: inline-block; 2554 white-space: nowrap; 2555 padding-right: 48px; /* intervalo entre o fim de uma cópia e o inÃcio da outra */ 2556 font-style: normal; 2557 }
2558 @keyframes nmp-marquee-scroll { 2559 0% { transform: translateX(0); } 2560 100% { transform: translateX(-50%); } 2561 } 2562 /* Respeita quem pediu menos animação no sistema: em vez de rolar, volta ao 2563 texto estático com reticências â a segunda cópia sai de cena para não 2564 aparecer texto repetido cortado no meio. */ 2565 @media (prefers-reduced-motion: reduce) { 2566 .mini-player-text span.scrolling { 2567 display: inline; 2568 animation: none !important; 2569 transform: none !important; 2570 } 2571 .mini-player-text span.scrolling .nmp-run { 2572 padding-right: 0 !important; 2573 } 2574 .mini-player-text span.scrolling .nmp-run + .nmp-run { 2575 display: none !important; 2576 } 2577 #player.nmp-one-line #nmpDisplayTitle { 2578 text-overflow: ellipsis !important; 2579 } 2580 } 2581 2582 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2583 LAYOUT GLOBAL â Mini Player Mobile (TODOS OS TEMAS) 2584 Abordagem: seletores de CLASSE e nth-child apenas. 2585 SEM :has() â compatÃvel com qualquer browser. 2586 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2587 2588 /* --- Container direto do #player: força flex-start em TUDO --- */ 2589 #player > div, 2590 #player > .neon-player__inner, 2591 #player > .wave-player__inner, 2592 #player > .pulse-player__inner, 2593 #player > .glow-player__inner, 2594 #player > .d5-player-inner { 2595 flex-wrap: nowrap !important; 2596 gap: 10px !important; 2597 padding: 0 !important; 2598 overflow: hidden !important; 2599 justify-content: flex-start !important; 2600 align-items: center !important; 2601 } 2602 2603 /* Nota: as regras de #player .neon-player__left-group, .neon-player__info e 2604 .zmp-player-controls-wrapper com prefixo #player estão na seção LÃGICA FLEX acima. */ 2605 2606 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2607 DEMO 01 â Inner: .max-w-screen-2xl 2608 Estrutura: [1:Left=info] [2:Center=play] [3:Right=extras] 2609 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2610 /* Centro (playBtn) â vai para a ESQUERDA */ 2611 #player > div.max-w-screen-2xl > div:nth-child(2) { 2612 order: 1 !important; 2613 flex: 0 0 auto !important; 2614 width: auto !important; 2615 margin: 0 !important; 2616 gap: 0 !important; 2617 align-items: center !important; 2618 } 2619 /* Esquerda (artwork + info) â expande para preencher */ 2620 #player > div.max-w-screen-2xl > div:nth-child(1) { 2621 order: 2 !important; 2622 flex: 1 1 0 !important; 2623 min-width: 0 !important; 2624 overflow: hidden !important; 2625 width: auto !important; 2626 } 2627 /* Direita (extras/volume) â oculto */ 2628 #player > div.max-w-screen-2xl > div:nth-child(3) { 2629 display: none !important; 2630 } 2631 /* Container de texto dentro do bloco de info do Demo01 */ 2632 #player > div.max-w-screen-2xl > div:nth-child(1) > div:last-child { 2633 flex: 1 1 0 !important; 2634 min-width: 0 !important; 2635 overflow: hidden !important; 2636 } 2637 2638 /* --- Containers de meta-info internos --- */ 2639 .neon-player__info, 2640 .wave-player__info, 2641 .pulse-player__info, 2642 .glow-player__info, 2643 .d5-player-meta { 2644 flex: 1 1 0 !important; 2645 min-width: 0 !important; 2646 overflow: hidden !important; 2647 } 2648 2649 /* --- TÃtulo e Artista: sempre truncados com ellipsis --- 2650 IMPORTANTE: NÃO adicionar display:block !important aqui â 2651 o JS do marquee precisa conseguir ocultar esses elementos 2652 via element.style.display = 'none' para mostrar os 2653 substitutos #nmpDisplayTitle / #nmpDisplayArtist no lugar. --- */ 2654 #player #playerTitle, 2655 #player #playerArtist, 2656 #player .neon-player__title, 2657 #player .wave-player__title, 2658 #player .pulse-player__title, 2659 #player .glow-player__title, 2660 #player .d5-player-title, 2661 #player .player-title, 2662 #player .neon-player__artist, 2663 #player .wave-player__artist, 2664 #player .pulse-player__artist, 2665 #player .glow-player__artist, 2666 #player .d5-player-artist, 2667 #player .player-artist { 2668 max-width: 100% !important; 2669 overflow: hidden !important; 2670 text-overflow: ellipsis !important; 2671 white-space: nowrap !important; 2672 /* display intencionalmente ausente â controlado pelo JS do marquee */ 2673 } 2674 2675 /* Garante que playerTitle/Artist aparecem (desfaz 'hidden' do Tailwind/temas) 2676 SEM !important para não bloquear o display:none que o marquee-JS coloca */ 2677 #player #playerTitle { display: block; } 2678 #player #playerArtist { display: block; } 2679 2680 /* Elementos de display do marquee (substitutos). 2681 Tipografia e altura reservada IDÃNTICAS à s do tÃtulo/artista originais â 2682 o fallback é troca de string, nunca troca de estilo. */ 2683 #nmpDisplayTitle { 2684 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important; 2685 font-size: 13px !important; 2686 font-weight: 800 !important; 2687 color: var(--player-text-color, #ffffff) !important; 2688 text-transform: uppercase !important; 2689 letter-spacing: 0.02em !important; 2690 line-height: 16px !important; 2691 height: 16px !important; 2692 flex: 0 0 16px !important; 2693 margin: 0 0 2px 0 !important; 2694 } 2695 #nmpDisplayArtist { 2696 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important; 2697 font-size: 11px !important; 2698 font-weight: 400 !important; 2699 color: var(--player-text-color, #ffffff) !important; 2700 opacity: 0.8 !important; 2701 text-transform: uppercase !important; 2702 line-height: 12px !important; 2703 height: 12px !important; 2704 flex: 0 0 12px !important; 2705 } 2706 2707 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2708 ESTADO COM METADADOS â LINHA ÃNICA (classe .nmp-one-line) 2709 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2710 Com faixa tocando, música e artista deixam de ocupar duas linhas 2711 estreitas e passam a formar UMA linha só: MÃSICA ⢠ARTISTA. 2712 A coluna de texto do mini player tem ~150px; dividir isso em duas 2713 linhas de 13px/11px espremia os dois textos e cortava os dois. 2714 Em uma linha só, o texto ganha o dobro de largura útil e o que não 2715 couber rola em marquee contÃnuo (nunca reticências, nunca fonte menor). 2716 2717 O slot do artista continua existindo no DOM â apenas fica vazio e sai 2718 do fluxo â para o esqueleto (capa â badge â texto â play) não mudar. 2719 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2720 2721 /* Linha vazia (o slot do artista) sai do fluxo: nada de 12px em branco. */ 2722 #player.nmp-one-line .nmp-empty { 2723 display: none !important; 2724 } 2725 2726 /* Bloco de texto opticamente centrado com a capa de 48px. */ 2727 #player.nmp-one-line .neon-player__meta, 2728 #player.nmp-one-line .wave-player__meta, 2729 #player.nmp-one-line .pulse-player__meta, 2730 #player.nmp-one-line .glow-player__meta, 2731 #player.nmp-one-line .d5-player-meta, 2732 #player.nmp-one-line .player-meta, 2733 #player.nmp-one-line .nmp-meta-wrap { 2734 justify-content: center !important; 2735 gap: 0 !important; 2736 } 2737 2738 /* A linha única. Mesmo corpo de fonte do tÃtulo antigo (13px) â o gan
2738ho de 2739 leitura vem da largura, não de encolher a tipografia. 2740 text-overflow: clip (e não ellipsis) porque o overflow aqui é tratado 2741 pelo marquee; reticências só voltam em prefers-reduced-motion. */ 2742 #player.nmp-one-line #nmpDisplayTitle { 2743 font-size: 13px !important; 2744 font-weight: 700 !important; 2745 letter-spacing: 0.01em !important; 2746 line-height: 18px !important; 2747 height: 18px !important; 2748 flex: 0 0 18px !important; 2749 margin: 0 !important; 2750 overflow: hidden !important; 2751 text-overflow: clip !important; 2752 } 2753 2754 /* Nome da música em destaque; separador e artista recuam em peso/opacidade 2755 para a linha ter hierarquia mesmo sendo uma só. */ 2756 #player.nmp-one-line #nmpDisplayTitle .nmp-track { 2757 font-weight: 800 !important; 2758 } 2759 #player.nmp-one-line #nmpDisplayTitle .nmp-sep { 2760 font-weight: 400 !important; 2761 opacity: 0.55 !important; 2762 padding: 0 2px !important; 2763 } 2764 #player.nmp-one-line #nmpDisplayTitle .nmp-artist { 2765 font-weight: 400 !important; 2766 opacity: 0.8 !important; 2767 } 2768 2769 /* âââââââââââââââââââââââââââââââââââââââââââââââ 2770 BADGE "â AO VIVO" â Mini Player Mobile Integrado 2771 âââââââââââââââââââââââââââââââââââââââââââââââ */ 2772 .wave-player__meta [data-player-status], 2773 .neon-player__meta [data-player-status], 2774 .pulse-player__meta [data-player-status], 2775 .glow-player__meta [data-player-status], 2776 .d5-player-meta [data-player-status], 2777 .player-meta [data-player-status], 2778 .nmp-meta-wrap [data-player-status] { 2779 display: block !important; 2780 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; 2781 font-size: 10px !important; 2782 font-weight: 700 !important; 2783 text-transform: uppercase !important; 2784 letter-spacing: 0.08em !important; 2785 white-space: nowrap !important; 2786 overflow: hidden !important; 2787 text-overflow: ellipsis !important; 2788 line-height: 1 !important; 2789 margin-bottom: 2px !important; 2790 } 2791 2792 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2793 STATUS DO MINI PLAYER â apenas "Ao Vivo", sem avisos offline 2794 O estado de conexão é responsabilidade exclusiva da pÃlula 2795 flutuante (connection-status.php). Aqui sempre mostramos "Ao Vivo". 2796 Especificidade #player + attr > classe + attr â sobrescreve o bloco acima. 2797 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2798 2799 /* Zera o texto dinâmico injetado pelo player.js */ 2800 #player [data-player-status] { 2801 font-size: 0 !important; 2802 letter-spacing: 0 !important; 2803 color: transparent !important; 2804 margin-bottom: 3px !important; 2805 } 2806 2807 /* Injeta "â Ao Vivo" fixo via pseudo-elemento */ 2808 #player [data-player-status]::before { 2809 content: "â Ao Vivo"; 2810 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; 2811 font-size: 9px !important; 2812 font-weight: 500 !important; 2813 color: var(--player-text-color, #ffffff) !important; 2814 opacity: 0.7 !important; 2815 text-transform: none !important; 2816 letter-spacing: normal !important; 2817 line-height: 1 !important; 2818 } 2819 2820 /* Quando em modo podcast/música, oculta "Ao Vivo" também */ 2821 #player [data-player-status][data-is-stream="false"]::before { 2822 content: "" !important; 2823 } 2824 2825 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2826 ESTADO FALLBACK â streaming SEM metadados (classe .nmp-no-track) 2827 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2828 Estado próprio, com design fechado: não existe faixa para anunciar, 2829 então a hierarquia passa a ser 2830 [selo AO VIVO] â [nome da emissora] â [praça que está tocando] 2831 em um bloco de texto ÃNICO e compacto (entrelinhas apertadas), em vez 2832 de linhas soltas com altura reservada em branco. 2833 2834 O esqueleto NÃO muda (capa â texto â play, mesma ordem, mesmos slots) â 2835 apenas as alturas reservadas das linhas vazias são liberadas e a 2836 tipografia é redistribuÃda. Ver o histórico em assets/css/style.css: 2837 o fallback antigo tirava a capa do fluxo e sangrava o texto. 2838 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2839 2840 /* Linha sem conteúdo sai do fluxo â nada de 12/16px vazios entre os textos. 2841 Vale Sà no fallback; com metadados o esqueleto de altura fixa é mantido. */ 2842 #player.nmp-no-track .nmp-empty { 2843 display: none !important; 2844 } 2845 2846 /* Coluna de texto: bloco coeso, opticamente centrado com a capa de 48px. */ 2847 #player.nmp-no-track .neon-player__meta, 2848 #player.nmp-no-track .wave-player__meta, 2849 #player.nmp-no-track .pulse-player__meta, 2850 #player.nmp-no-track .glow-player__meta, 2851 #player.nmp-no-track .d5-player-meta, 2852 #player.nmp-no-track .player-meta, 2853 #player.nmp-no-track .nmp-meta-wrap { 2854 justify-content: center !important; 2855 gap: 0 !important; 2856 } 2857 2858 /* Selo "AO VIVO" â chip translúcido que ancora o topo do bloco. 2859 align-self: flex-start é obrigatório: sem ele o chip esticaria por toda a 2860 largura da coluna (align-items: stretch do flex) e o fundo viraria faixa. 2861 Cor derivada de --player-text-color â acompanha o painel de cores. */ 2862 #player.nmp-no-track [data-player-status] { 2863 display: inline-flex !important; 2864 align-items: center !important; 2865 gap: 5px !important; 2866 align-self: flex-start !important; 2867 width: auto !important; 2868 max-width: 100% !important; 2869 height: auto !important; 2870 flex: 0 0 auto !important; 2871 padding: 3px 8px 3px 7px !important; 2872 margin: 0 0 5px 0 !important; 2873 border-radius: 999px !important; 2874 background: rgba(255, 255, 255, 0.16) !important; 2875 background: color-mix(in srgb, var(--player-text-color, #ffffff) 18%, transparent) !important; 2876 color: var(--player-text-color, #ffffff) !important; 2877 font-size: 0 !important; /* silencia o texto dinâmico do player.js */ 2878 line-height: 1 !important; 2879 overflow: visible !important; 2880 } 2881 2882 #player.nmp-no-track [data-player-status]::before { 2883 content: "AO VIVO" !important; 2884 font-size: 9px !important; 2885 font-weight: 700 !important; 2886 letter-spacing: 0.1em !important; 2887 line-height: 1 !important; 2888 color: inherit !important; 2889 opacity: 1 !important; 2890 text-transform: uppercase !important; 2891 } 2892 2893 /* Ponto do "ao vivo" com pulso â order:-1 coloca o pseudo depois no código 2894 à ESQUERDA do texto, sem precisar de elemento extra no DOM. */ 2895 #player.nmp-no-track [data-player-status]::after { 2896 content: "" !important; 2897 order: -1 !important; 2898 width: 5px !important; 2899 height: 5px !important; 2900 flex: 0 0 5px !important; 2901 border-radius: 50% !important; 2902 background: currentColor !important; 2903 animation: nmp-live-pulse 1.9s ease-in-out infinite !important; 2904 } 2905 2906 /* Podcast/música sob demanda não é "ao vivo" â chip inteiro sai. */ 2907 #player.nmp-no-track [data-player-status][data-is-stream="false"] { 2908 display: none !important; 2909 } 2910
2911 @keyframes nmp-live-pulse { 2912 0%, 100% { opacity: 1; transform: scale(1); } 2913 50% { opacity: 0.45; transform: scale(0.8); } 2914 } 2915 2916 /* Nome da emissora â linha dominante do estado. Caixa natural (não força 2917 maiúsculas): respeita como o nome foi cadastrado no painel. */ 2918 #player.nmp-no-track #nmpDisplayTitle { 2919 font-size: 14px !important; 2920 font-weight: 700 !important; 2921 letter-spacing: 0 !important; 2922 text-transform: none !important; 2923 line-height: 17px !important; 2924 height: 17px !important; 2925 flex: 0 0 17px !important; 2926 margin: 0 !important; 2927 opacity: 1 !important; 2928 } 2929 2930 /* Praça em transmissão ("Você está ouvindo X") â linha de apoio, colada ao 2931 nome da emissora. Reaproveita o slot do artista, então herda o marquee 2932 quando o nome da praça não cabe. */ 2933 #player.nmp-no-track #nmpDisplayArtist { 2934 font-size: 11px !important; 2935 font-weight: 500 !important; 2936 letter-spacing: 0 !important; 2937 text-transform: none !important; 2938 line-height: 13px !important; 2939 height: 13px !important; 2940 flex: 0 0 13px !important; 2941 margin: 1px 0 0 0 !important; 2942 opacity: 0.72 !important; 2943 } 2944 2945 /* O elemento do seletor de praças vira redundante no fallback: seu texto já 2946 está na linha de apoio acima (com marquee). Só aqui â no desktop e no 2947 estado com metadados ele continua intacto. */ 2948 #player.nmp-no-track #agNowPlaying { 2949 display: none !important; 2950 } 2951 2952 /* Instalação sem nome de emissora configurado: a praça assume o papel de 2953 linha principal em vez de sobrar uma linha pequena e solta. */ 2954 #player.nmp-no-track.nmp-no-name #nmpDisplayArtist { 2955 font-size: 13px !important; 2956 font-weight: 600 !important; 2957 line-height: 16px !important; 2958 height: 16px !important; 2959 flex: 0 0 16px !important; 2960 margin: 0 !important; 2961 opacity: 0.92 !important; 2962 } 2963 2964 /* LÃGICA DO BOTÃO VOLTAR AO VIVO NO MOBILE (Quando toca Podcast) */ 2965 #player.is-playing-ext #zmpLiveBtn { 2966 display: flex !important; 2967 position: absolute !important; 2968 right: 12px !important; 2969 top: 50% !important; 2970 transform: translateY(-50%) !important; 2971 width: 40px !important; 2972 height: 40px !important; 2973 background: var(--color-primary) !important; 2974 border: 2px solid var(--border-color, transparent) !important; 2975 border-radius: 0 !important; /* Estilo Brutalista Quadrado */ 2976 color: var(--player-text-color, #ffffff) !important; 2977 align-items: center !important; 2978 justify-content: center !important; 2979 z-index: 99 !important; 2980 cursor: pointer !important; 2981 box-sizing: border-box !important; 2982 padding: 0 !important; 2983 } 2984 #player.is-playing-ext #zmpLiveBtn svg { 2985 width: 18px !important; 2986 height: 18px !important; 2987 margin: 0 !important; 2988 display: block !important; 2989 } 2990 #player.is-playing-ext .neon-player__info, 2991 #player.is-playing-ext .wave-player__info, 2992 #player.is-playing-ext .pulse-player__info, 2993 #player.is-playing-ext .glow-player__info { 2994 padding-right: 48px !important; /* Evitar que o texto fique debaixo do botão */ 2995 } 2996 2997 /* ---- ESTILO DO BOTÃO DE PLAY ---- */ 2998 #playBtn, .zmp-btn, .d5-play-btn, .neon-player__play-btn { 2999 background: transparent !important; 3000 border: none !important; 3001 box-shadow: none !important; 3002 width: auto !important; 3003 height: auto !important; 3004 padding: 0 !important; 3005 display: flex !important; 3006 align-items: center !important; 3007 justify-content: center !important; 3008 border-radius: 0 !important; 3009 transform: none !important; 3010 pointer-events: auto !important; 3011 cursor: pointer !important; 3012 z-index: 10 !important; 3013 } 3014 3015 #playBtn svg, .zmp-svg, .d5-play-btn i, .neon-player__play-btn svg { 3016 width: 28px !important; 3017 height: 28px !important; 3018 fill: var(--player-text-color, #ffffff) !important; 3019 color: var(--player-text-color, #ffffff) !important; 3020 filter: none !important; 3021 } 3022 3023 /* Outros widgets fixos de rodapé (WhatsApp / Video aberto) sobem para escapar da pÃlula */ 3024 .vmp-widget, #vmpWidget, #waWidget, .wa-widget { 3025 bottom: calc(145px + env(safe-area-inset-bottom)) !important; 3026 z-index: 9998 !important; 3027 } 3028 3029 /* Garantir que o player__inner não clippe os elementos filhos. 3030 O recorte final é do #player (overflow:hidden na pÃlula). */ 3031 .neon-player__inner, 3032 .wave-player__inner, 3033 .pulse-player__inner, 3034 .glow-player__inner { 3035 overflow: visible !important; 3036 } 3037} 3038</style> 3039 3040
3041<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script>
vendor: 1 bytes, line 3041
3041
3042<script> 3043document.addEventListener('DOMContentLoaded', function () { 3044 const items = document.querySelectorAll('.mobile-bottom-nav .nav-item'); 3045 const currentPath = window.location.pathname; 3046 3047 items.forEach(item => { 3048 const href = item.getAttribute('href'); 3049 if (!href) return; 3050 3051 try { 3052 // Parse full URLs perfectly by using the current origin as base 3053 const urlObj = new URL(href, window.location.origin); 3054 const itemPath = urlObj.pathname; 3055 3056 // Match if exact path, or if current starts with itemPath (except for root '/') 3057 if (itemPath === currentPath || (currentPath.startsWith(itemPath) && itemPath !== '/')) { 3058 item.classList.add('active'); 3059 } 3060 } catch (e) { 3061 console.warn("Invalid Mobile Nav URL: ", href); 3062 } 3063 }); 3064 3065 // Special case: Ensure Home acts correctly when at exact root 3066 const homeItems = document.querySelectorAll('.mobile-bottom-nav .nav-item'); 3067 homeItems.forEach(item => { 3068 const href = item.getAttribute('href'); 3069 if (href) { 3070 try { 3071 const urlObj = new URL(href, window.location.origin); 3072 if (urlObj.pathname === '/' && currentPath === '/') { 3073 item.classList.add('active'); 3074 } else if (urlObj.pathname === '/') { 3075 item.classList.remove('active'); 3076 } 3077 } catch(e){} 3078 } 3079 }); 3080 3081 // ========================================= 3082 // LOGIC FOR EXPANDED PLAYER 3083 // ========================================= 3084 const miniPlayers = document.querySelectorAll('#player, .radio-player, .neon-player, .d5-player, .app-player, .player-container, .global-player'); 3085 const epCard = document.getElementById('expandedPlayerCard'); 3086 const epHandle = document.getElementById('epHandle'); 3087 const epPlayBtn = document.getElementById('epPlayBtn'); 3088 3089 // Synchronizing the UI 3090 let observerActivated = false; 3091 3092 // Waveform logic 3093 const epWaveform = document.getElementById('epWaveform'); 3094 const waveCtx = epWaveform ? epWaveform.getContext('2d') : null; 3095 let waveAnimId; 3096 let waveBars = []; 3097 const NUM_BARS = 18; 3098 const BAR_WIDTH = 3; 3099 const BAR_GAP = 3; 3100 let isPlayingAudio = false; 3101 3102 if (waveCtx) { 3103 for(let i=0; i<NUM_BARS; i++) { 3104 waveBars.push({ height: 2, phase: Math.random() * Math.PI * 2, speed: 0.05 + Math.random() * 0.06 }); 3105 } 3106 } 3107 3108 function renderWaveform() { 3109 if(!waveCtx) return; 3110 waveCtx.clearRect(0, 0, epWaveform.width, epWaveform.height); 3111 const isCardOpen = epCard && epCard.classList.contains('active'); 3112 let allSettled = true; 3113 const centerY = epWaveform.height; // Bars from bottom up 3114 3115 for(let i=0; i<NUM_BARS; i++) { 3116 const bar = waveBars[i]; 3117 let targetHeight = 2; // Idle state 3118 3119 if(isPlayingAudio && isCardOpen) { 3120 bar.phase += bar.speed; 3121 targetHeight = 4 + Math.abs(Math.sin(bar.phase)) * (epWaveform.height - 6); 3122 allSettled = false; 3123 } 3124 3125 bar.height += (targetHeight - bar.height) * 0.2; 3126 if(Math.abs(bar.height - targetHeight) > 0.5) { 3127 allSettled = false; 3128 } 3129 3130 const isLight = document.body.classList.contains('theme-light'); 3131 waveCtx.fillStyle = isLight ? 'rgba(26,23,48,0.7)' : 'rgba(255,255,255,0.7)'; 3132 const startX = (epWaveform.width / 2) - ((NUM_BARS * (BAR_WIDTH + BAR_GAP)) / 2) + i * (BAR_WIDTH + BAR_GAP); 3133 waveCtx.fillRect(startX, Math.max(0, centerY - bar.height), BAR_WIDTH, bar.height); 3134 } 3135 3136 if(!allSettled || (isPlayingAudio && isCardOpen)) { 3137 if (waveAnimId) cancelAnimationFrame(waveAnimId); 3138 waveAnimId = requestAnimationFrame(renderWaveform); 3139 } 3140 } 3141 3142 // Background ColorLogic 3143 const epBgLayer1 = document.getElementById('epBgLayer1'); 3144 const epBgLayer2 = document.getElementById('epBgLayer2'); 3145 let currentBgLayer = 1; 3146 let lastArtworkSrc = ''; 3147 let colorThief = null; 3148 try { colorThief = new ColorThief(); } catch(e) {} 3149 3150 function applyColors(imageUrl) { 3151 if (!colorThief || !imageUrl || imageUrl === lastArtworkSrc) return; 3152 lastArtworkSrc = imageUrl; 3153 3154 const img = new Image(); 3155 img.crossOrigin = 'Anonymous'; 3156 img.src = imageUrl; 3157 img.onload = () => { 3158 let palette = null; 3159 try { palette = colorThief.getPalette(img, 3); } catch(e) { return; } 3160 if(palette && palette.length >= 2) { 3161 const c1 = `rgb(${palette[0][0]}, ${palette[0][1]}, ${palette[0][2]})`; 3162 const c2 = `rgb(${palette[1][0]}, ${palette[1][1]}, ${palette[1][2]})`; 3163 const gradient = `linear-gradient(135deg, ${c1} 0%, ${c2} 100%)`; 3164 3165 if(currentBgLayer === 1 && epBgLayer2) { 3166 epBgLayer2.style.backgroundImage = gradient; 3167 epBgLayer2.style.opacity = '1'; 3168 if(epBgLayer1) epBgLayer1.style.opacity = '0'; 3169 currentBgLayer = 2; 3170 } else if (currentBgLayer === 2 && epBgLayer1) { 3171 epBgLayer1.style.backgroundImage = gradient; 3172 epBgLayer1.style.opacity = '1'; 3173 if(epBgLayer2) epBgLayer2.style.opacity = '0'; 3174 currentBgLayer = 1; 3175 } 3176 } 3177 }; 3178 } 3179 3180 window.syncAllPlayerUI = function() { 3181 const epTitle = document.getElementById('epTitle'); 3182 const epArtist = document.getElementById('epArtist'); 3183 const epArtworkImg = document.getElementById('epArtworkImg'); 3184 const epPlayIcon = document.getElementById('epPlayIcon'); 3185 3186 const pTitle = document.getElementById('playerTitle') || document.querySelector('.player-title'); 3187 const pArtist = document.getElementById('playerArtist') || document.querySelector('.player-artist'); 3188 const pArtwork = document.getElementById('playerArtwork') || document.querySelector('.player-artwork'); 3189 const pPlayIcon = document.getElementById('playIcon') || document.querySelector('.play-icon, #playBtn svg, .global-play-btn svg'); 3190 3191 const doSync = () => { 3192 if(pTitle && epTitle) epTitle.textContent = pTitle.textContent; 3193 if(pArtist && epArtist) epArtist.textContent = pArtist.textContent; 3194 3195 if(pArtwork && epArtworkImg) { 3196 const img = pArtwork.querySelector('img'); 3197 if(img && img.src) { 3198 epArtworkImg.removeAttribute('crossorigin'); 3199 epArtworkImg.src = img.src; 3200 applyColors(img.src); 3201 } else if (pArtwork.tagName === 'IMG') { 3202 epArtworkImg.removeAttribute('crossorigin'); 3203 epArtworkImg.src = pArtwork.src; 3204 applyColors(pArtwork.src); 3205 } 3206 } 3207 3208 let wasPlaying = isPlayingAudio; 3209 if(pPlayIcon && epPlayIcon) { 3210 const htmlStr = pPlayIcon.tagName.toLowerCase() === 'svg' ? pPlayIcon.outerHTML : pPlayIcon.innerHTML; 3211 // Remove inner IDs to avoid DOM conflicts 3212 const cleanHtmlStr = htmlStr.replace(/id="[^"]*"/g, ''); 3213 epPlayIcon.innerHTML = cleanHtmlStr; 3214 3215 if (htmlStr.includes('M7 ') || htmlStr.includes('M7,')) { 3216 isPlayingAudio = true; 3217 } else if (htmlStr.includes('M6 3.5') || htmlStr.includes('M6,')) { 3218 isPlayingAudio = false;
3219 } else { 3220 isPlayingAudio = window.radioPlayer && window.radioPlayer.state === 'transmitting'; 3221 } 3222 } 3223 if (wasPlaying !== isPlayingAudio) { 3224 renderWaveform(); 3225 } 3226 }; 3227 3228 doSync(); 3229 3230 if(!observerActivated) { 3231 observerActivated = true; 3232 const observer = new MutationObserver(() => doSync()); 3233 if(pTitle) observer.observe(pTitle, { childList: true, characterData: true, subtree: true }); 3234 if(pArtist) observer.observe(pArtist, { childList: true, characterData: true, subtree: true }); 3235 if(pArtwork) observer.observe(pArtwork, { childList: true, attributes: true, subtree: true, attributeFilter: ['src'] }); 3236 if(pPlayIcon) observer.observe(pPlayIcon, { childList: true, subtree: true, attributes: true }); 3237 } 3238 }; 3239 3240 // Listen to global changes from RadioPlayer 3241 document.addEventListener('radioPlayerStateChange', window.syncAllPlayerUI); 3242 3243 // Bind Open/Toggle actions explicitly 3244 miniPlayers.forEach(p => { 3245 p.addEventListener('click', (e) => { 3246 // Card expandido é exclusivo do mobile (inclui celular em paisagem) â ignorar em desktop/tablet 3247 if (!window.matchMedia('(max-width: 1024px), (pointer: coarse) and (max-width: 1366px)').matches) return; 3248 3249 const isPlayBtn = e.target.closest('#playBtn') || e.target.closest('.d5-play-btn') || 3250 e.target.closest('.neon-player__play-btn') || e.target.closest('.global-btn') || 3251 e.target.closest('.global-play') || e.target.closest('#volumeBtn') || 3252 e.target.closest('#zmpLiveBtn') || e.target.closest('#liveBtn') || 3253 e.target.closest('.zmp-volume-slider') || e.target.closest('.zmp-player-controls-wrapper') || 3254 e.target.closest('#btnAssistirAoVivo') || e.target.closest('.neon-player__video-btn-wrap'); 3255 3256 if (isPlayBtn) { 3257 e.stopPropagation(); 3258 return; 3259 } 3260 3261 if (epCard) { 3262 epCard.style.display = 'flex'; 3263 void epCard.offsetWidth; // Force Reflow 3264 epCard.classList.add('active'); 3265 document.body.style.overflow = 'hidden'; 3266 window.syncAllPlayerUI(); 3267 renderWaveform(); 3268 } 3269 }); 3270 }); 3271 3272 // Expanded Play Btn 3273 if(epPlayBtn) { 3274 epPlayBtn.addEventListener('click', (e) => { 3275 e.stopPropagation(); 3276 e.preventDefault(); 3277 if (window.radioPlayer && typeof window.radioPlayer.togglePlay === 'function') { 3278 window.radioPlayer.togglePlay(); 3279 } else { 3280 // Fallback 3281 const nativePlayBtn = document.getElementById('playBtn') || document.querySelector('.neon-player__play-btn, .d5-play-btn, .global-play'); 3282 if(nativePlayBtn) nativePlayBtn.click(); 3283 } 3284 setTimeout(window.syncAllPlayerUI, 50); 3285 }); 3286 } 3287 3288 // Swipe capabilities 3289 if(epCard && epHandle) { 3290 function closeExpandedPlayer() { 3291 epCard.classList.remove('active'); 3292 epCard.style.transform = ''; 3293 document.body.style.overflow = ''; 3294 renderWaveform(); // Jump start fade out 3295 setTimeout(() => { 3296 if(!epCard.classList.contains('active')) { 3297 epCard.style.display = 'none'; 3298 } 3299 }, 450); 3300 } 3301 3302 let startY = 0; 3303 let currentY = 0; 3304 let isDragging = false; 3305 3306 // Click to close 3307 epHandle.addEventListener('click', (e) => { 3308 e.stopPropagation(); 3309 closeExpandedPlayer(); 3310 }); 3311 epHandle.addEventListener('touchstart', (e) => { 3312 e.stopPropagation(); 3313 }, {passive: true}); 3314 epHandle.addEventListener('touchend', (e) => { 3315 e.stopPropagation(); 3316 e.preventDefault(); 3317 closeExpandedPlayer(); 3318 }, {passive: false}); 3319 3320 epCard.addEventListener('touchstart', (e) => { 3321 startY = e.touches[0].clientY; 3322 isDragging = true; 3323 epCard.style.transition = 'none'; 3324 }, {passive: true}); 3325 3326 epCard.addEventListener('touchmove', (e) => { 3327 if(!isDragging) return; 3328 currentY = e.touches[0].clientY; 3329 const diff = currentY - startY; 3330 if(diff > 0) { 3331 epCard.style.transform = `translateY(${diff}px)`; 3332 } 3333 }, {passive: true}); 3334 3335 epCard.addEventListener('touchend', (e) => { 3336 if(!isDragging) return; 3337 isDragging = false;
3338 const diff = currentY - startY; 3339 epCard.style.transition = 'transform 0.4s cubic-bezier(0.32, 0.72, 0, 1)'; 3340 3341 if(diff > 150) { 3342 closeExpandedPlayer(); 3343 } else { 3344 epCard.style.transform = ''; 3345 } 3346 }); 3347 } 3348 3349 3350 // ====================================================== 3351 // Filtro de Metadados + Marquee do mini player mobile 3352 // 3353 // ESQUELETO ÃNICO: a estrutura, a ordem e a distribuição são as mesmas em 3354 // todos os estados. Sem metadados, só o TEXTO muda â nada é removido do DOM 3355 // e nada troca de lugar. Por isso não existe mais bloco de fallback próprio. 3356 // CORREÃÃO: flag de reentrância para evitar loop infinito 3357 // ====================================================== 3358 (function initMetadataFilterAndMarquee() { 3359 if (!window.matchMedia('(max-width: 1024px), (pointer: coarse) and (max-width: 1366px)').matches) return; // Apenas mobile (inclui celular em paisagem) 3360 3361 // Palavras que indicam player em modo não-música. 'live' foi removido 3362 // para não bloquear podcasts como "Live Sessions" â 'ao vivo'/'aovivo' bastam. 3363 var BLOCKLIST = ['vinheta','trilha','break','off','locu\u00e7\u00e3o','locutor','intervalo','comercial','patroc\u00ednio','jingle','spot','ao vivo','aovivo']; 3364 var FALLBACK_NAME = "Rádio 93 FM"; 3365 // Deploy em que o include roda fora do escopo de $siteName: cai na constante 3366 // que o head.php do tema publica, em vez de deixar a linha do nome vazia. 3367 if (!FALLBACK_NAME && typeof SITE_NAME !== 'undefined') FALLBACK_NAME = SITE_NAME || ''; 3368 3369 // O texto de "carregando" só faz sentido nos primeiros segundos. Passada a 3370 // carência, se ainda não chegou metadado, a linha de tÃtulo passa a exibir o 3371 // nome da emissora (configuração do tenant) em vez de mentir "carregando". 3372 var LOADING_GRACE_MS = 3000; 3373 var _startedAt = Date.now(); 3374 3375 // FLAG: evitar loop infinito (observer -> DOM change -> observer...) 3376 var _busy = false; 3377 3378 function isMusicMetadata(title, artist) { 3379 var str = (title + ' ' + artist).toLowerCase(); 3380 return !BLOCKLIST.some(function(w){ return str.indexOf(w) !== -1; }); 3381 } 3382 3383 // TÃtulo que não representa uma faixa: vazio ou placeholder de carregamento 3384 // já fora da janela de carência. 3385 function isPlaceholderTitle(title) { 3386 if (!title) return true; 3387 return /^carregando/i.test(title) && (Date.now() - _startedAt) > LOADING_GRACE_MS; 3388 } 3389 3390 // Cria elementos de EXIBIÃÃO dedicados para o marquee. 3391 // Os elementos originais (#playerTitle/#playerArtist) NÃO são tocados â 3392 // apenas ocultados. Assim o MutationObserver não entra em loop. 3393 function getOrCreateDisplayEl(sourceEl, displayId) { 3394 var existing = document.getElementById(displayId); 3395 if (existing) return existing; 3396 var d = document.createElement('div'); 3397 d.id = displayId; 3398 d.className = 'mini-player-text'; 3399 d.appendChild(document.createElement('span')); 3400 sourceEl.parentNode.insertBefore(d, sourceEl.nextSibling); 3401 return d; 3402 } 3403 3404 // Velocidade do marquee em px/s. Duração fixa fazia texto curto se arrastar 3405 // e texto longo passar voando; com velocidade constante a leitura tem sempre 3406 // o mesmo ritmo â só o tempo de volta muda. 3407 var MARQUEE_SPEED = 45; 3408 var MARQUEE_MIN_S = 8; 3409 var MARQUEE_MAX_S = 45; 3410 3411 // Monta uma "passada" do texto. `parts` é uma lista de trechos {text, cls} â 3412 // é o que permite música, separador e artista terem pesos diferentes dentro 3413 // da MESMA linha. wrapAsRun envolve tudo em .nmp-run (usado só no estado 3414 // rolando, onde existem duas cópias iguais). 3415 function buildRun(parts, wrapAsRun) { 3416 var frag = document.createDocumentFragment(); 3417 var host = frag; 3418 if (wrapAsRun) { 3419 host = document.createElement('i'); 3420 host.className = 'nmp-run'; 3421 frag.appendChild(host); 3422 } 3423 for (var i = 0; i < parts.length; i++) { 3424 var p = parts[i]; 3425 if (!p.text) continue; 3426 if (p.cls) { 3427 var s = document.createElement('span'); 3428 s.className = p.cls; 3429 s.textContent = p.text; // textContent: metadado do streaming não é markup 3430 host.appendChild(s); 3431 } else { 3432 host.appendChild(document.createTextNode(p.text)); 3433 } 3434 } 3435 return frag; 3436 } 3437 3438 // Ativa o marquee Sà quando o texto realmente estoura o container. 3439 // A medição é SÃNCRONA: ler clientWidth já força o cálculo de layout, então 3440 // não dependemos de requestAnimationFrame â que fica suspenso quando a aba 3441 // está em segundo plano. Era essa dependência (mais a medição num único 3442 // frame, quando o elemento ainda podia ter largura 0) que deixava tÃtulo 3443 // longo cortado sem nunca rolar. O retry usa setTimeout pelo mesmo motivo. 3444 function applyMarqueeOnDisplay(displayEl, parts) { 3445 if (!displayEl) return;
3446 var span = displayEl.querySelector('span'); 3447 if (!span) return; 3448 3449 span.classList.remove('scrolling'); 3450 span.style.animationDuration = ''; 3451 span.textContent = ''; 3452 span.appendChild(buildRun(parts, false)); 3453 3454 var tries = 0; 3455 function decide() { 3456 // Container ainda sem largura medÃvel (acabou de entrar no DOM, ou a 3457 // pÃlula ainda não foi posicionada) â tenta de novo em 100ms. 3458 if (displayEl.clientWidth === 0 && tries++ < 20) { 3459 setTimeout(decide, 100); 3460 return; 3461 } 3462 // Medir no CONTAINER, não no <span>: fora do estado de rolagem o span é 3463 // inline (para o ellipsis do pai funcionar) e inline não expõe scrollWidth. 3464 if (displayEl.scrollWidth <= displayEl.clientWidth + 1) return; // cabe: fica parado 3465 3466 // Duas cópias idênticas, cada uma com o intervalo embutido no próprio 3467 // padding â metade da largura total vira exatamente uma cópia, então o 3468 // translateX(-50%) emenda o fim no começo sem salto. 3469 span.textContent = ''; 3470 span.appendChild(buildRun(parts, true)); 3471 span.appendChild(buildRun(parts, true)); 3472 span.classList.add('scrolling'); 3473 3474 // A distância percorrida por ciclo é UMA cópia (texto + intervalo) â 3475 // é ela que define a duração, não a largura total do span. 3476 var run = span.querySelector('.nmp-run'); 3477 var dist = run ? run.offsetWidth : 0; 3478 if (dist > 0) { 3479 var secs = Math.min(MARQUEE_MAX_S, Math.max(MARQUEE_MIN_S, dist / MARQUEE_SPEED)); 3480 span.style.animationDuration = secs.toFixed(1) + 's'; 3481 } 3482 } 3483 decide(); 3484 } 3485 3486 // Texto da praça em transmissão ("Você está ouvindo X"), injetado pelo 3487 // seletor de praças (Modo Rede). No estado fallback ele passa a ser a linha 3488 // de apoio do bloco de texto â o elemento original é ocultado por CSS para 3489 // não duplicar a informação. Fora do Modo Rede o elemento não existe e o 3490 // fallback fica com duas linhas (selo + nome da emissora). 3491 function getCityLine() { 3492 var el = document.getElementById('agNowPlaying'); 3493 if (!el || el.style.display === 'none') return ''; 3494 return (el.textContent || '').trim(); 3495 } 3496 3497 // Localiza o bloco capa+texto em qualquer um dos quatro temas. 3498 function findInfoEl(playerEl, titleEl) { 3499 var themed = playerEl.querySelector( 3500 '.neon-player__info, .wave-player__info, .pulse-player__info, .glow-player__info' 3501 ); 3502 if (themed) return themed; 3503 return (titleEl.parentNode && titleEl.parentNode !== playerEl) ? titleEl.parentNode : null; 3504 } 3505 3506 function applyMetadataFilter() { 3507 if (_busy) return; // Evitar reentrada 3508 3509 var playerEl = document.getElementById('player') || 3510 document.querySelector('.neon-player, .radio-player'); 3511 if (!playerEl) return; 3512 3513 var titleEl = document.getElementById('playerTitle'); 3514 var artistEl = document.getElementById('playerArtist'); 3515 if (!titleEl || !artistEl) return; 3516 3517 var infoEl = findInfoEl(playerEl, titleEl); 3518 if (!infoEl) return; 3519 3520 var title = (titleEl.textContent || '').trim(); 3521 var artist = (artistEl.textContent || '').trim(); 3522 3523 // Sem faixa exibÃvel â ESTADO FALLBACK: a linha de tÃtulo recebe o nome 3524 // da emissora e a de apoio recebe a praça em transmissão. A classe 3525 // .nmp-no-track no player liga o design próprio desse estado (bloco de 3526 // texto compacto + selo "AO VIVO"); o esqueleto continua o mesmo. 3527 var hasTrack = !isPlaceholderTitle(title) && isMusicMetadata(title, artist); 3528 var showTitle = hasTrack ? title : FALLBACK_NAME; 3529 var showArtist = hasTrack ? artist : getCityLine(); 3530 3531 // COM FAIXA â LINHA ÃNICA "MÃSICA ⢠ARTISTA". 3532 // Duas linhas de ~150px espremiam os dois textos; em uma linha só o texto 3533 // tem o dobro da largura útil e o excedente rola em marquee. O slot do 3534 // artista continua no DOM, apenas vazio (.nmp-empty) â o esqueleto do 3535 // mini player não muda. Sem faixa, o fallback segue com as duas linhas 3536 // (nome da emissora + praça), cada uma com seu próprio marquee. 3537 var titleParts, artistParts; 3538 if (hasTrack) { 3539 titleParts = [{ text: showTitle, cls: 'nmp-track' }]; 3540 if (showArtist) { 3541 titleParts.push({ text: ' ⢠', cls: 'nmp-sep' }); 3542 titleParts.push({ text: showArtist, cls: 'nmp-artist' }); 3543 } 3544 artistParts = []; 3545 showArtist = ''; // o slot do artista fica vazio â sai do fluxo 3546 } else { 3547 titleParts = [{ text: showTitle }
3547]; 3548 artistParts = [{ text: showArtist }]; 3549 } 3550 3551 _busy = true; 3552 try { 3553 // Classes de estado no #player â não são observadas pelo 3554 // MutationObserver (que só olha o texto de title/artist), então não há 3555 // risco de reentrância aqui. 3556 playerEl.classList.toggle('nmp-no-track', !hasTrack); 3557 playerEl.classList.toggle('nmp-no-name', !hasTrack && !showTitle); 3558 playerEl.classList.toggle('nmp-one-line', hasTrack); 3559 3560 // O bloco capa+texto NUNCA é ocultado â o esqueleto é sempre o mesmo. 3561 infoEl.style.display = ''; 3562 3563 // Exibição dedicada para marquee (não toca nos originais observados) 3564 var displayTitle = getOrCreateDisplayEl(titleEl, 'nmpDisplayTitle'); 3565 var displayArtist = getOrCreateDisplayEl(artistEl, 'nmpDisplayArtist'); 3566 3567 applyMarqueeOnDisplay(displayTitle, titleParts); 3568 applyMarqueeOnDisplay(displayArtist, artistParts); 3569 3570 // Linha vazia é retirada do fluxo no fallback e na linha única (regras 3571 // CSS #player.nmp-no-track .nmp-empty / #player.nmp-one-line .nmp-empty) 3572 // â evita o vão vertical em branco entre o selo e o texto. 3573 displayTitle.classList.toggle('nmp-empty', !showTitle); 3574 displayArtist.classList.toggle('nmp-empty', !showArtist); 3575 3576 // Usa classe CSS com !important para sobrescrever regras de tema (ex: wave-player__title) 3577 titleEl.classList.add('nmp-src-hidden'); 3578 artistEl.classList.add('nmp-src-hidden'); 3579 displayTitle.classList.remove('nmp-src-hidden'); 3580 displayArtist.classList.remove('nmp-src-hidden'); 3581 displayTitle.style.display = ''; 3582 displayArtist.style.display = ''; 3583 } finally { 3584 _busy = false; 3585 } 3586 } 3587 3588 // ---------------------------------------------------------------- 3589 // Observer nos dois elementos originais. 3590 // CRITICAL: usar characterData:true + subtree:true para detectar 3591 // quando player.js faz `element.textContent = "..."` (muda text node) 3592 // O flag _busy já previne o loop de reintrância. 3593 // ---------------------------------------------------------------- 3594 var titleEl = document.getElementById('playerTitle'); 3595 var artistEl = document.getElementById('playerArtist'); 3596 var mo = new MutationObserver(function() { 3597 if (_busy) return; 3598 applyMetadataFilter(); 3599 }); 3600 var obsOpts = { childList: true, characterData: true, subtree: true }; 3601 if (titleEl) mo.observe(titleEl, obsOpts); 3602 if (artistEl) mo.observe(artistEl, obsOpts); 3603 3604 // Também acionar quando o próprio radioPlayer dispara changestate 3605 setTimeout(applyMetadataFilter, 800); 3606 // Passada extra ao fim da carência: troca "Carregando..." pelo nome da 3607 // emissora caso o streaming não tenha mandado metadado nenhum. 3608 setTimeout(applyMetadataFilter, LOADING_GRACE_MS + 200); 3609 document.addEventListener('radioPlayerStateChange', function() { 3610 if (_busy) return; 3611 // Pequeno delay para o DOM ja ter sido atualizado 3612 setTimeout(applyMetadataFilter, 50); 3613 }); 3614 3615 // Rotação de tela / mudança de largura muda a decisão do marquee. 3616 var _rzTimer; 3617 window.addEventListener('resize', function() { 3618 clearTimeout(_rzTimer); 3619 _rzTimer = setTimeout(applyMetadataFilter, 200); 3620 }); 3621 3622 // Expor para reavaliação externa (ex.: troca de praça no Modo Rede) 3623 window.checkMiniPlayerOverflow = applyMetadataFilter; 3624 })(); 3625 3626}); 3627</script>
3627 3628 3629<!-- EXPANDED MOBILE PLAYER CARD --> 3630<div class="expanded-player-card" id="expandedPlayerCard"> 3631 <div class="ep-bg-layer" id="epBgLayer1" style="opacity: 1;"></div> 3632 <div class="ep-bg-layer" id="epBgLayer2" style="opacity: 0;"></div> 3633 <div class="ep-bg-overlay"></div> 3634 <button class="ep-handle-btn" id="epHandle" aria-label="Minimize"> 3635 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg> 3636 </button> 3637 <div class="ep-content"> 3638 <div class="ep-artwork has-image"> 3639 <img src="" alt="Album Art" id="epArtworkImg"> 3640 </div> 3641 <div class="ep-metadata"> 3642 <h2 class="ep-title" id="epTitle">...</h2> 3643 <p class="ep-artist" id="epArtist">...</p> 3644 <canvas id="epWaveform" class="ep-waveform" width="120" height="30"></canvas> 3645 </div> 3646 <div class="ep-controls"> 3647 <button class="ep-play-btn" id="epPlayBtn" aria-label="Play/Pause"> 3648 <div class="ep-play-icon" id="epPlayIcon"> 3649 <!-- Ãcone injetado dinamicamente --> 3650 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 3.5a1.5 1.5 0 0 1 2.3-.8l11.5 8a1.5 1.5 0 0 1 0 2.6l-11.5 8A1.5 1.5 0 0 1 6 20.5v-17z" fill="currentColor"/></svg> 3651 </div> 3652 </button> 3653 </div> 3654 </div> 3655</div> 3656 3657<style> 3658/* Oculto em Desktop, exibido em Mobile */ 3659.expanded-player-card { 3660 display: none; 3661} 3662 3663@media (max-width: 1024px), (pointer: coarse) and (max-width: 1366px) { 3664 .expanded-player-card { 3665 display: flex; 3666 flex-direction: column; 3667 position: fixed; 3668 bottom: calc(85px + env(safe-area-inset-bottom)); 3669 left: 12px; 3670 width: calc(100% - 24px); 3671 height: 60px; 3672 z-index: 99999; 3673 background-color: var(--neon-bg-deep, var(--bg-color, #121212)); 3674 color: var(--neon-text, var(--text-color, #ffffff)); 3675 border-radius: 12px; 3676 opacity: 0; 3677 pointer-events: none; 3678 transition: all 0.42s cubic-bezier(0.32, 0.72, 0, 1); 3679 overflow: hidden; 3680 box-sizing: border-box; 3681 } 3682 3683 .expanded-player-card.active { 3684 bottom: 0; 3685 left: 0; 3686 width: 100%; 3687 height: 100vh; 3688 height: 100dvh; 3689 border-radius: 0; 3690 opacity: 1; 3691 pointer-events: auto; 3692 } 3693 3694 .ep-bg-layer { 3695 position: absolute; 3696 top: 0; left: 0; right: 0; bottom: 0; 3697 z-index: 0; 3698 transition: opacity 1.5s ease; 3699 background-size: cover; 3700 background-position: center; 3701 background-image: linear-gradient(135deg, var(--neon-bg-deep, var(--bg-color, #121212)) 0%, rgba(0,0,0,0.9) 100%); 3702 } 3703 3704 body.theme-light .ep-bg-layer { 3705 background-image: linear-gradient(135deg, var(--neon-bg-deep, var(--bg-color, #f0eef5)) 0%, rgba(255,255,255,0.9) 100%); 3706 } 3707 3708 .ep-bg-overlay { 3709 position: absolute; 3710 top: 0; left: 0; right: 0; bottom: 0; 3711 z-index: 1; 3712 background: linear-gradient(to bottom, rgba(0,0,0,0.4) 0%, var(--neon-bg-deep, var(--bg-color, #121212)) 100%); 3713 pointer-events: none; 3714 } 3715 3716 body.theme-light .ep-bg-overlay { 3717 background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, var(--neon-bg-deep, var(--bg-color, #f0eef5)) 100%); 3718 } 3719 3720 .ep-handle-btn { 3721 position: absolute; 3722 top: calc(16px + env(safe-area-inset-top)); 3723 left: 16px; 3724 width: 44px; 3725 height: 44px; 3726 border-radius: 50%; 3727 background-color: var(--glass-border, rgba(255,255,255,0.1)); 3728 border: none; 3729 display: flex; 3730 justify-content: center; 3731 align-items: center; 3732 cursor: pointer; 3733 color: var(--neon-text, var(--text-color, #fff)); 3734 transition: transform 0.1s, background-color 0.2s; 3735 z-index: 99; 3736 padding: 0; 3737 pointer-events: auto; 3738 } 3739 3740 .ep-handle-btn:active { 3741 transform: scale(0.9); 3742 background-color: var(--neon-surface-hover, rgba(255,255,255,0.2)); 3743 } 3744 3745 .ep-content { 3746 flex: 1; 3747 display: flex; 3748 flex-direction: column; 3749 align-items: center; 3750 justify-content: center; 3751 width: 100%; 3752 padding: 0 24px; 3753 box-sizing: border-box; 3754 opacity: 0; 3755 transition: opacity 0.2s ease; 3756 position: relative; 3757 z-index: 2; 3758 } 3759 .expanded-player-card.active .ep-content { 3760 opacity: 1; 3761 transition-delay: 0.15s; 3762 } 3763 3764 .ep-artwork { 3765 width: 85%; 3766 aspect-ratio: 1 / 1; 3767 border-radius: 16px; 3768 background-color: var(--glass-border, rgba(255,255,255,0.1)); 3769 box-shadow: var(--shadow-lg, 0 16px 40px rgba(0,0,0,0.5)); 3770 margin-bottom: 6vh; 3771 overflow: hidden; 3772 display: flex; 3773 align-items: center; 3774 justify-content: center; 3775 } 3776 3777 .ep-artwork img { 3778 width: 100%; 3779 height: 100%; 3780 object-fit: cover; 3781 } 3782 3783 .ep-metadata { 3784 width: 100%; 3785 text-align: center; 3786 margin-bottom: 6vh; 3787 } 3788 3789 .ep-title { 3790 font-family: var(--font-primary, sans-serif); 3791 font-size: 24px; 3792 font-weight: 700; 3793 margin: 0 0 8px 0; 3794 white-space: nowrap; 3795 overflow: hidden; 3796 text-overflow: ellipsis; 3797 color: var(--neon-text, var(--primary-color, var(--text-color, #fff))); 3798 } 3799 3800 .ep-artist { 3801 font-family: var(--font-secondary, var(--font-primary, sans-serif)); 3802 font-size: 16px; 3803 font-weight: 400; 3804 margin: 0; 3805 color: var(--neon-text-muted, var(--text-color, #ccc)); 3806 opacity: 0.8; 3807 white-space: nowrap; 3808 overflow: hidden; 3809 text-overflow: ellipsis; 3810 } 3811 3812 .ep-waveform { 3813 display: block; 3814 margin: 16px auto 0 auto; 3815 opacity: 0.8; 3816 } 3817 3818 .ep-controls { 3819 width: 100%; 3820 display: flex; 3821 justify-content: center; 3822 align-items: center; 3823 } 3824 3825 .ep-play-btn { 3826 width: 72px; 3827 height: 72px; 3828 border-radius: 50%; 3829 background-color: var(--primary-color, #1B1464); 3830 border: none; 3831 display: flex; 3832 justify-content: center; 3833 align-items: center; 3834 cursor: pointer; 3835 padding: 0; 3836 box-shadow: 0 8px 16px rgba(0,0,0,0.2); 3837 transition: transform 0.2s, background-color 0.2s; 3838 color: #ffffff !important; 3839 } 3840 3841 .ep-play-btn:active { 3842 transform: scale(0.95); 3843 } 3844 3845 .ep-play-icon { 3846 width: 28px; 3847 height: 28px; 3848 display: flex; 3849 justify-content: center; 3850 align-items: center; 3851 } 3852 .ep-play-icon svg { 3853 width: 100%; 3854 height: 100%; 3855 color: #ffffff; 3856 fill: currentColor; 3857 } 3858} 3859</style> 3860 3861 <!-- Barra de Status de Conexão Global (Apenas Mobile via CSS) --> 3862 3863<div id="connectionStatusBar" role="alert" aria-live="assertive" aria-atomic="true"> 3864 <span id="connectionStatusIcon"></span> 3865 <span id="connectionStatusMsg"></span> 3866</div> 3867 3868<style> 3869/* Oculto em desktop */ 3870#connectionStatusBar { 3871 display: none; 3872} 3873 3874@media (max-width: 768px) { 3875 #connectionStatusBar { 3876 position: fixed; 3877 top: calc(env(safe-area-inset-top) + 12px); 3878 left: 16px; 3879 right: 16px; 3880 z-index: 100000; 3881 display: flex; 3882 align-items: center; 3883 gap: 10px; 3884 padding: 12px 20px; 3885 border-radius: 50px; 3886 color: #ffffff; 3887 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 3888 font-size: 14px; 3889 font-weight: 500; 3890 line-height: 1.3; 3891 box-sizing: border-box; 3892 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35); 3893 /* Estado padrão: acima da viewport com fade */ 3894 transform: translateY(-80px); 3895 opacity: 0; 3896 pointer-events: none; 3897 transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), 3898 opacity 0.3s ease, 3899 background-color 0.25s ease; 3900 will-change: transform, opacity; 3901 } 3902 3903 #connectionStatusBar.is-visible { 3904 transform: translateY(0); 3905 opacity: 1; 3906 pointer-events: auto; 3907 } 3908 3909 #connectionStatusBar.state-offline { 3910 background-color: #111111; 3911 } 3912 3913 #connectionStatusBar.state-online { 3914 background-color: #2e7d32; 3915 } 3916 3917 #connectionStatusIcon { 3918 flex-shrink: 0;
3919 width: 20px; 3920 height: 20px; 3921 display: flex; 3922 align-items: center; 3923 justify-content: center; 3924 } 3925 3926 #connectionStatusIcon svg { 3927 width: 20px; 3928 height: 20px; 3929 display: block; 3930 } 3931 3932 #connectionStatusMsg { 3933 flex: 1; 3934 min-width: 0; 3935 white-space: nowrap; 3936 overflow: hidden; 3937 text-overflow: ellipsis; 3938 } 3939} 3940</style> 3941
3942<script> 3943(function () { 3944 /* Ãcone Wi-Fi com X â estilo Happn */ 3945 var ICON_OFFLINE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6C4.27 3.47 8.46 2 12 2s7.73 1.47 11 4"/><path d="M5 10a10.16 10.16 0 0 1 7-2.82A10.16 10.16 0 0 1 19 10"/><path d="M8.53 14A5.14 5.14 0 0 1 12 12.85"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="21" y1="15" x2="15" y2="21"/></svg>'; 3946 var ICON_ONLINE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>'; 3947 3948 var bar = document.getElementById('connectionStatusBar'); 3949 var iconEl = document.getElementById('connectionStatusIcon'); 3950 var msgEl = document.getElementById('connectionStatusMsg'); 3951 var dismissTimer = null; 3952 3953 function showBar(type) { 3954 if (!bar) return; 3955 3956 if (dismissTimer) { 3957 clearTimeout(dismissTimer); 3958 dismissTimer = null; 3959 } 3960 3961 // Remove estados anteriores e força reflow para reiniciar a transição 3962 bar.classList.remove('state-offline', 'state-online', 'is-visible'); 3963 void bar.offsetWidth; 3964 3965 if (type === 'offline') { 3966 iconEl.innerHTML = ICON_OFFLINE; 3967 msgEl.textContent = 'Sem conexão à internet'; 3968 bar.classList.add('state-offline', 'is-visible'); 3969 3970 // Vibração háptica â iOS não suporta navigator.vibrate 3971 if (navigator.vibrate) { 3972 navigator.vibrate([200, 100, 200]); 3973 } 3974 } else { 3975 iconEl.innerHTML = ICON_ONLINE; 3976 msgEl.textContent = 'Conexão restabelecida'; 3977 bar.classList.add('state-online', 'is-visible'); 3978 3979 dismissTimer = setTimeout(function () { 3980 bar.classList.remove('is-visible'); 3981 dismissTimer = null; 3982 }, 3000); 3983 } 3984 } 3985 3986 window.addEventListener('offline', function () { showBar('offline'); }); 3987 window.addEventListener('online', function () { showBar('online'); }); 3988})(); 3989</script>
3989 3990 3991 <!-- Audio Player Script â não carregado dentro do app Rádio 3.0: sem o markup 3992 do player ele quebraria no bindEvents, e lá o site não deve tocar áudio nem 3993 disputar a Media Session com o player nativo. Quem usa window.radioPlayer 3994 já confere se ele existe. --> 3995
3995<script src="https://radio93portofeliz.com.br/assets/js/player.js?v=1790901861"></script>
3995 3996
3996<script src="https://radio93portofeliz.com.br/assets/js/app.js?v=1790901861" defer></script>
3996 3997 3998 <!-- PWA Service Worker Registration --> 3999
3999<script src="https://radio93portofeliz.com.br/sw-init.js" defer></script>
3999 4000 4001 <!-- Analytics Tracker --> 4002
4002<script src="https://radio93portofeliz.com.br/assets/js/tracker.js" defer></script>
4002 4003 4004 <!-- Top Músicas: tÃtulo/artista que não cabe no card desliza em vez de cortar --> 4005
4005<script src="https://radio93portofeliz.com.br/assets/js/chart-marquee.js?v=1790785403" defer></script>
4005 4006 4007 <!-- Banner Tracking (impressões e cliques) --> 4008 <!-- Não redeclarar: o head.php do tema já define `const SITE_URL`. Usar 4009 window.SITE_URL evita "Identifier 'SITE_URL' has already been declared" 4010 (que quebrava o tracking de banner e scripts seguintes). --> 4011
4011<script>window.SITE_URL = window.SITE_URL || 'https://radio93portofeliz.com.br';</script>
4011 4012
4012<script>const ACTIVE_CITY_ID = null;</script>
4012 4013
4013<script src="https://radio93portofeliz.com.br/assets/js/banner-tracking.js?v=1784221298" defer></script>
4013 4014 4015 4016 <!-- JSON-LD para SEO --> 4017
4017<script type="application/ld+json"> 4018 { 4019 "@context": "https://schema.org", 4020 "@type": "RadioStation", 4021 "name": "Rádio 93 FM", 4022 "url": "https://radio93portofeliz.com.br", 4023 "logo": "https://radio93portofeliz.com.br/uploads/media/6a05dc8e13c73_1778769038.png", 4024 "description": "A primeira em Porto Feliz!" 4025 } 4026 </script>
4026 4027 4028 <!-- Código Personalizado â fim do <body> (raw, escopo do tenant) --> 4029 </body> 4030</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.