version 1

This commit is contained in:
2026-08-21 00:10:27 -03:00
commit fa04541059
72 changed files with 2051 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# Usamos la imagen ligera de Nginx
FROM nginx:alpine
# Copiamos todos tus archivos web a la carpeta que Nginx sirve por defecto
COPY . /usr/share/nginx/html
# Exponemos el puerto 80 (el estándar de web)
EXPOSE 80
+11
View File
@@ -0,0 +1,11 @@
version: '3.8'
services:
web:
# Le decimos que construya la imagen usando el Dockerfile que está en el mismo directorio
build: .
# Mapeamos el puerto 80 del contenedor a un puerto de tu VM (ej. 8080)
ports:
- "8080:80"
# Para que el contenedor se reinicie solo si se cae
restart: unless-stopped
+558
View File
@@ -0,0 +1,558 @@
"use strict";
// ===== CONFIGURACIÓN DEL EFECTO =====
const config = {
TEXTURE_DOWNSAMPLE: 1,
DENSITY_DISSIPATION: 0.975,
VELOCITY_DISSIPATION: 0.98,
PRESSURE_DISSIPATION: 0.8,
PRESSURE_ITERATIONS: 20,
CURL: 30,
SPLAT_RADIUS: 0.003
};
// ===== INICIALIZAR CANVAS =====
let canvas = document.getElementById("fluidCanvas");
console.log("Canvas encontrado:", canvas);
if (!canvas) {
console.error("ERROR: No se encontró el canvas con id 'fluidCanvas'");
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// ===== CONTEXTO WEBGL =====
let pointers = [];
let splatStack = [];
function getWebGLContext(canvas) {
let params = {
alpha: false,
depth: false,
stencil: false,
antialias: false
};
let gl = canvas.getContext("webgl2", params);
let isWebGL2 = !!gl;
if (!isWebGL2)
gl =
canvas.getContext("webgl", params) ||
canvas.getContext("experimental-webgl", params);
let halfFloat = gl.getExtension("OES_texture_half_float");
let support_linear_float = gl.getExtension("OES_texture_half_float_linear");
if (isWebGL2) {
gl.getExtension("EXT_color_buffer_float");
support_linear_float = gl.getExtension("OES_texture_float_linear");
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
let internalFormat = isWebGL2 ? gl.RGBA16F : gl.RGBA;
let internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA;
let formatRG = isWebGL2 ? gl.RG : gl.RGBA;
let texType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
return {
gl: gl,
ext: {
internalFormat: internalFormat,
internalFormatRG: internalFormatRG,
formatRG: formatRG,
texType: texType
},
support_linear_float: support_linear_float
};
}
let _getWebGLContext = getWebGLContext(canvas);
let gl = _getWebGLContext.gl;
let ext = _getWebGLContext.ext;
let support_linear_float = _getWebGLContext.support_linear_float;
// ===== POINTER =====
function pointerPrototype() {
this.id = -1;
this.x = 0;
this.y = 0;
this.dx = 0;
this.dy = 0;
this.down = false;
this.moved = false;
this.color = [30, 0, 300];
}
pointers.push(new pointerPrototype());
// ===== SHADERS =====
let GLProgram = (function () {
function GLProgram(vertexShader, fragmentShader) {
if (!(this instanceof GLProgram))
throw new TypeError("Cannot call a class as a function");
this.uniforms = {};
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
throw gl.getProgramInfoLog(this.program);
let uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
let uniformName = gl.getActiveUniform(this.program, i).name;
this.uniforms[uniformName] = gl.getUniformLocation(
this.program,
uniformName
);
}
}
GLProgram.prototype.bind = function bind() {
gl.useProgram(this.program);
};
return GLProgram;
})();
function compileShader(type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
throw gl.getShaderInfoLog(shader);
return shader;
}
// ===== VERTEX SHADER =====
let baseVertexShader = compileShader(
gl.VERTEX_SHADER,
"precision highp float; precision mediump sampler2D; attribute vec2 aPosition; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform vec2 texelSize; void main () { vUv = aPosition * 0.5 + 0.5; vL = vUv - vec2(texelSize.x, 0.0); vR = vUv + vec2(texelSize.x, 0.0); vT = vUv + vec2(0.0, texelSize.y); vB = vUv - vec2(0.0, texelSize.y); gl_Position = vec4(aPosition, 0.0, 1.0); }"
);
// ===== FRAGMENT SHADERS =====
let clearShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTexture; uniform float value; void main () { gl_FragColor = value * texture2D(uTexture, vUv); }"
);
let displayShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTexture; void main () { gl_FragColor = texture2D(uTexture, vUv); }"
);
let splatShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uTarget; uniform float aspectRatio; uniform vec3 color; uniform vec2 point; uniform float radius; void main () { vec2 p = vUv - point.xy; p.x *= aspectRatio; vec3 splat = exp(-dot(p, p) / radius) * color; vec3 base = texture2D(uTarget, vUv).xyz; gl_FragColor = vec4(base + splat, 1.0); }"
);
let advectionManualFilteringShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; vec4 bilerp (in sampler2D sam, in vec2 p) { vec4 st; st.xy = floor(p - 0.5) + 0.5; st.zw = st.xy + 1.0; vec4 uv = st * texelSize.xyxy; vec4 a = texture2D(sam, uv.xy); vec4 b = texture2D(sam, uv.zy); vec4 c = texture2D(sam, uv.xw); vec4 d = texture2D(sam, uv.zw); vec2 f = p - st.xy; return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } void main () { vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy; gl_FragColor = dissipation * bilerp(uSource, coord); gl_FragColor.a = 1.0; }"
);
let advectionShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform float dt; uniform float dissipation; void main () { vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; gl_FragColor = dissipation * texture2D(uSource, coord); }"
);
let divergenceShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; vec2 sampleVelocity (in vec2 uv) { vec2 multiplier = vec2(1.0, 1.0); if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; } if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; } if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; } if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; } return multiplier * texture2D(uVelocity, uv).xy; } void main () { float L = sampleVelocity(vL).x; float R = sampleVelocity(vR).x; float T = sampleVelocity(vT).y; float B = sampleVelocity(vB).y; float div = 0.5 * (R - L + T - B); gl_FragColor = vec4(div, 0.0, 0.0, 1.0); }"
);
let curlShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; void main () { float L = texture2D(uVelocity, vL).y; float R = texture2D(uVelocity, vR).y; float T = texture2D(uVelocity, vT).x; float B = texture2D(uVelocity, vB).x; float vorticity = R - L - T + B; gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0); }"
);
let vorticityShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; uniform sampler2D uCurl; uniform float curl; uniform float dt; void main () { float L = texture2D(uCurl, vL).y; float R = texture2D(uCurl, vR).y; float T = texture2D(uCurl, vT).x; float B = texture2D(uCurl, vB).x; float C = texture2D(uCurl, vUv).x; vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L)); force *= 1.0 / length(force + 0.00001) * curl * C; vec2 vel = texture2D(uVelocity, vUv).xy; gl_FragColor = vec4(vel + force * dt, 0.0, 1.0); }"
);
let pressureShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uDivergence; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; float C = texture2D(uPressure, vUv).x; float divergence = texture2D(uDivergence, vUv).x; float pressure = (L + R + B + T - divergence) * 0.25; gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); }"
);
let gradientSubtractShader = compileShader(
gl.FRAGMENT_SHADER,
"precision highp float; precision mediump sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uPressure; uniform sampler2D uVelocity; vec2 boundary (in vec2 uv) { uv = min(max(uv, 0.0), 1.0); return uv; } void main () { float L = texture2D(uPressure, boundary(vL)).x; float R = texture2D(uPressure, boundary(vR)).x; float T = texture2D(uPressure, boundary(vT)).x; float B = texture2D(uPressure, boundary(vB)).x; vec2 velocity = texture2D(uVelocity, vUv).xy; velocity.xy -= vec2(R - L, T - B); gl_FragColor = vec4(velocity, 0.0, 1.0); }"
);
// ===== FRAMEBUFFERS =====
let textureWidth, textureHeight;
let density, velocity, divergence, curl, pressure;
function initFramebuffers() {
textureWidth = gl.drawingBufferWidth >> config.TEXTURE_DOWNSAMPLE;
textureHeight = gl.drawingBufferHeight >> config.TEXTURE_DOWNSAMPLE;
let iFormat = ext.internalFormat;
let iFormatRG = ext.internalFormatRG;
let formatRG = ext.formatRG;
let texType = ext.texType;
density = createDoubleFBO(
0,
textureWidth,
textureHeight,
iFormat,
gl.RGBA,
texType,
support_linear_float ? gl.LINEAR : gl.NEAREST
);
velocity = createDoubleFBO(
2,
textureWidth,
textureHeight,
iFormatRG,
formatRG,
texType,
support_linear_float ? gl.LINEAR : gl.NEAREST
);
divergence = createFBO(
4,
textureWidth,
textureHeight,
iFormatRG,
formatRG,
texType,
gl.NEAREST
);
curl = createFBO(
5,
textureWidth,
textureHeight,
iFormatRG,
formatRG,
texType,
gl.NEAREST
);
pressure = createDoubleFBO(
6,
textureWidth,
textureHeight,
iFormatRG,
formatRG,
texType,
gl.NEAREST
);
}
function createFBO(texId, w, h, internalFormat, format, type, param) {
gl.activeTexture(gl.TEXTURE0 + texId);
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
let fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0
);
gl.viewport(0, 0, w, h);
gl.clear(gl.COLOR_BUFFER_BIT);
return [texture, fbo, texId];
}
function createDoubleFBO(texId, w, h, internalFormat, format, type, param) {
let fbo1 = createFBO(texId, w, h, internalFormat, format, type, param);
let fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param);
return {
get first() {
return fbo1;
},
get second() {
return fbo2;
},
swap: function swap() {
let temp = fbo1;
fbo1 = fbo2;
fbo2 = temp;
}
};
}
initFramebuffers();
// ===== PROGRAMS =====
let clearProgram = new GLProgram(baseVertexShader, clearShader);
let displayProgram = new GLProgram(baseVertexShader, displayShader);
let splatProgram = new GLProgram(baseVertexShader, splatShader);
let advectionProgram = new GLProgram(
baseVertexShader,
support_linear_float ? advectionShader : advectionManualFilteringShader
);
let divergenceProgram = new GLProgram(baseVertexShader, divergenceShader);
let curlProgram = new GLProgram(baseVertexShader, curlShader);
let vorticityProgram = new GLProgram(baseVertexShader, vorticityShader);
let pressureProgram = new GLProgram(baseVertexShader, pressureShader);
let gradienSubtractProgram = new GLProgram(
baseVertexShader,
gradientSubtractShader
);
// ===== BLIT =====
let blit = (function () {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]),
gl.STATIC_DRAW
);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array([0, 1, 2, 0, 2, 3]),
gl.STATIC_DRAW
);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
return function (destination) {
gl.bindFramebuffer(gl.FRAMEBUFFER, destination);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
};
})();
// ===== SPLAT =====
function splat(x, y, dx, dy, color) {
splatProgram.bind();
gl.uniform1i(splatProgram.uniforms.uTarget, velocity.first[2]);
gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
gl.uniform2f(
splatProgram.uniforms.point,
x / canvas.width,
1.0 - y / canvas.height
);
gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0);
gl.uniform1f(splatProgram.uniforms.radius, config.SPLAT_RADIUS);
blit(velocity.second[1]);
velocity.swap();
gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]);
gl.uniform3f(
splatProgram.uniforms.color,
color[0] * 0.3,
color[1] * 0.3,
color[2] * 0.3
);
blit(density.second[1]);
density.swap();
}
// ===== RESIZE =====
function resizeCanvas() {
(canvas.width !== canvas.clientWidth ||
canvas.height !== canvas.clientHeight) &&
((canvas.width = canvas.clientWidth),
(canvas.height = canvas.clientHeight),
initFramebuffers());
}
// ===== COLORES DE LA MARCA CON TRANSICIÓN =====
const brandColors = [
[0.874, 0.741, 0.725], // Rosa (#DFBDB9)
[0.729, 0.071, 0.302], // Bordo (#BA124D)
[0.000, 0.247, 0.322] // Teal (#003F52)
];
let currentColor = brandColors[0];
let targetColor = brandColors[1];
let transitionProgress = 0;
const transitionSpeed = 0.008;
function lerpColor(colorA, colorB, t) {
return [
colorA[0] + (colorB[0] - colorA[0]) * t,
colorA[1] + (colorB[1] - colorA[1]) * t,
colorA[2] + (colorB[2] - colorA[2]) * t
];
}
let colorArr = currentColor;
let count = 0;
// ===== EVENTOS CON COLORES DE MARCA =====
canvas.addEventListener("mousemove", function (e) {
console.log("Mouse moviéndose:", e.clientX, e.clientY);
count++;
if (count > 1) {
transitionProgress += transitionSpeed;
if (transitionProgress >= 1) {
transitionProgress = 0;
let currentIndex = brandColors.indexOf(currentColor);
let nextIndex = (currentIndex + 1) % brandColors.length;
currentColor = targetColor;
targetColor = brandColors[nextIndex];
}
colorArr = lerpColor(currentColor, targetColor, transitionProgress);
count = 0;
}
pointers[0].down = true;
pointers[0].color = colorArr;
pointers[0].moved = pointers[0].down;
pointers[0].dx = (e.offsetX - pointers[0].x) * 10.0;
pointers[0].dy = (e.offsetY - pointers[0].y) * 10.0;
pointers[0].x = e.offsetX;
pointers[0].y = e.offsetY;
});
canvas.addEventListener(
"touchmove",
function (e) {
e.preventDefault();
let touches = e.targetTouches;
count++;
if (count > 20) {
transitionProgress += transitionSpeed;
if (transitionProgress >= 1) {
transitionProgress = 0;
let currentIndex = brandColors.indexOf(currentColor);
let nextIndex = (currentIndex + 1) % brandColors.length;
currentColor = targetColor;
targetColor = brandColors[nextIndex];
}
colorArr = lerpColor(currentColor, targetColor, transitionProgress);
count = 0;
}
for (let i = 0, len = touches.length; i < len; i++) {
if (i >= pointers.length) pointers.push(new pointerPrototype());
pointers[i].id = touches[i].identifier;
pointers[i].down = true;
pointers[i].x = touches[i].pageX;
pointers[i].y = touches[i].pageY;
pointers[i].color = colorArr;
let pointer = pointers[i];
pointer.moved = pointer.down;
pointer.dx = (touches[i].pageX - pointer.x) * 10.0;
pointer.dy = (touches[i].pageY - pointer.y) * 10.0;
pointer.x = touches[i].pageX;
pointer.y = touches[i].pageY;
}
},
false
);
// ===== LOOP PRINCIPAL =====
let lastTime = Date.now();
function update() {
resizeCanvas();
let dt = Math.min((Date.now() - lastTime) / 1000, 0.016);
lastTime = Date.now();
gl.viewport(0, 0, textureWidth, textureHeight);
advectionProgram.bind();
gl.uniform2f(
advectionProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(advectionProgram.uniforms.uSource, velocity.first[2]);
gl.uniform1f(advectionProgram.uniforms.dt, dt);
gl.uniform1f(
advectionProgram.uniforms.dissipation,
config.VELOCITY_DISSIPATION
);
blit(velocity.second[1]);
velocity.swap();
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(advectionProgram.uniforms.uSource, density.first[2]);
gl.uniform1f(
advectionProgram.uniforms.dissipation,
config.DENSITY_DISSIPATION
);
blit(density.second[1]);
density.swap();
for (let i = 0, len = pointers.length; i < len; i++) {
let pointer = pointers[i];
if (pointer.moved) {
splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color);
pointer.moved = false;
}
}
curlProgram.bind();
gl.uniform2f(
curlProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.first[2]);
blit(curl[1]);
vorticityProgram.bind();
gl.uniform2f(
vorticityProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(vorticityProgram.uniforms.uCurl, curl[2]);
gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
gl.uniform1f(vorticityProgram.uniforms.dt, dt);
blit(velocity.second[1]);
velocity.swap();
divergenceProgram.bind();
gl.uniform2f(
divergenceProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.first[2]);
blit(divergence[1]);
clearProgram.bind();
let pressureTexId = pressure.first[2];
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
gl.bindTexture(gl.TEXTURE_2D, pressure.first[0]);
gl.uniform1i(clearProgram.uniforms.uTexture, pressureTexId);
gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE_DISSIPATION);
blit(pressure.second[1]);
pressure.swap();
pressureProgram.bind();
gl.uniform2f(
pressureProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence[2]);
pressureTexId = pressure.first[2];
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
for (let _i = 0; _i < config.PRESSURE_ITERATIONS; _i++) {
gl.bindTexture(gl.TEXTURE_2D, pressure.first[0]);
gl.uniform1i(pressureProgram.uniforms.uPressure, pressureTexId);
blit(pressure.second[1]);
pressure.swap();
}
gradienSubtractProgram.bind();
gl.uniform2f(
gradienSubtractProgram.uniforms.texelSize,
1.0 / textureWidth,
1.0 / textureHeight
);
gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.first[2]);
gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.first[2]);
blit(velocity.second[1]);
velocity.swap();
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
displayProgram.bind();
gl.uniform1i(displayProgram.uniforms.uTexture, density.first[2]);
blit(null);
requestAnimationFrame(update);
}
update();
// ===== REDIMENSIONAR AL CAMBIAR TAMAÑO DE VENTANA =====
window.addEventListener("resize", function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initFramebuffers();
});
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+674
View File
@@ -0,0 +1,674 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Amor & Roma — Estudio Creativo</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- ===== CANVAS PARA EFECTO DE HUMO ===== -->
<canvas id="fluidCanvas"></canvas>
<!-- ===== OVERLAY ===== -->
<div class="overlay" id="overlay" onclick="toggleMenu()"></div>
<!-- ===== MENU BOTTOM SHEET ===== -->
<nav class="sheet" id="sheet">
<div class="sheet-handle"></div>
<div class="sheet-top">
<div class="logo">
<a href="index.html">
<img src="logo_ciruela.png" alt="Amor & Roma" class="logo-img">
</a>
</div>
<button class="close-btn" onclick="toggleMenu()">✕</button>
</div>
<div class="sheet-body">
<div class="sheet-col">
<div class="sheet-col-title">Soluciones Web</div>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Diseño Web</span>
<span class="sheet-link-desc">Sitios informativos modernos</span>
</a>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">E-commerce</span>
<span class="sheet-link-desc">Tiendas online con todas las funcionalidades</span>
</a>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Google Ads</span>
<span class="sheet-link-desc">Publicidad en Search y Display</span>
</a>
</div>
<div class="sheet-col">
<div class="sheet-col-title">Redes Sociales</div>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Community Manager</span>
<span class="sheet-link-desc">Administración profesional de redes</span>
</a>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Creación de Contenido</span>
<span class="sheet-link-desc">Contenido original y atractivo</span>
</a>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Meta Ads</span>
<span class="sheet-link-desc">Publicidad en Facebook e Instagram</span>
</a>
</div>
<div class="sheet-col">
<div class="sheet-col-title">Branding</div>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Diseño de Logos</span>
<span class="sheet-link-desc">Personalizados y profesionales</span>
</a>
<a href="#servicios" onclick="toggleMenu()" class="sheet-link">
<span class="sheet-link-icon">⊞</span>
<span class="sheet-link-main">Identidad de Marca</span>
<span class="sheet-link-desc">Creación y desarrollo de marca</span>
</a>
<a href="#contacto" onclick="toggleMenu()" class="sheet-link highlight">
<span class="sheet-link-icon">✉</span>
<span class="sheet-link-main">Contacto</span>
<span class="sheet-link-desc">Empezá tu proyecto</span>
</a>
</div>
</div>
</nav>
<!-- ===== HEADER ===== -->
<header>
<div class="logo">
<img src="logo_frio.png" alt="Amor & Roma" class="logo-img">
</div>
<button class="menu-btn" onclick="toggleMenu()" aria-label="Abrir menú">
<span class="bars"><span></span><span></span><span></span></span>
<span class="menu-label">Menú</span>
</button>
</header>
<!-- ===== HERO ===== -->
<section class="hero" id="inicio">
<div class="hero-content">
<div class="eyebrow">Estudio Creativo · Agencia Boutique</div>
<h1>Marcas con <span class="grad-word">esencia,</span><br>construidas con <span class="grad-word">estrategia.</span></h1>
<p>Branding, diseño web y marketing digital para negocios que quieren crecer sin perder lo que los hace únicos.</p>
<div class="hero-actions">
<a href="#servicios" class="btn btn-solid">Ver servicios</a>
<a href="#contacto" class="btn btn-outline">Contactanos</a>
</div>
</div>
<a href="#servicios" class="home-btn" aria-label="Ir a servicios">✦</a>
<div class="scroll-hint"><div class="line"></div>Scroll</div>
</section>
<!-- ===== SERVICIOS ===== -->
<section class="servicios" id="servicios">
<div class="eyebrow">Lo que hacemos</div>
<h2>Servicios pensados para marcas con <span class="grad-word">esencia.</span></h2>
<p class="lede">Cuatro disciplinas que trabajan juntas, no por separado — cada proyecto se arma con la combinación justa para tu marca.</p>
<div class="tabs">
<button class="tab active" onclick="showTab(0,this)"><span class="num">01</span>Branding</button>
<button class="tab" onclick="showTab(1,this)"><span class="num">02</span>Diseño Web</button>
<button class="tab" onclick="showTab(2,this)"><span class="num">03</span>Marketing Digital</button>
<button class="tab" onclick="showTab(3,this)"><span class="num">04</span>IA & Automatización</button>
</div>
<div class="panel active" data-panel="0">
<div class="panel-copy">
<h3>Identidad con <em>alma,</em><br>no solo con estilo.</h3>
<p>Construimos el sistema visual y verbal que sostiene tu marca: desde el naming y el logo hasta el tono de voz con el que hablás en cada canal.</p>
<a href="#contacto" class="btn btn-outline">Quiero mi identidad de marca →</a>
</div>
<div class="panel-list">
<div class="item"><span class="icon-min">✦</span> Naming y concepto de marca</div>
<div class="item"><span class="icon-min">✦</span> Diseño de logo e isotipo</div>
<div class="item"><span class="icon-min">✦</span> Paleta, tipografía y manual de marca</div>
<div class="item"><span class="icon-min">✦</span> Tono de voz y guía de comunicación</div>
</div>
</div>
<div class="panel" data-panel="1">
<div class="panel-copy">
<h3>Sitios que <em>convierten,</em><br>no solo que se ven bien.</h3>
<p>Diseño y desarrollo de sitios web a medida — rápidos, responsivos y pensados para que quien llega, actúe.</p>
<a href="#contacto" class="btn btn-outline">Quiero mi sitio web →</a>
</div>
<div class="panel-list">
<div class="item"><span class="icon-min">✦</span> Landing pages y sitios institucionales</div>
<div class="item"><span class="icon-min">✦</span> UX/UI a medida</div>
<div class="item"><span class="icon-min">✦</span> Optimización SEO técnica</div>
<div class="item"><span class="icon-min">✦</span> E-commerce y tiendas online</div>
</div>
</div>
<div class="panel" data-panel="2">
<div class="panel-copy">
<h3>Presencia <em>constante,</em><br>estrategia real.</h3>
<p>Gestión de redes, contenido y pauta — con reportes claros para que sepas siempre qué está funcionando y por qué.</p>
<a href="#contacto" class="btn btn-outline">Quiero mi estrategia digital →</a>
</div>
<div class="panel-list">
<div class="item"><span class="icon-min">✦</span> Community management</div>
<div class="item"><span class="icon-min">✦</span> Publicidad en Meta e Instagram Ads</div>
<div class="item"><span class="icon-min">✦</span> Estrategia de contenido y calendario</div>
<div class="item"><span class="icon-min">✦</span> Reportes mensuales de performance</div>
</div>
</div>
<div class="panel" data-panel="3">
<div class="panel-copy">
<h3>Tecnología al servicio<br>de <em>tu tiempo.</em></h3>
<p>Automatizamos lo repetitivo para que tu equipo se enfoque en lo estratégico — sin perder el trato cercano con tus clientes.</p>
<a href="#contacto" class="btn btn-outline">Quiero automatizar procesos →</a>
</div>
<div class="panel-list">
<div class="item"><span class="icon-min">✦</span> Chatbots y respuestas automáticas</div>
<div class="item"><span class="icon-min">✦</span> Automatización de leads y seguimiento</div>
<div class="item"><span class="icon-min">✦</span> Integraciones con CRM y WhatsApp</div>
<div class="item"><span class="icon-min">✦</span> Consultoría en IA aplicada a tu negocio</div>
</div>
</div>
<a href="#planes" class="home-btn" aria-label="Ir a planes">✦</a>
</section>
<!-- ===== PLANES ===== -->
<section class="planes" id="planes">
<div class="eyebrow">Inversión</div>
<h2>Un plan para cada <span class="grad-word">etapa</span> de tu marca.</h2>
<p class="lede">Cuatro niveles pensados para acompañar tu crecimiento — desde presencia digital consistente hasta gestión integral de marca.</p>
<div class="plans-grid">
<div class="plan-card">
<div class="plan-name">Esencial</div>
<div class="plan-stars">★</div>
<div class="plan-obj">Establecer una presencia digital profesional y consistente.</div>
<ul class="plan-feats">
<li>2 redes sociales</li>
<li>8 publicaciones + 8 historias / mes</li>
<li>1 diseño de marca (logo)</li>
<li>Reunión mensual de estrategia</li>
<li>Reporte mensual de métricas</li>
</ul>
<a href="#contacto" class="plan-cta">+Info</a>
</div>
<div class="plan-card">
<div class="plan-name">Impulso</div>
<div class="plan-stars">★★</div>
<div class="plan-obj">Aumentar visibilidad, interacción y generación de leads calificados.</div>
<ul class="plan-feats">
<li>2 redes · 12 publicaciones + 4 reels</li>
<li>Logo + paleta de colores</li>
<li>1 campaña Meta Ads + retargeting</li>
<li>Automatización de respuestas frecuentes</li>
<li>Gestión de Meta Business Suite</li>
</ul>
<a href="#contacto" class="plan-cta">+Info</a>
</div>
<div class="plan-card reco">
<div class="badge">Más Popular</div>
<div class="plan-name">Premium</div>
<div class="plan-stars">★★★</div>
<div class="plan-obj">Consolidar y expandir la empresa comercialmente.</div>
<ul class="plan-feats">
<li>3 redes · 16 publicaciones + 8 reels</li>
<li>Sesión de fotos/video quincenal</li>
<li>2 campañas Meta Ads + Google Ads</li>
<li>Respuestas con IA 24/7</li>
<li>Reporte semanal + reunión mensual</li>
<li>Soporte prioritario</li>
</ul>
<a href="#contacto" class="plan-cta">+Info</a>
</div>
<div class="plan-card">
<div class="plan-name">Elite</div>
<div class="plan-stars">★★★★</div>
<div class="plan-obj">Máximo posicionamiento y gestión integral de marca.</div>
<ul class="plan-feats">
<li>4 redes · 20 publicaciones + 16 reels</li>
<li>Dirección creativa integral de marca</li>
<li>3 campañas + TikTok Ads + Google Ads</li>
<li>Estrategia 360° con planificación mensual</li>
<li>Gestión de reputación online</li>
</ul>
<a href="#contacto" class="plan-cta">+Info</a>
</div>
</div>
<a href="#cotizador" class="btn btn-outline plan-main-cta">Consulta tu plan ideal</a>
<a href="#proceso" class="home-btn" aria-label="Ir a proceso">✦</a>
</section>
<!-- ===== SERVICIOS A LA CARTA ===== -->
<section class="servicios-carta" id="servicios-carta">
<div class="eyebrow">Servicios a la carta</div>
<h2>Para complementar cualquier <span class="grad-word">plan,</span><br>o contratar de forma <span class="grad-word">independiente.</span></h2>
<p class="lede">Elegí los servicios que necesitás, sin compromiso mensual. Todo lo que tu marca necesita, a tu ritmo.</p>
<div class="acc" id="acc">
<div class="acc-item open">
<div class="acc-head" onclick="toggleAcc(this)">
<div class="acc-head-left"><span class="acc-num">01</span><span class="acc-name">Diseño Web & Desarrollo</span></div>
<div class="acc-toggle">+</div>
</div>
<div class="acc-body">
<div class="acc-body-inner">
<div class="service-row"><b>Landing Page Profesional</b>Diseño de una sección, UI/UX + SEO básico <span class="price">Desde $350.000</span></div>
<div class="service-row"><b>Sitio Institucional (hasta 7 secciones)</b>Inicio, Servicios, Nosotros, Blog, Contacto <span class="price">Desde $600.000</span></div>
<div class="service-row"><b>E-commerce</b>Tienda online autoadministrable con pasarela de pago <span class="price">Desde $900.000</span></div>
<div class="service-row"><b>Mantenimiento Web</b>Backups, seguridad y soporte técnico <span class="price">Desde $50.000/mes</span></div>
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-head" onclick="toggleAcc(this)">
<div class="acc-head-left"><span class="acc-num">02</span><span class="acc-name">Branding e Identidad Visual</span></div>
<div class="acc-toggle">+</div>
</div>
<div class="acc-body">
<div class="acc-body-inner">
<div class="service-row"><b>Diseño de Logotipo</b>3 propuestas + revisiones <span class="price">Desde $180.000</span></div>
<div class="service-row"><b>Manual de Marca Completo</b>Logo, paleta, tipografías, tono de voz <span class="price">Desde $300.000</span></div>
<div class="service-row"><b>Rediseño de Identidad Visual</b>Actualización de marca existente <span class="price">A cotizar</span></div>
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-head" onclick="toggleAcc(this)">
<div class="acc-head-left"><span class="acc-num">03</span><span class="acc-name">Publicidad Digital Avanzada</span></div>
<div class="acc-toggle">+</div>
</div>
<div class="acc-body">
<div class="acc-body-inner">
<div class="service-row"><b>Campaña Google Ads</b>Búsqueda y display <span class="price">Desde $150.000/mes</span></div>
<div class="service-row"><b>Campaña TikTok Ads</b>Alcance a audiencias jóvenes <span class="price">Desde $120.000/mes</span></div>
<div class="service-row"><b>Auditoría de Campañas</b>Análisis y oportunidades de mejora <span class="price">Desde $90.000</span></div>
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-head" onclick="toggleAcc(this)">
<div class="acc-head-left"><span class="acc-num">04</span><span class="acc-name">Diseño Digital & Gráfico</span></div>
<div class="acc-toggle">+</div>
</div>
<div class="acc-body">
<div class="acc-body-inner">
<div class="service-row"><b>Infografías</b>Comunicación visual de datos y procesos <span class="price">Desde $80.000/u</span></div>
<div class="service-row"><b>Plantillas para Redes</b>Set editable de 5 piezas <span class="price">Desde $100.000</span></div>
<div class="service-row"><b>Material Impreso</b>Flyers, folletos, tarjetas <span class="price">A cotizar</span></div>
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-head" onclick="toggleAcc(this)">
<div class="acc-head-left"><span class="acc-num">05</span><span class="acc-name">Producción Audiovisual</span></div>
<div class="acc-toggle">+</div>
</div>
<div class="acc-body">
<div class="acc-body-inner">
<div class="service-row"><b>Sesión Fotográfica</b>Producto, instalaciones o equipo <span class="price">A cotizar</span></div>
<div class="service-row"><b>Reel / Video Corto</b>Hasta 30 segundos, con edición <span class="price">Desde $120.000</span></div>
<div class="service-row"><b>Pack de 3 Reels</b>Contenido consistente de alto impacto <span class="price">Desde $300.000</span></div>
</div>
</div>
</div>
</div>
<a href="#cifras" class="home-btn" aria-label="Ir a cifras">✦</a>
</section>
<!-- ===== PROCESO ===== -->
<section class="proceso" id="proceso">
<div class="eyebrow">Cómo trabajamos</div>
<h2>Tu proyecto, paso a paso, y sin <span class="grad-word">sorpresas.</span></h2>
<p class="lede">Un proceso claro y colaborativo — sabés exactamente qué pasa en cada etapa, sin plantillas genéricas.</p>
<div class="arcade-grid">
<div class="column">
<div class="arch"><span class="num">01</span></div>
<div class="cat">Contacto</div>
<div class="step-title">Reunión de brief</div>
<div class="step-desc">Nos contás tu marca y objetivos.</div>
</div>
<div class="column">
<div class="arch"><span class="num">02</span></div>
<div class="cat">Propuesta</div>
<div class="step-title">Plan de trabajo</div>
<div class="step-desc">Entregables, inversión y tiempos.</div>
</div>
<div class="column">
<div class="arch"><span class="num">03</span></div>
<div class="cat">Diseño</div>
<div class="step-title">Dirección de arte</div>
<div class="step-desc">Concepto visual a tu aprobación.</div>
</div>
<div class="column">
<div class="arch"><span class="num">04</span></div>
<div class="cat">Producción</div>
<div class="step-title">Desarrollo</div>
<div class="step-desc">Construimos con seguimiento.</div>
</div>
<div class="column">
<div class="arch"><span class="num">05</span></div>
<div class="cat">Revisión</div>
<div class="step-title">Ajustes finales</div>
<div class="step-desc">Tu feedback moldea el resultado.</div>
</div>
<div class="column">
<div class="arch final"><svg viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="2.4"><path d="M5 12l5 5L19 7"/></svg></div>
<div class="cat">Lanzamiento</div>
<div class="step-title">Entrega</div>
<div class="step-desc">Accesos y capacitación completa.</div>
</div>
</div>
<a href="#cifras" class="home-btn" aria-label="Ir a cifras">✦</a>
</section>
<!-- ===== CIFRAS ===== -->
<section class="cifras" id="cifras">
<div class="eyebrow">Inversión & tiempos</div>
<h2>Transparencia antes de la <span class="grad-word">primera reunión.</span></h2>
<p class="lede">Los números, dichos de entrada — no como parte de una negociación, sino como punto de partida.</p>
<div class="cifras-grid">
<div class="cifra-card">
<div class="cifra-icon"></div>
<div class="cifra-label">Inversión</div>
<div class="cifra-value">Desde $250.000/mes</div>
<div class="cifra-desc">Planes desde <b>$250.000</b> (Esencial) hasta <b>$1.000.000</b> (Elite). El armado inicial —branding, sitio web— se presupuesta por proyecto.</div>
</div>
<div class="cifra-card">
<div class="cifra-icon"></div>
<div class="cifra-label">Tiempos</div>
<div class="cifra-value">15–30 días</div>
<div class="cifra-desc">Presencia digital lista en <b>15-30 días</b>. Resultados de posicionamiento sostenido en <b>3-6 meses</b>. Sin promesas de magia inmediata.</div>
</div>
<div class="cifra-card">
<div class="cifra-icon"></div>
<div class="cifra-label">Contrato</div>
<div class="cifra-value">3 meses · luego mensual</div>
<div class="cifra-desc">Compromiso mínimo de <b>3 meses</b> para que la estrategia tome forma, luego renovación mensual. Cancelación <b>con 30 días de aviso</b>.</div>
</div>
</div>
<a href="#cotizador" class="home-btn" aria-label="Ir a cotizador">✦</a>
</section>
<!-- ===== COTIZADOR ===== -->
<section class="cotizador" id="cotizador">
<div class="eyebrow">Cotizador</div>
<h2>¿Cuál es tu <span class="grad-word">plan ideal</span><span class="signopreg">?</span></h2>
<p class="lede">3 preguntas rápidas y te decimos qué plan se ajusta mejor a tu marca.</p>
<div class="card">
<div class="progress">
<div class="seg"><i id="p1"></i></div>
<div class="seg"><i id="p2"></i></div>
<div class="seg"><i id="p3"></i></div>
</div>
<div class="step active" data-step="0">
<div class="q-num">Pregunta 01</div>
<div class="q-title">¿Cómo describirías el momento de tu negocio?</div>
<div class="options" data-q="0">
<div class="opt" data-val="1"><div class="opt-title">Recién empezando</div><div class="opt-sub">Necesito presencia digital desde cero</div></div>
<div class="opt" data-val="2"><div class="opt-title">En crecimiento</div><div class="opt-sub">Ya tengo redes, quiero más alcance</div></div>
<div class="opt" data-val="3"><div class="opt-title">Establecido</div><div class="opt-sub">Busco consolidar y vender más</div></div>
<div class="opt" data-val="4"><div class="opt-title">Consolidado</div><div class="opt-sub">Quiero gestión integral de marca</div></div>
</div>
<div class="nav-row"><span></span><button class="btn btn-cotizador" onclick="next()" disabled id="next0">Siguiente →</button></div>
</div>
<div class="step" data-step="1">
<div class="q-num">Pregunta 02</div>
<div class="q-title">¿Qué es lo que más necesitás hoy?</div>
<div class="options" data-q="1">
<div class="opt" data-val="1"><div class="opt-title">Presencia en redes</div><div class="opt-sub">Contenido consistente y profesional</div></div>
<div class="opt" data-val="2"><div class="opt-title">Más leads y ventas</div><div class="opt-sub">Publicidad digital que convierta</div></div>
<div class="opt" data-val="3"><div class="opt-title">Producción de contenido</div><div class="opt-sub">Fotos, video y piezas de calidad</div></div>
<div class="opt" data-val="4"><div class="opt-title">Todo integrado</div><div class="opt-sub">Estrategia 360° gestionada por otros</div></div>
</div>
<div class="nav-row"><button class="btn btn-cotizador-back" onclick="prev()">← Atrás</button><button class="btn btn-cotizador" onclick="next()" disabled id="next1">Siguiente →</button></div>
</div>
<div class="step" data-step="2">
<div class="q-num">Pregunta 03</div>
<div class="q-title">¿Cuál es tu presupuesto mensual aproximado?</div>
<div class="options" data-q="2">
<div class="opt" data-val="1"><div class="opt-title">Hasta $300.000</div><div class="opt-sub">ARS / mes</div></div>
<div class="opt" data-val="2"><div class="opt-title">$300.000 – $450.000</div><div class="opt-sub">ARS / mes</div></div>
<div class="opt" data-val="3"><div class="opt-title">$450.000 – $700.000</div><div class="opt-sub">ARS / mes</div></div>
<div class="opt" data-val="4"><div class="opt-title">Más de $700.000</div><div class="opt-sub">ARS / mes</div></div>
</div>
<div class="nav-row"><button class="btn btn-cotizador-back" onclick="prev()">← Atrás</button><button class="btn btn-cotizador" onclick="showResult()" disabled id="next2">Ver resultado →</button></div>
</div>
<div class="result" id="result">
<div class="result-badge">Tu plan recomendado</div>
<div class="result-plan" id="rp-name">—</div>
<div class="result-price" id="rp-price">—</div>
<p class="result-desc" id="rp-desc">—</p>
<div class="result-actions">
<a class="btn btn-solid" href="#contacto">Quiero este plan</a>
<button class="btn btn-cotizador-restart" onclick="restart()">Volver a empezar</button>
</div>
</div>
</div>
<a href="#nosotros" class="home-btn" aria-label="Ir a nosotros">✦</a>
</section>
<!-- ===== NOSOTROS ===== -->
<section class="quienes" id="nosotros">
<div class="eyebrow">Nosotros</div>
<h2>Un equipo chico, con <span class="grad-word">mucha energía.</span></h2>
<p class="lede">Creemos que las marcas se construyen con escucha, estrategia y mucho amor por lo que hacemos.</p>
<div class="team-grid team-grid-simple">
<div class="team-card-simple">
<div class="team-name">Patricia Pugliese</div>
<div class="team-role">Estrategia & Creatividad</div>
<div class="team-detail">Investigación, branding, identidad visual y todo lo que da forma a una marca con personalidad.</div>
</div>
<div class="team-card-simple">
<div class="team-name">Francisco Anfuso</div>
<div class="team-role">Desarrollo & Tecnología</div>
<div class="team-detail">Diseño web, desarrollo, automatizaciones y soluciones técnicas que funcionan sin complicaciones.</div>
</div>
</div>
<div class="team-footer">
<p class="team-footer-text">Y como todo equipo en crecimiento, sumamos colaboradores según el proyecto — diseñadores, redactores y especialistas que se suman cuando hace falta.</p>
</div>
<a href="#faq" class="home-btn" aria-label="Ir a FAQ">✦</a>
</section>
<!-- ===== FAQ ===== -->
<section class="faq" id="faq">
<div class="eyebrow">Preguntas Frecuentes</div>
<h2>Todo lo que necesitás saber <span class="grad-word">antes de empezar.</span></h2>
<p class="lede">Respondemos las dudas más comunes para que llegues con confianza al primer encuentro.</p>
<div class="faq-grid">
<div class="faq-item open">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Cómo funciona el proceso de trabajo?</span>
<span class="faq-toggle">−</span>
</div>
<div class="faq-body"><p>Empezamos con una llamada de descubrimiento para conocer tu negocio, objetivos y desafíos. Luego armamos una propuesta a medida, la ejecutamos con entregas parciales y ajustamos hasta que estés 100% conforme. Todo con comunicación clara y tiempos acordados.</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Qué incluye exactamente cada plan?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>Cada plan tiene un desglose detallado en la sección de Planes. Pero en resumen: incluyen gestión de redes, diseño de marca, publicidad digital, automatizaciones y reportes. Si necesitás algo específico que no esté listado, lo sumamos a medida.</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Puedo contratar servicios sueltos sin un plan mensual?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>¡Sí! En la sección "Servicios a la carta" tenés disponibles branding, diseño web, campañas puntuales y producción de contenido. Podés contratarlos de forma independiente, sin compromiso mensual.</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Qué resultados puedo esperar?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>Depende del punto de partida, pero nuestros clientes suelen ver aumento en alcance, engagement y generación de leads en los primeros 3 meses. Compartimos reportes mensuales para que siempre sepas cómo evoluciona tu marca.</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Trabajan con negocios de cualquier rubro?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>Trabajamos con marcas de moda, gastronomía, tecnología, salud, educación y más. Lo importante es que tengas claros tus valores y quieras construir una marca con identidad propia — el rubro es secundario.</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Cómo facturan? ¿Hay algún costo de inicio?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>Facturamos con factura electrónica tipo C o E. No hay costo de inicio: los planes se abonan mes a mes, con un 50% de anticipo en proyectos puntuales (como diseño web o branding completo).</p></div>
</div>
<div class="faq-item">
<div class="faq-head" onclick="toggleFaq(this)">
<span class="faq-q">¿Puedo cambiar de plan o cancelar cuando quiera?</span>
<span class="faq-toggle">+</span>
</div>
<div class="faq-body"><p>Por supuesto. Podés subir o bajar de plan con 15 días de aviso. Si querés cancelar, también con 15 días de anticipación. Sin letra chica ni permanencias forzadas.</p></div>
</div>
</div>
<a href="#contacto" class="home-btn" aria-label="Ir a contacto">✦</a>
</section>
<!-- ===== CONTACTO ===== -->
<section class="contacto" id="contacto">
<div class="eyebrow">Contacto</div>
<h2>¿Empezamos a <span class="grad-word">construir</span> tu marca?</h2>
<p class="lede">Completá el formulario o escribinos directo — te respondemos en menos de 24 horas.</p>
<div class="contact-grid">
<div class="contact-form-wrap">
<form class="contact-form" id="contactForm" onsubmit="return handleSubmit(event)">
<div class="form-row">
<div class="form-group">
<label for="nombre">Nombre completo <span class="req">*</span></label>
<input type="text" id="nombre" name="nombre" required placeholder="Tu nombre">
</div>
<div class="form-group">
<label for="email">Email <span class="req">*</span></label>
<input type="email" id="email" name="email" required placeholder="tu@email.com">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="empresa">Empresa / Emprendimiento</label>
<input type="text" id="empresa" name="empresa" placeholder="Nombre de tu marca">
</div>
<div class="form-group">
<label for="telefono">Teléfono</label>
<input type="tel" id="telefono" name="telefono" placeholder="+54 9 388 000-0000">
</div>
</div>
<div class="form-group full">
<label for="servicio">¿Qué servicio te interesa? <span class="req">*</span></label>
<div class="filter-tags" id="servicioFilter">
<button type="button" class="filter-tag active" data-value="todos" onclick="selectFilter(event)">Todos</button>
<button type="button" class="filter-tag" data-value="branding" onclick="selectFilter(event)">Branding</button>
<button type="button" class="filter-tag" data-value="web" onclick="selectFilter(event)">Diseño Web</button>
<button type="button" class="filter-tag" data-value="marketing" onclick="selectFilter(event)">Marketing Digital</button>
<button type="button" class="filter-tag" data-value="ia" onclick="selectFilter(event)">IA & Automatización</button>
<button type="button" class="filter-tag" data-value="otro" onclick="selectFilter(event)">Otro</button>
</div>
<input type="hidden" id="servicioSeleccionado" name="servicio" value="todos">
<input type="text" id="servicioOtro" name="servicio_otro" placeholder="Especificá cuál..." style="display:none; margin-top:10px; width:100%;">
</div>
<div class="form-group full">
<label for="mensaje">¿Qué necesitás? <span class="req">*</span></label>
<textarea id="mensaje" name="mensaje" required placeholder="Contanos sobre tu proyecto, tus objetivos y lo que estás buscando..." rows="5"></textarea>
</div>
<div class="form-row">
<div class="form-group" style="flex:1;">
<label class="checkbox-label">
<input type="checkbox" required>
<span>Acepto la <a href="#" style="color:var(--bordo);">política de privacidad</a></span>
</label>
</div>
<button type="submit" class="btn btn-solid" style="padding:14px 44px;">Enviar mensaje</button>
</div>
</form>
</div>
<div class="contact-direct">
<div class="contact-card">
<div class="contact-icon">📧</div>
<div class="contact-info">
<div class="contact-label">Email</div>
<a href="mailto:contacto@amoryroma.com.ar">contacto@amoryroma.com.ar</a>
</div>
</div>
<div class="contact-card">
<div class="contact-icon">💬</div>
<div class="contact-info">
<div class="contact-label">WhatsApp</div>
<a href="https://wa.me/5493886118304" target="_blank">+54 9 388 611-8304</a>
</div>
</div>
<div class="contact-card">
<div class="contact-icon">📱</div>
<div class="contact-info">
<div class="contact-label">Redes</div>
<div class="contact-socials">
<a href="https://www.instagram.com/amoryroma.creativo/" target="_blank">Instagram</a>
<a href="https://www.linkedin.com/in/patricia-pugliese/" target="_blank">LinkedIn</a>
<a href="https://web.facebook.com/amoryroma.creativo" target="_blank">Facebook</a>
</div>
</div>
</div>
<div class="contact-card">
<div class="contact-icon">📍</div>
<div class="contact-info">
<div class="contact-label">Argentina</div>
<span style="font-size:13px; color:var(--dusty);">Trabajamos remoto worldwide</span>
</div>
</div>
</div>
</div>
<a href="#inicio" class="home-btn" aria-label="Volver arriba">✦</a>
</section>
<!-- ===== FOOTER ===== -->
<footer class="footer">
<div class="footer-inner">
<div class="footer-brand">
<div class="footer-logo"><img src="logo_frio.png" alt="Amor & Roma" style="height:36px;width:auto;"></div>
<p class="footer-desc">Branding, diseño web y marketing digital para marcas con esencia.</p>
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Navegación</h4>
<a href="#servicios">Servicios</a>
<a href="#planes">Planes</a>
<a href="#nosotros">Nosotros</a>
<a href="#cifras">Cifras</a>
<a href="#faq">FAQ</a>
<a href="#contacto">Contacto</a>
</div>
<div class="footer-col">
<h4>Servicios</h4>
<a href="#servicios">Branding</a>
<a href="#servicios">Diseño Web</a>
<a href="#servicios">Marketing Digital</a>
<a href="#servicios">IA & Automatización</a>
</div>
<div class="footer-col">
<h4>Contacto</h4>
<a href="mailto:contacto@amoryroma.com.ar">contacto@amoryroma.com.ar</a>
<a href="https://wa.me/5493886118304" target="_blank">+54 9 388 611-8304</a>
<div class="footer-socials">
<a href="https://www.instagram.com/amoryroma.creativo/" target="_blank" aria-label="Instagram">IG</a>
<a href="https://www.linkedin.com/in/patricia-pugliese/" target="_blank" aria-label="LinkedIn">IN</a>
<a href="https://web.facebook.com/amoryroma.creativo" target="_blank" aria-label="Facebook">FB</a>
</div>
</div>
</div>
</div>
<div class="footer-bottom">
<span>&copy; 2026 Amor & Roma — Estudio Creativo. Todos los derechos reservados.</span>
<span class="footer-legal"><a href="#">Política de privacidad</a><a href="#">Términos y condiciones</a></span>
</div>
</footer>
<!-- ===== SCRIPTS ===== -->
<script src="scripts.js"></script>
<script src="fluid.js"></script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

