Initial commit
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<div>
|
||||
@if ($post != null)
|
||||
|
||||
<div class="relative flex flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px] my-9">
|
||||
<a class="relative flex cursor-pointer flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px]"
|
||||
href="{{ route('post.show_by_slug', [$post->slug]) }}">
|
||||
|
||||
<!-- Photo as background -->
|
||||
@if ($media != null)
|
||||
<div class="absolute inset-0 z-0 h-full w-full">
|
||||
{{ $media('4_3', ['class' => 'absolute inset-0 z-0 h-full w-full object-cover blur-[1px]']) }}
|
||||
</div>
|
||||
@else
|
||||
<!-- Default background if no image -->
|
||||
<div class="absolute inset-0 z-0 h-full w-full bg-gradient-to-br from-primary-700 to-theme-brand"></div>
|
||||
@endif
|
||||
|
||||
<!-- Optional overlay for contrast -->
|
||||
<div class="absolute inset-0 z-10 bg-black bg-opacity-65"></div>
|
||||
|
||||
<!-- All card content on top of photo -->
|
||||
<div class="relative z-20 flex h-full flex-col justify-between px-6 md:px-10 lg:px-14 py-14 md:py-14 lg:py-20 text-white">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start gap-4">
|
||||
<h2 class="text text-3xl lg:text-4xl font-semibold leading-tight pr-16 md:pr-20">
|
||||
{{ $post->title }}
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="inline-block items-center rounded-sm bg-theme-brand px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal">
|
||||
{{ $post->category }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-6 my-4 flex items-center justify-center">
|
||||
@if (isset($post->excerpt))
|
||||
<p class="w-full text-base md:text-xl font-normal leading-relaxed line-clamp-8">
|
||||
{{ $post->excerpt }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<!-- Bottom section: author info -->
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
<h2 class="text-lg md:text-2xl ml-auto pt-3 font-semibold text-white">
|
||||
{{ $post->author }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
154
resources/views/livewire/main-page/call-card-carousel.blade.php
Normal file
154
resources/views/livewire/main-page/call-card-carousel.blade.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<div>
|
||||
@if (count($calls) > 0)
|
||||
<div x-data="{
|
||||
navigate: true,
|
||||
atStart: true,
|
||||
atEnd: false,
|
||||
init() {
|
||||
this.$nextTick(() => this.updateState());
|
||||
this.$refs.track.addEventListener('scroll', () => this.updateState());
|
||||
},
|
||||
updateState() {
|
||||
const t = this.$refs.track;
|
||||
this.atStart = t.scrollLeft <= 0;
|
||||
this.atEnd = t.scrollLeft + t.offsetWidth >= t.scrollWidth - 1;
|
||||
},
|
||||
scrollLeft() {
|
||||
this.navigate = false;
|
||||
this.$refs.track.scrollBy({ left: -this.$refs.track.offsetWidth, behavior: 'smooth' });
|
||||
setTimeout(() => { this.navigate = true }, 600);
|
||||
},
|
||||
scrollRight() {
|
||||
this.navigate = false;
|
||||
this.$refs.track.scrollBy({ left: this.$refs.track.offsetWidth, behavior: 'smooth' });
|
||||
setTimeout(() => { this.navigate = true }, 600);
|
||||
},
|
||||
}" class="relative group">
|
||||
|
||||
{{-- Scrollable track --}}
|
||||
<div x-ref="track"
|
||||
class="overflow-x-auto"
|
||||
style="scrollbar-width: none; -ms-overflow-style: none;">
|
||||
<div class="flex gap-3" style="width: max-content;">
|
||||
<div class="w-2 flex-shrink-0"></div>
|
||||
@foreach ($calls as $index => $result)
|
||||
<div @click="if (navigate) window.location='{{ route('call.show', ['id' => $result['id']]) }}'"
|
||||
class="relative flex flex-col justify-end overflow-hidden rounded-lg shadow-lg flex-shrink-0 cursor-pointer"
|
||||
style="width: calc((100vw - 3rem) / 3); min-width: 200px; max-width: 320px; height: 170px;">
|
||||
|
||||
{{-- Tag color background + overlay --}}
|
||||
<div class="absolute inset-0 z-0 bg-{{ $result['tag_color'] ?? 'gray' }}-400"></div>
|
||||
<div class="absolute inset-0 z-10 bg-black/50"></div>
|
||||
|
||||
{{-- Card content --}}
|
||||
<div class="relative z-20 flex h-full flex-col justify-between p-4 text-white">
|
||||
<div class="flex-1 min-w-0">
|
||||
{{-- Deepest tag category badge --}}
|
||||
@php $leafCat = !empty($result['tag_categories']) ? end($result['tag_categories']) : null; @endphp
|
||||
@if ($leafCat)
|
||||
<div class="mb-2">
|
||||
<span class="bg-{{ $leafCat['color'] ?? 'gray' }}-400 inline-flex items-center rounded px-1.5 py-0.5 text-xs font-normal text-black">
|
||||
{{ $leafCat['name'] }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Title --}}
|
||||
<h3 class="text-lg font-semibold leading-tight truncate">
|
||||
{{ $result['title'] }}
|
||||
</h3>
|
||||
|
||||
{{-- Location + expiry badges --}}
|
||||
@if (!empty($result['location']) || !empty($result['expiry_badge_text']))
|
||||
<div class="mt-2 flex items-center gap-1 overflow-hidden">
|
||||
@if (!empty($result['location']))
|
||||
<span class="inline-block flex-shrink-0 rounded-sm bg-black px-1.5 pb-0.5 pt-0.5 text-xs font-normal uppercase text-white truncate max-w-[50%]">
|
||||
{{ $result['location'] }}
|
||||
</span>
|
||||
@endif
|
||||
@if (!empty($result['expiry_badge_text']))
|
||||
<span class="inline-block flex-shrink-0 rounded-sm bg-black px-1.5 pb-0.5 pt-0.5 text-xs font-normal uppercase text-white truncate max-w-[50%]">
|
||||
{{ $result['expiry_badge_text'] }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Callable avatar + name --}}
|
||||
@if (!empty($result['callable_name']))
|
||||
<div class="flex items-center gap-2 mt-3">
|
||||
@if (!empty($result['photo']))
|
||||
<div class="h-8 w-8 flex-shrink-0 rounded-full outline outline-1 outline-offset-1 outline-white/50 overflow-hidden">
|
||||
<img src="{{ $result['photo'] }}"
|
||||
alt="{{ $result['callable_name'] }}"
|
||||
class="h-full w-full object-cover" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="text-xs font-medium truncate">{{ $result['callable_name'] }}</span>
|
||||
@if (!empty($result['callable_location']))
|
||||
<span class="text-xs text-white/70 truncate">{{ $result['callable_location'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Prioritisation score --}}
|
||||
@if ($showScore)
|
||||
<div class="absolute bottom-3 right-3 z-30 text-xs text-white/70">
|
||||
{{ round($result['score'], 2) }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Reaction button --}}
|
||||
@if ($showReactions ?? true)
|
||||
<div class="absolute top-3 right-3 z-30">
|
||||
@livewire('reaction-button', [
|
||||
'typeName' => 'like',
|
||||
'showCounter' => true,
|
||||
'reactionCounter' => $result['like_count'],
|
||||
'modelClass' => $result['model'],
|
||||
'modelId' => $result['id'],
|
||||
'size' => 'w-5 h-5',
|
||||
'inverseColors' => true,
|
||||
], key('like-carousel-' . $result['id'] . '-' . $index))
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="w-2 flex-shrink-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Left button — hidden at start --}}
|
||||
<button type="button"
|
||||
@mousedown.prevent.stop
|
||||
@click.prevent.stop="scrollLeft()"
|
||||
x-show="!atStart"
|
||||
x-cloak
|
||||
class="absolute left-2 top-1/2 -translate-y-1/2 z-20 flex h-9 w-9 items-center justify-center rounded-full bg-black text-white transition-all opacity-0 group-hover:opacity-100 group-hover:outline group-hover:outline-2 group-hover:outline-white group-hover:shadow-lg hover:outline-1"
|
||||
aria-label="{{ __('Scroll left') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{{-- Right button — hidden at end --}}
|
||||
<button type="button"
|
||||
@mousedown.prevent.stop
|
||||
@click.prevent.stop="scrollRight()"
|
||||
x-show="!atEnd"
|
||||
x-cloak
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 z-20 flex h-9 w-9 items-center justify-center rounded-full bg-black text-white transition-all opacity-0 group-hover:opacity-100 group-hover:outline group-hover:outline-2 group-hover:outline-white group-hover:shadow-lg hover:outline-1"
|
||||
aria-label="{{ __('Scroll right') }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
95
resources/views/livewire/main-page/call-card-full.blade.php
Normal file
95
resources/views/livewire/main-page/call-card-full.blade.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<div>
|
||||
@if ($call !== null)
|
||||
|
||||
<div class="relative flex flex-col justify-end overflow-hidden bg-theme-background my-9">
|
||||
<a class="relative flex cursor-pointer flex-col justify-end overflow-hidden bg-{{ $call['tag_color'] ?? 'gray' }}-400"
|
||||
href="{{ route('call.show', ['id' => $call['id']]) }}">
|
||||
|
||||
<!-- Tag color background + overlay -->
|
||||
<div class="absolute inset-0 z-0 bg-{{ $call['tag_color'] ?? 'gray' }}-400"></div>
|
||||
<div class="absolute inset-0 z-10 bg-black/50"></div>
|
||||
|
||||
<!-- Card content -->
|
||||
<div class="relative z-20 flex h-full flex-col justify-between px-6 md:px-10 lg:px-14 py-14 md:py-14 lg:py-20 text-white">
|
||||
<div class="space-y-3">
|
||||
|
||||
{{-- Deepest tag category badge only --}}
|
||||
@php $deepestCat = !empty($call['tag_categories']) ? last($call['tag_categories']) : null; @endphp
|
||||
@if ($deepestCat)
|
||||
<div class="flex flex-wrap items-center gap-2 pr-16 md:pr-20">
|
||||
<span class="bg-{{ $deepestCat['color'] ?? 'gray' }}-400 inline-flex items-center rounded-md px-2 py-1 text-sm font-normal text-black">
|
||||
{{ $deepestCat['name'] }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Title --}}
|
||||
<h2 class="text text-3xl lg:text-4xl font-semibold leading-tight pr-16 md:pr-20 line-clamp-2">
|
||||
{{ $call['title'] }}
|
||||
</h2>
|
||||
|
||||
{{-- Location + expiry badges --}}
|
||||
@if (!empty($call['location']) || !empty($call['expiry_badge_text']))
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@if (!empty($call['location']))
|
||||
<h4 class="inline-block items-center rounded-sm bg-black px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal uppercase text-white">
|
||||
{{ $call['location'] }}
|
||||
</h4>
|
||||
@endif
|
||||
@if (!empty($call['expiry_badge_text']))
|
||||
<h4 class="inline-block items-center rounded-sm bg-black px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal uppercase text-white">
|
||||
{{ $call['expiry_badge_text'] }}
|
||||
</h4>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Excerpt --}}
|
||||
@if (!empty($call['excerpt']))
|
||||
<div class="mx-6 my-4 flex items-center justify-center">
|
||||
<p class="w-full text-base md:text-xl font-normal leading-relaxed line-clamp-2">
|
||||
{{ $call['excerpt'] }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Callable avatar + name + location --}}
|
||||
@if (!empty($call['callable_name']))
|
||||
<div class="flex flex-wrap items-end gap-4 mt-4">
|
||||
<div class="flex items-center gap-3">
|
||||
@if (!empty($call['photo']))
|
||||
<div class="h-16 w-16 flex-shrink-0 rounded-full outline outline-1 outline-offset-1 outline-white/50 overflow-hidden">
|
||||
<img src="{{ $call['photo'] }}"
|
||||
alt="{{ $call['callable_name'] }}"
|
||||
class="h-full w-full object-cover" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col gap-1 text-sm">
|
||||
<span class="font-medium">{{ $call['callable_name'] }}</span>
|
||||
@if (!empty($call['callable_location']))
|
||||
<span class="text-white/80">{{ $call['callable_location'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Like button positioned absolutely outside anchor tag -->
|
||||
<div class="absolute top-14 right-4 md:right-10 lg:right-14 z-30">
|
||||
@livewire('reaction-button', [
|
||||
'typeName' => 'like',
|
||||
'showCounter' => true,
|
||||
'reactionCounter' => $call['like_count'],
|
||||
'modelClass' => $call['model'],
|
||||
'modelId' => $call['id'],
|
||||
'size' => 'w-10 h-10',
|
||||
'inverseColors' => true,
|
||||
], key('like-call-main-' . $call['id']))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
25
resources/views/livewire/main-page/call-card-half.blade.php
Normal file
25
resources/views/livewire/main-page/call-card-half.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div>
|
||||
@if (!empty($calls))
|
||||
<div class="flex flex-col gap-6 m-6">
|
||||
@foreach (collect($calls)->chunk(2) as $row)
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@foreach ($row as $index => $call)
|
||||
<x-call-card
|
||||
:result="$call"
|
||||
:index="$index"
|
||||
:show-score="$showScore"
|
||||
:show-callable="true"
|
||||
:show-reactions="$showReactions ?? true"
|
||||
:photo-blur="$guestPhotoBlur ?? 0"
|
||||
:photo-contrast="$guestPhotoContrast ?? 100"
|
||||
:photo-saturate="$guestPhotoSaturate ?? 100"
|
||||
:photo-brightness="$guestPhotoBrightness ?? 100"
|
||||
height-class="h-[430px] md:h-[550px] lg:h-[430px]"
|
||||
:truncate-excerpt="true"
|
||||
/>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
74
resources/views/livewire/main-page/event-card-full.blade.php
Normal file
74
resources/views/livewire/main-page/event-card-full.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<div>
|
||||
@if ($post != null)
|
||||
|
||||
<div class="relative flex flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px] my-9">
|
||||
<a class="relative flex cursor-pointer flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px]"
|
||||
href="{{ route('post.show_by_slug', [$post->slug]) }}">
|
||||
|
||||
<!-- Photo as background -->
|
||||
@if ($media != null)
|
||||
<div class="absolute inset-0 z-0 h-full w-full">
|
||||
{{ $media('4_3', ['class' => 'absolute inset-0 z-0 h-full w-full object-cover blur-[1px]']) }}
|
||||
</div>
|
||||
@else
|
||||
<!-- Default background if no image -->
|
||||
<div class="absolute inset-0 z-0 h-full w-full bg-gradient-to-br from-primary-700 to-theme-brand"></div>
|
||||
@endif
|
||||
|
||||
<!-- Optional overlay for contrast -->
|
||||
<div class="absolute inset-0 z-10 bg-black bg-opacity-65"></div>
|
||||
|
||||
<!-- All card content on top of photo -->
|
||||
<div class="relative z-20 flex h-full flex-col justify-between px-6 md:px-10 lg:px-14 py-14 md:py-14 lg:py-20 text-white">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start gap-4">
|
||||
<h2 class="text text-3xl lg:text-4xl font-semibold leading-tight pr-16 md:pr-20">
|
||||
{{ $post->title }}
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="inline-block items-center rounded-sm bg-theme-brand px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal">
|
||||
{{ $post->category }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-6 my-4 flex items-center justify-center">
|
||||
@if (isset($post->excerpt))
|
||||
<p class="w-full text-base md:text-xl font-normal leading-relaxed line-clamp-8">
|
||||
{{ $post->excerpt }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<!-- Bottom section: author and address -->
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
<h2 class="text-lg md:text-2xl ml-auto pt-3 font-semibold text-white">
|
||||
@if (isset($post->venue))
|
||||
{{ $post->venue . ' ' . $post->city}}
|
||||
@endif
|
||||
@if (isset($post->from))
|
||||
<span class="text-center font-semibold">
|
||||
{{ Illuminate\Support\Carbon::parse($post->from)->translatedFormat('d F') }}
|
||||
{{ Illuminate\Support\Carbon::parse($post->from)->format('H:i') . ' ' . __('messages.hour_abbrevation') }}
|
||||
</span>
|
||||
@endif
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Like button positioned absolutely outside anchor tag -->
|
||||
<div class="absolute top-14 right-4 md:right-10 lg:right-14 z-30">
|
||||
@livewire('reaction-button', [
|
||||
'typeName' => 'like',
|
||||
'showCounter' => true,
|
||||
'reactionCounter' => $post['like_count'],
|
||||
'modelClass' => $post['model'],
|
||||
'modelId' => $post['id'],
|
||||
'size' => 'w-10 h-10',
|
||||
'inverseColors' => true,
|
||||
], key('like-' . $post['model'] . '-' . $post['id'] . '-' . $postNr))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
135
resources/views/livewire/main-page/image-card-full.blade.php
Normal file
135
resources/views/livewire/main-page/image-card-full.blade.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<div x-data="{
|
||||
showFullscreen: false,
|
||||
async toggleFullscreen() {
|
||||
if (!this.showFullscreen) {
|
||||
this.showFullscreen = true;
|
||||
await this.$nextTick();
|
||||
const elem = this.$refs.fullscreenModal;
|
||||
if (elem.requestFullscreen) {
|
||||
elem.requestFullscreen();
|
||||
} else if (elem.webkitRequestFullscreen) {
|
||||
elem.webkitRequestFullscreen();
|
||||
} else if (elem.msRequestFullscreen) {
|
||||
elem.msRequestFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
this.showFullscreen = false;
|
||||
}
|
||||
}
|
||||
}"
|
||||
@fullscreenchange.window="if (!document.fullscreenElement) { showFullscreen = false }"
|
||||
@webkitfullscreenchange.window="if (!document.webkitFullscreenElement) { showFullscreen = false }"
|
||||
@msfullscreenchange.window="if (!document.msFullscreenElement) { showFullscreen = false }">
|
||||
@if ($post != null)
|
||||
|
||||
<div class="relative flex flex-col bg-theme-background my-9">
|
||||
<!-- Caption overlay calculation -->
|
||||
@php
|
||||
$bottomText = '';
|
||||
$isLink = false;
|
||||
$linkUrl = '';
|
||||
$mediaOwner = isset($post->media_owner) && !empty($post->media_owner) ? $post->media_owner : '';
|
||||
|
||||
if (isset($post->author_id) && !empty($post->author_id)) {
|
||||
$bottomText = $post->author;
|
||||
$isLink = true;
|
||||
$linkUrl = url('user/' . $post->author_id);
|
||||
} elseif (isset($post->media_caption) && !empty($post->media_caption)) {
|
||||
$bottomText = $post->media_caption;
|
||||
} elseif (isset($post->content) && !empty($post->content)) {
|
||||
$bottomText = strip_tags($post->content);
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="relative flex flex-col justify-center overflow-hidden bg-theme-background {{ !$media ? 'pt-32 pb-32' : '' }}">
|
||||
|
||||
<!-- Photo - full width within container -->
|
||||
@if ($media != null)
|
||||
<div @click="toggleFullscreen()" class="w-full mx-auto max-w-7xl cursor-pointer">
|
||||
{{ $media('hero', ['class' => 'w-full h-auto']) }}
|
||||
</div>
|
||||
|
||||
<!-- Caption overlaid on image - positioned like author in article card -->
|
||||
@if (!empty($bottomText) || !empty($mediaOwner))
|
||||
<div class="absolute bottom-0 w-full">
|
||||
<div class="w-full mx-auto max-w-7xl px-6 md:px-10 lg:px-14 py-4 md:py-6 lg:py-8">
|
||||
<div class="flex flex-col items-end gap-1">
|
||||
@if (!empty($bottomText))
|
||||
<h4 class="inline-flex items-center gap-2 rounded-sm bg-theme-brand px-3 pb-1.5 pt-1 text-base lg:text-xl font-normal text-white">
|
||||
@if ($isLink && isset($post->author_profile_photo_path) && $post->author_profile_photo_path)
|
||||
<a href="{{ $linkUrl }}" @click.stop class="flex-shrink-0">
|
||||
<img src="{{ Storage::url($post->author_profile_photo_path) }}"
|
||||
alt="{{ $bottomText }}"
|
||||
class="w-5 h-5 lg:w-6 lg:h-6 rounded-full profile-photo object-cover">
|
||||
</a>
|
||||
@endif
|
||||
@if ($isLink)
|
||||
<a href="{{ $linkUrl }}" @click.stop class="text-white hover:underline">
|
||||
{{ $bottomText }}
|
||||
</a>
|
||||
@else
|
||||
{{ $bottomText }}
|
||||
@endif
|
||||
</h4>
|
||||
@endif
|
||||
@if (!empty($mediaOwner))
|
||||
<h6 class="inline-block items-center rounded-sm bg-theme-brand mt-1 px-2 pb-0.5 pt-0.5 text-xs lg:text-sm font-normal text-white">
|
||||
{{ $mediaOwner }}
|
||||
</h6>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Excerpt text centered - big like article title (only if no media) -->
|
||||
@elseif (isset($post->excerpt) && !empty($post->excerpt))
|
||||
<div class="flex items-center justify-center px-6 md:px-10 lg:px-14">
|
||||
<h2 class="text-3xl lg:text-5xl font-semibold leading-tight text-center text-theme-text-background bg-theme-brand p-6 m-8">
|
||||
{{ $post->excerpt }}
|
||||
</h2>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-screen modal -->
|
||||
<div x-show="showFullscreen"
|
||||
x-ref="fullscreenModal"
|
||||
@click="toggleFullscreen()"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black"
|
||||
style="display: none;">
|
||||
|
||||
@if ($media != null)
|
||||
<div class="relative w-full h-full flex items-center justify-center p-4">
|
||||
<!-- Close button -->
|
||||
{{-- <button @click="toggleFullscreen()"
|
||||
class="absolute top-4 right-4 z-10 text-white hover:text-gray-300 transition-colors">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button> --}}
|
||||
|
||||
<!-- Full-screen image - use first (largest) responsive image -->
|
||||
@php
|
||||
// Get the largest responsive image URL (first in array = largest)
|
||||
$responsiveImages = $media->getResponsiveImageUrls('hero');
|
||||
$largestImageUrl = !empty($responsiveImages) ? reset($responsiveImages) : $media->getUrl('hero');
|
||||
@endphp
|
||||
<img src="{{ $largestImageUrl }}"
|
||||
alt="{{ $post->title ?? '' }}"
|
||||
class="max-w-full max-h-full object-contain"
|
||||
loading="eager">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,127 @@
|
||||
<div x-data="{
|
||||
showFullscreen: false,
|
||||
async toggleFullscreen() {
|
||||
if (!this.showFullscreen) {
|
||||
this.showFullscreen = true;
|
||||
await this.$nextTick();
|
||||
const elem = this.$refs.fullscreenModal;
|
||||
if (elem.requestFullscreen) {
|
||||
elem.requestFullscreen();
|
||||
} else if (elem.webkitRequestFullscreen) {
|
||||
elem.webkitRequestFullscreen();
|
||||
} else if (elem.msRequestFullscreen) {
|
||||
elem.msRequestFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
this.showFullscreen = false;
|
||||
}
|
||||
}
|
||||
}"
|
||||
@fullscreenchange.window="if (!document.fullscreenElement) { showFullscreen = false }"
|
||||
@webkitfullscreenchange.window="if (!document.webkitFullscreenElement) { showFullscreen = false }"
|
||||
@msfullscreenchange.window="if (!document.msFullscreenElement) { showFullscreen = false }">
|
||||
@if ($post != null)
|
||||
|
||||
<div class="relative flex flex-col bg-theme-background">
|
||||
<!-- Caption overlay calculation -->
|
||||
@php
|
||||
$bottomText = '';
|
||||
$isLink = false;
|
||||
$linkUrl = '';
|
||||
$mediaOwner = isset($post->media_owner) && !empty($post->media_owner) ? $post->media_owner : '';
|
||||
|
||||
if (isset($post->author_id) && !empty($post->author_id)) {
|
||||
$bottomText = $post->author;
|
||||
$isLink = true;
|
||||
$linkUrl = url('user/' . $post->author_id);
|
||||
} elseif (isset($post->media_caption) && !empty($post->media_caption)) {
|
||||
$bottomText = $post->media_caption;
|
||||
} elseif (isset($post->content) && !empty($post->content)) {
|
||||
$bottomText = strip_tags($post->content);
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="relative flex flex-col justify-center overflow-hidden bg-theme-background {{ !$media ? 'pt-32 pb-32' : '' }}">
|
||||
|
||||
<!-- Photo - full width within container -->
|
||||
@if ($media != null)
|
||||
<div @click="toggleFullscreen()" class="w-full mx-auto max-w-7xl cursor-pointer">
|
||||
{{ $media('hero', ['class' => 'w-full h-auto']) }}
|
||||
</div>
|
||||
|
||||
<!-- Caption overlaid on image - positioned like author in article card -->
|
||||
@if (!empty($bottomText) || !empty($mediaOwner))
|
||||
<div class="absolute bottom-0 w-full">
|
||||
<div class="w-full mx-auto max-w-7xl px-6 md:px-10 lg:px-14 py-4 md:py-6 lg:py-8">
|
||||
<div class="flex flex-col items-end gap-1">
|
||||
@if (!empty($bottomText))
|
||||
<h4 class="inline-flex items-center gap-2 rounded-sm bg-theme-brand px-3 pb-1.5 pt-1 text-base lg:text-xl font-normal text-white">
|
||||
@if ($isLink && isset($post->author_profile_photo_path) && $post->author_profile_photo_path)
|
||||
<a href="{{ $linkUrl }}" @click.stop class="flex-shrink-0">
|
||||
<img src="{{ Storage::url($post->author_profile_photo_path) }}"
|
||||
alt="{{ $bottomText }}"
|
||||
class="w-5 h-5 lg:w-6 lg:h-6 rounded-full profile-photo object-cover">
|
||||
</a>
|
||||
@endif
|
||||
@if ($isLink)
|
||||
<a href="{{ $linkUrl }}" @click.stop class="text-white hover:underline">
|
||||
{{ $bottomText }}
|
||||
</a>
|
||||
@else
|
||||
{{ $bottomText }}
|
||||
@endif
|
||||
</h4>
|
||||
@endif
|
||||
@if (!empty($mediaOwner))
|
||||
<h6 class="inline-block items-center rounded-sm bg-theme-brand mt-1 px-2 pb-0.5 pt-0.5 text-xs lg:text-sm font-normal text-white">
|
||||
{{ $mediaOwner }}
|
||||
</h6>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Excerpt text centered - big like article title (only if no media) -->
|
||||
@elseif (isset($post->excerpt) && !empty($post->excerpt))
|
||||
<div class="flex items-center justify-center px-6 md:px-10 lg:px-14">
|
||||
<h2 class="text-3xl lg:text-5xl font-semibold leading-tight text-center text-theme-text-background bg-theme-brand p-6 m-8">
|
||||
{{ $post->excerpt }}
|
||||
</h2>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-screen modal -->
|
||||
<div x-show="showFullscreen"
|
||||
x-ref="fullscreenModal"
|
||||
@click="toggleFullscreen()"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black"
|
||||
style="display: none;">
|
||||
|
||||
@if ($media != null)
|
||||
<div class="relative w-full h-full flex items-center justify-center p-4">
|
||||
<!-- Full-screen image - use first (largest) responsive image -->
|
||||
@php
|
||||
// Get the largest responsive image URL (first in array = largest)
|
||||
$responsiveImages = $media->getResponsiveImageUrls('hero');
|
||||
$largestImageUrl = !empty($responsiveImages) ? reset($responsiveImages) : $media->getUrl('hero');
|
||||
@endphp
|
||||
<img src="{{ $largestImageUrl }}"
|
||||
alt="{{ $post->title ?? '' }}"
|
||||
class="max-w-full max-h-full object-contain"
|
||||
loading="eager">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
60
resources/views/livewire/main-page/news-card-full.blade.php
Normal file
60
resources/views/livewire/main-page/news-card-full.blade.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<div>
|
||||
@if ($post != null)
|
||||
|
||||
<div class="relative flex flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px] my-9">
|
||||
<a class="relative flex cursor-pointer flex-col justify-end overflow-hidden bg-theme-background max-h-[600px] lg:max-h-[800px]"
|
||||
href="{{ route('post.show_by_slug', [$post->slug]) }}">
|
||||
|
||||
<!-- Photo as background -->
|
||||
@if ($media != null)
|
||||
<div class="absolute inset-0 z-0 h-full w-full">
|
||||
{{ $media('4_3', ['class' => 'absolute inset-0 z-0 h-full w-full object-cover blur-[1px]']) }}
|
||||
</div>
|
||||
@else
|
||||
<!-- Default background if no image -->
|
||||
<div class="absolute inset-0 z-0 h-full w-full bg-gradient-to-br from-primary-700 to-theme-brand"></div>
|
||||
@endif
|
||||
|
||||
<!-- Optional overlay for contrast -->
|
||||
<div class="absolute inset-0 z-10 bg-black bg-opacity-65"></div>
|
||||
|
||||
<!-- All card content on top of photo -->
|
||||
<div class="relative z-20 flex h-full flex-col justify-between px-6 md:px-10 lg:px-14 py-14 md:py-14 lg:py-20 text-white">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start gap-4">
|
||||
<h2 class="text text-3xl lg:text-4xl font-semibold leading-tight pr-16 md:pr-20">
|
||||
{{ $post->title }}
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="inline-block items-center rounded-sm bg-theme-brand px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal">
|
||||
{{ $post->category }}
|
||||
</h4>
|
||||
<h4 class="inline-block items-center rounded-sm bg-theme-brand ml-3 px-2 pb-1 pt-0.5 text-sm lg:text-base font-normal">
|
||||
@if (isset($post->from))
|
||||
<span class="">
|
||||
{{ Illuminate\Support\Carbon::parse($post->from)->translatedFormat('l, j F') }}
|
||||
</span>
|
||||
@endif
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-6 my-4 flex items-center justify-center">
|
||||
@if (isset($post->excerpt))
|
||||
<p class="w-full text-base md:text-xl font-normal leading-relaxed line-clamp-8">
|
||||
{{ $post->excerpt }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<!-- Bottom section: author info -->
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
<h2 class="text-lg md:text-2xl ml-auto pt-3 font-semibold text-white">
|
||||
{{ $post->author }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
236
resources/views/livewire/main-page/skills-card-full.blade.php
Normal file
236
resources/views/livewire/main-page/skills-card-full.blade.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<div class="col-span-6">
|
||||
<div>
|
||||
<!-- Skills -->
|
||||
<x-jetstream.label for="tags" value="{{ $label }}"
|
||||
wire:loading.remove />
|
||||
<x-jetstream.label for="tags" value="{{ __('Loading...') }}" wire:loading />
|
||||
<div wire:ignore>
|
||||
<input class="w-full" data-suggestions='@json($suggestions)' id="tags"
|
||||
placeholder="{{ __('Select or create a new tag title') }}" type="text"
|
||||
value="{{ $tagsArray }}" x-data="{ input: @entangle('tagsArray').live }"
|
||||
x-ref="input"
|
||||
style="color: white;"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 my-3 flex items-center justify-end px-0 pb-3 text-right">
|
||||
|
||||
<div class="grid grid-cols-1 gap-6" wire:loading wire:target="tagsArray">
|
||||
<x-mini-button flat icon="" primary rounded spinner /> <span>
|
||||
</span>
|
||||
</div>
|
||||
@if ($tagsArrayChanged)
|
||||
<div class="text-sm text-theme-primary mr-3">
|
||||
{{ __('You have unsaved changes')}}
|
||||
</div>
|
||||
@endif
|
||||
<x-jetstream.action-message class="mr-3" on="saved">
|
||||
{{ __('Saved') }}
|
||||
</x-jetstream.action-message>
|
||||
<x-jetstream.button
|
||||
wire:click="saveDisabled === true ? $dispatch('updatedTagsArray') : $dispatch('save')"
|
||||
wire:target="save"
|
||||
wire:loading.attr="disabled"
|
||||
wire:key="saveTagsArrayButton">
|
||||
{{ __('Save') }}
|
||||
</x-jetstream.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---- New Tag Modal ---->
|
||||
@if ($newTag)
|
||||
<form wire:submit.prevent="createTag">
|
||||
|
||||
<x-jetstream.dialog-modal wire:model.live="modalVisible">
|
||||
<x-slot name="title">
|
||||
{{ str_replace('@PLATFORM_NAME@', platform_name(), __('Add a new activity or skill to @PLATFORM_NAME@')) }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<div class='my-3 text-xl'>
|
||||
<span
|
||||
class="bg-{{ $categoryColor }}-300 inline-flex items-center rounded-md px-3 py-2 text-sm font-normal">
|
||||
{{ $newTag['name'] }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-1 gap-6">
|
||||
<x-input label="{{ __('Activity tag (min. 2 words)') }}"
|
||||
placeholder="{{ __('Accurate and unique name for this activity, avoid vague or general keywords') }}"
|
||||
wire:model.live="newTag.name" />
|
||||
</div>
|
||||
@if (!$sessionLanguageOk)
|
||||
<div class="mt-3 grid grid-cols-1 gap-6">
|
||||
@php $locale = app()->getLocale();
|
||||
$localeName = \Locale::getDisplayName($locale, $locale);
|
||||
@endphp
|
||||
<x-checkbox :disabled="$sessionLanguageOk" id="sessionLang-ignore-checkbox"
|
||||
label="{{ __('This tag is in :locale.', [
|
||||
'locale' => $localeName
|
||||
]) }}"
|
||||
wire:model.live="sessionLanguageIgnored" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($translationPossible && $translationAllowed)
|
||||
<div class="mt-6 grid grid-cols-1 gap-6 ">
|
||||
<x-checkbox id="checkbox"
|
||||
label="{{ __('Attach a translation to this tag (recommended)') }}"
|
||||
wire:model.live="translationVisible" />
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-6" wire:loading wire:target="translationVisible">
|
||||
<x-mini-button flat icon="" primary rounded spinner /> <span>
|
||||
{{ __('Loading...') }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Tag Translation --->
|
||||
|
||||
<div>
|
||||
@if ($translationVisible)
|
||||
|
||||
<div class="my-6 grid grid-cols-1 gap-6 pl-8 md:grid-cols-2"
|
||||
id='select-translation-language'>
|
||||
<x-select :options="$translationLanguages" class="placeholder-theme-light"
|
||||
id="translation-language" label="{{__('Translation language')}}" option-label="name"
|
||||
option-value="lang_code" placeholder="{{ __('Select a translation language') }}"
|
||||
wire:model.live="selectTranslationLanguage"/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 pl-8" wire:loading wire:target="selectTranslationLanguage">
|
||||
<x-mini-button flat icon="" primary rounded spinner /> <span>
|
||||
{{ __('Loading...') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if ($selectTranslationLanguage)
|
||||
|
||||
@php
|
||||
if ($selectTranslationLanguage) {
|
||||
$translationLang = App\Models\Language::where('lang_code', $selectTranslationLanguage)->first()->name;
|
||||
} else {
|
||||
$translationLang = "...";
|
||||
}
|
||||
@endphp
|
||||
|
||||
<hr class="border-t border-theme-primary" py-12 />
|
||||
<x-radio id="radio-select"
|
||||
label="{{ str_replace('@LANGUAGE@', $translationLang, __('Select an existing, untranslated activity tag in @LANGUAGE@')) }}"
|
||||
value="select" wire:model.live="translateRadioButton" />
|
||||
<div class="my-6 grid grid-cols-1 gap-6 pl-8 md:grid-cols-2"
|
||||
id='select-translation'>
|
||||
@if (count($translationOptions) > 0)
|
||||
<x-select
|
||||
:disabled="$translateRadioButton === 'input'"
|
||||
:options="$translationOptions"
|
||||
class="placeholder-theme-light"
|
||||
id="translation"
|
||||
label=""
|
||||
option-label="name"
|
||||
option-value="tag_id"
|
||||
placeholder="{{ __('Select a translation') }}"
|
||||
wire:model.live="selectTagTranslation"
|
||||
/>
|
||||
@else
|
||||
<x-select
|
||||
:disabled="count($translationOptions) < 1 || $translateRadioButton === 'input'"
|
||||
:options="count($translationOptions) > 0
|
||||
? $translationOptions
|
||||
: [['tag_id' => '', 'name' => __('No translations available'), 'disabled' => true]]"
|
||||
class="placeholder-theme-light"
|
||||
id="translation"
|
||||
label=""
|
||||
option-label="name"
|
||||
option-value="tag_id"
|
||||
placeholder="{{ __('No existing translation available') }}"
|
||||
wire:model.live="selectTagTranslation"
|
||||
/>
|
||||
@endif
|
||||
<div class="mt-6 grid grid-cols-1 gap-6" wire:loading
|
||||
wire:target="translationOptions">
|
||||
{{ __('Updating...') }}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="border-t border-theme-primary" py-12 />
|
||||
<x-radio id="radio-input"
|
||||
label="{{ str_replace('@LANGUAGE@', $translationLang, __('Or create a new Activity tag in @LANGUAGE@')) }}"
|
||||
value="input" wire:model.live="translateRadioButton" />
|
||||
<div id="input-translation">
|
||||
<div class="mt-6 grid grid-cols-1 gap-6 pl-8">
|
||||
|
||||
<x-input :disabled="$translateRadioButton === 'select'"
|
||||
label="{{ str_replace('@LANGUAGE@', $translationLang, __('Activity tag in @LANGUAGE@ (min. 2 words)')) }}"
|
||||
placeholder="{{ !empty($newTag['name'])
|
||||
? '\'' . $newTag['name'] . '\'' . ' ' . __('in') . ' ' . $translationLang
|
||||
: __('Activity tag name in') . ' ' . $translationLang }}"
|
||||
wire:key="nameInput" wire:model.live="inputTagTranslation.name" />
|
||||
@if (!$transLanguageOk)
|
||||
<div class="grid grid-cols-1 gap-6 transition-opacity duration-1500 ease-in-out opacity-100">
|
||||
@php $localeTranslation = $selectTranslationLanguage ?? '';
|
||||
$localeNameTranslation = \Locale::getDisplayName($localeTranslation, $localeTranslation);
|
||||
@endphp
|
||||
<x-checkbox :disabled="$translateRadioButton === 'select'" id="transLang-ignore-checkbox"
|
||||
label="{{ __('This tag is in :localeTranslation.', [
|
||||
'localeTranslation' => $localeNameTranslation
|
||||
]) }}"
|
||||
wire:model.live="transLanguageIgnored"
|
||||
/>
|
||||
</div>
|
||||
@endif
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-select :disabled="$translateRadioButton === 'select'" :options="$categoryOptions"
|
||||
class="placeholder-theme-light" id="category"
|
||||
label="{{ __('Category') }}" option-label="name"
|
||||
option-value="category_id"
|
||||
placeholder="{{ __('Select a category') }}"
|
||||
wire:model.live="newTagCategory" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (!$translationVisible || !$translationAllowed)
|
||||
<div class="mt-3 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-select :options="$categoryOptions" class="placeholder-theme-light" id="category"
|
||||
label="{{ __('Category') }}" option-label="name" option-value="category_id"
|
||||
placeholder="{{ __('Select a category') }}"
|
||||
wire:model.live="newTagCategory" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="pt-10 my-3 grid grid-cols-1">
|
||||
<x-skill-tag-warning />
|
||||
</div>
|
||||
{{-- <x-errors /> --}}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="footer">
|
||||
<x-jetstream.secondary-button wire:click="cancelCreateTag" wire:loading.attr="disabled">
|
||||
{{ __('Cancel') }}
|
||||
</x-jetstream.secondary-button>
|
||||
|
||||
<x-jetstream.secondary-button class="ml-3"
|
||||
wire:click="createTag"
|
||||
wire:key="createTagButton"
|
||||
wire:loading.attr="disabled">
|
||||
{{ __('Save') }}
|
||||
</x-jetstream.secondary-button>
|
||||
</x-slot>
|
||||
</x-jetstream.dialog-modal>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<script src="{{ asset('js/skilltags.js') }}"></script>
|
||||
<script>
|
||||
document.addEventListener('livewire:load', () => {
|
||||
// Listener for page reload event
|
||||
document.addEventListener('reloadPage', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
Reference in New Issue
Block a user