Commit 36e7bbf3 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(website): reachable dropdowns, one h1 per page, and the content a reference sweep found missing

A pointer-level test and a page-by-page diff against the reference site, rather
than reading the markup.

Dropdowns could be opened but not used. The panel sat 4px below its trigger with
nothing in the gap, and the menu closed the instant the pointer left the
trigger's box — so travelling to an item crossed dead space and the menu shut
before the click landed. The panel now starts flush against the trigger
(`top-full`, padding inside the panel rather than a margin outside it), and the
close is delayed and cancellable. Same contract applied to the language
switcher, which had the identical gap. Verified by dispatching real mouse moves
along the path a hand takes: the menu survives the trip and the click lands on
/en/football.

`columns` silently dropped its children. It reads them from slots col1..colN, so
a child in the generic `default` slot — what a blueprint import or a hand-built
tree naturally produces — matched no column and vanished with no error and no
empty box. That is how the about-us video disappeared. Loose children are now
dealt out across the columns.

Nine of eleven pages had no h1 at all. Blocks hardcoded their heading level, so
whichever section carried a page's title rendered as h2 and the document had no
top-level heading. `heading_level` is now part of the section-header contract
(and of text_image, video, profile_card, contact_form, app_download, cta,
rich_text), so the section carrying the page title is an authoring decision
rather than an accident of which block was used. Every page now has exactly one.

Also from the sweep:
- El3ab's entire privacy policy — nine sections, both languages — was absent
  from our page. Restored from the reference render.
- The line the reference shows above its contact form was missing; contact_form
  gains `form_intro`.
- The chairman's name was a <p> on top of the portrait; it is a heading.
- Footer column titles were <h4> with no <h3> above them, a skipped level in the
  outline screen readers navigate by. Promoted to <h3>.
- `heading_rule` had been added to app_download *inside* its features repeater,
  so every feature row offered a meaningless "underline" control and the section
  heading offered none. Moved to where the heading actually is.

