|
270 | 270 |
|
271 | 271 | .project-grid { |
272 | 272 | display: grid; |
273 | | - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
| 273 | + /* |
| 274 | + minmax(min(100%, 250px), 1fr): the min() guard caps each track at the |
| 275 | + container width, so a track can never overflow its parent (the classic |
| 276 | + CSS-grid mobile overflow bug). At ~250px the layout naturally yields a |
| 277 | + single column on phones, 2 on tablets, and 3+ on desktop. See the |
| 278 | + @media (max-width: 991px) block below, which also un-flexes .row-flex so |
| 279 | + the grid's parent reports a definite width (otherwise auto-fill collapses |
| 280 | + to one full-width column on tablets — the original off-screen-thumbnail bug). |
| 281 | + */ |
| 282 | + grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); |
274 | 283 | gap: var(--space-8); |
275 | 284 | margin-top: var(--space-5); |
276 | 285 | /* Removed min-height - let grid size naturally based on content */ |
277 | 286 | } |
278 | 287 |
|
279 | | -/* Responsive adjustments */ |
280 | | -@media (max-width: 576px) { |
281 | | - .project-grid { |
282 | | - grid-template-columns: 1fr; |
283 | | - gap: var(--space-6); |
284 | | - } |
285 | | -} |
286 | | - |
287 | 288 |
|
288 | 289 | /* ============================================================================= |
289 | 290 | PROJECT CARD |
290 | 291 | ============================================================================= */ |
291 | 292 |
|
292 | 293 | .project-card { |
293 | 294 | position: relative; |
| 295 | + min-width: 0; /* Allow the card to shrink inside its grid track (prevents overflow) */ |
294 | 296 | transition: opacity 500ms ease; /* Match FADE_DURATION in JS */ |
295 | 297 | } |
296 | 298 |
|
|
319 | 321 | display: block; |
320 | 322 | width: 100%; |
321 | 323 | height: auto; |
| 324 | + /* Thumbnails are cropped to 500x300 (5:3); reserving the box prevents |
| 325 | + layout shift (CLS) while the image loads. object-fit guards any |
| 326 | + off-ratio source. */ |
| 327 | + aspect-ratio: 5 / 3; |
| 328 | + object-fit: cover; |
322 | 329 | border: 1px solid var(--color-border); |
323 | 330 | border-radius: var(--border-radius-md); |
324 | 331 | transition: transform var(--transition-normal); |
325 | 332 | } |
326 | 333 |
|
327 | | -.project-image:hover .project-thumbnail { |
328 | | - transform: scale(1.05); |
| 334 | +/* Only apply the hover zoom on devices with a real pointer; on touchscreens |
| 335 | + a :hover transform sticks awkwardly after a tap. */ |
| 336 | +@media (hover: hover) { |
| 337 | + .project-image:hover .project-thumbnail { |
| 338 | + transform: scale(1.05); |
| 339 | + } |
329 | 340 | } |
330 | 341 |
|
331 | 342 | /* Reduce motion for users who prefer it */ |
|
406 | 417 | RESPONSIVE ADJUSTMENTS |
407 | 418 | ============================================================================= */ |
408 | 419 |
|
409 | | -/* Tablet and below: adjust grid gaps */ |
| 420 | +/* Tablet and below: tighten the grid gap. (The flex->block switch that makes |
| 421 | + the grid go multi-column here lives in the FLEXBOX ROW HELPER section, which |
| 422 | + is mobile-first: block by default, flex only at >=992px.) */ |
410 | 423 | @media (max-width: 991px) { |
411 | 424 | .project-grid { |
412 | 425 | gap: var(--space-6); |
413 | 426 | } |
| 427 | + |
| 428 | + /* Narrow columns truncate titles aggressively; allow up to two lines. |
| 429 | + Overrides the shared .line-clamp-one-line utility for this page only. */ |
| 430 | + .project-title a { |
| 431 | + white-space: normal; |
| 432 | + overflow: visible; |
| 433 | + } |
| 434 | + |
| 435 | + .project-title .line-clamp-one-line { |
| 436 | + display: -webkit-box; |
| 437 | + -webkit-box-orient: vertical; |
| 438 | + -webkit-line-clamp: 2; |
| 439 | + line-clamp: 2; |
| 440 | + white-space: normal; |
| 441 | + overflow: hidden; |
| 442 | + } |
414 | 443 | } |
415 | 444 |
|
416 | 445 | /* Phone: stack layout */ |
|
419 | 448 | flex-direction: column; |
420 | 449 | align-items: flex-start; |
421 | 450 | } |
422 | | - |
| 451 | + |
423 | 452 | .filter-mobile-container { |
424 | 453 | width: 100%; |
425 | 454 | } |
426 | | - |
| 455 | + |
427 | 456 | .filter-mobile-select { |
428 | 457 | width: 100%; |
429 | 458 | } |
|
434 | 463 | FLEXBOX ROW HELPER |
435 | 464 | ============================================================================= */ |
436 | 465 |
|
437 | | -.row-flex { |
438 | | - display: flex; |
439 | | - flex-wrap: wrap; |
| 466 | +/* |
| 467 | + Mobile-first: the row is a plain block by default so that .project-grid's |
| 468 | + parent (.col-md-10) has a DEFINITE width and the grid's auto-fill resolves |
| 469 | + to multiple columns. Only at >=992px — where the desktop filter sidebar |
| 470 | + appears — do we switch to flex to place the sidebar beside the content. |
| 471 | + (A flex parent gives the grid an indefinite width, which collapses auto-fill |
| 472 | + to a single oversized column — the original off-screen-thumbnail bug.) |
| 473 | +*/ |
| 474 | +@media (min-width: 992px) { |
| 475 | + .row-flex { |
| 476 | + display: flex; |
| 477 | + flex-wrap: wrap; |
| 478 | + } |
440 | 479 | } |
441 | 480 |
|
442 | 481 | /* Keep sidebar column aligned to top */ |
|
0 commit comments