<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue'

// El campo de puntos conectándose, una sola vez al entrar. Es el mismo lenguaje
// visual de la página pública —puntos que se enlazan, como el espacio vectorial
// donde vive el conocimiento del agente— así que refuerza la marca en vez de
// inventar algo nuevo para adentro.
//
// Dura poco más de un segundo y se destruye. Un fondo animado permanente en una
// herramienta de trabajo se ve bien diez segundos y molesta las ocho horas
// siguientes, además de tener un canvas comiendo batería todo el día.
const CLAVE = 'umind_despertar_visto'
const DURACION = 1250

const lienzo = ref(null)
const visible = ref(false)
let raf = null
let apagar = null

function quieto() {
  return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}

onMounted(() => {
  // Una vez por sesión: verlo en cada navegación del SPA sería exactamente el
  // fondo permanente que se quiere evitar.
  if (quieto() || sessionStorage.getItem(CLAVE)) return
  sessionStorage.setItem(CLAVE, '1')
  visible.value = true

  // El apagado se programa ACÁ y no dentro del requestAnimationFrame: en una
  // pestaña de fondo el navegador no corre rAF, así que el timeout nunca
  // llegaba a programarse y el canvas quedaba puesto para siempre. Abrir el
  // Studio en una pestaña que no se está mirando alcanzaba para dejarlo tapado
  // con una capa invisible.
  apagar = setTimeout(() => (visible.value = false), DURACION)

  requestAnimationFrame(() => {
    const cv = lienzo.value
    if (!cv) return
    const ctx = cv.getContext('2d')
    const dpr = Math.min(window.devicePixelRatio || 1, 2)
    const w = window.innerWidth
    const h = window.innerHeight
    cv.width = Math.round(w * dpr)
    cv.height = Math.round(h * dpr)
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0)

    // Menos puntos en pantallas chicas: en un teléfono la misma densidad se ve
    // sucia y cuesta más de dibujar.
    const cantidad = w < 640 ? 22 : 46
    const puntos = Array.from({ length: cantidad }, () => ({
      x: Math.random() * w,
      y: Math.random() * h,
      r: 1 + Math.random() * 1.6,
      demora: Math.random() * 0.45,
    }))

    const inicio = performance.now()

    const pintar = (ahora) => {
      const t = Math.min(1, (ahora - inicio) / DURACION)
      ctx.clearRect(0, 0, w, h)

      // Entra y sale en la misma curva: sin el desvanecido final el corte se
      // nota como un parpadeo.
      const opacidadGlobal = t < 0.75 ? 1 : 1 - (t - 0.75) / 0.25

      for (let i = 0; i < puntos.length; i++) {
        const p = puntos[i]
        const avance = Math.max(0, Math.min(1, (t - p.demora) / 0.4))
        if (avance <= 0) continue

        for (let j = i + 1; j < puntos.length; j++) {
          const q = puntos[j]
          const dx = p.x - q.x
          const dy = p.y - q.y
          const d = Math.sqrt(dx * dx + dy * dy)
          if (d > 170) continue
          ctx.strokeStyle = `rgba(142, 176, 47, ${0.16 * (1 - d / 170) * avance * opacidadGlobal})`
          ctx.lineWidth = 1
          ctx.beginPath()
          ctx.moveTo(p.x, p.y)
          ctx.lineTo(q.x, q.y)
          ctx.stroke()
        }

        ctx.beginPath()
        ctx.arc(p.x, p.y, p.r * avance, 0, Math.PI * 2)
        ctx.fillStyle = `rgba(142, 176, 47, ${0.5 * avance * opacidadGlobal})`
        ctx.fill()
      }

      if (t < 1) raf = requestAnimationFrame(pintar)
    }

    raf = requestAnimationFrame(pintar)
  })
})

// Si alguien navega antes de que termine, no queda un rAF girando ni un
// timeout tocando un componente que ya no existe.
onBeforeUnmount(() => {
  if (raf) cancelAnimationFrame(raf)
  if (apagar) clearTimeout(apagar)
})
</script>

<template>
  <!-- pointer-events-none: es decorativo y no puede robarle un click a nada. -->
  <canvas
    v-if="visible"
    ref="lienzo"
    class="fixed inset-0 w-full h-full pointer-events-none z-50"
    aria-hidden="true"
  ></canvas>
</template>
