Fujimod
Try your camera firmware in the browser and build Fujimod SD boot files.
Plataforma gratuita y comunitaria para que cada mascota vuelva a casa: perdidos, encontrados y adopción en un mismo lugar.
Translation pending
LifestyleJavaScript · CSSGitHub @ipiseradevOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Plataforma gratuita y comunitaria, con una landing 3D interactiva: personajes procedurales en WebGL, un sistema de calidad por dispositivo y una experiencia completa en mobile.
Huella es una plataforma ficticia (pieza de portfolio de creative development) para publicar mascotas perdidas, encontradas y en adopción, gratis y sostenida por la comunidad. Esta etapa es solo frontend: toda la información viene de datos de ejemplo preparados con la misma forma que va a tener la API real, y las acciones que requieren cuenta (publicar, contactar) llevan a /login, todavía sin implementar.
El proyecto nació como la landing de una clínica veterinaria y se reconvirtió manteniendo la identidad visual —estilo 3D clay, Toby y Luna, paleta cálida— pero con dos objetivos igual de importantes: el contenido nuevo, y que ande fluido en celulares y compus de gama media, no solo en una máquina potente.
Toda la escena 3D se genera 100% por código: no hay modelos .glb, texturas ni imágenes externas. Los personajes, los objetos flotantes y las partículas se construyen a partir de primitivas y shaders propios. Las fotos de las publicaciones son ilustraciones SVG generadas en el momento, no imágenes de mascotas reales de terceros.
| 🧠 Calidad adaptativa | Un sistema de tiers (src/quality.js) mide hardware, FPS real y prefers-reduced-motion al arrancar, y decide cuántas partículas, sombras, floaters y efectos de cursor se activan — con baja en caliente si el FPS cae. |
| 🐶 Personajes procedurales | Toby y Luna, armados con primitivas y material tipo clay. Respiran, parpadean, mueven cola y orejas y siguen el cursor con la mirada. Toby lleva collar con chapita. |
| 💥 Interacción | Hover: el personaje se pone contento y saca la lengua. Clic: salta con squash & stretch, gira 360° y suelta corazones 3D (tier alto). |
| 🎬 Coreografía de scroll | Los personajes recorren la página entre secciones, interpolando pose, escala y rotación con ScrollTrigger en modo scrub. |
| ✨ Morph de partículas | Hasta 4.000 partículas en un shader GLSL que pasan de una nube a una huella, un corazón y una casita, en una sección pinned que cambia a modo oscuro. |
| 📱 3D real en mobile | Sistema de anclaje propio: en mobile los personajes se ubican sobre espacios reservados del layout y saltan de uno a otro, sin tapar contenido. |
| 🩹 Fallback sin WebGL | Si no hay WebGL o el usuario pide menos movimiento, se muestra una ilustración SVG estática en vez del canvas. |
| 📋 Cards desde datos | Publicaciones de perdidos/encontrados, adopción y reencuentros se renderizan desde src/data/*.js, con la misma forma que va a tener la respuesta de la API. Filtros visuales por estado y especie. |
| 🪟 Detalle de mascota | Modal con animación FLIP que crece desde la card, con focus trap, cierre con Esc y foto, estado, zona y contacto. |
| 🐾 Micro-interacciones | Cursor con forma de huella que deja pisadas, botones magnéticos y cards con tilt 3D — solo en equipos que lo pueden mover sin trabarse. |
| Tecnología | Uso |
|---|---|
| Three.js | Render WebGL, materiales PBR, shader de partículas |
| GSAP + ScrollTrigger | Timelines, intro, pinning y coreografía ligada al scroll |
| Lenis | Smooth scroll, activado solo en desktop de gama alta |
| Vite | Dev server y build de producción con code splitting |
| HTML + CSS + JS vanilla | Sin framework: el DOM es estático y la complejidad está en la capa 3D |
Requisitos: Node.js 18 o superior (recomendado 22, ver .nvmrc).
git clone https://github.com/ipiseradev/Landing-Huella.git
cd Landing-Huella
npm install
npm run dev
La app queda en http://localhost:5173.
| Script | Descripción |
|---|---|
npm run dev | Servidor de desarrollo con HMR |
npm run build | Build de producción en dist/ |
npm run preview | Sirve el build localmente para probarlo antes de deployar |
💡 Para probar en el celular dentro de la misma red:
npx vite --hosty abrí la IP que aparece en la terminal.
.
├── index.html # Markup de todas las secciones (contenido estático)
├── vite.config.js # Build y división en chunks
├── vercel.json # Headers de caché y seguridad
└── src/
├── main.js # Bootstrap: calidad, Lenis opcional, loop único, coreografía, carga diferida del 3D
├── quality.js # Sistema de tiers: hardware, sondeo de FPS, baja en caliente
├── poses.js # Poses 3D por sección (desktop) y anclajes (mobile)
├── style.css # Design tokens, layout y responsive
├── data/
│ ├── pets.js # Mock: publicaciones perdidas/encontradas
│ ├── adoptions.js # Mock: animales en adopción
│ ├── stories.js # Mock: historias de reencuentro (marcadas como ejemplo)
│ ├── donation.js # Único lugar con el link de donación
│ └── placeholder.js # Genera la "foto" (SVG) de cada publicación de ejemplo
├── three/
│ ├── Experience.js # Renderer, cámara, luces, raycasting, modo anclado y render bajo demanda
│ ├── Critter.js # Clase base: parpadeo, mirada, salto, sombra de contacto
│ ├── Dog.js # Toby (collar + chapita)
│ ├── Cat.js # Luna
│ ├── ParticleMorph.js # Shader de partículas: huella -> corazón -> casita
│ ├── Floaters.js # Objetos flotantes con parallax (cantidad según tier)
│ ├── HeartBurst.js # Pool de corazones al acariciar (cantidad según tier)
│ ├── materials.js # Materiales "clay" y brillantes
│ └── shapes.js # Geometrías procedurales (huella, corazón, casita, chapita…)
└── ui/
├── cards.js # Renderiza cards desde los datos de src/data/* + filtros
├── cursor.js # Cursor huella + rastro de pisadas (tier alto)
├── modal.js # Modal de detalle de mascota: FLIP + focus trap + Esc
├── interactions.js # Magnetic, tilt, FAQ, menú mobile
└── split.js # Split de texto para animaciones
Toda la app corre sobre un único loop (gsap.ticker), que existe desde el arranque aunque el 3D todavía no haya terminado de cargar:
flowchart LR
A[gsap.ticker] --> B["Lenis.raf (si aplica)"]
B --> C[ScrollTrigger.update]
C --> D["Timelines con scrub<br/>modifican exp.state"]
A --> E["exp?.tick() si el 3D ya cargó"]
D -. lee .-> E
E --> F{¿Pestaña oculta?}
F -- sí --> Z[No actualiza ni renderiza]
F -- no --> G{¿Mobile?}
G -- no --> H[Aplica poses<br/>desde state]
G -- sí --> I[Anclaje a .stage<br/>del DOM]
H --> J[Mirada, hover,<br/>partículas, flotantes]
I --> J
J --> K{¿Frame-cap del tier<br/>ya se cumplió?}
K -- no --> Z
K -- sí --> L[renderer.render]
quality.js decide cuánto puede hacer el dispositivo. El resto de los módulos solo leen quality.tier (o se suscriben a onQualityChange); no vuelven a medir nada por su cuenta.main.js decide qué pasa con el scroll y cuándo carga el 3D. Cada transición entre secciones es un fromTo con scrub que escribe en un objeto de estado plano (exp.state). Three.js se importa con import() dinámico recién después del primer render del contenido.Experience decide cómo se ve. Lee ese estado en cada frame y lo traduce a transformaciones 3D. No sabe nada de ScrollTrigger ni de tiers más allá del que recibió al construirse.Critter encapsula el comportamiento compartido (parpadeo, mirada, salto, sombra) y Dog/Cat solo definen su geometría y sus animaciones propias.cards.js no sabe de dónde vienen los datos. Recibe un array y una función de render; hoy ese array sale de un import, mañana sale de un fetch.Gracias a esto, agregar una sección nueva es sumar una pose en poses.js y una línea transition() en main.js; agregar un tipo de publicación nuevo es sumar un archivo en src/data/ y una función de render en cards.js.
Todo lo "caro" de la página (sombras, partículas, floaters, Lenis, cursor con rastro, tilt 3D) está condicionado a un tier que se calcula una sola vez al arrancar, en src/quality.js:
navigator.hardwareConcurrency, navigator.deviceMemory y prefers-reduced-motion. Con eso ya arranca en low, medium o high.low antes de que el 3D se construya.| Alto | Medio | Bajo | |
|---|---|---|---|
| Partículas del morph | 4.000 | 2.000 | 0 (no se crea el shader) |
| Objetos flotantes | 12 | 6 | 0 |
| Pool de corazones al acariciar | 16 | 10 | 0 |
| Sombras reales del renderer | Solo desktop (PCFShadowMap 1024px) | No | No |
| Pixel ratio | hasta 1.5 (1.25 mobile) | hasta 1.5 (1.25 mobile) | hasta 1 |
| Antialias | Sí | No | No |
| Cap de frame-rate | Sin cap | ~45 fps | ~30 fps |
| Lenis (smooth scroll) | Solo desktop | No (scroll nativo) | No (scroll nativo) |
| Cursor huella / tilt / magnético | Sí (si hay mouse real) | No | No |
| Blur real en el header | Sí | No (fondo sólido) | No (fondo sólido) |
Si no hay WebGL, o si prefers-reduced-motion está activo, directamente no se carga Three.js: se muestra una ilustración SVG estática de Toby y Luna (.webgl-fallback en index.html).
Para forzar un tier manualmente mientras desarrollás, lo más simple es imitar la señal que usa el sondeo: en DevTools → Performance → CPU throttling, o reduciendo navigator.hardwareConcurrency vía un override de dispositivo.
Toda la información de las cards vive en src/data/, con la misma forma que va a tener la respuesta de la API cuando exista el backend:
// forma base (pets.js, adoptions.js)
{ id, tipo, especie, nombre, foto, zona, fecha, descripcion, estado, contacto }
// adoptions.js agrega:
{ edad, tamaño, caracter }
src/data/pets.js — publicaciones de perdidos (tipo: 'lost') y encontrados (tipo: 'found').src/data/adoptions.js — animales en adopción (tipo: 'adoption').src/data/stories.js — historias de reencuentro, todas con ejemplo: true. Como la plataforma es nueva, no hay historias reales todavía: la UI les pone un badge "Ejemplo" explícito. Sacá esa bandera (o el campo entero) cuando haya historias reales.src/data/placeholder.js — genera la "foto" de cada entrada mock como un SVG liviano (huella sobre un color de marca). El campo foto ya es un string usable en <img src>, así que conectar la API real es simplemente dejar de llamar a petPlaceholder() y guardar ahí la URL de la foto subida.src/ui/cards.js — mountCards(container, items, renderFn) arma el HTML desde un array; initPetFilters(container, filterBar) filtra por estado/especie togglando card.hidden. Ninguna de las dos funciones sabe de dónde salió items.Para conectar la API real: en src/main.js, la función mountData() es el único lugar que importa estos arrays. El día que haya backend, cambiás esos import por fetch (o los dejás como fallback si el fetch falla) y el resto de la app —cards, filtros, modal de detalle— sigue funcionando sin tocar nada más.
El modal de detalle (src/ui/modal.js) busca la publicación por id en el array combinado de PETS + ADOPTIONS que le pasa main.js; si sumás una fuente de datos nueva, solo hay que incluirla en ese array.
Geometría procedural en lugar de modelos. Evita depender de assets externos, mantiene el bundle chico y permite animar cada parte (orejas, cola, párpados) sin rigging. Todas las piezas reutilizan una única SphereGeometry unitaria escalada.
Posiciones relativas al viewport. Las poses de desktop usan fx (fracción del ancho visible) en lugar de coordenadas absolutas, así la composición se mantiene en cualquier relación de aspecto. Las secciones con grilla de cards (Perdidos, Adopción, Reencuentros) reservan la columna izquierda para los personajes con width: min(…, …vw); margin-left: auto, el mismo truco que usaba el layout original para las especialidades — así el contenido nunca queda tapado.
Modo anclado para mobile. En pantallas angostas no hay espacio lateral para un personaje fijo. En lugar de esconderlo, cada sección reserva un div.stage y el personaje se proyecta sobre él en cada frame (getBoundingClientRect → coordenadas de mundo). Al cambiar de sección, interpola entre los dos anclajes con un pequeño salto.
Morph de partículas en GPU. Las tres formas (huella, corazón, casita) se muestrean una sola vez en CPU (rejection sampling sobre funciones implícitas) y se guardan como atributos. El vertex shader interpola entre ellas con un desfase aleatorio por partícula. CPU por frame: un uniform.
Three.js diferido. El <script> de entrada importa Three.js con import() dinámico recién después de que el contenido de texto ya es legible y el sondeo de calidad terminó — no antes. Esto evita que 130 kB gzip de JS bloqueen el primer render, a costa de que Toby tarde un par de segundos en aparecer (con una pequeña animación de caída cuando lo hace).
Hit-testing barato. El raycasting se hace solo contra el rig de cada personaje, se saltea en dispositivos táctiles (no hay hover) y se ignora cuando el puntero está sobre UI.
Medido con Lighthouse (mobile) y un script propio de FPS por Puppeteer (scripts/measure-fps.cjs) antes y después de la Etapa 2 de optimización, sobre el mismo contenido.
⚠️ Lighthouse se corrió en una máquina de desarrollo compartida (no un runner limpio), así que el score varió bastante entre corridas (61–78). El dato confiable ahí es el First Contentful Paint, estable en todas las corridas. Para rendimiento real, la comparación de FPS de abajo es la que importa.
| Métrica | Antes | Después |
|---|---|---|
| Lighthouse performance (mobile) | 61 | 61–78 (ruido de medición) |
| First Contentful Paint | 3.5 s | 2.9 s |
| FPS desktop (idle / scroll) | ~14.4 / ~13.2 | ~17.6 / ~24.4 |
| FPS mobile emulado + CPU 4x (idle / scroll) | ~14.5 / ~12.7 | ~59.8 / ~59.7 |
El caso que más importa —mobile con CPU lenta— pasó de ~13 fps (destrabable) a ~60 fps (fluido), gracias al sistema de tiers: en gama baja no hay sombras reales, partículas, floaters, Lenis ni cursor/tilt, y el render está limitado a 30 fps.
Otros números:
| Medida | Detalle |
|---|---|
| Three.js | Se carga en su propio chunk, con import() dinámico después del primer render — no bloquea el contenido inicial |
| Sin assets externos | Cero requests de modelos o texturas; las "fotos" de las publicaciones son SVG generados; solo las fuentes de Google Fonts como request externo |
backdrop-filter | De 22 usos a 2 (header, solo tier alto) |
| Geometrías compartidas | Una esfera unitaria para todas las piezas; pool de objetos para los corazones |
| Caché HTTP | Assets con hash servidos con Cache-Control: immutable (ver vercel.json) |
Para repetir la medición: npm run build && npm run preview, y correr Lighthouse contra http://localhost:4173 o node scripts/measure-fps.cjs http://localhost:4173/ <etiqueta> (requiere puppeteer-core y Chrome instalado).
header, nav, main, section, article, figure, footer) y jerarquía de títulos consistente.aria-hidden) y nunca captura eventos (pointer-events: none); si no hay WebGL se reemplaza por una ilustración SVG, también decorativa.tabindex, role="button", aria-label) y se abren con Enter/Espacio además de clic.role="dialog", aria-modal, focus trap, cierre con Esc y retorno del foco a la card de origen.aria-expanded y botones navegables por teclado.prefers-reduced-motion se respeta de verdad: sin smooth scroll, sin coreografía 3D, sin marquees, sin pulsos, y el contenido se muestra directo sin esperar ninguna animación.pointer: fine y tier alto; en el resto se mantiene el comportamiento nativo.| Qué querés cambiar | Dónde |
|---|---|
| Textos y secciones | index.html |
| Publicaciones de ejemplo (perdidos/encontrados) | src/data/pets.js |
| Animales en adopción de ejemplo | src/data/adoptions.js |
| Historias de reencuentro de ejemplo | src/data/stories.js |
| Link de donación (Cafecito, Mercado Pago, etc.) | src/data/donation.js — un solo lugar |
| Colores, tipografías y espaciados | Variables en :root de src/style.css |
| Umbrales y qué activa cada tier de calidad | src/quality.js |
| Dónde aparece cada personaje en desktop | POSES en src/poses.js |
| Dónde aparece cada personaje en mobile | MOBILE_ANCHORS en src/poses.js + los div.stage del HTML |
| Formas y colores de los personajes | src/three/Dog.js, src/three/Cat.js |
| Figuras del morph de partículas | SHAPES en src/three/ParticleMorph.js |
| Objetos flotantes | src/three/Floaters.js + src/three/shapes.js |
El proyecto está listo para Vercel sin configuración adicional:
npm run build → dist/).También funciona en Netlify, Cloudflare Pages o cualquier hosting estático: solo hay que publicar la carpeta dist/.
Esta etapa es solo frontend, con todo preparado para conectar lo que falta:
/login, que todavía no existe.petPlaceholder()).ejemplo: true de src/data/stories.js cuando existan.Diseño y desarrollo por @ipiseradev
Huella es una plataforma ficticia creada como pieza de portfolio. Las publicaciones, nombres y datos de contacto son de ejemplo.