@@ -17,26 +17,25 @@ const { data } = Astro.props;
1717 <div class = " quote-icon" >
1818 "
1919 </div >
20- <div class = " secondary-image" >
21- <img
22- src = { testimonial .imageSecondary }
23- alt = " logo image"
24- loading = " lazy"
25- decoding = " async"
26- />
27- </div >
20+ { testimonial .imageSecondary && (
21+ <div class = " secondary-image" >
22+ <img
23+ src = { testimonial .imageSecondary }
24+ alt = " logo image"
25+ loading = " lazy"
26+ decoding = " async"
27+ />
28+ </div >
29+ )}
2830 </div >
2931 <div class = " content-wrapper" >
30- <div class = " testimonial-content" data-testimonial-content >
32+ <div class = " testimonial-content" >
3133 { testimonial .markdownRaw ? (
3234 <ClientMarkdown data = { testimonial .markdownRaw } client :load />
3335 ) : (
3436 <p >No content available</p >
3537 )}
3638 </div >
37- <button class = " read-more-btn" data-read-more-btn style = " display: none;" >
38- Read More
39- </button >
4039 </div >
4140 <div class = " testimonial-author" >
4241 <div class = " author-name" >{ testimonial .title } </div >
@@ -52,29 +51,23 @@ const { data } = Astro.props;
5251<style is:global >
5352 .testimonials-grid ul {
5453 display: grid;
55- grid-template-columns: repeat(3 , 1fr);
54+ grid-template-columns: repeat(2 , 1fr);
5655 gap: 1.5rem;
5756 padding: 2rem 0;
5857 list-style: none;
5958 margin: 0;
6059 justify-items: center;
6160 }
6261
63- /* Center single item in second row when there are 4 testimonials */
64- .testimonials-grid li:nth-child(4):nth-last-child(1) {
65- grid-column: 2;
66- }
67-
6862 .testimonials-grid li {
69- height: 320px; /* forced height */
63+ min- height: 280px;
7064 padding: 1.25rem;
7165 border: 1px solid var(--border);
7266 border-radius: 8px;
7367 background: var(--surface-nav-bg);
7468 display: flex;
7569 flex-direction: column;
7670 align-items: flex-start;
77- transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
7871 }
7972
8073 .testimonial-header {
@@ -116,44 +109,13 @@ const { data } = Astro.props;
116109 }
117110
118111 .testimonial-content {
119- max-height: 9rem; /* fixed height for paragraph space */
120- overflow: hidden;
121112 margin-bottom: 1rem;
122- transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
123- mask-image 0.2s cubic-bezier(0.4, 0, 0.2, 1),
124- -webkit-mask-image 0.2s cubic-bezier(0.4, 0, 0.2, 1);
125- position: relative;
126- /* No default mask - will be applied by JavaScript only when needed */
127- }
128-
129- .testimonial-content.expanded {
130- max-height: 100rem;
131- display: block;
132- mask-image: none;
133- -webkit-mask-image: none;
134- }
135-
136- .read-more-btn {
137- @include typography(paragraph, var(--text-body-primary));
138- background: none;
139- border: none;
140- cursor: pointer;
141- padding: 0;
142- margin: 0;
143- text-decoration: underline;
144- align-self: flex-end; /* position on the right */
145- & {
146- font-size: calc(0.95rem * var(--font-scale-factor));
147- }
148- }
149-
150- .read-more-btn:hover {
151- opacity: 0.8;
152113 }
153114
154115 .testimonial-author {
155116 margin-top: auto;
156117 padding-bottom: 0;
118+ text-align: left;
157119 }
158120
159121 .author-name {
@@ -172,55 +134,31 @@ const { data } = Astro.props;
172134 }
173135 }
174136
175- /* Style for content within testimonial-content */
137+ /* Style for content within testimonial-content - all text left-aligned */
138+ .testimonial-content {
139+ text-align: left;
140+ }
141+
176142 .testimonial-content p {
177143 @include typography(paragraph);
178144 margin: 0;
179- text-align: justify ;
145+ text-align: left ;
180146 & {
181147 font-size: calc(0.95rem * var(--font-scale-factor));
182148 }
183149 }
184150
185151 /* Media queries for responsive design */
186- /* Tablet: 2 columns (below 992px) */
187- @media (max-width: 991.98px) {
188- .testimonials-grid ul {
189- grid-template-columns: repeat(2, 1fr) !important;
190- }
191-
192- /* Reset centering for 2-column layout */
193- .testimonials-grid li:nth-child(4):nth-last-child(1) {
194- grid-column: auto;
195- }
196-
197- /* Adjust text clamping for 2-column layout */
198- .testimonial-content {
199- max-height: 10.5rem; /* slightly more height for 2 columns */
200- }
201- }
202-
203152 /* Mobile: 1 column (below 768px) */
204153 @media (max-width: 767.98px) {
205154 .testimonials-grid ul {
206155 grid-template-columns: 1fr !important;
207156 gap: 1rem;
208157 }
209158
210- /* Reset centering for 1-column layout */
211- .testimonials-grid li:nth-child(4):nth-last-child(1) {
212- grid-column: auto;
213- }
214-
215159 .testimonials-grid li {
216- height: auto;
217160 min-height: 280px;
218161 }
219-
220- /* Adjust text clamping for 1-column layout */
221- .testimonial-content {
222- max-height: 8rem; /* adjust height for mobile */
223- }
224162 }
225163
226164 /* Small Mobile: optimized spacing (below 576px) */
@@ -240,89 +178,3 @@ const { data } = Astro.props;
240178 }
241179 }
242180</style >
243-
244- <script >
245- // Wait for DOM to be fully loaded
246- document.addEventListener('DOMContentLoaded', function() {
247- // Function to check if content is overflowing and show/hide read more button
248- function checkOverflow() {
249- const testimonialCards = document.querySelectorAll('.testimonials-grid li');
250-
251- testimonialCards.forEach(card => {
252- const content = card.querySelector('[data-testimonial-content]') as HTMLElement;
253- const readMoreBtn = card.querySelector('[data-read-more-btn]') as HTMLElement;
254-
255- if (content && readMoreBtn) {
256- // Check if content is taller than its container
257- const isOverflowing = content.scrollHeight > content.clientHeight;
258-
259- if (isOverflowing) {
260- readMoreBtn.style.display = 'block';
261- // Apply gradient mask only when content overflows
262- content.style.maskImage = 'linear-gradient(to bottom, black 80%, transparent 100%)';
263- (content.style as any).webkitMaskImage = 'linear-gradient(to bottom, black 80%, transparent 100%)';
264- } else {
265- readMoreBtn.style.display = 'none';
266- // Remove gradient mask when content doesn't overflow
267- content.style.maskImage = 'none';
268- (content.style as any).webkitMaskImage = 'none';
269- }
270- }
271- });
272- }
273-
274- // Function to handle read more button clicks
275- function handleReadMore() {
276- const readMoreButtons = document.querySelectorAll('[data-read-more-btn]');
277-
278- readMoreButtons.forEach(button => {
279- button.addEventListener('click', function(this: HTMLButtonElement) {
280- const card = this.closest('li') as HTMLElement;
281- const content = card.querySelector('[data-testimonial-content]') as HTMLElement;
282-
283- if (content.classList.contains('expanded')) {
284- // Collapse - control animation entirely through JavaScript
285- this.textContent = 'Read More';
286-
287- // First set explicit max-height to current scroll height
288- content.style.maxHeight = content.scrollHeight + 'px';
289-
290- // Force reflow
291- content.offsetHeight;
292-
293- // Then animate to collapsed height
294- setTimeout(() => {
295- content.style.maxHeight = '9rem';
296- content.style.maskImage = 'linear-gradient(to bottom, black 80%, transparent 100%)';
297- (content.style as any).webkitMaskImage = 'linear-gradient(to bottom, black 80%, transparent 100%)';
298- }, 10);
299-
300- // Remove expanded class after animation
301- setTimeout(() => {
302- content.classList.remove('expanded');
303- card.style.height = '320px';
304- }, 400);
305-
306- } else {
307- // Expand - remove mask and expand
308- content.style.maskImage = 'none';
309- (content.style as any).webkitMaskImage = 'none';
310- content.classList.add('expanded');
311-
312- // Set max-height to scroll height for smooth animation
313- content.style.maxHeight = content.scrollHeight + 'px';
314- card.style.height = 'auto';
315- this.textContent = 'Read Less';
316- }
317- });
318- });
319- }
320-
321- // Initialize
322- checkOverflow();
323- handleReadMore();
324-
325- // Recheck on window resize
326- window.addEventListener('resize', checkOverflow);
327- });
328- </script >
0 commit comments