diff --git a/landing/hero-p0.css b/landing/hero-p0.css index 294de89c6..d91b3709c 100644 --- a/landing/hero-p0.css +++ b/landing/hero-p0.css @@ -1,5 +1,5 @@ /* ARSAS P0 hero refinement. - Desktop-only composition fixes based on production visual review. + Desktop composition + localized-copy safety based on production visual review. Keeps content, SEO, release sync and mobile behavior unchanged. */ @media (min-width: 981px) { @@ -24,16 +24,21 @@ } .hero h1 { - max-width: 34rem; + max-width: min(36.5rem, 100%); margin-top: 1.22rem; font-size: clamp(3.65rem, 4.35vw, 4.08rem); line-height: 1.025; letter-spacing: -.046em; } + /* Never make translated headline length part of the layout contract. + English still remains on one line where it naturally fits; longer locales + may wrap instead of escaping the text column and being clipped by .hero. */ .hero h1 .gradient-text { display: block; - white-space: nowrap; + max-inline-size: 100%; + white-space: normal; + text-wrap: balance; } .hero-copy { @@ -85,6 +90,32 @@ right: -.7rem; bottom: 11%; } + + /* Indonesian copy is materially longer than English. Give the message and + product equal authority instead of shrinking or clipping the translation. */ + .hero:lang(id) .hero-grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: clamp(1.85rem, 2.8vw, 2.6rem); + } + + .hero:lang(id) h1 { + max-width: min(36.5rem, 100%); + font-size: clamp(3.45rem, 4.05vw, 3.86rem); + } + + .hero:lang(id) .hero-copy { + max-width: 36rem; + } + + .hero:lang(id) .eyebrow { + max-width: 100%; + white-space: normal; + line-height: 1.35; + } + + .hero:lang(id) .hero-visual { + width: 105%; + } } @media (min-width: 981px) and (max-width: 1180px) { @@ -109,4 +140,19 @@ .hero-visual { width: 104%; } + + /* At compact desktop widths localized controls may wrap deliberately. + This is preferable to horizontal overflow or hidden actions. */ + .hero:lang(id) h1 { + font-size: clamp(3.25rem, 4.75vw, 3.55rem); + } + + .hero:lang(id) .hero-actions, + .hero:lang(id) .hero-proof { + flex-wrap: wrap; + } + + .hero:lang(id) .hero-visual { + width: 102%; + } } diff --git a/tests/ARSAS.Tests/WebsiteHeroLocalizationRegressionTests.cs b/tests/ARSAS.Tests/WebsiteHeroLocalizationRegressionTests.cs new file mode 100644 index 000000000..361c658b3 --- /dev/null +++ b/tests/ARSAS.Tests/WebsiteHeroLocalizationRegressionTests.cs @@ -0,0 +1,43 @@ +namespace ARSAS.Tests; + +public sealed class WebsiteHeroLocalizationRegressionTests +{ + [Fact] + public void HeroGradientHeadline_AllowsLocalizedCopyToWrapInsteadOfClipping() + { + var css = Read("landing/hero-p0.css").Replace("\r\n", "\n", StringComparison.Ordinal); + + Assert.Contains(".hero h1 .gradient-text", css, StringComparison.Ordinal); + Assert.Contains("white-space: normal;", css, StringComparison.Ordinal); + Assert.DoesNotContain( + ".hero h1 .gradient-text {\n display: block;\n white-space: nowrap;", + css, + StringComparison.Ordinal); + } + + [Fact] + public void IndonesianHero_HasDedicatedDesktopSafetyRules() + { + var css = Read("landing/hero-p0.css"); + + Assert.Contains(".hero:lang(id) .hero-grid", css, StringComparison.Ordinal); + Assert.Contains(".hero:lang(id) h1", css, StringComparison.Ordinal); + Assert.Contains(".hero:lang(id) .eyebrow", css, StringComparison.Ordinal); + Assert.Contains(".hero:lang(id) .hero-actions", css, StringComparison.Ordinal); + Assert.Contains("flex-wrap: wrap;", css, StringComparison.Ordinal); + } + + private static string Read(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return File.ReadAllText(candidate); + directory = directory.Parent; + } + + throw new FileNotFoundException(relativePath); + } +}