Skip to content

Commit 8119fe7

Browse files
committed
Refactor stacked card carousel animations and add touch support
1 parent 9dd2bb9 commit 8119fe7

3 files changed

Lines changed: 212 additions & 224 deletions

File tree

src/app/features/landing/components/stacked-card-carousel/stacked-card-carousel.component.css

Lines changed: 119 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
}
55

66
.carousel-container {
7+
position: relative;
78
width: 100%;
89
max-width: 36rem; /* 576px */
910
margin: 0 auto;
@@ -14,19 +15,22 @@
1415
position: relative;
1516
height: calc(87px + 26px);
1617
width: 100%;
18+
display: flex;
19+
justify-content: center;
20+
perspective: 1000px;
1721
}
1822

1923
.card {
2024
position: absolute;
2125
width: 16rem;
26+
max-width: 70%; /* Responsive cap */
27+
height: 100%;
2228
background-color: white;
2329
border-radius: 0.5rem;
24-
transition: all 500ms ease-in-out;
30+
transition: all 500ms cubic-bezier(0.25, 0.8, 0.25, 1);
2531
overflow: hidden;
26-
27-
&.animating {
28-
pointer-events: none;
29-
}
32+
cursor: pointer;
33+
will-change: transform, opacity, left, right;
3034

3135
&.new {
3236
--tint: 150;
@@ -47,121 +51,108 @@
4751
}
4852
}
4953

50-
.card-content {
51-
padding: 16px 24px;
52-
height: 100%;
53-
display: flex;
54-
flex-direction: column;
55-
56-
* {
57-
margin: 0;
58-
}
59-
60-
.card-header {
61-
display: flex;
62-
justify-content: space-between;
63-
align-items: center;
64-
margin-bottom: 12px;
65-
66-
> span:first-child {
67-
display: flex;
68-
gap: 0.25rem;
69-
align-items: center;
70-
background-color: hsl(var(--tint), 100%, 86%, 0.5);
71-
color: hsl(var(--tint), 100%, 30%);
72-
border-radius: 4px;
73-
padding: 0.25rem 0.5rem;
74-
font-size: 0.8rem;
75-
font-weight: 500;
76-
}
77-
}
78-
}
79-
80-
:host-context(.dark) .card-header > span:first-child {
81-
background-color: hsl(var(--tint), 100%, 25%, 0.4);
82-
color: hsl(var(--tint), 100%, 95%);
83-
}
84-
85-
/* Card positions */
8654
.center {
87-
left: 50%;
88-
transform: translateX(-50%) scale(1);
89-
top: 0;
90-
width: 18rem;
91-
z-index: 2;
55+
z-index: 10;
9256
opacity: 1;
57+
/* Use translate3d for hardware acceleration */
58+
transform: translateX(0) scale(1);
59+
left: 0;
60+
right: 0;
61+
margin: auto;
62+
width: 18rem;
9363
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
94-
cursor: pointer;
64+
}
9565

96-
&:hover {
97-
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
98-
}
66+
.center:hover {
67+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
9968
}
10069

10170
.left {
102-
left: 0;
103-
transform: translateX(-1.5rem) scale(0.9);
104-
z-index: 1;
71+
z-index: 5;
10572
opacity: 0.7;
106-
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
107-
cursor: pointer;
108-
109-
&:hover {
110-
opacity: 0.9;
111-
}
73+
transform: translateX(-55%) scale(0.9);
74+
left: 0; right: 0; margin: auto; /* Base center */
75+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
11276
}
11377

11478
.right {
115-
right: 0;
116-
transform: translateX(1.5rem) scale(0.9);
117-
z-index: 1;
79+
z-index: 5;
11880
opacity: 0.7;
119-
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
120-
cursor: pointer;
81+
transform: translateX(55%) scale(0.9);
82+
left: 0; right: 0; margin: auto; /* Base center */
83+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
84+
}
12185

122-
&:hover {
123-
opacity: 0.9;
124-
}
86+
:is(.left, .right):hover {
87+
opacity: 0.85;
12588
}
12689

12790
.hidden {
91+
z-index: 0;
12892
opacity: 0;
12993
transform: scale(0.75);
13094
pointer-events: none;
95+
/* Keep them centered so they animate OUT from the center */
96+
left: 0; right: 0; margin: auto;
97+
}
98+
99+
.card-content {
100+
padding: 16px 24px;
101+
height: 100%;
102+
display: flex;
103+
flex-direction: column;
104+
105+
p {
106+
margin: 0;
107+
}
108+
}
109+
110+
.card-header {
111+
display: flex;
112+
justify-content: space-between;
113+
align-items: center;
114+
margin-bottom: 12px;
115+
116+
> span:first-child {
117+
display: flex;
118+
gap: 0.25rem;
119+
align-items: center;
120+
background-color: hsl(var(--tint), 100%, 86%, 0.5);
121+
color: hsl(var(--tint), 100%, 30%);
122+
border-radius: 4px;
123+
padding: 0.25rem 0.5rem;
124+
font-size: 0.8rem;
125+
font-weight: 500;
126+
}
127+
}
128+
129+
:host-context(.dark) .card-header > span:first-child {
130+
background-color: hsl(var(--tint), 100%, 25%, 0.4);
131+
color: hsl(var(--tint), 100%, 95%);
131132
}
132133

