24-1: public design foundation (stylesheet, script, fonts, layout) — implemented, QA Review
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: 24-1
|
id: 24-1
|
||||||
title: 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout)
|
title: 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout)
|
||||||
status: Ready for Dev
|
status: QA Review
|
||||||
date: 2026-10-31
|
date: 2026-10-31
|
||||||
parent: 24
|
parent: 24
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
| 22 | 22 Fix Footer Address - Add Missing Street Name | Done |
|
| 22 | 22 Fix Footer Address - Add Missing Street Name | Done |
|
||||||
| 23 | 23 Fix AdminNotificationService Carbon TypeError Masking Exceptions | Done |
|
| 23 | 23 Fix AdminNotificationService Carbon TypeError Masking Exceptions | Done |
|
||||||
| 24 | 24 Public-Site Redesign (PRD 1.4) | Ready for Dev |
|
| 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-1 | 24-1 Public Design Foundation (Stylesheet, Script, Fonts, Layout) | QA Review |
|
||||||
| 24-2 | 24-2 Navigation and Footer | Ready for Dev |
|
| 24-2 | 24-2 Navigation and Footer | Ready for Dev |
|
||||||
| 24-3 | 24-3 Signature Balance Component | 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-4 | 24-4 Home Page Sections (Hero to Latest Articles) | Ready for Dev |
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'progress_label' => 'تقدّم القراءة',
|
||||||
|
];
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'progress_label' => 'Reading progress',
|
||||||
|
];
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/* Public-site redesign foundation (PRD 1.4, task 24-1).
|
||||||
|
Every selector is scoped under body.site so the dashboards are unaffected.
|
||||||
|
Reference: docs/ux-mockup.html */
|
||||||
|
|
||||||
|
/* ---------- Tokens ---------- */
|
||||||
|
body.site{
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
html:has(body.site){scroll-behavior:smooth}
|
||||||
|
|
||||||
|
/* ---------- Ground and type ---------- */
|
||||||
|
body.site{
|
||||||
|
font-family:var(--body);color:var(--ink);font-size:17px;line-height:1.75;
|
||||||
|
background-color:var(--cream);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
body.site img{max-width:100%;display:block}
|
||||||
|
body.site a{color:inherit}
|
||||||
|
body.site .wrap{max-width:1200px;margin-inline:auto;padding-inline:28px}
|
||||||
|
body.site .skip{position:absolute;inset-inline-start:12px;top:-60px;background:var(--forest);color:var(--cream);padding:10px 16px;border-radius:8px;z-index:100}
|
||||||
|
body.site .skip:focus{top:12px}
|
||||||
|
body.site :focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:4px}
|
||||||
|
body.site h1,body.site h2,body.site h3{font-family:var(--display);font-weight:700;line-height:1.25;margin:0;color:var(--ink)}
|
||||||
|
body.site .eyebrow{display:inline-flex;align-items:center;gap:10px;font-size:.82rem;font-weight:600;letter-spacing:0;color:var(--gold-ink)}
|
||||||
|
html[lang=en] body.site .eyebrow{letter-spacing:.16em;text-transform:uppercase;font-size:.74rem}
|
||||||
|
body.site .eyebrow::before{content:"";width:22px;height:2px;background:currentColor;border-radius:2px}
|
||||||
|
body.site .art-no{font-family:var(--display);font-size:1.15rem;font-weight:600;color:var(--gold-ink)}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Buttons ---------- */
|
||||||
|
body.site .btn{position:relative;display:inline-flex;align-items:center;gap:10px;min-height:48px;padding:12px 26px;border-radius:999px;
|
||||||
|
font:600 1rem/1 var(--body);text-decoration:none;cursor:pointer;border:0;overflow:hidden;isolation:isolate;
|
||||||
|
transition:transform .25s var(--ease), box-shadow .25s var(--ease), background-color .25s}
|
||||||
|
body.site .btn-gold{background:var(--gold);color:var(--ink);box-shadow:0 1px 0 rgba(255,255,255,.35) inset, 0 8px 22px -8px rgba(166,137,102,.8)}
|
||||||
|
body.site .btn-gold::after{content:"";position:absolute;inset:0;z-index:-1;
|
||||||
|
background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.55) 50%,transparent 70%);transform:translateX(-120%);transition:transform .7s var(--ease)}
|
||||||
|
body.site .btn-gold:hover{transform:translateY(-2px);box-shadow:0 1px 0 rgba(255,255,255,.35) inset, 0 16px 34px -10px rgba(166,137,102,.95), 0 0 0 6px rgba(166,137,102,.14)}
|
||||||
|
body.site .btn-gold:hover::after{transform:translateX(120%)}
|
||||||
|
body.site .btn-ghost{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1.5px var(--forest)}
|
||||||
|
body.site .btn-ghost:hover{background:var(--forest);color:var(--cream);transform:translateY(-2px);box-shadow:inset 0 0 0 1.5px var(--forest), 0 12px 26px -12px rgba(45,54,36,.7)}
|
||||||
|
body.site .on-dark .btn-ghost{color:var(--cream);box-shadow:inset 0 0 0 1.5px rgba(244,241,234,.6)}
|
||||||
|
body.site .on-dark .btn-ghost:hover{background:var(--cream);color:var(--forest)}
|
||||||
|
body.site .btn .arr{transition:transform .25s var(--ease)}
|
||||||
|
body.site .btn:hover .arr{transform:translateX(-4px)}
|
||||||
|
html[dir=ltr] body.site .btn .arr{transform:scaleX(-1)}
|
||||||
|
html[dir=ltr] body.site .btn:hover .arr{transform:scaleX(-1) translateX(-4px)}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Scroll progress ---------- */
|
||||||
|
body.site .site-progress{position:fixed;z-index:60;top:0;inset-inline:0;height:2px;pointer-events:none;transform:scaleX(0);transform-origin:right;
|
||||||
|
background:linear-gradient(90deg,#8A7555,#E2CDA4,#A68966)}
|
||||||
|
html[dir=ltr] body.site .site-progress{transform-origin:left}
|
||||||
|
|
||||||
|
/* ---------- Sections ---------- */
|
||||||
|
body.site section{position:relative}
|
||||||
|
body.site .sec{padding:120px 0}
|
||||||
|
body.site .sec-head{display:flex;align-items:end;justify-content:space-between;gap:32px;margin-bottom:54px;flex-wrap:wrap}
|
||||||
|
body.site .sec-head h2{font-size:clamp(2rem,3.4vw,3rem);margin-top:12px}
|
||||||
|
body.site .sec-head p{max-width:30em;color:var(--muted);margin:0}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Reveal (hidden only when JavaScript runs) ---------- */
|
||||||
|
body.site.js [data-reveal]{opacity:0;transform:translateY(28px);transition:opacity .9s var(--ease),transform .9s var(--ease)}
|
||||||
|
body.site.js [data-reveal].in{opacity:1;transform:none}
|
||||||
|
body.site.js [data-reveal][data-d="1"]{transition-delay:.08s}
|
||||||
|
body.site.js [data-reveal][data-d="2"]{transition-delay:.16s}
|
||||||
|
body.site.js [data-reveal][data-d="3"]{transition-delay:.24s}
|
||||||
|
body.site.js [data-reveal][data-d="4"]{transition-delay:.32s}
|
||||||
|
body.site.js [data-reveal][data-d="5"]{transition-delay:.4s}
|
||||||
|
|
||||||
|
/* ---------- Responsive base ---------- */
|
||||||
|
@media (max-width:760px){
|
||||||
|
body.site{font-size:16px}
|
||||||
|
body.site .wrap{padding-inline:18px}
|
||||||
|
body.site .sec{padding:80px 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Reduced motion ---------- */
|
||||||
|
@media (prefers-reduced-motion:reduce){
|
||||||
|
body.site *, body.site *::before, body.site *::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
|
||||||
|
body.site [data-reveal]{opacity:1;transform:none}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as reveal from './site/reveal.js';
|
||||||
|
import * as progress from './site/progress.js';
|
||||||
|
|
||||||
|
const modules = [reveal, progress];
|
||||||
|
|
||||||
|
document.addEventListener('livewire:navigated', () => {
|
||||||
|
document.body.classList.add('js');
|
||||||
|
modules.forEach((m) => m.init());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('livewire:navigating', () => {
|
||||||
|
modules.forEach((m) => m.destroy());
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
let onScroll = null;
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
const bar = document.querySelector('.site-progress');
|
||||||
|
const nav = document.getElementById('site-nav');
|
||||||
|
|
||||||
|
onScroll = () => {
|
||||||
|
const max = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
const p = max > 0 ? window.scrollY / max : 1;
|
||||||
|
|
||||||
|
if (bar) bar.style.transform = `scaleX(${p})`;
|
||||||
|
nav?.classList.toggle('scrolled', window.scrollY > 8);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
onScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroy() {
|
||||||
|
if (onScroll) window.removeEventListener('scroll', onScroll);
|
||||||
|
onScroll = null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
let observer = null;
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
const items = document.querySelectorAll('[data-reveal]');
|
||||||
|
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||||
|
items.forEach((el) => el.classList.add('in'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('in');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { rootMargin: '0px 0px -8% 0px' });
|
||||||
|
|
||||||
|
items.forEach((el) => observer.observe(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroy() {
|
||||||
|
observer?.disconnect();
|
||||||
|
observer = null;
|
||||||
|
}
|
||||||
@@ -2,22 +2,20 @@
|
|||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
||||||
<head>
|
<head>
|
||||||
@include('partials.head')
|
@include('partials.head')
|
||||||
|
@include('partials.site-fonts')
|
||||||
|
@vite(['resources/css/public.css', 'resources/js/public.js'])
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen flex flex-col bg-background" style="font-family: var(--font-{{ app()->getLocale() === 'ar' ? 'arabic' : 'english' }})">
|
<body class="site min-h-screen flex flex-col">
|
||||||
<!-- Skip to content link for keyboard accessibility -->
|
<!-- Skip to content link for keyboard accessibility -->
|
||||||
<a href="#main-content" class="skip-link" data-test="skip-to-content">
|
<a href="#main-content" class="skip-link" data-test="skip-to-content">
|
||||||
{{ __('accessibility.skip_to_content') }}
|
{{ __('accessibility.skip_to_content') }}
|
||||||
</a>
|
</a>
|
||||||
|
<div class="site-progress" aria-hidden="true"></div>
|
||||||
|
|
||||||
<x-navigation />
|
<x-navigation />
|
||||||
|
|
||||||
<!-- Spacer for fixed navigation -->
|
|
||||||
<div class="h-16"></div>
|
|
||||||
|
|
||||||
<main id="main-content" role="main" class="flex-1" tabindex="-1">
|
<main id="main-content" role="main" class="flex-1" tabindex="-1">
|
||||||
<div class="max-w-[1200px] mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<x-footer />
|
<x-footer />
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<div class="wrap py-8">
|
||||||
{{-- Hero Section --}}
|
{{-- Hero Section --}}
|
||||||
<section class="bg-background py-10 sm:py-[60px] lg:py-20 -mx-4 sm:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8">
|
<section class="bg-background py-10 sm:py-[60px] lg:py-20">
|
||||||
<div class="container mx-auto px-4 text-center">
|
<div class="container mx-auto px-4 text-center">
|
||||||
{{-- Page Title --}}
|
{{-- Page Title --}}
|
||||||
<h1 class="text-[2rem] sm:text-[2.5rem] lg:text-[3rem] font-bold text-text mb-2">
|
<h1 class="text-[2rem] sm:text-[2.5rem] lg:text-[3rem] font-bold text-text mb-2">
|
||||||
@@ -292,3 +293,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
//
|
//
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
|
<div class="wrap py-8">
|
||||||
<div class="max-w-2xl mx-auto py-16 px-4 text-center">
|
<div class="max-w-2xl mx-auto py-16 px-4 text-center">
|
||||||
<flux:icon name="check-circle" class="w-16 h-16 mx-auto text-green-500 mb-6" />
|
<flux:icon name="check-circle" class="w-16 h-16 mx-auto text-green-500 mb-6" />
|
||||||
|
|
||||||
@@ -23,3 +24,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
{{ __('navigation.home') }}
|
{{ __('navigation.home') }}
|
||||||
</flux:button>
|
</flux:button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
}
|
}
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
|
<div class="wrap py-8">
|
||||||
<div class="max-w-4xl mx-auto py-8 px-4">
|
<div class="max-w-4xl mx-auto py-8 px-4">
|
||||||
<flux:heading size="xl" class="mb-6 text-body">
|
<flux:heading size="xl" class="mb-6 text-body">
|
||||||
{{ __('booking.request_consultation') }}
|
{{ __('booking.request_consultation') }}
|
||||||
@@ -367,3 +368,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<div class="wrap py-8">
|
||||||
{{-- Hero Section --}}
|
{{-- Hero Section --}}
|
||||||
<section class="bg-background py-8 sm:py-12 lg:py-16">
|
<section class="bg-background py-8 sm:py-12 lg:py-16">
|
||||||
<div class="container mx-auto px-4 text-center">
|
<div class="container mx-auto px-4 text-center">
|
||||||
@@ -235,3 +236,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
</section>
|
</section>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
}
|
}
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
|
<div class="wrap py-8">
|
||||||
<div class="max-w-4xl mx-auto px-0">
|
<div class="max-w-4xl mx-auto px-0">
|
||||||
<flux:heading size="xl" class="text-body text-xl sm:text-2xl lg:text-3xl">{{ __('posts.posts') }}</flux:heading>
|
<flux:heading size="xl" class="text-body text-xl sm:text-2xl lg:text-3xl">{{ __('posts.posts') }}</flux:heading>
|
||||||
|
|
||||||
@@ -141,3 +142,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
{{ $posts->links() }}
|
{{ $posts->links() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
}
|
}
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
|
<div class="wrap py-8">
|
||||||
<article class="max-w-3xl mx-auto">
|
<article class="max-w-3xl mx-auto">
|
||||||
<header class="mb-8">
|
<header class="mb-8">
|
||||||
<flux:heading size="xl" class="text-body">{{ $post->getTitle() }}</flux:heading>
|
<flux:heading size="xl" class="text-body">{{ $post->getTitle() }}</flux:heading>
|
||||||
@@ -37,3 +38,4 @@ new #[Layout('components.layouts.public')] class extends Component
|
|||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<x-layouts.public>
|
<x-layouts.public>
|
||||||
<div class="py-8">
|
<div class="wrap py-8">
|
||||||
<h1 class="mb-6 text-3xl font-bold text-text">{{ $page->getTitle() }}</h1>
|
<h1 class="mb-6 text-3xl font-bold text-text">{{ $page->getTitle() }}</h1>
|
||||||
<div class="prose prose-brand max-w-none rounded-lg bg-card p-8 shadow-card" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
<div class="prose prose-brand max-w-none rounded-lg bg-card p-8 shadow-card" dir="{{ app()->getLocale() === 'ar' ? 'rtl' : 'ltr' }}">
|
||||||
@if ($page->getContent())
|
@if ($page->getContent())
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<x-layouts.public>
|
<x-layouts.public>
|
||||||
<div class="py-8">
|
<div class="wrap py-8">
|
||||||
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.privacy') }}</h1>
|
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.privacy') }}</h1>
|
||||||
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
||||||
<p class="text-body">{{ __('Privacy policy content will be added here.') }}</p>
|
<p class="text-body">{{ __('Privacy policy content will be added here.') }}</p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<x-layouts.public>
|
<x-layouts.public>
|
||||||
<div class="py-8">
|
<div class="wrap py-8">
|
||||||
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.terms') }}</h1>
|
<h1 class="text-3xl font-bold text-text mb-6">{{ __('footer.terms') }}</h1>
|
||||||
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
<div class="bg-card p-8 rounded-lg shadow-card prose prose-brand max-w-none">
|
||||||
<p class="text-body">{{ __('Terms of service content will be added here.') }}</p>
|
<p class="text-body">{{ __('Terms of service content will be added here.') }}</p>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png">
|
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
<meta name="theme-color" content="#4A4A42">
|
<meta name="theme-color" content="#2D3624">
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<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">
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
test('public stylesheet exists and every selector is scoped under body.site', function () {
|
||||||
|
$path = resource_path('css/public.css');
|
||||||
|
expect(file_exists($path))->toBeTrue();
|
||||||
|
|
||||||
|
$lines = preg_grep('/^[^@\/ }].*\{/', file($path, FILE_IGNORE_NEW_LINES));
|
||||||
|
$unscoped = array_filter($lines, fn ($l) => ! str_contains($l, 'body.site') && ! str_contains($l, 'html:has(body.site)'));
|
||||||
|
|
||||||
|
expect($unscoped)->toBe([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('public script modules exist', function () {
|
||||||
|
foreach (['public.js', 'site/reveal.js', 'site/progress.js'] as $f) {
|
||||||
|
expect(file_exists(resource_path("js/$f")))->toBeTrue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('vite config lists the public entries', function () {
|
||||||
|
$cfg = file_get_contents(base_path('vite.config.js'));
|
||||||
|
expect($cfg)->toContain('resources/css/public.css')->toContain('resources/js/public.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('home page uses the site foundation', function () {
|
||||||
|
$html = $this->get('/')->assertOk()->getContent();
|
||||||
|
|
||||||
|
expect($html)
|
||||||
|
->toContain('<body class="site')
|
||||||
|
->toContain('Markazi+Text')
|
||||||
|
->toMatch('~build/assets/public-[\w-]+\.css~')
|
||||||
|
->toMatch('~build/assets/public-[\w-]+\.js~')
|
||||||
|
->toContain('<div class="site-progress"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dashboards do not load the public foundation', function () {
|
||||||
|
foreach ([[User::factory()->admin()->create(), '/admin/dashboard'], [User::factory()->individual()->create(), '/client/dashboard']] as [$user, $url]) {
|
||||||
|
$html = $this->actingAs($user)->get($url)->assertOk()->getContent();
|
||||||
|
|
||||||
|
expect($html)
|
||||||
|
->not->toContain('public.css')
|
||||||
|
->not->toContain('public.js')
|
||||||
|
->not->toContain('Markazi')
|
||||||
|
->not->toContain('class="site');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('head theme colour is the forest green', function () {
|
||||||
|
$head = file_get_contents(resource_path('views/partials/head.blade.php'));
|
||||||
|
expect($head)->toContain('content="#2D3624"')->not->toContain('#4A4A42');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('site lang files carry the progress label', function () {
|
||||||
|
expect(trans('site.progress_label', [], 'en'))->toBe('Reading progress');
|
||||||
|
expect(trans('site.progress_label', [], 'ar'))->toBe('تقدّم القراءة');
|
||||||
|
});
|
||||||
+1
-1
@@ -7,7 +7,7 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
laravel({
|
laravel({
|
||||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
input: ['resources/css/app.css', 'resources/js/app.js', 'resources/css/public.css', 'resources/js/public.js'],
|
||||||
refresh: true,
|
refresh: true,
|
||||||
}),
|
}),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
|||||||
Reference in New Issue
Block a user