share-header

This commit is contained in:
Amit Rana 2026-04-06 13:27:24 +05:30
parent 6a4f826b79
commit 953e6f107b
6 changed files with 547 additions and 0 deletions

25
django-base.html Normal file
View File

@ -0,0 +1,25 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}YourSite{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
<!--
Shared Header
Update the src URL below to point to your live static site.
To bust cache after updating header.js, append ?v=2 to the URL.
-->
<script src="https://quantumtaskai.com/header.js"></script>
<main>
{% block content %}{% endblock %}
</main>
{% block extra_scripts %}{% endblock %}
</body>
</html>

265
header.js Normal file
View File

@ -0,0 +1,265 @@
/*
Shared Header hosted at https://quantumtaskai.com/header.js
Update nav links and colors in this file only.
All connected apps will reflect changes on next page load.
To bust cache after updates, add ?v=2 to the script src.
*/
(function () {
// ─── Guard: prevent double injection (e.g. React hot reload) ───
if (document.getElementById('sh-header')) return;
// ─── Config: update nav links here ──────────────────────────────
var NAV_LINKS = [
{ label: 'Home', url: 'https://quantumtaskai.com' },
{ label: 'AI Digital Branding', url: 'https://quantumtaskai.com/digital-branding.html' },
{ label: 'Blog', url: 'https://blog.quantumtaskai.com/' },
];
// ─── Styles ──────────────────────────────────────────────────────
var css = `
:root {
--sh-bg: #0a0e1a;
--sh-accent: #3b82f6;
--sh-text: #ffffff;
}
#sh-header {
position: sticky;
top: 0;
left: 0;
width: 100%;
height: 64px;
background: var(--sh-bg);
z-index: 99999;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.sh-inner {
max-width: 1200px;
margin: 0 auto;
height: 100%;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.sh-logo {
font-size: 1.2rem;
font-weight: 700;
color: var(--sh-accent);
text-decoration: none;
letter-spacing: 0.02em;
white-space: nowrap;
}
.sh-logo:hover {
color: var(--sh-accent);
opacity: 0.85;
}
.sh-nav {
display: flex;
align-items: center;
gap: 8px;
list-style: none;
margin: 0;
padding: 0;
}
.sh-nav a {
color: var(--sh-text);
text-decoration: none;
font-size: 0.92rem;
font-weight: 500;
padding: 6px 12px;
border-radius: 6px;
transition: background 0.18s, color 0.18s;
white-space: nowrap;
}
.sh-nav a:hover {
background: rgba(59, 130, 246, 0.15);
color: var(--sh-accent);
}
.sh-nav a.sh-active {
color: var(--sh-accent);
background: rgba(59, 130, 246, 0.12);
}
/* ── Hamburger ── */
.sh-burger {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 36px;
height: 36px;
cursor: pointer;
background: none;
border: none;
padding: 4px;
border-radius: 6px;
transition: background 0.18s;
}
.sh-burger:hover {
background: rgba(255,255,255,0.08);
}
.sh-burger span {
display: block;
width: 22px;
height: 2px;
background: var(--sh-text);
border-radius: 2px;
transition: transform 0.22s, opacity 0.22s;
}
.sh-burger.sh-open span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.sh-burger.sh-open span:nth-child(2) {
opacity: 0;
}
.sh-burger.sh-open span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
/* ── Mobile menu ── */
.sh-mobile-menu {
display: none;
position: absolute;
top: 64px;
left: 0;
width: 100%;
background: var(--sh-bg);
padding: 12px 0 20px;
border-top: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.sh-mobile-menu.sh-open {
display: block;
}
.sh-mobile-menu a {
display: block;
color: var(--sh-text);
text-decoration: none;
font-size: 0.98rem;
font-weight: 500;
padding: 12px 28px;
transition: background 0.15s, color 0.15s;
}
.sh-mobile-menu a:hover {
background: rgba(59, 130, 246, 0.12);
color: var(--sh-accent);
}
.sh-mobile-menu a.sh-active {
color: var(--sh-accent);
}
@media (max-width: 768px) {
#sh-header {
height: 56px;
}
.sh-nav {
display: none;
}
.sh-burger {
display: flex;
}
.sh-mobile-menu {
top: 56px;
}
}
`;
// ─── Inject styles ───────────────────────────────────────────────
var styleEl = document.createElement('style');
styleEl.id = 'sh-styles';
styleEl.textContent = css;
document.head.appendChild(styleEl);
// ─── Detect active link ──────────────────────────────────────────
var currentUrl = window.location.href;
function buildNavItems(isMobile) {
return NAV_LINKS.map(function (link) {
var isActive = currentUrl === link.url ||
(link.url !== 'https://quantumtaskai.com' && currentUrl.indexOf(link.url) === 0);
var activeClass = isActive ? ' sh-active' : '';
if (isMobile) {
return '<a href="' + link.url + '" class="' + activeClass.trim() + '">' + link.label + '</a>';
}
return '<li><a href="' + link.url + '" class="' + activeClass.trim() + '">' + link.label + '</a></li>';
}).join('');
}
// ─── Build HTML ──────────────────────────────────────────────────
var headerHTML = `
<header id="sh-header">
<div class="sh-inner">
<a href="https://quantumtaskai.com" class="sh-logo">Quantum Tasks AI</a>
<nav aria-label="Main navigation">
<ul class="sh-nav">` + buildNavItems(false) + `</ul>
</nav>
<button class="sh-burger" id="sh-burger" aria-label="Toggle menu" aria-expanded="false" aria-controls="sh-mobile-menu">
<span></span>
<span></span>
<span></span>
</button>
</div>
<nav class="sh-mobile-menu" id="sh-mobile-menu" aria-label="Mobile navigation">
` + buildNavItems(true) + `
</nav>
</header>
`;
// ─── Inject header at top of body ───────────────────────────────
document.body.insertAdjacentHTML('afterbegin', headerHTML);
// ─── Hamburger logic ─────────────────────────────────────────────
var burger = document.getElementById('sh-burger');
var mobileMenu = document.getElementById('sh-mobile-menu');
function openMenu() {
burger.classList.add('sh-open');
mobileMenu.classList.add('sh-open');
burger.setAttribute('aria-expanded', 'true');
}
function closeMenu() {
burger.classList.remove('sh-open');
mobileMenu.classList.remove('sh-open');
burger.setAttribute('aria-expanded', 'false');
}
burger.addEventListener('click', function (e) {
e.stopPropagation();
mobileMenu.classList.contains('sh-open') ? closeMenu() : openMenu();
});
// Close on outside click
document.addEventListener('click', function (e) {
if (!document.getElementById('sh-header').contains(e.target)) {
closeMenu();
}
});
// Close on ESC
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeMenu();
});
console.log('Shared header loaded ✓');
})();

45
react-SharedHeader.jsx Normal file
View File

@ -0,0 +1,45 @@
import { useEffect } from 'react';
/**
* SharedHeader
*
* Dynamically loads the shared header script from your static site.
* Renders nothing itself the script injects the header into <body>.
*
* Usage:
* import SharedHeader from '@/components/SharedHeader';
* // Place it once in your root layout:
* <SharedHeader />
*
* To bust cache after updating header.js, change the version param:
* const HEADER_URL = 'https://quantumtaskai.com/header.js?v=2';
*/
const HEADER_URL = 'https://quantumtaskai.com/header.js';
export default function SharedHeader() {
useEffect(() => {
// Avoid loading twice (e.g. StrictMode double-invoke)
if (document.getElementById('sh-header')) return;
if (document.querySelector(`script[src="${HEADER_URL}"]`)) return;
const script = document.createElement('script');
script.src = HEADER_URL;
script.async = true;
document.body.appendChild(script);
return () => {
// Clean up on unmount
const injectedScript = document.querySelector(`script[src="${HEADER_URL}"]`);
if (injectedScript) injectedScript.remove();
const injectedHeader = document.getElementById('sh-header');
if (injectedHeader) injectedHeader.remove();
const injectedStyles = document.getElementById('sh-styles');
if (injectedStyles) injectedStyles.remove();
};
}, []);
return null;
}

25
react-layout-example.jsx Normal file
View File

@ -0,0 +1,25 @@
import SharedHeader from '@/components/SharedHeader';
/**
* Root layout for Next.js (works for both app/ and pages/ router).
*
* app/ router: use this as app/layout.jsx
* pages/ router: use this as pages/_app.jsx, wrapping <Component {...pageProps} />
*
* The padding-top on <main> prevents page content from being hidden
* behind the sticky header (64px desktop / 56px mobile).
*/
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{/* Injects the shared header into <body> via header.js */}
<SharedHeader />
<main style={{ paddingTop: '64px' }}>
{children}
</main>
</body>
</html>
);
}

158
shared-header-README.md Normal file
View File

