Merge branch 'feature/home-org-carousel'

This commit is contained in:
Yisroel Baum 2026-06-13 21:49:42 +03:00
commit 5014193484
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
2 changed files with 161 additions and 18 deletions

View file

@ -401,11 +401,10 @@ describe('homepage sponsor strip', () => {
cy.visit('/') cy.visit('/')
}) })
it('renders the heading and all seven partner logos', () => { it('renders the heading and all seven partner logos in a carousel', () => {
cy.get('[data-cy="sponsors"]').within(() => { cy.get('[data-cy="sponsors"]').within(() => {
cy.contains('h2', 'Organizations Rabbi Gerzi has Worked With') cy.contains('h2', 'Organizations Rabbi Gerzi has Worked With')
.should('be.visible') .should('be.visible')
cy.get('[data-cy="sponsor-logo"]').should('have.length', 7)
const expectedAlts = [ const expectedAlts = [
'SHARE', 'SHARE',
'U of Israel', 'U of Israel',
@ -415,9 +414,67 @@ describe('homepage sponsor strip', () => {
'Aish HaTorah', 'Aish HaTorah',
'Yeshivat Lev HaTorah', 'Yeshivat Lev HaTorah',
] ]
cy.get('[data-cy="sponsor-logo"]').should('have.length', 7)
expectedAlts.forEach((altText) => { expectedAlts.forEach((altText) => {
cy.get(`[data-cy="sponsor-logo"][alt="${altText}"]`) cy.get(`[data-cy="sponsor-logo"][alt="${altText}"]`)
.should('be.visible') .should('have.attr', 'src')
})
cy.get('[data-cy="sponsor-carousel"]').should('be.visible')
cy.get('[data-cy="sponsor-logo-duplicate"]')
.should('have.length', 7)
cy.get('[data-cy="sponsor-logo-duplicate"][alt=""]')
.should('have.length', 7)
cy.get('[data-cy="sponsor-logo-group-duplicate"]')
.should('have.attr', 'aria-hidden', 'true')
})
})
it('centers the carousel with side buffers and edge fades', () => {
cy.viewport(1200, 900)
cy.get('[data-cy="sponsors"]').within(() => {
cy.get('[data-cy="sponsor-carousel"]').then((carouselElements) => {
const carouselElement = carouselElements[0]
const sponsorInnerElement = carouselElement.parentElement
if (sponsorInnerElement === null) {
throw new Error('Sponsor inner element is missing')
}
const carouselBounds = carouselElement.getBoundingClientRect()
const sponsorInnerBounds =
sponsorInnerElement.getBoundingClientRect()
const leftBuffer = carouselBounds.left - sponsorInnerBounds.left
const rightBuffer = sponsorInnerBounds.right - carouselBounds.right
const carouselRatio = carouselBounds.width / sponsorInnerBounds.width
const carouselStyle = getComputedStyle(carouselElement)
const maskImage =
carouselStyle.maskImage || carouselStyle.webkitMaskImage
expect(carouselRatio).to.be.closeTo(0.85, 0.02)
expect(leftBuffer).to.be.closeTo(rightBuffer, 1)
expect(maskImage).to.contain('50px')
})
})
})
it('uses the faster carousel timing', () => {
cy.get('[data-cy="sponsor-track"]').then((trackElements) => {
const trackStyle = getComputedStyle(trackElements[0])
expect(trackStyle.animationDuration).to.equal('20s')
})
})
it('scrolls the sponsor logos from right to left', () => {
cy.get('[data-cy="sponsor-track"]').then((trackElements) => {
const startingLeft = trackElements[0].getBoundingClientRect().left
cy.wait(750)
cy.get('[data-cy="sponsor-track"]').then((laterTrackElements) => {
const laterLeft =
laterTrackElements[0].getBoundingClientRect().left
expect(startingLeft).to.be.greaterThan(laterLeft)
}) })
}) })
}) })

View file

@ -173,6 +173,32 @@ const sponsors: Sponsor[] = [
{ name: 'Yeshivat Lev HaTorah', src: '/assets/levhatorah.png' }, { name: 'Yeshivat Lev HaTorah', src: '/assets/levhatorah.png' },
] ]
const sponsorGroups: Sponsor[][] = [sponsors, sponsors]
function sponsorGroupDataCy(sponsorGroupIndex: number): string {
if (sponsorGroupIndex === 0) {
return 'sponsor-logo-group'
}
return 'sponsor-logo-group-duplicate'
}
function sponsorLogoAlt(sponsor: Sponsor, sponsorGroupIndex: number): string {
if (sponsorGroupIndex === 0) {
return sponsor.name
}
return ''
}
function sponsorLogoDataCy(sponsorGroupIndex: number): string {
if (sponsorGroupIndex === 0) {
return 'sponsor-logo'
}
return 'sponsor-logo-duplicate'
}
interface Project { interface Project {
title: string title: string
description: string description: string
@ -363,17 +389,27 @@ const projects: Project[] = [
<section class="sponsors" data-cy="sponsors"> <section class="sponsors" data-cy="sponsors">
<div class="sponsors__inner"> <div class="sponsors__inner">
<h2 class="sponsors__heading">Organizations Rabbi Gerzi has Worked With</h2> <h2 class="sponsors__heading">Organizations Rabbi Gerzi has Worked With</h2>
<div class="sponsors__row"> <div class="sponsors__carousel" data-cy="sponsor-carousel">
<div class="sponsors__track" data-cy="sponsor-track">
<div
v-for="(sponsorGroup, sponsorGroupIndex) in sponsorGroups"
:key="`sponsor-group-${sponsorGroupIndex}`"
class="sponsors__group"
:aria-hidden="sponsorGroupIndex === 1 ? 'true' : undefined"
:data-cy="sponsorGroupDataCy(sponsorGroupIndex)"
>
<img <img
v-for="sponsor in sponsors" v-for="sponsor in sponsorGroup"
:key="sponsor.name" :key="`${sponsorGroupIndex}-${sponsor.name}`"
:src="sponsor.src" :src="sponsor.src"
:alt="sponsor.name" :alt="sponsorLogoAlt(sponsor, sponsorGroupIndex)"
class="sponsors__logo" class="sponsors__logo"
data-cy="sponsor-logo" :data-cy="sponsorLogoDataCy(sponsorGroupIndex)"
/> />
</div> </div>
</div> </div>
</div>
</div>
</section> </section>
<section class="projects" data-cy="projects"> <section class="projects" data-cy="projects">
@ -883,21 +919,62 @@ const projects: Project[] = [
margin: 0 0 3rem 0; margin: 0 0 3rem 0;
} }
.sponsors__row { .sponsors__carousel {
--sponsors-edge-fade: 50px;
--sponsors-logo-gap: clamp(2.75rem, 4vw, 4.25rem);
width: 85%;
margin: 0 auto;
overflow: hidden;
mask-image: linear-gradient(
to right,
transparent,
black var(--sponsors-edge-fade),
black calc(100% - var(--sponsors-edge-fade)),
transparent
);
-webkit-mask-image: linear-gradient(
to right,
transparent,
black var(--sponsors-edge-fade),
black calc(100% - var(--sponsors-edge-fade)),
transparent
);
}
.sponsors__track {
display: flex; display: flex;
flex-wrap: wrap; width: max-content;
animation: sponsors-scroll 20s linear infinite;
will-change: transform;
}
.sponsors__group {
display: flex;
flex: 0 0 auto;
align-items: center; align-items: center;
justify-content: center; gap: var(--sponsors-logo-gap);
gap: 2.5rem 3rem; padding-right: var(--sponsors-logo-gap);
} }
.sponsors__logo { .sponsors__logo {
height: 60px; height: clamp(78px, 7vw, 96px);
width: auto; width: auto;
max-width: 160px; max-width: 220px;
flex: 0 0 auto;
object-fit: contain; object-fit: contain;
} }
@keyframes sponsors-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.projects { .projects {
background: var(--color-cream); background: var(--color-cream);
padding: 5rem 2rem; padding: 5rem 2rem;
@ -1228,6 +1305,15 @@ const projects: Project[] = [
transform: none; transform: none;
} }
.sponsors__carousel {
width: min(100%, 680px);
}
.sponsors__logo {
height: clamp(68px, 18vw, 86px);
max-width: 190px;
}
.journey__cards { .journey__cards {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }