|
1 | 1 | .testimonial-card { |
2 | 2 | max-width: 696px; |
| 3 | + position: relative; |
| 4 | + padding: var(--space-large) 0 !important; |
| 5 | + overflow: hidden; |
3 | 6 | } |
4 | 7 |
|
5 | 8 | .testimonial-card__carousel { |
|
37 | 40 | fill: var(--color-surface-strong); |
38 | 41 | } |
39 | 42 |
|
| 43 | +/* Suppress the default outline on the link itself — it stretches the full |
| 44 | + slide width and would bleed into the neighboring slide. Render the focus |
| 45 | + ring on the bubble with a negative offset so it stays inside the bubble. */ |
| 46 | +.testimonial-card__quote-link:focus-visible { |
| 47 | + outline: none; |
| 48 | +} |
| 49 | + |
| 50 | +.testimonial-card__quote-link:focus-visible .testimonial-card__text-wrapper { |
| 51 | + outline: 2px solid var(--color-stroke-link-accent); |
| 52 | + outline-offset: -2px; |
| 53 | +} |
| 54 | + |
40 | 55 | .testimonial-card__text { |
41 | 56 | color: var(--color-text-primary, #050816); |
42 | 57 | font-family: var(--font-sans); |
43 | 58 | font-size: var(--font-size-xl, 32px); |
44 | 59 | font-weight: var(--font-weight-regular); |
45 | 60 | line-height: var(--line-height-tight); |
46 | 61 | letter-spacing: var(--letter-spacing-tight); |
| 62 | + |
| 63 | + /* Cap at 3 lines and truncate with an ellipsis on overflow. */ |
| 64 | + display: -webkit-box; |
| 65 | + -webkit-box-orient: vertical; |
| 66 | + -webkit-line-clamp: 3; |
| 67 | + line-clamp: 3; |
| 68 | + overflow: hidden; |
| 69 | + text-overflow: ellipsis; |
47 | 70 | } |
48 | 71 |
|
49 | 72 | .testimonial-card__text-tail { |
|
113 | 136 | display: flex; |
114 | 137 | flex-direction: row; |
115 | 138 | flex-wrap: nowrap; |
116 | | - gap: var(--space-card); |
117 | | - overflow-x: auto; |
118 | | - overflow-y: hidden; |
119 | | - -webkit-overflow-scrolling: touch; |
120 | 139 | min-width: 0; |
121 | | - -ms-overflow-style: none; |
122 | | - scrollbar-width: none; |
| 140 | + transition: transform 0.3s ease; |
| 141 | + will-change: transform; |
123 | 142 | } |
124 | 143 |
|
125 | 144 | .testimonial-card__list-item { |
126 | 145 | list-style: none; |
127 | | - flex: 0 0 auto; |
| 146 | + flex: 0 0 100%; |
128 | 147 | min-width: 0; |
129 | 148 | max-width: 100%; |
130 | 149 | border: none; |
131 | 150 | padding: 0; |
| 151 | + box-sizing: border-box; |
| 152 | + |
| 153 | + /* Hide off-screen slides from the tab order and accessibility tree. |
| 154 | + Delayed transition keeps the outgoing slide visible during the |
| 155 | + 0.3s carousel slide animation. The active slide overrides this |
| 156 | + in the inline <style> block emitted per-testimonial. */ |
| 157 | + visibility: hidden; |
| 158 | + transition: visibility 0s linear 0.3s; |
| 159 | +} |
| 160 | + |
| 161 | +@media (prefers-reduced-motion: reduce) { |
| 162 | + .testimonial-card__list, |
| 163 | + .testimonial-card__list-item { |
| 164 | + transition: none; |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +/* ============================================ |
| 169 | + CSS-only carousel state — driven by hidden radios. |
| 170 | + The radios are at the start of .testimonial-card; sibling selectors |
| 171 | + translate the track and toggle nav/dot active states accordingly. |
| 172 | + ============================================ */ |
| 173 | + |
| 174 | +/* Hide the radios visually but keep them keyboard-accessible. */ |
| 175 | +.testimonial-card__radio { |
| 176 | + position: absolute; |
| 177 | + width: 1px; |
| 178 | + height: 1px; |
| 179 | + padding: 0; |
| 180 | + margin: -1px; |
| 181 | + overflow: hidden; |
| 182 | + clip: rect(0, 0, 0, 0); |
| 183 | + white-space: nowrap; |
| 184 | + border: 0; |
| 185 | +} |
| 186 | + |
| 187 | +/* All per-state rules (track translation, active nav block, active dot, |
| 188 | + active CTA, focus ring) are emitted by the Django template via an inline |
| 189 | + <style> block in `_testimonial_card.html`, so they scale with the actual |
| 190 | + testimonial count instead of being hardcoded. */ |
| 191 | + |
| 192 | +/* Per-state prev/next nav blocks are hidden by default; the inline <style> |
| 193 | + block reveals only the one matching the active radio. */ |
| 194 | +.testimonial-card__nav { |
| 195 | + display: none; |
| 196 | +} |
| 197 | + |
| 198 | +/* The carousel-buttons labels need cursor:pointer because <label> isn't |
| 199 | + a button by default. */ |
| 200 | +.testimonial-card__nav .btn-carousel { |
| 201 | + cursor: pointer; |
| 202 | +} |
| 203 | + |
| 204 | +/* ============================================ |
| 205 | + PAGINATION DOTS |
| 206 | + Absolutely positioned to the right of the card so they sit on the |
| 207 | + same visual row as the user profile (which is at the bottom-left of |
| 208 | + each slide). Lifted off the card's bottom edge to clear the divider |
| 209 | + and the Read-more CTA below. |
| 210 | + ============================================ */ |
| 211 | +.testimonial-card__dots { |
| 212 | + position: absolute; |
| 213 | + /* Card padding-bottom + CTA height (~36px) + small gap + hr-area |
| 214 | + spacing. Tweak if the button height or surrounding gaps change. */ |
| 215 | + bottom: calc(3 * var(--space-large) + 36px); |
| 216 | + right: var(--space-large); |
| 217 | + display: flex; |
| 218 | + align-items: center; |
| 219 | + gap: var(--space-s); |
| 220 | + z-index: 1; |
| 221 | +} |
| 222 | + |
| 223 | +.testimonial-card__dot { |
| 224 | + width: 12px; |
| 225 | + height: 12px; |
| 226 | + border-radius: 2px; |
| 227 | + background-color: var(--color-icon-secondary); |
| 228 | + opacity: 0.2; |
| 229 | + cursor: pointer; |
| 230 | + transition: background-color 0.2s ease, transform 0.2s ease; |
| 231 | +} |
| 232 | + |
| 233 | +.testimonial-card__dot:hover { |
| 234 | + background-color: var(--color-text-secondary); |
| 235 | +} |
| 236 | + |
| 237 | +/* The active dot's `opacity: 1` highlight is emitted by the inline <style> |
| 238 | + in the template (per-state rules). */ |
| 239 | + |
| 240 | +/* ============================================ |
| 241 | + READ-MORE CTA — full-width primary button, in flow. |
| 242 | + Hidden by default; the inline <style> in the template shows only the |
| 243 | + one matching the active radio. |
| 244 | + ============================================ */ |
| 245 | +.testimonial-card__cta { |
| 246 | + display: none; |
| 247 | + width: calc(100% - 2 * var(--space-large)); |
| 248 | + margin: 0 var(--space-large); |
| 249 | + box-sizing: border-box; |
132 | 250 | } |
133 | 251 |
|
134 | 252 | .testimonial-card__author-role-text { |
|
0 commit comments