|
139 | 139 | #table-body.table-content-enter{animation:tableRefresh .22s ease-out} |
140 | 140 | @media (prefers-reduced-motion:reduce){#table-body.table-content-enter{animation:none}main.space-y-6 > #selected-tabs-panel{transition:none}main.space-y-6 > #blacklist-panel{transition:none}} |
141 | 141 | </style> |
| 142 | +<style id="catalog-summary-styles"> |
| 143 | + #app-subtitle:empty { display: none; } |
| 144 | + #ticker-count { cursor: help; font-family: inherit; } |
| 145 | + #ticker-count:focus-visible { outline: 2px solid #3b82f6; outline-offset: 3px; } |
| 146 | + #app-summary[hidden] { display: none; } |
| 147 | + #app-summary { |
| 148 | + box-sizing: border-box; position: fixed; z-index: 100; display: block; |
| 149 | + width: min(42rem, calc(100vw - 2rem)); max-height: min(70vh, 32rem); |
| 150 | + overflow: auto; overflow-wrap: anywhere; padding: 1rem; |
| 151 | + border: 1px solid #cbd5e1; border-radius: .75rem; |
| 152 | + background: #fff; color: #334155; font-size: .875rem; line-height: 1.5; |
| 153 | + box-shadow: 0 12px 30px rgb(15 23 42 / .2); |
| 154 | + } |
| 155 | + .dark #app-summary { background: #0f172a; color: #cbd5e1; border-color: #475569; } |
| 156 | + </style> |
142 | 157 | </head> |
143 | 158 | <body class="min-h-screen font-sans bg-slate-50 text-slate-900 dark:bg-slate-900 dark:text-slate-100 transition-colors duration-300"> |
144 | 159 | <div class="min-h-screen flex flex-col animate-fade-in"> |
|
153 | 168 | </svg> |
154 | 169 | <h1 class="min-w-0 text-xl font-bold tracking-tight text-slate-900 dark:text-white leading-tight">WisdomTree ETFs</h1> |
155 | 170 | </div> |
156 | | - <span id="app-subtitle" class="basis-full sm:basis-auto text-sm text-slate-500 dark:text-slate-400"> |
157 | | - Search WisdomTree ETFs, select ETFs via the “Use” checkbox, then use the Watchlist tab. Data comes from |
158 | | - <a href="./api/wisdomtree/index.json" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 dark:text-blue-400 hover:underline">api/wisdomtree/index.json</a> generated from |
159 | | - <a href="https://www.wisdomtree.com/us/products" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 dark:text-blue-400 hover:underline">official WisdomTree product pages</a> + SEC EDGAR N-PORT-P + Yahoo Finance |
160 | | - </span> |
| 171 | + <span id="app-subtitle" class="basis-full sm:basis-auto text-sm text-slate-500 dark:text-slate-400"></span> |
161 | 172 | </div> |
162 | 173 | <div class="flex items-center gap-3 shrink-0"> |
163 | | - <span id="ticker-count" class="bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 text-sm font-semibold px-3.5 py-1.5 rounded-full border border-blue-200 dark:border-blue-500/30"> |
| 174 | + <button type="button" aria-controls="app-summary" aria-expanded="false" aria-label="ETF catalog information" id="ticker-count" class="bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 text-sm font-semibold px-3.5 py-1.5 rounded-full border border-blue-200 dark:border-blue-500/30"> |
164 | 175 | Loading… |
| 176 | + </button> |
| 177 | + <span id="app-summary" role="region" aria-label="ETF catalog information" hidden> |
| 178 | + Search WisdomTree ETFs, select ETFs via the “Use” checkbox, then use the Watchlist tab. Data comes from |
| 179 | + <a href="./api/wisdomtree/index.json" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 dark:text-blue-400 hover:underline">api/wisdomtree/index.json</a> generated from |
| 180 | + <a href="https://www.wisdomtree.com/us/products" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 dark:text-blue-400 hover:underline">official WisdomTree product pages</a> + SEC EDGAR N-PORT-P + Yahoo Finance |
165 | 181 | </span> |
166 | 182 | <button id="theme-toggle" class="p-1.5 rounded-lg bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:bg-slate-200 dark:hover:bg-slate-700 transition text-base leading-none" title="Toggle Theme">🌙</button> |
167 | 183 | </div> |
@@ -263,5 +279,59 @@ <h1 class="min-w-0 text-xl font-bold tracking-tight text-slate-900 dark:text-whi |
263 | 279 | </div> |
264 | 280 |
|
265 | 281 | <script type="text/babel" data-presets="typescript" src="./app.tsx"></script> |
| 282 | +<script id="catalog-summary-behavior"> |
| 283 | + (() => { |
| 284 | + const trigger = document.getElementById('ticker-count'); |
| 285 | + const panel = document.getElementById('app-summary'); |
| 286 | + if (!trigger || !panel) return; |
| 287 | + let pinned = false; |
| 288 | + let closeTimer; |
| 289 | + const position = () => { |
| 290 | + const rect = trigger.getBoundingClientRect(); |
| 291 | + panel.style.left = `${Math.max(16, Math.min(rect.right - panel.offsetWidth, innerWidth - panel.offsetWidth - 16))}px`; |
| 292 | + panel.style.top = `${Math.max(16, Math.min(rect.bottom + 8, innerHeight - panel.offsetHeight - 16))}px`; |
| 293 | + }; |
| 294 | + const show = () => { |
| 295 | + clearTimeout(closeTimer); |
| 296 | + panel.hidden = false; |
| 297 | + trigger.setAttribute('aria-expanded', 'true'); |
| 298 | + position(); |
| 299 | + }; |
| 300 | + const hide = () => { |
| 301 | + clearTimeout(closeTimer); |
| 302 | + panel.hidden = true; |
| 303 | + trigger.setAttribute('aria-expanded', 'false'); |
| 304 | + }; |
| 305 | + const scheduleHide = () => { |
| 306 | + clearTimeout(closeTimer); |
| 307 | + closeTimer = setTimeout(() => { |
| 308 | + const focused = document.activeElement; |
| 309 | + if (!pinned && !trigger.matches(':hover') && !panel.matches(':hover') && |
| 310 | + focused !== trigger && !panel.contains(focused)) hide(); |
| 311 | + }, 150); |
| 312 | + }; |
| 313 | + trigger.addEventListener('pointerenter', event => { if (event.pointerType !== 'touch') show(); }); |
| 314 | + panel.addEventListener('pointerenter', () => clearTimeout(closeTimer)); |
| 315 | + trigger.addEventListener('pointerleave', scheduleHide); |
| 316 | + panel.addEventListener('pointerleave', scheduleHide); |
| 317 | + trigger.addEventListener('focus', show); |
| 318 | + panel.addEventListener('focusin', show); |
| 319 | + trigger.addEventListener('blur', scheduleHide); |
| 320 | + panel.addEventListener('focusout', scheduleHide); |
| 321 | + trigger.addEventListener('click', () => { pinned = !pinned; if (pinned) show(); else hide(); }); |
| 322 | + document.addEventListener('keydown', event => { |
| 323 | + if (event.key !== 'Escape') return; |
| 324 | + pinned = false; |
| 325 | + if (panel.contains(document.activeElement)) trigger.focus(); |
| 326 | + hide(); |
| 327 | + }); |
| 328 | + document.addEventListener('pointerdown', event => { |
| 329 | + if (!trigger.contains(event.target) && !panel.contains(event.target)) { pinned = false; hide(); } |
| 330 | + }); |
| 331 | + addEventListener('resize', () => { if (!panel.hidden) position(); }); |
| 332 | + addEventListener('scroll', () => { if (!panel.hidden) position(); }, { passive: true }); |
| 333 | + if (typeof ResizeObserver === 'function') new ResizeObserver(() => { if (!panel.hidden) position(); }).observe(panel); |
| 334 | + })(); |
| 335 | + </script> |
266 | 336 | </body> |
267 | 337 | </html> |
0 commit comments