diff --git a/showcase/angular/src/app/pages/home/home-page.component.scss b/showcase/angular/src/app/pages/home/home-page.component.scss index 8c22b420..97d67fe9 100644 --- a/showcase/angular/src/app/pages/home/home-page.component.scss +++ b/showcase/angular/src/app/pages/home/home-page.component.scss @@ -49,6 +49,8 @@ .hero-content { text-align: center; + width: 100%; + min-width: 0; max-width: 800px; padding: 48px 24px; position: relative; @@ -1211,9 +1213,8 @@ } /* Mobile-native hero: first phone viewport holds only the FSB title, the two - CTAs, and the scroll cue. Append-only region: these rules override the - @media (max-width: 768px) block above purely by source order — do not move - or merge them upward, and never edit the rules above. */ + CTAs, and the scroll cue. These rules override the broader mobile block + above by source order. */ @media (max-width: 768px) { .hero { min-height: 100vh; @@ -1227,12 +1228,15 @@ } .hero h1 { - /* Single line at every phone width: available width is 100vw - 48px, so - the size must track vw with a negative constant (title is ~8.5x its - font-size wide). */ + /* Keep the English wordmark on one line when it fits, while allowing + longer translations and unbroken locale text to wrap before clipping. */ font-size: clamp(1.75rem, 11.2vw - 0.4rem, 3.2rem); + line-height: 1.1; + max-width: 100%; margin-bottom: 36px; - white-space: nowrap; + overflow-wrap: anywhere; + text-wrap: balance; + white-space: normal; } .hero-subtitle { diff --git a/tests/showcase-build-smoke.test.js b/tests/showcase-build-smoke.test.js index 518dc1f0..5972753a 100644 --- a/tests/showcase-build-smoke.test.js +++ b/tests/showcase-build-smoke.test.js @@ -44,6 +44,8 @@ console.log('--- showcase-build-smoke (STATS-05 + STATS-06) ---'); const SOURCE_XLF_PATH = path.join(ROOT, 'showcase/angular/src/locale/messages.xlf'); const sourceXlf = fs.readFileSync(SOURCE_XLF_PATH, 'utf8'); +const homeStylePath = path.join(ROOT, 'showcase/angular/src/app/pages/home/home-page.component.scss'); +const homeStyle = fs.readFileSync(homeStylePath, 'utf8'); const RETIRED_STATS_ISSUES_IDS = [ 'stats.view.issues', 'stats.metric.open', @@ -83,6 +85,21 @@ check('source: retired Issues/Sankey Stats messages are absent', RETIRED_STATS_ISSUES_IDS.every((id) => !sourceXlf.includes(` match[1]); +const localizedHeroTitleRule = heroTitleRules.find((rule) => /overflow-wrap:\s*anywhere/.test(rule)); +check('source: hero content is constrained to the viewport width', + heroContentRule !== null && /width:\s*100%/.test(heroContentRule[1]) && /min-width:\s*0/.test(heroContentRule[1]), + '.hero-content must have width: 100% and min-width: 0'); +check('source: localized mobile hero titles can wrap safely', + localizedHeroTitleRule !== undefined + && /white-space:\s*normal/.test(localizedHeroTitleRule) + && /max-width:\s*100%/.test(localizedHeroTitleRule), + 'mobile .hero h1 must allow wrapping within its content box'); +check('source: no hero title rule forces translated text onto one line', + heroTitleRules.every((rule) => !/white-space:\s*nowrap/.test(rule)), + 'remove white-space: nowrap from .hero h1'); + // Each non-en locale must have a block for every // SHOWCASE_STATS_FSB_* id in the source. const LOCALES = ['es', 'de', 'ja', 'zh-CN', 'zh-TW'];