docs: client-approved public-site redesign — PRD 1.4, UX spec v2.0, architecture §6.2, tasks 24-x Ready for Dev

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Naser Mansour
2026-10-31 18:23:02 +02:00
parent caec396fa4
commit 3d1fe40be6
17 changed files with 1761 additions and 5 deletions
+19 -1
View File
@@ -116,7 +116,7 @@ php artisan make:model ModelName -mf
- `app/Enums/` - UserType, UserStatus, ConsultationType, ConsultationStatus, PaymentStatus, TimelineStatus, PostStatus
- `docs/prd.md` - Full product requirements document
- `docs/architecture.md` - Complete architecture document
- `docs/ux-design.md` - Design system reference (colors, typography, layout, components — retrospective, written 2026-09-27)
- `docs/ux-design.md` - Design spec. **Part A** = the public-site redesign (approved by the client 2026-10-31, mockup `docs/ux-mockup.html`); **Part B** = the pre-redesign system, which still governs the dashboards, emails and the Nablus form
- `docs/tasks/` - **Current task/status tracker** (`statuses.md` is the status source of truth). One row per epic (parent) and per story (subtask), e.g. `3.md` / `3-4.md`.
- `docs/stories/`, `docs/epics/`, `docs/qa/gates/` - **Legacy.** Original BMAD-agent build records (98→102 stories across 16 epics). Kept for implementation history and traceability, no longer the status source — `docs/tasks/statuses.md` superseded them 2026-09-27. `.bmad-core/` and `.claude/commands/BMad/` are the retired BMAD toolchain, also kept but unused.
@@ -246,7 +246,25 @@ Located in `app/Notifications/`:
- **Cascade deletes:** User deletion cascades to consultations, timelines, notifications
- **Bilingual required:** All user-facing text needs Arabic and English
## Public-site redesign (PRD 1.4, tasks 24-x)
The public pages (home, about, posts, booking, legal) and the auth pages' type/controls follow
`docs/ux-design.md` Part A; the map is `docs/architecture.md` §6.2. Boundaries that keep the
dashboards safe:
- Redesign styles live in `resources/css/public.css`, every selector under `body.site` (auth:
`body.site-auth`); its script is `resources/js/public.js` + `resources/js/site/*`, initialised on
`livewire:navigated`. Both load only from the public (and, CSS only, auth) layouts.
- Never edit `resources/css/app.css` `@theme`/`.btn-*`, `components/language-toggle.blade.php`,
or `lang/*/navigation.php` for the redesign — the dashboards share them. New public copy goes in
`lang/{ar,en}/site.php`.
- `availability-calendar` defaults to `variant="month"` (the client dashboard uses it); the home
page uses `variant="strip"`. `pages.booking` takes `compact` for the home card; booking rules are
shared and unchanged.
## Design Requirements (from PRD)
Below: the dashboards' palette and rules. Public pages: Part A of `docs/ux-design.md`.
- **Color scheme:**
- Dark Forest Green (#2D3624) - Header/Footer backgrounds
- Warm Gold (#A68966) - CTA buttons, links, accents
+64 -2
View File
@@ -1,7 +1,7 @@
# Libra Law Firm - Architecture Document
**Version:** 1.1
**Date:** December 21, 2025
**Version:** 1.2
**Date:** October 31, 2026
**Status:** Approved
**Domain:** libra.ps
@@ -13,6 +13,7 @@
|------|---------|-------------|--------|
| Dec 21, 2025 | 1.0 | Initial architecture document | Winston (Architect) |
| Dec 21, 2025 | 1.1 | Added CI/CD, rollback procedures, monitoring, timezone handling, alternatives analysis | Winston (Architect) |
| Oct 31, 2026 | 1.2 | Added §6.2 Public-site presentation layer (PRD 1.4 redesign, ux-design.md v2.0 Part A) | Jarvis |
---
@@ -1008,6 +1009,67 @@ libra/
└── build/ # Compiled assets
```
> §6.1 is the original (Dec 2025) plan. As built, the public layout is
> `resources/views/components/layouts/public.blade.php` (not `guest.blade.php`) and public pages
> are Volt components under `resources/views/livewire/pages/`. §6.2 is current.
### 6.2 Public-Site Presentation Layer (redesign, PRD 1.4)
**Overview.** The October 2026 redesign (PRD Update 1.4; `docs/ux-design.md` Part A) is a
presentation-only change to the public pages. No route, model, migration, service, mail or job
changes. The redesign is isolated from the admin and client dashboards by loading its stylesheet
and script only from the public and auth layouts, and by scoping every rule under a body class.
**Components**
| Component | Path | Owns |
|---|---|---|
| Public stylesheet | `resources/css/public.css` (new Vite entry) | Part A tokens (`--forest`, `--gold`, …), linen ground, type, buttons, sections, cards, docket rows, booking card, footer, reveal and reduced-motion rules. Every selector is scoped under `body.site`; auth-page rules under `body.site-auth`. |
| Public script | `resources/js/public.js` (new Vite entry) imports `resources/js/site/balance.js`, `reveal.js`, `progress.js` | The balance SVG builder and damped spring, hero pointer response, finale scroll mapping, IntersectionObserver reveal, reading-progress rule, nav scrolled state. Initialises on `livewire:navigated` (fires on first load and after every `wire:navigate`) and tears down listeners on `livewire:navigating`. |
| Font partial | `resources/views/partials/site-fonts.blade.php` (new) | Google Fonts link for Markazi Text (400–700) and IBM Plex Sans Arabic (300–600), `display=swap`. |
| Public layout | `resources/views/components/layouts/public.blade.php` (changed) | Body class `site`; includes the font partial and `@vite(['resources/css/public.css','resources/js/public.js'])` after `partials.head`; drops the 1200px content wrapper and the 64px fixed-nav spacer (nav becomes sticky); adds the progress rule element. |
| Auth layouts | `resources/views/components/layouts/auth/split.blade.php`, `card.blade.php`, `simple.blade.php` (changed) | Body class `site-auth`; font partial; `public.css` only (no public script). Composition unchanged. |
| Navigation / footer | `resources/views/components/navigation.blade.php`, `footer.blade.php` (restyled in place; used only by the public layout) | Part A nav and footer. Keeps the existing Alpine mobile menu, auth-aware links and language toggle. |
| Site Blade components | `resources/views/components/site/` (new): `balance.blade.php` (SVG mount point; props `id`, `size`, `mode` = `hero` / `finale` / `static`), `section-head.blade.php` (article label + h2 + lede), `post-card.blade.php` (text-only post card) | Shared markup for the public pages. |
| Redesign copy | `lang/ar/site.php`, `lang/en/site.php` (new) | Strings introduced by the redesign only (hero line, trust strip, pillars, article labels, card titles, booking lede, nav and footer labels). Existing keys in `home.php`, `about.php`, `booking.php`, `footer.php`, `navigation.php` are reused, never duplicated or edited. `navigation.php` keys used by the dashboards are untouched. |
| Photography | `public/images/site/olive-tree.jpg`, `embroidery-hoop.jpg`, `olives.jpg`, `hills.jpg` (new; extracted from the data URIs in `docs/ux-mockup.html`) | Production images (Unsplash licence). Portrait stays `public/images/huda-armouche.jpg`; seal uses `public/images/logo.png`. |
| Guest booking | `resources/views/livewire/pages/booking.blade.php` (changed) | Gains `public bool $compact = false` (mount parameter). `compact` renders the one-screen home layout; default renders the `/booking` page layout. Validation, captcha, one-per-day rule, confirm step and redirect to `booking.success` are shared and unchanged. |
| Availability calendar | `resources/views/livewire/availability-calendar.blade.php` (changed) | Gains `public string $variant = 'month'`. `strip` lists the next 14 dates whose `AvailabilityService::getDateStatus()` is available (scanning at most 60 days ahead) and their `getAvailableSlots()`; `month` is the existing grid. Default stays `month`, so the client dashboard's booking page (`resources/views/livewire/client/consultations/book.blade.php`) is unaffected; its public restyle applies only under `body.site`. |
**Data flows**
1. Request to a public route → Volt page with `#[Layout('components.layouts.public')]` → layout
loads `app.css` + `app.js` (unchanged, via `partials.head`), then `public.css` + `public.js`.
2. Home `/` → `Post::published()->latest('published_at')->take(3)` (existing) → `x-site.post-card`;
embeds `<livewire:pages.booking :compact="true" />`, which embeds
`<livewire:availability-calendar variant="strip" />`. Slot choice → `$parent.selectSlot(date,
time)` (existing contract) → submit → existing confirm step → existing `submit()` → redirect
`/booking/success`.
3. `public.js` on `livewire:navigated` → finds `[data-balance]` mounts → builds the SVG → binds
pointer (hero, fine pointers only) and scroll (finale) → spring per `requestAnimationFrame`.
`prefers-reduced-motion: reduce` → every balance drawn level, no listeners bound.
4. Language: unchanged — `/language/{locale}` sets the session locale; layouts set `lang` and
`dir`.
**Module boundaries**
- The redesign owns `public.css`, `public.js`, `resources/js/site/*`, `components/site/*`,
`lang/*/site.php`, `public/images/site/*`, and the restyle of the public views listed above.
- It does not own and must not change: `resources/css/app.css` `@theme` tokens and `.btn-*`
classes, Flux configuration, anything under `resources/views/livewire/admin/` or
`livewire/client/`, `components/layouts/app*`, emails, PDFs,
`resources/views/livewire/pages/forms/nablus-workshop.blade.php`, `layouts/standalone.blade.php`,
routes, models, services (read-only use of `AvailabilityService`), migrations.
- Pages restyled: `livewire/pages/home.blade.php`, `about.blade.php`, `booking.blade.php`,
`booking-success.blade.php`, `posts/index.blade.php`, `posts/show.blade.php`,
`resources/views/pages/legal.blade.php` (and the `terms`/`privacy` wrappers if they render
markup).
- `partials/head.blade.php`: `theme-color` corrected to `#2D3624` (was the superseded
`#4A4A42`); nothing else changes there, because the dashboards share it.
**Open gaps** — none blocking. The four rulings (photography, 14-day strip, unmocked pages, auth
scope) are recorded in `docs/ux-design.md` Part A *Open Questions* and the project ledger.
---
## 7. Authentication & Authorization
+57 -1
View File
@@ -1,7 +1,7 @@
# Product Requirements Document (PRD)
## Libra Law Firm Website
**Version:** 1.2
**Version:** 1.4
**Date:** January 3, 2026
**Domain:** libra.ps
**Prepared for:** Development Team & Client Review
@@ -1106,6 +1106,7 @@ All emails in both Arabic and English based on user preference:
| 1.1 | Dec 20, 2025 | Added brand identity section, clarified booking rules (1/day, 45min), multiple timelines, account management (deactivate/delete), exports, analytics, Terms/Privacy pages | Development Team |
| 1.2 | Jan 3, 2026 | Added guest booking functionality: public booking form at /booking, custom captcha, 1-per-day limit, guest email notifications, admin guest booking management | Development Team |
| 1.3 | Sep 27, 2026 | Added standalone Nablus workshop registration form (unlinked page at /forms/nablus-workshop) — see Update below | Development Team |
| 1.4 | Oct 31, 2026 | Public-site redesign approved by the client (mockup v3): new visual system for the public pages, compact booking card on the home page — see Update below | Development Team |
---
@@ -1145,4 +1146,59 @@ None outstanding — all decisions confirmed directly with Naser in the same ses
---
## Update — Version 1.4
**Date:** 2026-10-31
### New Features Added
**Public-site redesign.** ElenosLabs proposed a redesign of Libra's public website (mockup v3,
`docs/ux-mockup.html`); the client approved it on 2026-10-31. Scope answers given by Naser on
2026-10-31: scope "Public site" (home, about, posts, booking, auth); brand "Keep logo, evolve
rest" (the logo and the forest/gold/cream palette stay; type, layout and motion change); driver
"Our pitch". Naser's constraint on content: "we dont need to promice smth we wont deliver" — every
claim on the new pages is backed by existing behaviour. The full visual specification is
`docs/ux-design.md` Part A.
- **New visual system on every public page** — `/`, `/about`, `/posts`, `/posts/{post}`,
`/booking`, `/booking/success`, `/page/{slug}`: Markazi Text (display) and IBM Plex Sans Arabic
(body); the brand palette used in full-bleed forest bands, gold rules and a linen cream ground;
sections numbered as articles; a sticky translucent navigation with a reading-progress rule; a
four-column forest footer.
- **Signature balance** — an engraved-gold scales-of-justice graphic over olive-tree photography in
the home hero, settling into balance on load and responding to the pointer; a second balance in
the booking card comes level as the reader arrives. All motion respects
`prefers-reduced-motion`.
- **Redesigned home page** — hero, pillars (Justice, Dignity, Advocacy), founder, services docket,
vision with photography, values, latest three articles (text-only cards), and an embedded
booking card.
- **Compact booking on the home page** — the home page embeds the existing guest-booking form as
one screen (name, phone, email, a 14-day strip of days with free slots, slot buttons, case
summary, captcha), with the same rules as `/booking` (required fields, 20-character minimum
summary, captcha, one booking per email per day, slots from the availability settings, the
confirm step, the success page). This realises §5.1's "Embedded simplified booking form".
- **Auth pages** — the new type and form controls; the split layout is unchanged.
- **Production photography** — four licensed Unsplash photographs (olive tree, embroidery hoop,
olives, hills) and the founder's existing portrait.
### Scope Changes
- §5.1 and §7 (Design Requirements) are superseded for the public pages by `docs/ux-design.md`
Part A. §7's typography (Cairo/Montserrat), button radius and card specs continue to apply to
the admin and client dashboards, which are unchanged.
- §5.2 navigation gains a "Services" link (to the home page's services section); the menu labels
on the public site read Home, About, Services, Articles, Booking.
- Out of scope: admin and client dashboards, emails, PDF exports, `/forms/nablus-workshop`, any
backend or database change, post images or categories, new content beyond the redesign's
section labels and short lines approved in the mockup.
### Open Questions
None blocking. Rulings recorded in the project ledger (2026-10-31): the mockup's Unsplash photos
ship to production (replaceable by the client's own later); the home day strip covers the next 14
days with free slots; pages not shown in the mockup are composed from its components with their
existing content; auth pages change type and controls only. The build price is not yet set (a
commercial matter, not a product requirement).
---
**END OF DOCUMENT**
+147
View File
@@ -0,0 +1,147 @@
---
id: 24-1
title: 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout)
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Create the shared foundation every later redesign subtask builds on. It must be loaded only by
the public layout (and, for the stylesheet only, by the auth layouts in task 24-9), and every rule
must be scoped under `body.site` so the admin and client dashboards are unaffected.
The approved visual reference is the mockup file `docs/ux-mockup.html` (committed in `caec396`).
Where this task says "port lines X–Y", copy those CSS rules from that file and prefix every
selector with `body.site ` (for example `.btn-gold` → `body.site .btn-gold`; `:root{…}` →
`body.site{…}`; `body{…}` → `body.site{…}`; `html[dir=ltr] .x` → `html[dir=ltr] body.site .x`;
`html[lang=en] .x` → `html[lang=en] body.site .x`). The mockup's lines contain no images, so line
numbers are exact.
### Files to create
1. **`resources/css/public.css`** — contains, in this order:
- Tokens on `body.site` (from mockup lines 12–29):
`--forest:#2D3624; --forest-2:#232A1C; --gold:#A68966; --gold-ink:#8A7555; --gold-lt:#C4A882;
--cream:#F4F1EA; --olive:#4A5D23; --ink:#1E2219; --muted:#5B6152; --line:rgba(45,54,36,.14);
--display:"Markazi Text","Times New Roman",serif; --body:"IBM Plex Sans Arabic",system-ui,sans-serif;
--r:10px; --ease:cubic-bezier(.2,.7,.2,1);
--shadow-1:0 1px 2px rgba(30,34,25,.06),0 4px 14px rgba(30,34,25,.06);
--shadow-2:0 2px 4px rgba(30,34,25,.08),0 18px 40px -12px rgba(30,34,25,.28);`
- Page ground and type on `body.site` (port lines 32–39): `font-family:var(--body);
color:var(--ink); font-size:17px; line-height:1.75; background-color:var(--cream)` plus the
linen weave `background-image: repeating-linear-gradient(0deg, rgba(45,54,36,.025) 0 1px,
transparent 1px 3px), repeating-linear-gradient(90deg, rgba(45,54,36,.022) 0 1px, transparent
1px 4px)`. This overrides the inline `style="font-family: var(--font-…)"` on the body, so the
layout's inline style is removed (see layout changes below).
- Port lines 40–50 (img, links, `.wrap`, skip link, focus-visible, h1–h3, `.eyebrow`,
`.art-no`). `.wrap` = `max-width:1200px; margin-inline:auto; padding-inline:28px`.
Focus = `outline:2px solid var(--gold); outline-offset:3px; border-radius:4px`.
- Buttons: port lines 52–68 (`.btn`, `.btn-gold` with shimmer `::after`, `.btn-ghost`,
`.on-dark .btn-ghost`, `.btn .arr` and LTR mirroring). Class names `.btn`, `.btn-gold`,
`.btn-ghost` do not exist in `resources/css/app.css` (which uses `.btn-primary`,
`.btn-secondary`), and they are scoped under `body.site`, so there is no collision.
- Progress rule: port lines 70–73 (`#hem` → rename to `.site-progress`; fixed, top 0, z 60,
height 2px, gradient `linear-gradient(90deg,#8A7555,#E2CDA4,#A68966)`, `transform:scaleX(0)`,
origin right; `html[dir=ltr]` origin left).
- Sections: port lines 139–144 (`section{position:relative}`, `.sec{padding:120px 0}`,
`.sec-head`, `.sec-head h2`, `.sec-head p`).
- Reveal: port lines 286–290, changed so content is 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}`,
and the `data-d` 1–5 delays (.08s, .16s, .24s, .32s, .4s).
- Responsive base: at `max-width:760px`: `body.site{font-size:16px}`,
`body.site .wrap{padding-inline:18px}`, `body.site .sec{padding:80px 0}`.
- Reduced motion (port lines 324–327): under `@media (prefers-reduced-motion:reduce)`,
`body.site *, body.site *::before, body.site *::after{animation:none!important;
transition:none!important; scroll-behavior:auto!important}` and
`body.site [data-reveal]{opacity:1;transform:none}`.
- `html:has(body.site){scroll-behavior:smooth}`.
2. **`resources/js/public.js`** — imports `./site/reveal.js` and `./site/progress.js`, and calls
each module's `init()` on the `livewire:navigated` document event (Livewire 3 dispatches it on
the first page load and after every `wire:navigate`), and each module's `destroy()` on
`livewire:navigating`. On init it adds the class `js` to `document.body`.
3. **`resources/js/site/reveal.js`** — exports `init()`/`destroy()`. If
`matchMedia('(prefers-reduced-motion: reduce)').matches`, add class `in` to every
`[data-reveal]` and return. Otherwise create one `IntersectionObserver` with
`rootMargin: '0px 0px -8% 0px'`; when an observed element intersects, add class `in` and
unobserve it. `destroy()` disconnects the observer.
4. **`resources/js/site/progress.js`** — exports `init()`/`destroy()`. Adds a passive `scroll`
listener on `window` that sets `.site-progress` `style.transform = scaleX(p)` where
`p = max > 0 ? scrollY / max : 1` and `max = document.documentElement.scrollHeight - innerHeight`,
and toggles class `scrolled` on `#site-nav` when `scrollY > 8` (if that element exists; it is
added in task 24-2). Runs once on init. `destroy()` removes the listener.
5. **`resources/views/partials/site-fonts.blade.php`** — exactly:
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Markazi+Text:wght@400;500;600;700&family=IBM+Plex+Sans+Arabic:wght@300;400;500;600&display=swap" rel="stylesheet">
```
6. **`lang/ar/site.php`** and **`lang/en/site.php`** — each `<?php return [ ... ];` with the
keys `progress_label` (ar `'تقدّم القراءة'`, en `'Reading progress'`). Later subtasks add keys
to these files.
### Files to modify
7. **`vite.config.js`** — `input` becomes
`['resources/css/app.css', 'resources/js/app.js', 'resources/css/public.css', 'resources/js/public.js']`.
8. **`resources/views/components/layouts/public.blade.php`**:
- After `@include('partials.head')` add `@include('partials.site-fonts')` and
`@vite(['resources/css/public.css', 'resources/js/public.js'])`.
- `<body>` becomes `<body class="site min-h-screen flex flex-col">` (remove `bg-background`
and the inline `style="font-family: …"`).
- Immediately after the skip link add
`<div class="site-progress" aria-hidden="true"></div>`.
- Remove the spacer `<div class="h-16"></div>` (the navigation becomes sticky in 24-2; until
then the existing fixed navigation may overlap the first 64px — acceptable only between
24-1 and 24-2).
- `<main>` keeps `id="main-content" role="main" class="flex-1" tabindex="-1"` but its child
`<div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">` is removed, so
`{{ $slot }}` is rendered directly inside `<main>`.
9. Because the wrapper is gone, wrap the root content of each existing public page that is not
yet redesigned in `<div class="wrap py-8">…</div>` so these pages keep a contained width until
their own subtask restyles them:
- `resources/views/livewire/pages/home.blade.php`
- `resources/views/livewire/pages/about.blade.php` (also remove the negative margins
`-mx-4 sm:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8` from its first `<section>`)
- `resources/views/livewire/pages/booking.blade.php`
- `resources/views/livewire/pages/booking-success.blade.php`
- `resources/views/livewire/pages/posts/index.blade.php`
- `resources/views/livewire/pages/posts/show.blade.php`
- `resources/views/pages/legal.blade.php`
- `resources/views/pages/terms.blade.php`
- `resources/views/pages/privacy.blade.php`
(`resources/views/pages/booking.blade.php` is a dead placeholder — `/booking` is served by the
Volt page — and is left untouched.)
For a Volt page whose root is a single element, put the wrapper inside the root element so the
component keeps one root.
10. **`resources/views/partials/head.blade.php`** — change
`<meta name="theme-color" content="#4A4A42">` to `<meta name="theme-color" content="#2D3624">`.
Change nothing else in this file (the dashboards share it).
Do not modify `resources/css/app.css` or any file under `resources/views/livewire/admin/`,
`resources/views/livewire/client/`, `resources/views/components/layouts/app*`.
## Acceptance Criteria
- [ ] `npm run build` succeeds and `public/build/manifest.json` contains entries for
`resources/css/public.css` and `resources/js/public.js`.
- [ ] Every selector in `resources/css/public.css` is scoped under `body.site` (or
`html[...] body.site`, or `html:has(body.site)`), verify:
`grep -nE '^[^@/ }].*\{' resources/css/public.css | grep -v 'body.site'` returns nothing.
- [ ] The HTML of `/` contains `<body class="site`, the `Markazi+Text` font link, a
`public.css` and a `public.js` asset, and `<div class="site-progress"`.
- [ ] The HTML of `/admin/dashboard` (logged in as admin) and `/client/dashboard` (logged in as a
client) contains neither `public.css`, `public.js`, `Markazi` nor `class="site`.
- [ ] `resources/css/app.css` is unchanged: `git diff --quiet BASE -- resources/css/app.css`.
- [ ] On `/` with the browser at 390px wide, the document has no horizontal scroll, and every
public page listed in item 9 still renders its content inside a 1200px-max container.
- [ ] Scrolling `/` changes the `.site-progress` element's transform from `scaleX(0)` toward
`scaleX(1)`; with `prefers-reduced-motion: reduce` emulated, every `[data-reveal]` element
is visible without scrolling.
- [ ] `partials/head.blade.php` contains `content="#2D3624"` and no `#4A4A42`.
## Sub-tasks
None.
+63
View File
@@ -0,0 +1,63 @@
---
id: 24-10
title: 24-10 Redesign Integration Verification
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Verify the finished redesign as a whole before it is offered for deployment, and fix any defect
found within the files touched by tasks 24-1 to 24-9. This task adds no feature. Build the assets
(`npm run build`) first so the pages are not rendered from stale CSS.
Check, on the local site (`https://libra.test` via Valet, or `php artisan serve`), with at least
three published posts in the database and working hours configured so slots exist:
1. **Pages** — `/`, `/about`, `/posts`, one `/posts/{post}`, `/booking`, `/booking/success`,
`/page/terms`, `/page/privacy`, `/login`, `/forgot-password`: each in Arabic (RTL) and English
(LTR), each at 1440, 768 and 390px wide.
2. **Booking end-to-end** — one guest booking from the home card and one from `/booking`, each
reaching `/booking/success` and each appearing in the admin's pending bookings
(`/admin/bookings/pending`).
3. **Unchanged areas** — `/admin/dashboard`, `/admin/bookings/pending`, `/admin/posts`,
`/client/dashboard`, `/client/consultations/book`, `/forms/nablus-workshop`.
4. **Motion** — load `/` with and without `prefers-reduced-motion: reduce` emulated.
5. **Navigation** — move between public pages with the navigation links (`wire:navigate`) and back
to `/`.
Fix defects in the redesign's own files only: `resources/css/public.css`, `resources/js/public.js`,
`resources/js/site/*.js`, `resources/views/components/site/*.blade.php`,
`resources/views/components/navigation.blade.php`, `resources/views/components/footer.blade.php`,
`resources/views/components/layouts/public.blade.php`,
`resources/views/components/layouts/auth/*.blade.php`, `resources/views/partials/site-fonts.blade.php`,
`resources/views/livewire/pages/home.blade.php`, `about.blade.php`, `booking.blade.php`,
`booking-success.blade.php`, `posts/index.blade.php`, `posts/show.blade.php`,
`resources/views/livewire/availability-calendar.blade.php`, `resources/views/pages/legal.blade.php`,
`terms.blade.php`, `privacy.blade.php`, `lang/ar/site.php`, `lang/en/site.php`.
## Acceptance Criteria
- [ ] Every page in item 1, in both languages and at all three widths, has no horizontal scroll
(`document.documentElement.scrollWidth <= innerWidth`), no console error, and no missing
translation key (no raw `site.` or `home.` key text visible).
- [ ] Each page in item 1 has exactly one `<h1>`, every `<img>` has a non-empty `alt`, and every
interactive element in the navigation, footer and booking card is reachable by Tab with a
visible gold focus ring.
- [ ] Both bookings in item 2 succeed and appear in `/admin/bookings/pending`.
- [ ] The pages in item 3 contain no `public.css`, `public.js`, `class="site` or `site-auth`, and
`git diff BASE -- resources/css/app.css resources/views/livewire/admin resources/views/livewire/client resources/views/components/layouts/app.blade.php resources/views/components/layouts/app resources/views/livewire/pages/forms`
(BASE = the commit recorded before task 24-1) is empty.
- [ ] With reduced motion emulated, all `[data-reveal]` content is visible on load and every
balance is level; without it, the hero balance settles and the finale levels on scroll.
- [ ] After five `wire:navigate` hops ending on `/`, the hero balance and reveal still work and the
finale still levels.
- [ ] Contrast: body text on cream, `--gold-lt` text on forest, and the gold button text meet
WCAG AA (4.5:1 for body text, 3:1 for large text) — measured, values noted in the task's QA
record.
- [ ] No page contains the text "Unsplash" or "mockup".
## Sub-tasks
None.
+146
View File
@@ -0,0 +1,146 @@
---
id: 24-2
title: 24-2 Navigation and Footer
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Restyle the public navigation and footer to the approved redesign. Both components are used only
by `resources/views/components/layouts/public.blade.php`, so they are restyled in place. Keep all
existing behaviour: the Alpine `x-data="{ mobileMenuOpen: false }"` menu with its
`x-trap.inert.noscroll`, `@click.away` and `@keydown.escape.window` handling, the `@auth` branch
(Dashboard link to `route('admin.dashboard')` or `route('client.dashboard')` by
`auth()->user()->isAdmin()`, and the logout `<form method="POST" action="{{ route('logout') }}">`),
the language toggle's `/language/{locale}` links, and `wire:navigate` on internal links.
All CSS goes in `resources/css/public.css` (created in 24-1), every selector prefixed with
`body.site `. Port the CSS from `docs/ux-mockup.html`: navigation lines 75–98, footer lines
273–284, and the responsive nav/footer rules inside lines 293–323 (`.links` hidden and
`.menu-btn` shown at ≤1100px; `.nav .btn` and `.login` hidden at ≤760px; footer grid 2 columns at
≤1100px and 1 column at ≤760px).
### Navigation — `resources/views/components/navigation.blade.php`
- Root: `<header class="nav" id="site-nav">` — sticky (`position:sticky; top:0; z-index:50`),
height 72px, background `rgba(244,241,234,.82)`, `backdrop-filter: blur(14px) saturate(1.2)`
(with `-webkit-` prefix), bottom border transparent; with class `scrolled` (toggled by
`resources/js/site/progress.js` from 24-1 when `scrollY > 8`) the border becomes
`1px solid var(--line)` and the shadow `0 8px 24px -18px rgba(30,34,25,.5)`. Remove the old
fixed positioning and forest background.
- Inner `<div class="wrap">` laid out as flex, `align-items:center; gap:28px; height:72px`:
1. Brand link to `route('home')`: `<span class="seal">` (46×46px, background
`#fff url('/images/logo.png') center/cover no-repeat`, `border:2px solid #1A1A1A`,
`box-shadow:3px 3px 5px rgba(26,26,26,.35)`) then a two-line wordmark: `<b>` =
`__('site.brand_name')` (display font, 1.35rem, line-height 1) and `<small>` =
`__('site.brand_sub')` (.72rem, `--muted`; letter-spacing .12em in English).
`aria-label="{{ __('site.brand_label') }}"`.
2. `<nav class="links" aria-label="{{ __('site.nav_main') }}">` with links: Home →
`route('home')`, About → `route('about')`, Services → `route('home').'#services'`,
Articles → `route('posts.index')`, Booking → `route('booking')`. Labels from `site.nav_*`.
The current route's link gets `aria-current="page"` (home for `home`, about for `about`,
articles for `posts.*`, booking for `booking*`). Link style: padding 10px 14px, weight 500,
.95rem, radius 8px; hover colour `--olive`; `::after` underline — `inset-inline:14px;
bottom:4px; height:2px; background:repeating-linear-gradient(90deg,var(--gold) 0 4px,
transparent 4px 7px); transform:scaleX(0)`, origin right (left in LTR), transition
`.35s var(--ease)`; `scaleX(1)` on hover and on `[aria-current]`.
3. `.nav-tools` (flex, gap 10px): replace both `<x-language-toggle />` usages in this file
(desktop and mobile) with a plain link
`<a class="lang" href="{{ url('/language/'.(app()->getLocale() === 'ar' ? 'en' : 'ar')) }}" aria-label="{{ __('site.lang_switch') }}">`
— a pill (min 48×44px, inline-grid centred, radius 999px, `1.5px solid var(--line)`, white,
weight 600 .85rem, no underline; hover gold border and lift 1px) whose text is `EN` when the
locale is `ar` and `ع` when it is `en`. Do not edit
`resources/views/components/language-toggle.blade.php` (the auth layouts and the dashboard
sidebar use it); for guests a `.login` link (`__('site.nav_login')` →
`route('login')`, .92rem, min-height 44px, hover olive); for authenticated users the
existing Dashboard link and logout button styled like `.login`; then
`<a class="btn btn-gold" href="{{ route('home') }}#book">{{ __('site.nav_book') }}</a>`
(in the nav: min-height 44px, padding 10px 20px, .92rem); then the mobile menu button
`.menu-btn` (44×44px, radius 10px, `1.5px solid var(--line)`, white, hidden above 1100px)
carrying the existing Alpine toggle, `:aria-expanded`, and
`aria-label="{{ __('site.nav_menu') }}"`.
- Mobile menu sheet (the existing `x-show="mobileMenuOpen"` panel): cream background
(`var(--cream)`), directly under the nav, full width, each link a 48px-high row with a
`1px solid var(--line)` bottom border; contains the five links, the login (or Dashboard and
logout) and the gold Book button full width.
### Footer — `resources/views/components/footer.blade.php`
- `<footer class="site-footer">`: background `var(--forest-2)`, colour `var(--gold-lt)`,
margin-top 120px, padding 72px 0 28px, .92rem, position relative. First child
`<span class="foot-band" aria-hidden="true">` — absolute, top 0, full width, height 1px,
`linear-gradient(90deg,transparent,rgba(196,168,130,.7),transparent)`.
- `<div class="wrap"><div class="foot">` grid `1.4fr 1fr 1fr 1fr`, gap 40px:
1. The brand mark (same seal + wordmark as the nav; wordmark `<b>` cream, `<small>` gold-lt)
and `<p>` = `__('home.tagline')` (margin-top 16px, max-width 26em).
2. `<h4>{{ __('site.footer_explore') }}</h4>` then links About → `route('about')`, Services →
`route('home').'#services'`, Articles → `route('posts.index')`, Booking → `route('booking')`.
3. `<h4>{{ __('site.footer_clients') }}</h4>` then Client login → `route('login')`, Terms →
`route('terms')` with `__('site.footer_terms')`, Privacy → `route('privacy')` with
`__('site.footer_privacy')`.
4. `<h4>{{ __('site.footer_contact') }}</h4>` then `<a href="tel:+970599353502" dir="ltr">`
`{{ __('footer.phone') }}`, `<a href="mailto:{{ __('footer.email') }}">`, and the address
`{{ __('footer.address') }}` as plain text.
- `h4`: display font, 1.15rem, cream, margin 0 0 14px. Links: block, padding 10px 0, hover
colour cream and `translateX(-3px)` (RTL) / `translateX(3px)` (LTR).
- `.legal` row: flex, space-between, wrap, `border-top:1px solid rgba(244,241,234,.1)`,
margin-top 48px, padding-top 22px, .8rem, colour `rgba(196,168,130,.75)`; content
`© {{ date('Y') }} {{ __('footer.copyright') }}`. No photography credit.
### Language keys — add to `lang/ar/site.php` and `lang/en/site.php`
| Key | ar | en |
|---|---|---|
| `brand_name` | ليبرا | Libra |
| `brand_sub` | للحقوق | FOR RIGHTS |
| `brand_label` | ليبرا للحقوق | Libra for Rights |
| `nav_main` | القائمة الرئيسية | Main |
| `nav_home` | الرئيسية | Home |
| `nav_about` | من نحن | About |
| `nav_services` | الخدمات | Services |
| `nav_articles` | المقالات | Articles |
| `nav_booking` | الحجز | Booking |
| `nav_login` | دخول العملاء | Client login |
| `nav_book` | احجز استشارة | Book a consultation |
| `nav_menu` | القائمة | Menu |
| `lang_switch` | English | العربية |
| `footer_explore` | تصفّح | Explore |
| `footer_clients` | العملاء | Clients |
| `footer_contact` | تواصل | Contact |
| `footer_terms` | شروط الاستخدام | Terms of use |
| `footer_privacy` | سياسة الخصوصية | Privacy policy |
Do not edit `lang/*/navigation.php` or `lang/*/footer.php` (the dashboards use `navigation.php`).
### Files
- Modify: `resources/views/components/navigation.blade.php`,
`resources/views/components/footer.blade.php`, `resources/css/public.css`,
`lang/ar/site.php`, `lang/en/site.php`.
## Acceptance Criteria
- [ ] On `/` at 1440px the navigation is 72px high, stays at the top of the viewport while
scrolling, shows brand, five links, language pill, Client login and the gold Book button,
and gains a bottom border once the page is scrolled past 8px.
- [ ] On `/about` the About link has `aria-current="page"`; on `/posts` the Articles link does.
- [ ] At ≤1100px the five links are hidden and the menu button opens a sheet containing all five
links, login and Book; Escape closes it; at 390px there is no horizontal scroll.
- [ ] Logged in as a client, the navigation shows Dashboard (to `/client/dashboard`) and a working
Log out instead of Client login; logged in as admin, Dashboard goes to `/admin/dashboard`.
- [ ] The language pill reads `EN` on Arabic pages and `ع` on English pages and switches locale;
`resources/views/components/language-toggle.blade.php` is unchanged against BASE.
- [ ] The footer shows four columns at 1440px with the phone, email and address from
`lang/*/footer.php`, links to `/terms` and `/privacy`, and `© <current year>`; it contains no
"Unsplash" text.
- [ ] Every key in the table exists in both `lang/ar/site.php` and `lang/en/site.php`, and
`lang/*/navigation.php` and `lang/*/footer.php` are unchanged against BASE.
- [ ] The admin and client dashboards' navigation is unchanged (they do not render
`components/navigation.blade.php`): verify `grep -rn "x-navigation" resources/views` lists
only `components/layouts/public.blade.php`.
## Sub-tasks
None.
+81
View File
@@ -0,0 +1,81 @@
---
id: 24-3
title: 24-3 Signature Balance Component
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Build the redesign's signature: an engraved-gold balance (scales of justice) rendered as an
inline SVG, with three behaviours — `hero` (settles on load, weighed by the pointer), `finale`
(comes level as the reader scrolls to the booking card) and `static` (always level). Pages place
it in later subtasks (24-4 hero and vision card, 24-5 finale, 24-6 success page).
### Files to create
1. **`resources/views/components/site/balance.blade.php`** — props: `mode` (`hero` | `finale` |
`static`, default `static`), `label` (default null), `size` (integer px, default null; when set,
the SVG gets `width` and `height` of that many px). Renders
`<svg class="site-balance" data-balance="{{ $mode }}" viewBox="0 0 400 400" …>` with
`overflow:visible`; when `label` is set: `role="img" aria-label="{{ $label }}"`, otherwise
`aria-hidden="true"`. Pass through `$attributes` (class, id, style).
2. **`resources/js/site/balance.js`** — exports `init()` and `destroy()`; imported and wired in
`resources/js/public.js` exactly like `reveal.js` (init on `livewire:navigated`, destroy on
`livewire:navigating`). It contains:
- `build(svg)` — fills the SVG with the markup from `docs/ux-mockup.html` lines 566–595
(the `scales()` function's template: four gradients `gd`, `gc`, `gb`, `gh`, two filters
`sh` (drop shadow dy 8, stdDeviation 7, flood `#0d1009` opacity .45) and `bl` (blur 6), the
ground shadow ellipse, base, pillar, olive-leaf finial, the `.beam` group, the pivot, and two
`.pan` groups). Gradient and filter ids must be unique per instance (suffix a running
counter) so several balances on one page do not share ids. Returns `tilt(deg)` (mockup lines
597–602): `beam.setAttribute('transform', rotate(deg 200 110))`; with
`r = deg·π/180, c = cos(r)·142, s = sin(r)·142`, pan 0 at `translate(200−c, 110−s)` and pan 1
at `translate(200+c, 110+s)`.
- `spring(tilt, start)` (mockup lines 605–610): state `a = start, v = 0, target = 0`; each
animation frame `v += (target − a)·0.028; v ·= 0.935; a += v; tilt(a)`; keep requesting
frames while `|v| > 0.002` or `|target − a| > 0.01`. Exposes `set(target)`.
- Behaviour per `data-balance`:
- `static`: `tilt(0)`.
- `hero`: `tilt(14)`, create a spring from 14, after 450ms `set(0)`. If
`matchMedia('(pointer:fine)').matches`, find the closest ancestor `.frame` (the hero frame,
built in 24-4); on `pointermove` over it: `x = (clientX − rect.left) / rect.width`,
`set(clamp((x − 0.5)·2.4, −1, 1)·9)`; move the frame's `.glow` element's `left`/`top` to
the pointer position within the frame; set the frame's `img` transform to
`scale(1.08) translate(${(clientX − rect.left − rect.width/2)/−40}px, ${(clientY − rect.top − rect.height/2)/−40}px)`.
On `pointerleave`: `set(0)` and clear the image transform.
- `finale`: `tilt(−12)`, spring from −12; on a passive window `scroll` listener (and once on
init): `p = scrollY / (scrollHeight − innerHeight)` (1 if the denominator is ≤ 0),
`q = clamp((p − 0.7) / 0.25, 0, 1)`, `set(−12·(1 − q))`.
- If `matchMedia('(prefers-reduced-motion: reduce)').matches`: every balance is built and
`tilt(0)`; no spring, no listeners.
- `destroy()` removes every listener added and cancels pending animation frames.
3. CSS in **`resources/css/public.css`**: `body.site .site-balance{display:block;overflow:visible}`.
### Files to modify
- `resources/js/public.js` (import and wire `./site/balance.js`), `resources/css/public.css`.
## Acceptance Criteria
- [ ] `<x-site.balance mode="hero" label="ميزان العدالة" />` renders an `<svg>` with
`data-balance="hero"`, `viewBox="0 0 400 400"`, `role="img"` and the given `aria-label`;
without `label` it renders `aria-hidden="true"`.
- [ ] With two balances on one page, every `id` inside the SVGs is unique
(`document.querySelectorAll('[id]')` has no duplicate values).
- [ ] A `hero` balance is drawn tilted on load and its beam's `transform` reaches `rotate(0…)`
(|angle| < 0.05) within 4 seconds without pointer input.
- [ ] Moving a fine pointer to the right edge of the hero frame tips the beam toward +9° (in the
browser, read the beam's transform); leaving the frame returns it toward 0°.
- [ ] A `finale` balance's beam is at −12° at the top of the page and within 0.5° of 0° when the
page is scrolled to the bottom.
- [ ] With `prefers-reduced-motion: reduce` emulated, every balance's beam transform is
`rotate(0 200 110)` and no animation frame is requested after load.
- [ ] Navigating between public pages with `wire:navigate` and back to `/` re-initialises the
hero balance (it renders and settles) and leaves no duplicate scroll listeners (the finale
still levels correctly after three navigations).
## Sub-tasks
None.
+196
View File
@@ -0,0 +1,196 @@
---
id: 24-4
title: 24-4 Home Page Sections (Hero to Latest Articles)
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Rebuild `resources/views/livewire/pages/home.blade.php` as the approved redesign, sections 1–7
(hero, pillars, founder, services, vision, values, latest articles). Section 8, the booking card,
is task 24-5; leave a placeholder comment `{{-- booking card: task 24-5 --}}` where it goes. Keep
the component's PHP class as it is (`#[Layout('components.layouts.public')]`, `with()` returning
`latestPosts` = `Post::published()->latest('published_at')->take(3)->get()`). Remove the
temporary `<div class="wrap py-8">` wrapper added in 24-1: each section is full-bleed and holds a
`<div class="wrap">`.
The visual reference is `docs/ux-mockup.html` (commit `caec396`). Port the CSS for each section
from the line ranges below into `resources/css/public.css`, every selector prefixed with
`body.site `, and port the matching responsive rules from lines 293–323. Put `data-reveal` and
`data-d` attributes on the same elements as the mockup (lines 358–506).
### Photography — create `public/images/site/`
Extract these JPEG data URIs from `docs/ux-mockup.html` and save them, decoded, as files (a short
script reading the file and base64-decoding the `src="data:image/jpeg;base64,…"` value on each
line is enough; do not commit the script):
| Mockup line | `<img>` | Save as |
|---|---|---|
| 377 | hero olive tree | `public/images/site/olive-tree.jpg` |
| 447 | embroidery hoop | `public/images/site/embroidery-hoop.jpg` |
| 455 | olives on a branch | `public/images/site/olives.jpg` |
| 459 | hills and road | `public/images/site/hills.jpg` |
The founder portrait uses the existing `public/images/huda-armouche.jpg`. The images are from
Unsplash (licence permits commercial use without attribution); show no credit line.
### Sections (in order, inside the root `<div>`)
1. **Hero** — `<section class="hero" id="top">`; CSS lines 100–125. Two-column grid
(`1fr 1.08fr`, gap 56px; one column ≤1100px). Start column: `.eyebrow` = `site.hero_eyebrow`;
`<h1>` = `{{ __('site.hero_title_a') }}<em>{{ __('site.hero_title_em') }}</em>{{ __('site.hero_title_b') }}`
(the `em` is upright, `--olive`, with `text-decoration: underline dashed var(--gold) .07em;
text-underline-offset:.28em`); `<p class="lede">` = `home.intro`; CTA row: gold button
`home.cta_book` → `#book` with the arrow SVG (mockup line 366), ghost button
`home.cta_services` → `#services`; `.trust` strip with three items, each
`<strong>` + `<span>`: (`site.trust_1_title`, `site.trust_1_text`), (`site.trust_2_title`,
`site.trust_2_text`), (`site.trust_3_title`, `site.trust_3_text`). End column `.hero-art`:
`<figure class="frame">` (aspect 4/4.6, radius `999px 999px 18px 18px`, forest ground,
gradient veil `::before` from `rgba(35,42,28,.35)` to `.9`), containing
`<img src="/images/site/olive-tree.jpg" alt="{{ __('site.alt_olive_tree') }}">` (filter
`saturate(.75) contrast(1.05)`, `scale(1.08)`), `<span class="glow">` (420px radial gold
glow), `<x-site.balance mode="hero" :label="__('site.balance_label')" id="scales" />`
positioned `inset:10% 8% 12%`, and `<figcaption>` = `site.frame_caption` (display italic,
cream .78). Then `<div class="tag">` with `<span class="dot">` (olive, pulsing) and
`site.frame_tag`.
2. **Pillars band** — `<section class="band on-dark" aria-label="{{ __('site.pillars_label') }}">`;
CSS lines 127–137. Three `.pillar` columns: (`site.pillar_justice`, `site.pillar_justice_text`),
(`site.pillar_dignity`, `site.pillar_dignity_text`), (`site.pillar_advocacy`,
`site.pillar_advocacy_text`), each title preceded by `<span class="gem">` (CSS line 210).
3. **Founder** — `<section class="sec" id="founder">`; CSS lines 146–157. Portrait
`/images/huda-armouche.jpg` (alt `home.lawyer_name`) with `.namecard`
(`<b>` `home.lawyer_name`, `<span>` `site.founder_role`). Text column: `.art-no`
`site.art_founder`; `<h2>` `home.about_title`; `<blockquote>` `site.founder_quote`;
paragraphs `home.lawyer_bio` and `home.lawyer_bio_2`; ghost button `site.founder_cta` →
`route('about')`.
4. **Services docket** — `<section class="sec" id="services" style="padding-top:40px">`;
CSS lines 159–174. `x-site.section-head` (see below) with label `site.art_services`, title
`home.services_subtitle`, lede `site.services_lede`. Four `<a class="row" href="#book">` rows:
number `01`–`04` (`.n`), `<h3>` title, `.chips` of the items, `.go` arrow circle. Rows in order:
(`home.service_consultation_title`, `home.service_consultation_items`),
(`home.service_family_title`, `home.service_family_items`),
(`home.service_civil_title`, `home.service_civil_items`),
(`home.service_business_title`, `home.service_business_items`). Items are arrays — render each
as `<span class="chip">`.
5. **Vision** — `<section class="sec fabric" id="mission">`; CSS lines 176–197. Text column:
`.art-no` `site.art_vision`, `<h2>` `site.vision_title`, `<p class="big">` `site.vision_text`;
beside it `.hoop` with `/images/site/embroidery-hoop.jpg` (alt `site.alt_hoop`; filter
`hue-rotate(-140deg) saturate(.55) sepia(.25)`). Below, `.three` grid of `<article class="card">`:
(a) `.ph.motif-ph` containing `<x-site.balance mode="static" />` (150×150px) and label
`site.card_institution_label`, title `site.card_institution_title`, text
`site.card_institution_text`; (b) `.ph` with `/images/site/olives.jpg` (alt `site.alt_olives`),
label `site.card_society_label`, title `site.card_society_title`, text `site.card_society_text`;
(c) `.ph` with `/images/site/hills.jpg` (alt `site.alt_hills`), label
`site.card_diaspora_label`, title `site.card_diaspora_title`, text `site.card_diaspora_text`.
6. **Values** — `<section class="sec" id="values">`; CSS lines 199–212. Section head: label
`site.art_values`, title `home.values_title`, lede `home.values_subtitle`. `.sampler` grid of six
`.val` cards, each `<span class="medal">` with the line icon and `<b>` =
`home.values.<key>.title`, keys in order `integrity`, `justice`, `knowledge`, `empowerment`,
`professionalism`, `innovation`. Icons: the inline SVGs on mockup lines 474–479 in the same
order (24×24 viewBox, `aria-hidden="true"`, stroke `--gold-ink` 1.5, no fill). Replace the
current `flux:icon` usage.
7. **Latest articles** — `<section class="sec" id="posts" style="padding-top:20px">`; CSS lines
214–234. Head: label `site.art_articles`, title `home.posts_title`, and in place of the lede a
ghost button `home.view_all_posts` → `route('posts.index')`. If `$latestPosts` is not empty:
`.posts` grid of `<x-site.post-card :post="$post" />`; otherwise a `<p>` with
`home.posts_empty` (muted, centred).
### Components to create
- **`resources/views/components/site/section-head.blade.php`** — props `label`, `title`,
`lede` (nullable); renders
`<div class="sec-head"><div><span class="art-no" data-reveal>{{ $label }}</span><h2 data-reveal data-d="1">{{ $title }}</h2></div>`
then either `<p data-reveal data-d="2">{{ $lede }}</p>` or the `$slot` (for the button), then
`</div>`.
- **`resources/views/components/site/post-card.blade.php`** — prop `post` (App\Models\Post);
`<a class="post" href="{{ route('posts.show', $post) }}" wire:navigate data-reveal>` →
`<div class="bd">` with `<span class="meta">` = the published date
(`$post->published_at?->translatedFormat('j F Y')`), `<h3>` = `$post->getTitle()`, `<p>` = an
excerpt of `strip_tags($post->getBody())` limited to 140 characters (`Str::limit`), and
`<span class="more">` = `home.read_more`. Text-only: no image element.
### Language keys — add to `lang/ar/site.php` and `lang/en/site.php`
| Key | ar | en |
|---|---|---|
| `hero_eyebrow` | ليبرا للحقوق · فلسطين | Libra for Rights · Palestine |
| `hero_title_a` | العدالة␠ | Justice is a␠ |
| `hero_title_em` | ممارسةٌ يومية | daily practice |
| `hero_title_b` | ، لا مبدأٌ نظري. | , not a theory. |
| `trust_1_title` | أفراد وشركات | Individuals & companies |
| `trust_1_text` | استشارات وتمثيل | Consultations and representation |
| `trust_2_title` | المرأة والأسرة | Women & family |
| `trust_2_text` | دعم قانوني متخصص | Specialised legal support |
| `trust_3_title` | عربي · English | Arabic · English |
| `trust_3_text` | خدمة بلغتين | Bilingual service |
| `alt_olive_tree` | شجرة زيتون معمّرة بين صخور كلسية | An ancient olive tree among limestone rocks |
| `balance_label` | ميزان العدالة | The scales of justice |
| `frame_caption` | لكلّ حقٍّ وزنُه | Every right carries its weight |
| `frame_tag` | استشارات حضورية أو عن بُعد | Consultations in person or remotely |
| `pillars_label` | ركائزنا | Our pillars |
| `pillar_justice` | العدالة | Justice |
| `pillar_justice_text` | الإنصاف، والمساءلة، وسيادة القانون. | Fairness, accountability and the rule of law. |
| `pillar_dignity` | الكرامة | Dignity |
| `pillar_dignity_text` | القيمة الأصيلة لكل إنسان. | The inherent worth of every person. |
| `pillar_advocacy` | الدفاع | Advocacy |
| `pillar_advocacy_text` | انخراطٌ فاعل، وقولُ الحق في وجه السلطة. | Active engagement, and speaking truth to power. |
| `art_founder` | المادة ٠١ — المؤسِّسة | Article 01 — The founder |
| `founder_role` | المؤسِّسة · محامية | Founder · Attorney at Law |
| `founder_quote` | «العدالة ممارسة يومية، وليست مجرد مبدأ نظري.» | “Justice is a daily practice, not merely a theoretical principle.” |
| `founder_cta` | اقرأ قصتنا | Read our story |
| `art_services` | المادة ٠٢ — الخدمات | Article 02 — Services |
| `services_lede` | ممارسة قانونية أصيلة، تقوم على البحث والمعرفة الميدانية. | Traditional legal practice, combined with research-driven insight. |
| `art_vision` | المادة ٠٣ — الرؤية | Article 03 — Vision |
| `vision_title` | منسوجةٌ من نسيج المجتمع | Woven from the fabric of society |
| `vision_text` | ليبرا نسيجٌ أصيل من المجتمع، تتقصّى مشكلاته وتوثّقها وتكشف جذورها، وتقدّم حلولاً عملية لرأب أوجاعه مهما صغرت، عبر العمل التشاركي والبحث الميداني الحقيقي. | Libra investigates society's problems, documents them, uncovers their roots, and offers practical solutions to mend social pains — however small — through collaborative work and genuine field research. |
| `alt_hoop` | تطريز بغرزة الصليب مشدود على طارة خشبية | Cross-stitch embroidery stretched on a wooden hoop |
| `card_institution_label` | لمؤسستنا | For our institution |
| `card_institution_title` | القانون والبحث والميدان | Law, research and fieldwork |
| `card_institution_text` | نلتقط نبض المجتمع ونحوّل تحدياته إلى معرفة وحلول قانونية مبتكرة. | Capturing society's pulse and turning its challenges into knowledge and innovative legal solutions. |
| `alt_olives` | ثمار زيتون على غصن | Olives ripening on a branch |
| `card_society_label` | للمجتمع | For society |
| `card_society_title` | شريكٌ موثوق للقضاء | A trusted partner to the judiciary |
| `card_society_text` | نعيد وصل المجتمع بتاريخه وإرثه القانوني. | Reconnecting society with its history and legal heritage. |
| `alt_hills` | تلال وطريق متعرّج عند الغروب | Hills and a winding road at sunset |
| `card_diaspora_label` | لفلسطينيي الشتات | For Palestinians in the diaspora |
| `card_diaspora_title` | عودةٌ إلى الأرض والعائلة | Back to the land and the family |
| `card_diaspora_text` | نساعد الفلسطينيين في الخارج على البحث في عائلاتهم وتاريخهم وأراضيهم. | Helping Palestinians abroad research their families, their history and their land. |
| `art_values` | المادة ٠٤ — القيم | Article 04 — Values |
| `art_articles` | المادة ٠٥ — المقالات | Article 05 — Articles |
`␠` marks a trailing space that must be kept in the value.
### Files
- Create: `public/images/site/olive-tree.jpg`, `public/images/site/embroidery-hoop.jpg`,
`public/images/site/olives.jpg`, `public/images/site/hills.jpg`,
`resources/views/components/site/section-head.blade.php`,
`resources/views/components/site/post-card.blade.php`.
- Modify: `resources/views/livewire/pages/home.blade.php`, `resources/css/public.css`,
`lang/ar/site.php`, `lang/en/site.php`.
- Do not edit `lang/*/home.php`.
## Acceptance Criteria
- [ ] `/` renders, in order, sections with ids `top`, (the pillars band), `founder`, `services`,
`mission`, `values`, `posts`, each with its content from the keys above in both Arabic and
English (switch with `/language/en`).
- [ ] `/` has exactly one `<h1>`, and every `<img>` has a non-empty `alt` in both languages.
- [ ] The four files in `public/images/site/` exist, are valid JPEGs (`file` reports JPEG image
data) and load with HTTP 200 on `/`.
- [ ] The hero balance renders inside the hero frame and settles level (task 24-3 behaviour).
- [ ] Each services row links to `#book`; each row shows the item chips from `home.php`.
- [ ] With three or more published posts, three post cards show date, title, excerpt and "Read
more", link to `/posts/{post}`, and contain no `<img>`; with no published posts, the
`home.posts_empty` text shows instead of the grid.
- [ ] At 1440, 768 and 390px wide, `/` has no horizontal scroll (`document.documentElement.scrollWidth <= innerWidth`).
- [ ] Hovering a service row fills it forest with cream text; hovering a value card lifts it and
rotates its medallion; hovering a post card lifts it.
- [ ] The page contains no "Unsplash" text and `lang/*/home.php` is unchanged against BASE.
## Sub-tasks
None.
+145
View File
@@ -0,0 +1,145 @@
---
id: 24-5
title: 24-5 Home Booking Card (Compact Booking and Day Strip)
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Add section 8 of the home page: a forest booking card that embeds the existing guest-booking
component in a one-screen "compact" mode, with a 14-day strip of bookable days. The booking rules
must not change: name, email and phone required; case summary required, minimum 20 characters;
the math captcha (`App\Services\CaptchaService`); one booking per email per day; slots from
`App\Services\AvailabilityService`; the existing confirm step (`showConfirmation`); and the
existing `submit()` that redirects to `route('booking.success')`. Logged-in visitors see the
existing logged-in message instead of the form.
### 1. Availability calendar — `resources/views/livewire/availability-calendar.blade.php`
- Add `public string $variant = 'month';` (settable from the tag: `variant="strip"`). The default
`month` must render exactly as today, because
`resources/views/livewire/client/consultations/book.blade.php` also uses this component.
- When `$variant === 'strip'`:
- Compute `$stripDays`: starting today, step one day at a time up to 60 days ahead and collect
the first 14 dates for which `AvailabilityService::getDateStatus($date)` returns
`'available'` or `'partial'` (the same two values `selectDate()` accepts today), each with
its `getAvailableSlots($date)`.
- On mount, select the first strip day (`$selectedDate`) and load its slots with the existing
`loadAvailableSlots()`.
- Render a day strip: `<div class="days" role="group" aria-label="{{ __('site.booking_day') }}">`
of `<button type="button" class="day" aria-pressed="true|false" wire:click="selectDate('Y-m-d')">`
with the weekday name (`translatedFormat('l')`) and `<b>` the day number. Then a slot row:
`<div class="slots" role="group" aria-label="{{ __('site.booking_time') }}">` of
`<button type="button" class="slot" dir="ltr" aria-pressed=…>` per slot, clicking calls the
existing `$parent.selectSlot('<date>', '<slot>')`, and the button for the parent's current
`selectedTime` on the selected date is pressed. Slot labels use the same time format as the
month view.
- No strip day available within 60 days → show `site.booking_none` (ar «لا توجد مواعيد
متاحة حالياً»; en "No times are available at the moment") in place of the strip and slots.
- CSS (in `resources/css/public.css`, scoped `body.site`; mockup `docs/ux-mockup.html` lines
256–265): `.days` flex, gap 8px, `overflow-x:auto`, `scrollbar-width:none`; `.day` 66×72px min,
radius 14px, `1.5px solid rgba(45,54,36,.15)`, white, .78rem muted, `<b>` display font 1.35rem
ink; hover gold border and lift 2px; `[aria-pressed=true]` forest background, `--gold-lt` text,
cream number, shadow `0 10px 22px -10px rgba(45,54,36,.8)`. `.slots` flex-wrap gap 8px;
`.slot` min-height 44px, padding 8px 16px, pill, same border, white; hover gold border;
`[aria-pressed=true]` gold fill, gold border, ink text.
### 2. Booking component — `resources/views/livewire/pages/booking.blade.php`
- Add `public bool $compact = false;` and accept it in `mount(bool $compact = false)` (keep the
existing mount logic). The `#[Layout]` attribute stays; it is ignored when the component is
embedded.
- When `$compact` is true, render (instead of the page markup) a
`<form class="book-r" wire:submit="showConfirm">` (mockup CSS lines 247–267: cream, padding
44px 44px 40px, margin 12px, radius 20px) containing, in order:
1. `.f2` two-column row: full name (`guestName`, `autocomplete="name"`) and phone
(`guestPhone`, `type="tel" dir="ltr" autocomplete="tel"`), labels from the existing
`booking.*` keys with ` *`;
2. email (`guestEmail`, `type="email" dir="ltr" autocomplete="email"`);
3. `<livewire:availability-calendar variant="strip" />` — the strip variant itself renders a
visible label `site.booking_day` (.85rem, weight 600, margin-bottom 6px) above `.days` and
`site.booking_time` above `.slots`;
4. case summary textarea (`problemSummary`, rows 3, label `booking.problem_summary` + ` *`,
help text `booking.problem_summary_help`);
5. the captcha question and input (`captchaAnswer`, `inputmode="numeric" dir="ltr"`,
max-width 140px) exactly as the page version renders it;
6. `<button type="submit" class="btn btn-gold">` `site.booking_submit`, full width;
7. `<p class="note">` `booking.limit_message`.
Validation errors render under each field as in the page version. Submitting without a
selected slot shows the existing `selectedTime` error.
- When `$compact` and `$showConfirmation` are true, the same `.book-r` panel shows the existing
confirmation summary (date, time, name, email, phone, summary) with the existing Confirm
(`submit`) and Back (`$set('showConfirmation', false)`) actions, styled `btn-gold` and
`btn-ghost`.
- When `$compact` and the visitor is logged in, the panel shows the existing
`booking.logged_in_title`, `booking.logged_in_message` and the `booking.go_to_dashboard` link
(`route('client.consultations.book')`) styled as `btn-gold`.
- Inputs and textarea (scoped `body.site .book-r`): min-height 48px, padding 12px 16px, radius
12px, `1.5px solid rgba(45,54,36,.18)`, white, `font:400 1rem var(--body)`; focus border
`--gold` and `box-shadow:0 0 0 4px rgba(166,137,102,.2)`; labels .85rem weight 600. Flux inputs
may be replaced by plain inputs in compact mode, keeping the same `wire:model` names.
- The non-compact (page) rendering is unchanged by this task.
### 3. Home page — `resources/views/livewire/pages/home.blade.php`
Replace `{{-- booking card: task 24-5 --}}` with
`<section id="book"><div class="wrap"><div class="book" data-reveal>` (mockup CSS lines 236–246:
forest, radius 28px, two columns `1fr 1.1fr`, one column ≤1100px, shadow
`0 50px 90px -40px rgba(30,34,25,.8)`), containing:
- `<div class="book-l on-dark">` (padding 56px 52px; 40px 24px ≤760px): `.eyebrow`
`site.art_booking` (colour `--gold-lt`), `<h2>` `site.booking_title` (cream), `<p>`
`site.booking_lede` (`--gold-lt`), `.contact` list — phone link `tel:+970599353502` with
`<span dir="ltr">{{ __('footer.phone') }}</span>`, email link `mailto:` `footer.email`, and the
address `footer.address`, each with the 20px gold line icon from mockup lines 517–519 — and
`<x-site.balance mode="finale" style="width:190px;height:190px;margin-top:22px" />`.
- `<livewire:pages.booking :compact="true" />`.
### Language keys — add to `lang/ar/site.php` and `lang/en/site.php`
| Key | ar | en |
|---|---|---|
| `art_booking` | المادة ٠٦ — الاستشارة | Article 06 — Consultation |
| `booking_title` | احجز استشارة | Book a consultation |
| `booking_lede` | اختر يوماً ووقتاً متاحَين، وأخبرنا باختصار عن قضيتك. نراجع كل طلب ونؤكّد الموعد بالبريد الإلكتروني — دون الحاجة إلى حساب. | Choose an available day and time and tell us briefly about your case. We review every request and confirm by email — no account needed. |
| `booking_day` | اليوم | Day |
| `booking_time` | الوقت | Time |
| `booking_submit` | اطلب هذا الموعد | Request this appointment |
| `booking_none` | لا توجد مواعيد متاحة حالياً | No times are available at the moment |
### Files
- Modify: `resources/views/livewire/availability-calendar.blade.php`,
`resources/views/livewire/pages/booking.blade.php`,
`resources/views/livewire/pages/home.blade.php`, `resources/css/public.css`,
`lang/ar/site.php`, `lang/en/site.php`.
- Do not modify `app/Services/AvailabilityService.php`, `app/Services/CaptchaService.php`,
`resources/views/livewire/client/consultations/book.blade.php`, or any migration.
## Acceptance Criteria
- [ ] `/` shows the booking card with id `book`: the forest panel (title, lede, phone, email,
address, finale balance) and the cream form with fields in the order name, phone, email,
day strip, slots, summary, captcha, submit, one-per-day note.
- [ ] The day strip lists up to 14 dates, all of which have at least one slot in
`AvailabilityService::getAvailableSlots()`; the first is pressed on load and its slots show;
clicking another day loads that day's slots.
- [ ] A guest who fills valid data, picks a slot, answers the captcha and confirms creates exactly
one guest consultation (same attributes as a booking made on `/booking`) and lands on
`/booking/success`.
- [ ] A summary of fewer than 20 characters, a wrong captcha answer, a missing slot, or a second
booking with the same email on the same day each produce the same error as on `/booking`,
and no consultation is created.
- [ ] Logged in as a client, `/` shows the logged-in message with a link to
`/client/consultations/book` and no form.
- [ ] `/booking` and `/client/consultations/book` render the month calendar as before this task
(`variant` defaults to `month`).
- [ ] The finale balance is tilted at the top of `/` and level when scrolled to the booking card at
the end of the page.
- [ ] At 390px the card is one column, the day strip scrolls horizontally inside the card, and the
page has no horizontal scroll.
## Sub-tasks
None.
+90
View File
@@ -0,0 +1,90 @@
---
id: 24-6
title: 24-6 Booking Page and Booking Success
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Restyle `/booking` (the non-compact rendering of `resources/views/livewire/pages/booking.blade.php`)
and `/booking/success` (`resources/views/livewire/pages/booking-success.blade.php`) to the
redesign. Behaviour does not change: the month calendar, the guest fields, the captcha, the
confirm step, the one-per-day rule, the logged-in message, and the redirect to `/booking/success`.
Task 24-5 already added `$compact`, the strip variant and the `.book`, `.book-l`, `.book-r`,
input and label styles in `resources/css/public.css`; reuse them.
### `/booking` (non-compact branch of `booking.blade.php`)
Replace the page markup (keeping every `wire:` binding, error output, and the
`@if($isLoggedIn)` / step logic) with:
`<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')" />` →
`<div class="book">`:
- `.book-l.on-dark`: `.contact` list (phone `tel:+970599353502` with
`<span dir="ltr">{{ __('footer.phone') }}</span>`, `mailto:` `footer.email`, address
`footer.address`, same 20px gold icons as the home card) and
`<x-site.balance mode="static" style="width:190px;height:190px;margin-top:22px" />`.
- `.book-r` (cream inset): for a logged-in visitor, the existing logged-in title, message and
`booking.go_to_dashboard` link (`route('client.consultations.book')`) as `btn btn-gold`.
For a guest: step 1 — `booking.select_date_time` then `<livewire:availability-calendar />`
(month variant); step 2 — the selected date/time summary with the existing "change" action,
the fields name, email, phone, case summary, captcha, the gold submit; step 3 — the existing
confirmation summary with Confirm (`btn-gold`) and Back (`btn-ghost`). The existing
`session('success')` callout stays at the top of `.book-r`.
Below the card, inside the same `.wrap`, keep `<x-booking-expectations class="mt-10" />`
unchanged.
### Month calendar restyle (CSS only, scoped to the public site)
In `resources/css/public.css`, under `body.site`, style the month grid rendered by
`resources/views/livewire/availability-calendar.blade.php` (month variant) without editing its
markup: day cells min 44×44px, radius 10px; available and partial days white with a 6px gold dot
below the number (`::after`, `background:var(--gold)`); the selected day `background:var(--forest)`
with cream text; unavailable, closed, blocked and past days `--muted` text at 50% opacity, no
hover; month navigation buttons as 44px pills with `1.5px solid var(--line)`. Read the template
to find the exact classes or `wire:click` attributes the selectors must target; if the template
has no class to hook onto, add a class attribute only (e.g. `site-cal-day`, plus
`is-available`, `is-selected`) without changing structure or behaviour, so the client dashboard
(which has no `body.site`) renders as before.
### `/booking/success` — `booking-success.blade.php`
`<section class="sec"><div class="wrap">` → a centred cream card (max-width 560px, margin-inline auto,
`background:var(--cream)`, `1px solid var(--line)` border, radius 20px, `--shadow-2`, padding
48px 40px, text-align center) containing
`<x-site.balance mode="static" style="width:120px;height:120px;margin:0 auto 18px" />`, `<h1>`
`booking.success_title` (display font, `clamp(2rem,3.4vw,2.6rem)`), `<p>`
`booking.success_message` (muted), and `<a class="btn btn-ghost" href="{{ route('home') }}" wire:navigate>`
`site.nav_home`. Remove the `flux:icon` check-circle, `flux:heading` and `flux:button`.
### Files
- Modify: `resources/views/livewire/pages/booking.blade.php`,
`resources/views/livewire/pages/booking-success.blade.php`, `resources/css/public.css`, and
(class attributes only, if needed) `resources/views/livewire/availability-calendar.blade.php`.
## Acceptance Criteria
- [ ] `/booking` as a guest shows the section head, the forest card with contact details and a
level balance, and the month calendar inside the cream panel; selecting an available day and
a slot reveals the fields; completing the form and confirming creates one guest consultation
and redirects to `/booking/success`.
- [ ] Every validation rule behaves as before this task (required fields, 20-character summary,
captcha, one booking per email per day), each error rendered under its field.
- [ ] `/booking` logged in as a client shows the logged-in message and the link to
`/client/consultations/book`, and no form.
- [ ] In the month calendar on `/booking`, available days show a gold dot, the selected day is
forest with cream text, and unavailable days are dimmed; day cells are at least 44×44px.
- [ ] `/client/consultations/book` (logged in as a client) renders the calendar as before
(no `body.site` styles apply there).
- [ ] `/booking/success` shows the balance, title, message and a Home button, and contains no
`flux:` markup output (`data-flux-` attributes) in its main content.
- [ ] `/booking` and `/booking/success` have no horizontal scroll at 1440, 768 and 390px.
## Sub-tasks
None.
+85
View File
@@ -0,0 +1,85 @@
---
id: 24-7
title: 24-7 About Page
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Restyle `/about` (`resources/views/livewire/pages/about.blade.php`) with the redesign's components,
using only the page's existing content from `lang/*/about.php` (no new copy beyond the article
labels below). Keep the Volt class and layout attribute. Remove the temporary
`<div class="wrap py-8">` from 24-1; each section is full-bleed with its own `.wrap`. The
Scholarship section stays removed (it was taken out on 2026-01-11 at Naser's request).
Sections, in order (all CSS classes already exist in `resources/css/public.css` from 24-1 to 24-5;
add only what is listed as new):
1. **Hero** — `<section class="sec" style="padding-bottom:64px">`, `.wrap`, max-width 52em:
`.eyebrow` = `about.tagline`; `<h1>` = `about.title` (display font,
`clamp(2.6rem,4.6vw,4.1rem)`, line-height 1.12); `<p class="lede">` = `about.identity`.
2. **Vision and mission** — `<section class="sec fabric">`: `.fabric-grid` with the text column
(`.art-no` `site.about_art_vision`, `<h2>` `about.vision_title`, `<p class="big">`
`about.vision_text`, then `<h3>` `about.mission_title` and `<p>` `about.mission_intro`) and the
`.hoop` photo `/images/site/embroidery-hoop.jpg` (alt `site.alt_hoop`); below, `.three` cards
exactly as on the home page but with the about texts: (a) static balance panel, label
`site.card_institution_label`, title `about.mission_institution_title`, text
`about.mission_institution_text`; (b) `/images/site/olives.jpg`, label
`site.card_society_label`, title `about.mission_society_title`, text
`about.mission_society_text`; (c) `/images/site/hills.jpg`, label `site.card_diaspora_label`,
title `about.mission_diaspora_title`, text `about.mission_diaspora_text`.
3. **Values** — `<section class="sec">`: `<x-site.section-head :label="__('site.about_art_values')" :title="__('about.values_title')" :lede="__('about.values_intro')" />`,
then the `.sampler` of six `.val` medallion cards with the same six icons and order as the home
page (inline SVGs from `docs/ux-mockup.html` lines 474–479), names `about.value_integrity`,
`about.value_justice`, `about.value_knowledge`, `about.value_women`,
`about.value_professionalism`, `about.value_innovation`.
4. **Goals** — `<section class="sec" style="padding-top:20px">`: section head with label
`site.about_art_goals`, title `about.goals_title`; then a `.docket` list (`<ol>`, no bullets)
with one `.row.row-static` per item of `about.goals` (an array of 7 strings): `.n` = the
two-digit number (`01`–`07`) and a `<p>` with the goal text (1.1rem, ink). New CSS:
`body.site .row-static{grid-template-columns:90px 1fr}` (52px 1fr at ≤760px) and
`body.site .row-static:hover{background:none;color:inherit;transform:none;box-shadow:none}`,
`body.site .row-static::before{display:none}`.
5. **Legal services** — `<section class="sec" id="services" style="padding-top:20px">`: section
head with label `site.about_art_services`, title `about.services_title`, lede
`about.services_intro`; four `<a class="row">` rows linking to `route('booking')`, as on the
home page, with titles and chips from `about.service_consultation_title/items`,
`about.service_family_title/items`, `about.service_civil_title/items`,
`about.service_business_title/items`.
6. **Closing CTA** — `<section class="band on-dark">` with a `.wrap` centred column, padding
72px 0: `<h2>` `site.about_cta_title` (cream, display font) and
`<a class="btn btn-gold" href="{{ route('booking') }}" wire:navigate>` `about.services_cta`.
All elements that reveal on the home page carry `data-reveal` here too.
### Language keys — add to `lang/ar/site.php` and `lang/en/site.php`
| Key | ar | en |
|---|---|---|
| `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 |
### Files
- Modify: `resources/views/livewire/pages/about.blade.php`, `resources/css/public.css`,
`lang/ar/site.php`, `lang/en/site.php`. Do not edit `lang/*/about.php`.
## Acceptance Criteria
- [ ] `/about` renders the six sections in order with all existing `about.*` content in Arabic and
English: identity, vision, mission intro and three mission cards, six values, seven goals,
four service groups with their items.
- [ ] `/about` has exactly one `<h1>`; every `<img>` has a non-empty `alt`.
- [ ] Goal rows have no hover fill; service rows have the forest hover fill and link to `/booking`.
- [ ] No Scholarship content appears (`grep -i scholarship resources/views/livewire/pages/about.blade.php` returns nothing).
- [ ] `/about` has no horizontal scroll at 1440, 768 and 390px.
- [ ] `lang/*/about.php` is unchanged against BASE.
## Sub-tasks
None.
+91
View File
@@ -0,0 +1,91 @@
---
id: 24-8
title: 24-8 Articles and Legal Pages
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Restyle the articles index, the article page and the legal pages. Behaviour does not change:
search (`wire:model.live.debounce.300ms="search"`), pagination (`paginate(10)`), result counts,
empty states, the 404 for unpublished posts, and the CMS content of legal pages. Remove the
temporary `<div class="wrap py-8">` wrappers added in 24-1.
### `/posts` — `resources/views/livewire/pages/posts/index.blade.php`
`<section class="sec" style="padding-top:56px"><div class="wrap">`:
- `<x-site.section-head :label="__('site.hero_eyebrow')" :title="__('site.nav_articles')">`
(the h2 reads «المقالات» / "Articles"; the existing `posts.posts` key reads "Posts" in English, so
it is not used here) with,
in the slot, the search field: a pill input (`.search`: min-height 48px, radius 999px, padding
12px 20px, `1.5px solid rgba(45,54,36,.18)`, white, width min(360px, 100%); focus gold border
plus `0 0 0 4px rgba(166,137,102,.2)`), same `wire:model` and placeholder
`posts.search_placeholder`, with an accessible label (`sr-only` text
`posts.search_placeholder`).
- The existing result-count / no-result lines (`posts.search_results`, `posts.no_results`) as a
muted paragraph, and the existing clear-search action as `btn btn-ghost`.
- The posts as `<x-site.post-card :post="$post" />` in a `.posts` grid (3 columns; 2 at ≤1100px;
1 at ≤760px — the grid CSS exists from 24-4; at this page the last card does not span two
columns: add `body.site .posts-index .post:last-child{grid-column:auto}` and give the grid the
extra class `posts-index`).
- Empty states `posts.no_posts` as before.
- Pagination: `{{ $posts->links() }}` kept; style its links under `body.site .pagination-wrap`
(wrap the call in `<div class="pagination-wrap">`): 44px pill buttons, `1.5px solid var(--line)`,
current page `background:var(--forest)` with cream text, disabled at 50% opacity.
### `/posts/{post}` — `resources/views/livewire/pages/posts/show.blade.php`
`<section class="sec" style="padding-top:56px"><article class="wrap" style="max-width:768px">`
(720px content + padding):
- `<span class="meta">` the published date (existing `translatedFormat('l, d F Y')` expression),
colour `--gold-ink`, .85rem, weight 600.
- `<h1>` = `$post->getTitle()` — display font, `clamp(2rem,3.4vw,3rem)`, margin 10px 0 28px.
- Body `<div class="prose prose-lg prose-brand max-w-none site-prose">{!! $post->getBody() !!}</div>`
(keep the existing output exactly). New CSS: `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}`.
- Footer: `<a class="btn btn-ghost" href="{{ route('posts.index') }}" wire:navigate>`
`posts.back_to_posts`, preceded by a hairline `border-top:1px solid var(--line)` with 48px
margin-top and 24px padding-top. Remove `flux:heading`.
### Legal pages
- `resources/views/pages/legal.blade.php` — inside `<x-layouts.public>`:
`<section class="sec" style="padding-top:56px"><div class="wrap" style="max-width:768px">`,
`<h1>` `$page->getTitle()` (same h1 style as the article page), the existing
`prose prose-brand` block with the class `site-prose` added and its card styling replaced by
white background, radius 16px, `--shadow-1`, padding 40px (24px ≤760px); the existing
"last updated" line as muted .85rem text. Keep the `clean()` call and the
`pages.content_coming_soon` fallback unchanged.
- `resources/views/pages/terms.blade.php` and `resources/views/pages/privacy.blade.php` — the same
structure with their existing title and placeholder text. (`/terms` and `/privacy` redirect to
`/page/terms` and `/page/privacy`; these two views are kept in case they are rendered
directly.)
### Language keys
None new; `site.hero_eyebrow` (24-4) and `site.nav_articles` (24-2) are reused.
### Files
- Modify: `resources/views/livewire/pages/posts/index.blade.php`,
`resources/views/livewire/pages/posts/show.blade.php`, `resources/views/pages/legal.blade.php`,
`resources/views/pages/terms.blade.php`, `resources/views/pages/privacy.blade.php`,
`resources/css/public.css`.
## Acceptance Criteria
- [ ] `/posts` shows the section head, a working search (typing filters results after the
debounce and shows the count line), text-only post cards and styled pagination when there
are more than 10 published posts.
- [ ] `/posts/{post}` for a published post shows date, a display-font `<h1>`, the body and a Back
button to `/posts`; an unpublished post still returns 404.
- [ ] `/page/terms` and `/page/privacy` show the title, the sanitised CMS content (or the
coming-soon text) and the last-updated line.
- [ ] Each of these pages has exactly one `<h1>` and no horizontal scroll at 1440, 768 and 390px.
- [ ] The admin posts screens (`/admin/posts`) are unchanged (they do not use the public layout).
## Sub-tasks
None.
+67
View File
@@ -0,0 +1,67 @@
---
id: 24-9
title: 24-9 Auth Pages Type and Controls
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Apply the redesign's type and form controls to the auth pages (login, forgot password, reset
password, confirm password, verify email, two-factor challenge) without changing their
composition. The split layout (forest panel with the logo on the start side at ≥1024px, cream form
panel with the existing `auth-pattern` background, the language toggle at the top end, and the
mobile logo above the form) stays exactly as it is. No public script is loaded on auth pages.
### Layouts
In each of `resources/views/components/layouts/auth/split.blade.php`,
`resources/views/components/layouts/auth/card.blade.php` and
`resources/views/components/layouts/auth/simple.blade.php`:
- After `@include('partials.head')` add `@include('partials.site-fonts')` and
`@vite(['resources/css/public.css'])`.
- Add the class `site-auth` to `<body>` and remove the inline
`style="font-family: var(--font-…)"` from `<body>`.
### CSS — `resources/css/public.css`, scoped under `body.site-auth`
- `body.site-auth{font-family:"IBM Plex Sans Arabic",system-ui,sans-serif;color:#1E2219}`.
- Headings (`h1, h2, [data-flux-heading]`): `font-family:"Markazi Text","Times New Roman",serif;
font-weight:700`.
- Text inputs (`input[type=email], input[type=password], input[type=text]`) — including those
rendered by `flux:input`: min-height 48px, radius 12px, border `1.5px solid rgba(45,54,36,.18)`,
white background; focus border `#A68966` and `box-shadow:0 0 0 4px rgba(166,137,102,.2)`, no
default outline.
- Primary submit buttons (`[data-flux-button][type=submit]` and `button[type=submit]`): radius
999px, min-height 48px, background `#A68966`, text `#1E2219`, weight 600, shadow
`0 1px 0 rgba(255,255,255,.35) inset, 0 8px 22px -8px rgba(166,137,102,.8)`; hover lift 2px.
- Links inside the form panel: colour `#4A5D23`, underline on hover.
- Focus ring for every control: `outline:2px solid #A68966; outline-offset:3px`.
- Reduced motion: no transitions under `prefers-reduced-motion: reduce`.
Inspect the rendered login page to confirm which attributes Flux outputs before writing the
selectors; target only elements inside `body.site-auth`.
### Files
- Modify: `resources/views/components/layouts/auth/split.blade.php`,
`resources/views/components/layouts/auth/card.blade.php`,
`resources/views/components/layouts/auth/simple.blade.php`, `resources/css/public.css`.
- Do not modify any file under `resources/views/livewire/auth/` or `app/Actions/Fortify/`.
## Acceptance Criteria
- [ ] `/login` renders in IBM Plex Sans Arabic with the heading in Markazi Text, 48px inputs with a
gold focus ring, and a gold pill submit button; the forest logo panel and the cream pattern
panel are unchanged in layout.
- [ ] Logging in with valid credentials still works for an admin and a client (redirect to their
dashboard), and invalid credentials still show the existing error.
- [ ] `/forgot-password` shows the same styles and still sends the reset link for a known email.
- [ ] The auth pages' HTML contains `public.css` and `class="site-auth`, and does not contain
`public.js`.
- [ ] The admin and client dashboards contain neither `public.css` nor `site-auth`.
- [ ] `/login` has no horizontal scroll at 1440, 768 and 390px.
## Sub-tasks
None.
+35
View File
@@ -0,0 +1,35 @@
---
id: 24
title: 24 Public-Site Redesign (PRD 1.4)
status: Ready for Dev
date: 2026-10-31
parent: null
---
## Description
Container for the public-site redesign the client approved on 2026-10-31 (mockup v3,
`docs/ux-mockup.html`, commit `caec396`). The redesign is presentation-only: no route, model,
migration, service, mail or job changes. It restyles the public pages (`/`, `/about`, `/posts`,
`/posts/{post}`, `/booking`, `/booking/success`, `/page/{slug}`, `/terms`, `/privacy`) and the
auth pages' type and controls. The admin and client dashboards, emails, PDFs and
`/forms/nablus-workshop` must not change.
This parent carries no implementation of its own. Execute the subtasks in ID order.
## Acceptance Criteria
- [ ] Subtasks 24-1, 24-2, 24-3, 24-4, 24-5, 24-6, 24-7, 24-8, 24-9 and 24-10 are all Done.
## Sub-tasks
- 24-1 Public design foundation (stylesheet, script, fonts, layout)
- 24-2 Navigation and footer
- 24-3 Signature balance component
- 24-4 Home page sections (hero to latest articles)
- 24-5 Home booking card (compact booking and day strip)
- 24-6 Booking page and booking success
- 24-7 About page
- 24-8 Articles and legal pages
- 24-9 Auth pages type and controls
- 24-10 Redesign integration verification
+143
View File
@@ -0,0 +1,143 @@
# Task Index: Libra
**Generated:** 2026-10-31
| ID | Title | Status | Parent |
|----|-------|--------|--------|
| 1 | 1 Core Foundation & Infrastructure | Done | — |
| 1-1 | 1-1 Project Setup & Database Schema | Done | 1 |
| 1-2 | 1-2 Authentication & Role System | Done | 1 |
| 1-3 | 1-3 Bilingual Infrastructure (Arabic/English) | Done | 1 |
| 1-4 | 1-4 Base UI & Navigation | Done | 1 |
| 2 | 2 User Management System | Done | — |
| 2-1 | 2-1 Individual Client Account Management | Done | 2 |
| 2-2 | 2-2 Company/Corporate Client Account Management | Done | 2 |
| 2-3 | 2-3 Account Type Conversion | Done | 2 |
| 2-4 | 2-4 Account Lifecycle Management | Done | 2 |
| 2-5 | 2-5 Account Creation Email Notification | Done | 2 |
| 3 | 3 Booking & Consultation System | Done | — |
| 3-1 | 3-1 Working Hours Configuration | Done | 3 |
| 3-2 | 3-2 Time Slot Blocking | Done | 3 |
| 3-3 | 3-3 Availability Calendar Display | Done | 3 |
| 3-4 | 3-4 Booking Request Submission | Done | 3 |
| 3-5 | 3-5 Admin Booking Review & Approval | Done | 3 |
| 3-6 | 3-6 Calendar File Generation (.ics) | Done | 3 |
| 3-7 | 3-7 Consultation Management | Done | 3 |
| 3-8 | 3-8 Consultation Reminders | Done | 3 |
| 3-9 | 3-9 Guest Booking Request | Done | 3 |
| 4 | 4 Case Timeline System | Done | — |
| 4-1 | 4-1 Timeline Creation | Done | 4 |
| 4-2 | 4-2 Timeline Updates Management | Done | 4 |
| 4-3 | 4-3 Timeline Archiving | Done | 4 |
| 4-4 | 4-4 Admin Timeline Dashboard | Done | 4 |
| 4-5 | 4-5 Client Timeline View | Done | 4 |
| 4-6 | 4-6 Timeline Update Notifications | Done | 4 |
| 5 | 5 Posts/Blog System | Done | — |
| 5-1 | 5-1 Post Creation & Editing | Done | 5 |
| 5-2 | 5-2 Post Management Dashboard | Done | 5 |
| 5-3 | 5-3 Post Deletion | Done | 5 |
| 5-4 | 5-4 Public Posts Display | Done | 5 |
| 5-5 | 5-5 Post Search | Done | 5 |
| 6 | 6 Admin Dashboard | Done | — |
| 6-1 | 6-1 Dashboard Overview & Statistics | Done | 6 |
| 6-2 | 6-2 Analytics Charts | Done | 6 |
| 6-3 | 6-3 Quick Actions Panel | Done | 6 |
| 6-4 | 6-4 Data Export - User Lists | Done | 6 |
| 6-5 | 6-5 Data Export - Consultation Records | Done | 6 |
| 6-6 | 6-6 Data Export - Timeline Reports | Done | 6 |
| 6-7 | 6-7 Monthly Statistics Report | Done | 6 |
| 6-8 | 6-8 System Settings | Done | 6 |
| 6-9 | 6-9 Legal Pages Editor | Done | 6 |
| 6-10 | 6-10 Audit Log Viewer | Done | 6 |
| 7 | 7 Client Dashboard | Done | — |
| 7-1 | 7-1 Client Dashboard Overview | Done | 7 |
| 7-2 | 7-2 My Consultations View | Done | 7 |
| 7-3 | 7-3 My Cases/Timelines View (Dashboard Integration) | Done | 7 |
| 7-4 | 7-4 My Profile View | Done | 7 |
| 7-5 | 7-5 New Booking Interface | Done | 7 |
| 7-6 | 7-6 Booking Limit Indicator | Done | 7 |
| 8 | 8 Email Notification System | Done | — |
| 8-1 | 8-1 Email Infrastructure Setup | Done | 8 |
| 8-2 | 8-2 Welcome Email (Account Created) | Done | 8 |
| 8-3 | 8-3 Booking Submitted Confirmation | Done | 8 |
| 8-4 | 8-4 Booking Approved Email | Done | 8 |
| 8-5 | 8-5 Booking Rejected Email | Done | 8 |
| 8-6 | 8-6 Consultation Reminder (24 Hours) | Done | 8 |
| 8-7 | 8-7 Consultation Reminder (2 Hours) | Done | 8 |
| 8-8 | 8-8 Timeline Update Notification | Done | 8 |
| 8-9 | 8-9 Admin Notification - New Booking | Done | 8 |
| 8-10 | 8-10 Admin Notification - System Events | Done | 8 |
| 9 | 9 Design & Branding Implementation | Done | — |
| 9-1 | 9-1 Color System Implementation | Done | 9 |
| 9-2 | 9-2 Typography System | Done | 9 |
| 9-3 | 9-3 Logo Integration | Done | 9 |
| 9-4 | 9-4 Component Styling - Buttons | Done | 9 |
| 9-5 | 9-5 Component Styling - Forms | Done | 9 |
| 9-6 | 9-6 Component Styling - Cards & Containers | Done | 9 |
| 9-7 | 9-7 Navigation & Footer Styling | Done | 9 |
| 9-8 | 9-8 RTL/LTR Layout Perfection | Done | 9 |
| 9-9 | 9-9 Responsive Design Implementation | Done | 9 |
| 9-10 | 9-10 Accessibility Compliance | Done | 9 |
| 9-11 | 9-11 Animations & Micro-interactions | Done | 9 |
| 10 | 10 Brand Color Refresh | Done | — |
| 10-1 | 10-1 Core CSS Theme Update | Done | 10 |
| 10-2 | 10-2 Blade Component Color Migration | Done | 10 |
| 10-3 | 10-3 Email Template Color Update | Done | 10 |
| 10-4 | 10-4 PDF Template Color Update | Done | 10 |
| 10-5 | 10-5 Logo and SVG Assets Update | Done | 10 |
| 10-6 | 10-6 Documentation Update | Done | 10 |
| 11 | 11 Guest Booking | Done | — |
| 11-1 | 11-1 Database Schema & Model Updates | Done | 11 |
| 11-2 | 11-2 Public Booking Form with Custom Captcha | Done | 11 |
| 11-3 | 11-3 Guest Email Notifications & Admin Integration | Done | 11 |
| 11-4 | 11-4 Documentation Updates | Done | 11 |
| 12 | 12 Branding Refresh (Logo + Color Scheme) | Done | — |
| 12-1 | 12-1 Logo Asset Deployment and CSS Layout Updates | Done | 12 |
| 12-2 | 12-2 Core CSS Theme Update - Dark Charcoal & Warm Gold Palette | Done | 12 |
| 12-3 | 12-3 Blade Component Color Migration - Dark Charcoal & Warm Gold | Done | 12 |
| 12-4 | 12-4 Email Template Color Update - Dark Charcoal & Warm Gold | Done | 12 |
| 12-5 | 12-5 PDF Template Color Update - Dark Charcoal & Warm Gold | Done | 12 |
| 12-6 | 12-6 Brand Documentation Update - Dark Charcoal & Warm Gold | Done | 12 |
| 13 | 13 Authentication Pages Design Enhancement | Done | — |
| 13-1 | 13-1 Language Toggle Visibility Enhancement | Done | 13 |
| 13-2 | 13-2 Auth Layout Split Design Update | Done | 13 |
| 13-3 | 13-3 Subtle Background Pattern Implementation | Done | 13 |
| 13-4 | 13-4 Auth Pages Migration to Split Layout | Done | 13 |
| 13-5 | 13-5 Visual QA and Refinement | Done | 13 |
| 14 | 14 Home Page Redesign | Done | — |
| 14-1 | 14-1 Hero Section Implementation | Done | 14 |
| 14-2 | 14-2 About Section with Lawyer Profile | Done | 14 |
| 14-3 | 14-3 Services Section | Done | 14 |
| 14-4 | 14-4 Values Section | Done | 14 |
| 14-5 | 14-5 Latest Posts Section | Done | 14 |
| 14-6 | 14-6 Footer Contact Information Update | Done | 14 |
| 15 | 15 Potential Clients Management | Done | — |
| 15-1 | 15-1 Database Schema & Model Setup | Done | 15 |
| 15-2 | 15-2 Potential Clients List with Type Filter | Done | 15 |
| 15-3 | 15-3 CRUD Operations for Potential Clients | Done | 15 |
| 16 | 16 About Us Page | Done | — |
| 16-1 | 16-1 About Page Layout & Hero Section | Done | 16 |
| 16-2 | 16-2 Vision & Mission Section | Done | 16 |
| 16-3 | 16-3 Values Section | Done | 16 |
| 16-4 | 16-4 Goals Section | Done | 16 |
| 16-5 | 16-5 Legal Services Section | Done | 16 |
| 16-6 | 16-6 Scholarship Aspiration Section (REMOVED from production 2026-01-11 — do not re-implement) | Done | 16 |
| 17 | 17 Nablus Workshop Registration Form | Done | — |
| 17-1 | 17-1 Registration mailable and email template | Done | 17 |
| 17-2 | 17-2 Standalone registration page, form, and submit wiring | Done | 17 |
| 18 | 18 Nablus Workshop Form - Image Sizing Fix | Done | — |
| 19 | 19 Nablus Workshop Form - Remove Logo, Shrink Flyer Further | Done | — |
| 20 | 20 Nablus Workshop Form - Match Site Background and Card Styling | Done | — |
| 21 | 21 Nablus Workshop Form - Flyer Size Adjustment | Done | — |
| 22 | 22 Fix Footer Address - Add Missing Street Name | Done | — |
| 23 | 23 Fix AdminNotificationService Carbon TypeError Masking Exceptions | Done | — |
| 24 | 24 Public-Site Redesign (PRD 1.4) | Ready for Dev | — |
| 24-1 | 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout) | Ready for Dev | 24 |
| 24-2 | 24-2 Navigation and Footer | Ready for Dev | 24 |
| 24-3 | 24-3 Signature Balance Component | Ready for Dev | 24 |
| 24-4 | 24-4 Home Page Sections (Hero to Latest Articles) | Ready for Dev | 24 |
| 24-5 | 24-5 Home Booking Card (Compact Booking and Day Strip) | Ready for Dev | 24 |
| 24-6 | 24-6 Booking Page and Booking Success | Ready for Dev | 24 |
| 24-7 | 24-7 About Page | Ready for Dev | 24 |
| 24-8 | 24-8 Articles and Legal Pages | Ready for Dev | 24 |
| 24-9 | 24-9 Auth Pages Type and Controls | Ready for Dev | 24 |
| 24-10 | 24-10 Redesign Integration Verification | Ready for Dev | 24 |
+12
View File
@@ -5,6 +5,7 @@
**Last synced:** 2026-09-28 — 19 QA passed (logo removed, flyer max-h-40 confirmed, 7/7 tests), Done
**Last synced:** 2026-09-28 — 20 QA passed (auth-pattern/bg-background/card/text-body confirmed in compiled CSS and Blade, 7/7 tests), Done
**Last synced:** 2026-09-28 — 21 QA passed (flyer max-h-56 confirmed in Blade and compiled CSS, HTTP 200, 7/7 tests), Done
**Last synced:** 2026-10-31 — added tasks 24, 24-1…24-10 (public-site redesign, PRD 1.4); readiness review same day: 6 task-level fixes, all 11 promoted to Ready for Dev
| ID | Title | Status |
|----|-------|--------|
@@ -135,3 +136,14 @@
| 21 | 21 Nablus Workshop Form - Flyer Size Adjustment | Done |
| 22 | 22 Fix Footer Address - Add Missing Street Name | Done |
| 23 | 23 Fix AdminNotificationService Carbon TypeError Masking Exceptions | Done |
| 24 | 24 Public-Site Redesign (PRD 1.4) | Ready for Dev |
| 24-1 | 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout) | Ready for Dev |
| 24-2 | 24-2 Navigation and Footer | Ready for Dev |
| 24-3 | 24-3 Signature Balance Component | Ready for Dev |
| 24-4 | 24-4 Home Page Sections (Hero to Latest Articles) | Ready for Dev |
| 24-5 | 24-5 Home Booking Card (Compact Booking and Day Strip) | Ready for Dev |
| 24-6 | 24-6 Booking Page and Booking Success | Ready for Dev |
| 24-7 | 24-7 About Page | 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-10 | 24-10 Redesign Integration Verification | Ready for Dev |
+320 -1
View File
@@ -1,4 +1,323 @@
# UX / Design Reference — Libra Law Firm
# UX Design Spec: Libra for Rights — Public-Site Redesign
**Version:** 2.0
**Date:** 2026-10-31
**Status:** Approved (client approved mockup v3 on 2026-10-31)
**Visual mockup:** `docs/ux-mockup.html` (open in a browser to view; v3, commit `caec396`)
This file has two parts:
- **Part A — Public site (redesign, approved 2026-10-31).** Governs every page rendered through
`resources/views/components/layouts/public.blade.php` and the auth layouts' type and controls.
- **Part B — Dashboards, emails and the Nablus form (unchanged).** The retrospective record of the
design system as built before the redesign. It still governs the admin and client dashboards,
the emails and `/forms/nablus-workshop`, none of which the redesign touches.
Where Part A and Part B disagree about a public page, Part A wins.
---
# Part A — Public site
## Design Direction
Arabic-first, editorial and quiet: the firm reads like a well-bound legal document, not a tech
product. The brand palette (forest, gold, cream, olive) is kept and used with more confidence —
large forest bands, gold as thread and rule, cream as linen. Headlines are set in an editorial
naskh serif (Markazi Text); body in IBM Plex Sans Arabic. Sections are numbered like articles of
a statute ("المادة ٠١ — المؤسِّسة"). The signature is an engraved-gold balance over olive-tree
photography that settles into level on load and is weighed by the visitor's pointer. Tatreez
appears only inside real photography — Naser ruled out drawn cross-stitch ("drawing using xxx and
other symbols is more of a tech stuff not lawyers", 2026-10-31). Every claim on the page is backed
by the code (code-fit check, 2026-10-31): no availability promises, no response-time promises, no
article images.
**Layout default:** desktop-first; every layout works at 768px and 390px with no horizontal scroll.
**Scope:** public pages only — home `/`, about `/about`, posts `/posts` and `/posts/{post}`,
booking `/booking` and `/booking/success`, legal `/page/{slug}` (terms, privacy), and the auth
pages (type and controls only, see *Auth pages*). Out of scope: admin and client dashboards,
emails, PDFs, `/forms/nablus-workshop`.
## Layout & Navigation
- **Containment.** The public layout no longer wraps pages in one padded 1200px container; each
page section is full-bleed and places its own content in a `.wrap` container:
`max-width: 1200px; margin-inline: auto; padding-inline: 28px` (18px at ≤760px).
- **Section rhythm.** Standard section padding 120px top and bottom (80px at ≤760px). Section
header (`.sec-head`): flex row, items aligned to the end, space-between, gap 32px, wrap,
margin-bottom 54px; left side holds the article label and h2, right side a muted lede
(max-width 30em).
- **Article label** (`.art-no`): display font, 1.15rem, weight 600, `--gold-ink`. Format
`المادة ٠N — <name>` / `Article 0N — <Name>` (Arabic-Indic digits in Arabic).
- **Eyebrow** (`.eyebrow`): 0.82rem, weight 600, `--gold-ink`, preceded by a 22×2px rule in
currentColor with 10px gap; in English 0.74rem, uppercase, letter-spacing .16em. Arabic text
never gets letter-spacing (it breaks joining).
- **Navigation** (sticky, top 0, z 50, height 72px): background `rgba(244,241,234,.82)` with
`backdrop-filter: blur(14px) saturate(1.2)`; bottom border transparent until the page has
scrolled more than 8px, then `1px solid var(--line)` plus shadow `0 8px 24px -18px
rgba(30,34,25,.5)`. Order (start → end): brand (seal + wordmark), links, then tools (language
toggle, client login, gold "Book a consultation" button). Links: Home, About, Services (→
`/#services`), Articles (→ `/posts`), Booking (→ `/booking`). Link style: 10px 14px padding,
weight 500, .95rem; hover colour `--olive`; a 2px dashed gold underline (4px dash, 3px gap)
scales in from the inline-start edge on hover and stays on the current page's link
(`aria-current="page"`). Logged-in visitors see "Dashboard" (to their role's dashboard) and
"Log out" in place of "Client login", as today.
- **Brand mark:** a 46×46px "seal" — `public/images/logo.png` as a cover background on white,
2px solid `#1A1A1A` border, shadow `3px 3px 5px rgba(26,26,26,.35)` — followed by the wordmark:
«ليبرا» (display font, 1.35rem) over «للحقوق» (.72rem, muted). English: "Libra" over
"FOR RIGHTS" (letter-spacing .12em).
- **Mobile (≤1100px):** links hide; a 44×44px menu button (1.5px `--line` border, radius 10px,
white) opens the existing Alpine mobile menu, restyled as a cream sheet below the nav with
full-width 48px rows. At ≤760px the nav's gold button and the client-login link also hide (both
remain inside the sheet).
- **Reading progress rule:** a fixed 2px bar at the very top of the viewport (z 60), gradient
`#8A7555 → #E2CDA4 → #A68966`, `transform: scaleX(p)` where p = scrollY / (scrollHeight −
innerHeight); origin is the inline-start edge (right in RTL, left in LTR). Decorative
(`aria-hidden`).
- **Footer:** background `--forest-2` (#232A1C), text `--gold-lt`, margin-top 120px, padding 72px 0
28px, .92rem. A 1px top hairline: horizontal gradient transparent → `rgba(196,168,130,.7)` →
transparent. Four columns (1.4fr 1fr 1fr 1fr, gap 40px; 2 columns ≤1100px; 1 column ≤760px):
(1) brand mark with cream wordmark and the tagline «ملتزمون بالعدالة – متجذرون بالكرامة – مدفوعون
للدفاع»; (2) "Explore": About, Services, Articles, Booking; (3) "Clients": Client login, Terms of
use, Privacy policy; (4) "Contact": phone (LTR), email, address. Column headings: display font,
1.15rem, cream. Links: 10px vertical padding, hover → cream and a 3px nudge toward the
inline-start. Bottom row: copyright, top border `rgba(244,241,234,.1)`, .8rem. No photography
credit line.
## Visual Style
### Color Palette
| Token | Hex | Use |
|---|---|---|
| `--forest` | `#2D3624` | hero frame ground, pillar band, booking card, hover fill of service rows |
| `--forest-2` | `#232A1C` | footer only (depth) |
| `--gold` | `#A68966` | gold buttons, rules, row numbers, focus ring |
| `--gold-ink` | `#8A7555` | gold text on cream (labels, dates) |
| `--gold-lt` | `#C4A882` | small gold text on forest |
| `--cream` | `#F4F1EA` | page ground |
| `--olive` | `#4A5D23` | link hover, emphasis word in the hero, active accents |
| `--ink` | `#1E2219` | body and heading text |
| `--muted` | `#5B6152` | secondary text |
| `--line` | `rgba(45,54,36,.14)` | hairlines and borders |
| fabric ground | `#ECE7DC` | background of the Vision section |
The palette is the brand's own (`docs/brand.md`); `--ink`, `--muted`, `--forest-2` and the fabric
ground are darker/lighter steps of it, added for contrast and depth. Status colours (success,
danger, warning) are unchanged from Part B.
**Page ground:** `--cream` with a linen weave —
`repeating-linear-gradient(0deg, rgba(45,54,36,.025) 0 1px, transparent 1px 3px),
repeating-linear-gradient(90deg, rgba(45,54,36,.022) 0 1px, transparent 1px 4px)`.
**Shadows:** `--shadow-1: 0 1px 2px rgba(30,34,25,.06), 0 4px 14px rgba(30,34,25,.06)`;
`--shadow-2: 0 2px 4px rgba(30,34,25,.08), 0 18px 40px -12px rgba(30,34,25,.28)`.
**Easing:** `--ease: cubic-bezier(.2,.7,.2,1)`. **Radius:** cards 16px, buttons and chips 999px,
inputs 12px.
### Typography
- **Display** (h1–h3, article labels, blockquotes, numbers): **Markazi Text**, weights 400–700,
fallback `"Times New Roman", serif`. Headings weight 700, line-height 1.25.
- **Body and UI:** **IBM Plex Sans Arabic**, weights 300–600, fallback `system-ui, sans-serif`.
17px, line-height 1.75 (16px at ≤760px). Both families carry Latin glyphs, so English uses the
same two families.
- Loaded from Google Fonts with `display=swap`, on the public and auth layouts only. The dashboards
keep Cairo/Montserrat.
- Sizes: hero h1 `clamp(2.6rem, 4.6vw, 4.1rem)`, line-height 1.12, word-spacing .08em (English:
letter-spacing −.02em); section h2 `clamp(2rem, 3.4vw, 3rem)`; row h3 1.65rem; card h3 1.4rem;
post h3 1.35rem; lede 1.15rem.
### Tone & Style
Editorial, warm, unhurried. Real photography only (olive tree, embroidery on a hoop, olives,
hills, the founder's portrait). No drawn cross-stitch, no gradient blobs, no carousels. Depth comes
from layered shadows, gold hairlines and the engraved balance.
### Buttons
- **Gold** (`.btn-gold`): pill (radius 999px), min-height 48px, padding 12px 26px, weight 600,
background `--gold`, text `--ink`, shadow `0 1px 0 rgba(255,255,255,.35) inset, 0 8px 22px -8px
rgba(166,137,102,.8)`. Hover: lift 2px, deeper glow plus a 6px `rgba(166,137,102,.14)` halo, and
a white shimmer (105° gradient, 30–70%) sweeps across in .7s.
- **Ghost** (`.btn-ghost`): transparent, 1.5px inset `--forest` ring, text `--ink`. Hover: fills
`--forest`, text cream, lift 2px. On dark grounds: cream text and a `rgba(244,241,234,.6)` ring;
hover fills cream with forest text.
- An arrow icon inside a button nudges 4px toward the reading direction on hover; it is mirrored
in LTR.
- Focus (all controls): `outline: 2px solid var(--gold); outline-offset: 3px`.
## Key Screens / Flows
### Home `/`
Top to bottom (content is real; copy keys are listed in the tasks):
1. **Hero** — two columns (1fr : 1.08fr, gap 56px; one column ≤1100px). Start column: eyebrow
«ليبرا للحقوق · فلسطين»; h1 «العدالة *ممارسةٌ يومية*، لا مبدأٌ نظري.» with the emphasised phrase
in `--olive` and a dashed gold underline; lede (the existing `home.intro`); gold "Book a
consultation" (→ `#book`) and ghost "Our services" (→ `#services`); a trust strip of three
items separated from the CTAs by a hairline: «أفراد وشركات / استشارات وتمثيل», «المرأة والأسرة /
دعم قانوني متخصص», «عربي · English / خدمة بلغتين». End column: the **balance frame** (see
Signature Moment): an arched frame (aspect 4:4.6, radius 999px 999px 18px 18px) holding the
olive-tree photo under a forest gradient veil, a soft gold glow, the gold balance, and the
italic caption «لكلّ حقٍّ وزنُه». A cream tag with a pulsing olive dot overlaps the frame edge:
«استشارات حضورية أو عن بُعد».
2. **Pillars band** — full-bleed forest band, three columns divided by cream hairlines: Justice,
Dignity, Advocacy, each with a gold gem (12px rotated square) and a one-line gloss in
`--gold-lt`. Hover lightens the column background.
3. **Founder** (Article 01) — portrait (aspect 4:5, radius 18px, greyscale, an inset dashed gold
frame, a cream name card «هدى عرموش / المؤسِّسة · محامية»), h2 «تعرّف على المؤسِّسة», a pull
quote «العدالة ممارسة يومية، وليست مجرد مبدأ نظري.» (display font, gold inline-start rule), the
two bio paragraphs, and a ghost "Read our story" → `/about`. Hover on the portrait restores some
colour and scales it 3%.
4. **Services docket** (Article 02) — four rows, each a link to `#book`: big gold number, title,
chips of the service's items, a round arrow button. Hover: the row fills forest, text turns
cream, chips go gold-light, the arrow fills gold, a dashed gold inner border appears, lift 2px.
5. **Vision** (Article 03, fabric ground `#ECE7DC`) — text column (h2 «منسوجةٌ من نسيج المجتمع»
and a display-font paragraph) beside the embroidery-hoop photo (circular, two gold ring
shadows). Below it, three cards: "For our institution" (forest panel with a static balance),
"For society" (olives photo), "For Palestinians in the diaspora" (hills photo); each with an
italic label on the image, a title and one line.
6. **Values** (Article 04) — six cards in a row (3 ≤1100px, 2 ≤760px): a gold-ringed medallion
with a fine-line icon, and the value name. Hover: card lifts and tilts −0.6°, the medallion
rotates −8°, gains a gold glow, the icon turns olive.
7. **Latest articles** (Article 05) — the three most recent published posts as text-only cards
(date, title, excerpt, "Read more"), gold top border turning olive on hover, a large faint
quotation mark in the corner; "View all articles" ghost button → `/posts`. No post → the
existing "Articles coming soon" line in place of the grid.
8. **Booking card** (Article 06, `id="book"`) — one rounded (28px) forest card in two columns:
start panel with h2 "Book a consultation", the lede «اختر يوماً ووقتاً متاحَين، وأخبرنا باختصار
عن قضيتك. نراجع كل طلب ونؤكّد الموعد بالبريد الإلكتروني — دون الحاجة إلى حساب.», phone, email,
address, and the finale balance (190px); end panel is a cream inset form (see *Booking flow*).
### Booking flow (home card and `/booking`)
Both surfaces run the existing guest-booking component and its rules unchanged: name, email,
phone (all required), a case summary (required, minimum 20 characters), the math captcha, one
booking per email per day, slots from `AvailabilityService`, the existing confirm step, and the
redirect to `/booking/success`. Logged-in visitors see the existing "you are logged in" message,
restyled, in place of the form.
- **Home card (compact):** fields in this order — name and phone side by side, email, a **day
strip**, a **slot row**, the case summary, the captcha, the gold submit "Request this
appointment" (full width), and the note «ملاحظة: يمكنك حجز استشارة واحدة كحدّ أقصى في اليوم.».
The day strip shows the next 14 calendar days that have at least one available slot, as
66×72px buttons (weekday name over the day number), horizontally scrollable; the first is
pre-selected. Selected day: forest fill, cream number. The slot row shows that day's available
slots as pill buttons (min-height 44px); selected slot: gold fill. Both use `aria-pressed`.
Submit opens the existing confirm step inside the same card (summary of date, time and contact,
with Confirm and Back).
- **`/booking` page:** the same two-panel forest card, full page; the end panel keeps the month
calendar (the existing component, restyled: cream ground, 44px day cells, selected day forest,
available days with a gold dot, unavailable days muted), then the fields and confirm step.
- **Inputs:** min-height 48px, padding 12px 16px, radius 12px, 1.5px border `rgba(45,54,36,.18)`,
white; focus: border `--gold` plus a 4px `rgba(166,137,102,.2)` ring. Labels .85rem, weight 600.
Errors: the existing message under the field, in the danger colour.
- **`/booking/success`:** a centred cream card on the linen ground with a small level balance, the
existing success copy and a ghost button home.
### About `/about`
The same system, existing content only (no new copy beyond section labels): hero (eyebrow, h1
from `about.title`, the tagline, the identity statement), Vision and Mission (the fabric section
pattern; the three mission cards with the olives, hills and static-balance panels), Values (the
medallion row), Goals (a numbered docket list: gold display numbers, no chips, no links), Legal
services (the docket rows, linking to `/booking`), closing gold CTA to `/booking`.
### Articles `/posts` and `/posts/{post}`
- **Index:** page head (article label, h2 "Articles"), the existing search field restyled as a
pill input, the text-only post cards in a three-column grid (2 ≤1100px, 1 ≤760px), the existing
pagination restyled (pill buttons, current page forest).
- **Show:** a reading column max-width 720px; date in `--gold-ink`; title in the display font
`clamp(2rem, 3.4vw, 3rem)`; body in the existing prose styles with headings in the display font
and links in `--olive`; a "Back to articles" ghost button.
### Legal pages `/page/{slug}`
Reading column max-width 720px, display-font title, existing prose body.
### Auth pages (login, forgot/reset password, confirm password, verify email)
Type and controls only: the auth layouts load the two new families and apply the input, focus and
gold-button styles above. The split composition (forest panel with the logo, cream form panel) and
the existing auth pattern stay as they are.
## Signature Moment
**Element:** an engraved-gold balance (scales of justice) drawn as layered SVG — gradient-filled
beam, pillar, olive-leaf finial, base and two pans with a drop shadow — standing over the
olive-tree photograph in the hero frame. A second instance (the "finale", 190px) sits in the
booking card; a static instance fills the "For our institution" card.
**Built from:** the firm's own name — Libra, the balance — and the olive tree of the region; the
photograph is real.
**Trigger:** page load; pointer movement over the hero frame (fine pointers only); scroll position
for the finale.
**Mapping:**
- Load: the hero beam starts tilted 14° and, after 450ms, a damped spring settles it to 0°
(per frame: `v += (target − a) × 0.028; v ×= 0.935; a += v`; stop when |v| < 0.002 and
|target − a| < 0.01).
- Pointer: x = (clientX − frame.left) / frame.width; target = clamp((x − 0.5) × 2.4, −1, 1) × 9°.
The gold glow follows the pointer; the photo pans opposite the pointer by
`(pointer − centre) / −40` px at scale 1.08. Pointer leave → target 0°, photo resets.
- Pans hang from the beam ends: for beam angle θ, pan centres are at (200 ∓ 142·cos θ,
110 ∓ 142·sin θ) in the 400×400 viewBox, so they stay vertical while the beam tips.
- Finale: with p the page progress, q = clamp((p − 0.7) / 0.25, 0, 1); target = −12° × (1 − q), so
the balance comes level as the reader reaches the booking card.
**Tech suggestion:** an inline SVG Blade component and a small vanilla JS module
(`requestAnimationFrame`); no library.
**Reduced-motion fallback:** all balances render level (0°), no spring, no pointer response, no
glow movement; the photo stays still.
## Interaction Patterns (secondary)
- **Entrance choreography:** elements marked `data-reveal` start at opacity 0 and translateY 28px
and transition (.9s, `--ease`) to rest when 8% inside the viewport (IntersectionObserver,
`rootMargin: 0px 0px -8% 0px`, observe once). Delays by `data-d` 1–5: .08s steps. Content stays
visible if JavaScript fails (the hidden state applies only once the script adds a `js` class).
- **Hover depth:** cards lift 6px with `--shadow-2` and a gold border `rgba(166,137,102,.55)`;
card images scale 1.05–1.06 over .8s.
- **Language toggle:** the existing `/language/{locale}` route; a 48×44px pill showing "EN" in
Arabic and "ع" in English.
- **Reduced motion:** `@media (prefers-reduced-motion: reduce)` disables every animation and
transition on the public pages and shows all `data-reveal` content at rest.
- **Touch targets:** every interactive element at least 44×44px.
- **RTL/LTR:** logical properties throughout; arrows and the progress rule's origin mirror.
## Design Decisions & Rationale
- **Markazi Text + IBM Plex Sans Arabic** — Amiri's calligraphic ligatures broke in large
headlines; Markazi is an editorial naskh with a matching Latin serif (brand.md asks for a serif
headline); Plex Arabic is highly legible at body size. Two families total.
- **Balance over olive photography, no drawn tatreez** — Naser rejected the cross-stitch drawings
as "tech stuff"; the balance is the firm's name and reads as law at a glance.
- **Article numbering** — frames the firm's content in the language of a statute; costs nothing.
- **Text-only article cards** — posts have no image field (code-fit check); no images are promised.
- **Copy limited to what the code backs** — no availability or response-time claims; booking copy
is drawn from the existing lang strings.
- **Public-only scope** — the dashboards share global tokens and `.btn` classes; the redesign is
scoped to the public layout so no dashboard screen changes.
- **Compact booking on the home page** — the client approved a one-screen form; it reuses the same
component and rules, so the backend does not change.
## Open Questions
None blocking. Rulings taken for the build (ledger, 2026-10-31):
1. **Photography** — the mockup's four Unsplash photos (olive tree, embroidery hoop, olives, hills)
ship as the production images; the Unsplash licence allows commercial use without attribution.
The client may replace them later with their own.
2. **Home day strip** — the next 14 calendar days with an available slot; `/booking` keeps the
month calendar.
3. **Pages the mockup does not show** (about, articles, booking page, success, legal) — composed
from the homepage's components with their existing content; no new sections.
4. **Auth pages** — type and controls only; composition unchanged.
---
# Part B — Dashboards, emails and the Nablus form (pre-redesign record)
Retrospective documentation of the design system as it is currently **built and live**. This
is not a proposal — it describes what shipped, so a dev/QA agent picking up a new task can match