Files
libra/docs/tasks/24-1.md
T
2026-10-31 18:41:12 +02:00

148 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: 24-1
title: 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout)
status: QA Review
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.