Testimonials: blend into page, inline nav arrows, drop dots

User wanted the testimonial section to feel "of the page" rather than
a card sitting on top of it, and to occupy less vertical space.

- Strip all chrome from .card--testimonial: transparent background,
  no border, no shadow, no hover lift. Same treatment applied in the
  swiss + cosmos overrides so per-theme backgrounds don't sneak back.
- Restructure the carousel markup to put the prev/next buttons
  directly beside the viewport (carousel--inline = flex row), no
  separate controls block, no dot indicators.
- Shrink the curly quote mark and the avatar (44 -> 32px); tighten
  internal gaps. Removed the rule above the attribution row.
- Keep healthy horizontal padding via clamp() so the arrows don't
  crowd the surrounding sections.
- Update aria-label on the carousel itself instead of the dots so
  screen readers still get position context.
This commit is contained in:
Joel Brock
2026-05-16 09:18:41 -07:00
parent b257d92636
commit 4868111a14
4 changed files with 41 additions and 26 deletions

View File

@@ -572,7 +572,9 @@
/* ───── testimonial / carousel ───── */ /* ───── testimonial / carousel ───── */
:root[data-template="cosmos"] .card--testimonial { :root[data-template="cosmos"] .card--testimonial {
background: linear-gradient(135deg, rgba(30, 22, 64, 0.55), rgba(14, 8, 38, 0.55)); background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
} }
:root[data-template="cosmos"] .testimonial__mark { :root[data-template="cosmos"] .testimonial__mark {
color: var(--accent); color: var(--accent);

View File

@@ -479,6 +479,12 @@ body::before {
/* ───── carousel (testimonials) ───── */ /* ───── carousel (testimonials) ───── */
.carousel { position: relative; } .carousel { position: relative; }
.carousel--inline {
display: flex; align-items: center;
gap: clamp(0.5rem, 2vw, 1.5rem);
padding: 0 clamp(0.5rem, 3vw, 2rem);
}
.carousel--inline .carousel__viewport { flex: 1 1 0; min-width: 0; }
.carousel__viewport { .carousel__viewport {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@@ -542,25 +548,38 @@ body::before {
.carousel__dot:hover { background: var(--ink-2); } .carousel__dot:hover { background: var(--ink-2); }
.carousel__dot.is-active { background: var(--accent); transform: scale(1.35); } .carousel__dot.is-active { background: var(--accent); transform: scale(1.35); }
/* ───── testimonial card ───── */ /* ───── testimonial — "of the page", no chrome ───── */
.card--testimonial { .card--testimonial {
padding: clamp(1.75rem, 3vw, 3rem) clamp(1.5rem, 4vw, 3.5rem); padding: 0.5rem 0;
background: transparent;
border: 0;
border-radius: 0;
box-shadow: none;
display: flex; flex-direction: column; display: flex; flex-direction: column;
align-items: center; align-items: center;
text-align: center; text-align: center;
gap: 1.25rem; gap: 0.85rem;
min-height: clamp(220px, 24vw, 300px); min-height: 0;
isolation: auto;
} }
.card--testimonial::after { display: none; } .card--testimonial:hover {
transform: none;
background: transparent;
border-color: transparent;
box-shadow: none;
}
.card--testimonial::after,
.card--testimonial::before { display: none; }
.testimonial__mark { .testimonial__mark {
font-family: var(--display); font-family: var(--display);
font-size: clamp(4rem, 6vw, 6rem); font-size: clamp(2.5rem, 3.5vw, 3.5rem);
line-height: 0.6; line-height: 0.55;
color: var(--accent); color: var(--accent);
font-style: italic; font-style: italic;
font-variation-settings: "opsz" 144, "SOFT" 100; font-variation-settings: "opsz" 144, "SOFT" 100;
font-weight: 400; font-weight: 400;
user-select: none; user-select: none;
opacity: 0.85;
} }
.testimonial__quote { .testimonial__quote {
font-family: var(--display); font-family: var(--display);
@@ -598,16 +617,15 @@ body::before {
} }
.testimonial__by { .testimonial__by {
display: flex; align-items: center; justify-content: center; gap: 0.85rem; display: flex; align-items: center; justify-content: center; gap: 0.7rem;
margin-top: auto; margin: 0;
padding-top: 0.85rem; padding: 0;
border-top: 1px solid var(--rule); border: 0;
width: 100%; width: auto;
max-width: 32rem;
} }
.testimonial__avatar { .testimonial__avatar {
flex: 0 0 auto; flex: 0 0 auto;
width: 44px; height: 44px; width: 32px; height: 32px;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
background: var(--paper-2); background: var(--paper-2);

View File

@@ -419,8 +419,8 @@
} }
/* ───── testimonial / carousel ───── */ /* ───── testimonial / carousel ───── */
:root[data-template="swiss"] .card--testimonial { background: var(--paper-2); } :root[data-template="swiss"] .card--testimonial { background: transparent; }
:root[data-template="swiss"] .card--testimonial:hover { background: var(--paper-2); color: var(--ink); } :root[data-template="swiss"] .card--testimonial:hover { background: transparent; color: var(--ink); }
:root[data-template="swiss"] .card--testimonial:hover .testimonial__quote, :root[data-template="swiss"] .card--testimonial:hover .testimonial__quote,
:root[data-template="swiss"] .card--testimonial:hover .testimonial__name { color: var(--ink); } :root[data-template="swiss"] .card--testimonial:hover .testimonial__name { color: var(--ink); }
:root[data-template="swiss"] .testimonial__mark { :root[data-template="swiss"] .testimonial__mark {

View File

@@ -155,19 +155,13 @@
let body; let body;
if (isTestimonials) { if (isTestimonials) {
const count = (sec.items || []).length; const count = (sec.items || []).length;
const dots = Array.from({ length: count }, (_, i) =>
`<button type="button" class="carousel__dot${i === 0 ? " is-active" : ""}" aria-label="Show testimonial ${i + 1} of ${count}" data-slide="${i}"></button>`).join("");
body = ` body = `
<div class="carousel" data-carousel data-count="${count}"> <div class="carousel carousel--inline" data-carousel data-count="${count}">
${count > 1 ? `<button type="button" class="carousel__nav carousel__nav--prev" aria-label="Previous testimonial"></button>` : ""}
<div class="carousel__viewport"> <div class="carousel__viewport">
<div class="carousel__track" aria-live="polite">${items}</div> <div class="carousel__track" aria-live="polite">${items}</div>
</div> </div>
${count > 1 ? ` ${count > 1 ? `<button type="button" class="carousel__nav carousel__nav--next" aria-label="Next testimonial"></button>` : ""}
<div class="carousel__controls">
<button type="button" class="carousel__nav carousel__nav--prev" aria-label="Previous testimonial"></button>
<div class="carousel__dots" role="tablist">${dots}</div>
<button type="button" class="carousel__nav carousel__nav--next" aria-label="Next testimonial"></button>
</div>` : ""}
</div>`; </div>`;
} else { } else {
const gridClass = isClients ? "grid--clients" : "grid"; const gridClass = isClients ? "grid--clients" : "grid";
@@ -475,6 +469,7 @@
i = (n + slides.length) % slides.length; i = (n + slides.length) % slides.length;
slides.forEach((s, k) => s.classList.toggle("is-active", k === i)); slides.forEach((s, k) => s.classList.toggle("is-active", k === i));
dots.forEach((d, k) => d.classList.toggle("is-active", k === i)); dots.forEach((d, k) => d.classList.toggle("is-active", k === i));
car.setAttribute("aria-label", `Testimonial ${i + 1} of ${slides.length}`);
}; };
show(0); show(0);