24-8: articles and legal pages — implemented, QA Review
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,7 @@ html[dir=ltr] body.site .site-progress{transform-origin:left}
|
||||
body.site section{position:relative}
|
||||
body.site .sec{padding:120px 0}
|
||||
body.site .sec-head{display:flex;align-items:end;justify-content:space-between;gap:32px;margin-bottom:54px;flex-wrap:wrap}
|
||||
body.site .sec-head h2{font-size:clamp(2rem,3.4vw,3rem);margin-top:12px}
|
||||
body.site .sec-head h2,body.site .sec-head h1{font-size:clamp(2rem,3.4vw,3rem);margin-top:12px}
|
||||
body.site .sec-head p{max-width:30em;color:var(--muted);margin:0}
|
||||
|
||||
|
||||
@@ -338,6 +338,26 @@ body.site .row-static:hover{background:none;color:inherit;transform:none;box-sha
|
||||
body.site .row-static::before{display:none}
|
||||
@media (max-width:760px){body.site .row-static{grid-template-columns:52px 1fr}}
|
||||
|
||||
/* ---------- Articles and legal pages (24-8) ---------- */
|
||||
body.site .search{min-height:48px;border-radius:999px;padding:12px 20px;border:1.5px solid rgba(45,54,36,.18);background:#fff;width:min(360px,100%);font:400 1rem var(--body);color:var(--ink);transition:border-color .2s,box-shadow .2s}
|
||||
body.site .search:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 4px rgba(166,137,102,.2)}
|
||||
body.site .search-info{color:var(--muted);margin:0 0 24px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
|
||||
body.site .search-info .btn{min-height:44px;padding:8px 18px}
|
||||
body.site .posts-index .post:last-child{grid-column:auto}
|
||||
body.site .pagination-wrap{margin-top:40px}
|
||||
body.site .pagination-wrap nav>div:first-child{display:none}
|
||||
body.site .pagination-wrap nav a,body.site .pagination-wrap nav span[aria-disabled] span,body.site .pagination-wrap nav span[aria-current] span{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;padding:0 14px;border-radius:999px;border:1.5px solid var(--line);background:#fff;color:var(--ink);text-decoration:none;font-weight:500}
|
||||
body.site .pagination-wrap nav a:hover{border-color:var(--gold)}
|
||||
body.site .pagination-wrap nav span[aria-current] span{background:var(--forest);border-color:var(--forest);color:var(--cream)}
|
||||
body.site .pagination-wrap nav span[aria-disabled] span{opacity:.5}
|
||||
body.site .site-prose :is(h2,h3,h4){font-family:var(--display)}
|
||||
body.site .site-prose a{color:var(--olive)}
|
||||
body.site .site-prose{font-size:1.06rem;line-height:1.85}
|
||||
body.site .post-meta,body.site article>.meta{display:block;font-size:.85rem;font-weight:600;color:var(--gold-ink)}
|
||||
body.site .legal-card{background:#fff;border-radius:16px;box-shadow:var(--shadow-1);padding:40px}
|
||||
body.site .legal-updated{font-size:.85rem;color:var(--muted);margin:16px 0 0}
|
||||
@media (max-width:760px){body.site .legal-card{padding:24px}}
|
||||
|
||||
/* ---------- Reveal (hidden only when JavaScript runs) ---------- */
|
||||
body.site.js [data-reveal]{opacity:0;transform:translateY(28px);transition:opacity .9s var(--ease),transform .9s var(--ease)}
|
||||
body.site.js [data-reveal].in{opacity:1;transform:none}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@props(['label', 'title', 'lede' => null])
|
||||
@props(['label', 'title', 'lede' => null, 'tag' => 'h2'])
|
||||
|
||||
<div class="sec-head">
|
||||
<div>
|
||||
<span class="art-no" data-reveal>{{ $label }}</span>
|
||||
<h2 data-reveal data-d="1">{{ $title }}</h2>
|
||||
<{{ $tag }} data-reveal data-d="1">{{ $title }}</{{ $tag }}>
|
||||
</div>
|
||||
@if ($lede)
|
||||
<p data-reveal data-d="2">{{ $lede }}</p>
|
||||
|
||||
@@ -278,7 +278,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
||||
@else
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap">
|
||||
<x-site.section-head :label="__('site.art_booking')" :title="__('booking.request_consultation')" :lede="__('site.booking_lede')" />
|
||||
<x-site.section-head tag="h1" :label="__('site.art_booking')" :title="__('booking.request_consultation')" :lede="__('site.booking_lede')" />
|
||||
|
||||
<div class="book">
|
||||
<div class="book-l on-dark">
|
||||
|
||||
@@ -55,91 +55,44 @@ new #[Layout('components.layouts.public')] class extends Component
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="wrap py-8">
|
||||
<div class="max-w-4xl mx-auto px-0">
|
||||
<flux:heading size="xl" class="text-body text-xl sm:text-2xl lg:text-3xl">{{ __('posts.posts') }}</flux:heading>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div class="mt-4 sm:mt-6 relative">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('posts.search_placeholder') }}"
|
||||
class="w-full min-h-[44px]"
|
||||
>
|
||||
<x-slot:iconLeading>
|
||||
<flux:icon name="magnifying-glass" class="w-5 h-5 text-body/50" />
|
||||
</x-slot:iconLeading>
|
||||
</flux:input>
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap">
|
||||
<x-site.section-head tag="h1" :label="__('site.hero_eyebrow')" :title="__('site.nav_articles')">
|
||||
<div style="position:relative">
|
||||
<label class="sr-only" for="posts-search">{{ __('posts.search_placeholder') }}</label>
|
||||
<input
|
||||
id="posts-search"
|
||||
type="search"
|
||||
class="search"
|
||||
wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('posts.search_placeholder') }}"
|
||||
>
|
||||
</div>
|
||||
</x-site.section-head>
|
||||
|
||||
@if($search)
|
||||
<button
|
||||
wire:click="clearSearch"
|
||||
class="absolute end-3 top-1/2 -translate-y-1/2 text-body/50 hover:text-body min-h-[44px] min-w-[44px] flex items-center justify-center"
|
||||
>
|
||||
<flux:icon name="x-mark" class="w-5 h-5" />
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Search Results Info -->
|
||||
@if($search)
|
||||
<p class="mt-4 text-sm text-body/70">
|
||||
@if($posts->total() > 0)
|
||||
{{ __('posts.search_results', ['count' => $posts->total(), 'query' => $search]) }}
|
||||
@else
|
||||
{{ __('posts.no_results', ['query' => $search]) }}
|
||||
@endif
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<!-- Posts List -->
|
||||
<div class="mt-6 sm:mt-8 space-y-4 sm:space-y-6">
|
||||
@forelse($posts as $post)
|
||||
<article wire:key="post-{{ $post->id }}" class="bg-card p-4 sm:p-6 rounded-lg shadow-card hover:shadow-card-hover transition-shadow">
|
||||
<h2 class="text-lg sm:text-xl font-semibold text-text">
|
||||
<a href="{{ route('posts.show', $post) }}" class="hover:text-cta" wire:navigate>
|
||||
@if($search)
|
||||
{!! $this->highlightSearch($post->getTitle(), $search) !!}
|
||||
@else
|
||||
{{ $post->getTitle() }}
|
||||
@endif
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<time class="text-xs sm:text-sm text-body/70 mt-2 block">
|
||||
{{ $post->published_at?->translatedFormat('d F Y') ?? $post->created_at->translatedFormat('d F Y') }}
|
||||
</time>
|
||||
|
||||
<p class="mt-2 sm:mt-3 text-body text-sm sm:text-base">
|
||||
@if($search)
|
||||
{!! $this->highlightSearch($post->getExcerpt(), $search) !!}
|
||||
@else
|
||||
{{ $post->getExcerpt() }}
|
||||
@endif
|
||||
</p>
|
||||
|
||||
<a href="{{ route('posts.show', $post) }}" class="text-cta hover:text-cta-hover hover:underline mt-3 sm:mt-4 inline-flex items-center min-h-[44px]" wire:navigate>
|
||||
{{ __('posts.read_more') }} →
|
||||
</a>
|
||||
</article>
|
||||
@empty
|
||||
<div class="empty-state bg-card rounded-lg">
|
||||
@if($search)
|
||||
<flux:icon name="magnifying-glass" class="empty-state-icon text-body/30" />
|
||||
<p class="text-body/70">{{ __('posts.no_results', ['query' => $search]) }}</p>
|
||||
<flux:button wire:click="clearSearch" class="mt-4">
|
||||
{{ __('posts.clear_search') }}
|
||||
</flux:button>
|
||||
<p class="search-info">
|
||||
@if($posts->total() > 0)
|
||||
{{ __('posts.search_results', ['count' => $posts->total(), 'query' => $search]) }}
|
||||
@else
|
||||
<flux:icon name="document-text" class="empty-state-icon text-body/40" />
|
||||
<p class="text-body/70">{{ __('posts.no_posts') }}</p>
|
||||
{{ __('posts.no_results', ['query' => $search]) }}
|
||||
@endif
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
<button type="button" class="btn btn-ghost" wire:click="clearSearch">{{ __('posts.clear_search') }}</button>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<div class="mt-8">
|
||||
{{ $posts->links() }}
|
||||
@if($posts->count() > 0)
|
||||
<div class="posts posts-index">
|
||||
@foreach($posts as $post)
|
||||
<x-site.post-card :post="$post" wire:key="post-{{ $post->id }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif(! $search)
|
||||
<p class="text-center" style="color:var(--muted)">{{ __('posts.no_posts') }}</p>
|
||||
@endif
|
||||
|
||||
<div class="pagination-wrap">
|
||||
{{ $posts->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -18,24 +18,17 @@ new #[Layout('components.layouts.public')] class extends Component
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="wrap py-8">
|
||||
<article class="max-w-3xl mx-auto">
|
||||
<header class="mb-8">
|
||||
<flux:heading size="xl" class="text-body">{{ $post->getTitle() }}</flux:heading>
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<article class="wrap" style="max-width:768px">
|
||||
<span class="meta">{{ $post->published_at?->translatedFormat('l, d F Y') ?? $post->created_at->translatedFormat('l, d F Y') }}</span>
|
||||
<h1 style="font-size:clamp(2rem,3.4vw,3rem);margin:10px 0 28px">{{ $post->getTitle() }}</h1>
|
||||
|
||||
<time class="text-body/70 mt-2 block">
|
||||
{{ $post->published_at?->translatedFormat('l, d F Y') ?? $post->created_at->translatedFormat('l, d F Y') }}
|
||||
</time>
|
||||
</header>
|
||||
<div class="prose prose-lg prose-brand max-w-none site-prose">
|
||||
{!! $post->getBody() !!}
|
||||
</div>
|
||||
|
||||
<div class="prose prose-lg prose-brand max-w-none">
|
||||
{!! $post->getBody() !!}
|
||||
</div>
|
||||
|
||||
<footer class="mt-12 pt-6 border-t border-cta/20">
|
||||
<a href="{{ route('posts.index') }}" class="text-cta hover:text-cta-hover hover:underline" wire:navigate>
|
||||
← {{ __('posts.back_to_posts') }}
|
||||
</a>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
<footer style="border-top:1px solid var(--line);margin-top:48px;padding-top:24px">
|
||||
<a class="btn btn-ghost" href="{{ route('posts.index') }}" wire:navigate>{{ __('posts.back_to_posts') }}</a>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<x-layouts.public>
|
||||
<div class="wrap py-8">
|
||||
<h1 class="mb-6 text-3xl font-bold text-text">{{ $page->getTitle() }}</h1>
|
||||
<div class="prose prose-brand max-w-none rounded-lg bg-card p-8 shadow-card" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
||||
@if ($page->getContent())
|
||||
{!! clean($page->getContent()) !!}
|
||||
@else
|
||||
<p class="text-body">{{ __('pages.content_coming_soon') }}</p>
|
||||
@endif
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap" style="max-width:768px">
|
||||
<h1 style="font-size:clamp(2rem,3.4vw,3rem);margin:0 0 28px">{{ $page->getTitle() }}</h1>
|
||||
<div class="prose prose-brand max-w-none site-prose legal-card" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
||||
@if ($page->getContent())
|
||||
{!! clean($page->getContent()) !!}
|
||||
@else
|
||||
<p>{{ __('pages.content_coming_soon') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<p class="legal-updated">
|
||||
{{ __('pages.last_updated') }}: {{ $page->updated_at->format('M d, Y') }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-body/70">
|
||||
{{ __('pages.last_updated') }}: {{ $page->updated_at->format('M d, Y') }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</x-layouts.public>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<x-layouts.public>
|
||||
<div class="wrap py-8">
|
||||
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.privacy') }}</h1>
|
||||
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
||||
<p class="text-body">{{ __('Privacy policy content will be added here.') }}</p>
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap" style="max-width:768px">
|
||||
<h1 style="font-size:clamp(2rem,3.4vw,3rem);margin:0 0 28px">{{ __('footer.privacy') }}</h1>
|
||||
<div class="prose prose-brand max-w-none site-prose legal-card">
|
||||
<p>{{ __('Privacy policy content will be added here.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</x-layouts.public>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<x-layouts.public>
|
||||
<div class="wrap py-8">
|
||||
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.terms') }}</h1>
|
||||
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
||||
<p class="text-body">{{ __('Terms of service content will be added here.') }}</p>
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap" style="max-width:768px">
|
||||
<h1 style="font-size:clamp(2rem,3.4vw,3rem);margin:0 0 28px">{{ __('footer.terms') }}</h1>
|
||||
<div class="prose prose-brand max-w-none site-prose legal-card">
|
||||
<p>{{ __('Terms of service content will be added here.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</x-layouts.public>
|
||||
|
||||
Reference in New Issue
Block a user