Commit e53700c2 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Polish website builder UI/UX — theme-aware CSS, section animations, visual controls

- CSS: all hardcoded colors replaced with CSS variables
- CSS: data-animation system (fade-up/down/left/right, zoom, flip, bounce)
- CSS: card style overrides via data-card, navbar variants via classes
- CSS: reduced-motion support, RTL logical properties throughout
- JS: IntersectionObserver for data-animation sections
- JS: gallery lightbox, FAQ accordion, floating navbar scroll
- Theme editor: mini-mockup template previews, better color preview strip
- Theme editor: visual density indicators, shape/card mini previews
- Theme editor: spinner on save, auto-dismiss success toast
- Section manager: RTL-safe toggle switch, icon on visual controls header
- Section manager: better save button with icon + spinner
- Section manager: auto-dismiss success toast with Alpine transition
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 7f1a8034
This diff is collapsed.
/** /**
* Bold & Athletic — Public Website Interactivity * El Captain — Public Website Interactivity
* Scroll animations, counter animations, navbar transitions * Section animations, counters, navbar transitions, gallery lightbox
*/ */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initNavbar(); initNavbar();
initSectionAnimations();
initRevealAnimations(); initRevealAnimations();
initCounterAnimations(); initCounterAnimations();
initSmoothScroll(); initSmoothScroll();
initGalleryLightbox();
initFAQAccordion();
}); });
// ─── Navbar scroll transition ───
function initNavbar() { function initNavbar() {
const nav = document.querySelector('.site-nav'); const nav = document.querySelector('.site-nav');
if (!nav) return; if (!nav) return;
const onScroll = () => { const onScroll = () => {
if (window.scrollY > 50) { if (window.scrollY > 60) {
nav.classList.add('site-nav--scrolled'); nav.classList.add('site-nav--scrolled');
} else { } else {
nav.classList.remove('site-nav--scrolled'); nav.classList.remove('site-nav--scrolled');
...@@ -27,7 +29,31 @@ function initNavbar() { ...@@ -27,7 +29,31 @@ function initNavbar() {
onScroll(); onScroll();
} }
// ─── Reveal on scroll (Intersection Observer) ─── function initSectionAnimations() {
const body = document.body;
if (body.dataset.animations === 'false') return;
const sections = document.querySelectorAll('[data-animation]');
if (!sections.length) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('animated');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -60px 0px' }
);
sections.forEach((el, i) => {
el.style.transitionDelay = `${i * 0.08}s`;
observer.observe(el);
});
}
function initRevealAnimations() { function initRevealAnimations() {
const elements = document.querySelectorAll('.reveal'); const elements = document.querySelectorAll('.reveal');
if (!elements.length) return; if (!elements.length) return;
...@@ -47,7 +73,6 @@ function initRevealAnimations() { ...@@ -47,7 +73,6 @@ function initRevealAnimations() {
elements.forEach((el) => observer.observe(el)); elements.forEach((el) => observer.observe(el));
} }
// ─── Counter animation (count up on scroll) ───
function initCounterAnimations() { function initCounterAnimations() {
const counters = document.querySelectorAll('[data-counter]'); const counters = document.querySelectorAll('[data-counter]');
if (!counters.length) return; if (!counters.length) return;
...@@ -76,7 +101,7 @@ function animateCounter(el) { ...@@ -76,7 +101,7 @@ function animateCounter(el) {
function update(now) { function update(now) {
const elapsed = now - start; const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1); const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 4); // ease-out quart const eased = 1 - Math.pow(1 - progress, 4);
const current = Math.floor(eased * target); const current = Math.floor(eased * target);
el.textContent = current.toLocaleString('ar-EG') + suffix; el.textContent = current.toLocaleString('ar-EG') + suffix;
...@@ -89,11 +114,11 @@ function animateCounter(el) { ...@@ -89,11 +114,11 @@ function animateCounter(el) {
requestAnimationFrame(update); requestAnimationFrame(update);
} }
// ─── Smooth scroll for anchor links ───
function initSmoothScroll() { function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach((link) => { document.querySelectorAll('a[href^="#"]').forEach((link) => {
link.addEventListener('click', (e) => { link.addEventListener('click', (e) => {
const targetId = link.getAttribute('href').slice(1); const targetId = link.getAttribute('href').slice(1);
if (!targetId) return;
const target = document.getElementById(targetId); const target = document.getElementById(targetId);
if (target) { if (target) {
e.preventDefault(); e.preventDefault();
...@@ -102,3 +127,74 @@ function initSmoothScroll() { ...@@ -102,3 +127,74 @@ function initSmoothScroll() {
}); });
}); });
} }
function initGalleryLightbox() {
const items = document.querySelectorAll('.gallery-item[data-src]');
if (!items.length) return;
let lightbox = null;
items.forEach((item) => {
item.addEventListener('click', () => {
const src = item.dataset.src;
if (!src) return;
if (!lightbox) {
lightbox = document.createElement('div');
lightbox.className = 'fixed inset-0 z-[200] bg-black/90 flex items-center justify-center p-4 cursor-pointer opacity-0 transition-opacity duration-300';
lightbox.innerHTML = '<img class="max-w-full max-h-[90vh] rounded-lg object-contain transform scale-95 transition-transform duration-300" />';
lightbox.addEventListener('click', () => {
lightbox.classList.remove('opacity-100');
lightbox.querySelector('img').classList.remove('scale-100');
lightbox.querySelector('img').classList.add('scale-95');
setTimeout(() => lightbox.remove(), 300);
lightbox = null;
});
document.body.appendChild(lightbox);
}
lightbox.querySelector('img').src = src;
requestAnimationFrame(() => {
lightbox.classList.add('opacity-100');
lightbox.querySelector('img').classList.remove('scale-95');
lightbox.querySelector('img').classList.add('scale-100');
});
});
});
}
function initFAQAccordion() {
const items = document.querySelectorAll('.faq-item');
if (!items.length) return;
items.forEach((item) => {
const trigger = item.querySelector('[data-faq-trigger]');
const content = item.querySelector('[data-faq-content]');
const icon = item.querySelector('[data-faq-icon]');
if (!trigger || !content) return;
trigger.addEventListener('click', () => {
const isOpen = item.dataset.open === 'true';
items.forEach((other) => {
if (other !== item) {
other.dataset.open = 'false';
const otherContent = other.querySelector('[data-faq-content]');
const otherIcon = other.querySelector('[data-faq-icon]');
if (otherContent) otherContent.style.maxHeight = '0';
if (otherIcon) otherIcon.style.transform = '';
}
});
if (isOpen) {
item.dataset.open = 'false';
content.style.maxHeight = '0';
if (icon) icon.style.transform = '';
} else {
item.dataset.open = 'true';
content.style.maxHeight = content.scrollHeight + 'px';
if (icon) icon.style.transform = 'rotate(180deg)';
}
});
});
}
...@@ -26,7 +26,15 @@ ...@@ -26,7 +26,15 @@
</div> </div>
@if(session('success')) @if(session('success'))
<div class="mb-4 p-3 rounded-lg bg-green-50 border border-green-200 text-green-700 text-sm"> <div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 3000)"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-2"
class="mb-4 p-3 rounded-lg bg-green-50 border border-green-200 text-green-700 text-sm flex items-center gap-2">
<svg class="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
{{ session('success') }} {{ session('success') }}
</div> </div>
@endif @endif
...@@ -55,8 +63,8 @@ ...@@ -55,8 +63,8 @@
{{-- Toggle --}} {{-- Toggle --}}
<button wire:click.stop="toggleSection({{ $section['id'] }})" <button wire:click.stop="toggleSection({{ $section['id'] }})"
class="shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled'] ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600' }}"> class="relative shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled'] ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600' }}">
<span class="block w-4 h-4 rounded-full bg-white shadow transform transition-transform {{ $section['is_enabled'] ? '-translate-x-4' : '-translate-x-0.5' }}"></span> <span class="absolute top-0.5 block w-4 h-4 rounded-full bg-white shadow transition-all {{ $section['is_enabled'] ? 'inset-inline-start-4' : 'inset-inline-start-0.5' }}"></span>
</button> </button>
</div> </div>
@endforeach @endforeach
...@@ -170,7 +178,10 @@ class="shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled'] ...@@ -170,7 +178,10 @@ class="shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled']
{{-- Visual Controls --}} {{-- Visual Controls --}}
<div class="border-t border-gray-200 dark:border-gray-700 pt-5 mt-5"> <div class="border-t border-gray-200 dark:border-gray-700 pt-5 mt-5">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">الإعدادات المرئية</h4> <h4 class="flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>
الإعدادات المرئية
</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{{-- Background Type --}} {{-- Background Type --}}
...@@ -265,13 +276,19 @@ class="shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled'] ...@@ -265,13 +276,19 @@ class="shrink-0 w-9 h-5 rounded-full transition-colors {{ $section['is_enabled']
</div> </div>
{{-- Actions --}} {{-- Actions --}}
<div class="flex items-center gap-3 pt-4 border-t border-gray-200 dark:border-gray-700"> <div class="flex items-center gap-3 pt-5 mt-5 border-t border-gray-200 dark:border-gray-700">
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium text-sm transition-colors" <button type="submit" class="inline-flex items-center gap-2 px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium text-sm transition-all shadow-sm hover:shadow-md"
wire:loading.attr="disabled" wire:target="saveSection"> wire:loading.attr="disabled" wire:loading.class="opacity-70 cursor-wait" wire:target="saveSection">
<span wire:loading.remove wire:target="saveSection">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</span>
<span wire:loading wire:target="saveSection">
<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
</span>
<span wire:loading.remove wire:target="saveSection">حفظ التغييرات</span> <span wire:loading.remove wire:target="saveSection">حفظ التغييرات</span>
<span wire:loading wire:target="saveSection">جارٍ الحفظ...</span> <span wire:loading wire:target="saveSection">جارٍ الحفظ...</span>
</button> </button>
<button type="button" wire:click="cancelEdit" class="px-4 py-2.5 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-sm transition-colors"> <button type="button" wire:click="cancelEdit" class="px-4 py-2.5 text-gray-600 dark:text-gray-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-sm transition-colors">
إلغاء إلغاء
</button> </button>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment