-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomponents.js
More file actions
150 lines (132 loc) · 6.21 KB
/
Copy pathcomponents.js
File metadata and controls
150 lines (132 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// =============================================================================
// Shared Web Components for ARtrio / X-ARt site
// Usage: <script src="/components.js"></script> (add to every page <head>)
//
// Components:
// <site-header page="home|tty2|efv"></site-header>
// <site-footer year="2026" name="「ARtrio」"></site-footer>
//
// Canonical <head> block to include on every page (adjust paths for depth):
// <!-- ES Module shims + import map (required for model-viewer-module + effects) -->
// <script async src="https://ga.jspm.io/npm:es-module-shims@1.7.1/dist/es-module-shims.js"></script>
// <script type="importmap">{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.174.0/build/three.module.min.js" } }</script>
// <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
// <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=Inter:wght@400;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
// <link rel="stylesheet" href="../style.css"> <!-- adjust depth: style.css or ../style.css -->
// <script src="../components.js"></script> <!-- adjust depth: components.js or ../components.js -->
// <!-- model-viewer@4.1.0 + effects (pinned — 4.2.0+ requires three@^0.182.0 which breaks effects@1.5.0) -->
// <script type="module" src="https://cdn.jsdelivr.net/npm/@google/model-viewer@4.1.0/dist/model-viewer-module.min.js"></script>
// <script type="module" src="https://cdn.jsdelivr.net/npm/@google/model-viewer-effects/dist/model-viewer-effects.min.js"></script>
// =============================================================================
// ---------------------------------------------------------------------------
// <site-header page="home|tty2|efv">
// ---------------------------------------------------------------------------
class SiteHeader extends HTMLElement {
connectedCallback() {
const page = this.getAttribute('page') || 'home';
// Resolve paths relative to the current page's depth
// page="home" → root level; page="tty2"|"efv" → one level deep
const isRoot = page === 'home';
const root = isRoot ? '.' : '..';
const logoSrc = `${root}/images/ARtrio_logo_white-01.png`;
const homeHref = isRoot ? './' : '../';
// Nav link hrefs — current page link becomes '#' (no navigation)
const links = {
tty2: isRoot ? 'TTY2/' : (page === 'tty2' ? '#' : '../TTY2/'),
efv: isRoot ? 'EFV/' : (page === 'efv' ? '#' : '../EFV/'),
};
this.innerHTML = `
<header id="siteHeader" class="site-header${page === 'efv' ? ' is-visible' : ''}">
<div class="header-container site-container">
<a href="${homeHref}" class="header-brand">
<img src="${logoSrc}" alt="ARtrio Logo"
style="width:150px;height:auto;clip-path:inset(20% 0);">
</a>
<button class="menu-toggle"
aria-label="Open navigation menu"
aria-expanded="false"
aria-controls="main-nav">
<span class="hamburger"></span>
</button>
</div>
<nav id="main-nav" class="main-navigation site-container">
<ul>
<li class="has-dropdown">
<a class="dropdown-toggle" role="button">Projects</a>
<ul class="dropdown-menu">
<li><a href="${links.tty2}"${page === 'tty2' ? ' aria-current="page"' : ''}>Through The Years, To Touch You</a></li>
<li><a href="${links.efv}"${page === 'efv' ? ' aria-current="page"' : ''}>EQUUS: FORM + VOID</a></li>
</ul>
</li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>`;
this._initMenu();
}
_initMenu() {
const header = this.querySelector('#siteHeader');
const menuToggle = this.querySelector('.menu-toggle');
const mainNav = this.querySelector('#main-nav');
const navLinks = mainNav ? mainNav.querySelectorAll('a:not(.dropdown-toggle)') : [];
let autoCloseTimer = null;
function closeMobileNav() {
if (document.body.classList.contains('nav-open')) {
document.body.classList.remove('nav-open');
menuToggle.setAttribute('aria-expanded', 'false');
if (autoCloseTimer) clearTimeout(autoCloseTimer);
}
}
// Show/hide header on scroll (pages that start hidden use is-visible class)
function onScrollMenu() {
if (!header) return;
if (window.scrollY > window.innerHeight * 0.5) {
header.classList.add('is-visible');
} else {
header.classList.remove('is-visible');
}
}
// Expose so page-level scroll handlers can call it
window._onScrollMenu = onScrollMenu;
menuToggle.addEventListener('click', () => {
const isNavOpen = document.body.classList.contains('nav-open');
if (isNavOpen) {
closeMobileNav();
} else {
document.body.classList.add('nav-open');
menuToggle.setAttribute('aria-expanded', 'true');
if (autoCloseTimer) clearTimeout(autoCloseTimer);
autoCloseTimer = setTimeout(closeMobileNav, 5000);
}
});
navLinks.forEach(link => link.addEventListener('click', closeMobileNav));
const dropdownToggles = this.querySelectorAll('.dropdown-toggle');
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', event => {
if (window.innerWidth < 768) {
event.preventDefault();
toggle.parentElement.classList.toggle('is-open');
}
});
});
}
}
customElements.define('site-header', SiteHeader);
// ---------------------------------------------------------------------------
// <site-footer year="2026" name="「ARtrio」">
// ---------------------------------------------------------------------------
class SiteFooter extends HTMLElement {
connectedCallback() {
const year = this.getAttribute('year') || new Date().getFullYear();
const name = this.getAttribute('name') || '「ARtrio」';
this.innerHTML = `
<footer id="contact" class="site-footer">
<h2>Contact</h2>
<p>For enquiries, please contact <a href="mailto:braudt@ust.hk">braudt@ust.hk</a></p>
<small>© ${year} ${name}</small>
</footer>`;
}
}
customElements.define('site-footer', SiteFooter);