Skip to content

Commit be77038

Browse files
authored
Merge pull request #106 from fil-one/filipa/fil-856-migrate-lpesbarcelona-to-design-tokens-shared-components
Migrate /lp/es/barcelona to design tokens + shared components (FIL-856)
2 parents 9a4e49d + ccbdc29 commit be77038

8 files changed

Lines changed: 284 additions & 563 deletions

File tree

scripts/routeMeta.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
PRICE_PER_TB_MONTH,
1414
PRICE_AMOUNT,
1515
PRICE_PER_TB_SHORT_EUR,
16+
PRICE_PER_TB_SHORT_EUR_ES,
1617
} from "../src/lib/pricing.constants.mjs";
1718

1819
export const BASE_URL = "https://www.fil.one";
@@ -369,9 +370,9 @@ export const ROUTE_META = {
369370
},
370371
"/lp/es/barcelona": {
371372
lang: "es",
372-
title: `Fil One para Barcelona: Almacenamiento Europeo, ${PRICE_PER_TB_SHORT_EUR}, Sin Egress`,
373+
title: `Fil One para Barcelona: Almacenamiento Europeo, ${PRICE_PER_TB_SHORT_EUR_ES}, Sin Egress`,
373374
description:
374-
`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.`,
375+
`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.`,
375376
},
376377
"/contact-sales": {
377378
title: "Contact Sales · Fil One",

src/components/CtaBanner.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ interface CtaBannerProps {
1313
*/
1414
secondaryCta?: { label: string; href: string; onClick?: () => void };
1515
note?: ReactNode;
16+
/** Cap on the heading width (px). Longer translations need more room. */
17+
headingMaxWidth?: number;
1618
}
1719

1820
/**
1921
* Dark closing CTA banner: a navy gradient card with a drifting grid texture
2022
* and a breathing glow, a headline, and a glowing primary button. Tuned for
2123
* the closing section of a page (sits on white, above the footer).
2224
*/
23-
const CtaBanner = ({ heading, subhead, cta, secondaryCta, note }: CtaBannerProps) => {
25+
const CtaBanner = ({ heading, subhead, cta, secondaryCta, note, headingMaxWidth = 480 }: CtaBannerProps) => {
2426
const { ref, inView } = useInView({ threshold: 0.05 });
2527

2628
return (
@@ -53,7 +55,7 @@ const CtaBanner = ({ heading, subhead, cta, secondaryCta, note }: CtaBannerProps
5355
<div className="relative">
5456
<h2
5557
className="text-[26px] md:text-[32px] font-display font-medium text-white mx-auto mb-3"
56-
style={{ letterSpacing: "-0.025em", lineHeight: "1.12", maxWidth: 480 }}
58+
style={{ letterSpacing: "-0.025em", lineHeight: "1.12", maxWidth: headingMaxWidth }}
5759
>
5860
{heading}
5961
</h2>

src/components/IntegrationsSection.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ReactNode } from "react";
12
import { SectionLabel, SectionHeading, SectionSub } from "@/components/LandingPrimitives";
23
import { useInView } from "@/hooks/useInView";
34

@@ -10,13 +11,34 @@ const INTEGRATIONS = [
1011
interface IntegrationsSectionProps {
1112
/** white (default) or the standard grey section treatment (zinc-50 + zinc-100 borders) */
1213
tone?: "white" | "grey";
14+
/** Section eyebrow; defaults to English. */
15+
label?: ReactNode;
16+
/** Section heading; defaults to English. */
17+
heading?: ReactNode;
18+
/** Supporting line under the heading; defaults to English. */
19+
description?: ReactNode;
20+
/** Docs link label; defaults to English. */
21+
ctaLabel?: ReactNode;
1322
}
1423

1524
/**
1625
* Auto-scrolling marquee of supported integrations — S3-compatibility
1726
* reassurance shared by the /lp/price and Barcelona landing pages.
27+
*
28+
* Copy is overridable so the Spanish pages can share the section rather than
29+
* inlining a translated copy of it; the defaults are the English strings.
1830
*/
19-
const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
31+
const IntegrationsSection = ({
32+
tone = "white",
33+
label = "Integrations",
34+
heading = (
35+
<>
36+
Works with your <span className="text-brand-500">existing stack</span>
37+
</>
38+
),
39+
description = "S3 API compatible. If it talks to AWS, it talks to us.",
40+
ctaLabel = "View documentation →",
41+
}: IntegrationsSectionProps) => {
2042
const { ref, inView } = useInView({ threshold: 0.05 });
2143

2244
return (
@@ -31,9 +53,9 @@ const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
3153
className={`flex flex-col gap-10 items-center text-center w-full max-w-container mx-auto reveal${inView ? " in-view" : ""}`}
3254
>
3355
<div className="flex flex-col gap-3 items-center">
34-
<SectionLabel>Integrations</SectionLabel>
35-
<SectionHeading>Works with your <span className="text-brand-500">existing stack</span></SectionHeading>
36-
<SectionSub maxWidth={440}>S3 API compatible. If it talks to AWS, it talks to us.</SectionSub>
56+
<SectionLabel>{label}</SectionLabel>
57+
<SectionHeading>{heading}</SectionHeading>
58+
<SectionSub maxWidth={440}>{description}</SectionSub>
3759
</div>
3860

3961
<div className="marquee-mask w-full overflow-hidden">
@@ -54,7 +76,7 @@ const IntegrationsSection = ({ tone = "white" }: IntegrationsSectionProps) => {
5476
</div>
5577

5678
<a href="https://docs.fil.one" target="_blank" rel="noopener noreferrer" className="btn-secondary">
57-
View documentation →
79+
{ctaLabel}
5880
</a>
5981
</div>
6082
</section>

src/components/PriceComparisonTable.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,25 @@ interface PriceComparisonTableProps {
3939
centerFootnote?: boolean;
4040
}
4141

42+
/**
43+
* Parse a formatted currency string to a number, tolerating both the English
44+
* ("€1,234.56") and Spanish ("1.234,56 €") conventions. Whichever of "." or ","
45+
* comes last is the decimal separator when 1 to 2 digits follow it; otherwise
46+
* every separator is thousands grouping. Returns NaN for non-numeric values,
47+
* which falls through to the neutral tone.
48+
*/
49+
const parseAmount = (value: string) => {
50+
const digits = value.replace(/[^0-9.,-]/g, "");
51+
const lastSep = Math.max(digits.lastIndexOf("."), digits.lastIndexOf(","));
52+
if (lastSep === -1) return parseFloat(digits);
53+
const trailing = digits.length - lastSep - 1;
54+
if (trailing < 1 || trailing > 2) return parseFloat(digits.replace(/[.,]/g, ""));
55+
return parseFloat(`${digits.slice(0, lastSep).replace(/[.,]/g, "")}.${digits.slice(lastSep + 1)}`);
56+
};
57+
4258
/** Zero reads as success, a large charge as danger, anything else neutral. */
4359
const valueTone = (value: string) => {
44-
const n = parseFloat(value.replace(/[^0-9.-]/g, ""));
60+
const n = parseAmount(value);
4561
if (n === 0) return "text-success-700";
4662
if (n > 50) return "text-danger-600";
4763
return "text-zinc-600";

src/lib/pricing.constants.d.mts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ export const PRICE_PER_TB_SHORT_EUR: string;
1111
export const PRICE_PER_TB_MONTH_EUR: string;
1212
export const EUR_USD_RATE: number;
1313
export const EUR_USD_RATE_SOURCE: string;
14+
export function eurEs(amount: number, decimals?: number): string;
15+
export const PRICE_DISPLAY_EUR_ES: string;
16+
export const PRICE_PER_TB_SHORT_EUR_ES: string;
17+
export const PRICE_PER_TB_MONTH_EUR_ES: string;
18+
export const EUR_USD_RATE_ES: string;
19+
export const EUR_USD_RATE_SOURCE_ES: string;

src/lib/pricing.constants.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,29 @@ export const EUR_USD_RATE = 1.17;
5555

5656
/** Where and when EUR_USD_RATE was taken, for the comparison-table footnote. */
5757
export const EUR_USD_RATE_SOURCE = "ECB rate, May 2026";
58+
59+
/* ── Spanish-locale EUR formatting ──────────────────────────────────────────
60+
* Spanish writes a decimal comma with the symbol after the number and a
61+
* non-breaking space ("4,99 €"), not "€4.99". The Spanish pages format every
62+
* figure this way, so the helper is shared rather than hand-written per page.
63+
* ─────────────────────────────────────────────────────────────────────────── */
64+
65+
/** Format a EUR amount the Spanish way, e.g. 49.9 -> "49,90 €". Pass
66+
* `decimals: 0` for whole-euro figures like "197 €". */
67+
export const eurEs = (amount, decimals = 2) =>
68+
`${amount.toFixed(decimals).replace(".", ",")} €`;
69+
70+
/** The bare EUR price, Spanish format, e.g. "4,99 €". */
71+
export const PRICE_DISPLAY_EUR_ES = eurEs(PRICE_PER_TB_EUR);
72+
73+
/** The short per-TB EUR rate, Spanish format, e.g. "4,99 €/TB". */
74+
export const PRICE_PER_TB_SHORT_EUR_ES = `${PRICE_DISPLAY_EUR_ES}/TB`;
75+
76+
/** The full per-TB EUR rate for inline Spanish copy, e.g. "4,99 €/TB al mes". */
77+
export const PRICE_PER_TB_MONTH_EUR_ES = `${PRICE_PER_TB_SHORT_EUR_ES} al mes`;
78+
79+
/** EUR_USD_RATE in Spanish format with the symbol after the number, "1,17 $". */
80+
export const EUR_USD_RATE_ES = `${EUR_USD_RATE.toFixed(2).replace(".", ",")} $`;
81+
82+
/** Spanish rendering of EUR_USD_RATE_SOURCE, for the ES comparison footnote. */
83+
export const EUR_USD_RATE_SOURCE_ES = "BCE, mayo de 2026";

src/lib/pricing.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ export {
3333
EUR_USD_RATE,
3434
/** Where and when EUR_USD_RATE was taken. */
3535
EUR_USD_RATE_SOURCE,
36+
/** Format a EUR amount the Spanish way, e.g. 49.9 -> "49,90 €". */
37+
eurEs,
38+
/** The bare EUR price, Spanish format, e.g. "4,99 €". */
39+
PRICE_DISPLAY_EUR_ES,
40+
/** The short per-TB EUR rate, Spanish format, e.g. "4,99 €/TB". */
41+
PRICE_PER_TB_SHORT_EUR_ES,
42+
/** The full per-TB EUR rate for Spanish copy, e.g. "4,99 €/TB al mes". */
43+
PRICE_PER_TB_MONTH_EUR_ES,
44+
/** EUR_USD_RATE in Spanish format, e.g. "1,17 $". */
45+
EUR_USD_RATE_ES,
46+
/** Spanish rendering of EUR_USD_RATE_SOURCE. */
47+
EUR_USD_RATE_SOURCE_ES,
3648
} from "./pricing.constants.mjs";
3749

3850
import { PRICE_PER_TB } from "./pricing.constants.mjs";

0 commit comments

Comments
 (0)