Verified: 22 page/locale combinations answer 200 with no logged block failures,
137 block/variant combinations render, every page has exactly one h1, the
carousel still does not scroll the page, and the suite passes.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 737b1fdb
...@@ -28,6 +28,12 @@ protected function sectionHeaderFields(): array ...@@ -28,6 +28,12 @@ protected function sectionHeaderFields(): array
BlockField::alignment('header_align', 'محاذاة العنوان'), BlockField::alignment('header_align', 'محاذاة العنوان'),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)',
'h1' => 'عنوان الصفحة (h1)',
])->default('h2')
->help('اختر h1 للقسم الذي يحمل عنوان الصفحة — صفحة بلا h1 تُقرأ بلا عنوان'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [ BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'none' => 'بدون',
'accent_bar' => 'شريط بلون التمييز', 'accent_bar' => 'شريط بلون التمييز',
......
...@@ -44,6 +44,12 @@ public function fields(): array ...@@ -44,6 +44,12 @@ public function fields(): array
return [ return [
BlockField::text('eyebrow', 'نص علوي صغير'), BlockField::text('eyebrow', 'نص علوي صغير'),
BlockField::text('title', 'العنوان')->required(), BlockField::text('title', 'العنوان')->required(),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)', 'h1' => 'عنوان الصفحة (h1)',
])->default('h2'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'),
BlockField::richText('description', 'الوصف'), BlockField::richText('description', 'الوصف'),
BlockField::image('screenshot', 'لقطة من التطبيق'), BlockField::image('screenshot', 'لقطة من التطبيق'),
BlockField::text('ios_url', 'رابط App Store'), BlockField::text('ios_url', 'رابط App Store'),
...@@ -57,9 +63,6 @@ public function fields(): array ...@@ -57,9 +63,6 @@ public function fields(): array
BlockField::repeater('features', 'مميزات التطبيق', [ BlockField::repeater('features', 'مميزات التطبيق', [
BlockField::icon('icon', 'أيقونة'), BlockField::icon('icon', 'أيقونة'),
BlockField::text('title', 'العنوان'), BlockField::text('title', 'العنوان'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'),
BlockField::text('body', 'الوصف'), BlockField::text('body', 'الوصف'),
]), ]),
]; ];
......
...@@ -50,6 +50,8 @@ public function fields(): array ...@@ -50,6 +50,8 @@ public function fields(): array
return [ return [
...$this->sectionHeaderFields(), ...$this->sectionHeaderFields(),
BlockField::textarea('description', 'الوصف'), BlockField::textarea('description', 'الوصف'),
BlockField::textarea('form_intro', 'نص فوق النموذج')
->help('سطر يمهّد للنموذج، يظهر أعلى الحقول'),
BlockField::text('submit_label', 'نص زر الإرسال') BlockField::text('submit_label', 'نص زر الإرسال')
->default(['ar' => 'إرسال', 'en' => 'Send']), ->default(['ar' => 'إرسال', 'en' => 'Send']),
BlockField::textarea('success_message', 'رسالة النجاح'), BlockField::textarea('success_message', 'رسالة النجاح'),
......
...@@ -44,6 +44,9 @@ public function fields(): array ...@@ -44,6 +44,9 @@ public function fields(): array
{ {
return [ return [
BlockField::text('title', 'العنوان')->required(), BlockField::text('title', 'العنوان')->required(),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)', 'h1' => 'عنوان الصفحة (h1)',
])->default('h2'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [ BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز', 'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'), ])->default('none'),
......
...@@ -54,6 +54,9 @@ public function fields(): array ...@@ -54,6 +54,9 @@ public function fields(): array
return [ return [
BlockField::text('eyebrow', 'نص علوي صغير'), BlockField::text('eyebrow', 'نص علوي صغير'),
BlockField::text('section_title', 'عنوان القسم'), BlockField::text('section_title', 'عنوان القسم'),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)', 'h1' => 'عنوان الصفحة (h1)',
])->default('h2'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [ BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز', 'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'), ])->default('none'),
......
...@@ -48,6 +48,13 @@ public function fields(): array ...@@ -48,6 +48,13 @@ public function fields(): array
{ {
return [ return [
BlockField::text('title', 'العنوان'), BlockField::text('title', 'العنوان'),
BlockField::select('heading_level', 'مستوى العنوان', [
'h1' => 'عنوان الصفحة (h1)', 'h2' => 'عنوان قسم (h2)',
])->default('h1')
->help('اجعله h2 إذا كان هذا القسم داخل صفحة لها عنوان آخر'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'),
BlockField::text('last_updated', 'آخر تحديث'), BlockField::text('last_updated', 'آخر تحديث'),
BlockField::richText('body', 'المحتوى'), BlockField::richText('body', 'المحتوى'),
// Numbered legal clauses, each optionally deep-linkable. // Numbered legal clauses, each optionally deep-linkable.
......
...@@ -45,6 +45,9 @@ public function fields(): array ...@@ -45,6 +45,9 @@ public function fields(): array
BlockField::text('eyebrow', 'نص علوي صغير'), BlockField::text('eyebrow', 'نص علوي صغير'),
BlockField::text('title', 'العنوان')->required(), BlockField::text('title', 'العنوان')->required(),
BlockField::richText('body', 'النص'), BlockField::richText('body', 'النص'),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)', 'h1' => 'عنوان الصفحة (h1)',
])->default('h2'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [ BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز', 'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'), ])->default('none'),
......
...@@ -44,6 +44,9 @@ public function fields(): array ...@@ -44,6 +44,9 @@ public function fields(): array
return [ return [
BlockField::text('eyebrow', 'نص علوي صغير'), BlockField::text('eyebrow', 'نص علوي صغير'),
BlockField::text('title', 'العنوان'), BlockField::text('title', 'العنوان'),
BlockField::select('heading_level', 'مستوى العنوان', [
'h2' => 'عنوان قسم (h2)', 'h1' => 'عنوان الصفحة (h1)',
])->default('h2'),
BlockField::select('heading_rule', 'خط أسفل العنوان', [ BlockField::select('heading_rule', 'خط أسفل العنوان', [
'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز', 'none' => 'بدون', 'accent_bar' => 'شريط بلون التمييز',
])->default('none'), ])->default('none'),
......
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
$headerTitle = $block->get('title'); $headerTitle = $block->get('title');
$subtitle = $block->get('subtitle'); $subtitle = $block->get('subtitle');
$headingSize = $headingSize ?? 'text-3xl sm:text-4xl'; $headingSize = $headingSize ?? 'text-3xl sm:text-4xl';
// A page needs exactly one h1. Which section carries it is an authoring
// decision, so it is a field rather than a guess about block order.
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
@endphp @endphp
@if (filled($eyebrow) || filled($headerTitle) || filled($subtitle)) @if (filled($eyebrow) || filled($headerTitle) || filled($subtitle))
...@@ -37,9 +41,9 @@ ...@@ -37,9 +41,9 @@
@endif @endif
@if (filled($headerTitle)) @if (filled($headerTitle))
<h2 class="ec-heading {{ $headingSize }} font-black leading-tight {{ $ruleClass }}"> <{{ $headingTag }} class="ec-heading {{ $headingSize }} font-black leading-tight {{ $ruleClass }}">
{!! website_highlight($headerTitle) !!} {!! website_highlight($headerTitle) !!}
</h2> </{{ $headingTag }}>
@endif @endif
@if (filled($subtitle)) @if (filled($subtitle))
......
...@@ -13,7 +13,13 @@ ...@@ -13,7 +13,13 @@
@if ($block->get('eyebrow')) @if ($block->get('eyebrow'))
<p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p> <p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p>
@endif @endif
<h2 class="ec-heading text-3xl sm:text-4xl font-bold">{{ $block->get('title') }}</h2> @php
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
$ruleClass = $block->get('heading_rule') === 'accent_bar' ? 'ec-heading-rule ec-heading-rule--start' : '';
@endphp
<{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-bold {{ $ruleClass }}">
{!! website_highlight($block->get('title')) !!}
</{{ $headingTag }}>
@if ($block->get('description')) @if ($block->get('description'))
<div class="ec-prose ec-muted leading-relaxed">{!! clean_html($block->get('description')) !!}</div> <div class="ec-prose ec-muted leading-relaxed">{!! clean_html($block->get('description')) !!}</div>
@endif @endif
......
...@@ -11,11 +11,27 @@ ...@@ -11,11 +11,27 @@
$valign = ['start' => 'items-start', 'center' => 'items-center', 'stretch' => 'items-stretch'][$block->get('vertical_align', 'stretch')] ?? 'items-stretch'; $valign = ['start' => 'items-start', 'center' => 'items-center', 'stretch' => 'items-stretch'][$block->get('vertical_align', 'stretch')] ?? 'items-stretch';
$reverse = $block->get('reverse_on_mobile') ? 'flex-col-reverse md:flex-row' : ''; $reverse = $block->get('reverse_on_mobile') ? 'flex-col-reverse md:flex-row' : '';
@endphp @endphp
@php
/*
* Columns read their children from the numbered slots col1..colN. A child
* sitting in the generic 'default' slot — which is what a blueprint import
* or a hand-built tree naturally produces — matched no column and was
* dropped without a trace: no error, no empty box, just missing content.
* Those children are dealt out across the columns instead.
*/
$loose = $block->children->where('slot', 'default')->values();
@endphp
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 {{ $grid }} {{ $gap }} {{ $valign }} {{ $reverse }}"> <div class="grid grid-cols-1 {{ $grid }} {{ $gap }} {{ $valign }} {{ $reverse }}">
@for ($i = 1; $i <= $count; $i++) @for ($i = 1; $i <= $count; $i++)
<div class="ec-column min-w-0"> <div class="ec-column min-w-0">
{!! $renderer->renderChildren($block, 'col' . $i, $ctx) !!} {!! $renderer->renderChildren($block, 'col' . $i, $ctx) !!}
@php $spill = $loose->slice($i - 1)->filter(fn ($c, $k) => ($k - ($i - 1)) % $count === 0); @endphp
@if ($spill->isNotEmpty())
{!! $renderer->renderMany($spill, $ctx) !!}
@endif
</div> </div>
@endfor @endfor
</div> </div>
......
...@@ -11,10 +11,11 @@ ...@@ -11,10 +11,11 @@
$ruleClass = $block->get('heading_rule') === 'accent_bar' $ruleClass = $block->get('heading_rule') === 'accent_bar'
? 'ec-heading-rule ec-heading-rule--'.$ruleAlign ? 'ec-heading-rule ec-heading-rule--'.$ruleAlign
: ''; : '';
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
@endphp @endphp
<h2 class="ec-heading text-3xl sm:text-4xl font-black {{ $ruleClass }}"> <{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-black {{ $ruleClass }}">
{!! website_highlight($block->get('title')) !!} {!! website_highlight($block->get('title')) !!}
</h2> </{{ $headingTag }}>
@endif @endif
@if ($block->get('description')) @if ($block->get('description'))
<p class="ec-muted text-lg leading-relaxed">{{ $block->get('description') }}</p> <p class="ec-muted text-lg leading-relaxed">{{ $block->get('description') }}</p>
...@@ -25,6 +26,9 @@ ...@@ -25,6 +26,9 @@
</div> </div>
<div class="{{ $variant === 'boxed' ? 'ec-surface rounded-2xl p-8 shadow-sm' : '' }}"> <div class="{{ $variant === 'boxed' ? 'ec-surface rounded-2xl p-8 shadow-sm' : '' }}">
@if ($block->get('form_intro'))
<p class="ec-muted mb-6 text-lg leading-relaxed">{{ $block->get('form_intro') }}</p>
@endif
@livewire('public.website-contact-form', [ @livewire('public.website-contact-form', [
'blockId' => $block->id, 'blockId' => $block->id,
'showSubject' => (bool) $block->get('show_subject', true), 'showSubject' => (bool) $block->get('show_subject', true),
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
$centered = in_array($variant, ['centered', 'quote_focus'], true); $centered = in_array($variant, ['centered', 'quote_focus'], true);
$overlay = $variant === 'overlay_split'; $overlay = $variant === 'overlay_split';
// A page needs exactly one h1; which section carries it is authored.
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
$ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start'; $ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start';
$ruleClass = $block->get('heading_rule') === 'accent_bar' $ruleClass = $block->get('heading_rule') === 'accent_bar'
? 'ec-heading-rule ec-heading-rule--'.$ruleAlign ? 'ec-heading-rule ec-heading-rule--'.$ruleAlign
...@@ -18,9 +20,9 @@ ...@@ -18,9 +20,9 @@
<p class="ec-eyebrow mb-3">{{ $block->get('eyebrow') }}</p> <p class="ec-eyebrow mb-3">{{ $block->get('eyebrow') }}</p>
@endif @endif
@if ($block->get('section_title')) @if ($block->get('section_title'))
<h2 class="ec-heading text-3xl sm:text-4xl font-black {{ $ruleClass }}"> <{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-black {{ $ruleClass }}">
{!! website_highlight($block->get('section_title')) !!} {!! website_highlight($block->get('section_title')) !!}
</h2> </{{ $headingTag }}>
@endif @endif
</header> </header>
@endif @endif
...@@ -37,7 +39,7 @@ class="w-full {{ $centered ? 'aspect-square rounded-full' : 'aspect-[4/5] rounde ...@@ -37,7 +39,7 @@ class="w-full {{ $centered ? 'aspect-square rounded-full' : 'aspect-[4/5] rounde
identified in the same glance as their face. --}} identified in the same glance as their face. --}}
<figcaption class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/45 to-transparent p-5 pt-16"> <figcaption class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/45 to-transparent p-5 pt-16">
@if ($block->get('name')) @if ($block->get('name'))
<p class="text-white text-2xl font-bold leading-tight">{{ $block->get('name') }}</p> <h3 class="text-white text-2xl font-bold leading-tight">{{ $block->get('name') }}</h3>
@endif @endif
@if ($block->get('role')) @if ($block->get('role'))
<p class="ec-accent-text text-sm font-bold uppercase tracking-widest mt-1">{{ $block->get('role') }}</p> <p class="ec-accent-text text-sm font-bold uppercase tracking-widest mt-1">{{ $block->get('role') }}</p>
......
@php @php
$sections = collect($block->get('sections') ?: []); $sections = collect($block->get('sections') ?: []);
$headingTag = $block->get('heading_level') === 'h2' ? 'h2' : 'h1';
$ruleClass = $block->get('heading_rule') === 'accent_bar' ? 'ec-heading-rule ec-heading-rule--start' : '';
$width = match ($variant) { $width = match ($variant) {
'prose_narrow' => 'max-w-3xl', 'two_column' => 'max-w-6xl', 'with_toc' => 'max-w-6xl', default => 'max-w-4xl', 'prose_narrow' => 'max-w-3xl', 'two_column' => 'max-w-6xl', 'with_toc' => 'max-w-6xl', default => 'max-w-4xl',
}; };
@endphp @endphp
<div class="{{ $width }} mx-auto px-4 sm:px-6 lg:px-8"> <div class="{{ $width }} mx-auto px-4 sm:px-6 lg:px-8">
@if ($block->get('title')) @if ($block->get('title'))
<h1 class="ec-heading text-3xl sm:text-4xl font-bold mb-3">{{ $block->get('title') }}</h1> <{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-bold mb-3 {{ $ruleClass }}">
{!! website_highlight($block->get('title')) !!}
</{{ $headingTag }}>
@endif @endif
@if ($block->get('last_updated')) @if ($block->get('last_updated'))
<p class="ec-muted text-sm mb-8">{{ $block->get('last_updated') }}</p> <p class="ec-muted text-sm mb-8">{{ $block->get('last_updated') }}</p>
...@@ -33,7 +37,7 @@ ...@@ -33,7 +37,7 @@
@php $anchor = website_text(data_get($s, 'anchor')) ?: 'sec-' . ($i + 1); @endphp @php $anchor = website_text(data_get($s, 'anchor')) ?: 'sec-' . ($i + 1); @endphp
<section id="{{ $anchor }}" class="mt-10 break-inside-avoid scroll-mt-24"> <section id="{{ $anchor }}" class="mt-10 break-inside-avoid scroll-mt-24">
@if (website_text(data_get($s, 'heading'))) @if (website_text(data_get($s, 'heading')))
<h2 class="ec-heading text-xl font-semibold mb-3">{{ $block->get("sections.{$i}.heading") ?: website_text(data_get($s, 'heading')) }}</h2> <{{ $headingTag === 'h1' ? 'h2' : 'h3' }} class="ec-heading text-xl font-semibold mb-3">{{ $block->get("sections.{$i}.heading") ?: website_text(data_get($s, 'heading')) }}</{{ $headingTag === 'h1' ? 'h2' : 'h3' }}>
@endif @endif
{!! clean_html($block->get("sections.{$i}.body") ?: website_text(data_get($s, 'body'))) !!} {!! clean_html($block->get("sections.{$i}.body") ?: website_text(data_get($s, 'body'))) !!}
</section> </section>
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
$ratio = ['square' => 'aspect-square', 'portrait' => 'aspect-[3/4]', 'landscape' => 'aspect-[4/3]', 'wide' => 'aspect-[16/9]', 'auto' => ''][$block->get('image_ratio', 'landscape')] ?? ''; $ratio = ['square' => 'aspect-square', 'portrait' => 'aspect-[3/4]', 'landscape' => 'aspect-[4/3]', 'wide' => 'aspect-[16/9]', 'auto' => ''][$block->get('image_ratio', 'landscape')] ?? '';
// The rule follows the heading's own alignment so it never floats away // The rule follows the heading's own alignment so it never floats away
// from the text it underlines. // from the text it underlines.
// A page needs exactly one h1; which section carries it is authored.
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
$ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start'; $ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start';
$ruleClass = $block->get('heading_rule') === 'accent_bar' $ruleClass = $block->get('heading_rule') === 'accent_bar'
? 'ec-heading-rule ec-heading-rule--'.$ruleAlign ? 'ec-heading-rule ec-heading-rule--'.$ruleAlign
...@@ -30,9 +32,9 @@ ...@@ -30,9 +32,9 @@
@if ($block->get('eyebrow')) @if ($block->get('eyebrow'))
<p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p> <p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p>
@endif @endif
<h2 class="ec-heading text-3xl sm:text-4xl font-bold {{ $ruleClass }}"> <{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-bold {{ $ruleClass }}">
{!! website_highlight($block->get('title')) !!} {!! website_highlight($block->get('title')) !!}
</h2> </{{ $headingTag }}>
@if ($block->get('body')) @if ($block->get('body'))
<div class="ec-prose ec-muted leading-relaxed {{ $block->get('body_accent_edge') ? 'ec-accent-edge' : '' }}"> <div class="ec-prose ec-muted leading-relaxed {{ $block->get('body_accent_edge') ? 'ec-accent-edge' : '' }}">
{!! clean_html($block->get('body')) !!} {!! clean_html($block->get('body')) !!}
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
$ratio = ['16:9' => 'aspect-video', '4:3' => 'aspect-[4/3]', '1:1' => 'aspect-square', '21:9' => 'aspect-[21/9]'][$block->get('ratio', '16:9')] ?? 'aspect-video'; $ratio = ['16:9' => 'aspect-video', '4:3' => 'aspect-[4/3]', '1:1' => 'aspect-square', '21:9' => 'aspect-[21/9]'][$block->get('ratio', '16:9')] ?? 'aspect-video';
// The rule follows the heading's own alignment so it never floats away // The rule follows the heading's own alignment so it never floats away
// from the text it underlines. // from the text it underlines.
// A page needs exactly one h1; which section carries it is authored.
$headingTag = $block->get('heading_level') === 'h1' ? 'h1' : 'h2';
$ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start'; $ruleAlign = ($block->get('header_align') ?? 'start') === 'center' ? 'center' : 'start';
$ruleClass = $block->get('heading_rule') === 'accent_bar' $ruleClass = $block->get('heading_rule') === 'accent_bar'
? 'ec-heading-rule ec-heading-rule--'.$ruleAlign ? 'ec-heading-rule ec-heading-rule--'.$ruleAlign
...@@ -20,9 +22,9 @@ ...@@ -20,9 +22,9 @@
<p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p> <p class="ec-eyebrow text-sm font-semibold uppercase tracking-widest">{{ $block->get('eyebrow') }}</p>
@endif @endif
@if ($block->get('title')) @if ($block->get('title'))
<h2 class="ec-heading text-3xl sm:text-4xl font-bold {{ $ruleClass }}"> <{{ $headingTag }} class="ec-heading text-3xl sm:text-4xl font-bold {{ $ruleClass }}">
{!! website_highlight($block->get('title')) !!} {!! website_highlight($block->get('title')) !!}
</h2> </{{ $headingTag }}>
@endif @endif
@if ($block->get('quote') && $variant === 'split') @if ($block->get('quote') && $variant === 'split')
<blockquote class="ec-muted text-lg italic leading-relaxed mt-2"> <blockquote class="ec-muted text-lg italic leading-relaxed mt-2">
......
...@@ -70,7 +70,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label=" ...@@ -70,7 +70,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label="
@case('links') @case('links')
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'روابط سريعة') }}</h4> <h3 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'روابط سريعة') }}</h3>
<ul class="space-y-2"> <ul class="space-y-2">
@if(!empty($block['links'])) @if(!empty($block['links']))
@foreach($block['links'] as $link) @foreach($block['links'] as $link)
...@@ -102,7 +102,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label=" ...@@ -102,7 +102,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label="
{{-- A column of links from an authored menu, so the footer and the {{-- A column of links from an authored menu, so the footer and the
navbar cannot drift apart as pages are added or renamed. --}} navbar cannot drift apart as pages are added or renamed. --}}
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, '') }}</h4> <h3 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, '') }}</h3>
@include('website.partials.site-menu', [ @include('website.partials.site-menu', [
'menuKey' => $block['menu_key'] ?? 'footer', 'menuKey' => $block['menu_key'] ?? 'footer',
'layout' => 'stacked', 'layout' => 'stacked',
...@@ -113,7 +113,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label=" ...@@ -113,7 +113,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label="
@case('contact') @case('contact')
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'تواصل معنا') }}</h4> <h3 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'تواصل معنا') }}</h3>
<ul class="space-y-3 text-sm"> <ul class="space-y-3 text-sm">
@if($academy->phone) @if($academy->phone)
<li class="flex items-center gap-2 {{ $mutedColor }}"> <li class="flex items-center gap-2 {{ $mutedColor }}">
...@@ -139,7 +139,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label=" ...@@ -139,7 +139,7 @@ class="{{ $textColor }} transition hover:text-[var(--site-accent)]" aria-label="
@case('social') @case('social')
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'تابعنا') }}</h4> <h3 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'تابعنا') }}</h3>
@if(is_array($settings->social_links ?? null) && count(array_filter($settings->social_links))) @if(is_array($settings->social_links ?? null) && count(array_filter($settings->social_links)))
<div class="flex items-center gap-3 flex-wrap"> <div class="flex items-center gap-3 flex-wrap">
@foreach(['facebook', 'instagram', 'twitter', 'youtube', 'tiktok'] as $platform) @foreach(['facebook', 'instagram', 'twitter', 'youtube', 'tiktok'] as $platform)
...@@ -158,7 +158,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent ...@@ -158,7 +158,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent
@case('hours') @case('hours')
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'ساعات العمل') }}</h4> <h3 class="{{ $textColor }} font-bold mb-4">{{ $blockTitle($block, 'ساعات العمل') }}</h3>
@if(!empty($block['hours'])) @if(!empty($block['hours']))
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
@foreach($block['hours'] as $hour) @foreach($block['hours'] as $hour)
...@@ -199,7 +199,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent ...@@ -199,7 +199,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent
@endif @endif
</div> </div>
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">روابط سريعة</h4> <h3 class="{{ $textColor }} font-bold mb-4">روابط سريعة</h3>
<ul class="space-y-2"> <ul class="space-y-2">
@foreach($sections->take(6) as $navSection) @foreach($sections->take(6) as $navSection)
<li> <li>
...@@ -211,7 +211,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent ...@@ -211,7 +211,7 @@ class="w-10 h-10 rounded-lg bg-white/5 border {{ $borderColor }} flex items-cent
</ul> </ul>
</div> </div>
<div> <div>
<h4 class="{{ $textColor }} font-bold mb-4">تواصل معنا</h4> <h3 class="{{ $textColor }} font-bold mb-4">تواصل معنا</h3>
<ul class="space-y-3 text-sm"> <ul class="space-y-3 text-sm">
@if($academy->phone) @if($academy->phone)
<li class="flex items-center gap-2 {{ $mutedColor }}"> <li class="flex items-center gap-2 {{ $mutedColor }}">
......
...@@ -13,9 +13,20 @@ ...@@ -13,9 +13,20 @@
$names = ['ar' => 'العربية', 'en' => 'English']; $names = ['ar' => 'العربية', 'en' => 'English'];
@endphp @endphp
<div class="relative" x-data="{ open: false }" x-on:mouseleave="open = false"> {{-- Same forgiving-hover contract as the nav dropdowns: a delayed, cancellable
close and a panel flush against its trigger, so the pointer can reach it. --}}
<div class="relative"
x-data="{
open: false,
closeTimer: null,
show() { clearTimeout(this.closeTimer); this.open = true; },
hide() { this.closeTimer = setTimeout(() => this.open = false, 220); },
}"
x-on:mouseenter="show()"
x-on:mouseleave="hide()"
x-on:focusout="if (! $el.contains($event.relatedTarget)) open = false">
<button type="button" <button type="button"
x-on:click="open = !open" x-on:mouseenter="open = true" x-on:click="open ? open = false : show()" x-on:mouseenter="show()"
x-bind:aria-expanded="open ? 'true' : 'false'" x-bind:aria-expanded="open ? 'true' : 'false'"
class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium transition {{ $linkClass }}" class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium transition {{ $linkClass }}"
aria-label="{{ __('تغيير اللغة') }}"> aria-label="{{ __('تغيير اللغة') }}">
...@@ -30,9 +41,11 @@ class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm ...@@ -30,9 +41,11 @@ class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm
</svg> </svg>
</button> </button>
<ul x-show="open" x-cloak x-transition.opacity <div x-show="open" x-cloak x-transition.opacity
x-on:keydown.escape.window="open = false" x-on:keydown.escape.window="open = false"
class="ec-nav-dropdown absolute z-50 mt-1 min-w-40 end-0 rounded-xl p-2 shadow-xl"> x-on:mouseenter="show()" x-on:mouseleave="hide()"
class="absolute z-50 top-full end-0 min-w-40 pt-2">
<ul class="ec-nav-dropdown rounded-xl p-2 shadow-xl">
@foreach (website_locales() as $loc) @foreach (website_locales() as $loc)
<li> <li>
<a href="{{ website_current_url_in($loc) }}" hreflang="{{ $loc }}" <a href="{{ website_current_url_in($loc) }}" hreflang="{{ $loc }}"
...@@ -49,4 +62,5 @@ class="flex items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm tran ...@@ -49,4 +62,5 @@ class="flex items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm tran
</li> </li>
@endforeach @endforeach
</ul> </ul>
</div>
</div> </div>
...@@ -40,10 +40,29 @@ ...@@ -40,10 +40,29 @@
@continue (! $href && $kids->isEmpty()) @continue (! $href && $kids->isEmpty())
<li class="relative" @if ($kids->isNotEmpty()) x-data="{ open: false }" x-on:mouseleave="open = false" @endif> {{--
The close is delayed and cancellable. A hover menu that shuts the
instant the pointer leaves the trigger is unusable: the reader has
to cross the space between the trigger and the list, and any
wobble off the trigger's box closes the menu before the click
lands. The panel below also starts flush against the trigger
(top-full, padding inside) so there is no dead gap to cross.
--}}
<li class="relative"
@if ($kids->isNotEmpty())
x-data="{
open: false,
closeTimer: null,
show() { clearTimeout(this.closeTimer); this.open = true; },
hide() { this.closeTimer = setTimeout(() => this.open = false, 220); },
}"
x-on:mouseenter="show()"
x-on:mouseleave="hide()"
x-on:focusout="if (! $el.contains($event.relatedTarget)) open = false"
@endif>
@if ($kids->isNotEmpty()) @if ($kids->isNotEmpty())
<button type="button" <button type="button"
x-on:click="open = !open" x-on:mouseenter="open = true" x-on:click="open ? open = false : show()" x-on:mouseenter="show()"
x-bind:aria-expanded="open ? 'true' : 'false'" x-bind:aria-expanded="open ? 'true' : 'false'"
class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 font-medium transition {{ $linkClass }}"> class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 font-medium transition {{ $linkClass }}">
@if ($item->icon)<x-website.icon :name="$item->icon" class="w-4 h-4" />@endif @if ($item->icon)<x-website.icon :name="$item->icon" class="w-4 h-4" />@endif
...@@ -54,11 +73,13 @@ class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 font-me ...@@ -54,11 +73,13 @@ class="ec-nav-item inline-flex items-center gap-1.5 rounded-lg px-3 py-2 font-me
</svg> </svg>
</button> </button>
<ul x-show="open" x-cloak x-transition.opacity <div x-show="open" x-cloak x-transition.opacity
x-on:keydown.escape.window="open = false" x-on:keydown.escape.window="open = false"
class="ec-nav-dropdown {{ $stacked x-on:mouseenter="show()" x-on:mouseleave="hide()"
? 'static ms-4 mt-1 space-y-1' class="{{ $stacked ? 'static' : 'absolute z-50 top-full start-0 min-w-56 pt-2' }}">
: 'absolute z-50 mt-1 min-w-56 start-0 rounded-xl p-2 shadow-xl' }}"> <ul class="ec-nav-dropdown {{ $stacked
? 'ms-4 mt-1 space-y-1'
: 'rounded-xl p-2 shadow-xl' }}">
@foreach ($kids as $child) @foreach ($kids as $child)
@php $childHref = $child->href(); @endphp @php $childHref = $child->href(); @endphp
@continue (! $childHref) @continue (! $childHref)
...@@ -72,6 +93,7 @@ class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition hover:bg- ...@@ -72,6 +93,7 @@ class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition hover:bg-
</li> </li>
@endforeach @endforeach
</ul> </ul>
</div>
@else @else
<a href="{{ $href }}" <a href="{{ $href }}"
@if ($item->open_in_new_tab) target="_blank" rel="noopener noreferrer" @endif @if ($item->open_in_new_tab) target="_blank" rel="noopener noreferrer" @endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment