mirror of
https://github.com/thecyberlearn/quantum-website-2026.git
synced 2026-08-18 14:03:21 +00:00
share-header
This commit is contained in:
parent
6a4f826b79
commit
953e6f107b
25
django-base.html
Normal file
25
django-base.html
Normal 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
265
header.js
Normal 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
45
react-SharedHeader.jsx
Normal 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
25
react-layout-example.jsx
Normal 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
158
shared-header-README.md
Normal 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
29
static-site-example.html
Normal 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;">
|
||||
<script src="https://quantumtaskai.com/header.js"></script></pre>
|
||||
<p>That's it. No extra HTML, no CSS imports, no dependencies.</p>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user