@ -0,0 +1,158 @@
# Shared Header System
One header file. Three apps. Zero duplication.
## How It Works
```
header.js (hosted on your main static site)
├── Static HTML site → <script src="/header.js">
├── Django app → <script src="https://yoursite.com/header.js">
└── React / Next.js app → <SharedHeader /> component
```
When `header.js` loads in any app, it:
1. Injects a `<style>` block into `<head>`
2. Injects the header HTML at the top of `<body>`
3. Attaches hamburger toggle logic for mobile
4. Highlights the active nav link based on the current URL
---
## Step 1 — Host header.js on Your Static Site
Copy `header.js` to the root of your static site so it's accessible at:
```
https://yoursite.com/header.js
```
---
## Step 2 — Static HTML Setup
Add one line anywhere in `<body>`:
```html
<script src="https://yoursite.com/header.js"></script>
```
See `static-site-example.html` for a full working example.
---
## Step 3 — Django Setup
In your `base.html` template, add after `{% load static %}`:
```html
<script src="https://yoursite.com/header.js"></script>
```
All templates that extend `base.html` automatically get the header.
See `django-base.html` for a full base template example.
---
## Step 4 — React / Next.js Setup
**1. Copy the component** to your project:
```
src/components/SharedHeader.jsx (from react-SharedHeader.jsx)
```
**2. Add it to your root layout** (from `react-layout-example.jsx`):
```jsx
import SharedHeader from '@/components/SharedHeader';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<SharedHeader />
<main style={{ paddingTop: '64px' }}>
{children}
</main>
</body>
</html>
);
}
```
---
## Updating Nav Links
Edit the `NAV_LINKS` array at the top of `header.js`:
```js
var NAV_LINKS = [
{ label: 'Home', url: 'https://yoursite.com' },
{ label: 'About', url: 'https://yoursite.com/about' },
// add or remove items here
];
```
All connected apps reflect the change on next page load. No deploys needed for Django or React — they load the script remotely.
---
## Updating Colors
Edit the CSS variables near the top of `header.js`:
```css
:root {
--sh-bg: #0a0e1a; /* background */
--sh-accent: #3b82f6; /* accent / active color */
--sh-text: #ffffff; /* nav link text */
}
```
---
## Adding a New App
Any app — PHP, Ruby, Vue, plain HTML — needs exactly one line:
```html
<script src="https://yoursite.com/header.js"></script>
```
---
## Cache Busting After Updates
When you update `header.js`, browsers may serve the old cached version.
Append a version query param to force a fresh fetch:
```html
<!-- bump ?v= each time you update header.js -->
<script src="https://yoursite.com/header.js?v=2"></script>
```
Update this in all three places (static HTML, Django base template, `SharedHeader.jsx`).
---
## Troubleshooting
**Header not showing?**
- Open browser DevTools → Network tab → check if `header.js` loaded (200 status).
- Check the console for errors.
**CORS error in Django or React?**
- Your static site's server must allow cross-origin requests for JS files.
- In nginx, add to your server block:
```nginx
add_header Access-Control-Allow-Origin "*";
```
**Content flashes before header appears?**
- Add `min-height: 64px` to a placeholder div before the script tag, or load the script in `<head>` with `defer`.
**z-index conflict (header appears behind other elements)?**
- The header uses `z-index: 99999`. If something still overlaps it, check for `z-index` or `transform` on a parent element (transforms create new stacking contexts).
**Double header on React hot reload?**
- `header.js` checks `if (document.getElementById('sh-header')) return;` — this prevents double injection automatically.

29
static-site-example.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title - YourSite</title>
</head>
<body>
<!-- ─────────────────────────────────────────────────────────────
Shared Header — one line, that's it.
For same-domain use: <script src="/header.js"></script>
For cross-domain use: <script src="https://quantumtaskai.com/header.js"></script>
To bust cache after update: <script src="https://quantumtaskai.com/header.js?v=2"></script>
──────────────────────────────────────────────────────────────── -->
<script src="/header.js"></script>
<!-- Your page content below -->
<main style="max-width:800px; margin:60px auto; padding:0 24px; font-family:sans-serif;">
<h1>Welcome to YourSite</h1>
<p>The header above is injected automatically by <code>header.js</code>.</p>
<p>To use on any page, add one script tag:</p>
<pre style="background:#f4f4f4; padding:16px; border-radius:8px;">
&lt;script src="https://quantumtaskai.com/header.js"&gt;&lt;/script&gt;</pre>
<p>That's it. No extra HTML, no CSS imports, no dependencies.</p>
</main>
</body>
</html>