1import { Controller } from "@hotwired/stimulus" 2 3// Barra de progreso de lectura que recorre los cuatro bordes de la pantalla: nace en el centro 4// del borde superior y baja simétrica por los dos lados (un brazo horario, otro antihorario) 5// hasta juntarse en el centro del borde inferior, para hacer visible que la propuesta sigue 6// hacia abajo y que el final (donde vive el CTA) se alcanza. 7// 8// La meta NO es el fondo de la página sino el elemento marcado con [data-progreso-scroll-meta] 9// (el CTA final): el marco se completa cuando este entra entero en pantalla â lo que hay 10// detrás (opiniones, FAQ) es refuerzo, no camino. Sin ese marcador (p. ej. propuesta caducada, 11// sin CTA final), cae al alto total del documento. 12// 13// El target es un <rect> SVG a pantalla completa con pathLength="1": el perÃmetro va de 0 a 1 14// empezando en la esquina superior izquierda y en sentido horario. Pintar el avance es un 15// único tramo de longitud `avance` centrado en el punto medio del borde superior (que está 16// en t = (W/2) / (2W+2H), NO en una constante: pathLength reparte por longitud real, asà el 17// recorrido va a velocidad uniforme por los cuatro lados). El tramo se coloca con 18// stroke-dashoffset y el patrón "avance, 1-avance" tiene periodo 1, con lo que el brazo que 19// va hacia la izquierda envuelve por t=0 sin corte. El color sale de la variable CSS --avance 20// (azul â esmeralda vÃa color-mix en la vista): la punta llega verde al final, el mismo 21// esmeralda que la página usa para "ahorro/hecho". Mejora progresiva: el contenedor llega con 22// `hidden` y es este controller quien lo enseña â sin JS no queda una pista vacÃa pintada 23// alrededor. 24export default class extends Controller { 25 static targets = ["barra"] 26 27 connect() { 28 this.frame = null 29 this.programar = this.programar.bind(this) 30 window.addEventListener("scroll", this.programar, { passive: true }) 31 window.addEventListener("resize", this.programar, { passive: true }) 32 // removeAttribute y no `.hidden = false`: el elemento es un <svg> y SVGElement no tiene la 33 // propiedad IDL `hidden` (solo HTMLElement); la asignación crearÃa un expando sin efecto. 34 this.element.removeAttribute("hidden") 35 this.actualizar() 36 } 37 38 disconnect() { 39 window.removeEventListener("scroll", this.programar) 40 window.removeEventListener("resize", this.programar) 41 if (this.frame) cancelAnimationFrame(this.frame) 42 } 43 44 // Coalescer a un frame: scroll dispara muchas veces por gesto y con pintar una vez basta. 45 programar() { 46 this.frame ??= requestAnimationFrame(() => { 47 this.frame = null 48 this.actualizar() 49 }) 50 } 51 52 actualizar() { 53 const meta = document.querySelector("[data-progreso-scroll-meta]") 54 const total = meta 55 ? window.scrollY + meta.getBoundingClientRect().bottom - window.innerHeight 56 : document.documentElement.scrollHeight - window.innerHeight 57 const avance = total > 0 ? Math.min(Math.max(window.scrollY / total, 0), 1) : 1 58 // El punto medio del borde superior en unidades de perÃmetro. Se mide el svg (no el 59 // viewport): con viewport-fit=cover el marco va entre los safe-area-inset y es más bajo 60 // que la pantalla. 61 const { width: ancho, height: alto } = this.element.getBoundingClientRect() 62 const centroArriba = (ancho / 2) / (2 * (ancho + alto)) 63 const inicio = centroArriba - avance / 2 64 this.barraTarget.setAttribute("stroke-dasharray", `${avance} ${1 - avance}`) 65 this.barraTarget.setAttribute("stroke-dashoffset", `${-inicio}`) 66 this.barraTarget.style.setProperty("--avance", avance) 67 } 68}
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.