--- 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 ``` 6. **`lang/ar/site.php`** and **`lang/en/site.php`** — each `` becomes `` (remove `bg-background` and the inline `style="font-family: …"`). - Immediately after the skip link add ``. - Remove the spacer `
` (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). - `
` keeps `id="main-content" role="main" class="flex-1" tabindex="-1"` but its child `
` is removed, so `{{ $slot }}` is rendered directly inside `
`. 9. Because the wrapper is gone, wrap the root content of each existing public page that is not yet redesigned in `
…
` 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 `
`) - `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 `` to ``. 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 `