diff --git a/styles/components/site-footer.css b/styles/components/site-footer.css index d38d6f2..9a1fa26 100644 --- a/styles/components/site-footer.css +++ b/styles/components/site-footer.css @@ -1,30 +1,46 @@ /* Footer styles */ .site-footer { - border-bottom: 1rem solid var(--color-orange, #F26B53); + border-bottom: 1rem solid var(--color-orange, #f26b53); - a { @apply text-white transition-colors duration-300 hover:text-secondary-400 focus-visible:text-secondary-400; } - - .widget { - li { @apply text-16px my-1 mb-2 leading-4; } - - h4 { @apply font-bold text-18px text-secondary mb-2; } + a { + @apply text-white transition-colors duration-300 hover:text-secondary-400 focus-visible:text-secondary-400; } - .menu-footer-menu-container { @apply grow; } + .widget { + li { + @apply text-16px my-1 mb-2 leading-4; + } + + h4 { + @apply font-bold text-18px text-secondary mb-2; + } + } + + .menu-footer-menu-container { + @apply grow; + } .copyright { - p { @apply text-white leading-none m-0 p-0; } + p { + @apply text-white leading-none m-0 p-0; + } - a { @apply text-white hover:text-primary-500 underline underline-offset-2; } + a { + @apply text-white hover:text-primary-500 underline underline-offset-2; + } } .contact__item { @apply flex justify-start items-start font-light text-white text-14px mb-2; - p { @apply m-0 p-0; } + p { + @apply m-0 p-0; + } - a { @apply text-white hover:text-secondary-400 focus-visible:text-secondary-400; } + a { + @apply text-white hover:text-secondary-400 focus-visible:text-secondary-400; + } &::before { @apply inline-block mr-2; @@ -35,13 +51,16 @@ } &.contact__phone::before { - background: url("../../static/img/footer-phone.svg") no-repeat center center; + background: url("../../static/img/footer-phone.svg") no-repeat + center center; } &.contact__email::before { - background: url("../../static/img/footer-email.svg") no-repeat center center; + background: url("../../static/img/footer-email.svg") no-repeat + center center; } &.contact__address::before { - background: url("../../static/img/footer-map.svg") no-repeat center center; + background: url("../../static/img/footer-map.svg") no-repeat center + center; padding-top: 2rem; } } @@ -49,32 +68,118 @@ @media (max-width: 767px) { .site-footer { - padding-block: 2.25rem 1.25rem; + border-bottom-width: clamp(4.0625rem, 20.4vw, 5.125rem); + font-size: clamp(0.875rem, 3.98vw, 1rem); + min-height: clamp(34rem, 167.4vw, 42.0625rem); + padding-block: clamp(2.375rem, 11.94vw, 3rem) + clamp(1.125rem, calc(12.2vw - 1.3125rem), 1.75rem); } .site-footer > .container { - gap: 1.5rem 1rem; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - padding-inline: 1rem; + display: grid; + gap: 0 clamp(0.8125rem, 3.98vw, 1rem); + grid-template-columns: minmax(0, 1fr) auto; + padding-inline: clamp(1.59375rem, 7.96vw, 2rem); } - .site-footer .site-footer__logo { - max-width: 6rem; + .site-footer > .container > div:first-child { + grid-column: 1; + grid-row: 1; } - .site-footer .widget li { - font-size: var(--text-14px); - line-height: 1.1; - margin-bottom: 0.4rem; + .site-footer #footer-header { + margin: 0; + max-width: clamp(9.75rem, 48.76vw, 12.25rem); } - .site-footer .widget h4 { - font-size: var(--text-16px); - margin-bottom: 0.5rem; + .site-footer #footer-nav { + grid-column: 1 / -1; + grid-row: 2; + margin-top: clamp(1.5625rem, 7.71vw, 1.9375rem); } - .site-footer .text-right { - align-items: flex-start; - text-align: left; + .site-footer .site-footer__nav-list { + display: grid; + gap: clamp(0.625rem, 3.23vw, 0.8125rem) clamp(1.59375rem, 7.96vw, 2rem); + grid-auto-flow: column; + grid-template-rows: repeat(3, auto); + } + + .site-footer .site-footer__nav-item { + margin-bottom: 0; + } + + .site-footer #footer-nav + div { + grid-column: 1 / -1; + grid-row: 3; + margin-top: clamp(1.25rem, 6.22vw, 1.5625rem); + } + + .site-footer #footer-description { + font-size: clamp(1rem, 4.98vw, 1.25rem); + line-height: 1.2; + max-width: 21rem; + } + + .site-footer .contact__item { + font-size: clamp(0.75rem, 3.48vw, 0.875rem); + } + + .site-footer .contact__address { + margin-top: clamp(0.875rem, 4.48vw, 1.125rem); + } + + .site-footer .contact__email, + .site-footer .contact__phone { + margin-top: clamp(0.8125rem, 3.98vw, 1rem); + } + + .site-footer .contact__item::before { + height: clamp(1.1875rem, 5.97vw, 1.5rem); + margin-right: clamp(0.375rem, 1.99vw, 0.5rem); + width: clamp(1.1875rem, 5.97vw, 1.5rem); + } + + .site-footer .contact__address::before { + padding-top: clamp(1.59375rem, 7.96vw, 2rem); + } + + .site-footer > .container > .text-right { + display: contents; + } + + .site-footer x-back-to-top { + align-self: start; + grid-column: 2; + grid-row: 1; + } + + .site-footer #backToTopBtn { + height: clamp(2rem, 9.95vw, 2.5rem); + padding: 0; + width: clamp(2rem, 9.95vw, 2.5rem); + } + + .site-footer .social-links { + grid-column: 1 / -1; + grid-row: 4; + justify-content: flex-end; + margin-top: clamp(0.5rem, 2.49vw, 0.625rem); + } + + .site-footer .social-icons { + --size: clamp(1.1875rem, 5.97vw, 1.5rem); + } + + .site-footer .social-links a { + margin-right: clamp(0.8125rem, 3.98vw, 1rem); + } + + .site-footer .copyright { + grid-column: 1 / -1; + grid-row: 5; + margin-top: clamp(0.8125rem, 4.23vw, 1.0625rem); + padding-block: 0; + text-align: center; } } diff --git a/tests/mobile-homepage.spec.js b/tests/mobile-homepage.spec.js index 9f92f6d..bc5c90a 100644 --- a/tests/mobile-homepage.spec.js +++ b/tests/mobile-homepage.spec.js @@ -697,6 +697,69 @@ test("configured footer channels use the prepared SVG icons", async ({ ).toBeVisible(); }); +test("mobile footer matches the reference hierarchy", async ({ page }) => { + await page.goto(homepage); + + const footer = page.locator(".site-footer"); + const footerBox = await getBox(footer); + const logo = await getBox(footer.locator(".site-footer__logo-image")); + const backToTop = await getBox(footer.locator("#backToTopBtn")); + const nav = await getBox(footer.locator("#footer-nav")); + const contact = await getBox(footer.locator(".contact")); + + expectNear(footerBox.height, 673, 12); + expectNear(logo.y, backToTop.y, 8); + expect(nav.y).toBeGreaterThan(logo.y + logo.height); + expect(contact.y).toBeGreaterThan(nav.y + nav.height); +}); + +test("mobile footer scales down and remains contained", async ({ page }) => { + for (const width of [320, 767]) { + await page.setViewportSize({ width, height: 900 }); + await page.goto(homepage); + + const footer = page.locator(".site-footer"); + const footerBox = await getBox(footer); + const logo = await getBox(footer.locator(".site-footer__logo-image")); + const backToTop = await getBox(footer.locator("#backToTopBtn")); + const nav = await getBox(footer.locator("#footer-nav")); + const contact = await getBox(footer.locator(".contact")); + const social = await getBox(footer.locator(".social-links")); + const copyright = await getBox(footer.locator(".copyright")); + const expectedHeight = Math.min(width, 402) * (673 / 402); + + expectNear(footerBox.height, expectedHeight, 12); + expectNear(logo.y, backToTop.y, 8); + expect(nav.y).toBeGreaterThan(logo.y + logo.height); + expect(contact.y).toBeGreaterThan(nav.y + nav.height); + expect(social.y).toBeGreaterThan(contact.y + contact.height); + expect(copyright.y).toBeGreaterThan(social.y + social.height); + expect(footerBox.x).toBeGreaterThanOrEqual(0); + expect(footerBox.x + footerBox.width).toBeLessThanOrEqual(width); + expect( + await page.evaluate(() => document.documentElement.scrollWidth), + ).toBeLessThanOrEqual(width); + } +}); + +test("footer restores its four-column desktop layout at 768px", async ({ + page, +}) => { + await page.setViewportSize({ width: 768, height: 900 }); + await page.goto(homepage); + + const footer = page.locator(".site-footer"); + const outer = footer.locator(":scope > .container"); + + await expect(footer).toHaveCSS("border-bottom-width", "16px"); + await expect(footer).toHaveCSS("padding-top", "64px"); + await expect(outer).toHaveCSS("grid-template-columns", /.+ .+ .+ .+/); + await expect(outer.locator(":scope > .text-right")).toHaveCSS( + "display", + "flex", + ); +}); + test("mobile homepage has no automated accessibility violations", async ({ page, }) => {