| 1 | {{define "head"}}<link rel="stylesheet" href="{{asset "static/calls.css"}}"> |
| 2 | {{/* #devices is in the layout from the first paint, so its arrival does |
| 3 | not push the page down when devices.js runs; without script this |
| 4 | sheet hides it, as the hidden attribute used to. */}} |
| 5 | <noscript><link rel="stylesheet" href="{{asset "static/noscript.css"}}"></noscript> |
| 6 | <script defer src="{{asset "static/devices.js"}}"></script> |
| 7 | <script defer src="{{asset "static/report.js"}}"></script> |
| 8 | {{end}} |
| 9 | {{/* The holding screen. Its layout is fixed at the first paint (spec |
| 10 | §2's "reserved up front"): the controls and the status line come |
| 11 | first in the device panel, and the rows devices.js may reveal later |
| 12 | come after them, the speaker last of all, so nothing that is |
| 13 | already on screen moves when they arrive. */}} |
| 14 | {{define "content"}} |
| 15 | {{if .IsMember}}{{template "back-nav" dict "Href" "/" "Label" "Meetings"}}{{end}} |
| 16 | <div class="calls-lobby-heading">{{template "page-header" dict "Title" .Title}}</div> |
| 17 | <p class="calls-standing">{{if .Starts}}Starts {{.Starts}}. {{end}}You’ll join as {{.Name}}{{if eq .Standing "guest"}}, a guest{{end}}.</p> |
| 18 | {{if .Floor}}{{template "callout" dict "Body" "Calls outside your network may not connect yet." "Tone" "warning"}}{{end}} |
| 19 | {{if .OpenElsewhere}}<div rst-callout rst-tone="warning"><span rst-callout-ic aria-hidden="true">{{icon "alert-triangle"}}</span><div rst-callout-body><p>This meeting is already open in another tab. <a href="/m/{{.Sqid}}/takeover">Use this tab instead</a>.</p></div></div>{{end}} |
| 20 | <noscript><p>The call needs JavaScript to reach your camera and the other participants; everything else on this page works without it.</p></noscript> |
| 21 | <div id="devices" data-sqid="{{.Sqid}}" data-join-href="{{if .OpenElsewhere}}/m/{{.Sqid}}/takeover{{else}}/m/{{.Sqid}}{{end}}"> |
| 22 | <div class="calls-device-layout"> |
| 23 | <div class="calls-preview-wrap"><video id="preview" muted autoplay playsinline aria-label="Your camera preview" class="calls-preview"></video></div> |
| 24 | <div estilo-call-panel> |
| 25 | <div class="calls-controls" estilo-call-actions> |
| 26 | <button type="button" id="toggle-mute" rst-btn aria-label="Microphone" aria-pressed="false">{{icon "mic"}}Microphone<span id="toggle-mute-state" class="calls-toggle-state" aria-hidden="true">On</span></button> |
| 27 | <button type="button" id="toggle-camera" rst-btn aria-label="Camera" aria-pressed="false">{{icon "video"}}Camera<span id="toggle-camera-state" class="calls-toggle-state" aria-hidden="true">On</span></button> |
| 28 | <button type="button" id="join" rst-btn="primary lg" disabled>Join in browser</button> |
| 29 | </div> |
| 30 | <p id="status" class="calls-status" role="status"></p> |
| 31 | <div id="pickers" class="calls-pickers"> |
| 32 | <div id="camera-row">{{template "field-select" dict "ID" "pick-camera" "Name" "camera" "Label" "Camera"}}</div> |
| 33 | <div id="mic-row">{{template "field-select" dict "ID" "pick-mic" "Name" "mic" "Label" "Microphone"}}</div> |
| 34 | {{/* Studio audio: the unprocessed capture option. field-check is the |
| 35 | vendored toggle (AGENTS.md's field rule), and the sentence under |
| 36 | it is wired to the input with aria-describedby by devices.js, |
| 37 | because field-check gives its checkbox no id to write here. It |
| 38 | is shown from the first paint, so the controls do not move when |
| 39 | devices.js decides; devices.js hides it only where there is no |
| 40 | microphone to apply it to. */}} |
| 41 | <div id="raw-row" class="calls-raw-row">{{template "field-check" dict "Name" "raw-audio" "Label" "Studio audio"}} |
| 42 | <p id="raw-help" class="calls-raw-help">Sends your microphone unprocessed and at a higher bitrate, for an audio interface in a room you have treated. It turns off echo cancellation, noise suppression and automatic gain, so leave it off on a laptop.</p></div> |
| 43 | {{/* Last, so that revealing it (only where the browser can choose an |
| 44 | output, and once it knows there is one) moves nothing below it. */}} |
| 45 | <div id="speaker-row" hidden>{{template "field-select" dict "ID" "pick-speaker" "Name" "speaker" "Label" "Speaker"}}</div> |
| 46 | </div> |
| 47 | {{/* Spec §15.20: the audio test, beside the choices it tests. A |
| 48 | native disclosure, so it opens and closes with no script of its |
| 49 | own; devices.js removes it where there is no Web Audio to run it |
| 50 | with, and like the rest of #devices it is never seen without |
| 51 | script. Each result is said in words in its own status line, and |
| 52 | the meters are native <meter>s with names of their own. Nothing |
| 53 | here is sent anywhere: the recording is played back from memory |
| 54 | and dropped. */}} |
| 55 | <details id="audio-test" class="calls-audio-test"> |
| 56 | <summary>Test audio</summary> |
| 57 | <p class="calls-raw-help">Everything here stays in this browser. Nothing you say is sent anywhere.</p> |
| 58 | <div class="calls-audio-row"> |
| 59 | <button type="button" id="test-mic" rst-btn>Test microphone</button> |
| 60 | <meter id="mic-level" min="0" max="1" value="0" aria-label="Microphone level"></meter> |
| 61 | <p id="mic-result" class="calls-audio-result" role="status"></p> |
| 62 | </div> |
| 63 | <div class="calls-audio-row"> |
| 64 | <button type="button" id="test-speaker" rst-btn>Play a test sound</button> |
| 65 | <meter id="speaker-level" min="0" max="1" value="0" aria-label="Test sound level"></meter> |
| 66 | <p id="speaker-result" class="calls-audio-result" role="status"></p> |
| 67 | </div> |
| 68 | <div class="calls-audio-row"> |
| 69 | <button type="button" id="test-loopback" rst-btn aria-describedby="loopback-help">Hear yourself as others will</button> |
| 70 | <meter id="loopback-level" min="0" max="1" value="0" aria-label="Recording and playback level"></meter> |
| 71 | <p id="loopback-help" class="calls-raw-help">Records five seconds from your microphone, with the same settings the call will use, and plays it back to you here. The recording never leaves this browser and is thrown away once it has played.</p> |
| 72 | <p id="loopback-result" class="calls-audio-result" role="status"></p> |
| 73 | </div> |
| 74 | <audio id="test-audio" hidden></audio> |
| 75 | </details> |
| 76 | </div> |
| 77 | </div> |
| 78 | </div> |
| 79 | <div class="calls-holding-more"> |
| 80 | {{/* Spec §15.26: for ten minutes after leaving a call, the call can |
| 81 | still be reported from here. report.js reveals it only when this |
| 82 | tab left this meeting's call that recently, and never without |
| 83 | script, which the sheet needs. */}} |
| 84 | <div id="report-last" class="calls-report-last" hidden><button type="button" id="report-last-open" rst-btn>Report a problem with the last call</button></div> |
| 85 | <p><a href="{{.AppHref}}" rst-btn>Join in the app</a> <span class="calls-option-help">The Calls app must be installed to open it.</span></p> |
| 86 | {{if .IsMember}}<form method="post" action="/m/{{.Sqid}}/share" target="_blank" rel="noopener" data-busy="false"><button type="submit" rst-btn aria-label="Share meeting link">{{icon "share"}}Share link</button></form> |
| 87 | <p class="calls-holding-links"><a href="/meetings/{{.Sqid}}/settings">Meeting settings</a> · <a href="/meetings/{{.Sqid}}/end">End meeting for everyone</a></p>{{end}} |
| 88 | {{/* §15.26's disclosure, where the small print is. The switch is in |
| 89 | the call's Devices panel. */}} |
| 90 | <p class="calls-option-help calls-quality-note">Calls sends anonymous connection quality reports to help fix call problems. You can turn them off under Devices during a call.</p> |
| 91 | </div> |
| 92 | {{template "report-sheet" .}} |
| 93 | {{end}} |
| 94 | |