inicio
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
const form = document.getElementById("contact-form");
|
||||
const confirmBox = document.getElementById("contact-confirmation");
|
||||
form?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
form.hidden = true;
|
||||
confirmBox?.classList.add("show");
|
||||
confirmBox?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,255 @@
|
||||
// ============================================================
|
||||
// AMOR & ROMA — Diagnóstico interactivo
|
||||
// 5 preguntas máx., con lógica condicional real:
|
||||
// - La pregunta 3 cambia según la respuesta de la pregunta 1.
|
||||
// - Si el usuario llega con contexto (?interes=redes o ?plan=premium
|
||||
// desde Servicios/Planes), se preselecciona y se saltea esa pregunta.
|
||||
// - El resultado combina las 5 respuestas en una recomendación de
|
||||
// servicio + plan sugerido.
|
||||
// ============================================================
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const form = document.getElementById("diagnostico");
|
||||
if (!form) return;
|
||||
|
||||
/* ---------- Contexto heredado desde otras páginas ---------- */
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const preInteres = params.get("interes"); // ej: redes, web, marca, automatizacion, captacion, varias
|
||||
const prePlan = params.get("plan"); // ej: esencial, impulso, premium, elite
|
||||
|
||||
const state = {
|
||||
necesidad: preInteres || null,
|
||||
etapa: null,
|
||||
desafio: null,
|
||||
objetivo: null,
|
||||
madurez: null,
|
||||
planPrevio: prePlan || null,
|
||||
};
|
||||
|
||||
/* ---------- Banco de preguntas ---------- */
|
||||
const desafiosPorNecesidad = {
|
||||
marca: [
|
||||
["identidad-debil", "Mi identidad no refleja el valor real de mi negocio"],
|
||||
["sin-relato", "No sé cómo comunicar lo que hago de forma consistente"],
|
||||
["rediseno", "Necesito un rediseño completo, lo actual ya no funciona"],
|
||||
],
|
||||
presencia: [
|
||||
["redes-sin-resultado", "Tengo redes, pero no generan resultados"],
|
||||
["sin-web", "No tengo una web propia"],
|
||||
["inconsistencia", "Todo se ve desordenado o inconsistente entre canales"],
|
||||
],
|
||||
contenido: [
|
||||
["sin-tiempo", "No tengo tiempo para crear contenido de forma constante"],
|
||||
["sin-estrategia", "No sé qué contenido realmente funciona"],
|
||||
["calidad", "Necesito subir la calidad de producción"],
|
||||
],
|
||||
web: [
|
||||
["sin-web-propia", "No tengo web propia todavía"],
|
||||
["no-convierte", "Tengo web, pero no convierte visitas en clientes"],
|
||||
["ecommerce", "Necesito catálogo o e-commerce"],
|
||||
],
|
||||
captacion: [
|
||||
["pocos-leads", "No estoy generando suficientes oportunidades"],
|
||||
["procesos-manuales", "Pierdo oportunidades por procesos manuales"],
|
||||
["necesito-ads", "Necesito publicidad paga para atraer clientes"],
|
||||
],
|
||||
automatizacion: [
|
||||
["todo-manual", "Respondo consultas manualmente y no llego a todo"],
|
||||
["sin-seguimiento", "Pierdo leads por falta de seguimiento"],
|
||||
["integracion", "Necesito integrar WhatsApp, redes y CRM"],
|
||||
],
|
||||
varias: [
|
||||
["desde-cero", "Necesito ordenar todo desde cero"],
|
||||
["piezas-sueltas", "Tengo piezas sueltas que no están integradas"],
|
||||
["ecosistema", "Quiero un ecosistema digital completo"],
|
||||
],
|
||||
};
|
||||
|
||||
const necesidadLabels = {
|
||||
marca: "Marca",
|
||||
presencia: "Presencia digital",
|
||||
contenido: "Contenido",
|
||||
web: "Web",
|
||||
captacion: "Captación de clientes",
|
||||
automatizacion: "Automatización",
|
||||
varias: "Varias cosas",
|
||||
};
|
||||
|
||||
/* ---------- Elementos del DOM ---------- */
|
||||
const steps = Array.from(form.querySelectorAll(".diag-step"));
|
||||
const progressWrap = form.querySelector(".diag-progress");
|
||||
const resultBox = document.getElementById("diag-result");
|
||||
const captureBox = document.getElementById("diag-capture");
|
||||
|
||||
let order = ["necesidad", "etapa", "desafio", "objetivo", "madurez"];
|
||||
if (preInteres && necesidadLabels[preInteres]) {
|
||||
order = order.filter((k) => k !== "necesidad");
|
||||
}
|
||||
let currentIndex = 0;
|
||||
|
||||
function buildProgress() {
|
||||
progressWrap.innerHTML = "";
|
||||
order.forEach(() => {
|
||||
const span = document.createElement("span");
|
||||
progressWrap.appendChild(span);
|
||||
});
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
Array.from(progressWrap.children).forEach((span, i) => {
|
||||
span.dataset.state = i < currentIndex ? "done" : i === currentIndex ? "active" : "";
|
||||
});
|
||||
}
|
||||
|
||||
function renderDesafioOptions() {
|
||||
const target = form.querySelector('[data-step="desafio"] .diag-options');
|
||||
const necesidad = state.necesidad || "varias";
|
||||
const options = desafiosPorNecesidad[necesidad] || desafiosPorNecesidad.varias;
|
||||
target.innerHTML = "";
|
||||
options.forEach(([value, label]) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "diag-option";
|
||||
btn.dataset.field = "desafio";
|
||||
btn.dataset.value = value;
|
||||
btn.setAttribute("aria-pressed", "false");
|
||||
btn.textContent = label;
|
||||
target.appendChild(btn);
|
||||
});
|
||||
attachOptionHandlers(target);
|
||||
}
|
||||
|
||||
function attachOptionHandlers(scope) {
|
||||
scope.querySelectorAll(".diag-option").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
scope.querySelectorAll(".diag-option").forEach((b) => b.setAttribute("aria-pressed", "false"));
|
||||
btn.setAttribute("aria-pressed", "true");
|
||||
state[btn.dataset.field] = btn.dataset.value;
|
||||
setTimeout(goNext, 220);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showStep(key) {
|
||||
steps.forEach((s) => s.classList.toggle("active", s.dataset.step === key));
|
||||
if (key === "desafio") renderDesafioOptions();
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
function goNext() {
|
||||
if (currentIndex < order.length - 1) {
|
||||
currentIndex++;
|
||||
showStep(order[currentIndex]);
|
||||
} else {
|
||||
showResult();
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
showStep(order[currentIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
form.querySelectorAll("[data-action='back']").forEach((btn) => btn.addEventListener("click", goBack));
|
||||
|
||||
// Handlers for static (non-dynamic) option steps
|
||||
form.querySelectorAll('.diag-step[data-step]').forEach((stepEl) => {
|
||||
if (stepEl.dataset.step === "desafio") return; // built dynamically
|
||||
attachOptionHandlers(stepEl);
|
||||
});
|
||||
|
||||
/* ---------- Motor de recomendación ---------- */
|
||||
const solucionPorNecesidad = {
|
||||
marca: "Branding & Identidad",
|
||||
presencia: "Redes Sociales & Contenido + Desarrollo Web",
|
||||
contenido: "Redes Sociales & Contenido",
|
||||
web: "Desarrollo Web",
|
||||
captacion: "Paid Media + Automatizaciones",
|
||||
automatizacion: "Automatizaciones",
|
||||
varias: "Solución Integral (Branding + Web + Redes)",
|
||||
};
|
||||
|
||||
function planSugerido() {
|
||||
const etapaScore = { comenzando: 0, validando: 1, creciendo: 2, consolidado: 3 };
|
||||
const madurezScore = { nula: 0, basica: 1, intermedia: 2, avanzada: 3 };
|
||||
const e = etapaScore[state.etapa] ?? 1;
|
||||
const m = madurezScore[state.madurez] ?? 1;
|
||||
const score = Math.round((e + m) / 2);
|
||||
const plan = ["esencial", "impulso", "premium", "elite"][Math.min(score, 3)];
|
||||
return plan;
|
||||
}
|
||||
|
||||
const planLabels = { esencial: "Plan Esencial", impulso: "Plan Impulso", premium: "Plan Premium", elite: "Plan Élite" };
|
||||
|
||||
function showResult() {
|
||||
steps.forEach((s) => s.classList.remove("active"));
|
||||
progressWrap.querySelectorAll("span").forEach((s) => (s.dataset.state = "done"));
|
||||
|
||||
const necesidadKey = state.necesidad || "varias";
|
||||
const solucion = solucionPorNecesidad[necesidadKey];
|
||||
const plan = state.planPrevio && planLabels[state.planPrevio] ? state.planPrevio : planSugerido();
|
||||
|
||||
resultBox.innerHTML = `
|
||||
<p class="eyebrow">Nuestra recomendación</p>
|
||||
<h3>Te conviene priorizar: ${solucion}</h3>
|
||||
<p class="lede">Situación actual: negocio en etapa "${etapaLabel(state.etapa)}", con una presencia digital ${madurezLabel(state.madurez)}, buscando ${objetivoLabel(state.objetivo)}.</p>
|
||||
<p style="margin-top:1rem;"><strong>Próximo paso recomendado:</strong> ${planLabels[plan]} ${state.planPrevio ? "— ya veníamos conversando sobre este plan" : ""}.</p>
|
||||
<p style="margin-top:0.4rem; color:var(--text-soft); font-size:0.9rem;">Esto es una orientación inicial, no un diagnóstico definitivo — lo afinamos juntos en una conversación.</p>
|
||||
`;
|
||||
resultBox.hidden = false;
|
||||
captureBox.hidden = false;
|
||||
document.getElementById("diag-plan-hidden").value = plan;
|
||||
document.getElementById("diag-solucion-hidden").value = solucion;
|
||||
resultBox.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
|
||||
function etapaLabel(v) {
|
||||
return { comenzando: "recién empezando", validando: "validando", creciendo: "en crecimiento", consolidado: "consolidado" }[v] || "sin definir";
|
||||
}
|
||||
function madurezLabel(v) {
|
||||
return { nula: "nula", basica: "básica", intermedia: "intermedia", avanzada: "avanzada" }[v] || "sin definir";
|
||||
}
|
||||
function objetivoLabel(v) {
|
||||
return {
|
||||
vender: "vender más",
|
||||
profesionalizar: "profesionalizar su imagen",
|
||||
clientes: "generar más clientes",
|
||||
tiempo: "ahorrar tiempo con procesos",
|
||||
referente: "posicionarse como referente",
|
||||
}[v] || "crecer";
|
||||
}
|
||||
|
||||
form.querySelector("[data-action='restart']")?.addEventListener("click", () => {
|
||||
currentIndex = 0;
|
||||
state.desafio = state.objetivo = state.madurez = state.etapa = null;
|
||||
if (!preInteres) state.necesidad = null;
|
||||
form.querySelectorAll(".diag-option").forEach((b) => b.setAttribute("aria-pressed", "false"));
|
||||
resultBox.hidden = true;
|
||||
captureBox.hidden = true;
|
||||
showStep(order[0]);
|
||||
});
|
||||
|
||||
const finalForm = document.getElementById("diag-lead-form");
|
||||
if (finalForm) {
|
||||
finalForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const confirmBox = document.getElementById("diag-confirmation");
|
||||
finalForm.hidden = true;
|
||||
confirmBox.classList.add("show");
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- Inicio ---------- */
|
||||
buildProgress();
|
||||
if (preInteres && necesidadLabels[preInteres]) {
|
||||
const banner = document.getElementById("diag-context-banner");
|
||||
if (banner) {
|
||||
banner.hidden = false;
|
||||
banner.textContent = `Ya sabemos que tu interés inicial es "${necesidadLabels[preInteres]}" — seguimos desde ahí.`;
|
||||
}
|
||||
}
|
||||
showStep(order[0]);
|
||||
})();
|
||||
@@ -0,0 +1,152 @@
|
||||
// ============================================================
|
||||
// AMOR & ROMA — Hero: campo de humo/partículas orgánicas
|
||||
// Canvas 2D, reactivo al cursor, pasa detrás del texto.
|
||||
// ============================================================
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const canvas = document.getElementById("hero-canvas");
|
||||
if (!canvas) return;
|
||||
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
let width, height, dpr;
|
||||
let pointer = { x: -9999, y: -9999, active: false };
|
||||
let particles = [];
|
||||
const COUNT = reduceMotion ? 0 : Math.min(70, Math.floor(window.innerWidth / 16));
|
||||
|
||||
function themeColors() {
|
||||
const dark = document.documentElement.getAttribute("data-theme") === "dark";
|
||||
return dark
|
||||
? [
|
||||
[186, 18, 77], // magenta
|
||||
[0, 109, 143], // teal web
|
||||
[223, 189, 185], // blush
|
||||
]
|
||||
: [
|
||||
[0, 63, 82], // teal deep
|
||||
[186, 18, 77], // magenta
|
||||
[223, 189, 185], // blush
|
||||
];
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const rect = canvas.parentElement.getBoundingClientRect();
|
||||
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
width = rect.width;
|
||||
height = rect.height;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
canvas.style.width = width + "px";
|
||||
canvas.style.height = height + "px";
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
|
||||
function rand(min, max) { return Math.random() * (max - min) + min; }
|
||||
|
||||
function makeParticle() {
|
||||
const colors = themeColors();
|
||||
const c = colors[Math.floor(Math.random() * colors.length)];
|
||||
return {
|
||||
x: rand(0, width),
|
||||
y: rand(0, height),
|
||||
baseX: 0,
|
||||
baseY: 0,
|
||||
vx: rand(-0.12, 0.12),
|
||||
vy: rand(-0.18, -0.05),
|
||||
r: rand(50, 130),
|
||||
alpha: rand(0.03, 0.09),
|
||||
color: c,
|
||||
wander: rand(0, Math.PI * 2),
|
||||
wanderSpeed: rand(0.002, 0.006),
|
||||
};
|
||||
}
|
||||
|
||||
function init() {
|
||||
resize();
|
||||
particles = new Array(COUNT).fill(0).map(makeParticle);
|
||||
}
|
||||
|
||||
function step() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
particles.forEach((p) => {
|
||||
// wander drift (turbulence)
|
||||
p.wander += p.wanderSpeed;
|
||||
p.x += p.vx + Math.cos(p.wander) * 0.15;
|
||||
p.y += p.vy + Math.sin(p.wander) * 0.1;
|
||||
|
||||
// cursor influence — smoke curls away/around pointer
|
||||
if (pointer.active) {
|
||||
const dx = p.x - pointer.x;
|
||||
const dy = p.y - pointer.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
const influence = Math.max(0, 220 - dist) / 220;
|
||||
if (influence > 0) {
|
||||
p.x += (dx / dist) * influence * 2.4;
|
||||
p.y += (dy / dist) * influence * 2.4;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap around edges softly
|
||||
if (p.y < -p.r) p.y = height + p.r;
|
||||
if (p.x < -p.r) p.x = width + p.r;
|
||||
if (p.x > width + p.r) p.x = -p.r;
|
||||
if (p.y > height + p.r) p.y = -p.r;
|
||||
|
||||
const [r, g, b] = p.color;
|
||||
const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r);
|
||||
gradient.addColorStop(0, `rgba(${r},${g},${b},${p.alpha})`);
|
||||
gradient.addColorStop(1, `rgba(${r},${g},${b},0)`);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
if (!reduceMotion) requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
const parent = canvas.parentElement;
|
||||
parent.addEventListener("mousemove", (e) => {
|
||||
const rect = parent.getBoundingClientRect();
|
||||
pointer.x = e.clientX - rect.left;
|
||||
pointer.y = e.clientY - rect.top;
|
||||
pointer.active = true;
|
||||
});
|
||||
parent.addEventListener("mouseleave", () => { pointer.active = false; });
|
||||
parent.addEventListener(
|
||||
"touchmove",
|
||||
(e) => {
|
||||
const rect = parent.getBoundingClientRect();
|
||||
const t = e.touches[0];
|
||||
if (!t) return;
|
||||
pointer.x = t.clientX - rect.left;
|
||||
pointer.y = t.clientY - rect.top;
|
||||
pointer.active = true;
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
resize();
|
||||
});
|
||||
|
||||
// Redraw once (or restart loop) if theme changes, to refresh palette
|
||||
const observer = new MutationObserver(() => {
|
||||
particles.forEach((p) => {
|
||||
const colors = themeColors();
|
||||
p.color = colors[Math.floor(Math.random() * colors.length)];
|
||||
});
|
||||
});
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
||||
|
||||
init();
|
||||
if (reduceMotion) {
|
||||
// Static single soft render for reduced motion users
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
} else {
|
||||
step();
|
||||
}
|
||||
})();
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// ============================================================
|
||||
// AMOR & ROMA — comportamiento compartido (todas las páginas)
|
||||
// ============================================================
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/* ---------- Tema claro / oscuro ---------- */
|
||||
const root = document.documentElement;
|
||||
const stored = localStorage.getItem("ar-theme");
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const initial = stored || (prefersDark ? "dark" : "light");
|
||||
root.setAttribute("data-theme", initial);
|
||||
|
||||
document.querySelectorAll("[data-theme-toggle]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const current = root.getAttribute("data-theme");
|
||||
const next = current === "dark" ? "light" : "dark";
|
||||
root.setAttribute("data-theme", next);
|
||||
localStorage.setItem("ar-theme", next);
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- Menú móvil ---------- */
|
||||
const navToggle = document.querySelector("[data-nav-toggle]");
|
||||
const mobileMenu = document.querySelector("[data-mobile-menu]");
|
||||
if (navToggle && mobileMenu) {
|
||||
navToggle.addEventListener("click", () => {
|
||||
mobileMenu.classList.toggle("open");
|
||||
const expanded = mobileMenu.classList.contains("open");
|
||||
navToggle.setAttribute("aria-expanded", String(expanded));
|
||||
});
|
||||
mobileMenu.querySelectorAll("a").forEach((a) =>
|
||||
a.addEventListener("click", () => mobileMenu.classList.remove("open"))
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------- Año dinámico en footer ---------- */
|
||||
document.querySelectorAll("[data-year]").forEach((el) => {
|
||||
el.textContent = new Date().getFullYear();
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,11 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
document.querySelectorAll(".plan-card").forEach((card) => {
|
||||
const trigger = card.querySelector("[data-plan-trigger]");
|
||||
trigger?.addEventListener("click", () => {
|
||||
const wasExpanded = card.classList.contains("expanded");
|
||||
document.querySelectorAll(".plan-card").forEach((c) => c.classList.remove("expanded"));
|
||||
if (!wasExpanded) card.classList.add("expanded");
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,48 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const filters = document.querySelectorAll("[data-filter]");
|
||||
const cards = document.querySelectorAll("[data-tags]");
|
||||
|
||||
filters.forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
filters.forEach((b) => b.setAttribute("data-active", "false"));
|
||||
btn.setAttribute("data-active", "true");
|
||||
const value = btn.dataset.filter;
|
||||
cards.forEach((card) => {
|
||||
const tags = card.dataset.tags.split(",");
|
||||
const show = value === "todos" || tags.includes(value);
|
||||
card.style.display = show ? "" : "none";
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const backdrop = document.getElementById("case-modal");
|
||||
const modalBody = document.getElementById("case-modal-body");
|
||||
document.querySelectorAll("[data-case-trigger]").forEach((card) => {
|
||||
card.addEventListener("click", () => {
|
||||
modalBody.innerHTML = `
|
||||
<p class="eyebrow">${card.dataset.caseTag || ""}</p>
|
||||
<h3>${card.dataset.caseTitle}</h3>
|
||||
<p style="margin-top:0.8rem;"><strong>Contexto:</strong> ${card.dataset.caseContext}</p>
|
||||
<p style="margin-top:0.6rem;"><strong>Problema:</strong> ${card.dataset.caseProblem}</p>
|
||||
<p style="margin-top:0.6rem;"><strong>Solución:</strong> ${card.dataset.caseSolution}</p>
|
||||
<div class="tags" style="margin-top:1rem;">${(card.dataset.tags || "")
|
||||
.split(",")
|
||||
.map((t) => `<span class="pill">${t}</span>`)
|
||||
.join("")}</div>
|
||||
<p style="margin-top:1rem; font-size:0.85rem; color:var(--text-soft);">* Caso de referencia (placeholder) — se reemplaza por proyectos reales del portfolio.</p>
|
||||
`;
|
||||
backdrop.classList.add("open");
|
||||
});
|
||||
});
|
||||
|
||||
backdrop?.addEventListener("click", (e) => {
|
||||
if (e.target === backdrop || e.target.closest("[data-modal-close]")) {
|
||||
backdrop.classList.remove("open");
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") backdrop?.classList.remove("open");
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user