PageSourceSearch

https://broadwayplayhome.com/search.htm

html broadwayplayhome.com collected 2026-10-08 12:40:44 UTC 51,165 bytes, 989 lines download raw bytes

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 &amp; 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 &amp; 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>&times;</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 { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[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.