Compare commits

..

No commits in common. "cac1ca221eb38d6f9465a43d7ad7990b1268cff1" and "f8942ba34888603e1ba943a01398370e3d747b03" have entirely different histories.

2 changed files with 15 additions and 388 deletions

View file

@ -162,220 +162,6 @@ describe('homepage testimonials carousel', () => {
}) })
}) })
it('shows faded half-card previews beside the active testimonial', () => {
cy.viewport(1200, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-active"]').as('activeCard')
cy.get('[data-cy="testimonial-previous-preview"]').as(
'previousPreview',
)
cy.get('[data-cy="testimonial-next-preview"]').as('nextPreview')
cy.get('[data-cy="testimonials-stage"]').as('testimonialStage')
})
cy.get('@activeCard').should('be.visible')
cy.get('@previousPreview').should('be.visible')
cy.get('@nextPreview').should('be.visible')
cy.get('@activeCard').then((activeCardElements) => {
const activeOpacity = Number(
getComputedStyle(activeCardElements[0]).opacity,
)
cy.get('@previousPreview').then((previousPreviewElements) => {
const previousPreview = previousPreviewElements[0]
const previousOpacity = Number(
getComputedStyle(previousPreview).opacity,
)
cy.wrap(previousOpacity).should('be.lessThan', activeOpacity)
})
cy.get('@nextPreview').then((nextPreviewElements) => {
const nextPreview = nextPreviewElements[0]
const nextOpacity = Number(getComputedStyle(nextPreview).opacity)
cy.wrap(nextOpacity).should('be.lessThan', activeOpacity)
})
})
cy.get('@testimonialStage').then((testimonialStageElements) => {
const stageBounds = testimonialStageElements[0].getBoundingClientRect()
cy.get('@previousPreview').then((previousPreviewElements) => {
const previousBounds =
previousPreviewElements[0].getBoundingClientRect()
const previousVisibleWidth = previousBounds.right - stageBounds.left
cy.wrap(previousVisibleWidth).should(
'be.closeTo',
previousBounds.width / 2,
1,
)
})
cy.get('@nextPreview').then((nextPreviewElements) => {
const nextBounds = nextPreviewElements[0].getBoundingClientRect()
const nextVisibleWidth = stageBounds.right - nextBounds.left
cy.wrap(nextVisibleWidth).should(
'be.closeTo',
nextBounds.width / 2,
1,
)
})
})
})
it('keeps desktop cards equal sized and separated', () => {
const expectedCardGap = 24
const expectedControlsGap = 12
cy.viewport(1200, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-active"]').as('activeCard')
cy.get('[data-cy="testimonial-previous-preview"]').as(
'previousPreview',
)
cy.get('[data-cy="testimonial-next-preview"]').as('nextPreview')
cy.get('[data-cy="testimonial-prev"]')
.parent()
.as('testimonialControls')
})
cy.get('@activeCard').then((activeCardElements) => {
const activeBounds = activeCardElements[0].getBoundingClientRect()
cy.get('@previousPreview').then((previousPreviewElements) => {
const previousBounds =
previousPreviewElements[0].getBoundingClientRect()
const previousGap = activeBounds.left - previousBounds.right
cy.wrap(previousBounds.height).should(
'be.closeTo',
activeBounds.height,
1,
)
cy.wrap(previousGap).should('be.closeTo', expectedCardGap, 1)
})
cy.get('@nextPreview').then((nextPreviewElements) => {
const nextBounds = nextPreviewElements[0].getBoundingClientRect()
const nextGap = nextBounds.left - activeBounds.right
cy.wrap(nextBounds.height).should(
'be.closeTo',
activeBounds.height,
1,
)
cy.wrap(nextGap).should('be.closeTo', expectedCardGap, 1)
})
cy.get('@testimonialControls').then((testimonialControlsElements) => {
const controlsBounds =
testimonialControlsElements[0].getBoundingClientRect()
const controlsGap = controlsBounds.top - activeBounds.bottom
cy.wrap(controlsGap).should(
'be.closeTo',
expectedControlsGap,
1,
)
})
})
})
it('rounds the outer corners of clipped preview cards', () => {
cy.viewport(1200, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonials-stage"]').as('testimonialStage')
cy.get('[data-cy="testimonial-active"]').as('activeCard')
})
cy.get('@activeCard').then((activeCardElements) => {
const activeCardStyle = getComputedStyle(activeCardElements[0])
cy.get('@testimonialStage').then((testimonialStageElements) => {
const testimonialStageStyle = getComputedStyle(
testimonialStageElements[0],
)
expect(testimonialStageStyle.borderTopLeftRadius).to.equal(
activeCardStyle.borderTopLeftRadius,
)
expect(testimonialStageStyle.borderTopRightRadius).to.equal(
activeCardStyle.borderTopRightRadius,
)
expect(testimonialStageStyle.borderBottomLeftRadius).to.equal(
activeCardStyle.borderBottomLeftRadius,
)
expect(testimonialStageStyle.borderBottomRightRadius).to.equal(
activeCardStyle.borderBottomRightRadius,
)
})
})
})
it('brings entering side previews in from the clicked direction', () => {
const transitionSampleDelay = 100
const transitionSettleDelay = 1100
cy.viewport(1200, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-prev"]').click()
cy.get('[data-cy="testimonial-previous-preview"]').as(
'enteringPreviousPreview',
)
})
cy.wait(transitionSampleDelay)
cy.get('@enteringPreviousPreview').then((previousPreviewElements) => {
const earlyPreviousLeft =
previousPreviewElements[0].getBoundingClientRect().left
cy.wait(transitionSettleDelay)
cy.get('@enteringPreviousPreview').then((finalPreviewElements) => {
const finalPreviousLeft =
finalPreviewElements[0].getBoundingClientRect().left
expect(earlyPreviousLeft).to.be.lessThan(finalPreviousLeft)
})
})
cy.visit('/')
cy.viewport(1200, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-next"]').click()
cy.get('[data-cy="testimonial-next-preview"]').as(
'enteringNextPreview',
)
})
cy.wait(transitionSampleDelay)
cy.get('@enteringNextPreview').then((nextPreviewElements) => {
const earlyNextLeft =
nextPreviewElements[0].getBoundingClientRect().left
cy.wait(transitionSettleDelay)
cy.get('@enteringNextPreview').then((finalPreviewElements) => {
const finalNextLeft =
finalPreviewElements[0].getBoundingClientRect().left
expect(earlyNextLeft).to.be.greaterThan(finalNextLeft)
})
})
})
it('hides side previews on narrow screens', () => {
cy.viewport(390, 800)
cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-active"]').should('be.visible')
cy.get('[data-cy="testimonial-previous-preview"]')
.should('not.be.visible')
cy.get('[data-cy="testimonial-next-preview"]').should('not.be.visible')
})
})
it('advances to the next testimonial when next is clicked', () => { it('advances to the next testimonial when next is clicked', () => {
cy.get('[data-cy="testimonials"]').within(() => { cy.get('[data-cy="testimonials"]').within(() => {
cy.get('[data-cy="testimonial-active"]') cy.get('[data-cy="testimonial-active"]')

View file

@ -23,8 +23,6 @@ interface Testimonial {
quote: string quote: string
} }
type TestimonialPosition = 'active' | 'previous' | 'next' | 'hidden'
const testimonials: Testimonial[] = [ const testimonials: Testimonial[] = [
{ {
name: 'Nathaniel M', name: 'Nathaniel M',
@ -43,109 +41,16 @@ const testimonials: Testimonial[] = [
name: 'Anonymous Student', name: 'Anonymous Student',
quote: 'My understanding of Torah has been profoundly improved through ' + 'his teachings.', quote: 'My understanding of Torah has been profoundly improved through ' + 'his teachings.',
}, },
{
name: 'Placeholder Student 1',
quote:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' +
'eiusmod tempor incididunt ut labore et dolore magna aliqua.',
},
{
name: 'Placeholder Student 2',
quote:
'Ut enim ad minim veniam, quis nostrud exercitation ullamco ' +
'laboris nisi ut aliquip ex ea commodo consequat.',
},
{
name: 'Placeholder Student 3',
quote:
'Duis aute irure dolor in reprehenderit in voluptate velit esse ' +
'cillum dolore eu fugiat nulla pariatur.',
},
] ]
const testimonialIndex = ref(0) const testimonialIndex = ref(0)
function testimonialOffset(index: number): number {
const relativeIndex = index - testimonialIndex.value + testimonials.length
return relativeIndex % testimonials.length
}
function previousTestimonialIndex(): number {
if (testimonialIndex.value === 0) {
return testimonials.length - 1
}
return testimonialIndex.value - 1
}
function nextTestimonialIndex(): number {
return (testimonialIndex.value + 1) % testimonials.length
}
function testimonialPosition(index: number): TestimonialPosition {
const offset = testimonialOffset(index)
if (offset === 0) {
return 'active'
}
if (offset === testimonials.length - 1) {
return 'previous'
}
if (offset === 1) {
return 'next'
}
return 'hidden'
}
function testimonialDataCy(index: number): string {
const position = testimonialPosition(index)
if (position === 'active') {
return 'testimonial-active'
}
if (position === 'previous') {
return 'testimonial-previous-preview'
}
if (position === 'next') {
return 'testimonial-next-preview'
}
return 'testimonial-hidden'
}
function testimonialHiddenClass(index: number): string {
const offset = testimonialOffset(index)
if (offset < testimonials.length / 2) {
return 'testimonials__card--hidden-next'
}
return 'testimonials__card--hidden-previous'
}
function testimonialClass(index: number): string[] {
const position = testimonialPosition(index)
const positionClass = `testimonials__card--${position}`
if (position === 'hidden') {
return [positionClass, testimonialHiddenClass(index)]
}
return [positionClass]
}
function nextTestimonial(): void { function nextTestimonial(): void {
testimonialIndex.value = nextTestimonialIndex() testimonialIndex.value = (testimonialIndex.value + 1) % testimonials.length
} }
function previousTestimonial(): void { function previousTestimonial(): void {
testimonialIndex.value = previousTestimonialIndex() testimonialIndex.value = (testimonialIndex.value - 1 + testimonials.length) % testimonials.length
} }
const newsletterFullName = ref('') const newsletterFullName = ref('')
@ -324,14 +229,16 @@ const projects: Project[] = [
<section class="testimonials" data-cy="testimonials"> <section class="testimonials" data-cy="testimonials">
<div class="testimonials__inner"> <div class="testimonials__inner">
<h2 class="testimonials__heading">What People Are Saying</h2> <h2 class="testimonials__heading">What People Are Saying</h2>
<div class="testimonials__stage" data-cy="testimonials-stage"> <div class="testimonials__stage">
<div <div
v-for="(testimonial, index) in testimonials" v-for="(testimonial, index) in testimonials"
:key="testimonial.name" :key="testimonial.name"
class="testimonials__card" class="testimonials__card"
:class="testimonialClass(index)" :class="{
:data-cy="testimonialDataCy(index)" 'testimonials__card--active': index === testimonialIndex,
:aria-hidden="testimonialPosition(index) !== 'active'" }"
:data-cy="index === testimonialIndex ? 'testimonial-active' : 'testimonial-side'"
:aria-hidden="index !== testimonialIndex"
> >
<h3 class="testimonials__name">{{ testimonial.name }}</h3> <h3 class="testimonials__name">{{ testimonial.name }}</h3>
<p class="testimonials__quote">&ldquo;{{ testimonial.quote }}&rdquo;</p> <p class="testimonials__quote">&ldquo;{{ testimonial.quote }}&rdquo;</p>
@ -741,15 +648,10 @@ const projects: Project[] = [
.testimonials { .testimonials {
background: var(--color-cream); background: var(--color-cream);
padding: 5rem 2rem; padding: 5rem 2rem;
--testimonials-card-entry-gap: 48px;
--testimonials-card-gap: 24px;
--testimonials-card-height: 220px;
--testimonials-controls-gap: 0.75rem;
--testimonials-stage-width: 1008px;
} }
.testimonials__inner { .testimonials__inner {
max-width: var(--testimonials-stage-width); max-width: 960px;
margin: 0 auto; margin: 0 auto;
text-align: center; text-align: center;
} }
@ -764,11 +666,7 @@ const projects: Project[] = [
.testimonials__stage { .testimonials__stage {
position: relative; position: relative;
width: min(100%, var(--testimonials-stage-width)); min-height: 220px;
height: var(--testimonials-card-height);
border-radius: 14px;
margin: 0 auto;
overflow: hidden;
} }
.testimonials__card { .testimonials__card {
@ -776,50 +674,18 @@ const projects: Project[] = [
border-radius: 14px; border-radius: 14px;
padding: 2rem; padding: 2rem;
text-align: left; text-align: left;
width: calc(50% - var(--testimonials-card-gap)); max-width: 480px;
height: var(--testimonials-card-height); margin: 0 auto;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
top: 0; inset: 0;
left: 50%; transition: opacity 0.3s ease;
margin: 0;
box-sizing: border-box;
pointer-events: none;
transform: translateX(-50%);
transition:
opacity 1s cubic-bezier(0.22, 1, 0.36, 1),
transform 1s cubic-bezier(0.22, 1, 0.36, 1);
} }
.testimonials__card--active { .testimonials__card--active {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
z-index: 2;
transform: translateX(-50%);
}
.testimonials__card--previous,
.testimonials__card--next {
opacity: 0.35;
visibility: visible;
z-index: 1;
}
.testimonials__card--previous {
transform: translateX(calc(-150% - var(--testimonials-card-gap)));
}
.testimonials__card--next {
transform: translateX(calc(50% + var(--testimonials-card-gap)));
}
.testimonials__card--hidden-previous {
transform: translateX(calc(-250% - var(--testimonials-card-entry-gap)));
}
.testimonials__card--hidden-next {
transform: translateX(calc(150% + var(--testimonials-card-entry-gap)));
} }
.testimonials__name { .testimonials__name {
@ -843,7 +709,7 @@ const projects: Project[] = [
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 0.75rem; gap: 0.75rem;
margin-top: var(--testimonials-controls-gap); margin-top: 2rem;
} }
.testimonials__arrow { .testimonials__arrow {
@ -1184,10 +1050,6 @@ const projects: Project[] = [
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.testimonials {
--testimonials-card-height: 320px;
}
.hero__content { .hero__content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
text-align: center; text-align: center;
@ -1207,27 +1069,6 @@ const projects: Project[] = [
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.testimonials__stage {
width: min(100%, 480px);
}
.testimonials__card {
width: 100%;
left: 0;
transform: none;
}
.testimonials__card--active {
transform: none;
}
.testimonials__card--previous,
.testimonials__card--next {
opacity: 0;
visibility: hidden;
transform: none;
}
.journey__cards { .journey__cards {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }