nixamp 0.27.3 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/backtoschool/dist/assets/ar-D-Ld5vpy.js +1 -0
  2. package/backtoschool/dist/assets/cs-_3DVOugI.js +1 -0
  3. package/backtoschool/dist/assets/da-tGj_pE2V.js +1 -0
  4. package/backtoschool/dist/assets/de-vZHKX4Ec.js +1 -0
  5. package/backtoschool/dist/assets/es-DXYBTMqx.js +1 -0
  6. package/backtoschool/dist/assets/fi-3Awdi4xT.js +1 -0
  7. package/backtoschool/dist/assets/fr-ChJB95FS.js +1 -0
  8. package/backtoschool/dist/assets/hi-OutMojZO.js +1 -0
  9. package/backtoschool/dist/assets/hu-fhESdEDW.js +1 -0
  10. package/backtoschool/dist/assets/id-DEiYuHM3.js +1 -0
  11. package/backtoschool/dist/assets/{index-Bot4FEXQ.css → index-D06NfY1L.css} +1 -1
  12. package/backtoschool/dist/assets/index-DBZjqzOv.js +93 -0
  13. package/backtoschool/dist/assets/it-BWr9AXgr.js +1 -0
  14. package/backtoschool/dist/assets/nl-CRyTjXF_.js +1 -0
  15. package/backtoschool/dist/assets/ru-B9x7d0z_.js +1 -0
  16. package/backtoschool/dist/assets/sv-Bj-BJsv_.js +1 -0
  17. package/backtoschool/dist/assets/uk-C1-8NwOQ.js +1 -0
  18. package/backtoschool/dist/assets/vi-z6CDEBrS.js +1 -0
  19. package/backtoschool/dist/assets/zh-DHLUyAFs.js +1 -0
  20. package/backtoschool/dist/index.html +44 -43
  21. package/dist/admin.js +5 -4
  22. package/dist/i18n.d.ts +26 -0
  23. package/dist/i18n.js +81 -0
  24. package/dist/locales/ar.d.ts +204 -0
  25. package/dist/locales/ar.js +203 -0
  26. package/dist/locales/cs.d.ts +204 -0
  27. package/dist/locales/cs.js +203 -0
  28. package/dist/locales/da.d.ts +204 -0
  29. package/dist/locales/da.js +203 -0
  30. package/dist/locales/de.d.ts +204 -0
  31. package/dist/locales/de.js +203 -0
  32. package/dist/locales/en.d.ts +204 -0
  33. package/dist/locales/en.js +204 -0
  34. package/dist/locales/es.d.ts +204 -0
  35. package/dist/locales/es.js +203 -0
  36. package/dist/locales/fi.d.ts +204 -0
  37. package/dist/locales/fi.js +203 -0
  38. package/dist/locales/fr.d.ts +204 -0
  39. package/dist/locales/fr.js +203 -0
  40. package/dist/locales/hi.d.ts +204 -0
  41. package/dist/locales/hi.js +203 -0
  42. package/dist/locales/hu.d.ts +204 -0
  43. package/dist/locales/hu.js +203 -0
  44. package/dist/locales/id.d.ts +204 -0
  45. package/dist/locales/id.js +203 -0
  46. package/dist/locales/it.d.ts +204 -0
  47. package/dist/locales/it.js +203 -0
  48. package/dist/locales/nl.d.ts +204 -0
  49. package/dist/locales/nl.js +203 -0
  50. package/dist/locales/ru.d.ts +204 -0
  51. package/dist/locales/ru.js +203 -0
  52. package/dist/locales/sv.d.ts +204 -0
  53. package/dist/locales/sv.js +203 -0
  54. package/dist/locales/uk.d.ts +204 -0
  55. package/dist/locales/uk.js +203 -0
  56. package/dist/locales/vi.d.ts +204 -0
  57. package/dist/locales/vi.js +203 -0
  58. package/dist/locales/zh.d.ts +204 -0
  59. package/dist/locales/zh.js +203 -0
  60. package/dist/main.js +26 -12
  61. package/dist/ui-aliases.d.ts +4 -0
  62. package/dist/ui-aliases.js +41 -0
  63. package/dist/ui-languages.d.ts +62 -0
  64. package/dist/ui-languages.js +32 -0
  65. package/package.json +1 -1
  66. package/src/admin.ts +5 -4
  67. package/src/i18n.ts +74 -0
  68. package/src/locales/ar.ts +204 -0
  69. package/src/locales/cs.ts +204 -0
  70. package/src/locales/da.ts +204 -0
  71. package/src/locales/de.ts +204 -0
  72. package/src/locales/en.ts +204 -0
  73. package/src/locales/es.ts +204 -0
  74. package/src/locales/fi.ts +204 -0
  75. package/src/locales/fr.ts +204 -0
  76. package/src/locales/hi.ts +204 -0
  77. package/src/locales/hu.ts +204 -0
  78. package/src/locales/id.ts +204 -0
  79. package/src/locales/it.ts +204 -0
  80. package/src/locales/nl.ts +204 -0
  81. package/src/locales/ru.ts +204 -0
  82. package/src/locales/sv.ts +204 -0
  83. package/src/locales/uk.ts +204 -0
  84. package/src/locales/vi.ts +204 -0
  85. package/src/locales/zh.ts +204 -0
  86. package/src/main.ts +25 -12
  87. package/src/ui-aliases.ts +42 -0
  88. package/src/ui-languages.ts +31 -0
  89. package/web/dist/assets/ar-D-Ld5vpy.js +1 -0
  90. package/web/dist/assets/cs-_3DVOugI.js +1 -0
  91. package/web/dist/assets/da-tGj_pE2V.js +1 -0
  92. package/web/dist/assets/de-vZHKX4Ec.js +1 -0
  93. package/web/dist/assets/es-DXYBTMqx.js +1 -0
  94. package/web/dist/assets/fi-3Awdi4xT.js +1 -0
  95. package/web/dist/assets/fr-ChJB95FS.js +1 -0
  96. package/web/dist/assets/hi-OutMojZO.js +1 -0
  97. package/web/dist/assets/{hls-3VKVEQE3-_BiJh76x.js → hls-3VKVEQE3-DQXeuMAN.js} +1 -1
  98. package/web/dist/assets/hu-fhESdEDW.js +1 -0
  99. package/web/dist/assets/id-DEiYuHM3.js +1 -0
  100. package/web/dist/assets/index-BKyAT0fX.css +1 -0
  101. package/web/dist/assets/index-HDrZHpqb.js +3 -0
  102. package/web/dist/assets/it-BWr9AXgr.js +1 -0
  103. package/web/dist/assets/{mpegts-CV0glck7.js → mpegts-BRcpX4rU.js} +1 -1
  104. package/web/dist/assets/{mpegts-LO6RVLD6-iuTMg-O6.js → mpegts-LO6RVLD6-tqmB8BfI.js} +1 -1
  105. package/web/dist/assets/nl-CRyTjXF_.js +1 -0
  106. package/web/dist/assets/ru-B9x7d0z_.js +1 -0
  107. package/web/dist/assets/sv-Bj-BJsv_.js +1 -0
  108. package/web/dist/assets/uk-C1-8NwOQ.js +1 -0
  109. package/web/dist/assets/vi-z6CDEBrS.js +1 -0
  110. package/web/dist/assets/zh-DHLUyAFs.js +1 -0
  111. package/web/dist/index.html +144 -150
  112. package/web/dist/sw.js +23 -6
  113. package/backtoschool/dist/assets/index-Cjai7LgT.js +0 -93
  114. package/web/dist/assets/index-BgLxkXKe.js +0 -3
  115. package/web/dist/assets/index-D34dTY18.css +0 -1
@@ -17,22 +17,22 @@
17
17
  <meta property="og:title" content="nixamp: broadcast live radio, TV and film from your own machine" />
18
18
  <meta property="og:description" content="Launch your own live radio, TV and film network from your machine. Listen free, go live in one command, get paid with x402." />
19
19
  <meta property="og:type" content="website" />
20
- <script type="module" crossorigin src="/assets/index-BgLxkXKe.js"></script>
21
- <link rel="stylesheet" crossorigin href="/assets/index-D34dTY18.css">
20
+ <script type="module" crossorigin src="/assets/index-HDrZHpqb.js"></script>
21
+ <link rel="stylesheet" crossorigin href="/assets/index-BKyAT0fX.css">
22
22
  </head>
23
23
  <body>
24
- <nav class="skip-links" aria-label="Skip to a section">
25
- <a href="#transport">Skip to playback controls</a>
26
- <a href="#playlist-panel">Skip to playlist</a>
27
- <a href="#transcript-panel">Skip to transcript and translated audio</a>
24
+ <nav class="skip-links" aria-label="Skip to a section" data-i18n-aria-label="Skip to a section">
25
+ <a href="#transport" data-i18n="Skip to playback controls">Skip to playback controls</a>
26
+ <a href="#playlist-panel" data-i18n="Skip to playlist">Skip to playlist</a>
27
+ <a href="#transcript-panel" data-i18n="Skip to transcript and translated audio">Skip to transcript and translated audio</a>
28
28
  </nav>
29
29
  <div id="sr-status" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>
30
- <main id="app" aria-label="Nixamp player">
31
- <h1 class="sr-only">Nixamp audio and video player</h1>
30
+ <main id="app" aria-label="Nixamp player" data-i18n-aria-label="Nixamp player">
31
+ <h1 class="sr-only" data-i18n="Nixamp audio and video player">Nixamp audio and video player</h1>
32
32
  <header class="bar">
33
33
  <span class="brand"><img class="brand-mark" src="/logo.png" alt="" width="20" height="20" decoding="async" />NIXAMP</span>
34
- <span id="status" class="status" data-playing="false">■ STOPPED</span>
35
- <span id="source" class="chip">no source</span>
34
+ <span id="status" class="status" data-playing="false" data-i18n="■ STOPPED">■ STOPPED</span>
35
+ <span id="source" class="chip" data-i18n="no source">no source</span>
36
36
  <!-- The eye or the gear for the server you are on: step down to
37
37
  viewing, or up to driving, without finding it in a list again. -->
38
38
  <span id="way-in-here" class="way-in-here" hidden></span>
@@ -41,15 +41,16 @@
41
41
  <!-- The panels themselves: which are on screen and in what order.
42
42
  Winamp's windows were dragged about and snapped into place; the
43
43
  grip on each panel does that here, and this lists them all. -->
44
- <button id="panels-toggle" type="button" class="ghost" aria-controls="panels-panel" aria-expanded="false" title="Panels: show, hide, and reorder the panels on this page">Panels</button>
45
- <button id="install" type="button" class="ghost" hidden>Install</button>
44
+ <button id="panels-toggle" type="button" class="ghost" popovertarget="panels-panel" aria-controls="panels-panel" aria-expanded="false" title="Panels: show, hide, and reorder the panels on this page" data-i18n="Panels">Panels</button>
45
+ <label class="ui-language"><span data-i18n="Interface language">Interface language</span> <select data-ui-language class="transcript-language" aria-label="Interface language" data-i18n-aria-label="Interface language"></select></label>
46
+ <button id="install" type="button" class="ghost" hidden data-i18n="Install">Install</button>
46
47
  </header>
47
48
 
48
49
  <!-- The pitch, for somebody who has never seen this before. In the shell
49
50
  so a crawler and a link preview read it; shown by app.ts only where
50
51
  accounts live (nixamp.com), only signed out, and only until hidden.
51
52
  On your own server you already know what this is. -->
52
- <section class="panel welcome player-only" data-title="What this is" id="welcome" hidden>
53
+ <section class="panel welcome player-only" data-title="What this is" data-i18n-data-title="What this is" id="welcome" hidden>
53
54
  <h2 class="welcome-title">Launch your own live radio, TV and film network in one command.</h2>
54
55
  <p class="welcome-lead">
55
56
  nixamp is the open source streaming platform that turns a folder on your
@@ -60,12 +61,12 @@
60
61
  </p>
61
62
  <div class="welcome-grid">
62
63
  <div>
63
- <h2>Listen</h2>
64
+ <h2 data-i18n="Listen">Listen</h2>
64
65
  <p>See what is on the air right now and press play. No account needed. Every
65
66
  live stream has a room code you can dial from any phone.</p>
66
67
  </div>
67
68
  <div>
68
- <h2>Publish</h2>
69
+ <h2 data-i18n="Publish">Publish</h2>
69
70
  <p><code>nixamp serve ~/Music</code> and you are live with a link, a room code
70
71
  and a phone number. Your recordings, a lecture, a podcast, or a link you paste.</p>
71
72
  </div>
@@ -76,15 +77,15 @@
76
77
  </div>
77
78
  </div>
78
79
  <div class="picker welcome-actions">
79
- <button id="welcome-create" type="button" class="button primary">Create a free account</button>
80
- <button id="welcome-browse" type="button" class="button">See what is live</button>
80
+ <button id="welcome-create" type="button" class="button primary" data-i18n="Create a free account">Create a free account</button>
81
+ <button id="welcome-browse" type="button" class="button" data-i18n="See what is live">See what is live</button>
81
82
  <code class="welcome-install">curl -fsSL https://nixamp.com/install.sh | sh</code>
82
- <button id="welcome-hide" type="button" class="ghost" title="Hide this, on this device">Hide this</button>
83
+ <button id="welcome-hide" type="button" class="ghost" title="Hide this, on this device" data-i18n="Hide this">Hide this</button>
83
84
  </div>
84
85
  </section>
85
86
 
86
- <section class="panel player-only classroom-player" data-title="Now Playing" aria-label="Now Playing">
87
- <h2 class="panel-heading">Now Playing</h2>
87
+ <section class="panel player-only classroom-player" data-title="Now Playing" data-i18n-data-title="Now Playing" aria-label="Now Playing" data-i18n-aria-label="Now Playing">
88
+ <h2 class="panel-heading" data-i18n="Now Playing">Now Playing</h2>
88
89
  <span class="panel-tools" id="translation-buy-tools"></span>
89
90
  <video id="video" playsinline hidden></video>
90
91
  <!-- What the picture is saying, on the picture: the current caption
@@ -98,7 +99,7 @@
98
99
  allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe>
99
100
  </div>
100
101
  <audio id="audio"></audio>
101
- <div id="title-line" class="track-title">Nothing loaded.</div>
102
+ <div id="title-line" class="track-title" data-i18n="Nothing loaded.">Nothing loaded.</div>
102
103
  <div id="album-line" class="track-sub">—</div>
103
104
  <!-- What is known about what is playing: live or on demand, the
104
105
  picture's size, who else is watching, where in a catalog it came
@@ -114,27 +115,27 @@
114
115
  <p id="meta-blurb" class="track-blurb" hidden></p>
115
116
  <div class="scrub">
116
117
  <span id="elapsed" class="time">00:00</span>
117
- <input id="seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek" disabled />
118
+ <input id="seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek" data-i18n-aria-label="Seek" disabled />
118
119
  <span id="total" class="time muted">--:--</span>
119
120
  <button id="copy-now" type="button" class="icon" hidden
120
121
  title="Copy this stream's URL, to play it in VLC or another player"
121
- aria-label="Copy this stream's URL">⧉</button>
122
+ aria-label="Copy this stream's URL" data-i18n-aria-label="Copy this stream's URL">⧉</button>
122
123
  <button id="fullscreen" type="button" class="ghost" hidden
123
- title="Full screen" aria-label="Full screen">⛶</button>
124
+ title="Full screen" data-i18n-title="Full screen" aria-label="Full screen" data-i18n-aria-label="Full screen">⛶</button>
124
125
  <button id="download-now" type="button" class="ghost" hidden
125
- title="Download now: the server fetches the whole thing with yt-dlp and hands it to this device">⤓ Download</button>
126
+ title="Download now: the server fetches the whole thing with yt-dlp and hands it to this device" data-i18n="⤓ Download">⤓ Download</button>
126
127
  </div>
127
- <div class="audio-translation" role="group" aria-label="Audio language">
128
- <label class="transcript-switch"><input type="checkbox" id="transcript-audio" disabled /> Translate audio</label>
129
- <label class="audio-language">Language <select id="transcript-language" class="transcript-language" aria-label="Translation language"></select></label>
130
- <p class="hint" id="transcript-audio-note">Original audio.</p>
128
+ <div class="audio-translation" role="group" aria-label="Audio language" data-i18n-aria-label="Audio language">
129
+ <label class="transcript-switch"><input type="checkbox" id="transcript-audio" disabled /><span data-i18n="Translate audio"> Translate audio</span></label>
130
+ <label class="audio-language"><span data-i18n="Language">Language </span><select id="transcript-language" class="transcript-language" aria-label="Translation language" data-i18n-aria-label="Translation language"></select></label>
131
+ <p class="hint" id="transcript-audio-note" data-i18n="Original audio.">Original audio.</p>
131
132
  <p class="hint" id="translation-balance"></p>
132
133
  <details id="transcript-voice-settings" hidden>
133
- <summary>Audio options</summary>
134
- <label class="transcript-switch"><input type="checkbox" id="transcript-background" checked /> Keep background sound</label>
135
- <label class="background-level">Background level <input id="transcript-background-level" type="range" min="0" max="200" step="5" value="100" aria-valuetext="100 percent" /> <output id="transcript-background-value" for="transcript-background-level">100%</output></label>
134
+ <summary data-i18n="Audio options">Audio options</summary>
135
+ <label class="transcript-switch"><input type="checkbox" id="transcript-background" checked /><span data-i18n="Keep background sound"> Keep background sound</span></label>
136
+ <label class="background-level"><span data-i18n="Background level">Background level </span><input id="transcript-background-level" type="range" min="0" max="200" step="5" value="100" aria-valuetext="100 percent" /> <output id="transcript-background-value" for="transcript-background-level">100%</output></label>
136
137
  <p class="hint">Speech removal is approximate; turn this off if original voices bleed through.</p>
137
- <div id="transcript-speakers" role="group" aria-label="Speaker voices"></div>
138
+ <div id="transcript-speakers" role="group" aria-label="Speaker voices" data-i18n-aria-label="Speaker voices"></div>
138
139
  </details>
139
140
  </div>
140
141
 
@@ -147,15 +148,15 @@
147
148
  the directory. -->
148
149
  <form id="link-form" class="picker link-form">
149
150
  <input id="link-url" type="url" inputmode="url" placeholder="Paste a link: an audio or video file, an .m3u playlist, an IPTV feed, YouTube…"
150
- aria-label="A link to play" />
151
+ aria-label="A link to play" data-i18n-aria-label="A link to play" />
151
152
  <select id="link-server" aria-label="Which server goes live with it" title="Which server goes live with it" hidden></select>
152
- <button type="submit" class="button" title="Play it here, in this browser, where the browser can">Play link</button>
153
+ <button type="submit" class="button" title="Play it here, in this browser, where the browser can" data-i18n="Play link">Play link</button>
153
154
  <!-- The one Go live for a link: the server fetches it and puts it on
154
155
  the air for everyone, listed with Join party. A file, an .m3u
155
156
  playlist played entry by entry, an IPTV feed, a page yt-dlp
156
157
  reads. -->
157
158
  <button id="link-go-live" type="button" class="button golive"
158
- title="Go live with this link: the server fetches it and puts it on the air for everyone, listed in the directory">● Go live</button>
159
+ title="Go live with this link: the server fetches it and puts it on the air for everyone, listed in the directory" data-i18n="● Go live">● Go live</button>
159
160
  </form>
160
161
  <!-- What just happened, where it happened: the same message as the
161
162
  Status line at the foot of the page, but under the box the
@@ -165,7 +166,7 @@
165
166
  </section>
166
167
 
167
168
  <div class="split" id="zone-top">
168
- <section class="panel player-only" data-title="Spectrum Analyser" id="spectrum-panel">
169
+ <section class="panel player-only" data-title="Spectrum Analyser" data-i18n-data-title="Spectrum Analyser" id="spectrum-panel">
169
170
  <canvas id="spectrum" aria-hidden="true"></canvas>
170
171
  <div class="meters" aria-hidden="true">
171
172
  <span id="glyphs" class="glyphs"></span>
@@ -176,138 +177,135 @@
176
177
  <!-- Connected to a server: its files to browse, and what is live on
177
178
  it. The two things a visitor came for, beside the analyser,
178
179
  before anything about accounts or sharing. -->
179
- <section class="panel" id="playlist-panel" data-title="Playlist (0)" tabindex="-1">
180
- <input id="filter" type="search" class="filter" placeholder="Filter these files"
181
- aria-label="Filter the playlist" />
182
- <nav id="crumbs" class="crumbs" aria-label="Playlist folders" hidden></nav>
183
- <ol id="playlist" class="playlist" aria-label="Tracks and folders"></ol>
180
+ <section class="panel" id="playlist-panel" data-title="Playlist (0)" data-i18n-data-title="Playlist (0)" tabindex="-1">
181
+ <input id="filter" type="search" class="filter" placeholder="Filter these files" data-i18n-placeholder="Filter these files"
182
+ aria-label="Filter the playlist" data-i18n-aria-label="Filter the playlist" />
183
+ <nav id="crumbs" class="crumbs" aria-label="Playlist folders" data-i18n-aria-label="Playlist folders" hidden></nav>
184
+ <ol id="playlist" class="playlist" aria-label="Tracks and folders" data-i18n-aria-label="Tracks and folders"></ol>
184
185
  <!-- A page at a time. A television cannot scroll a box inside the
185
186
  page, and a folder of two hundred files is a list nobody
186
187
  wants in one go anyway. -->
187
- <nav id="playlist-pager" class="pager" aria-label="Playlist pages" hidden></nav>
188
+ <nav id="playlist-pager" class="pager" aria-label="Playlist pages" data-i18n-aria-label="Playlist pages" hidden></nav>
188
189
  </section>
189
190
 
190
- <!-- What is on, on the server you are connected to: its own stream,
191
- and anybody publishing into it. Two different things that were
192
- only ever visible in two different places, so "what is on here?"
193
- had no answer. -->
194
- <section class="panel player-only" data-title="Parties on this server" id="onair-panel" hidden>
195
- <p class="hint" id="onair-note"></p>
196
- <ul id="onair-list" class="directory-list"></ul>
191
+ <!-- One live list across servers and connected sites. -->
192
+ <section class="panel player-only" data-title="Parties" data-i18n-data-title="Parties" id="parties-panel">
193
+ <p class="hint" id="parties-note" data-i18n="Loading…">Loading…</p>
194
+ <ul id="parties-list" class="party-groups" aria-live="off"></ul>
195
+ <details class="party-invite">
196
+ <summary data-i18n="Invite code">Invite code</summary>
197
+ <form id="party-form" class="picker">
198
+ <label for="party-code" data-i18n="Invite code">Invite code</label>
199
+ <input id="party-code" type="text" inputmode="latin" autocomplete="off"
200
+ autocapitalize="characters" spellcheck="false"
201
+ placeholder="ABC123" />
202
+ <button id="party-join" type="submit" class="button" title="Join live" data-i18n-title="Join live" data-i18n="Join party">Join party</button>
203
+ </form>
204
+ </details>
197
205
  </section>
198
206
 
199
207
  <!-- The m3u catalogs a server keeps: IPTV lists of live channels and
200
208
  lists of things on demand, browsed by group rather than dumped
201
209
  as three thousand rows. Anyone with the link may browse and
202
210
  play; adding and removing one is administering the server. -->
203
- <section class="panel player-only" data-title="Catalogs on this server" id="catalogs-panel" hidden>
211
+ <section class="panel player-only" data-title="Catalogs on this server" data-i18n-data-title="Catalogs on this server" id="catalogs-panel" hidden>
204
212
  <p class="hint" id="catalogs-note"></p>
205
213
  <form id="catalogs-form" class="picker" hidden>
206
214
  <input id="catalog-source" type="text" inputmode="url"
207
215
  placeholder="https://…/list.m3u or a path on the server"
208
- aria-label="An m3u catalog to add" />
209
- <input id="catalog-name" type="text" placeholder="call it something"
210
- aria-label="What to call it" />
211
- <button type="submit" class="button">Add catalog</button>
216
+ aria-label="An m3u catalog to add" data-i18n-aria-label="An m3u catalog to add" />
217
+ <input id="catalog-name" type="text" placeholder="call it something" data-i18n-placeholder="call it something"
218
+ aria-label="What to call it" data-i18n-aria-label="What to call it" />
219
+ <button type="submit" class="button" data-i18n="Add catalog">Add catalog</button>
212
220
  </form>
213
- <nav id="catalogs-crumbs" class="crumbs" aria-label="Catalog folders" hidden></nav>
221
+ <nav id="catalogs-crumbs" class="crumbs" aria-label="Catalog folders" data-i18n-aria-label="Catalog folders" hidden></nav>
214
222
  <ul id="catalogs-list" class="directory-list"></ul>
215
- <input id="catalogs-filter" type="search" class="filter" placeholder="Filter these entries"
216
- aria-label="Filter the entries" hidden />
223
+ <input id="catalogs-filter" type="search" class="filter" placeholder="Filter these entries" data-i18n-placeholder="Filter these entries"
224
+ aria-label="Filter the entries" data-i18n-aria-label="Filter the entries" hidden />
217
225
  <ol id="catalogs-entries" class="playlist" hidden></ol>
218
226
  </section>
219
227
  </div>
220
228
 
221
- <section class="transport" id="transport" role="group" aria-label="Playback controls" aria-describedby="keyboard-help" tabindex="0">
222
- <button id="prev" type="button" aria-label="Previous">|◀◀</button>
223
- <button id="play-pause" type="button" class="primary" aria-label="Play">▶</button>
229
+ <section class="transport" id="transport" role="group" aria-label="Playback controls" data-i18n-aria-label="Playback controls" aria-describedby="keyboard-help" tabindex="0">
230
+ <button id="prev" type="button" aria-label="Previous" data-i18n-aria-label="Previous">|◀◀</button>
231
+ <button id="play-pause" type="button" class="primary" aria-label="Play" data-i18n-aria-label="Play">▶</button>
224
232
  <!-- For whoever administers the server: Play plays it for you; this
225
233
  puts it on the air for everybody, lists it, and copies the link. -->
226
234
  <button id="go-live-now" type="button" class="golive" hidden
227
- title="Go live: put this on the air for everyone, list it in the directory with a phone code, and copy the link to share">● GO LIVE</button>
228
- <button id="stop" type="button" aria-label="Stop">■</button>
229
- <button id="next" type="button" aria-label="Next">▶▶|</button>
235
+ title="Go live: put this on the air for everyone, list it in the directory with a phone code, and copy the link to share" data-i18n="● GO LIVE">● GO LIVE</button>
236
+ <button id="stop" type="button" aria-label="Stop" data-i18n-aria-label="Stop">■</button>
237
+ <button id="next" type="button" aria-label="Next" data-i18n-aria-label="Next">▶▶|</button>
230
238
  <!-- A link to what is playing, for anybody: nixamp.com/?play=<the
231
239
  stream's own address>, which plays in VLC as it is and which
232
240
  nixamp.com opens as a viewer. The device's share sheet where
233
241
  there is one; copied otherwise. Only when there is something
234
242
  safe to hand out: a viewing address, never the driving one. -->
235
243
  <button id="share-now" type="button" class="share" hidden
236
- title="Share: a nixamp.com link that plays this" aria-label="Share what is playing">Share</button>
244
+ title="Share: a nixamp.com link that plays this" aria-label="Share what is playing" data-i18n-aria-label="Share what is playing" data-i18n="Share">Share</button>
237
245
  <label class="volume">
238
246
  <span aria-hidden="true" class="vol">VOL</span>
239
- <input id="volume" type="range" min="0" max="100" value="100" aria-label="Volume" />
247
+ <input id="volume" type="range" min="0" max="100" value="100" aria-label="Volume" data-i18n-aria-label="Volume" />
240
248
  </label>
241
249
  </section>
242
250
 
243
251
  <div class="split" id="zone-main">
244
- <section class="panel player-only col-a" data-title="Files" id="files-panel">
252
+ <section class="panel player-only col-a" data-title="Files" data-i18n-data-title="Files" id="files-panel">
245
253
  <p class="hint">Files play on this device. Live captions and translated audio send short audio clips only after you enable them.</p>
246
254
  <div class="picker">
247
- <label class="button">
255
+ <label class="button"><span data-i18n="Open files">
248
256
  Open files
249
- <input id="files" type="file" multiple accept="audio/*,video/*" />
257
+ </span><input id="files" type="file" multiple accept="audio/*,video/*" />
250
258
  </label>
251
- <label class="button">
259
+ <label class="button"><span data-i18n="Open folder">
252
260
  Open folder
253
- <input id="folder" type="file" multiple webkitdirectory directory />
261
+ </span><input id="folder" type="file" multiple webkitdirectory directory />
254
262
  </label>
255
263
  </div>
256
264
  </section>
257
265
 
258
- <section class="panel col-b" data-title="Servers streaming now" id="directory" hidden>
259
- <p class="hint" id="directory-note">Looking for servers…</p>
266
+ <section class="panel col-b" data-title="Servers streaming now" data-i18n-data-title="Servers streaming now" id="directory" hidden>
267
+ <p class="hint" id="directory-note" data-i18n="Looking for servers…">Looking for servers…</p>
260
268
  <ul id="directory-list" class="directory-list"></ul>
261
269
  <p class="hint" id="recent-note" hidden>Recently live &mdash; follow them to hear next time:</p>
262
270
  <ul id="recent-list" class="directory-list"></ul>
263
- <p class="hint"><a href="/" id="directory-back" hidden>Back to the player</a></p>
271
+ <p class="hint"><a href="/" id="directory-back" hidden data-i18n="Back to the player">Back to the player</a></p>
264
272
  </section>
265
273
 
266
- <section class="panel player-only col-a" data-title="Favourites" id="favorites-panel" hidden>
274
+ <section class="panel player-only col-a" data-title="Favourites" data-i18n-data-title="Favourites" id="favorites-panel" hidden>
267
275
  <p class="hint" id="favorites-note">Servers you hearted. Connect to one, or let it go.</p>
268
276
  <ul id="favorites-list" class="directory-list"></ul>
269
277
  </section>
270
278
 
271
- <section class="panel col-b" data-title="Parties" id="parties-panel" hidden>
272
- <p class="hint" id="parties-note">Join a party, or enter an invite code.</p>
273
- <ul id="parties-list" class="directory-list"></ul>
274
- <form id="party-form" class="picker">
275
- <label for="party-code">Invite code</label>
276
- <input id="party-code" type="text" inputmode="latin" autocomplete="off"
277
- autocapitalize="characters" spellcheck="false"
278
- placeholder="ABC123" />
279
- <button id="party-join" type="submit" class="button" title="Join live">Join party</button>
280
- </form>
281
- </section>
282
279
 
283
- <section class="panel player-only col-a" data-title="Account" id="account-panel">
280
+
281
+ <section class="panel player-only col-a" data-title="Account" data-i18n-data-title="Account" id="account-panel">
284
282
  <p class="hint" id="account-note">Listening needs no account. Sign in to keep favourites, follow people, and publish.</p>
285
283
  <div id="account-providers" class="picker" hidden></div>
286
284
  <form id="account-form" class="picker">
287
285
  <input id="account-email" type="email" autocomplete="email"
288
- placeholder="you@example.com" aria-label="Email" />
286
+ placeholder="you@example.com" aria-label="Email" data-i18n-aria-label="Email" />
289
287
  <input id="account-password" type="password" autocomplete="current-password"
290
- placeholder="password" aria-label="Password" />
291
- <button type="submit" class="button" id="account-submit">Sign in</button>
292
- <button id="account-toggle" type="button" class="ghost">Create one</button>
288
+ placeholder="password" data-i18n-placeholder="password" aria-label="Password" data-i18n-aria-label="Password" />
289
+ <button type="submit" class="button" id="account-submit" data-i18n="Sign in">Sign in</button>
290
+ <button id="account-toggle" type="button" class="ghost" data-i18n="Create one">Create one</button>
293
291
  </form>
294
292
  <!-- Outside the form, because signing out is the one thing you can do
295
293
  once the form is gone. -->
296
- <button id="account-signout" type="button" class="ghost" hidden>Sign out</button>
294
+ <button id="account-signout" type="button" class="ghost" hidden data-i18n="Sign out">Sign out</button>
297
295
  <!-- Who the rooms know you as: the handle on every line, the voice
298
296
  your lines are read in on the phone, and the OpenProfile the
299
297
  voice may be read from. Only once signed in. -->
300
298
  <form id="persona-form" class="picker" hidden>
301
299
  <input id="persona-handle" type="text" maxlength="30" autocomplete="nickname"
302
- placeholder="handle" aria-label="Your public handle" />
300
+ placeholder="handle" data-i18n-placeholder="handle" aria-label="Your public handle" data-i18n-aria-label="Your public handle" />
303
301
  <select id="persona-voice" aria-label="The voice your lines are read in on the phone">
304
- <option value="">Any voice</option>
305
- <option value="female">A woman's voice</option>
306
- <option value="male">A man's voice</option>
302
+ <option value="" data-i18n="Any voice">Any voice</option>
303
+ <option value="female" data-i18n="A woman's voice">A woman's voice</option>
304
+ <option value="male" data-i18n="A man's voice">A man's voice</option>
307
305
  </select>
308
306
  <input id="persona-profile" type="url" autocomplete="url"
309
307
  placeholder="https://you.example/.well-known/openprofile.md" aria-label="Your OpenProfile URL" />
310
- <button type="submit" class="button">Save</button>
308
+ <button type="submit" class="button" data-i18n="Save">Save</button>
311
309
  </form>
312
310
  <p class="hint" id="persona-note" hidden></p>
313
311
  <!-- The sites that hold a token for this account. Nothing is shown
@@ -322,12 +320,12 @@
322
320
  Accounts live at <a href="https://nixamp.com">nixamp.com</a>.
323
321
  </p>
324
322
 
325
- <section class="panel player-only col-b" data-title="Notifications" id="notify-panel" hidden>
323
+ <section class="panel player-only col-b" data-title="Notifications" data-i18n-data-title="Notifications" id="notify-panel" hidden>
326
324
  <p class="hint" id="notify-note">Get told when someone you follow goes live.</p>
327
325
  <div class="picker">
328
326
  <label class="toggle">
329
- <input id="notify-web" type="checkbox" /> On this device
330
- </label>
327
+ <input id="notify-web" type="checkbox" /><span data-i18n="On this device"> On this device
328
+ </span></label>
331
329
  <label class="toggle">
332
330
  <input id="notify-email" type="checkbox" /> By email
333
331
  </label>
@@ -337,15 +335,15 @@
337
335
  </div>
338
336
  <form id="notify-phone-form" class="picker">
339
337
  <input id="notify-phone" type="tel" autocomplete="tel"
340
- placeholder="+1 555 555 0123" aria-label="Phone number for texts" />
341
- <button type="submit" class="button">Save number</button>
338
+ placeholder="+1 555 555 0123" aria-label="Phone number for texts" data-i18n-aria-label="Phone number for texts" />
339
+ <button type="submit" class="button" data-i18n="Save number">Save number</button>
342
340
  </form>
343
341
  <p class="hint" id="notify-phone-note"></p>
344
342
  <p class="hint" id="following-note" hidden>You follow:</p>
345
343
  <ul id="following-list" class="directory-list"></ul>
346
344
  </section>
347
345
 
348
- <section class="panel player-only col-a" data-title="Your servers" id="servers-panel" hidden>
346
+ <section class="panel player-only col-a" data-title="Your servers" data-i18n-data-title="Your servers" id="servers-panel" hidden>
349
347
  <p class="hint" id="servers-note">The machines on your account. Open one, or forget it.</p>
350
348
  <ul id="servers-list" class="directory-list"></ul>
351
349
  </section>
@@ -357,24 +355,24 @@
357
355
  <!-- Every message the page has shown, newest first, with the time:
358
356
  what went wrong and when, in words, for whoever missed it as it
359
357
  went by. Hide it from the Panels list if it is noise. -->
360
- <section class="panel col-b" data-title="Log" id="log-panel">
358
+ <section class="panel col-b" data-title="Log" data-i18n-data-title="Log" id="log-panel">
361
359
  <p class="hint">What this page has said, newest first.</p>
362
- <ul id="log-list" tabindex="0" aria-label="Activity log" class="log-list" aria-live="off"></ul>
360
+ <ul id="log-list" tabindex="0" aria-label="Activity log" data-i18n-aria-label="Activity log" class="log-list" aria-live="off"></ul>
363
361
  </section>
364
362
 
365
- <section class="panel col-b" data-title="Trollbox" id="trollbox-panel" hidden>
363
+ <section class="panel col-b" data-title="Trollbox" data-i18n-data-title="Trollbox" id="trollbox-panel" hidden>
366
364
  <p class="hint" id="trollbox-note">Join a live and the room's chat is here.</p>
367
- <ul id="trollbox-list" tabindex="0" class="trollbox-list" aria-live="off" aria-label="The room's chat"></ul>
365
+ <ul id="trollbox-list" tabindex="0" class="trollbox-list" aria-live="off" aria-label="The room's chat" data-i18n-aria-label="The room's chat"></ul>
368
366
  <form id="trollbox-form" class="picker" hidden>
369
367
  <input id="trollbox-input" type="text" maxlength="500" autocomplete="off"
370
- placeholder="Say something to the room…" aria-label="A line for the trollbox" />
368
+ placeholder="Say something to the room…" data-i18n-placeholder="Say something to the room…" aria-label="A line for the trollbox" data-i18n-aria-label="A line for the trollbox" />
371
369
  <!-- Tap, talk, tap: the words are posted to the room as you said
372
370
  them, no Send to click. Heard by nixamp.com's own ear (Whisper),
373
371
  so it only appears where a line can be sent from: signed in, on
374
372
  nixamp.com. -->
375
373
  <button type="button" class="button mic" id="trollbox-mic" hidden aria-pressed="false"
376
374
  title="Say it instead: tap, talk, tap again, and it is sent">🎤</button>
377
- <button type="submit" class="button">Send</button>
375
+ <button type="submit" class="button" data-i18n="Send">Send</button>
378
376
  </form>
379
377
  <!-- Edit mode: a dictated line waits in the box for Send instead of
380
378
  going straight to the room. Off unless this device turned it on. -->
@@ -388,21 +386,21 @@
388
386
  held here until your own sound gets there. On by default; the
389
387
  switch turns it off for this device, and the Panels list hides the
390
388
  panel. -->
391
- <section class="panel col-b" data-title="Transcript" id="transcript-panel" tabindex="-1" hidden>
392
- <label class="transcript-switch"><input type="checkbox" id="transcript-on" checked /> Captions</label>
393
- <p class="hint" id="transcript-note">Original-language captions.</p>
394
- <ul id="transcript-list" tabindex="0" class="transcript-list" aria-live="off" aria-label="What is being said"></ul>
389
+ <section class="panel col-b" data-title="Transcript" data-i18n-data-title="Transcript" id="transcript-panel" tabindex="-1" hidden>
390
+ <label class="transcript-switch"><input type="checkbox" id="transcript-on" checked /><span data-i18n="Captions"> Captions</span></label>
391
+ <p class="hint" id="transcript-note" data-i18n="Original-language captions.">Original-language captions.</p>
392
+ <ul id="transcript-list" tabindex="0" class="transcript-list" aria-live="off" aria-label="What is being said" data-i18n-aria-label="What is being said"></ul>
395
393
  </section>
396
394
 
397
395
  <!-- What to send somebody who is not technical: one link, one number,
398
396
  one code. Everything else about this page assumes you know what a
399
397
  server is; this part must not. -->
400
- <section class="panel player-only col-b" data-title="Share this stream" id="share-panel" hidden>
401
- <p class="hint" id="share-note">Checking…</p>
398
+ <section class="panel player-only col-b" data-title="Share this stream" data-i18n-data-title="Share this stream" id="share-panel" hidden>
399
+ <p class="hint" id="share-note" data-i18n="Checking…">Checking…</p>
402
400
  <label class="share-line">
403
- <span class="share-what">Link</span>
404
- <input id="share-link" type="text" readonly aria-label="Link to this stream" />
405
- <button id="share-copy" type="button" class="ghost">Copy</button>
401
+ <span class="share-what" data-i18n="Link">Link</span>
402
+ <input id="share-link" type="text" readonly aria-label="Link to this stream" data-i18n-aria-label="Link to this stream" />
403
+ <button id="share-copy" type="button" class="ghost" data-i18n="Copy">Copy</button>
406
404
  </label>
407
405
  <p class="hint" id="share-phone" hidden></p>
408
406
  <div class="picker" id="live-controls" hidden>
@@ -412,21 +410,21 @@
412
410
  <form id="share-send" class="picker" hidden>
413
411
  <input id="share-to" type="text" inputmode="email"
414
412
  placeholder="a phone number or an email" aria-label="Who to send it to" />
415
- <button type="submit" class="button">Send it</button>
413
+ <button type="submit" class="button" data-i18n="Send it">Send it</button>
416
414
  </form>
417
415
  </section>
418
416
 
419
- <section class="panel player-only col-a" data-title="Admin" id="admin-panel" hidden>
420
- <p class="hint" id="admin-note">Checking…</p>
417
+ <section class="panel player-only col-a" data-title="Admin" data-i18n-data-title="Admin" id="admin-panel" hidden>
418
+ <p class="hint" id="admin-note" data-i18n="Checking…">Checking…</p>
421
419
  <p class="said" id="admin-said" hidden></p>
422
420
  <table class="admin-table" id="admin-connections"></table>
423
421
  <form id="admin-restream" class="picker">
424
422
  <input id="admin-source" type="text" placeholder="a folder, an album URL, a channel"
425
- aria-label="Something to add" />
423
+ aria-label="Something to add" data-i18n-aria-label="Something to add" />
426
424
  <input id="admin-name" type="text" placeholder="call it something (MLB Network)"
427
- aria-label="What to call it" />
428
- <button type="submit" class="button">Go live</button>
429
- <button type="button" id="admin-add" class="ghost">Add to library</button>
425
+ aria-label="What to call it" data-i18n-aria-label="What to call it" />
426
+ <button type="submit" class="button" data-i18n="Go live">Go live</button>
427
+ <button type="button" id="admin-add" class="ghost" data-i18n="Add to library">Add to library</button>
430
428
  </form>
431
429
  <label class="check">
432
430
  <input id="admin-replace" type="checkbox" />
@@ -443,11 +441,11 @@
443
441
 
444
442
  <!-- The panels: shown, hidden, reordered, from a list, for a keyboard,
445
443
  a phone, or putting everything back where it started. -->
446
- <section class="panel col-b" data-title="Panels" id="panels-panel" hidden>
444
+ <section class="panel col-b" data-title="Panels" data-i18n-data-title="Panels" id="panels-panel" popover="auto" hidden>
447
445
  <p class="hint">Every panel, on or off. Move a panel by dragging its ≡ handle, or focus that handle and press Alt + Up or Down. ▁ shades it to its title bar; ✕ hides it.</p>
448
446
  <ul id="panels-list" class="directory-list panels-list"></ul>
449
447
  <div class="picker">
450
- <button id="panels-reset" type="button" class="ghost" title="Every panel back where the page puts it">Reset layout</button>
448
+ <button id="panels-reset" type="button" class="ghost" title="Every panel back where the page puts it" data-i18n="Reset layout">Reset layout</button>
451
449
  </div>
452
450
  </section>
453
451
 
@@ -457,28 +455,24 @@
457
455
  into the directory: one command, a sign-in, and it names and
458
456
  lists itself. A host flag and an IP were how it worked before
459
457
  nixamp.com knew about servers at all. -->
