Skip to content

Commit f4b8b82

Browse files
committed
style: update the UI for testimonial cards
1 parent bfef8ab commit f4b8b82

5 files changed

Lines changed: 269 additions & 72 deletions

File tree

static/css/v3/testimonial-card.css

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.testimonial-card {
22
max-width: 696px;
3+
position: relative;
4+
padding: var(--space-large) 0 !important;
5+
overflow: hidden;
36
}
47

58
.testimonial-card__carousel {
@@ -37,13 +40,33 @@
3740
fill: var(--color-surface-strong);
3841
}
3942

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+
4055
.testimonial-card__text {
4156
color: var(--color-text-primary, #050816);
4257
font-family: var(--font-sans);
4358
font-size: var(--font-size-xl, 32px);
4459
font-weight: var(--font-weight-regular);
4560
line-height: var(--line-height-tight);
4661
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;
4770
}
4871

4972
.testimonial-card__text-tail {
@@ -113,22 +136,117 @@
113136
display: flex;
114137
flex-direction: row;
115138
flex-wrap: nowrap;
116-
gap: var(--space-card);
117-
overflow-x: auto;
118-
overflow-y: hidden;
119-
-webkit-overflow-scrolling: touch;
120139
min-width: 0;
121-
-ms-overflow-style: none;
122-
scrollbar-width: none;
140+
transition: transform 0.3s ease;
141+
will-change: transform;
123142
}
124143

125144
.testimonial-card__list-item {
126145
list-style: none;
127-
flex: 0 0 auto;
146+
flex: 0 0 100%;
128147
min-width: 0;
129148
max-width: 100%;
130149
border: none;
131150
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;
132250
}
133251

134252
.testimonial-card__author-role-text {

static/css/v3/user-profile.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@
5151
row-gap: var(--space-s);
5252
}
5353

54+
.user-profile__avatar-link {
55+
border-radius: var(--border-radius-m);
56+
}
57+
58+
.user-profile__avatar-link:focus-visible {
59+
outline-offset: -2px;
60+
}
61+
62+
.user-profile__avatar-link {
63+
border-radius: var(--border-radius-m);
64+
}
65+
66+
.user-profile__avatar-link:focus-visible {
67+
outline-offset: -2px;
68+
}
69+
5470
.user-profile__name-group {
5571
display: inline-flex;
5672
align-items: center;

static/css/v3/v3-homepage.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
body:has(.homepage-v3) {
2+
overflow-x: clip;
3+
}
4+
15
body:has(.homepage-v3) .header {
26
position: absolute;
37
z-index: 10;

templates/v3/examples/_v3_example_section.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ <h3 class="v3-examples-section__toc-heading">Table of Contents</h3>
5050
v3_template, has_legacy_template.
5151
{% endcomment %}
5252
{% with section_title="V3 Paths Registry – auto generated by grepping V3Mixin views" %}
53-
<div class="v3-examples-section__block" id="{{ section_title|slugify }}">
53+
<div class="v3-examples-section__block " id="{{ section_title|slugify }}">
5454
<h2>{{ section_title }}</h2>
55-
<div class="v3-examples-section__example-box">
55+
<div class="v3-examples-section__example-box v3-examples-section-horizontal-container">
5656
<table class="v3-examples-section__registry-table">
5757
<thead>
5858
<tr>

0 commit comments

Comments
 (0)