Add testimonial item type with crossfade carousel layout
New `type: "testimonial"` with structured attribution (quote/name/role/org/url/image/date) plus a `layout: "testimonials"` section behavior that renders the items as a tasteful crossfade carousel — one quote at a time, 7s auto-advance, prev/next nav, dot indicators, swipe support, ←/→ keys, pause on hover/focus, ARIA live region. Reduced-motion users automatically get .carousel--stacked: every testimonial visible at once, controls hidden, no auto-advance. A single-item section also skips the carousel chrome. Per-template treatment: editorial uses Fraunces italic for the curly quote mark and the body, swiss strips uppercase from titles per prior fix, cosmos glows the mark with the cyan/violet accent stack. Section auto-detects the layout when the first item is a testimonial, matching how the clients layout already works.
This commit is contained in:
@@ -477,6 +477,138 @@ body::before {
|
||||
}
|
||||
.yt iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
|
||||
|
||||
/* ───── carousel (testimonials) ───── */
|
||||
.carousel { position: relative; }
|
||||
.carousel__viewport {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.carousel__track {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-areas: "stack";
|
||||
}
|
||||
.carousel__track > * {
|
||||
grid-area: stack;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(8px);
|
||||
transition: opacity 600ms var(--ease-strong), transform 600ms var(--ease-strong), visibility 0s linear 600ms;
|
||||
pointer-events: none;
|
||||
}
|
||||
.carousel__track > .is-active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
transition: opacity 600ms var(--ease-strong), transform 600ms var(--ease-strong), visibility 0s;
|
||||
}
|
||||
.carousel--stacked .carousel__track {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
.carousel--stacked .carousel__track > * {
|
||||
opacity: 1; visibility: visible; transform: none; pointer-events: auto;
|
||||
}
|
||||
.carousel--stacked .carousel__controls { display: none; }
|
||||
|
||||
.carousel__controls {
|
||||
margin-top: 1.25rem;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.carousel__nav {
|
||||
width: 38px; height: 38px;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 50%;
|
||||
background: var(--paper);
|
||||
color: var(--ink-2);
|
||||
font: 500 1.2rem/1 var(--display);
|
||||
cursor: pointer;
|
||||
display: grid; place-items: center;
|
||||
transition: border-color 200ms var(--ease), color 200ms var(--ease), background 200ms var(--ease);
|
||||
}
|
||||
.carousel__nav:hover { border-color: var(--ink); color: var(--ink); background: color-mix(in oklch, var(--ink) 4%, transparent); }
|
||||
.carousel__dots { display: flex; gap: 0.5rem; align-items: center; }
|
||||
.carousel__dot {
|
||||
width: 8px; height: 8px;
|
||||
border: 0; padding: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--rule);
|
||||
cursor: pointer;
|
||||
transition: background 220ms var(--ease), transform 220ms var(--ease);
|
||||
}
|
||||
.carousel__dot:hover { background: var(--ink-2); }
|
||||
.carousel__dot.is-active { background: var(--accent); transform: scale(1.35); }
|
||||
|
||||
/* ───── testimonial card ───── */
|
||||
.card--testimonial {
|
||||
padding: clamp(1.75rem, 3vw, 2.75rem);
|
||||
display: flex; flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
min-height: clamp(220px, 24vw, 300px);
|
||||
}
|
||||
.card--testimonial::after { display: none; }
|
||||
.testimonial__mark {
|
||||
font-family: var(--display);
|
||||
font-size: clamp(4rem, 6vw, 6rem);
|
||||
line-height: 0.6;
|
||||
color: var(--accent);
|
||||
font-style: italic;
|
||||
font-variation-settings: "opsz" 144, "SOFT" 100;
|
||||
font-weight: 400;
|
||||
align-self: start;
|
||||
user-select: none;
|
||||
}
|
||||
.testimonial__quote {
|
||||
font-family: var(--display);
|
||||
font-size: clamp(1.15rem, 0.95rem + 0.7vw, 1.45rem);
|
||||
line-height: 1.45;
|
||||
font-weight: 400;
|
||||
font-variation-settings: "opsz" 60, "SOFT" 100;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
letter-spacing: -0.005em;
|
||||
max-width: 60ch;
|
||||
}
|
||||
.testimonial__by {
|
||||
display: flex; align-items: center; gap: 0.85rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.testimonial__avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 44px; height: 44px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: var(--paper-2);
|
||||
display: grid; place-items: center;
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
.testimonial__avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.testimonial__avatar img.is-favicon { width: 60%; height: 60%; object-fit: contain; }
|
||||
.testimonial__attr { display: flex; flex-direction: column; gap: 0.1rem; line-height: 1.3; min-width: 0; }
|
||||
.testimonial__name {
|
||||
font-family: var(--body);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
font-size: var(--fs-sm);
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
.testimonial__meta {
|
||||
font-family: var(--mono);
|
||||
font-size: var(--fs-mini);
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.testimonial__by .card__date {
|
||||
font-size: var(--fs-mini);
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
/* footer */
|
||||
.foot {
|
||||
margin-top: clamp(4rem, 8vw, 7rem);
|
||||
|
||||
Reference in New Issue
Block a user