460
- <section class="panel player-only col-a" data-title="Server" id="remote-panel">
461
- <p class="hint">
462
- Pick a server in the directory, or paste a server's link here. To run your own:
463
- <code>curl -fsSL https://nixamp.com/install.sh | sh</code>, then <code>nixamp login</code>
464
- and <code>nixamp serve ~/Music</code>. It lists itself in the directory and on your account.
465
- </p>
458
+ <section class="panel player-only col-a" data-title="Server" data-i18n-data-title="Server" id="remote-panel">
459
+ <p class="hint" data-i18n="Pick a server in the directory, or paste its link here.">Pick a server in the directory, or paste its link here.</p>
466
460
  <form id="remote-form" class="picker">
467
- <input id="remote-url" type="text" inputmode="url" placeholder="a server's link, https://…:4321/view/…" aria-label="A server's link" />
468
- <button type="submit" class="button">Connect</button>
469
- <button id="disconnect" type="button" class="ghost" hidden>Disconnect</button>
470
- <button id="browse" type="button" class="ghost">Browse the directory</button>
461
+ <input id="remote-url" type="text" inputmode="url" placeholder="a server's link, https://…:4321/view/…" aria-label="A server's link" data-i18n-aria-label="A server's link" />
462
+ <button type="submit" class="button" data-i18n="Connect">Connect</button>
463
+ <button id="disconnect" type="button" class="ghost" hidden data-i18n="Disconnect">Disconnect</button>
464
+ <button id="browse" type="button" class="ghost" data-i18n="Browse the directory">Browse the directory</button>
471
465
  </form>
472
466
 
473
467
  <label class="check">
474
- <input id="listen-here" type="checkbox" checked />
468
+ <input id="listen-here" type="checkbox" checked /><span data-i18n="Play on this device">
475
469
  Play on this device
476
- </label>
470
+ </span></label>
477
471
  <p class="hint">
478
472
  Off means the server plays through its own speakers and this is only a
479
473
  remote control for it.
480
474
  </p>
481
- <p class="hint">Status: <span id="remote-state" data-status="idle">not connected</span></p>
475
+ <p class="hint"><span data-i18n="Status:">Status: </span><span id="remote-state" data-status="idle" data-i18n="not connected">not connected</span></p>
482
476
  <p class="hint" id="listen-only" hidden></p>
483
477
  </section>
484
478
  </div>
@@ -486,14 +480,14 @@
486
480
  <p id="note" class="note" hidden></p>
487
481
 
488
482
  <footer class="statusbar">
489
- <span id="keyboard-help">With playback controls focused: <b>Space</b> Play</span>
490
- <span><b>↑↓</b> Select</span>
491
- <span><b>n/p</b> Next/Prev</span>
492
- <span><b>s</b> Stop</span>
483
+ <span id="keyboard-help">With playback controls focused: <b>Space</b><span data-i18n="Play"> Play</span></span>
484
+ <span><b>↑↓</b><span data-i18n="Select"> Select</span></span>
485
+ <span><b>n/p</b><span data-i18n="Next/Prev"> Next/Prev</span></span>
486
+ <span><b>s</b><span data-i18n="Stop"> Stop</span></span>
493
487
  <span class="spacer"></span>
494
488
  <!-- For a set whose browser does not say what it is: one press, and
495
489
  the page is laid out for a remote and a sofa. -->
496
- <button id="tv-toggle" type="button" class="ghost tv-toggle">TV mode</button>
490
+ <button id="tv-toggle" type="button" class="ghost tv-toggle" data-i18n="TV mode">TV mode</button>
497
491
  <span class="muted"> · </span>
498
492
  <a href="https://github.com/profullstack/nixamp">github.com/profullstack/nixamp</a>
499
493
  <span class="muted"> · </span>
@@ -501,17 +495,17 @@
501
495
  </footer>
502
496
  </main>
503
497
  <dialog id="translation-purchase" aria-labelledby="translation-purchase-title" aria-describedby="translation-purchase-summary">
504
- <div class="purchase-heading"><h2 id="translation-purchase-title">Buy translated audio</h2><button type="button" id="translation-purchase-close" aria-label="Close translated audio purchase">✕</button></div>
498
+ <div class="purchase-heading"><h2 id="translation-purchase-title" data-i18n="Buy translated audio">Buy translated audio</h2><button type="button" id="translation-purchase-close" aria-label="Close translated audio purchase" data-i18n-aria-label="Close translated audio purchase">✕</button></div>
505
499
  <p id="translation-purchase-summary">Prepaid translation for live channels, video and audio.</p>
506
500
  <p id="translation-purchase-balance"></p>
507
501
  <p class="hint" id="translation-free-description"></p>
508
- <label for="translation-plan">Pass</label><select id="translation-plan"></select>
502
+ <label for="translation-plan" data-i18n="Pass">Pass</label><select id="translation-plan"></select>
509
503
  <p class="hint" id="translation-plan-description"></p>
510
- <label for="translation-coin">Pay with</label><select id="translation-coin"></select>
504
+ <label for="translation-coin" data-i18n="Pay with">Pay with</label><select id="translation-coin"></select>
511
505
  <p class="hint">Crypto checkout through CoinPay. Any network fees are shown at checkout.</p>
512
506
  <a href="#account-panel" id="translation-sign-in">Sign in for free sessions or to purchase</a>
513
- <div class="purchase-actions"><button type="button" class="button" id="translation-checkout" disabled>Buy pass</button><a id="translation-checkout-link" target="_blank" rel="noopener noreferrer" hidden>Open secure checkout ↗ (new tab)</a><button type="button" id="translation-check-payment" hidden>Check payment</button></div>
514
- <details class="purchase-terms"><summary>Usage rates and expiry</summary>
507
+ <div class="purchase-actions"><button type="button" class="button" id="translation-checkout" disabled data-i18n="Buy pass">Buy pass</button><a id="translation-checkout-link" target="_blank" rel="noopener noreferrer" hidden data-i18n="Open secure checkout ↗ (new tab)">Open secure checkout ↗ (new tab)</a><button type="button" id="translation-check-payment" hidden data-i18n="Check payment">Check payment</button></div>
508
+ <details class="purchase-terms"><summary data-i18n="Usage rates and expiry">Usage rates and expiry</summary>
515
509
  <p class="hint">After the five free sessions, new sessions draw from purchased credit at 5× the base speech API rates: $0.25 per 1,000 voice characters plus $1.10 per submitted audio hour. Live recognition includes repeated context, usually three submitted hours per listening hour. Shared live audio has the same paid access price for every listener.</p>
516
510
  <p class="hint">Unused credit expires. Buying before expiry adds credit and keeps the later expiry date. Your pass works across players on this account.</p>
517
511
  </details>