diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index a3e50b8f64fe..16b90f4c9fd6 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -1015,7 +1015,7 @@ test.describe('LandingCarousel component', () => { // Check that article cards are present const items = page.locator('[data-testid="carousel-items"]') - const cards = items.locator('div') + const cards = items.locator('a') await expect(cards.first()).toBeVisible() // Verify cards have real titles (not "Unknown Article" when article not found) @@ -1190,7 +1190,7 @@ test.describe('LandingArticleGridWithFilter component', () => { await expect(articleCards.first()).toBeVisible() const firstCard = articleCards.first() - const titleLink = firstCard.locator('h3 a') + const titleLink = firstCard.locator('h3 span') await expect(titleLink).toBeVisible() const intro = firstCard.locator('div').last() // cardIntro is the last div diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.module.scss b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss index ffac7be779dc..7facfe23f6ac 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.module.scss +++ b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss @@ -24,6 +24,19 @@ box-shadow: 0 0.0625rem 0.1875rem 0 rgba(31, 35, 40, 0.08), 0 0.0625rem 0 0 rgba(31, 35, 40, 0.06); + transition: all 0.2s ease-in-out; + cursor: pointer; + text-decoration: none !important; + color: inherit; + + &:hover { + box-shadow: + 0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12), + 0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08); + transform: translateY(-2px); + background-color: var(--bgColor-muted, var(--color-canvas-subtle)); + text-decoration: none !important; + } } .cardHeader { @@ -40,10 +53,6 @@ .cardTitleLink { color: var(--fgColor-accent); text-decoration: none; - - &:hover { - text-decoration: underline; - } } .cardIntro { @@ -51,6 +60,7 @@ color: var(--fgColor-muted); font-size: 0.9rem; line-height: 1.4; + text-decoration: none !important; } .tagsContainer { @@ -145,6 +155,10 @@ margin-left: 0; width: auto; + input { + font-size: 1rem; + } + // Medium screens: flexible width with spacing @include breakpoint(md) { flex: 0 1 25%; diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx index 4b57ef1485f0..1009ce4bdcbd 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.tsx +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -51,6 +51,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { const articlesPerPage = useResponsiveArticlesPerPage() const inputRef = useRef(null) + const headingRef = useRef(null) // Reset to first page when articlesPerPage changes (screen size changes) useEffect(() => { @@ -112,6 +113,14 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { e.preventDefault() if (pageNumber >= 1 && pageNumber <= totalPages) { setCurrentPage(pageNumber) + if (headingRef.current) { + const elementPosition = headingRef.current.getBoundingClientRect().top + window.scrollY + const offsetPosition = elementPosition - 140 // 140px offset from top + window.scrollTo({ + top: offsetPosition, + behavior: 'smooth', + }) + } } } @@ -122,7 +131,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { {/* Title and Dropdown Row */}
{/* Title */} -

+

{t('article_grid.heading')}

@@ -156,7 +165,6 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
e.preventDefault()}> { return ( -
{

- - {article.title} - + {article.title}

{article.intro &&
{article.intro}
} -
+ ) } diff --git a/src/landings/components/shared/LandingCarousel.module.scss b/src/landings/components/shared/LandingCarousel.module.scss index 6b8e7b71223e..39d29d59f513 100644 --- a/src/landings/components/shared/LandingCarousel.module.scss +++ b/src/landings/components/shared/LandingCarousel.module.scss @@ -1,6 +1,6 @@ .carousel { margin-top: 3rem; - --carousel-transition-duration: 0.3s; + --carousel-transition-duration: 0.1s; } .header { @@ -44,7 +44,7 @@ display: grid; gap: 1.5rem; grid-template-columns: 1fr; - transition: opacity var(--carousel-transition-duration) ease-out; + transition: opacity var(--carousel-transition-duration) ease-in-out; opacity: 1; @media (min-width: 768px) { @@ -56,7 +56,7 @@ } &.animating { - opacity: 0; + opacity: 0.3; } } @@ -68,6 +68,19 @@ box-shadow: 0px 1px 3px 0px rgba(31, 35, 40, 0.08), 0px 1px 0px 0px rgba(31, 35, 40, 0.06); + transition: all 0.2s ease-in-out; + cursor: pointer; + text-decoration: none !important; + color: inherit; + + &:hover { + box-shadow: + 0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12), + 0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08); + transform: translateY(-2px); + background-color: var(--bgColor-muted, var(--color-canvas-subtle)); + text-decoration: none !important; + } } .articleTitle { @@ -78,10 +91,6 @@ .articleLink { color: var(--fgColor-accent); text-decoration: none; - - &:hover { - text-decoration: underline; - } } .articleDescription { @@ -89,6 +98,7 @@ color: var(--fgColor-muted); font-size: 0.9rem; line-height: 1.4; + text-decoration: none !important; } .pagination { diff --git a/src/landings/components/shared/LandingCarousel.tsx b/src/landings/components/shared/LandingCarousel.tsx index 4a4c81ff1421..f779d1488aba 100644 --- a/src/landings/components/shared/LandingCarousel.tsx +++ b/src/landings/components/shared/LandingCarousel.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useRef } from 'react' import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react' -import { Token } from '@primer/react' import cx from 'classnames' import type { ResolvedArticle } from '@/types' import { useTranslation } from '@/languages/components/useTranslation' @@ -78,11 +77,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr setCurrentPage((prev) => Math.max(0, prev - 1)) // Set animation state to false after transition completes - // Duration matches CSS custom property --carousel-transition-duration (300ms) + // Duration matches CSS custom property --carousel-transition-duration (100ms) animationTimeoutRef.current = setTimeout(() => { setIsAnimating(false) animationTimeoutRef.current = null - }, 300) + }, 100) } const goToNext = () => { @@ -97,11 +96,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr setCurrentPage((prev) => Math.min(totalPages - 1, prev + 1)) // Set animation state to false after transition completes - // Duration matches CSS custom property --carousel-transition-duration (300ms) + // Duration matches CSS custom property --carousel-transition-duration (100ms) animationTimeoutRef.current = setTimeout(() => { setIsAnimating(false) animationTimeoutRef.current = null - }, 300) + }, 100) } // Calculate the start index based on current page @@ -144,19 +143,13 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr data-testid="carousel-items" > {visibleItems.map((article: ResolvedArticle, index) => ( -
-
- {article.category.map((cat: string) => ( - - ))} -

- - {article.title} - + {article.title}

-
+ ))}
diff --git a/src/landings/components/shared/LandingHero.module.scss b/src/landings/components/shared/LandingHero.module.scss index 9068b7d93e4c..69a36ab098d4 100644 --- a/src/landings/components/shared/LandingHero.module.scss +++ b/src/landings/components/shared/LandingHero.module.scss @@ -98,6 +98,14 @@ .heroSecondaryAction { background-color: transparent; color: var(--fgColor-default, var(--color-fg-default, #1f2328)); + + @media (max-width: 865px) { + background-color: var( + --bgColor-muted, + var(--color-canvas-subtle, #f6f8fa) + ) !important; + } + border-color: var( --borderColor-default, var(--color-border-default, #d1d9e0) @@ -121,18 +129,37 @@ @media (max-width: 865px) { .landingHero { - height: 24rem; - background-image: none !important; + height: 28rem; flex-direction: column; text-align: center; justify-content: center; background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa)); + position: relative; + + background-position: unset; + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: inherit; + background-size: contain; + background-position: bottom; + background-repeat: no-repeat; + opacity: 0.5; + z-index: 0; + } } .heroContent { width: 100%; order: 2; padding: 0 1rem; + position: relative; + z-index: 1; } .heroHeading { @@ -144,7 +171,11 @@ } .heroActions { + flex-direction: column; + align-self: baseline; + margin-left: 5rem; justify-content: center; + align-items: center; } .heroAction { @@ -162,19 +193,36 @@ @media (max-width: 480px) { .landingHero { - height: 32rem; - background-image: none !important; + height: 28rem; background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa)); flex-direction: column; text-align: center; - padding: 2rem 0 1rem; + padding: 1rem 0 1rem; justify-content: center; + position: relative; + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: inherit; + background-size: contain; + background-position: bottom; + background-repeat: no-repeat; + opacity: 0.8; + z-index: 0; + } } .heroContent { width: 100%; order: 2; - padding: 1rem; + padding: 0 1rem; + position: relative; + z-index: 1; } .heroText { @@ -184,8 +232,27 @@ align-items: center; } + .heroHeading { + font-size: 2.5rem; + line-height: 1; + } + .heroActions { + flex-direction: column; + align-self: baseline; + margin-left: 2rem; justify-content: center; align-items: center; } + + .heroAction { + min-width: 9rem; + padding: 0.5rem 1rem; + } + + .heroDescription { + font-size: 1rem; + max-height: 14rem; + text-overflow: ellipsis; + } }