Files
jitsi-launch/launch.html
T

817 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jitsi Meeting</title>
<link rel='shortlink' href='https://meet.bnvb.eu/' />
<link href="https://meet.bnvb.eu/assets/favicon.ico" rel="alternate icon">
<link href="https://meet.bnvb.eu/assets/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="https://meet.bnvb.eu/assets/favicon.svg" rel="icon" type="image/svg+xml">
<style>
:root {
color-scheme: light;
}
:root.dark {
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
color: #222;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
width: min(700px, calc(100% - 32px));
}
.navbar {
position: fixed;
top: 16px;
left: 2%;
width: 96%;
box-sizing: border-box;
z-index: 100;
display: flex;
align-items: center;
gap: 8px;
padding: 15px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(7px);
backdrop-filter: blur(7px);
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.30), 0 12px 40px rgba(0, 0, 0, 0.45);
}
.logo-bar {
display: flex;
align-items: center;
flex: 0 1 auto;
min-width: 0;
}
.logo-bar img {
display: block;
height: 60px;
max-width: 100%;
width: auto;
}
.nav-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
margin-left: auto;
}
/* Hamburger: nur im Phone-Portrait sichtbar.
* "button.nav-toggle" ist spezifischer als ".button",
* sonst überschreibt display: inline-block diese Regel. */
button.nav-toggle {
display: none;
}
/* Enger als der globale 6px-Margin der Buttons */
.navbar .button {
margin: 0;
}
.lang-menu {
position: relative;
}
.lang-dropdown {
display: none;
position: absolute;
right: 0;
top: 100%;
min-width: 150px;
margin-top: 6px;
padding: 6px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.lang-dropdown.open {
display: block;
}
.lang-dropdown button {
display: block;
width: 100%;
margin: 0;
padding: 10px 14px;
border: 0;
border-radius: 6px;
background: none;
text-align: left;
font-size: 14px;
font-family: inherit;
cursor: pointer;
}
.lang-dropdown button:hover {
background: #f0f0f0;
}
.lang-dropdown button.active {
font-weight: 600;
background: #f0f8ff;
}
.lang-flag {
width: 20px;
height: 13px;
margin-right: 6px;
border-radius: 2px;
vertical-align: -2px;
}
.card {
box-sizing: border-box;
width: min(600px, 100%);
padding: 32px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(7px);
backdrop-filter: blur(7px);
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.30), 0 12px 40px rgba(0, 0, 0, 0.45);
text-align: center;
}
h1 {
margin-top: 0;
}
p {
color: #555;
}
button, .button {
display: inline-block;
border: 0;
border-radius: 8px;
padding: 12px 20px;
margin: 6px;
font-size: 16px;
font-family: inherit;
cursor: pointer;
text-decoration: none;
}
.primary {
background: #1f73e8;
color: white;
}
.secondary {
background: #e9e9e9;
color: #222;
}
.hidden {
display: none !important;
}
.status {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0f0f0;
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
border: 2px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.7s linear infinite;
vertical-align: -3px;
}
.download {
margin-top: 24px;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
}
.room {
font-family: monospace;
word-break: break-all;
}
.error {
padding: 16px;
border-radius: 8px;
background: #fff0f0;
color: #900;
}
.form-group {
margin-bottom: 16px;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
min-height: 80px;
resize: vertical;
}
.form-actions {
margin-top: 24px;
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
.success-message {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0f8ff;
color: #0066cc;
}
.adhoc-result {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0fff4;
color: #1a7f37;
}
.adhoc-link {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.adhoc-link a {
font-family: monospace;
word-break: break-all;
color: #0066cc;
}
/*
* Verhindert abgeschnittene Inhalte, wenn die Karte
* höher als der Viewport ist ("center" schneidet oben ab).
*/
@supports (align-items: safe center) {
body {
align-items: safe center;
}
}
/*
* Schmale Bildschirme: Navbar umbrechen lassen,
* Abstände und Logo verkleinern.
*/
@media (max-width: 600px) {
body {
/* 130px räumen die fixe Navbar frei */
padding-top: 130px;
/* min-height gilt für die Content-Box: Navbar-Freiraum
* abziehen, sonst sitzt die Karte unter der Mitte */
min-height: calc(100vh - 130px);
}
.navbar {
top: 8px;
left: 4%;
width: 92%;
padding: 10px;
flex-wrap: wrap;
}
.logo-bar img {
height: 40px;
}
button, .button {
padding: 10px 14px;
font-size: 15px;
}
.card {
padding: 20px;
}
}
/* Sehr schmale Telefone: noch kompakter */
@media (max-width: 400px) {
.navbar {
left: 2%;
width: 96%;
padding: 8px;
gap: 6px;
}
.logo-bar img {
height: 32px;
}
.nav-actions {
gap: 6px;
}
.nav-actions .button {
padding: 8px 12px;
font-size: 14px;
}
}
/*
* Phone-Portrait: Navbar-Aktionen hinter dem
* Hamburger-Button als Dropdown anzeigen.
*/
@media (orientation: portrait) and (max-width: 600px) {
body {
/* Navbar ist hier einzeilig */
padding-top: 80px;
min-height: calc(100vh - 80px);
}
button.nav-toggle {
display: inline-flex;
}
/* ".navbar .button" setzt margin: 0 – deshalb
* höher spezifisch ans Ziel: Button nach rechts */
.navbar .nav-toggle {
margin-left: auto;
}
.nav-actions {
display: none;
position: absolute;
top: 100%;
right: 0;
margin: 8px 0 0;
padding: 8px;
min-width: 200px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
flex-direction: column;
align-items: stretch;
}
.nav-actions.open {
display: flex;
}
/* Alle Panel-Buttons gleich breit, Text zentriert */
.nav-actions .button {
text-align: center;
}
/* Sprach-Button liegt im .lang-menu-Wrapper */
.nav-actions .lang-menu .button {
display: block;
width: 100%;
box-sizing: border-box;
}
:root.dark .nav-actions {
background: #242424;
}
}
/*
* Sicherheit: Hamburger im Landscape grundsätzlich
* ausblenden, falls orientation falsch gemeldet wird.
*/
@media (orientation: landscape) {
button.nav-toggle {
display: none;
}
}
/*
* Phone-Landscape: schmaler, hoher Karteninhalt –
* einzeilige Navbar (~106px) freihalten.
*/
@media (orientation: landscape) and (max-height: 500px) {
body {
padding-top: 120px;
/* min-height gilt für die Content-Box: Navbar-Freiraum
* abziehen, sonst sitzt die Karte unter der Mitte */
min-height: calc(100vh - 120px);
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/*
* Dark-Theme über die Klasse "dark" am html-Element.
* JS setzt sie anhand der gespeicherten Wahl oder der
* System-Einstellung (siehe detectTheme).
*/
:root.dark body {
background: #181818;
color: #eee;
}
:root.dark .navbar {
background: rgba(36, 36, 36, 0.72);
}
:root.dark .card {
background: rgba(36, 36, 36, 0.72);
}
:root.dark .lang-dropdown {
background: #242424;
}
:root.dark .lang-dropdown button:hover {
background: #303030;
}
:root.dark .lang-dropdown button.active {
background: #1a3a5a;
}
:root.dark p {
color: #bbb;
}
:root.dark .secondary {
background: #444;
color: #eee;
}
:root.dark .status,
:root.dark .download {
background: #303030;
}
:root.dark .spinner {
border-color: #555;
border-top-color: #ddd;
}
:root.dark .form-group label {
color: #ddd;
}
:root.dark .form-group input,
:root.dark .form-group textarea {
background: #303030;
border-color: #555;
color: #eee;
}
:root.dark .success-message {
background: #1a3a5a;
color: #99ccff;
}
:root.dark .adhoc-result {
background: #1c3527;
color: #7ee2a8;
}
:root.dark .logo-bar img {
filter: url(#logo-dark-tint);
}
:root.dark .adhoc-link a {
color: #99ccff;
}
</style>
</head>
<body>
<svg width="0" height="0" style="position: absolute;" aria-hidden="true">
<filter id="logo-dark-tint">
<feColorMatrix type="matrix" values="0 0 0 0 0.8667
0 0 0 0 0.8431
0 0 0 0 0.8235
0 0 0 1 0"/>
</filter>
</svg>
<nav class="navbar">
<div id="logoBar" class="logo-bar hidden">
<img id="logoImage" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Logo">
</div>
<button id="navToggle" class="button secondary nav-toggle" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="navActions" aria-label="Menu">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 7h16M4 12h16M4 17h16"/>
</svg>
</button>
<div id="navActions" class="nav-actions">
<a class="button secondary" href="https://www.bnvb-ingenieure.de/impressum" target="_blank" rel="noopener noreferrer" data-i18n="impressum">
Impressum
</a>
<a class="button secondary" href="https://git.bnvb.eu/stefan.tollkuehn/jitsi-launch" target="_blank" rel="noopener noreferrer" data-i18n="source">
Source
</a>
<div id="langMenu" class="lang-menu">
<button id="langButton" class="button secondary" type="button" aria-haspopup="true" aria-expanded="false">
<img id="langFlag" class="lang-flag" src="assets/uk.svg" alt="">
<span id="langLabel">English</span>
</button>
<div id="langDropdown" class="lang-dropdown">
<button type="button" data-lang="en"><img class="lang-flag" src="assets/uk.svg" alt="">English</button>
<button type="button" data-lang="de"><img class="lang-flag" src="assets/de.svg" alt="">Deutsch</button>
<button type="button" data-lang="fr"><img class="lang-flag" src="assets/fr.svg" alt="">Français</button>
<button type="button" data-lang="zh"><img class="lang-flag" src="assets/cn.svg" alt="">中文</button>
</div>
</div>
<button id="themeButton" class="button secondary" type="button" aria-label="Toggle dark mode">
<svg id="themeIconMoon" class="theme-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>
</svg>
<svg id="themeIconSun" class="theme-icon hidden" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"/>
<path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>
</svg>
</button>
</div>
</nav>
<div class="container">
<div class="card">
<!-- NORMALER LAUNCHER -->
<div id="launcher" class="hidden">
<h1 data-i18n="launcher.title">Jitsi Meeting</h1>
<p>
<span data-i18n="launcher.meetingPrefix">
The meeting
</span>
<span class="room">
<strong id="roomName"></strong>
</span>
<span data-i18n="launcher.meetingSuffix">
will be opened.
</span>
</p>
<button id="openButton" class="button primary" type="button" data-i18n="launcher.openButton">
Open meeting
</button>
<button id="createNewFromLauncherButton" class="button secondary hidden" type="button" data-i18n="creator.createNewButton">
Create New Meeting
</button>
</div>
<!-- STARTVORGANG -->
<div id="starting" class="hidden">
<h1 data-i18n="starting.title">
Starting Jitsi Meet …
</h1>
<div class="status">
<span class="spinner"></span>
<span data-i18n="starting.status">
The Jitsi Meet desktop client is being opened.
</span>
</div>
</div>
<!-- FALLBACK -->
<div id="fallback" class="hidden">
<h1 data-i18n="fallback.title">
Jitsi Meet could not be opened
</h1>
<p data-i18n="fallback.description">
The Jitsi Meet desktop client may not have been started.
</p>
<button id="retryButton" class="button primary" type="button" data-i18n="fallback.retryButton">
Try again
</button>
<!-- Windows -->
<div id="windowsDownload" class="download hidden">
<h2 data-i18n="downloads.windows.title">
Jitsi Meet for Microsoft Windows
</h2>
<p data-i18n="downloads.windows.description">
Install the Jitsi Meet desktop client and then try opening the meeting again.
</p>
<a id="windowsLink" class="button secondary" target="_blank" rel="noopener noreferrer" data-i18n="downloads.windows.button">
Download Jitsi for Windows
</a>
</div>
<!-- macOS -->
<div id="macosDownload" class="download hidden">
<h2 data-i18n="downloads.macos.title">
Jitsi Meet for Apple macOS
</h2>
<p data-i18n="downloads.macos.description">
Install the Jitsi Meet desktop client and then try opening the meeting again.
</p>
<a id="macosLink" class="button secondary" target="_blank" rel="noopener noreferrer" data-i18n="downloads.macos.button">
Download Jitsi for macOS
</a>
</div>
<!-- Linux -->
<div id="linuxDownload" class="download hidden">
<h2 data-i18n="downloads.linux.title">
Jitsi Meet for Linux
</h2>
<p data-i18n="downloads.linux.description">
Install the Jitsi Meet desktop client and then try opening the meeting again.
</p>
<a id="linuxLink" class="button secondary" target="_blank" rel="noopener noreferrer" data-i18n="downloads.linux.button">
Download Jitsi for Linux
</a>
</div>
<!-- iOS -->
<div id="iosDownload" class="download hidden">
<h2 data-i18n="downloads.ios.title">
Jitsi Meet for iOS
</h2>
<p data-i18n="downloads.ios.description">
Install the Jitsi Meet app and then try opening the meeting again.
</p>
<a id="iosLink" class="button secondary" target="_blank" rel="noopener noreferrer" data-i18n="downloads.ios.button">
Download Jitsi for iOS
</a>
</div>
<!-- unknown -->
<div id="otherDownload" class="download hidden">
<h2 data-i18n="downloads.other.title">
Jitsi Meet Desktop
</h2>
<p data-i18n="downloads.other.description">
No desktop download is configured for your operating system.
</p>
</div>
<!-- Browser -->
<div class="download">
<h2 data-i18n="browser.title">
Join in browser
</h2>
<p data-i18n="browser.description">
You can also join the meeting directly in your browser.
</p>
<a id="browserLink" class="button secondary" data-i18n="browser.button">
Join in browser
</a>
</div>
</div>
<!-- FEHLER -->
<div id="invalidRoom" class="hidden">
<h1 id="missingRoomTitle" data-i18n="error.title">
No meeting specified
</h1>
<div id="missingRoomError" class="error">
<span data-i18n="error.description">
Please specify a meeting using the
</span>
<code>?room=...</code>
<span data-i18n="error.descriptionSuffix">
parameter.
</span>
</div>
<h1 id="invalidNameTitle" class="hidden" data-i18n="error.invalidTitle">
Invalid meeting name
</h1>
<div id="invalidNameError" class="error hidden">
<span data-i18n="error.invalidDescription">
Only letters, numbers and hyphens are allowed:
</span>
<br>
<span class="room">
<strong id="invalidRoomName"></strong>
</span>
</div>
<div style="margin-top: 24px;">
<button id="createNewMeetingButton" class="button primary hidden" type="button" data-i18n="creator.createNewButton">
Create New Meeting
</button>
</div>
</div>
<!-- MEETING CREATOR -->
<div id="meetingCreator" class="hidden">
<h1 data-i18n="creator.title">Create New Meeting</h1>
<div class="form-group">
<label for="meetingName" data-i18n="creator.meetingNameLabel">Meeting Name</label>
<input type="text" id="meetingName" placeholder="My Meeting" />
</div>
<div class="form-group">
<label for="meetingDescription" data-i18n="creator.meetingDescriptionLabel">Description</label>
<textarea id="meetingDescription" placeholder="Meeting description (optional)"></textarea>
</div>
<div class="form-group">
<label for="meetingStart" data-i18n="creator.meetingStartLabel">Start Date & Time</label>
<input type="datetime-local" id="meetingStart" step="900" />
</div>
<div class="form-group">
<label for="meetingEnd" data-i18n="creator.meetingEndLabel">End Date & Time</label>
<input type="datetime-local" id="meetingEnd" step="900" />
</div>
<div class="form-group">
<label for="meetingOrganizer" data-i18n="creator.meetingOrganizerLabel">Organizer Email</label>
<input type="email" id="meetingOrganizer" placeholder="organizer@example.com" />
</div>
<div class="form-actions">
<button id="createMeetingButton" class="button primary" type="button" data-i18n="creator.createButton">
Create Meeting
</button>
<button id="createAdhocButton" class="button secondary" type="button" data-i18n="creator.adhocButton">
Create Adhoc Link
</button>
<button id="cancelCreateButton" class="button secondary" type="button" data-i18n="creator.cancelButton">
Cancel
</button>
</div>
<div id="adhocResult" class="adhoc-result hidden">
<span data-i18n="creator.adhocLinkLabel">Your adhoc meeting link:</span>
<div class="adhoc-link">
<a id="adhocLink" target="_blank" rel="noopener noreferrer"></a>
<button id="copyAdhocLinkButton" class="button secondary" type="button" data-i18n="creator.copyLinkButton">Copy Link</button>
</div>
</div>
<div id="icsSuccess" class="success-message hidden">
<span data-i18n="creator.successMessage">Meeting created! Download your calendar file and open it to import the meeting into your personal calendar.</span>
<a id="downloadIcsLink" class="button primary" style="margin-top: 12px; display: inline-block;" data-i18n="creator.downloadButton">Download ICS File</a>
</div>
</div>
</div>
</div>
<script src="assets/ua-parser.min.js"></script>
<script src="assets/launch.js"></script>
</body>
</html>