Files
SkinbaseNova/resources/views/web/home/sections/news.blade.php

134 lines
8.2 KiB
PHP

@php
$newsItems = collect(is_array($items ?? null) ? $items : [])->filter()->take(10)->values();
$carouselId = 'news-carousel-'.uniqid();
@endphp
@if ($newsItems->isNotEmpty())
<section class="mt-14 px-4 sm:px-6 lg:px-8">
<div class="mb-5 flex items-center justify-between gap-4">
<h2 class="text-xl font-bold text-white">News &amp; Updates</h2>
<a href="/news" class="text-sm text-nova-300 transition hover:text-white">All news</a>
</div>
<div class="mt-5 relative">
<div class="relative">
<button type="button" class="news-carousel-prev absolute left-2 top-1/2 z-20 -translate-y-1/2 rounded-full bg-black/70 border border-white/10 p-2 text-white/90 shadow-md hover:scale-105 transition" aria-controls="{{ $carouselId }}" aria-label="Previous news">
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4" aria-hidden="true"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
</button>
<div id="{{ $carouselId }}" class="news-carousel overflow-x-auto snap-x snap-proximity -mx-4 px-4 py-2">
<div class="flex gap-4">
@foreach ($newsItems as $item)
<article class="snap-start flex-shrink-0 w-[260px] group overflow-hidden rounded-[20px] border border-white/[0.06] bg-[linear-gradient(180deg,rgba(11,16,26,0.94),rgba(7,11,19,0.92))] shadow-[0_12px_30px_rgba(0,0,0,0.18)] transition hover:-translate-y-0.5 hover:border-white/[0.12]">
<a href="{{ $item['url'] ?? '#' }}" class="block">
<div class="relative aspect-[4/3] overflow-hidden bg-black/20">
@if (!empty($item['cover_url']))
<img
src="{{ $item['cover_mobile_url'] ?? $item['cover_url'] }}"
@if (!empty($item['cover_srcset'])) srcset="{{ $item['cover_srcset'] }}" sizes="(max-width: 767px) 100vw, 260px" @endif
alt="{{ $item['title'] ?? 'News item' }}"
class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.04]"
>
@else
<div class="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_45%),linear-gradient(180deg,rgba(15,23,42,0.92),rgba(2,6,23,0.98))]"></div>
@endif
<div class="absolute inset-0 bg-gradient-to-t from-[#020611cc] via-transparent to-transparent"></div>
</div>
</a>
<div class="flex h-full flex-col p-3">
<div class="flex flex-wrap items-center gap-2">
@if (!empty($item['type_label']))
<span class="inline-flex items-center rounded-full border border-white/[0.08] bg-white/[0.04] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.12em] text-white/70">{{ $item['type_label'] }}</span>
@endif
@if (!empty($item['category']['name']))
<span class="inline-flex items-center rounded-full border border-sky-400/20 bg-sky-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.12em] text-sky-200">{{ $item['category']['name'] }}</span>
@endif
</div>
<h3 class="mt-2 text-lg font-semibold leading-tight text-white/95">
<a href="{{ $item['url'] ?? '#' }}" class="transition hover:text-sky-200">{{ \Illuminate\Support\Str::limit($item['title'] ?? 'News item', 70) }}</a>
</h3>
@if (!empty($item['excerpt']))
<p class="mt-2 flex-1 text-sm leading-6 text-white/55">{{ \Illuminate\Support\Str::limit($item['excerpt'], 110) }}</p>
@endif
<div class="mt-3 flex items-center justify-between gap-3 text-sm text-white/40">
<span class="truncate">{{ $item['author']['name'] ?? 'Skinbase' }}</span>
@if (array_key_exists('views', $item))
<span class="shrink-0 inline-flex items-center gap-1.5">
<i class="fa-regular fa-eye text-[11px]"></i>
{{ number_format((int) ($item['views'] ?? 0)) }}
</span>
@endif
</div>
</div>
</article>
@endforeach
</div>
</div>
<button type="button" class="news-carousel-next absolute right-2 top-1/2 z-20 -translate-y-1/2 rounded-full bg-black/70 border border-white/10 p-2 text-white/90 shadow-md hover:scale-105 transition" aria-controls="{{ $carouselId }}" aria-label="Next news">
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4" aria-hidden="true"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</section>
@endif
@push('scripts')
<script>
(function(){
try {
const carousel = document.getElementById('{{ $carouselId }}');
if (!carousel) return;
const prev = carousel.parentElement.querySelector('.news-carousel-prev');
const next = carousel.parentElement.querySelector('.news-carousel-next');
const card = carousel.querySelector('.snap-start');
if (!card) return;
const gap = 16; // matches gap-4
const scrollByAmount = () => (card.offsetWidth + gap);
prev && prev.addEventListener('click', () => {
carousel.scrollBy({ left: -scrollByAmount(), behavior: 'smooth' });
});
next && next.addEventListener('click', () => {
carousel.scrollBy({ left: scrollByAmount(), behavior: 'smooth' });
});
const updateFades = () => {
const atStart = carousel.scrollLeft <= 8;
const atEnd = carousel.scrollLeft + carousel.clientWidth >= carousel.scrollWidth - 8;
if (prev) {
prev.classList.toggle('opacity-30', atStart);
prev.classList.toggle('pointer-events-none', atStart);
prev.classList.toggle('opacity-100', !atStart);
}
if (next) {
next.classList.toggle('opacity-30', atEnd);
next.classList.toggle('pointer-events-none', atEnd);
next.classList.toggle('opacity-100', !atEnd);
}
};
carousel.addEventListener('scroll', () => {
window.requestAnimationFrame(updateFades);
}, { passive: true });
updateFades();
} catch (e) { /* no-op */ }
})();
</script>
<style>
#{{ $carouselId }} {
scrollbar-width: none;
-ms-overflow-style: none;
}
#{{ $carouselId }}::-webkit-scrollbar {
display: none;
}
</style>
@endpush