My agentic slop goes here. Not intended for anyone else!
1/**
2 * Arod Menu Bar Component
3 * A responsive navigation menu with search and theme toggle
4 *
5 * Usage:
6 * <arod-menu>
7 * <menu-item href="/papers">Papers</menu-item>
8 * <menu-item href="/notes">Notes</menu-item>
9 * <menu-item href="/about">About</menu-item>
10 * </arod-menu>
11 */
12
13// ============================================
14// AROD-MENU - Main Menu Bar Component
15// ============================================
16class ArodMenu extends HTMLElement {
17 constructor() {
18 super();
19 this.attachShadow({ mode: 'open' });
20 }
21
22 connectedCallback() {
23 this.render();
24 this.setupMobileToggle();
25 }
26
27 render() {
28 this.shadowRoot.innerHTML = `
29 <style>
30 :host {
31 --menu-height: 3.5rem;
32 --menu-bg: #fffffc;
33 --menu-border: #666;
34 --menu-text: #1a1a1a;
35 --menu-hover: #090c8d;
36 --menu-mobile-breakpoint: 768px;
37
38 display: block;
39 position: sticky;
40 top: 0;
41 z-index: 1000;
42 background: var(--menu-bg);
43 border-bottom: 1px solid var(--menu-border);
44 height: var(--menu-height);
45 }
46
47 @media (prefers-color-scheme: dark) {
48 :host {
49 --menu-bg: #1a1a1a;
50 --menu-border: #444;
51 --menu-text: #e0e0e0;
52 --menu-hover: #4db8ff;
53 }
54 }
55
56 :host([theme="light"]) {
57 --menu-bg: #fffffc;
58 --menu-border: #666;
59 --menu-text: #1a1a1a;
60 --menu-hover: #090c8d;
61 }
62
63 :host([theme="dark"]) {
64 --menu-bg: #1a1a1a;
65 --menu-border: #444;
66 --menu-text: #e0e0e0;
67 --menu-hover: #4db8ff;
68 }
69
70 .menu-container {
71 display: flex;
72 align-items: center;
73 justify-content: space-between;
74 height: 100%;
75 max-width: 1200px;
76 margin: 0 auto;
77 padding: 0 1rem;
78 }
79
80 .menu-left {
81 display: flex;
82 align-items: center;
83 gap: 2rem;
84 }
85
86 .menu-logo {
87 font-size: 1.2rem;
88 font-weight: 700;
89 color: var(--menu-text);
90 text-decoration: none;
91 }
92
93 .menu-items {
94 display: flex;
95 align-items: center;
96 gap: 1.5rem;
97 list-style: none;
98 }
99
100 .menu-right {
101 display: flex;
102 align-items: center;
103 gap: 1rem;
104 }
105
106 .mobile-toggle {
107 display: none;
108 background: none;
109 border: none;
110 color: var(--menu-text);
111 cursor: pointer;
112 padding: 0.5rem;
113 }
114
115 .mobile-toggle svg {
116 width: 24px;
117 height: 24px;
118 }
119
120 @media (max-width: 768px) {
121 .menu-items {
122 display: none;
123 position: absolute;
124 top: var(--menu-height);
125 left: 0;
126 right: 0;
127 background: var(--menu-bg);
128 border-bottom: 1px solid var(--menu-border);
129 flex-direction: column;
130 padding: 1rem;
131 gap: 0.5rem;
132 align-items: stretch;
133 }
134
135 .menu-items.mobile-open {
136 display: flex;
137 }
138
139 .mobile-toggle {
140 display: block;
141 }
142
143 .menu-right {
144 margin-left: auto;
145 }
146 }
147
148 ::slotted(menu-item) {
149 display: inline-flex !important;
150 align-items: center;
151 cursor: pointer;
152 height: 100%;
153 }
154 </style>
155
156 <nav class="menu-container">
157 <div class="menu-left">
158 <a href="/" class="menu-logo">AROD</a>
159 <button class="mobile-toggle" aria-label="Menu">
160 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
161 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
162 </svg>
163 </button>
164 <div class="menu-items">
165 <slot></slot>
166 </div>
167 </div>
168 <div class="menu-right">
169 <slot name="actions"></slot>
170 </div>
171 </nav>
172 `;
173 }
174
175 setupMobileToggle() {
176 const toggle = this.shadowRoot.querySelector('.mobile-toggle');
177 const items = this.shadowRoot.querySelector('.menu-items');
178
179 toggle?.addEventListener('click', () => {
180 items?.classList.toggle('mobile-open');
181 });
182
183 // Close menu when clicking outside
184 document.addEventListener('click', (e) => {
185 if (!this.contains(e.target)) {
186 items?.classList.remove('mobile-open');
187 }
188 });
189 }
190}
191
192// ============================================
193// MENU-ITEM - Individual Menu Item Component
194// ============================================
195class MenuItem extends HTMLElement {
196 constructor() {
197 super();
198 // Store the original text content
199 this._label = '';
200 }
201
202 connectedCallback() {
203 // Capture the text content before any rendering
204 this._label = this.textContent.trim();
205 this.render();
206 this.setupNavigation();
207 }
208
209 render() {
210 const href = this.getAttribute('href') || '#';
211
212 // Style the menu-item element itself
213 this.style.cssText = `
214 display: inline-flex;
215 align-items: center;
216 height: 100%;
217 `;
218
219 // Create link element
220 const link = document.createElement('a');
221 link.href = href;
222 link.textContent = this._label;
223 link.style.cssText = `
224 color: var(--menu-text, #222);
225 text-decoration: none;
226 padding: 0.5rem 0.75rem;
227 border-radius: 4px;
228 transition: all 0.2s ease;
229 display: inline-block;
230 white-space: nowrap;
231 `;
232
233 // Clear and add link
234 this.innerHTML = '';
235 this.appendChild(link);
236
237 // Add hover effects directly to the link
238 link.addEventListener('mouseenter', () => {
239 link.style.color = 'var(--menu-hover, #0066cc)';
240 link.style.background = 'rgba(0, 102, 204, 0.1)';
241 });
242
243 link.addEventListener('mouseleave', () => {
244 if (!this.hasAttribute('active')) {
245 link.style.color = 'var(--menu-text, #222)';
246 link.style.background = 'transparent';
247 }
248 });
249 }
250
251 setupNavigation() {
252 // Mark active based on current path
253 const href = this.getAttribute('href');
254 const link = this.querySelector('a');
255 if (href && window.location.pathname === href) {
256 this.setAttribute('active', '');
257 if (link) {
258 link.style.color = 'var(--menu-hover, #0066cc)';
259 link.style.fontWeight = '500';
260 }
261 }
262 }
263}
264
265// Register components
266customElements.define('arod-menu', ArodMenu);
267customElements.define('menu-item', MenuItem);