+150
View File
@@ -0,0 +1,150 @@
// ===== MENU =====
function toggleMenu() {
document.getElementById('sheet').classList.toggle('open');
document.getElementById('overlay').classList.toggle('open');
}
// ===== TABS =====
function showTab(i, el) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
el.classList.add('active');
document.querySelector('.panel[data-panel="' + i + '"]').classList.add('active');
}
// ===== ACCORDION =====
function toggleAcc(head) {
const item = head.parentElement;
document.querySelectorAll('.acc-item').forEach(i => { if (i !== item) i.classList.remove('open'); });
item.classList.toggle('open');
}
// ===== FAQ =====
function toggleFaq(head) {
const item = head.parentElement;
const isOpen = item.classList.contains('open');
document.querySelectorAll('.faq-item').forEach(i => {
i.classList.remove('open');
const t = i.querySelector('.faq-toggle');
if (t) t.textContent = '+';
});
if (!isOpen) {
item.classList.add('open');
const toggle = item.querySelector('.faq-toggle');
if (toggle) toggle.textContent = '−';
}
}
// ===== COTIZADOR =====
const answers = [null, null, null];
let current = 0;
document.querySelectorAll('.opt').forEach(opt => {
opt.addEventListener('click', function() {
const group = this.parentElement;
const q = parseInt(group.dataset.q);
group.querySelectorAll('.opt').forEach(o => o.classList.remove('selected'));
this.classList.add('selected');
answers[q] = parseInt(this.dataset.val);
document.getElementById('next' + q).disabled = false;
const step = this.closest('.step');
const qNum = step.querySelector('.q-num');
if (qNum) qNum.classList.add('active');
});
});
function updateProgress() {
for (let i = 0; i < 3; i++) {
document.getElementById('p' + (i + 1)).style.width = i <= current ? '100%' : '0%';
}
}
function next() {
document.querySelector('.step[data-step="' + current + '"]').classList.remove('active');
current++;
document.querySelector('.step[data-step="' + current + '"]').classList.add('active');
updateProgress();
}
function prev() {
document.querySelector('.step[data-step="' + current + '"]').classList.remove('active');
current--;
document.querySelector('.step[data-step="' + current + '"]').classList.add('active');
updateProgress();
}
const plans = [
{ name: 'Esencial', price: '$250.000 ARS', desc: 'Ideal para arrancar con presencia digital profesional y consistente, sin sobre-invertir todavía.' },
{ name: 'Impulso', price: '$420.000 ARS', desc: 'Pensado para marcas en crecimiento que necesitan más alcance, leads y una primera campaña de publicidad.' },
{ name: 'Premium', price: '$650.000 ARS + pauta', desc: 'Nuestro plan más recomendado: gestión integral de contenido, publicidad y estrategia para consolidar y expandir.' },
{ name: 'Elite', price: '$1.000.000 ARS', desc: 'Máximo posicionamiento y gestión de marca completa, para negocios listos para escalar en serio.' }
];
function showResult() {
document.querySelector('.step[data-step="2"]').classList.remove('active');
const avg = Math.round((answers[0] + answers[1] + answers[2]) / 3);
const idx = Math.min(Math.max(avg - 1, 0), 3);
document.getElementById('rp-name').textContent = plans[idx].name;
document.getElementById('rp-price').innerHTML = plans[idx].price + ' <small>/ mes</small>';
document.getElementById('rp-desc').textContent = plans[idx].desc;
document.getElementById('result').classList.add('active');
}
function restart() {
answers[0] = answers[1] = answers[2] = null;
current = 0;
document.querySelectorAll('.opt').forEach(o => o.classList.remove('selected'));
document.querySelectorAll('[id^="next"]').forEach(b => b.disabled = true);
document.querySelectorAll('.q-num').forEach(q => q.classList.remove('active'));
document.getElementById('result').classList.remove('active');
document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
document.querySelector('.step[data-step="0"]').classList.add('active');
updateProgress();
}
// ===== FILTRO DE SERVICIOS EN CONTACTO =====
function selectFilter(event) {
const el = event.currentTarget;
const container = el.parentElement;
container.querySelectorAll('.filter-tag').forEach(t => t.classList.remove('active'));
el.classList.add('active');
const value = el.dataset.value;
document.getElementById('servicioSeleccionado').value = value;
const otroInput = document.getElementById('servicioOtro');
if (value === 'otro') {
otroInput.style.display = 'block';
otroInput.required = true;
} else {
otroInput.style.display = 'none';
otroInput.required = false;
otroInput.value = '';
}
}
// ===== FORMULARIO =====
function handleSubmit(e) {
e.preventDefault();
const form = document.getElementById('contactForm');
const data = new FormData(form);
const nombre = data.get('nombre');
const email = data.get('email');
const mensaje = data.get('mensaje');
if (!nombre || !email || !mensaje) {
alert('Por favor, completá todos los campos obligatorios.');
return false;
}
const btn = form.querySelector('button[type="submit"]');
const originalText = btn.textContent;
btn.textContent = 'Enviando...';
btn.disabled = true;
setTimeout(() => {
alert('¡Mensaje enviado con éxito! Te responderemos a la brevedad.');
form.reset();
btn.textContent = originalText;
btn.disabled = false;
document.querySelectorAll('.filter-tag').forEach(t => t.classList.remove('active'));
document.querySelector('.filter-tag[data-value="todos"]').classList.add('active');
document.getElementById('servicioOtro').style.display = 'none';
}, 1500);
return false;
}
+650
View File
@@ -0,0 +1,650 @@
/* ===== FUENTES ===== */
@import url('https://fonts.googleapis.com/css2?family=Literata:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400;1,500;1,600;1,700;1,800&family=Raleway:wght@300;400;500;600;700&family=Jost:wght@300;400;500;600;700&display=swap');
/* ===== VARIABLES ===== */
:root {
--teal: #003F52;
--ligth-teal: #006d8f;
--bordo: #BA124D;
--oscuro: #110916;
--dusty: #a79bab;
--claro: #EAF0EE;
--rosa: #DFBDB9;
--rose-gold-1: #DFBDB9;
--rose-gold-2: #BA8B7A;
--rose-gold-3: #C89A82;
--font-display: 'Literata', serif;
--font-ui: 'Raleway', sans-serif;
--font-body: 'Jost', sans-serif;
--font-name: 'Kaleagnetta', handwriting;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-body);
background: var(--oscuro);
color: var(--claro);
overflow-x: hidden;
font-weight: 300;
}
/* ===== LOGO ===== */
.logo-img { height: 32px; width: auto; display: block; }
/* ===== CANVAS FLUIDO ===== */
#fluidCanvas {
display: block;
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 0;
pointer-events: auto;
background: var(--oscuro);
}
/* Asegurar que el contenido esté por encima */
.hero, section {
position: relative;
z-index: 1;
}
/* ===== HEADER ===== */
header {
position: fixed; top: 0; left: 0; right: 0; z-index: 50;
display: flex; align-items: center; justify-content: space-between;
padding: 26px 48px;
background: linear-gradient(180deg, rgba(17,9,22,.9) 0%, rgba(17,9,22,0) 100%);
}
.menu-btn {
display: flex; align-items: center; gap: 10px;
background: transparent; border: 1px solid rgba(234,240,238,.2);
border-radius: 100px; padding: 10px 20px;
color: var(--claro); font-family: var(--font-ui); font-size: 12px;
letter-spacing: .14em; text-transform: uppercase; font-weight: 500;
cursor: pointer; transition: .25s;
}
.menu-btn:hover { border-color: var(--rosa); color: var(--rosa); }
.menu-btn .bars { display: flex; flex-direction: column; gap: 3px; }
.menu-btn .bars span { width: 14px; height: 1.4px; background: currentColor; display: block; }
/* ===== OVERLAY & SHEET ===== */
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,.6);
opacity: 0; pointer-events: none; transition: .35s; z-index: 90;
}
.overlay.open { opacity: 1; pointer-events: auto; }
.sheet {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
background: var(--claro); border-radius: 22px 22px 0 0;
padding: 10px 36px 30px;
transform: translateY(102%);
transition: transform .4s cubic-bezier(.2,.8,.2,1);
max-height: 55vh; overflow-y: auto;
color: var(--oscuro); box-shadow: 0 -8px 40px rgba(0,0,0,.3);
}
.sheet.open { transform: translateY(0); }
.sheet-handle { width: 44px; height: 4px; background: rgba(34,23,40,.2); border-radius: 100px; margin: 0 auto 16px; }
.sheet-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.sheet-top .logo { font-size: 0; }
.sheet-top .logo img { height: 28px; width: auto; }
.close-btn {
width: 32px; height: 32px; border-radius: 100px;
border: 1px solid rgba(34,23,40,.2);
background: transparent; color: var(--oscuro); font-size: 14px;
cursor: pointer; display: flex; align-items: center; justify-content: center;
transition: .2s;
}
.close-btn:hover { border-color: var(--bordo); color: var(--bordo); }
.sheet-body { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 30px; align-items: start; }
.sheet-col { display: flex; flex-direction: column; gap: 2px; }
.sheet-col-title {
font-family: var(--font-ui); font-size: 16px; font-weight: 500;
letter-spacing: .12em; text-transform: uppercase;
color: var(--bordo); margin-bottom: 8px; padding-right: 12px; text-align: right;
}
.sheet-link {
display: grid; grid-template-columns: 1fr 20px; align-items: center;
gap: 8px; padding: 6px 12px; border-radius: 6px;
text-decoration: none; color: var(--oscuro); transition: .2s;
background: transparent; text-align: right;
}
.sheet-link:hover { background: rgba(186,18,77,.06); }
.sheet-link-icon { font-size: 12px; color: var(--bordo); opacity: .4; text-align: center; grid-row: 1/3; grid-column: 2; }
.sheet-link-main {
font-family: var(--font-display); font-style: italic; font-weight: 600;
font-size: 16px; color: var(--oscuro); line-height: 1.2; grid-column: 1;
}
.sheet-link-desc {
font-family: var(--font-ui); font-size: 13px; color: var(--dusty);
font-weight: 500; opacity: .8; grid-column: 1; margin-top: -2px;
}
.sheet-link.highlight {
background: var(--bordo); border-radius: 6px; padding: 10px 14px; margin-top: 6px;
}
.sheet-link.highlight .sheet-link-icon { color: #fff; opacity: 1; }
.sheet-link.highlight .sheet-link-main { color: #fff; }
.sheet-link.highlight .sheet-link-desc { color: rgba(255,255,255,.7); }
.sheet-link.highlight:hover { background: #9c0f40; }
/* ===== HERO ===== */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
padding: 0 80px;
max-width: 1200px;
margin: 0 auto;
text-align: center;
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 2;
max-width: 900px;
margin: 0 auto;
pointer-events: auto;
}
/* ===== TIPOGRAFÍA ===== */
.eyebrow {
font-family: var(--font-ui); font-size: 12px; letter-spacing: .28em;
text-transform: uppercase; color: var(--claro); margin-bottom: 22px;
display: flex; align-items: center; gap: 12px; opacity: 0.6;
}
.eyebrow::before {
content: ''; width: 30px; height: 1px;
background: linear-gradient(90deg, var(--rose-gold-3), var(--rose-gold-1));
}
h1, h2 {
font-family: var(--font-display); font-weight: 300; font-style: italic;
font-size: clamp(40px, 6vw, 72px); line-height: 1.05;
margin-bottom: 16px;
}
.grad-word {
background: linear-gradient(100deg, var(--rose-gold-1) 0%, var(--bordo) 70%, var(--ligth-teal) 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
p, .lede {
font-family: var(--font-ui); font-size: 18px; line-height: 1.7;
color: var(--claro); max-width: 560px; margin: 0 auto 26px; opacity: 0.8;
font-weight: 300;
}
.lede { font-size: 18px; opacity: 0.7; max-width: 640px; }
/* ===== BOTONES ===== */
.btn {
font-family: var(--font-ui); font-size: 13px; font-weight: 500;
letter-spacing: .05em; padding: 16px 30px; border-radius: 2px;
cursor: pointer; transition: .25s; text-decoration: none;
display: inline-flex; align-items: center; gap: 8px;
}
.btn-solid {
background: var(--bordo); color: #fff; border: 1px solid var(--bordo);
}
.btn-solid:hover { background: #9c0f40; }
.btn-outline {
background: transparent; color: var(--claro);
border: 1px solid rgba(234,240,238,.2);
}
.btn-outline:hover { border-color: var(--rosa); color: var(--rosa); }
.hero-actions { display: flex; gap: 16px; justify-content: center; margin-top: 40px; }
.hero-actions .btn {
pointer-events: auto;
}
/* ===== HOME BUTTON ===== */
.home-btn {
position: absolute; right: 20px; bottom: 20px;
font-size: 20px; color: var(--rose-gold-1);
text-decoration: none; transition: .3s; z-index: 10;
pointer-events: auto;
}
.home-btn:hover { color: rgba(234,240,238,.4); }
/* ===== SCROLL HINT ===== */
.scroll-hint {
position: absolute; bottom: 36px; left: 50%; transform: translateX(-50%);
font-family: var(--font-ui); font-size: 10.5px; letter-spacing: .2em;
text-transform: uppercase; color: rgba(234,240,238,.3);
display: flex; flex-direction: column; align-items: center; gap: 8px;
pointer-events: auto;
}
.scroll-hint .line { width: 1px; height: 36px; background: rgba(234,240,238,.2); }
/* ===== SECCIONES GENERALES ===== */
section { padding: 80px 48px; max-width: 1200px; margin: 0 auto; position: relative; }
section .eyebrow { justify-content: center; }
section h2 { text-align: center; max-width: 800px; margin: 0 auto 12px; }
section .lede { text-align: center; max-width: 900px; margin-bottom: 38px; }
/* ===== SERVICIOS ===== */
.tabs {
display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
margin-bottom: 44px; border-bottom: 1px solid rgba(234,240,238,.06);
}
.tab {
font-family: var(--font-ui); font-size: 20px; font-weight: 400;
letter-spacing: .03em; color: rgba(234,240,238,.4);
background: none; border: none; cursor: pointer;
padding: 14px 22px; position: relative; transition: .2s;
}
.tab .num {
font-family: var(--font-display); font-style: italic; font-size: 13px;
color: var(--rosa); margin-right: 8px;
}
.tab.active { color: var(--claro); }
.tab.active::after {
content: ''; position: absolute; left: 0; right: 0; bottom: -1px;
height: 2px; background: linear-gradient(90deg, var(--rose-gold-3), var(--bordo));
}
.tab:hover { color: var(--claro); }
.panel { display: none; animation: fade .4s ease; }
.panel.active { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: start; }
@keyframes fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.panel-copy h3 {
font-family: var(--font-display); font-style: italic; font-weight: 500;
font-size: 25px; margin-bottom: 16px;
}
.panel-copy h3 em { color: var(--bordo); font-style: italic; }
.panel-copy p { font-size: 15px; max-width: 600px; margin-bottom: 26px; }
.panel-list { display: flex; flex-direction: column; border-top: 1px solid rgba(234,240,238,.06); }
.panel-list .item {
display: flex; align-items: center; gap: 14px;
padding: 14px 0; border-bottom: 1px solid rgba(234,240,238,.06);
font-family: var(--font-ui); font-size: 15px; color: var(--claro); opacity: 0.8;
}
.icon-min {
font-size: 14px; color: var(--rose-gold-1); opacity: 0.5;
flex-shrink: 0; width: 24px; text-align: center;
}
/* ===== PLANES ===== */
.plans-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-bottom: 40px;
}
.plan-card {
background: rgba(234,240,238,.03); border: 1px solid rgba(234,240,238,.06);
border-radius: 10px; padding: 30px 24px; display: flex; flex-direction: column;
text-align: center;
}
.plan-card.reco {
border: 1px solid rgba(223,189,185,.2);
background: rgba(234,240,238,.04);
box-shadow: 0 0 30px rgba(186,18,77,.05);
}
.badge {
align-self: center; font-family: var(--font-ui); font-size: 9px; font-weight: 700;
letter-spacing: .1em; text-transform: uppercase;
color: var(--bordo); border: 1px solid var(--bordo);
padding: 4px 14px; border-radius: 100px; margin-bottom: 14px;
}
.plan-name {
font-family: var(--font-display); font-style: italic; font-weight: 500;
font-size: 24px; margin-bottom: 4px;
}
.plan-stars { color: var(--rosa); font-size: 11px; letter-spacing: 2px; margin-bottom: 14px; }
.plan-obj {
font-family: var(--font-ui); font-size: 12px; line-height: 1.5;
color: rgba(234,240,238,.5); margin-bottom: 22px; min-height: 52px;
}
.plan-feats { list-style: none; flex: 1; margin-bottom: 24px; text-align: left; }
.plan-feats li {
font-family: var(--font-ui); font-size: 12px; line-height: 1.5;
padding: 8px 0; border-top: 1px solid rgba(234,240,238,.06);
color: rgba(234,240,238,.7);
}
.plan-feats li:first-child { border-top: none; }
.plan-feats li::before { content: '— '; color: var(--rosa); opacity: 0.5; }
.plan-cta {
font-family: var(--font-ui); font-size: 12px; font-weight: 500;
letter-spacing: .04em; text-align: center; padding: 12px;
border-radius: 2px; text-decoration: none;
border: 1px solid rgba(234,240,238,.15); color: var(--claro);
transition: .25s;
}
.plan-cta:hover { border-color: var(--rosa); color: var(--rosa); }
.plan-main-cta { display: block; max-width: 300px; margin: 0 auto; text-align: center; }
/* ===== ACCORDION ===== */
.acc { border-top: 1px solid rgba(234,240,238,.06); }
.acc-item { border-bottom: 1px solid rgba(234,240,238,.06); }
.acc-head {
display: flex; align-items: center; justify-content: space-between;
padding: 20px 4px; cursor: pointer;
}
.acc-head-left { display: flex; align-items: center; gap: 16px; }
.acc-num { font-family: var(--font-display); font-style: italic; color: var(--dusty); font-size: 14px; opacity: 0.5; }
.acc-name { font-family: var(--font-ui); font-size: 26px; font-weight: 400; }
.acc-toggle {
width: 26px; height: 26px; border-radius: 100px;
border: 1px solid rgba(234,240,238,.15);
display: flex; align-items: center; justify-content: center;
font-size: 14px; color: var(--rosa); transition: .25s;
}
.acc-item.open .acc-toggle { transform: rotate(45deg); border-color: var(--bordo); color: var(--bordo); }
.acc-body { max-height: 0; overflow: hidden; transition: .35s ease; }
.acc-item.open .acc-body { max-height: 600px; }
.acc-body-inner { padding: 0 4px 26px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.service-row {
font-family: var(--font-ui); font-size: 16px; color: rgba(234,240,238,.6);
padding: 12px 0; border-top: 1px solid rgba(234,240,238,.04);
}
.service-row b { display: block; color: var(--claro); font-size: 14px; margin-bottom: 3px; font-weight: 500; }
.service-row .price { color: var(--rosa); font-family: var(--font-display); font-style: italic; font-size: 13px; }
/* ===== PROCESO ===== */
.arcade-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
.column { position: relative; padding: 0 6px; text-align: center; }
.column:not(:last-child)::after {
content: ''; position: absolute; right: -7px; top: 14%; bottom: 44px;
width: 1px; background: rgba(234,240,238,.06);
}
.arch {
width: 100%; aspect-ratio: 1/1; max-height: 100px;
border: 2px solid rgba(234,240,238,.06); border-bottom: none;
border-radius: 100px 100px 0 0;
display: flex; align-items: flex-end; justify-content: center;
padding-bottom: 16px; margin-bottom: 16px;
transition: .3s;
}
.column:hover .arch { border-color: rgba(223,189,185,.3); }
.arch .num { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 20px; color: var(--rosa); opacity: 0.5; }
.arch.final { background: var(--bordo); border-color: var(--bordo); }
.arch.final svg { width: 22px; height: 22px; stroke: #fff; fill: none; stroke-width: 2; }
.cat {
font-family: var(--font-ui); font-size: 10px; letter-spacing: .14em;
text-transform: uppercase; color: var(--ligth-teal); font-weight: 500;
margin-bottom: 6px;
}
.column:nth-child(even) .cat { color: var(--bordo); }
.step-title {
font-family: var(--font-display); font-style: italic; font-weight: 500;
font-size: 15px; margin-bottom: 6px;
}
.step-desc {
font-family: var(--font-ui); font-size: 12px; line-height: 1.5;
color: var(--dusty);
}
/* ===== CIFRAS ===== */
.cifras-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.cifra-card {
background: rgba(234,240,238,.02); border: 1px solid rgba(234,240,238,.06);
border-radius: 10px; padding: 34px 30px; text-align: center;
}
.cifra-icon {
width: 44px; height: 56px; margin: 0 auto 20px;
border: 2px solid rgba(234,240,238,.06); border-bottom: none;
border-radius: 22px 22px 0 0;
}
.cifra-label {
font-family: var(--font-ui); font-size: 16px; letter-spacing: .18em;
text-transform: uppercase; color: var(--ligth-teal); font-weight: 500; margin-bottom: 12px;
}
.cifra-value {
font-family: var(--font-display); font-style: italic; font-weight: 500;
font-size: 24px; color: var(--claro); margin-bottom: 14px;
}
.cifra-desc {
font-family: var(--font-body); font-size: 16px; line-height: 1.6;
color: var(--dusty);
}
.cifra-desc b { color: var(--claro); font-weight: 400; }
/* ===== COTIZADOR ===== */
.card {
background: rgba(234,240,238,.03); border: 1px solid rgba(234,240,238,.06);
border-radius: 14px; padding: 48px 52px; max-width: 800px; margin: 0 auto;
}
.progress { display: flex; gap: 6px; margin-bottom: 36px; }
.progress .seg { height: 3px; flex: 1; background: rgba(234,240,238,.1); border-radius: 100px; overflow: hidden; }
.progress .seg i { display: block; height: 100%; width: 0%; background: linear-gradient(90deg, var(--rose-gold-3), var(--bordo)); transition: .4s; }
.step { display: none; }
.step.active { display: block; animation: fade .35s ease; }
.q-num { font-family: var(--font-display); font-style: italic; color: var(--rosa); font-size: 14px; margin-bottom: 10px; }
.q-title { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 22px; margin-bottom: 28px; }
.options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 32px; }
.opt {
border: 1px solid rgba(234,240,238,.1); border-radius: 8px; padding: 18px 20px;
cursor: pointer; transition: .2s; font-family: var(--font-ui);
}
.opt:hover { border-color: rgba(223,189,185,.3); }
.opt.selected { border-color: var(--bordo); background: rgba(186,18,77,.05); }
.opt-title { font-size: 14px; font-weight: 500; color: var(--claro); margin-bottom: 3px; }
.opt-sub { font-size: 12px; color: var(--dusty); }
.nav-row { display: flex; justify-content: space-between; align-items: center; }
.btn-cotizador {
font-family: var(--font-ui); font-size: 13px; font-weight: 500;
padding: 12px 28px; border-radius: 4px; cursor: pointer; transition: .3s;
border: 1px solid var(--bordo); background: transparent; color: var(--bordo);
}
.btn-cotizador:hover:not(:disabled) { background: var(--bordo); color: #fff; }
.btn-cotizador:disabled { opacity: .2; cursor: not-allowed; border-color: #444; color: #666; }
.btn-cotizador-back {
font-family: var(--font-ui); font-size: 13px; font-weight: 500;
padding: 12px 28px; border-radius: 4px; cursor: pointer; transition: .3s;
border: 1px solid transparent; background: transparent; color: var(--dusty);
}
.btn-cotizador-back:hover { color: var(--claro); border-color: rgba(234,240,238,.1); }
.btn-cotizador-restart {
font-family: var(--font-ui); font-size: 13px; font-weight: 500;
padding: 12px 28px; border-radius: 4px; cursor: pointer; transition: .3s;
border: 1px solid var(--bordo); background: transparent; color: var(--bordo);
}
.btn-cotizador-restart:hover { background: var(--bordo); color: #fff; }
.result { display: none; text-align: center; padding-top: 10px; }
.result.active { display: block; animation: fade .4s ease; }
.result-badge {
font-family: var(--font-ui); font-size: 11px; letter-spacing: .14em;
text-transform: uppercase; color: var(--bordo); margin-bottom: 10px;
}
.result-plan { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 32px; margin-bottom: 6px; }
.result-price { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 22px; color: var(--bordo); margin-bottom: 18px; }
.result-desc { font-family: var(--font-body); font-size: 14px; color: var(--dusty); max-width: 400px; margin: 0 auto 30px; line-height: 1.6; }
.result-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
/* ===== NOSOTROS ===== */
.team-grid-simple { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; max-width: 800px; margin: 0 auto; }
.team-card-simple {
background: rgba(234,240,238,.02); border: 1px solid rgba(234,240,238,.06);
border-radius: 12px; padding: 40px 32px; text-align: center;
}
.team-name {
font-family: var(--font-name); font-weight: 500;
font-size: 30px; margin-bottom: 8px;
}
.team-role {
font-family: var(--font-ui); font-size: 13px; font-weight: 400;
letter-spacing: .06em; text-transform: uppercase; color: var(--rosa);
margin-bottom: 12px; opacity: 0.6;
}
.team-detail {
font-family: var(--font-ui); font-size: 13px; line-height: 1.6;
color: var(--claro);
}
.team-footer {
margin-top: 10px; padding-top: 32px;
border-top: 1px solid rgba(234,240,238,.04);
text-align: center;
}
.team-footer-text {
font-family: var(--font-ui); font-size: 16px; color: var(--dusty);
max-width: 700px; margin: 0 auto; line-height: 1.6;
}
/* ===== FAQ ===== */
.faq-grid { border-top: 1px solid rgba(234,240,238,.06); max-width: 900px; margin: 0 auto; }
.faq-item { border-bottom: 1px solid rgba(234,240,238,.06); }
.faq-head {
display: flex; justify-content: space-between; align-items: center;
padding: 20px 4px; cursor: pointer; transition: .2s;
}
.faq-head:hover .faq-q { color: var(--rosa); }
.faq-q {
font-family: var(--font-ui); font-weight: 400;
font-size: 20px; transition: .2s;
}
.faq-toggle {
font-family: var(--font-body); font-size: 18px; font-weight: 300;
color: var(--rose-gold-1); width: 28px; height: 28px;
display: flex; align-items:center; justify-content: center;
border-radius: 100px; border: 1px solid rgba(234,240,238,.1);
transition: .25s; flex-shrink: 0;
}
.faq-item.open .faq-toggle { border-color: var(--bordo); color: var(--bordo); }
.faq-body { max-height: 0; overflow: hidden; transition: .4s ease; }
.faq-item.open .faq-body { max-height: 100px; }
.faq-body p {
font-family: var(--font-body); font-size: 15px; line-height: 1.7;
color: var(--claro); padding: 0 4px 14px; max-width: 800px;
}
/* ===== CONTACTO ===== */
.contact-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 48px; align-items: start; }
.contact-form-wrap {
background: rgba(234,240,238,.02); border: 1px solid rgba(234,240,238,.06);
border-radius: 12px; padding: 40px 36px;
}
.contact-form { display: flex; flex-direction: column; gap: 18px; }
.form-row { display: flex; gap: 18px; }
.form-group { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.form-group.full { flex: 0 0 100%; }
.form-group label {
font-family: var(--font-ui); font-size: 11px; font-weight: 500;
letter-spacing: .04em; text-transform: uppercase; color: rgba(234,240,238,.5);
}
.form-group .req { color: var(--bordo); }
.form-group input, .form-group textarea {
font-family: var(--font-body); font-size: 14px; font-weight: 300;
padding: 12px 14px; border-radius: 6px;
border: 1px solid rgba(234,240,238,.08);
background: rgba(234,240,238,.03); color: var(--claro);
transition: .25s; width: 100%;
}
.form-group input:focus, .form-group textarea:focus {
outline: none; border-color: var(--bordo);
background: rgba(234,240,238,.06);
}
.filter-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.filter-tag {
font-family: var(--font-ui); font-size: 11px; font-weight: 500;
padding: 6px 16px; border-radius: 100px;
border: 1px solid rgba(234,240,238,.1); background: transparent;
color: rgba(234,240,238,.4); cursor: pointer; transition: .2s;
}
.filter-tag:hover { border-color: var(--rosa); color: var(--claro); }
.filter-tag.active { background: var(--bordo); border-color: var(--bordo); color: #fff; }
.checkbox-label {
display: flex; align-items: center; gap: 10px;
font-family: var(--font-body); font-size: 13px; color: rgba(234,240,238,.6);
cursor: pointer;
}
.checkbox-label input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--bordo); }
.contact-direct { display: flex; flex-direction: column; gap: 16px; }
.contact-card {
display: flex; align-items: flex-start; gap: 16px;
padding: 18px 20px; background: rgba(234,240,238,.02);
border: 1px solid rgba(234,240,238,.04); border-radius: 10px;
}
.contact-icon { font-size: 20px; line-height: 1.2; flex-shrink: 0; margin-top: 2px; }
.contact-label {
font-family: var(--font-ui); font-size: 13px; font-weight: 500;
letter-spacing: .08em; text-transform: uppercase; color: var(--claro); opacity: 0.5;
}
.contact-info a { font-family: var(--font-body); font-size: 14px; color: var(--claro); text-decoration: none; }
.contact-info a:hover { color: var(--rosa); }
.contact-info span { font-family: var(--font-body); font-size: 13px; color: var(--claro); }
.contact-socials { display: flex; gap: 12px; }
.contact-socials a {
font-family: var(--font-ui); font-size: 13px; font-weight: 400;
color: var(--claro); text-decoration: none; opacity: 1;
}
.contact-socials a:hover { opacity: 1; }
/* ===== FOOTER ===== */
.footer {
background: rgba(0,0,0,.2); border-top: 1px solid rgba(234,240,238,.04);
padding: 60px 48px 20px;
}
.footer-inner {
max-width: 1200px; margin: 0 auto; display: grid;
grid-template-columns: 1fr 2fr; gap: 60px; padding-bottom: 40px;
border-bottom: 1px solid rgba(234,240,238,.04);
}
.footer-brand { display: flex; flex-direction: column; gap: 12px; }
.footer-desc { font-family: var(--font-display); font-size: 16px; line-height: 1.6; color: var(--dusty); max-width: 320px; }
.footer-links { display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px; }
.footer-col h4 {
font-family: var(--font-ui); font-size: 10px; font-weight: 700;
letter-spacing: .1em; text-transform: uppercase; color: var(--rosa); opacity: 0.4;
margin-bottom: 12px;
}
.footer-col a {
display: block; font-family: var(--font-body); font-size: 13px;
color: var(--dusty); text-decoration: none; padding: 4px 0;
transition: .2s;
}
.footer-col a:hover { color: var(--claro); }
.footer-socials { display: flex; gap: 12px; margin-top: 8px; }
.footer-socials a {
font-family: var(--font-ui); font-size: 11px; font-weight: 500;
color: var(--claro); text-decoration: none; padding: 4px 12px;
border: 1px solid rgba(234,240,238,.08); border-radius: 100px;
transition: .2s;
}
.footer-socials a:hover { border-color: var(--rosa); color: var(--rosa); }
.footer-bottom {
max-width: 1200px; margin: 0 auto; padding-top: 20px;
display: flex; justify-content: space-between; align-items: center;
flex-wrap: wrap; gap: 12px;
}
.footer-bottom span { font-family: var(--font-body); font-size: 12px; color: rgba(234,240,238,.2); }
.footer-legal { display: flex; gap: 20px; }
.footer-legal a { font-family: var(--font-body); font-size: 12px; color: rgba(234,240,238,.2); text-decoration: none; }
.footer-legal a:hover { color: rgba(234,240,238,.5); }
/* ===== RESPONSIVE ===== */
@media (max-width: 980px) {
.plans-grid { grid-template-columns: 1fr 1fr; }
.arcade-grid { grid-template-columns: repeat(3, 1fr); row-gap: 40px; }
.column:nth-child(3)::after, .column:nth-child(6)::after { display: none; }
.cifras-grid { grid-template-columns: 1fr; }
}
@media (max-width: 820px) {
.sheet-body { grid-template-columns: 1fr 1fr; }
.sheet-col:last-child { grid-column: 1 / -1; }
.contact-grid { grid-template-columns: 1fr; }
.footer-inner { grid-template-columns: 1fr; gap: 40px; }
.team-grid-simple { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
header { padding: 20px 22px; }
.hero { padding: 0 30px; }
section { padding: 60px 22px; }
.panel.active { grid-template-columns: 1fr; gap: 30px; }
.plans-grid { grid-template-columns: 1fr; }
.arcade-grid { grid-template-columns: 1fr 1fr; row-gap: 30px; }
.column:nth-child(2n)::after { display: none; }
.options { grid-template-columns: 1fr; }
.card { padding: 32px 24px; }
.acc-body-inner { grid-template-columns: 1fr; }
.footer-links { grid-template-columns: 1fr 1fr; gap: 24px; }
.footer { padding: 40px 22px 16px; }
.footer-bottom { flex-direction: column; text-align: center; }
}
@media (max-width: 520px) {
.sheet { max-height: 70vh; padding: 10px 16px 20px; border-radius: 16px 16px 0 0; }
.sheet-body { grid-template-columns: 1fr; gap: 16px; }
.sheet-col:last-child { grid-column: auto; }
.home-btn { right: 12px; bottom: 12px; font-size: 10px; }
}