card.blade.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. {{-- resources/views/components/home/card.blade.php
  2. WikiPāli 首页通栏卡片。底图满铺,文字压在图上。
  3. available && href 非空 → 渲染链接按钮;否则渲染不可点击的状态角标。
  4. 底图按「存在的文件」动态输出:优先 AVIF → WebP → JPEG 兜底。
  5. 有底图时直接显示底图(不加颜色混合蒙版);素材未就位时才用蒙版纯色块占位,不引用任何不存在的文件,
  6. 避免浏览器选中 AVIF 源却 404 导致整张图不显示。
  7. --}}
  8. @props([
  9. 'slug',
  10. 'eyebrow',
  11. 'title',
  12. 'lead',
  13. 'image',
  14. 'tint',
  15. 'align',
  16. 'href' => null,
  17. 'cta',
  18. 'available' => false,
  19. 'eager' => false,
  20. ])
  21. @php
  22. $sizes = [640, 1280, 2560];
  23. $formats = ['avif' => 'image/avif', 'webp' => 'image/webp', 'jpg' => 'image/jpeg'];
  24. // 收集磁盘上实际存在的底图文件
  25. $byFormat = []; // ext => [ ['url'=>..., 'width'=>...], ... ]
  26. foreach ($formats as $ext => $mime) {
  27. $found = [];
  28. foreach ($sizes as $width) {
  29. $url = "/img/home/{$image}-{$width}.{$ext}";
  30. if (is_file(public_path("img/home/{$image}-{$width}.{$ext}"))) {
  31. $found[] = ['url' => $url, 'width' => $width];
  32. }
  33. }
  34. if ($found !== []) {
  35. $byFormat[$ext] = $found;
  36. }
  37. }
  38. // 每种存在格式的 srcset 串
  39. $sets = [];
  40. foreach ($byFormat as $ext => $entries) {
  41. $parts = [];
  42. foreach ($entries as $e) {
  43. $parts[] = $e['url'].' '.$e['width'].'w';
  44. }
  45. $sets[$ext] = implode(', ', $parts);
  46. }
  47. // <img> 兜底:优先 jpg,其次 webp,再次 avif;尺寸优先 1280
  48. $fallbackFormat = null;
  49. foreach (['jpg', 'webp', 'avif'] as $ext) {
  50. if (isset($byFormat[$ext])) {
  51. $fallbackFormat = $ext;
  52. break;
  53. }
  54. }
  55. $fallbackSrc = null;
  56. if ($fallbackFormat !== null) {
  57. foreach ($byFormat[$fallbackFormat] as $e) {
  58. if ($e['width'] === 1280) {
  59. $fallbackSrc = $e['url'];
  60. break;
  61. }
  62. }
  63. $fallbackSrc ??= $byFormat[$fallbackFormat][0]['url'];
  64. }
  65. $titleId = "{$slug}-title";
  66. @endphp
  67. <section id="card-{{ $slug }}"
  68. class="home-card home-card--{{ $align }}"
  69. style="--card-tint: {{ $tint }}"
  70. aria-labelledby="{{ $titleId }}">
  71. @if ($fallbackSrc !== null)
  72. <picture class="home-card__media-wrap">
  73. @if (isset($sets['avif']))
  74. <source type="image/avif" srcset="{{ $sets['avif'] }}" sizes="100vw">
  75. @endif
  76. @if (isset($sets['webp']))
  77. <source type="image/webp" srcset="{{ $sets['webp'] }}" sizes="100vw">
  78. @endif
  79. <img class="home-card__media"
  80. src="{{ $fallbackSrc }}"
  81. @if (isset($sets[$fallbackFormat])) srcset="{{ $sets[$fallbackFormat] }}" sizes="100vw" @endif
  82. alt=""
  83. decoding="async"
  84. @if($eager) loading="eager" fetchpriority="high" @else loading="lazy" @endif>
  85. </picture>
  86. @else
  87. <div class="home-card__veil" aria-hidden="true"></div>
  88. @endif
  89. <div class="home-card__body">
  90. <div class="home-card__text">
  91. <p class="home-card__eyebrow">{{ $eyebrow }}</p>
  92. <h2 class="home-card__title" id="{{ $titleId }}">{{ $title }}</h2>
  93. <p class="home-card__lead">{{ $lead }}</p>
  94. @if ($available && filled($href))
  95. <a class="home-card__cta" href="{{ $href }}">{{ $cta }}</a>
  96. @else
  97. <span class="home-card__badge">
  98. <i class="ti ti-clock" aria-hidden="true"></i>
  99. {{ $cta }}
  100. </span>
  101. @endif
  102. </div>
  103. </div>
  104. </section>