133134
/* Navigation buttons */
134135
.nav-button {
135136
position: absolute;
136-
top: calc(87px / 2);
137+
top: 50%;
137138
transform: translateY(-50%);
138139
width: 28px;
139140
height: 28px;
140-
display: flex;
141-
align-items: center;
142-
justify-content: center;
143141
border-radius: 9999px;
144142
background-color: rgba(255, 255, 255, 0.9);
145143
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
146-
z-index: 3;
144+
z-index: 20;
147145
border: none;
148146
cursor: pointer;
149-
transition: all 300ms;
147+
display: flex;
148+
align-items: center;
149+
justify-content: center;
150+
transition: all 200ms;
150151

151152
&:hover {
152153
background-color: white;
153154
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
154155
}
155-
156-
&:focus-visible {
157-
outline: 2px solid #3B82F6;
158-
outline-offset: 2px;
159-
}
160-
161-
&:disabled {
162-
opacity: 0.5;
163-
cursor: not-allowed;
164-
}
165156
}
166157

167158
:host-context(.dark) .nav-button {
@@ -174,57 +165,75 @@
174165
}
175166
}
176167

177-
.prev {
178-
left: -70px;
179-
}
168+
.prev { left: 0; }
169+
.next { right: 0; }
170+
.next svg { transform: rotate(180deg); }
171+
172+
/* Mobile: Hide Buttons, rely on swipe */
173+
@media (max-width: 600px) {
174+
.nav-button {
175+
display: none;
176+
}
180177

181-
.next {
182-
right: -70px;
178+
/* Adjust scaling for small screens so cards don't overlap too much */
179+
.left { transform: translateX(-20%) scale(0.85); opacity: 0.5; }
180+
.right { transform: translateX(20%) scale(0.85); opacity: 0.5; }
183181

184-
svg {
185-
transform: rotate(180deg);
182+
.card-content {
183+
padding: 12px 16px;
184+
font-size: 0.9rem;
186185
}
187186
}
188187

189-
/* Indicators */
188+
/* Desktop: Push buttons outside slightly if there is room */
189+
@media (min-width: 601px) {
190+
.prev { left: -40px; }
191+
.next { right: -40px; }
192+
}
193+
190194
.indicators {
191-
position: absolute;
192-
bottom: 0;
193-
left: 0;
194-
right: 0;
195195
display: flex;
196196
justify-content: center;
197197
gap: 0.5rem;
198-
z-index: 3;
198+
margin-top: 1rem;
199199
}
200200

201201
.indicator {
202-
width: 0.5rem;
203-
height: 0.5rem;
202+
width: 0.6rem;
203+
height: 0.6rem;
204204
border-radius: 9999px;
205205
background-color: rgba(0, 0, 0, 0.25);
206206
border: none;
207-
transition: all 300ms;
208-
padding: 0;
209207
cursor: pointer;
208+
transition: all 300ms cubic-bezier(0.25, 0.8, 0.25, 1);
209+
padding: 0;
210+
position: relative;
211+
overflow: hidden;
210212

211213
&:hover {
212214
background-color: rgba(var(--picton-blue-500), 0.4);
213215
}
214216

215217
&.active {
216-
background-color: rgba(var(--picton-blue-500), 0.75);
217-
width: 1.5rem;
218+
width: 1.75rem; /* Pill shape */
218219
}
220+
}
219221

220-
&:focus-visible {
221-
outline: 2px solid rgba(var(--picton-blue-500), 0.75);
222-
outline-offset: 2px;
223-
}
222+
/* Progress bar fill for active indicator */
223+
.indicator.active::after {
224+
content: '';
225+
position: absolute;
226+
top: 0;
227+
left: 0;
228+
height: 100%;
229+
background-color: rgba(var(--picton-blue-500), 0.75);
230+
width: 0;
231+
animation: fillProgress 5000ms linear forwards;
232+
}
224233

225-
&:disabled {
226-
cursor: not-allowed;
227-
}
234+
@keyframes fillProgress {
235+
from { width: 0; }
236+
to { width: 100%; }
228237
}
229238

230239
:host-context(.dark) .indicator {
@@ -234,7 +243,7 @@
234243
background-color: rgba(255, 255, 255, 0.5);
235244
}
236245

237-
&.active {
246+
&.active::after {
238247
background-color: #91b9ef;
239248
}
240249
}

0 commit comments

Comments
 (0)