v1.8.17: unified header/footer via components.js — all pages use same header, auth, branding

This commit is contained in:
2026-05-28 21:27:52 +00:00
parent 4705abeceb
commit cda4c36a1a
5 changed files with 194 additions and 372 deletions
+3 -141
View File
@@ -251,29 +251,7 @@ header { animation:slideDown 350ms var(--ease-out) both; }
<div class="fixed bottom-0 left-0 -z-10 w-72 h-72 bg-tertiary/5 rounded-full blur-[100px] pointer-events-none orb-2"></div>
<div class="fixed top-1/2 left-1/2 -z-10 w-64 h-64 bg-secondary/3 rounded-full blur-[140px] pointer-events-none" style="transform:translate(-50%,-50%);animation:orbFloat 16s ease-in-out infinite 4s"></div>
<!-- Header -->
<header class="sticky top-0 z-20 glass border-b border-primary/20">
<div class="max-w-6xl mx-auto px-4 lg:px-8 py-3 flex items-center justify-between gap-3">
<div class="flex items-center gap-3 shrink-0">
<div class="w-8 h-8 bg-primary/15 rounded-xl flex items-center justify-center shrink-0 brand-icon-wrap">
<span class="msym text-primary text-base">auto_awesome</span>
</div>
<div class="min-w-0 hidden sm:block">
<h1 class="text-sm font-bold text-primary leading-tight truncate" id="brand-name">AfakZaid RAG</h1>
<p class="text-[10px] text-on-surface-variant/60 uppercase tracking-widest" id="brand-sub">Scholarly Intelligence</p>
</div>
<div class="h-5 w-px bg-outline-variant/40 hidden sm:block"></div>
<a href="/library" class="glass glass-hover flex items-center gap-1 px-2.5 py-2 rounded-xl text-xs text-on-surface-variant hover:text-primary transition-colors">
<span class="msym text-base">library_books</span>
<span class="hidden sm:inline">المكتبة</span>
</a>
</div>
<nav class="flex items-center gap-2 shrink-0">
<div id="nav-user" class="shrink-0"></div>
</nav>
</div>
</header>
<div id="universal-header"></div>
<main class="max-w-6xl mx-auto px-4 lg:px-8 py-8">
<!-- Stats -->
@@ -369,92 +347,6 @@ const API_KEY = 'zRf77E_brSEnA2BUi40uxLxT4uofAk661WMZb-cdv4M';
let userToken = localStorage.getItem('rag_token');
let currentUser = null;
// ── Branding ──────────────────────────────────────────────────────────────────
// -- Branding color override --------------------------------------------------
function applyBrandingColors(p, s, bg, bgImg) {
p = p || '#4edea3';
s = s || '#95d3ba';
bg = bg || '#0c1324';
const hr = (hex) => {
hex = hex.replace('#','');
if(hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
return parseInt(hex.slice(0,2),16)+','+parseInt(hex.slice(2,4),16)+','+parseInt(hex.slice(4,6),16);
};
const pr = hr(p), sr = hr(s), bgr = hr(bg);
// Background
if(bgImg) { document.body.style.backgroundImage='url('+bgImg+')'; document.body.style.backgroundSize='cover'; document.body.style.backgroundPosition='center'; }
else { document.body.style.backgroundImage=''; document.body.style.background='radial-gradient(circle at 50% -20%,'+bg+' 0%,'+bg+' 100%)'; }
// Remove old style
const old = document.getElementById('_bco'); if(old) old.remove();
const st = document.createElement('style'); st.id='_bco';
st.textContent = [
// Primary color — text, bg, border
'.text-primary{color:'+p+'!important}',
'.bg-primary{background-color:'+p+'!important}',
'.border-primary{border-color:'+p+'!important}',
'[class*="text-primary"]{color:'+p+'!important}',
'[class*="bg-primary\/"]{}',
// Secondary
'.text-secondary{color:'+s+'!important}',
'.bg-secondary{background-color:'+s+'!important}',
'.border-secondary{border-color:'+s+'!important}',
'.text-tertiary{color:'+s+'!important}',
// Hover/focus states
'.hover\:text-primary:hover{color:'+p+'!important}',
'.hover\:bg-primary\/5:hover{background-color:rgba('+pr+',0.05)!important}',
'.focus\:border-primary:focus{border-color:'+p+'!important}',
// Active nav
'.active.bg-primary\/10{background-color:rgba('+pr+',0.1)!important}',
'.active.text-primary{color:'+p+'!important}',
'button.active{color:'+p+'!important}',
// Glass
'.glass{border-color:rgba('+pr+',0.12)!important}',
'.glass-hover:hover{background:rgba('+pr+',0.06)!important;border-color:rgba('+pr+',0.25)!important}',
// Glows
'.glow{box-shadow:0 0 20px rgba('+pr+',0.15)!important}',
'.glow-sm{box-shadow:0 0 10px rgba('+pr+',0.1)!important}',
'.glass.glow{box-shadow:inset 0 1px 0 rgba(255,255,255,0.06),0 0 20px rgba('+pr+',0.18),0 4px 24px rgba(0,0,0,0.15)!important}',
'.glass.glow-sm{box-shadow:inset 0 1px 0 rgba(255,255,255,0.06),0 0 10px rgba('+pr+',0.12)!important}',
// Spotlight card gradient
'.spotlight-card::before{background:radial-gradient(400px circle at var(--mx,50%) var(--my,50%),rgba('+pr+',0.06),transparent 60%)!important}',
// Alpha variants
'.bg-primary\/15,.bg-primary\/10,.bg-primary\/5{background-color:rgba('+pr+',0.1)!important}',
'.border-primary\/20{border-color:rgba('+pr+',0.2)!important}',
'.border-primary\/30{border-color:rgba('+pr+',0.3)!important}',
// Upload zone
'.upload-zone{border-color:rgba('+pr+',0.2)!important}',
'.upload-zone.dragover{border-color:rgba('+pr+',0.6)!important}',
// Scrollbar
'.scrollbar::-webkit-scrollbar-thumb{background:rgba('+pr+',0.2)!important}',
// Input focus
'.inp:focus,.inp:focus-within{border-color:'+p+'!important;box-shadow:0 0 0 2px rgba('+pr+',0.1)!important}',
// Filter chips active
'.filter-chip.active{background:rgba('+pr+',0.12)!important;border-color:rgba('+pr+',0.4)!important;color:'+p+'!important}',
// Book card hover
'.book-card:hover{box-shadow:0 8px 24px rgba('+pr+',0.1)!important;border-color:rgba('+pr+',0.28)!important}',
// Nav active
'.nav-btn.active{background-color:rgba('+pr+',0.1)!important;color:'+p+'!important}',
// Sidebar border
'aside{border-color:rgba('+pr+',0.2)!important}',
// CTA button shimmer
'.cta-primary::before{background:linear-gradient(90deg,transparent,rgba('+pr+',0.1),transparent)!important}',
// Shadow glow on hover
'[class*="hover:shadow"]:hover{--tw-shadow-color:rgba('+pr+',0.4)!important}',
].join('\n');
document.head.appendChild(st);
document.documentElement.style.setProperty('--primary', p);
document.documentElement.style.setProperty('--secondary', s);
}
fetch('/branding').then(r=>r.json()).then(b=>{
if(b.site_name){document.title=b.site_name;const bn=document.getElementById('brand-name');if(bn)bn.textContent=b.site_name;}
if(b.site_subtitle){const bs=document.getElementById('brand-sub');if(bs)bs.textContent=b.site_subtitle;}
applyBrandingColors(b.primary_color,b.secondary_color,b.bg_color,b.bg_image);
if(b.logo_url){
const logo=document.getElementById('brand-logo');
if(logo){logo.src=b.logo_url;logo.style.height=(b.logo_size||'40')+'px';logo.classList.remove('hidden');}
}
}).catch(()=>{});
// ── Counter animation ─────────────────────────────────────────────────────────
function animateCount(el, target, duration=900) {
@@ -480,37 +372,6 @@ const observer = new IntersectionObserver(entries => {
}, { threshold: 0.12 });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
// ── Auth ──────────────────────────────────────────────────────────────────────
async function initAuth() {
const nav = document.getElementById('nav-user');
if (!userToken) {
nav.innerHTML = `<a href="/login" class="bg-primary text-surface font-bold px-3 py-2 rounded-xl text-xs hover:shadow-[0_0_15px_rgba(78,222,163,0.4)] transition-all flex items-center gap-1.5 whitespace-nowrap" style="animation:scalePop .4s cubic-bezier(.22,1,.36,1) .8s both"><span class="msym text-base">login</span><span>دخول</span></a>`;
document.getElementById('upload-card').classList.add('hidden');
return;
}
try {
const r = await fetch('/auth/me', {headers:{'Authorization':'Bearer '+userToken}});
if (!r.ok) { localStorage.removeItem('rag_token'); initAuth(); return; }
currentUser = await r.json();
const perms = currentUser.permissions || [];
if (!perms.includes('upload')) document.getElementById('upload-card').classList.add('hidden');
nav.innerHTML = `
<div class="flex items-center gap-2" style="animation:fadeIn .4s both .6s">
<div class="text-right hidden sm:block">
<p class="text-xs font-bold">${currentUser.name}</p>
<p class="text-[10px] text-primary">${currentUser.role}</p>
</div>
${perms.includes('admin') ? '<a href="/admin" class="glass glass-hover p-2 rounded-xl transition-colors"><span class="msym text-base text-on-surface-variant">admin_panel_settings</span></a>' : ''}
<button onclick="logout()" class="glass glass-hover p-2 rounded-xl transition-colors"><span class="msym text-base text-on-surface-variant">logout</span></button>
</div>`;
} catch(e){}
}
function logout() {
if(userToken) fetch('/auth/logout',{method:'POST',headers:{'Authorization':'Bearer '+userToken}});
localStorage.removeItem('rag_token');
window.location.reload();
}
// ── Stats ─────────────────────────────────────────────────────────────────────
async function loadStats() {
@@ -683,7 +544,6 @@ spinStyle.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
document.head.appendChild(spinStyle);
// ── Init ──────────────────────────────────────────────────────────────────────
initAuth();
loadStats();
loadBooks();
setInterval(loadStats, 30000);
@@ -697,5 +557,7 @@ setInterval(loadStats, 30000);
<span style="margin:0 8px">© 2026 أفاق زيد</span>
</footer>
<script src="/components.js"></script>
<div id="universal-footer"></div>
</body>
</html>