24-7: about page — implemented, QA Review

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Naser Mansour
2026-10-31 18:52:36 +02:00
parent 7637294593
commit 7879ac7190
8 changed files with 177 additions and 265 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
id: 24-7 id: 24-7
title: 24-7 About Page title: 24-7 About Page
status: Ready for Dev status: QA Review
date: 2026-10-31 date: 2026-10-31
parent: 24 parent: 24
--- ---
+10
View File
@@ -0,0 +1,10 @@
# Manual tests — 24-7 About Page
Automated (passed 2026-10-31, `tests/Feature/Design/AboutRedesignTest.php`, 5 tests): the sections in order with all existing about.*
content in ar and en (identity, vision, three mission cards, six values, seven goals, four service groups, CTA); one h1 and alt on
every image; four service rows link to `/booking`; goal rows carry the static-row CSS; no scholarship content; site keys in both
languages; `lang/*/about.php` unchanged versus BASE. UI loop on `/about`: no horizontal scroll at 390/768/1440, h1 count 1.
- [ ] `/about` in Arabic and English: six sections look like the home page's family (hero, vision with hoop and three cards, values, goals list, services, closing band).
- [ ] Hover a goal row: nothing fills. Hover a service row: fills forest, arrow button turns gold, row links to the booking page.
- [ ] 390px: goal numbers narrow (52px column), nothing overflows sideways.
+1 -1
View File
@@ -143,7 +143,7 @@
| 24-4 | 24-4 Home Page Sections (Hero to Latest Articles) | QA Review | | 24-4 | 24-4 Home Page Sections (Hero to Latest Articles) | QA Review |
| 24-5 | 24-5 Home Booking Card (Compact Booking and Day Strip) | QA Review | | 24-5 | 24-5 Home Booking Card (Compact Booking and Day Strip) | QA Review |
| 24-6 | 24-6 Booking Page and Booking Success | QA Review | | 24-6 | 24-6 Booking Page and Booking Success | QA Review |
| 24-7 | 24-7 About Page | Ready for Dev | | 24-7 | 24-7 About Page | QA Review |
| 24-8 | 24-8 Articles and Legal Pages | Ready for Dev | | 24-8 | 24-8 Articles and Legal Pages | Ready for Dev |
| 24-9 | 24-9 Auth Pages Type and Controls | Ready for Dev | | 24-9 | 24-9 Auth Pages Type and Controls | Ready for Dev |
| 24-10 | 24-10 Redesign Integration Verification | Ready for Dev | | 24-10 | 24-10 Redesign Integration Verification | Ready for Dev |
+5
View File
@@ -72,4 +72,9 @@ return [
'booking_submit' => 'اطلب هذا الموعد', 'booking_submit' => 'اطلب هذا الموعد',
'booking_none' => 'لا توجد مواعيد متاحة حالياً', 'booking_none' => 'لا توجد مواعيد متاحة حالياً',
'captcha_refresh' => 'سؤال جديد', 'captcha_refresh' => 'سؤال جديد',
'about_art_vision' => 'المادة ٠١ — الرؤية والرسالة',
'about_art_values' => 'المادة ٠٢ — القيم',
'about_art_goals' => 'المادة ٠٣ — الأهداف',
'about_art_services' => 'المادة ٠٤ — الخدمات',
'about_cta_title' => 'لكلّ حقٍّ وزنُه',
]; ];
+5
View File
@@ -72,4 +72,9 @@ return [
'booking_submit' => 'Request this appointment', 'booking_submit' => 'Request this appointment',
'booking_none' => 'No times are available at the moment', 'booking_none' => 'No times are available at the moment',
'captcha_refresh' => 'New question', 'captcha_refresh' => 'New question',
'about_art_vision' => 'Article 01 — Vision and mission',
'about_art_values' => 'Article 02 — Values',
'about_art_goals' => 'Article 03 — Goals',
'about_art_services' => 'Article 04 — Services',
'about_cta_title' => 'Every right carries its weight',
]; ];
+6
View File
@@ -332,6 +332,12 @@ body.site .success-card{max-width:560px;margin-inline:auto;background:var(--crea
body.site .success-card h1{font-size:clamp(2rem,3.4vw,2.6rem);margin-bottom:12px} body.site .success-card h1{font-size:clamp(2rem,3.4vw,2.6rem);margin-bottom:12px}
body.site .success-card p{color:var(--muted);margin:0 0 26px} body.site .success-card p{color:var(--muted);margin:0 0 26px}
/* ---------- About page (24-7) ---------- */
body.site .row-static{grid-template-columns:90px 1fr}
body.site .row-static:hover{background:none;color:inherit;transform:none;box-shadow:none}
body.site .row-static::before{display:none}
@media (max-width:760px){body.site .row-static{grid-template-columns:52px 1fr}}
/* ---------- Reveal (hidden only when JavaScript runs) ---------- */ /* ---------- 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]{opacity:0;transform:translateY(28px);transition:opacity .9s var(--ease),transform .9s var(--ease)}
body.site.js [data-reveal].in{opacity:1;transform:none} body.site.js [data-reveal].in{opacity:1;transform:none}
+80 -257
View File
@@ -9,288 +9,111 @@ new #[Layout('components.layouts.public')] class extends Component
}; ?> }; ?>
<div> <div>
<div class="wrap py-8"> {{-- Hero --}}
{{-- Hero Section --}} <section class="sec" style="padding-bottom:64px">
<section class="bg-background py-10 sm:py-[60px] lg:py-20"> <div class="wrap" style="max-width:52em">
<div class="container mx-auto px-4 text-center"> <span class="eyebrow" data-reveal>{{ __('about.tagline') }}</span>
{{-- Page Title --}} <h1 data-reveal data-d="1" style="font-size:clamp(2.6rem,4.6vw,4.1rem);line-height:1.12;margin:18px 0 22px">{{ __('about.title') }}</h1>
<h1 class="text-[2rem] sm:text-[2.5rem] lg:text-[3rem] font-bold text-text mb-2"> <p class="lede" data-reveal data-d="2">{{ __('about.identity') }}</p>
{{ __('about.title') }}
</h1>
{{-- Tagline --}}
<p class="text-xl sm:text-2xl text-text/80 max-w-[800px] mx-auto mb-4">
{{ __('about.tagline') }}
</p>
{{-- Identity Statement --}}
<p class="text-lg text-text/70 max-w-[700px] mx-auto">
{{ __('about.identity') }}
</p>
</div> </div>
</section> </section>
{{-- Vision Section --}} {{-- Vision and mission --}}
<section class="bg-white py-8 sm:py-12 lg:py-[60px]"> <section class="sec fabric">
<div class="container mx-auto px-4"> <div class="wrap">
<h2 class="text-2xl lg:text-3xl font-bold text-text text-center mb-2"> <div class="fabric-grid">
{{ __('about.vision_title') }} <div data-reveal>
</h2> <span class="art-no">{{ __('site.about_art_vision') }}</span>
<div class="w-20 h-1 bg-accent mx-auto mb-8"></div> <h2>{{ __('about.vision_title') }}</h2>
<blockquote class="max-w-[900px] mx-auto text-center text-lg text-text/90"> <p class="big">{{ __('about.vision_text') }}</p>
<span class="text-4xl text-accent leading-none align-top">"</span> <h3 style="margin-top:28px">{{ __('about.mission_title') }}</h3>
<span class="italic">{{ __('about.vision_text') }}</span> <p>{{ __('about.mission_intro') }}</p>
<span class="text-4xl text-accent leading-none align-bottom">"</span>
</blockquote>
</div> </div>
</section> <div class="hoop" data-reveal data-d="2"><img src="{{ asset('images/site/embroidery-hoop.jpg') }}" alt="{{ __('site.alt_hoop') }}" width="773" height="1000"></div>
{{-- Mission Section --}}
<section class="bg-background py-8 sm:py-12 lg:py-[60px]">
<div class="container mx-auto px-4">
<h2 class="text-2xl lg:text-3xl font-bold text-text text-center mb-2">
{{ __('about.mission_title') }}
</h2>
<div class="w-20 h-1 bg-accent mx-auto mb-8"></div>
<div class="bg-white rounded-lg shadow-sm p-6 sm:p-8 max-w-[900px] mx-auto mb-12">
<p class="text-center text-lg text-text/90">
{{ __('about.mission_intro') }}
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{{-- Card 1: For Institution --}}
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="text-accent mb-4">
<flux:icon name="building-office" class="w-12 h-12" />
</div>
<h3 class="text-xl font-bold text-text mb-3">
{{ __('about.mission_institution_title') }}
</h3>
<p class="text-text/80">
{{ __('about.mission_institution_text') }}
</p>
</div>
{{-- Card 2: For Society --}}
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="text-accent mb-4">
<flux:icon name="user-group" class="w-12 h-12" />
</div>
<h3 class="text-xl font-bold text-text mb-3">
{{ __('about.mission_society_title') }}
</h3>
<p class="text-text/80">
{{ __('about.mission_society_text') }}
</p>
</div>
{{-- Card 3: For Diaspora --}}
<div class="bg-white rounded-lg shadow-sm p-6 md:col-span-2 lg:col-span-1">
<div class="text-accent mb-4">
<flux:icon name="globe-alt" class="w-12 h-12" />
</div>
<h3 class="text-xl font-bold text-text mb-3">
{{ __('about.mission_diaspora_title') }}
</h3>
<p class="text-text/80">
{{ __('about.mission_diaspora_text') }}
</p>
</div> </div>
<div class="three">
<article class="card" data-reveal>
<div class="ph motif-ph"><x-site.balance mode="static" :size="150" /><span class="lbl">{{ __('site.card_institution_label') }}</span></div>
<div class="bd"><h3>{{ __('about.mission_institution_title') }}</h3><p>{{ __('about.mission_institution_text') }}</p></div>
</article>
<article class="card" data-reveal data-d="1">
<div class="ph"><img src="{{ asset('images/site/olives.jpg') }}" alt="{{ __('site.alt_olives') }}" width="900" height="1350"><span class="lbl">{{ __('site.card_society_label') }}</span></div>
<div class="bd"><h3>{{ __('about.mission_society_title') }}</h3><p>{{ __('about.mission_society_text') }}</p></div>
</article>
<article class="card" data-reveal data-d="2">
<div class="ph"><img src="{{ asset('images/site/hills.jpg') }}" alt="{{ __('site.alt_hills') }}" width="900" height="1600"><span class="lbl">{{ __('site.card_diaspora_label') }}</span></div>
<div class="bd"><h3>{{ __('about.mission_diaspora_title') }}</h3><p>{{ __('about.mission_diaspora_text') }}</p></div>
</article>
</div> </div>
</div> </div>
</section> </section>
{{-- Values Section --}} {{-- Values --}}
<section class="bg-white py-8 sm:py-12 lg:py-[60px]"> <section class="sec">
<div class="container mx-auto px-4"> <div class="wrap">
<h2 class="text-2xl lg:text-3xl font-bold text-text text-center mb-2"> <x-site.section-head :label="__('site.about_art_values')" :title="__('about.values_title')" :lede="__('about.values_intro')" />
{{ __('about.values_title') }} @php
</h2> $valueIcons = [
<div class="w-20 h-1 bg-accent mx-auto mb-4"></div> '<path d="M12 3l7 3v6c0 4.5-3 7.6-7 9-4-1.4-7-4.5-7-9V6z"/><path d="M9 12l2 2 4-4"/>',
<p class="text-center text-text/70 max-w-xl mx-auto mb-12"> '<path d="M12 4v16M8 20h8M5 7h14"/><path d="M5 7l-3 6a3 3 0 0 0 6 0zM19 7l-3 6a3 3 0 0 0 6 0z"/>',
{{ __('about.values_intro') }} '<path d="M2 5h6a4 4 0 0 1 4 4v11a3 3 0 0 0-3-3H2zM22 5h-6a4 4 0 0 0-4 4v11a3 3 0 0 1 3-3h7z"/>',
</p> '<path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/>',
'<rect x="3" y="7.5" width="18" height="12" rx="2"/><path d="M9 7.5V5h6v2.5M3 13h18"/>',
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 lg:gap-6"> '<path d="M9 18h6M10 21h4M12 3a6 6 0 0 0-4 10.5c.8.8 1 1.5 1 2.5h6c0-1 .2-1.7 1-2.5A6 6 0 0 0 12 3z"/>',
{{-- Integrity --}} ];
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow"> @endphp
<div class="text-accent mb-3"> <div class="sampler">
<flux:icon name="shield-check" class="w-10 h-10 mx-auto" /> @foreach (['integrity', 'justice', 'knowledge', 'women', 'professionalism', 'innovation'] as $i => $value)
</div> <div class="val" data-reveal @if ($i) data-d="{{ $i }}" @endif>
<h4 class="font-semibold text-text"> <span class="medal"><svg viewBox="0 0 24 24" aria-hidden="true">{!! $valueIcons[$i] !!}</svg></span>
{{ __('about.value_integrity') }} <b>{{ __('about.value_'.$value) }}</b>
</h4>
</div>
{{-- Justice --}}
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow">
<div class="text-accent mb-3">
<flux:icon name="scale" class="w-10 h-10 mx-auto" />
</div>
<h4 class="font-semibold text-text">
{{ __('about.value_justice') }}
</h4>
</div>
{{-- Knowledge --}}
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow">
<div class="text-accent mb-3">
<flux:icon name="academic-cap" class="w-10 h-10 mx-auto" />
</div>
<h4 class="font-semibold text-text">
{{ __('about.value_knowledge') }}
</h4>
</div>
{{-- Women's Empowerment --}}
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow">
<div class="text-accent mb-3">
<flux:icon name="heart" class="w-10 h-10 mx-auto" />
</div>
<h4 class="font-semibold text-text">
{{ __('about.value_women') }}
</h4>
</div>
{{-- Professionalism --}}
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow">
<div class="text-accent mb-3">
<flux:icon name="briefcase" class="w-10 h-10 mx-auto" />
</div>
<h4 class="font-semibold text-text">
{{ __('about.value_professionalism') }}
</h4>
</div>
{{-- Social Innovation --}}
<div class="bg-background rounded-xl p-6 text-center hover:shadow-md transition-shadow">
<div class="text-accent mb-3">
<flux:icon name="light-bulb" class="w-10 h-10 mx-auto" />
</div>
<h4 class="font-semibold text-text">
{{ __('about.value_innovation') }}
</h4>
</div>
</div>
</div>
</section>
{{-- Goals Section --}}
<section class="bg-background py-8 sm:py-12 lg:py-[60px]">
<div class="container mx-auto px-4">
<h2 class="text-2xl lg:text-3xl font-bold text-text text-center mb-2">
{{ __('about.goals_title') }}
</h2>
<div class="w-20 h-1 bg-accent mx-auto mb-12"></div>
<div class="bg-white rounded-lg shadow-sm p-6 sm:p-8 max-w-5xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-6">
@foreach(__('about.goals') as $index => $goal)
<div class="flex items-start gap-4">
<span class="flex-shrink-0 w-8 h-8 bg-accent text-white rounded-full flex items-center justify-center font-bold text-sm">
{{ $index + 1 }}
</span>
<p class="text-text/90 pt-1">{{ $goal }}</p>
</div> </div>
@endforeach @endforeach
</div> </div>
</div> </div>
</div>
</section> </section>
{{-- Services Section --}} {{-- Goals --}}
<section class="bg-white py-8 sm:py-12 lg:py-[60px]"> <section class="sec" style="padding-top:20px">
<div class="container mx-auto px-4"> <div class="wrap">
<h2 class="text-2xl lg:text-3xl font-bold text-text text-center mb-2"> <x-site.section-head :label="__('site.about_art_goals')" :title="__('about.goals_title')" />
{{ __('about.services_title') }} <ol class="docket" style="list-style:none;margin:0;padding:0">
</h2> @foreach (__('about.goals') as $i => $goal)
<div class="w-20 h-1 bg-accent mx-auto mb-4"></div> <li class="row row-static" data-reveal>
<p class="text-center text-text/70 max-w-3xl mx-auto mb-12"> <span class="n">{{ sprintf('%02d', $i + 1) }}</span>
{{ __('about.services_intro') }} <p style="font-size:1.1rem;color:var(--ink);margin:0">{{ $goal }}</p>
</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
{{-- Legal Consultations & Representation --}}
<div class="bg-background rounded-xl p-8 text-center shadow-sm">
<div class="text-accent mb-4">
<flux:icon name="scale" class="w-12 h-12 mx-auto" />
</div>
<h3 class="text-lg font-bold text-text mb-4">
{{ __('about.service_consultation_title') }}
</h3>
<ul class="text-sm text-text/80 space-y-2 text-start">
@foreach(__('about.service_consultation_items') as $item)
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-accent rounded-full flex-shrink-0"></span>
{{ $item }}
</li> </li>
@endforeach @endforeach
</ul> </ol>
</div> </div>
</section>
{{-- Women's & Family Law --}} {{-- Legal services --}}
<div class="bg-background rounded-xl p-8 text-center shadow-sm"> <section class="sec" id="services" style="padding-top:20px">
<div class="text-accent mb-4"> <div class="wrap">
<flux:icon name="heart" class="w-12 h-12 mx-auto" /> <x-site.section-head :label="__('site.about_art_services')" :title="__('about.services_title')" :lede="__('about.services_intro')" />
</div> <div class="docket">
<h3 class="text-lg font-bold text-text mb-4"> @foreach (['consultation', 'family', 'civil', 'business'] as $i => $service)
{{ __('about.service_family_title') }} <a class="row" href="{{ route('booking') }}" wire:navigate data-reveal @if ($i) data-d="{{ $i }}" @endif>
</h3> <span class="n">{{ sprintf('%02d', $i + 1) }}</span>
<ul class="text-sm text-text/80 space-y-2 text-start"> <h3>{{ __('about.service_'.$service.'_title') }}</h3>
@foreach(__('about.service_family_items') as $item) <div class="chips">
<li class="flex items-center gap-2"> @foreach (__('about.service_'.$service.'_items') as $item)
<span class="w-1.5 h-1.5 bg-accent rounded-full flex-shrink-0"></span> <span class="chip">{{ $item }}</span>
{{ $item }}
</li>
@endforeach @endforeach
</ul>
</div> </div>
<span class="go"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6"/></svg></span>
{{-- Civil & Commercial Disputes --}} </a>
<div class="bg-background rounded-xl p-8 text-center shadow-sm">
<div class="text-accent mb-4">
<flux:icon name="building-office" class="w-12 h-12 mx-auto" />
</div>
<h3 class="text-lg font-bold text-text mb-4">
{{ __('about.service_civil_title') }}
</h3>
<ul class="text-sm text-text/80 space-y-2 text-start">
@foreach(__('about.service_civil_items') as $item)
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-accent rounded-full flex-shrink-0"></span>
{{ $item }}
</li>
@endforeach @endforeach
</ul>
</div>
{{-- Business Legal Services --}}
<div class="bg-background rounded-xl p-8 text-center shadow-sm">
<div class="text-accent mb-4">
<flux:icon name="briefcase" class="w-12 h-12 mx-auto" />
</div>
<h3 class="text-lg font-bold text-text mb-4">
{{ __('about.service_business_title') }}
</h3>
<ul class="text-sm text-text/80 space-y-2 text-start">
@foreach(__('about.service_business_items') as $item)
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-accent rounded-full flex-shrink-0"></span>
{{ $item }}
</li>
@endforeach
</ul>
</div>
</div>
<div class="text-center">
<flux:button variant="primary" href="{{ route('booking') }}">
{{ __('about.services_cta') }}
</flux:button>
</div> </div>
</div> </div>
</section> </section>
{{-- Closing call to action --}}
<section class="band on-dark">
<div class="wrap" style="text-align:center;padding-block:72px">
<h2 style="color:var(--cream)" data-reveal>{{ __('site.about_cta_title') }}</h2>
<p style="margin-top:22px" data-reveal data-d="1"><a class="btn btn-gold" href="{{ route('booking') }}" wire:navigate>{{ __('about.services_cta') }}</a></p>
</div> </div>
</section>
</div> </div>
@@ -0,0 +1,63 @@
<?php
function aboutHtml(string $locale): string
{
return test()->withSession(['locale' => $locale])->get('/about')->assertOk()->getContent();
}
test('about renders its sections in order with the existing content in both languages', function () {
foreach (['ar', 'en'] as $locale) {
$html = aboutHtml($locale);
app()->setLocale($locale);
$needles = [
__('about.title'), __('about.identity'), __('about.vision_title'), __('about.mission_institution_title'),
__('about.mission_society_title'), __('about.mission_diaspora_title'), __('about.values_title'), __('about.value_women'),
__('about.goals_title'), __('about.goals')[6], __('about.services_title'), __('about.service_business_items')[0],
__('site.about_cta_title'), __('about.services_cta'),
];
$last = -1;
foreach ($needles as $n) {
$pos = strpos($html, e($n), $last + 1);
$pos = $pos === false ? strpos($html, $n, $last + 1) : $pos;
expect($pos)->not->toBeFalse("missing or out of order [$locale]: $n");
$last = $pos;
}
expect(substr_count($html, 'class="sampler"'))->toBe(1);
expect(substr_count($html, 'class="val"'))->toBe(6);
expect(substr_count($html, 'row row-static'))->toBe(7);
}
});
test('about has one h1 and alt text on every image', function () {
$html = aboutHtml('ar');
expect(substr_count($html, '<h1'))->toBe(1);
preg_match_all('/<img\b[^>]*>/', $html, $m);
foreach ($m[0] as $img) {
expect($img)->toMatch('/\balt="[^"]+"/');
}
});
test('service rows link to booking and goal rows are static', function () {
$html = aboutHtml('en');
expect(substr_count($html, '<a class="row"'))->toBe(4);
expect(substr_count($html, 'href="'.route('booking').'"'))->toBeGreaterThanOrEqual(5);
expect(file_get_contents(resource_path('css/public.css')))
->toContain('body.site .row-static{grid-template-columns:90px 1fr}')
->toContain('body.site .row-static:hover{background:none;color:inherit;transform:none;box-shadow:none}')
->toContain('body.site .row-static::before{display:none}');
});
test('about page contains no scholarship content', function () {
expect(file_get_contents(resource_path('views/livewire/pages/about.blade.php')))->not->toContain('scholarship');
});
test('about site keys exist in both languages', function () {
foreach (['ar', 'en'] as $locale) {
$t = require base_path("lang/$locale/site.php");
foreach (['about_art_vision', 'about_art_values', 'about_art_goals', 'about_art_services', 'about_cta_title'] as $k) {
expect($t)->toHaveKey($k);
}
}
});