diff --git a/scripts/routeMeta.mjs b/scripts/routeMeta.mjs
index e6ecea0..0e135b9 100644
--- a/scripts/routeMeta.mjs
+++ b/scripts/routeMeta.mjs
@@ -13,6 +13,7 @@ import {
PRICE_PER_TB_MONTH,
PRICE_AMOUNT,
PRICE_PER_TB_SHORT_EUR,
+ PRICE_PER_TB_SHORT_EUR_ES,
} from "../src/lib/pricing.constants.mjs";
export const BASE_URL = "https://www.fil.one";
@@ -369,9 +370,9 @@ export const ROUTE_META = {
},
"/lp/es/barcelona": {
lang: "es",
- title: `Fil One para Barcelona: Almacenamiento Europeo, ${PRICE_PER_TB_SHORT_EUR}, Sin Egress`,
+ title: `Fil One para Barcelona: Almacenamiento Europeo, ${PRICE_PER_TB_SHORT_EUR_ES}, Sin Egress`,
description:
- `Almacenamiento de objetos compatible con S3 para equipos en Barcelona. Soberanía de datos en la UE, cero comisiones de egress, a ${PRICE_PER_TB_SHORT_EUR}. Intégralo en tu stack actual en minutos.`,
+ `Almacenamiento de objetos compatible con S3 para equipos en Barcelona. Soberanía de datos en la UE, cero comisiones de egress, a ${PRICE_PER_TB_SHORT_EUR_ES}. Intégralo en tu stack actual en minutos.`,
},
"/contact-sales": {
title: "Contact Sales · Fil One",
diff --git a/src/components/CtaBanner.tsx b/src/components/CtaBanner.tsx
index 07d8eb0..c72f75e 100644
--- a/src/components/CtaBanner.tsx
+++ b/src/components/CtaBanner.tsx
@@ -13,6 +13,8 @@ interface CtaBannerProps {
*/
secondaryCta?: { label: string; href: string; onClick?: () => void };
note?: ReactNode;
+ /** Cap on the heading width (px). Longer translations need more room. */
+ headingMaxWidth?: number;
}
/**
@@ -20,7 +22,7 @@ interface CtaBannerProps {
* and a breathing glow, a headline, and a glowing primary button. Tuned for
* the closing section of a page (sits on white, above the footer).
*/
-const CtaBanner = ({ heading, subhead, cta, secondaryCta, note }: CtaBannerProps) => {
+const CtaBanner = ({ heading, subhead, cta, secondaryCta, note, headingMaxWidth = 480 }: CtaBannerProps) => {
const { ref, inView } = useInView({ threshold: 0.05 });
return (
@@ -53,7 +55,7 @@ const CtaBanner = ({ heading, subhead, cta, secondaryCta, note }: CtaBannerProps
{heading}
diff --git a/src/components/IntegrationsSection.tsx b/src/components/IntegrationsSection.tsx
index 1bb4e9b..2dccd4f 100644
--- a/src/components/IntegrationsSection.tsx
+++ b/src/components/IntegrationsSection.tsx
@@ -1,3 +1,4 @@
+import type { ReactNode } from "react";
import { SectionLabel, SectionHeading, SectionSub } from "@/components/LandingPrimitives";
import { useInView } from "@/hooks/useInView";
@@ -10,13 +11,34 @@ const INTEGRATIONS = [
interface IntegrationsSectionProps {
/** white (default) or the standard grey section treatment (zinc-50 + zinc-100 borders) */
tone?: "white" | "grey";
+ /** Section eyebrow; defaults to English. */
+ label?: ReactNode;
+ /** Section heading; defaults to English. */
+ heading?: ReactNode;
+ /** Supporting line under the heading; defaults to English. */
+ description?: ReactNode;
+ /** Docs link label; defaults to English. */
+ ctaLabel?: ReactNode;
}
/**
* Auto-scrolling marquee of supported integrations — S3-compatibility
* reassurance shared by the /lp/price and Barcelona landing pages.
+ *
+ * Copy is overridable so the Spanish pages can share the section rather than
+ * inlining a translated copy of it; the defaults are the English strings.
*/
-const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
+const IntegrationsSection = ({
+ tone = "white",
+ label = "Integrations",
+ heading = (
+ <>
+ Works with your
existing stack
+ >
+ ),
+ description = "S3 API compatible. If it talks to AWS, it talks to us.",
+ ctaLabel = "View documentation →",
+}: IntegrationsSectionProps) => {
const { ref, inView } = useInView({ threshold: 0.05 });
return (
@@ -31,9 +53,9 @@ const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
className={`flex flex-col gap-10 items-center text-center w-full max-w-container mx-auto reveal${inView ? " in-view" : ""}`}
>
- Integrations
- Works with your existing stack
- S3 API compatible. If it talks to AWS, it talks to us.
+ {label}
+ {heading}
+ {description}
@@ -54,7 +76,7 @@ const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
- View documentation →
+ {ctaLabel}
diff --git a/src/components/PriceComparisonTable.tsx b/src/components/PriceComparisonTable.tsx
index 04536da..ec01ff0 100644
--- a/src/components/PriceComparisonTable.tsx
+++ b/src/components/PriceComparisonTable.tsx
@@ -39,9 +39,25 @@ interface PriceComparisonTableProps {
centerFootnote?: boolean;
}
+/**
+ * Parse a formatted currency string to a number, tolerating both the English
+ * ("€1,234.56") and Spanish ("1.234,56 €") conventions. Whichever of "." or ","
+ * comes last is the decimal separator when 1 to 2 digits follow it; otherwise
+ * every separator is thousands grouping. Returns NaN for non-numeric values,
+ * which falls through to the neutral tone.
+ */
+const parseAmount = (value: string) => {
+ const digits = value.replace(/[^0-9.,-]/g, "");
+ const lastSep = Math.max(digits.lastIndexOf("."), digits.lastIndexOf(","));
+ if (lastSep === -1) return parseFloat(digits);
+ const trailing = digits.length - lastSep - 1;
+ if (trailing < 1 || trailing > 2) return parseFloat(digits.replace(/[.,]/g, ""));
+ return parseFloat(`${digits.slice(0, lastSep).replace(/[.,]/g, "")}.${digits.slice(lastSep + 1)}`);
+};
+
/** Zero reads as success, a large charge as danger, anything else neutral. */
const valueTone = (value: string) => {
- const n = parseFloat(value.replace(/[^0-9.-]/g, ""));
+ const n = parseAmount(value);
if (n === 0) return "text-success-700";
if (n > 50) return "text-danger-600";
return "text-zinc-600";
diff --git a/src/lib/pricing.constants.d.mts b/src/lib/pricing.constants.d.mts
index e09f357..298a308 100644
--- a/src/lib/pricing.constants.d.mts
+++ b/src/lib/pricing.constants.d.mts
@@ -11,3 +11,9 @@ export const PRICE_PER_TB_SHORT_EUR: string;
export const PRICE_PER_TB_MONTH_EUR: string;
export const EUR_USD_RATE: number;
export const EUR_USD_RATE_SOURCE: string;
+export function eurEs(amount: number, decimals?: number): string;
+export const PRICE_DISPLAY_EUR_ES: string;
+export const PRICE_PER_TB_SHORT_EUR_ES: string;
+export const PRICE_PER_TB_MONTH_EUR_ES: string;
+export const EUR_USD_RATE_ES: string;
+export const EUR_USD_RATE_SOURCE_ES: string;
diff --git a/src/lib/pricing.constants.mjs b/src/lib/pricing.constants.mjs
index fa42bca..735a963 100644
--- a/src/lib/pricing.constants.mjs
+++ b/src/lib/pricing.constants.mjs
@@ -55,3 +55,29 @@ export const EUR_USD_RATE = 1.17;
/** Where and when EUR_USD_RATE was taken, for the comparison-table footnote. */
export const EUR_USD_RATE_SOURCE = "ECB rate, May 2026";
+
+/* ── Spanish-locale EUR formatting ──────────────────────────────────────────
+ * Spanish writes a decimal comma with the symbol after the number and a
+ * non-breaking space ("4,99 €"), not "€4.99". The Spanish pages format every
+ * figure this way, so the helper is shared rather than hand-written per page.
+ * ─────────────────────────────────────────────────────────────────────────── */
+
+/** Format a EUR amount the Spanish way, e.g. 49.9 -> "49,90 €". Pass
+ * `decimals: 0` for whole-euro figures like "197 €". */
+export const eurEs = (amount, decimals = 2) =>
+ `${amount.toFixed(decimals).replace(".", ",")} €`;
+
+/** The bare EUR price, Spanish format, e.g. "4,99 €". */
+export const PRICE_DISPLAY_EUR_ES = eurEs(PRICE_PER_TB_EUR);
+
+/** The short per-TB EUR rate, Spanish format, e.g. "4,99 €/TB". */
+export const PRICE_PER_TB_SHORT_EUR_ES = `${PRICE_DISPLAY_EUR_ES}/TB`;
+
+/** The full per-TB EUR rate for inline Spanish copy, e.g. "4,99 €/TB al mes". */
+export const PRICE_PER_TB_MONTH_EUR_ES = `${PRICE_PER_TB_SHORT_EUR_ES} al mes`;
+
+/** EUR_USD_RATE in Spanish format with the symbol after the number, "1,17 $". */
+export const EUR_USD_RATE_ES = `${EUR_USD_RATE.toFixed(2).replace(".", ",")} $`;
+
+/** Spanish rendering of EUR_USD_RATE_SOURCE, for the ES comparison footnote. */
+export const EUR_USD_RATE_SOURCE_ES = "BCE, mayo de 2026";
diff --git a/src/lib/pricing.ts b/src/lib/pricing.ts
index 765cf44..69b9e25 100644
--- a/src/lib/pricing.ts
+++ b/src/lib/pricing.ts
@@ -33,6 +33,18 @@ export {
EUR_USD_RATE,
/** Where and when EUR_USD_RATE was taken. */
EUR_USD_RATE_SOURCE,
+ /** Format a EUR amount the Spanish way, e.g. 49.9 -> "49,90 €". */
+ eurEs,
+ /** The bare EUR price, Spanish format, e.g. "4,99 €". */
+ PRICE_DISPLAY_EUR_ES,
+ /** The short per-TB EUR rate, Spanish format, e.g. "4,99 €/TB". */
+ PRICE_PER_TB_SHORT_EUR_ES,
+ /** The full per-TB EUR rate for Spanish copy, e.g. "4,99 €/TB al mes". */
+ PRICE_PER_TB_MONTH_EUR_ES,
+ /** EUR_USD_RATE in Spanish format, e.g. "1,17 $". */
+ EUR_USD_RATE_ES,
+ /** Spanish rendering of EUR_USD_RATE_SOURCE. */
+ EUR_USD_RATE_SOURCE_ES,
} from "./pricing.constants.mjs";
import { PRICE_PER_TB } from "./pricing.constants.mjs";
diff --git a/src/pages/BarcelonaLandingPageES.tsx b/src/pages/BarcelonaLandingPageES.tsx
index bd112fe..c46675b 100644
--- a/src/pages/BarcelonaLandingPageES.tsx
+++ b/src/pages/BarcelonaLandingPageES.tsx
@@ -4,632 +4,268 @@ import Footer from "@/components/Footer";
import { useInView } from "@/hooks/useInView";
import { useSeo } from "@/hooks/useSeo";
import { useLang } from "@/hooks/useLang";
-import { GRID_SVG, SectionLabel, SectionHeading, SectionSub } from '@/components/LandingPrimitives';
+import { SectionLabel, SectionHeading, SectionSub } from "@/components/LandingPrimitives";
+import Hero from "@/components/Hero";
+import { Button } from "@/components/Button";
+import FeaturedInBar from "@/components/FeaturedInBar";
+import StatCard from "@/components/StatCard";
+import FeatureCard from "@/components/FeatureCard";
+import CtaBanner from "@/components/CtaBanner";
+import IntegrationsSection from "@/components/IntegrationsSection";
+import PriceComparisonTable, {
+ type PriceComparisonColumn,
+ type PriceComparisonRow,
+} from "@/components/PriceComparisonTable";
+import {
+ PRICE_PER_TB_EUR,
+ PRICE_PER_TB_SHORT_EUR_ES,
+ EUR_USD_RATE_ES,
+ EUR_USD_RATE_SOURCE_ES,
+ eurEs,
+} from "@/lib/pricing";
-// ─── Grid texture (matches Index.tsx hero) ─────────────────────────────────────
+const SIGNUP_URL = "https://app.fil.one/login?screen_hint=signup";
+const SUPPORT_HREF = "/lp/es/soporte";
+const CONTACT_SALES_HREF = "/lp/es/contacto";
-// ─── Pricing table helpers ────────────────────────────────────────────────────
-// Colours egress/API cells: $0 → green, large fees → red, small fees → neutral
-const valueColor = (val: string) => {
- const n = parseFloat(val.replace(/[$€,]/g, ""));
- if (n === 0) return "#16a34a"; // green
- if (n > 50) return "#dc2626"; // red
- return "#52525B"; // neutral
-};
+/** The modelled workload: 10 TB stored, 10 TB egress, 500K operations a month. */
+const WORKLOAD_TB = 10;
+const FIL_ONE_TOTAL = eurEs(PRICE_PER_TB_EUR * WORKLOAD_TB);
+
+const PRICING_COLUMNS: PriceComparisonColumn[] = [
+ { key: "region", header: "Región" },
+ { key: "storage", header: "Almacenamiento" },
+ { key: "egress", header: "Egress", colorByValue: true },
+ { key: "api", header: "API / ops", colorByValue: true },
+ { key: "total", header: "Total / mes", total: true },
+];
-// ─── Pricing table data ────────────────────────────────────────────────────────
-const PRICING_ROWS = [
- { provider: "Fil One", region: "EU-West", storage: "€49.90", egress: "€0", api: "€0", total: "€49.90", isFilOne: true },
- { provider: "Backblaze B2", region: "eu-central-003 Amsterdam", storage: "€59.60", egress: "€0", api: "€0", total: "€59.60", isFilOne: false },
- { provider: "Wasabi", region: "eu-west-2 Paris", storage: "€59.90", egress: "€0", api: "€0", total: "€59.90", isFilOne: false },
- { provider: "AWS S3 Standard", region: "eu-south-2 Madrid", storage: "€197", egress: "€790", api: "€1.83", total: "€990", isFilOne: false },
+const PRICING_ROWS: PriceComparisonRow[] = [
+ {
+ provider: "Fil One",
+ isFilOne: true,
+ values: {
+ region: "EU-West",
+ storage: FIL_ONE_TOTAL,
+ egress: eurEs(0, 0),
+ api: eurEs(0, 0),
+ total: FIL_ONE_TOTAL,
+ },
+ },
+ {
+ provider: "Backblaze B2",
+ values: {
+ region: "eu-central-003 Amsterdam",
+ storage: eurEs(59.6),
+ egress: eurEs(0, 0),
+ api: eurEs(0, 0),
+ total: eurEs(59.6),
+ },
+ },
+ {
+ provider: "Wasabi",
+ values: {
+ region: "eu-west-2 Paris",
+ storage: eurEs(59.9),
+ egress: eurEs(0, 0),
+ api: eurEs(0, 0),
+ total: eurEs(59.9),
+ },
+ },
+ {
+ provider: "AWS S3 Standard",
+ values: {
+ region: "eu-south-2 Madrid",
+ storage: eurEs(197, 0),
+ egress: eurEs(790, 0),
+ api: eurEs(1.83),
+ total: eurEs(990, 0),
+ },
+ },
];
-// ─── Integrations ──────────────────────────────────────────────────────────────
-const INTEGRATIONS = [
- "Iconik", "LucidLink", "Veeam", "Rclone", "Restic",
- "MSP360", "Premiere", "DaVinci Resolve", "Hugging Face",
- "PyTorch", "Arq", "Duplicati",
+const STATS = [
+ { stat: PRICE_PER_TB_SHORT_EUR_ES, label: "Precio fijo mensual" },
+ { stat: eurEs(0, 0), label: "Costes por egress" },
+ // El 20× es sobre la factura total, que depende del egress, así que
+ // solo se cumple con un uso intensivo de lectura. La nota indica el escenario.
+ {
+ stat: "20×",
+ label: "Más barato que AWS",
+ note: `Con ${WORKLOAD_TB} TB de almacenamiento y ${WORKLOAD_TB} TB de egress`,
+ },
+];
+
+const FEATURES = [
+ { icon: Plug, title: "Compatibilidad inmediata con S3", desc: "La misma API, los mismos SDK y herramientas. Conecta tu flujo de trabajo a nuestro endpoint y sigue trabajando." },
+ { icon: ArrowsOut, title: "Sin cargos por egress", desc: "Cada lectura es gratis, así que tu factura se mantiene plana sin importar cuánto uses el servicio." },
+ // Espacio fino antes del %, según la convención del SI y la RAE.
+ { icon: ShieldCheck, title: "Once nueves de durabilidad", desc: "99,999999999 % de durabilidad, replicada en varias ubicaciones y monitorizada permanentemente." },
+ { icon: Lock, title: "Object Lock y versionado", desc: "Modos de cumplimiento, periodos de retención y registros de auditoría a prueba de manipulaciones." },
+ { icon: MapPin, title: "Tus datos nunca salen de la UE", desc: "La infraestructura de almacenamiento permanece dentro de las fronteras europeas." },
+ { icon: Rocket, title: "Listo en cuestión de minutos", desc: "Genera tus claves de acceso, apunta tus herramientas a nuestro endpoint y empieza a subir datos." },
];
-// ─── Page ──────────────────────────────────────────────────────────────────────
const BarcelonaLandingPageES = () => {
useLang("es");
useSeo({
- title: "Fil One para Barcelona: Almacenamiento Europeo, €4.99/TB, Sin Egress",
+ title: `Fil One para Barcelona: Almacenamiento Europeo, ${PRICE_PER_TB_SHORT_EUR_ES}, Sin Egress`,
description:
- "Almacenamiento de objetos compatible con S3 para equipos en Barcelona. Soberanía de datos en la UE, cero comisiones de egress, a €4.99/TB. Intégralo en tu stack actual en minutos.",
+ `Almacenamiento de objetos compatible con S3 para equipos en Barcelona. Soberanía de datos en la UE, cero comisiones de egress, a ${PRICE_PER_TB_SHORT_EUR_ES}. Intégralo en tu stack actual en minutos.`,
canonical: "https://www.fil.one/lp/es/barcelona",
});
- const { ref: posRef, inView: posInView } = useInView({ threshold: 0.05 });
- const { ref: pricingRef, inView: pricingInView } = useInView({ threshold: 0.05 });
- const { ref: featuresRef, inView: featuresInView } = useInView({ threshold: 0.05 });
- const { ref: integrationsRef, inView: integrationsInView } = useInView({ threshold: 0.05 });
- const { ref: ctaRef, inView: ctaInView } = useInView({ threshold: 0.05 });
+ const { ref: posRef, inView: posInView } = useInView({ threshold: 0.05 });
+ const { ref: pricingRef, inView: pricingInView } = useInView({ threshold: 0.05 });
+ const { ref: featuresRef, inView: featuresInView } = useInView({ threshold: 0.05 });
return (
-
-
+
+
{/* ── Hero ─────────────────────────────────────────────────────────── */}
-
- {/* Blue radial glow */}
-
- {/* Grid texture */}
-
-
-
- {/* Headline */}
-
- Almacenamiento europeo.
4,99 €/TB, sin cargos por tráfico de salida.
-
-
- {/* Subheadline */}
-
- Almacenamiento de objetos compatible con S3 que mantiene tus datos en Europa. Funciona con las herramientas que ya utilizas, sin necesidad de migraciones.
-
-
- {/* CTAs */}
-
-
-
-
+
+ Almacenamiento europeo.
+
+ {PRICE_PER_TB_SHORT_EUR_ES}, sin cargos por egress.
+ >
+ }
+ description="Almacenamiento de objetos compatible con S3 que mantiene tus datos en Europa. Funciona con las herramientas que ya utilizas, sin necesidad de migraciones."
+ ctas={[{ label: "Empieza con 30 días gratis", href: SIGNUP_URL, variant: "primary", size: "lg", glow: true }]}
+ />
{/* ── Publications / Social proof ──────────────────────────────────── */}
-
-
-
- Nuestra tecnología ha sido destacada en
-
-
-
- {[0, 1].map((copy) => (
-
- {["Fast Company", "CNBC", "Bloomberg", "Yahoo Finance", "VentureBeat"].map((pub) => (
-
- {pub}
- ·
-
- ))}
-
- ))}
-
-
-
-
+
- {/* ── Positioning ───────────────────────────────────────────────── */}
-
+ {/* ── Positioning ───────────────────────────────────────────────────── */}
+
Por qué Fil One
- Un coste imbatible
+
+ Un coste imbatible
+
- Compatible con S3, soberanía de datos en la UE y sin cargos por tráfico de salida. Sin costes ocultos ni sorpresas.
+ Compatible con S3, soberanía de datos en la UE y sin cargos por egress. Sin costes ocultos ni
+ sorpresas.
- {[
- { stat: "€4.99/TB", label: "Precio fijo mensual" },
- { stat: "€0", label: "Costes de salida" },
- { stat: "20×", label: "Más barato que AWS" },
- ].map(({ stat, label }) => (
-
+ {STATS.map(({ stat, label, note }) => (
+
))}
{/* ── Pricing table ─────────────────────────────────────────────────── */}
-
+
Precios
- Tu factura mensual, de cuatro formas
+
+ Tu factura mensual, de cuatro formas
+
- Un equipo de 10 TB en Barcelona, con 10 TB de tráfico de salida al mes y con 500.000 operaciones de objetos.
+ Un equipo en Barcelona con {WORKLOAD_TB} TB de almacenamiento, {WORKLOAD_TB} TB de egress al mes y
+ 500.000 operaciones sobre objetos.
- {/* Mobile: stacked cards (table below is md+ only) */}
-
- {PRICING_ROWS.map((row) => (
-
-
-
- {row.provider}
-
- {row.isFilOne && (
-
- Tú
-
- )}
-
-
- Región
- {row.region}
- Almacenamiento
- {row.storage}
- Egress
- {row.egress}
- API / ops
- {row.api}
- Total / mes
-
- {row.total}
-
-
-
- ))}
-
-
- {/* Desktop / tablet: full table */}
-
-
-
-
- {["Proveedor", "Región", "Almacenamiento", "Egress", "API / ops", "Total / mes"].map((h) => (
- |
- {h}
- |
- ))}
-
-
-
- {PRICING_ROWS.map((row) => (
-
- {/* Provider */}
- |
- {row.provider}
- {row.isFilOne && (
-
- Tú
-
- )}
- |
- {/* Region */}
-
- {row.region}
- |
- {/* Storage */}
-
- {row.storage}
- |
- {/* Egress */}
-
- {row.egress}
- |
- {/* API */}
-
- {row.api}
- |
- {/* Total */}
-
- {row.total}
- |
-
- ))}
-
-
-
-
- Los precios de la competencia se han convertido de USD utilizando el tipo de cambio de €1 = $1.17 (Tipo de cambio ECB, Mayo 2026). Fil One tiene un precio nativo en euros de 4.99 €/TB.
-
+
+ Los precios de los demás proveedores se han convertido de USD a EUR utilizando un tipo de cambio de
+ 1 € = {EUR_USD_RATE_ES} ({EUR_USD_RATE_SOURCE_ES}). Fil One tiene un precio nativo en euros de{" "}
+ {PRICE_PER_TB_SHORT_EUR_ES}.
+ >
+ }
+ />
{/* Mid-page CTA after pricing table */}
{/* ── Features ─────────────────────────────────────────────────────── */}
-
-
+
+
-
- Funcionalidades
-
-
- El S3 que esperabas
-
-
- Compatible con todo lo que tu equipo ya utiliza.
-
+
Funcionalidades
+
+ El S3 que esperabas
+
+
Compatible con todo lo que tu equipo ya utiliza.
- {[
- { icon: Plug, title: "Compatibilidad inmediata con S3", desc: "La misma API, los mismos SDKs y herramientas. Conecta tu flujo a nuestro endpoint y sigue trabajando." },
- { icon: ArrowsOut, title: "Sin cargos por tráfico de salida", desc: "Cada lectura es gratis, así que tu factura se mantiene plana sin importar cuánto uses el servicio." },
- { icon: ShieldCheck, title: "Once nueves de durabilidad", desc: "99,999999999% de durabilidad, replicada en varias ubicaciones y monitorizada permanentemente." },
- { icon: Lock, title: "Object Lock y versionado", desc: "Modos de cumplimiento, periodos de retención y registros de auditoría a prueba de manipulación." },
- { icon: MapPin, title: "Tus datos nunca salen de la UE", desc: "La infraestructura de almacenamiento permanece dentro de las fronteras europeas." },
- { icon: Rocket, title: "Listo en cuestión de minutos", desc: "Genera tus claves de acceso, configura tus herramientas existentes para que apunten a nuestro endpoint y empieza a subir datos." },
- ].map(({ icon: Icon, title, desc }) => (
-
(
+
-
-
-
-
-
- {title}
-
-
- {desc}
-
-
-
+ icon={icon}
+ title={title}
+ description={desc}
+ className={`reveal${featuresInView ? " in-view" : ""}`}
+ />
))}
{/* ── Integrations ──────────────────────────────────────────────────── */}
-
-
-
- Integraciones
- Funciona con tu stack actual
-
- Compatible con la API de S3. Si funciona con AWS, funciona con nosotros.
-
-
-
-
-
- {[0, 1].map((copy) => (
-
- {INTEGRATIONS.map((name) => (
-
- {name}
-
- ))}
-
- ))}
-
-
-
-
- Ver la documentación →
-
-
-
+
+ Funciona con tu stack actual
+ >
+ }
+ description="Compatible con la API de S3. Si funciona con AWS, funciona con nosotros."
+ ctaLabel="Ver documentación →"
+ />
{/* ── CTA Banner ────────────────────────────────────────────────────── */}
-
-
-
- {/* White grid texture, drifting slowly */}
-
')}")`,
- backgroundSize: "60px 60px",
- maskImage: "radial-gradient(ellipse 80% 90% at 50% 50%, black 0%, transparent 80%)",
- WebkitMaskImage: "radial-gradient(ellipse 80% 90% at 50% 50%, black 0%, transparent 80%)",
- pointerEvents: "none",
- }}
- />
-
- {/* Soft breathing glow behind the copy */}
-
-
-
-
- El almacenamiento de objetos más económico de Europa
-
-
- 4,99 €/TB, sin costes de salida y listo para usar en minutos.
-
-
-
-
-
- No se requiere tarjeta de crédito.
-
-
-
-
-
+
-
+
);
};