Compare commits
14 commits
f8942ba348
...
cac1ca221e
| Author | SHA1 | Date | |
|---|---|---|---|
| cac1ca221e | |||
| 0b972a67e3 | |||
| c58af4b720 | |||
| 6ab22d35d2 | |||
| f1751b6f82 | |||
| 5dd9808ef2 | |||
| 73cd748ec2 | |||
| 316bb05ef7 | |||
| 289ecec44e | |||
| fa70a4a1b7 | |||
| 87012bc236 | |||
| c774c73b26 | |||
| b88fe99b38 | |||
| 3c72cd296e |
2 changed files with 388 additions and 15 deletions
|
|
@ -162,6 +162,220 @@ 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', () => {
|
||||
cy.get('[data-cy="testimonials"]').within(() => {
|
||||
cy.get('[data-cy="testimonial-active"]')
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ interface Testimonial {
|
|||
quote: string
|
||||
}
|
||||
|
||||
type TestimonialPosition = 'active' | 'previous' | 'next' | 'hidden'
|
||||
|
||||
const testimonials: Testimonial[] = [
|
||||
{
|
||||
name: 'Nathaniel M',
|
||||
|
|
@ -41,16 +43,109 @@ const testimonials: Testimonial[] = [
|
|||
name: 'Anonymous Student',
|
||||
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)
|
||||
|
||||
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 {
|
||||
testimonialIndex.value = (testimonialIndex.value + 1) % testimonials.length
|
||||
testimonialIndex.value = nextTestimonialIndex()
|
||||
}
|
||||
|
||||
function previousTestimonial(): void {
|
||||
testimonialIndex.value = (testimonialIndex.value - 1 + testimonials.length) % testimonials.length
|
||||
testimonialIndex.value = previousTestimonialIndex()
|
||||
}
|
||||
|
||||
const newsletterFullName = ref('')
|
||||
|
|
@ -229,16 +324,14 @@ const projects: Project[] = [
|
|||
<section class="testimonials" data-cy="testimonials">
|
||||
<div class="testimonials__inner">
|
||||
<h2 class="testimonials__heading">What People Are Saying</h2>
|
||||
<div class="testimonials__stage">
|
||||
<div class="testimonials__stage" data-cy="testimonials-stage">
|
||||
<div
|
||||
v-for="(testimonial, index) in testimonials"
|
||||
:key="testimonial.name"
|
||||
class="testimonials__card"
|
||||
:class="{
|
||||
'testimonials__card--active': index === testimonialIndex,
|
||||
}"
|
||||
:data-cy="index === testimonialIndex ? 'testimonial-active' : 'testimonial-side'"
|
||||
:aria-hidden="index !== testimonialIndex"
|
||||
:class="testimonialClass(index)"
|
||||
:data-cy="testimonialDataCy(index)"
|
||||
:aria-hidden="testimonialPosition(index) !== 'active'"
|
||||
>
|
||||
<h3 class="testimonials__name">{{ testimonial.name }}</h3>
|
||||
<p class="testimonials__quote">“{{ testimonial.quote }}”</p>
|
||||
|
|
@ -648,10 +741,15 @@ const projects: Project[] = [
|
|||
.testimonials {
|
||||
background: var(--color-cream);
|
||||
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 {
|
||||
max-width: 960px;
|
||||
max-width: var(--testimonials-stage-width);
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -666,7 +764,11 @@ const projects: Project[] = [
|
|||
|
||||
.testimonials__stage {
|
||||
position: relative;
|
||||
min-height: 220px;
|
||||
width: min(100%, var(--testimonials-stage-width));
|
||||
height: var(--testimonials-card-height);
|
||||
border-radius: 14px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials__card {
|
||||
|
|
@ -674,18 +776,50 @@ const projects: Project[] = [
|
|||
border-radius: 14px;
|
||||
padding: 2rem;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
width: calc(50% - var(--testimonials-card-gap));
|
||||
height: var(--testimonials-card-height);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
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 {
|
||||
opacity: 1;
|
||||
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 {
|
||||
|
|
@ -709,7 +843,7 @@ const projects: Project[] = [
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 2rem;
|
||||
margin-top: var(--testimonials-controls-gap);
|
||||
}
|
||||
|
||||
.testimonials__arrow {
|
||||
|
|
@ -1050,6 +1184,10 @@ const projects: Project[] = [
|
|||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.testimonials {
|
||||
--testimonials-card-height: 320px;
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
|
|
@ -1069,6 +1207,27 @@ const projects: Project[] = [
|
|||
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 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue