1<!DOCTYPE html> 2<html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta name="description" content="Search Broadway Play Home for plays, people, theatres and more."> 7 <meta name="keywords" content="search, broadway, theatre, theater, play, plays, broadway play"> 8 <meta name="author" content="Valerie Rigsbee"> 9 <link rel="shortcut icon" href="images/favicon.ico"> 10 11 <title>Broadway Play Home - Search</title> 12 13 <!-- Social Sharing --> 14 <meta property="og:image" content="https://broadwayplayhome.com/images/bph-social.png"> 15 <meta property="og:image:type" content="image/png"> 16 <meta property="og:image:width" content="1200"> 17 <meta property="og:image:height" content="630"> 18 <meta property="og:type" content="website" /> 19 20 <link href="https://broadwayplayhome.com/css/bootstrap-select.css" rel="stylesheet" > 21 <link href="https://broadwayplayhome.com/css/theme.css" rel="stylesheet"> 22 <link href="https://broadwayplayhome.com/css/oscar.css" rel="stylesheet" /> 23 <link href="https://broadwayplayhome.com/css/2021.css" rel="stylesheet"> 24 <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> 25 <link href="https://broadwayplayhome.com/css/bootstrap.css" rel="stylesheet"> 26 <link href="https://broadwayplayhome.com/css/2024menu.css" rel="stylesheet"> 27 <link href="https://broadwayplayhome.com/css/2024footer.css" rel="stylesheet"> 28 <link href="https://broadwayplayhome.com/css/2024.css" rel="stylesheet"> 29 <link href="https://broadwayplayhome.com/css/2026.css" rel="stylesheet"> 30
30<script type="text/javascript" charset="UTF-8" src="//cdn.cookie-script.com/s/18085314d6b37b58c6602fa8d8ad0c28.js"></script>
30 31
31<script src="https://kit.fontawesome.com/b38ecd9d3c.js" crossorigin="anonymous"></script>
31 32 33
33<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
33 34 <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> 35 <!--[if lt IE 9]> 36
36<script src="js/html5shiv.js"></script>
36 37
37<script src="js/respond.min.js"></script>
37 38 <![endif]--> 39 <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 39-40
39 40 <script async src="https://www.googletagmanager.com/gtag/js?id=
40G-EF33EF6Y5D
vendor: 16 bytes, lines 40-41
40"></script> 41
41<script> 42
vendor: 139 bytes, lines 42-45
42window.dataLayer = window.dataLayer || []; 43 function gtag(){dataLayer.push(arguments);} 44 gtag('js', new Date()); 45 gtag('config', '
45G-EF33EF6Y5D
vendor: 8 bytes, lines 45-46
45'); 46
46</script>
46 47 48 </head> 49 50 <body> 51 <div id="wrap"> 52 53 <nav class="navbar navbar-expand-lg navbar-inverse sticky-top"> 54 <div class="container"> 55 <a class="navbar-brand" href="https://broadwayplayhome.com"><img src="https://broadwayplayhome.com/images/broadway-play-home.png" alt="Broadway Play Home" class="d-inline-block align-top mobilehide" /><img src="https://broadwayplayhome.com/images/broadway-play-home-3.png" alt="Broadway Play Home" class="d-inline-block align-top mobileonly" /></a> 56 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 57 <span class="navbar-toggler-icon">â°</span> 58 </button> 59 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 60 <ul class="navbar-nav navbar-right"> 61 <li class="nav-item"> 62 <a class="nav-link" href="https://broadwayplayhome.com/shows.htm">Plays</a> 63 </li> 64 <li class="nav-item"> 65 <a class="nav-link" href="https://broadwayplayhome.com/people.htm">People</a> 66 </li> 67 <li class="nav-item"> 68 <a class="nav-link" href="https://broadwayplayhome.com/theatres.htm">Theatres</a> 69 </li> 70 <li class="nav-item dropdown"> 71 <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 72 More 73 </a> 74 <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> 75 <a class="dropdown-item" href="https://broadwayplayblog.com/" target="_blank">Blog: Reviews & Buzz</a> 76 <a class="dropdown-item" href="https://broadwayplayhome.com/production-rights.htm">Production Rights</a> 77 <a class="dropdown-item" href="https://broadwayplayhome.com/watch.htm">Available for Streaming</a> 78 <a class="dropdown-item" href="https://broadwayplayhome.com/tonys.htm">Award Winners & Nominees</a> 79 <a class="dropdown-item separate" href="https://broadwaymusicalhome.com/" target="_blank">Broadway Musical Home</a> 80 <a class="dropdown-item" href="https://broadwayplayhome.com/bio.htm">About BPH</a> 81 <div class="socials-nav"> 82 <a href="https://feeds.feedburner.com/thebroadwayplayblog" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/rss.png" border="0" alt="Blog" width="35" title="The Broadway Musical Blog" /></a> 83 <a href="https://twitter.com/bwayplayhome" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/twitter.png" border="0" alt="Twitter" width="35" title="Twitter" /></a> 84 <a href="https://facebook.com/broadwayplayhome" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/facebook.png" border="0" alt="Facebook" width="35" title="Facebook" /></a> 85 <a href="https://www.instagram.com/broadwayplayhome/" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/instagram.png" border="0" alt="Instagram" width="35" title="Instagram" /></a> 86 <a href="https://broadwayplayblog.com/subscribe/" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/email.png" width="35" alt="Newsletter" title="Email Newsletter" /></a> 87 </div> 88 </li> 89 <li class="nav-item"> 90 <a class="nav-link" href="https://broadwayplayhome.com/search.htm" aria-label="Search"> 91 <i class="fa-solid fa-magnifying-glass"></i> 92 </a> 93 </li> 94 </ul> 95 </div> 96 </div> 97 </nav> 98 99 <div class="container" id="bph-search"> 100 <div class="row"> 101 102 <div class="col-md-6"> 103 <p class="mobile-hide">Info, tickets, merch, rights, and more. Your home for all things Broadway.</p> 104 <nav aria-label="breadcrumb"> 105 <ol class="breadcrumb"> 106 <li class="breadcrumb-item"><a href="https://broadwayplayhome.com"><i class="fa-solid fa-house"></i></a></li> 107 <li class="breadcrumb-item active" aria-current="page">Search</li> 108 </ol> 109 </nav> 110 </div> 111 <div class="col-md-6"> 112 <p class="text-right"> 113
113<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6415408182234082" 114 crossorigin="anonymous"></script>
114 115 <!-- BPH 468x90 Top Ad Unit --> 116 <ins class="adsbygoogle" 117 style="display:inline-block;width:468px;height:90px" 118 data-ad-client="ca-pub-6415408182234082" 119 data-ad-slot="6384761477"></ins> 120
120<script> 121 (adsbygoogle = window.adsbygoogle || []).push({}); 122 </script>
122 123 </p> 124 </div> 125 </div> 126 127 128 129 <div class="showtitle"> 130 <div class="row"> 131 <div class="col-12"> 132 <h1 style="margin-top:0px;">Search</h1> 133 </div> 134 </div> 135 </div> 136 137 <form class="ss-searchbar" role="search" id="ss-form" action="#" autocomplete="off"> 138 <div class="ss-bar"> 139 <div class="ss-dd" id="ss-cat-dd"> 140 <span id="ss-cat-label" class="ss-visually-hidden">Search in</span> 141 <button type="button" class="ss-dd-btn" id="ss-cat-btn" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="ss-cat-label ss-cat-btn"><span class="ss-dd-label">All Categories</span></button> 142 <ul class="ss-dd-list" role="listbox" tabindex="-1" aria-labelledby="ss-cat-label" hidden></ul> 143 </div> 144 <div class="ss-field"> 145 <label for="ss-q" class="ss-visually-hidden">Search Broadway Musical Home</label> 146 <input id="ss-q" class="ss-input" type="search" name="q" placeholder="Search here" enterkeyhint="search" spellcheck="false" autocapitalize="off"> 147 <button type="button" class="ss-clear" id="ss-clear" aria-label="Clear search" hidden>×</button> 148 </div> 149 <button type="submit" class="ss-go" aria-label="Search"><svg width="24" height="24" viewBox="0 0 20 20" aria-hidden="true" focusable="false"><circle cx="8.5" cy="8.5" r="6" fill="none" stroke="currentColor" stroke-width="2"/><path d="M13 13l5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button> 150 </div> 151 </form> 152 153 <noscript><p>Search needs JavaScript. You can still browse <a href="https://broadwayplayhome.com/shows.htm">all plays</a>, <a href="https://broadwayplayhome.com/people.htm">people</a>, and <a href="https://broadwayplayhome.com/theatres.htm">theatres</a>.</p></noscript> 154 155 <div id="ss-app" hidden> 156 <button type="button" class="ss-toggle" id="ss-toggle" aria-expanded="false" aria-controls="ss-panel" hidden>Show filters <span id="ss-toggle-n"></span></button> 157 158 <div class="well ss-panel" id="ss-panel" hidden> 159 <div class="ss-group"> 160 <h2 class="ss-group-title">Era</h2> 161 <div class="ss-era-row"> 162 <span class="ss-era-label" id="ss-from-label">From</span> 163 <div class="ss-dd" id="ss-from-dd"> 164 <button type="button" class="ss-dd-btn" id="ss-from-btn" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="ss-from-label ss-from-btn"><span class="ss-dd-label">Any decade</span></button> 165 <ul class="ss-dd-list" role="listbox" tabindex="-1" aria-labelledby="ss-from-label" hidden></ul> 166 </div> 167 </div> 168 <div class="ss-era-row"> 169 <span class="ss-era-label" id="ss-to-label">To</span> 170 <div class="ss-dd" id="ss-to-dd"> 171 <button type="button" class="ss-dd-btn" id="ss-to-btn" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="ss-to-label ss-to-btn"><span class="ss-dd-label">Any decade</span></button> 172 <ul class="ss-dd-list" role="listbox" tabindex="-1" aria-labelledby="ss-to-label" hidden></ul> 173 </div> 174 </div> 175 <button type="button" class="ss-linkbtn" id="ss-clear-filters" hidden>Clear filters</button> 176 </div> 177 <div class="ss-group"> 178 <h2 class="ss-group-title" id="ss-type-title">Play Type</h2> 179 <ul class="ss-chips" id="ss-type" aria-labelledby="ss-type-title"></ul> 180 </div> 181 </div> 182 183 <p class="ss-visually-hidden" id="ss-status" role="status" aria-live="polite"></p> 184 <div id="ss-results"></div> 185 </div> 186 187 <div id="ss-loading" class="ss-loading">Loading search...</div> 188 <div id="ss-error" class="ss-error" hidden> 189 <p><strong>The search index could not be loaded.</strong> Reload the page to try again.</p> 190 <div id="ss-local" hidden> 191 <p>If you are previewing this file from your own computer, browsers block the index from loading. Choose your <code>bph-search-index.json</code> file to test locally:</p> 192 <input type="file" id="ss-file" accept=".json,application/json" aria-label="Cho
192ose bph-search-index.json"> 193 </div> 194 </div> 195 </div> 196 </div> 197<div class="pre-footer footer-overlay"> 198 <div class="container"> 199 200 <div class="text-block"> 201 <h3><a href="https://broadwayplayhome.com"><img src="https://broadwayplayhome.com/images/broadway-play-home-2.png" alt="Broadway Play Home" class="footer-logo"></a></h3> 202 <p>From Shakespeare to Durang, award-winners to flops, big blockbusters to Fringe transfers, tried and true classics to avant-garde new works, Broadway Play Home is your home for Broadway tickets, merchandise, videos, and all the news, reviews, and information you want about Broadway plays, people, theatres, awards, production rights and more.</p> 203 <h4>Your home for all things Broadway.</h4> 204 </div> 205 206 <div class="footer-boxes row-eq-height row"> 207 <div class="box1 col-lg-5 col-md-9 col-sm-5"> 208 <h3>Become a Broadway Insider</h3> 209 <p>Be the first to learn about Broadway news and announcements, read opening night reviews, and hear gossip and buzz! Subscribe, follow and join the conversation with other lovers of the Great White Way.</p> 210 <p class="socials-nav"> 211 <a href="https://feeds.feedburner.com/thebroadwayplayblog" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/rss.png" border="0" alt="Blog" width="35" title="The Broadway Musical Blog"></a> 212 <a href="https://twitter.com/bwayplayhome" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/twitter.png" border="0" alt="Twitter" width="35" title="Twitter"></a> 213 <a href="https://facebook.com/broadwayplayhome" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/facebook.png" border="0" alt="Facebook" width="35" title="Facebook"></a> 214 <a href="https://www.instagram.com/broadwayplayhome/" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/instagram.png" border="0" alt="Instagram" width="35" title="Instagram"></a> 215 <a href="https://broadwayplayblog.com/subscribe/" target="_blank"><img src="https://broadwayplayhome.com/images/social/2021/email.png" width="35" alt="Newsletter" title="Email Newsletter"></a> 216 </p> 217 </div> 218 <div class="box2 col-lg-5 col-md-9 col-sm-5"> 219 <div class="row"> 220 <div class="col-xs-4"> 221 <h3><img src="https://broadwayplayhome.com/images/social/2021/bmh.png" width="100%" height="auto"></h3> 222 </div> 223 <div class="col-xs-8"> 224 <h3>Access even more Broadway!</h3> 225 <p>Check out our sister site, Broadway Musical Home, for musical theatre!</p> 226 <h5><a href="https://broadwaymusicalhome.com" target="_blank"><i class="fa-solid fa-bars"></i> More</a></h5> 227 </div> 228 </div> 229 </div> 230 </div> 231 232 <div class="footer-ads row"> 233 <div class="col-md-12 mobile-hide"> 234
234<script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6415408182234082" crossorigin="anonymous"></script>
234 235 <!-- BPH 728x90 Bottom Ad Unit --> 236 <ins class="adsbygoogle" style="display:inline-block;width:728px;height:90px" data-ad-client="ca-pub-6415408182234082" data-ad-slot="6105559871"></ins> 237
237<script> 238 (adsbygoogle = window.adsbygoogle || []).push({}); 239 </script>
239 240 </div> 241 <div class="col-md-12 mobile-only"> 242
242<script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6415408182234082" crossorigin="anonymous"></script>
242 243 <!-- BPH 250x250 Bottom Ad Mobile Unit --> 244 <ins class="adsbygoogle" style="display:inline-block;width:250px;height:250px" data-ad-client="ca-pub-6415408182234082" data-ad-slot="7418641542"></ins> 245
245<script> 246 (adsbygoogle = window.adsbygoogle || []).push({}); 247 </script>
247 248 </div> 249 </div> 250 251 </div> 252 253 <div id="footer"> 254 <p class="small text-center">Owned and operated by <a href="https://valrigsbee.com/" target="_blank">Valerie Rigsbee</a> ⢠Carefully crafted by <a href="https://chiselandbrand.com/" target="_blank">Chisel and Brand LLC</a><br> 255 Copyright © 2026 Broadway Play Home. All rights reserved. <a href="https://broadwayplayhome.com/privacy.htm">Privacy</a><br><br></p> 256 </div> 257 258 </div> 259
259<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
259 260
260<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
260 261
261<script> 262 (function () { 263 'use strict'; 264 265 /* ---------- Settings you may want to change ---------- */ 266 var INDEX_URL = '/bph-search-index.json'; // where the JSON file lives on your site 267 var SITE_ORIGIN = 'https://broadwayplayhome.com'; // used for posters and links only when previewing from a file on your computer 268 var HIDE_IDS = ['404', 'index-new']; // pages that should never appear in results 269 var PEOPLE_FOLDER = 'people'; // folder names, same as in the index builder 270 var THEATRES_FOLDER = 'theatres'; 271 var FIRST_SIZE = 6; // cards shown first in each section when several sections are showing 272 var PAGE_SIZE = 24; // cards added by "Show more" 273 var DESC_LIMIT = 170; // longest description shown on a card, in characters 274 var LIST_LIMIT = 150; // longest "Plays:" list on a person's card, in characters 275 var TRACK_DELAY = 1200; // ms of quiet before a search is sent to Google Analytics 276 277 var CATS = [ 278 { key: 'show', label: 'Plays', id: 'ss-shows', one: 'play', many: 'plays', header: 'circle1' }, 279 { key: 'people', label: 'People', id: 'ss-people', one: 'person', many: 'people', header: 'circle2' }, 280 { key: 'theatres', label: 'Theatres', id: 'ss-theatres', one: 'theatre', many: 'theatres', header: 'circle3' }, 281 { key: 'other', label: 'Other', id: 'ss-other', one: 'other page', many: 'other pages', header: 'circle4' } 282 ]; 283 284 var CAT_OPTIONS = [ 285 { value: 'all', label: 'All Categories' }, { value: 'show', label: 'Plays' }, { value: 'people', label: 'People' }, 286 { value: 'theatres', label: 'Theatres' }, { value: 'other', label: 'Other' } 287 ]; 288 289 var ERAS = [{ id: 'pre1950', label: 'Before 1950', test: function (y) { return y < 1950; } }]; 290 [1950, 1960, 1970, 1980, 1990, 2000, 2010, 2020].forEach(function (d) { 291 ERAS.push({ 292 id: String(d), label: d + 's', 293 test: d === 2020 ? function (y) { return y >= 2020; } : function (y) { return y >= d && y < d + 10; } 294 }); 295 }); 296 297 /* Field weights: how much a match in each field counts toward ranking. */ 298 var W = { title: 10, prev: 8, by: 6, role: 5, shows: 4, tags: 3, people: 3, theatres: 2, desc: 2 }; 299 300 var BASE = location.protocol === 'file:' ? SITE_ORIGIN : ''; 301 var $ = function (id) { return document.getElementById(id); }; 302 var DATA = []; 303 var TAGS = []; 304 var VOCAB = null; 305 var state = { q: '', cat: 'all', from: -1, to: -1, tags: {}, limits: {} }; 306 var focusAfter = null; 307 var trackTimer = null, lastTracked = '', updateTimer = null; 308 var lastSig = null; 309 var ddCat, ddFrom, ddTo; 310 311 /* ---------- Text helpers ---------- */ 312 function norm(s) { 313 return String(s == null ? '' : s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '') 314 .replace(/&/g, ' and ').replace(/['\u2018\u2019`]/g, '').replace(/[^a-z0-9]+/g, ' ').trim(); 315 } 316 function esc(s) { 317 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { 318 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; 319 }); 320 } 321 function mk(s) { var t = norm(s); return { t: t, p: ' ' + t + ' ', w: t ? t.split(' ') : [] }; } 322 function sortKey(title) { return norm(title).replace(/^(the|a|an) /, ''); } 323 function plural(n, one, many) { return n + ' ' + (n === 1 ? one : (many || one + 's')); } 324 function abs(path) { return path && path.charAt(0) === '/' ? BASE + path : path; } 325 326 /* Edit distance where swapping two neighboring letters counts as one mistake ("sondhiem"). */ 327 function lev(a, b, max) { 328 var al = a.length, bl = b.length; 329 if (Math.abs(al - bl) > max) return max + 1; 330 var d = [], i, j; 331 for (i = 0; i <= al; i++) d[i] = [i]; 332 for (j = 1; j <= bl; j++) d[0][j] = j; 333 for (i = 1; i <= al; i++) { 334 for (j = 1; j <= bl; j++) { 335 var cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1; 336 var v = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost); 337 if (i > 1 && j > 1 && a.charCodeAt(i - 1) === b.charCodeAt(j - 2) && a.charCodeAt(i - 2) === b.charCodeAt(j - 1)) { 338 v = Math.min(v, d[i - 2][j - 2] + 1); 339 } 340 d[i][j] = v; 341 } 342 } 343 return d[al][bl]; 344 } 345 function vocab() { 346 if (!VOCAB) { 347 var seen = {}; 348 DATA.forEach(function (e) { 349 ['title', 'by', 'people', 'prev', 'role', 'shows', 'theatres', 'tags'].forEach(function (k) { 350 var f = e._f[k];
351 if (f) f.w.forEach(function (w) { if (w.length >= 4) seen[w] = 1; }); 352 }); 353 }); 354 VOCAB = Object.keys(seen); 355 } 356 return VOCAB; 357 } 358 function similar(token) { 359 var max = token.length >= 9 ? 2 : 1, out = [], V = vocab(); 360 for (var i = 0; i < V.length && out.length < 20; i++) { 361 if (V[i] !== token && lev(token, V[i], max) <= max) out.push(V[i]); 362 } 363 return out; 364 } 365 366 /* ---------- Dropdown menus ---------- */ 367 function makeDropdown(root, onChange) { 368 var btn = root.querySelector('.ss-dd-btn'), list = root.querySelector('.ss-dd-list'), label = root.querySelector('.ss-dd-label'); 369 var opts = [], value = '', active = -1, typed = '', typedTimer = null; 370 371 function render() { 372 list.innerHTML = opts.map(function (o, i) { 373 return '<li role="option" class="ss-opt" id="' + root.id + '-o' + i + '" data-i="' + i + '" aria-selected="' + (o.value === value ? 'true' : 'false') + '"' + 374 (o.disabled ? ' aria-disabled="true"' : '') + '>' + esc(o.label) + '</li>'; 375 }).join(''); 376 var cur = opts.filter(function (o) { return o.value === value; })[0]; 377 label.textContent = cur ? cur.label : ''; 378 } 379 function isOpen() { return !list.hidden; } 380 function setActive(i) { 381 var prev = list.querySelector('.ss-active'); 382 if (prev) prev.classList.remove('ss-active'); 383 active = i; 384 var el = list.children[i]; 385 if (el) { 386 el.classList.add('ss-active'); 387 list.setAttribute('aria-activedescendant', el.id); 388 if (el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); 389 } 390 } 391 function step(from, dir) { 392 for (var i = from + dir; i >= 0 && i < opts.length; i += dir) if (!opts[i].disabled) return i; 393 return from; 394 } 395 function open() { 396 if (isOpen()) return; 397 list.hidden = false; 398 root.classList.add('ss-open'); 399 btn.setAttribute('aria-expanded', 'true'); 400 var sel = -1; 401 opts.forEach(function (o, i) { if (o.value === value) sel = i; });
402 setActive(sel >= 0 ? sel : step(-1, 1)); 403 list.focus(); 404 } 405 function close(returnFocus) { 406 if (!isOpen()) return; 407 list.hidden = true; 408 root.classList.remove('ss-open'); 409 btn.setAttribute('aria-expanded', 'false'); 410 if (returnFocus) btn.focus(); 411 } 412 function choose(i) { 413 if (i < 0 || !opts[i] || opts[i].disabled) return; 414 var changed = opts[i].value !== value; 415 value = opts[i].value; 416 render(); 417 close(true); 418 if (changed) onChange(value); 419 } 420 421 btn.addEventListener('click', function () { if (isOpen()) close(true); else open(); }); 422 btn.addEventListener('keydown', function (ev) { 423 if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { ev.preventDefault(); open(); } 424 }); 425 list.addEventListener('keydown', function (ev) { 426 var k = ev.key; 427 if (k === 'ArrowDown') { ev.preventDefault(); setActive(step(active, 1)); } 428 else if (k === 'ArrowUp') { ev.preventDefault(); setActive(step(active, -1)); } 429 else if (k === 'Home') { ev.preventDefault(); setActive(step(-1, 1)); } 430 else if (k === 'End') { ev.preventDefault(); setActive(step(opts.length, -1)); } 431 else if (k === 'Enter' || k === ' ') { ev.preventDefault(); choose(active); } 432 else if (k === 'Escape') { ev.preventDefault(); close(true); } 433 else if (k === 'Tab') { close(false); } 434 else if (k.length === 1 && !ev.ctrlKey && !ev.metaKey && !ev.altKey) { 435 clearTimeout(typedTimer); 436 typed += k.toLowerCase(); 437 typedTimer = setTimeout(function () { typed = ''; }, 700); 438 for (var n = 0; n < opts.length; n++) { 439 var i = (active + 1 + n) % opts.length; 440 if (!opts[i].disabled && opts[i].label.toLowerCase().indexOf(typed) === 0) { setActive(i); break; } 441 } 442 } 443 }); 444 list.addEventListener('mousemove', function (ev) { 445 var li = ev.target.closest ? ev.target.closest('li') : null; 446 if (li && !opts[+li.getAttribute('data-i')].disabled) setActive(+li.getAttribute('data-i')); 447 }); 448 list.addEventListener('click', function (ev) { 449 var li = ev.target.closest ? ev.target.closest('li') : null; 450 if (li) choose(+li.getAttribute('data-i')); 451 }); 452 document.addEventListener('mousedown', function (ev) { if (!root.contains(ev.target)) close(false); }); 453 454 return { 455 set: function (options, val) { opts = options; value = val; render(); }, 456 close: function () { close(false); } 457 }; 458 } 459 460 /* ---------- Loading the index ---------- */ 461 function catOf(e) { 462 if (e.kind === 'show') return 'show'; 463 var s = (e.section || '').toLowerCase(); 464 if (s === PEOPLE_FOLDER) return 'people'; 465 if (s === THEATRES_FOLDER) return 'theatres'; 466 return 'other'; 467 } 468 function eraIndex(y) { 469 for (var i = 0; i < ERAS.length; i++) if (ERAS[i].test(y)) return i; 470 return -1; 471 } 472 473 function prep(e) { 474 e._cat = catOf(e); 475 var f = { title: mk(e.title), desc: mk(e.description) }; 476 if (e._cat === 'show') { 477 f.by = mk(e.by); 478 f.tags = mk((e.tags || []).map(function (t) { return t.label; }).join(' ')); 479 f.people = mk(e.people); 480 f.theatres = mk((e.theatres || []).join(' ')); 481 e._tagSet = {}; 482 (e.tags || []).forEach(function (t) { e._tagSet[t.id] = true; }); 483 e._era = e.year ? eraIndex(e.year) : -1; 484 } else { 485 f.role = mk(e.role); 486 f.prev = mk((e.previousNames || []).join(' ')); 487 } 488 e._f = f; 489 e._sort = sortKey(e.title); 490 } 491 492 function init(entries) { 493 DATA = entries.filter(function (e) { return e && HIDE_IDS.indexOf(e.id) === -1; }); 494 DATA.forEach(prep); 495 var showById = {}; 496 DATA.forEach(function (e) { if (e._cat === 'show') showById[e.id] = e; }); 497 DATA.forEach(function (e) { 498 if (e._cat !== 'people') return; 499 e._showTitles = (e.shows || []).map(function (id) { return showById[id] ? showById[id].title : null; }
499).filter(Boolean); 500 e._f.shows = mk(e._showTitles.join(' ')); 501 }); 502 var counts = {}; 503 DATA.forEach(function (e) { 504 (e.tags || []).forEach(function (t) { 505 var c = counts[t.id] = counts[t.id] || {}; 506 c[t.label] = (c[t.label] || 0) + 1; 507 }); 508 }); 509 TAGS = Object.keys(counts).map(function (id) { 510 var labels = Object.keys(counts[id]).sort(function (a, b) { return counts[id][b] - counts[id][a]; }); 511 return { id: id, label: labels[0] }; 512 }).sort(function (a, b) { return a.label.localeCompare(b.label, 'en', { sensitivity: 'base' }); }); 513 readUrl(); 514 $('ss-loading').hidden = true; 515 $('ss-error').hidden = true; 516 $('ss-app').hidden = false; 517 $('ss-q').value = state.q; 518 update(true); 519 } 520 521 /* ---------- Searching ---------- */ 522 function fieldHit(f, alts) { 523 var w = f.w, hit = 0; 524 for (var i = 0; i < w.length; i++) { 525 for (var j = 0; j < alts.length; j++) { 526 if (w[i] === alts[j]) return 2; 527 if (!hit && w[i].indexOf(alts[j]) === 0) hit = 1; 528 } 529 } 530 return hit; 531 } 532 533 function run(tokens, phrase, fuzzy) { 534 var altList = tokens.map(function (t) { 535 var alts = [t]; 536 if (fuzzy && t.length >= 4) alts = alts.concat(similar(t)); 537 return alts; 538 }); 539 var out = []; 540 for (var n = 0; n < DATA.length; n++) { 541 var e = DATA[n], f = e._f, score = 0, ok = true, info = []; 542 for (var i = 0; i < tokens.length && ok; i++) { 543 var best = 0, hits = []; 544 for (var k in W) { 545 if (!f[k]) continue; 546 var h = fieldHit(f[k], altList[i]); 547 if (h) { 548 hits.push(k); 549 var sc = W[k] * (h === 2 ? 1.5 : 1); 550 if (sc > best) best = sc; 551 } 552 } 553 if (!best) ok = false; 554 score += best; 555 info.push({ alts: altList[i], fields: hits }); 556 } 557 if (!ok) continue; 558 if (f.title.t === phrase) score += 60; 559 else if (f.title.t.indexOf(phrase) === 0) score += 25; 560 else if (f.title.p.indexOf(' ' + phrase + ' ') !== -1) score
560+= 12; 561 if (tokens.length > 1) { 562 if (f.by && f.by.p.indexOf(' ' + phrase + ' ') !== -1) score += 6; 563 if (f.people && f.people.p.indexOf(' ' + phrase + ' ') !== -1) score += 5; 564 if (f.prev && f.prev.p.indexOf(' ' + phrase + ' ') !== -1) score += 8; 565 } 566 out.push({ e: e, score: score, info: info }); 567 } 568 return out; 569 } 570 571 function search(q) { 572 var tokens = norm(q).split(' ').filter(Boolean); 573 if (!tokens.length) return null; 574 var phrase = tokens.join(' '); 575 var res = run(tokens, phrase, false); 576 if (res.length < 3 && tokens.some(function (t) { return t.length >= 4; })) { 577 var fuzzy = run(tokens, phrase, true); 578 if (fuzzy.length > res.length) res = fuzzy; 579 } 580 return res; 581 } 582 583 /* ---------- Filters (Shows only) ---------- */ 584 function selected(map) { return Object.keys(map).filter(function (k) { return map[k]; }); } 585 function filterCount() { return selected(state.tags).length + (state.from >= 0 || state.to >= 0 ? 1 : 0); } 586 function passEra(e) { 587 if (state.from < 0 && state.to < 0) return true; 588 if (e._era < 0) return false; 589 var lo = state.from < 0 ? 0 : state.from, hi = state.to < 0 ? ERAS.length - 1 : state.to; 590 return e._era >= lo && e._era <= hi; 591 } 592 function passTags(e) { 593 return selected(state.tags).every(function (id) { return e._tagSet[id]; }); 594 } 595 596 /* ---------- Rendering helpers ---------- */ 597 function makeRx(alts) { 598 var flat = []; 599 alts.forEach(function (a) { a.forEach(function (x) { if (x && flat.indexOf(x) === -1) flat.push(x); }); }); 600 if (!flat.length) return null; 601 flat.sort(function (a, b) { return b.length - a.length; }); 602 try { 603 return new RegExp('(^|[^\\p{L}\\p{N}])(' + flat.map(function (x) { return x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('|') + ')', 'giu'); 604 } catch (err) { return null; } 605 } 606 /* Shortens long text to a limit at a word boundary. If the search term sits past the limit, the excerpt starts before it. */ 607 function excerpt(text, rx, limit) { 608 text = String(text == null ? '' : text); 609 if (text.length <= limit) return text; 610 var start = 0; 611 if (rx) { 612 rx.lastIndex = 0; 613 var m = rx.exec(text); 614 if (m) { 615 var at = m.index + m[1].length; 616 if (at + m[2].length > limit - 10) { 617 start = Math.max(0, at - Math.floor(limit / 3)); 618 var sp = text.indexOf(' ', start); 619 if (sp !== -1 && sp < at) start = sp + 1; 620 } 621 } 622 } 623 var end = start + limit; 624 var slice = text.slice(start, end); 625 if (end < text.length) { 626 var cut = slice.lastIndexOf(' '); 627 if (cut > limit * 0.6) slice = slice.slice(0, cut); 628 slice = slice.replace(/[\s,;:.\-\u2013\u2014]+$/, '') + '\u2026'; 629 } 630 return (start > 0 ? '\u2026' : '') + slice; 631 } 632 633 /* Search words the title does not already show. The description excerpt is built around these. */ 634 function focusRx(m) { 635 if (!m.info) return null; 636 var alts = m.info.filter(function (t) { return t.fields.indexOf('title') === -1; }).map(function (t) { return t.alts; }); 637 return alts.length ? makeRx(alts) : null; 638 } 639 640 function hl(text, rx) { 641 text = text == null ? '' : String(text); 642 if (!rx || !text) return esc(text); 643 var out = '', last = 0, m; 644 rx.lastIndex = 0; 645 while ((m = rx.exec(text))) { 646 var start = m.index + m[1].length, end = start + m[2].length; 647 if (end === start) { rx.lastIndex++; continue; } 648 out += esc(text.slice(last, start)) + '<mark>' + esc(text.slice(start, end)) + '</mark>'; 649 last = end; 650 } 651 return out + esc(text.slice(last)); 652 } 653 654 function pickValues(list, altSets, max) { 655 var seen = {}; 656 return list.map(function (s) { 657 var w = norm(s).split(' '); 658 var sc = 0;
659 altSets.forEach(function (alts) { 660 if (w.some(function (x) { return alts.some(function (a) { return x.indexOf(a) === 0; }); })) sc++; 661 }); 662 return { s: s.trim(), sc: sc }; 663 }).filter(function (x) { 664 if (!x.sc || !x.s || seen[x.s]) return false; 665 seen[x.s] = true; 666 return true; 667 }).sort(function (a, b) { return b.sc - a.sc; }).slice(0, max).map(function (x) { return x.s; }); 668 } 669 670 /* The "Matched:" line explains hits the card would not otherwise show. */ 671 function matchedLine(m, rx) { 672 if (!m.info || m.e._cat !== 'show') return ''; 673 var e = m.e; 674 var groups = { people: [], theatres: [], tags: [] }; 675 var order = ['people', 'theatres', 'tags']; 676 m.info.forEach(function (ti) { 677 if (ti.fields.indexOf('title') !== -1) return; 678 for (var i = 0; i < order.length; i++) { 679 if (ti.fields.indexOf(order[i]) !== -1) { groups[order[i]].push(ti.alts); return; } 680 } 681 }); 682 var parts = []; 683 var labels = { people: 'Cast or creative team', theatres: 'Theatre', tags: 'Play Type' }; 684 order.forEach(function (g) { 685 if (!groups[g].length) return; 686 var list = g === 'people' ? (e.people || '').split(/\s*,\s*/) 687 : g === 'theatres' ? (e.theatres || []) 688 : (e.tags || []).map(function (t) { return t.label; }); 689 var vals = pickValues(list, groups[g], g === 'people' ? 3 : 2); 690 if (vals.length) parts.push(labels[g] + ': ' + vals.map(function (v) { return hl(v, rx); }).join(', ')); 691 }); 692 return parts.length ? '<p class="ss-match"><strong>Matched:</strong> ' + parts.join('; ') + '</p>' : ''; 693 } 694 695 function initials(t) { 696 var w = String(t || '').replace(/[^A-Za-z0-9 ]/g, '').split(/\s+/).filter(Boolean); 697 return ((w[0] || '?').charAt(0) + (w.length > 1 ? w[w.length - 1].charAt(0) : '')).toUpperCase(); 698 } 699 function thumb(e, square) { 700 return e.image 701 ? '<img class="ss-thumb' + (square ? ' ss-thumb-sq' : '') + '" src="' + esc(abs(e.image)) + '" alt="" loading="lazy" width="96" height="' + (square ? 96 : 144) + '" data-ph="1" data-t="' + esc(initials(e.title)) + '"' + (square ? ' data-sq="1"' : '') + '>' 702 : '<span class="ss-ph' + (square ? ' ss-ph-sq' : '') + '">' + esc(initials(e.title)) + '</span>'; 703 } 704 705 function showCard(m, rx) { 706 var e = m.e; 707 return '<article class="ss-card ss-media"><div class="ss-thumb-wrap">' + thumb(e) + '</div><div class="ss-body">' + 708 '<h3 class="ss-title"><a href="' + esc(abs(e.url)) + '">' + hl(e.title, rx) + '</a></h3>' + 709 (e.by ? '<p class="ss-by">by ' + hl(e.by, rx) + '</p>' : '') + 710 (e.year ? '<p class="ss-year">' + esc(e.year) + '</p>' : '') + 711 (e.description ? '<p class="ss-desc">' + hl(excerpt(e.description, focusRx(m), DESC_LIMIT), rx) + '</p>' : '') + 712 matchedLine(m, rx) + '</div></article>'; 713 } 714 /* "Plays:" on a person's card. Titles that match the search come first; the list ends with an ellipsis if it is cut. */ 715 function showsLine(e, rx) { 716 var titles = e._showTitles; 717 if (!titles || !titles.length) return ''; 718 var ordered = titles; 719 if (rx) { 720 var hit = [], rest = [];
721 titles.forEach(function (t) { rx.lastIndex = 0; (rx.test(t) ? hit : rest).push(t); }); 722 ordered = hit.concat(rest); 723 } 724 var out = '', used = 0; 725 for (var i = 0; i < ordered.length; i++) { 726 var add = (i ? ', ' : '') + ordered[i]; 727 if (i > 0 && out.length + add.length > LIST_LIMIT) break; 728 out += add; used++; 729 } 730 return '<p class="ss-desc"><strong>Plays:</strong> ' + hl(out, rx) + (used < ordered.length ? '\u2026' : '') + '</p>'; 731 } 732 function personCard(m, rx) { 733 var e = m.e; 734 return '<article class="ss-card ss-media"><div class="ss-thumb-wrap">' + thumb(e) + '</div><div class="ss-body">' + 735 '<h3 class="ss-title"><a href="' + esc(abs(e.url)) + '">' + hl(e.title, rx) + '</a></h3>' + 736 (e.role ? '<p class="ss-by">' + hl(e.role, rx) + '</p>' : '') + showsLine(e, rx) + '</div></article>'; 737 } 738 function theatreCard(m, rx) { 739 var e = m.e; 740 var text = '<h3 class="ss-title"><a href="' + esc(abs(e.url)) + '">' + hl(e.title, rx) + '</a></h3>' + 741 (e.address && e.address.length ? '<p class="ss-by">' + e.address.map(esc).join('<br>') + '</p>' : '') + 742 (e.previousNames && e.previousNames.length ? '<p class="ss-desc"><b>Previous Names:</b> ' + hl(e.previousNames.join(', '), rx) + '</p>' : ''); 743 if (!e.image) return '<article class="ss-card">' + text + '</article>'; 744 return '<article class="ss-card ss-media"><div class="ss-thumb-wrap">' + thumb(e, true) + '</div><div class="ss-body">' + text + '</div></article>'; 745 } 746 function otherCard(m, rx) { 747 var e = m.e; 748 return '<article class="ss-card"><h3 class="ss-title"><a href="' + esc(abs(e.url)) + '">' + hl(e.title, rx) + '</a></h3>' + 749 (e.description ? '<p class="ss-desc">' + hl(excerpt(e.description, focusRx(m), DESC_LIMIT), rx) + '</p>' : '') + '</article>'; 750 } 751 var CARD = { show: showCard, people: personCard, theatres: theatreCard, other: otherCard }; 752 753 function sectionHtml(cat, items, rx) { 754 var limit = state.limits[cat.key]; 755 var shown = items.slice(0, limit); 756 var rest = items.length - shown.length; 757 var html = '<section class="ss-section" id="' + cat.id + '" aria-labelledby="' + cat.id + '-h">' + 758 '<div class="page-header ' + cat.header + '"><div class="box"><h2 id="' + cat.id + '-h">' + esc(cat.label) + 759 '<span class="ss-count">' + items.length + '</span></h2></div></div>' + 760 '<div class="well"><div class="ss-list">' + 761 shown.map(function (m) { return CARD[cat.key](m, rx); }).join('') + '</div>'; 762 if (rest > 0) { 763 html += '<button type="button" class="ss-more" data-cat="' + cat.key + '">Show more ' + esc(cat.many) + 764 ' (' + rest + ' remaining)</button>'; 765 } 766 return html + '</div></section>'; 767 } 768 769 function chip(label, count, pressed, disabled, attrs) { 770 return '<li><button type="button" class="ss-chip" aria-pressed="' + (pressed ? 'true' : 'false') + '"' + 771 (disabled ? ' disabled' : '') + ' ' + attrs + '>' + esc(label) + ' <span class="ss-n">' + count + '</span></button></li>'; 772 } 773 774 /* ---------- Main update ---------- */ 775 function update(fresh) { 776 var q = $('ss-q').value; 777 state.q = q; 778 var hits = search(q); 779 var hasQ = hits !== null; 780 if (!hasQ) hits = DATA.map(function (e) { return { e: e, score: 0, info: null }; }); 781 782 var byCat = { show: [], people: [], theatres: [], other: [] }; 783 hits.forEach(function (m) { byCat[m.e._cat].push(m); }); 784 785 var showMode = state.cat === 'show'; 786 var filtersOn = showMode && filterCount() > 0; 787 788 /* Show filters exist only in Shows mode. Each option shows how many shows it would leave. */ 789 if (showMode) { 790 var showHits = byCat.show; 791 var eraBase = showHits.filter(function (m) { return passTags(m.e); }); 792 var afterEra = showHits.filter(function (m) { return passEra(m.e); }); 793 var current = afterEra.filter(function (m) { return passTags(m.e); }); 794 var lo0 = state.from < 0 ? 0 : state.from, hi0 = state.to < 0 ? ERA
794S.length - 1 : state.to; 795 var inRange = function (lo, hi) { return eraBase.filter(function (m) { return m.e._era >= lo && m.e._era <= hi; }).length; }; 796 var eraOptions = function (fromSide) { 797 return [{ value: '', label: 'Any decade' }].concat(ERAS.map(function (er, i) { 798 var n = fromSide ? inRange(i, Math.max(i, hi0)) : inRange(Math.min(i, lo0), i); 799 var on = (fromSide ? state.from : state.to) === i; 800 return { value: String(i), label: er.label, disabled: !on && n === 0 }; 801 })); 802 }; 803 ddFrom.set(eraOptions(true), state.from < 0 ? '' : String(state.from)); 804 ddTo.set(eraOptions(false), state.to < 0 ? '' : String(state.to)); 805 $('ss-type').innerHTML = TAGS.map(function (t) { 806 var on = !!state.tags[t.id]; 807 var n = on ? current.length : current.filter(function (m) { return m.e._tagSet[t.id]; }).length; 808 return chip(t.label, n, on, !on && n === 0, 'data-type="' + esc(t.id) + '"'); 809 }).join(''); 810 byCat.show = current; 811 } 812 813 CATS.forEach(function (c) { 814 byCat[c.key].sort(function (a, b) { 815 if (hasQ && a.score !== b.score) return b.score - a.score; 816 return a.e._sort.localeCompare(b.e._sort, 'en', { numeric: true, sensitivity: 'base' }); 817 }); 818 }); 819 820 /* Section sizes reset whenever the search, category, or filters change. */ 821 var sig = q + '|' + state.cat + '|' + state.from + '|' + state.to + '|' + selected(state.tags).join(','); 822 if (fresh || lastSig !== sig) { 823 lastSig = sig; 824 var size = state.cat === 'all' ? FIRST_SIZE : PAGE_SIZE; 825 state.limits = { show: size, people: size, theatres: size, other: size }; 826 } 827 828 var cats = CATS.filter(function (c) { return state.cat === 'all' ? byCat[c.key].length > 0 : c.key === state.cat; }); 829 var total = cats.reduce(function (n, c) { return n + byCat[c.key].length; }, 0); 830 831 var rx = null; 832 if (hasQ) { 833 var first = hits.filter(function (m) { return m.info; })[0]; 834 rx = makeRx(first ? first.info.map(function (i) { return i.alts; }) : norm(q).split(' ').map(function (t) { return [t]; })); 835 } 836 837 var html = ''; 838 if (total === 0) { 839 html = emptyHtml(hasQ, filtersOn, q); 840 } else { 841 cats.forEach(function (c) { html += sectionHtml(c, byCat[c.key], rx); }); 842 } 843 $('ss-results').innerHTML = html; 844 845 /* Screen readers get the summary. Sighted visitors see the section counts instead. */ 846 $('ss-status').textContent = total === 0 ? (hasQ ? 'No results for ' + q.trim() + '.' : 'No plays match these filters.') 847 : cats.map(function (c) { return plural(byCat[c.key].length, c.one, c.many); }).join(', ') + (hasQ ? ' for ' + q.trim() : '') + '.'; 848 849 /* Controls */ 850 $('ss-panel').hidden = !showMode; 851 $('ss-toggle').hidden = !showMode; 852 var nOn = filterCount(); 853 $('ss-toggle-n').textContent = nOn ? '(' + nOn + ' on)' : ''; 854 $('ss-clear-filters').hidden = !filtersOn; 855 $('ss-clear').hidden = !q; 856 ddCat.set(CAT_OPTIONS, state.cat); 857 858 writeUrl(); 859 if (hasQ) scheduleTrack(q); 860 if (focusAfter) { 861 var list = document.querySelector('#' + focusAfter.id + ' .ss-list'); 862 var target = list && list.children[focusAfter.index]; 863 if (target) { var a = target.querySelector('a'); if (a) a.focus(); } 864 focusAfter = null; 865 } 866 } 867 868 function emptyHtml(hasQ, filtersOn, q) { 869 if (filtersOn) { 870 return '<div class="ss-empty"><h2>No plays match these filters</h2><p>' + 871 (hasQ ? 'Try removing a filter, or search for something else.' : 'Try removing one of the filters.') + '</p></div>'; 872 } 873 var where = state.cat === 'all' ? '' : ' in ' + CATS.filter(function (c) { return c.key === state.cat; })[0].label; 874 return '<div class="ss-empty"><h2>Nothing found for "' + esc(q.trim()) + '"' + esc(where) + '</h2>' + 875 '<p>Check the spelling or try fewer words. You can search by play title, writer, cast member, or theatre' + 876 (state.cat === 'all' ? '.' : ', or choose All Categories.') + '</p></div>'; 877 } 878 879 /* ---------- URL and analytics ---------- */ 880 function readUrl() { 881 var p = new URLSearchParams(location.search); 882 state.q = p.get('q') || ''; 883 var c = p.get('cat'); 884 state.cat = ['all', 'show', 'people', 'theatres', 'other'].indexOf(c) !== -1 ? c : 'all'; 885 var idOf = function (v) { for (var i = 0; i < ERAS.length; i++) if (ERAS[i].id === v) return i; return -1; }; 886 state.from = idOf(p.get('from')); state.to = idOf(p.get('to')); 887 state.tags = {}; 888 (p.get('type') || '').split(',').forEach(function (x) { if (TAGS.some(function (t) { return t.id === x; })) state.tags[x] = true; }); 889 if (!c && filterCount() > 0) state.cat = 'show'; 890 } 891 function writeUrl() { 892 var p = new URLSearchParams(); 893 if (state.q.trim()) p.set('q', state.q.trim()); 894 if (state.cat !== 'all') p.set('cat', state.cat); 895 if (state.cat === 'show') { 896 if (state.from >= 0) p.set('from', ERAS[state.from].id); 897 if (state.to >= 0) p.set('to', ERAS[state.to].id); 898 var t = selected(state.tags); 899 if (t.length) p.set('type', t.join(',')); 900 } 901 var qs = p.toString(); 902 try { history.replaceState(null, '', location.pathname + (qs ? '?' + qs : '')); } catch (err) { /* previewing from a file */ } 903 } 904 function scheduleTrack(q) { 905 clearTimeout(trackTimer); 906 var term = q.trim(); 907 if (term.length < 2 || term === lastTracked) return; 908 trackTimer = setTimeout(function () { 909 lastTracked = term; 910 if (typeof gtag === 'function') gtag('event', 'search', { search_term: term }); 911 }, TRACK_DELAY); 912 } 913 914 /* ---------- Events ---------- */ 915 function queueUpdate() { 916 clearTimeout(updateTimer); 917 updateTimer = setTimeout(function () { update(false); }, 70); 918 } 919 $('ss-q').addEventListener('input', queueUpdate); 920 $('ss-form').addEventListener('submit', function (ev) { ev.preventDefault(); clearTimeout(updateTimer); update(false); }); 921 $('ss-clear').addEventListener('click', function () { $('ss-q').value = ''; update(false); $('ss-q').focus(); }); 922 ddCat = makeDropdown($('ss-cat-dd'), function (v) { state.cat = v; update(false); }); 923 ddCat.set(CAT_OPTIONS, state.cat); 924 $('ss-toggle').addEventListener('click', function () { 925 var open = !$('ss-panel').classList.contains('ss-open');
926 $('ss-panel').classList.toggle('ss-open', open); 927 $('ss-toggle').setAttribute('aria-expanded', open ? 'true' : 'false'); 928 }); 929 ddFrom = makeDropdown($('ss-from-dd'), function (v) { 930 state.from = v === '' ? -1 : +v; 931 if (state.from >= 0 && state.to >= 0 && state.to < state.from) state.to = state.from; 932 update(false); 933 }); 934 ddTo = makeDropdown($('ss-to-dd'), function (v) { 935 state.to = v === '' ? -1 : +v; 936 if (state.to >= 0 && state.from >= 0 && state.from > state.to) state.from = state.to; 937 update(false); 938 }); 939 $('ss-clear-filters').addEventListener('click', function () { state.from = -1; state.to = -1; state.tags = {}; update(false); }); 940 941 document.addEventListener('click', function (ev) { 942 var b = ev.target.closest ? ev.target.closest('button') : null; 943 if (!b) return; 944 if (b.hasAttribute('data-type')) { 945 var tid = b.getAttribute('data-type'); 946 state.tags[tid] = !state.tags[tid]; 947 update(false); 948 var again = document.querySelector('[data-type="' + tid + '"]'); if (again) again.focus(); 949 } else if (b.classList.contains('ss-more')) { 950 var key = b.getAttribute('data-cat'); 951 var cat = CATS.filter(function (c) { return c.key === key; })[0]; 952 focusAfter = { id: cat.id, index: state.limits[key] }; 953 state.limits[key] += PAGE_SIZE; 954 update(false); 955 } 956 }); 957
958 /* Broken poster or headshot: swap in initials. */ 959 document.addEventListener('error', function (ev) { 960 var img = ev.target; 961 if (!img || img.tagName !== 'IMG' || !img.hasAttribute('data-ph')) return; 962 var ph = document.createElement('span'); 963 ph.className = 'ss-ph' + (img.hasAttribute('data-sq') ? ' ss-ph-sq' : ''); 964 ph.textContent = img.getAttribute('data-t') || '?'; 965 img.replaceWith(ph); 966 }, true); 967 968 /* ---------- Start ---------- */ 969 function failed() { 970 $('ss-loading').hidden = true; 971 $('ss-error').hidden = false; 972 $('ss-local').hidden = location.protocol !== 'file:'; 973 } 974 $('ss-file').addEventListener('change', function (ev) { 975 var f = ev.target.files[0]; 976 if (!f) return; 977 f.text().then(function (t) { init(JSON.parse(t)); }).catch(failed); 978 }); 979 980 fetch(INDEX_URL, { cache: 'no-cache' }) 981 .then(function (r) { if (!r.ok) throw new Error('bad status'); return r.json(); }) 982 .then(init) 983 .catch(failed); 984 985 if (window.matchMedia && window.matchMedia('(pointer: fine)').matches) $('ss-q').focus(); 986 })(); 987 </script>
987 988 </body> 989</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.